Files
lmms/plugins/LadspaEffect/caps/Amp.cc

638 lines
13 KiB
C++

/*
Amp.cc
Copyright 2003-7
Tim Goetze <tim@quitte.de>
David Yeh <dtyeh@ccrma.stanford.edu> (Tone Stack in TS models)
http://quitte.de/dsp/
Tube amplifier models
*/
/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA or point your web browser to http://www.gnu.org.
*/
#include "basics.h"
#include <stdio.h>
#include "Amp.h"
#include "Descriptor.h"
void
AmpStub::init (bool adjust_downsampler)
{
dc_blocker.set_f (10. / fs);
/* going a bit lower than nominal with fc */
double f = .7 * M_PI / OVERSAMPLE;
/* construct the upsampler filter kernel */
DSP::sinc (f, up.c, FIR_SIZE);
DSP::kaiser<DSP::apply_window> (up.c, FIR_SIZE, 6.4);
/* copy upsampler filter kernel for downsampler, make sum */
double s = 0;
for (int i = 0; i < up.n; ++i)
down.c[i] = up.c[i],
s += up.c[i];
s = 1 / s;
/* scale downsampler kernel for unity gain + correction for transfer */
double t = adjust_downsampler ?
s / max (fabs (tube.clip[0].value), fabs (tube.clip[1].value)) : s;
for (int i = 0; i < down.n; ++i)
down.c[i] *= t;
/* scale upsampler kernel for unity gain */
s *= OVERSAMPLE;
for (int i = 0; i < up.n; ++i)
up.c[i] *= s;
}
/* //////////////////////////////////////////////////////////////////////// */
void
AmpIII::init()
{
this->AmpStub::init (false);
/* need to filter out dc before the power amp stage, which is running at
* the oversampled rate */
dc_blocker.set_f (10. / (fs * OVERSAMPLE));
DSP::RBJ::LoShelve (200 / fs, .2, -3, filter.a, filter.b);
}
template <sample_func_t F, int OVERSAMPLE>
void
AmpIII::one_cycle (int frames)
{
sample_t * s = ports[0];
sample_t gain = getport(1);
sample_t temp = getport(2) * tube.scale;
drive = getport(3) * .5;
i_drive = 1 / (1 - drive);
sample_t * d = ports[4];
*ports[5] = OVERSAMPLE;
double g = current.g;
current.g = max (gain < 1 ? gain : exp2 (gain - 1), .000001);
current.g *= tube.scale / fabs (tube.transfer (temp));
/* recursive fade to prevent zipper noise from the 'gain' knob */
if (g == 0) g = current.g;
double one_over_n = frames > 0 ? 1. / frames : 1;
double gf = pow (current.g / g, one_over_n);
for (int i = 0; i < frames; ++i)
{
register sample_t a = s[i];
a = g * tube.transfer (a * temp);
a = filter.process (a + normal);
a = tube.transfer_clip (up.upsample (a));
a = power_transfer (dc_blocker.process (a));
a = down.process (a);
for (int o = 1; o < OVERSAMPLE; ++o)
down.store (
power_transfer (
dc_blocker.process (
normal + tube.transfer_clip (up.pad (o)))));
F (d, i, a, adding_gain);
g *= gf;
}
current.g = g;
}
/* //////////////////////////////////////////////////////////////////////// */
PortInfo
AmpIII::port_info [] =
{
{
"in",
INPUT | AUDIO,
{BOUNDED, -1, 1}
}, {
"gain",
INPUT | CONTROL,
{BOUNDED | DEFAULT_1, 0, 10}
}, {
"temperature",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MID, 0.005, 1}
}, {
"drive",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MAX, 0.0001, 1} /* ^2 gives the nice drive */
}, {
"out",
OUTPUT | AUDIO,
{0}
}, {
"latency",
OUTPUT | CONTROL,
{0}
}
};
template <> void
Descriptor<AmpIII>::setup()
{
UniqueID = 1786;
Label = "AmpIII";
Properties = HARD_RT;
Name = CAPS "AmpIII - Tube amp";
Maker = "Tim Goetze <tim@quitte.de>";
Copyright = "GPL, 2002-7";
/* fill port info and vtable */
autogen();
}
/* //////////////////////////////////////////////////////////////////////// */
void
AmpIV::init()
{
this->AmpStub::init (false);
/* need to filter out dc before the power amp stage, which is running at
* the oversampled rate */
dc_blocker.set_f (10. / (fs * OVERSAMPLE));
tone.init (fs);
}
template <sample_func_t F, int OVERSAMPLE>
void
AmpIV::one_cycle (int frames)
{
double one_over_n = frames > 0 ? 1. / frames : 1;
sample_t * s = ports[0];
sample_t gain = getport(1);
sample_t temp = getport(2) * tube.scale;
tone.start_cycle (ports + 3, one_over_n);
drive = getport(7) * .5;
i_drive = 1 / (1 - drive);
sample_t * d = ports[8];
*ports[9] = OVERSAMPLE;
double g = current.g;
current.g = max (gain < 1 ? gain : exp2 (gain - 1), .000001);
current.g *= tube.scale / fabs (tube.transfer (temp));
/* recursive fade to prevent zipper noise from the 'gain' knob */
if (g == 0) g = current.g;
double gf = pow (current.g / g, one_over_n);
for (int i = 0; i < frames; ++i)
{
register sample_t a = s[i] + normal;
a = g * tube.transfer (a * temp);
a = tone.process (a);
a = tube.transfer_clip (up.upsample (a));
a = power_transfer (dc_blocker.process (a));
a = down.process (a);
for (int o = 1; o < OVERSAMPLE; ++o)
down.store (
power_transfer (
dc_blocker.process (
normal + tube.transfer_clip (up.pad (o)))));
F (d, i, a, adding_gain);
g *= gf;
}
current.g = g;
}
/* //////////////////////////////////////////////////////////////////////// */
PortInfo
AmpIV::port_info [] =
{
{
"in",
INPUT | AUDIO,
{BOUNDED, -1, 1}
}, {
"gain",
INPUT | CONTROL,
{BOUNDED | DEFAULT_1, 0, 10}
}, {
"temperature",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MID, 0.005, 1}
}, {
"bass",
INPUT | CONTROL,
{BOUNDED | DEFAULT_0, -20, 20}
}, {
"mid",
INPUT | CONTROL,
{BOUNDED | DEFAULT_0, -20, 20}
}, {
"treble",
INPUT | CONTROL,
{BOUNDED | DEFAULT_0, -20, 20}
}, {
"hi",
INPUT | CONTROL,
{BOUNDED | DEFAULT_0, -20, 20}
}, {
"drive",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MAX, 0.0001, 1} /* ^2 gives the nice drive */
}, {
"out",
OUTPUT | AUDIO,
{0}
}, {
"latency",
OUTPUT | CONTROL,
{0}
}
};
template <> void
Descriptor<AmpIV>::setup()
{
UniqueID = 1794;
Label = "AmpIV";
Properties = HARD_RT;
Name = CAPS "AmpIV - Tube amp + tone controls";
Maker = "Tim Goetze <tim@quitte.de>";
Copyright = "GPL, 2002-7";
/* fill port info and vtable */
autogen();
}
/* //////////////////////////////////////////////////////////////////////// */
void
AmpV::init()
{
this->AmpStub::init (false);
/* need to filter out dc before the power amp stage, which is running at
* the oversampled rate */
dc_blocker.set_f (10. / (fs * OVERSAMPLE));
DSP::RBJ::LoShelve (210. / fs, .2, -1, filter[0].a, filter[0].b);
DSP::RBJ::LoShelve (4200. / fs, 1.2, +6, filter[1].a, filter[1].b);
DSP::RBJ::LoShelve (420. / fs, .2, +2, filter[2].a, filter[2].b);
/* power supply cap */
for (int i = 0; i < 2; ++i)
DSP::RBJ::LP (10. / fs, .3, power_cap[i].a, power_cap[i].b);
}
template <sample_func_t F, int OVERSAMPLE>
void
AmpV::one_cycle (int frames)
{
sample_t * s = ports[0];
sample_t gain = getport(1);
if (*ports[2] != cut)
{
cut = getport(2);
DSP::RBJ::LoShelve (210. / fs, .2, cut, filter[0].a, filter[0].b);
}
if (*ports[3] != tone)
{
tone = getport(3);
double f = tone * tone * 8400 + 420;
double q = tone * .4 + .2;
double db = tone * 2 + 2;
DSP::RBJ::LoShelve (f / fs, q, db, filter[2].a, filter[2].b);
}
drive = getport(4) * .5;
i_drive = 1 / (1 - drive);
#define MAX_WATTS port_info[5].range.UpperBound
sample_t sag = (MAX_WATTS - getport(5)) / MAX_WATTS;
sag = .6 * sag * sag;
sample_t * d = ports[6];
*ports[7] = OVERSAMPLE;
double g = current.g;
current.g = max (gain < 1 ? gain : pow (20, gain - 1), .000001);
#if 0
if (++_turn & 127) == 0)
fprintf (stderr, "supply = %.3f sag = %.3f\n", supply, sag);
#endif
if (g == 0) g = current.g;
/* recursive fade to prevent zipper noise from the 'gain' knob */
double one_over_n = frames > 0 ? 1. / frames : 1;
double gf = pow (current.g / g, one_over_n);
for (int i = 0; i < frames; ++i)
{
register sample_t a = s[i];
register sample_t v = 3 - supply;
/* alternative curve: v = v * v * .1 + .1; */
v = v * v * .06 + .46;
a = filter[0].process (a + normal);
if (0)
a = filter[2].process (a);
a = g * (a + supply * .001);
a = v * tube.transfer_clip (up.upsample (a));
a = power_transfer (dc_blocker.process (a));
a = down.process (a);
a = filter[1].process (a - normal);
if (1)
a = filter[2].process (a + normal);
{
for (int o = 1; o < OVERSAMPLE; ++o)
down.store (
power_transfer (
dc_blocker.process (
normal + tube.transfer_clip (
up.pad (o)))));
}
F (d, i, a, adding_gain);
/* integrate for an approximation of cumulative output power */
supply += sag * fabs (a) + normal;
/* filter integrated power consumption */
for (int j = 0; j < 2; ++j)
supply = 0.9 * (power_cap[j].process (supply));
g *= gf;
normal = -normal;
}
current.g = g;
}
/* //////////////////////////////////////////////////////////////////////// */
PortInfo
AmpV::port_info [] =
{
{
"in",
INPUT | AUDIO,
{BOUNDED, -1, 1}
}, {
"gain",
INPUT | CONTROL,
{BOUNDED | DEFAULT_1, 0, 3}
}, {
"bass",
INPUT | CONTROL,
{BOUNDED | DEFAULT_0, -9, 9}
}, {
"tone",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MIN, 0, 1}
}, {
"drive",
INPUT | CONTROL,
{BOUNDED | DEFAULT_HIGH, 0.0001, 1} /* ^2 gives the nice drive */
}, {
"watts",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MID, 5, 150}
}, {
"out",
OUTPUT | AUDIO,
{0}
}, {
"latency",
OUTPUT | CONTROL,
{0}
}
};
template <> void
Descriptor<AmpV>::setup()
{
UniqueID = 2587;
Label = "AmpV";
Properties = HARD_RT;
Name = CAPS "AmpV - Tube amp";
Maker = "Tim Goetze <tim@quitte.de>";
Copyright = "GPL, 2002-7";
/* fill port info and vtable */
autogen();
}
/* //////////////////////////////////////////////////////////////////////// */
void
AmpVTS::init()
{
this->AmpStub::init (false);
/* need to filter out dc before the power amp stage, which is running at
* the oversampled rate */
dc_blocker.set_f (10. / (fs * OVERSAMPLE));
/* power supply capacitance */
for (int i = 0; i < 2; ++i)
DSP::RBJ::LP (10. / fs, .3, power_cap[i].a, power_cap[i].b);
tonestack.init (fs);
}
template <sample_func_t F, int OVERSAMPLE>
void
AmpVTS::one_cycle (int frames)
{
sample_t * s = ports[0];
tonestack.start_cycle (ports + 1, 2);
sample_t gain = getport(2);
drive = getport(6) * .5;
i_drive = 1 / (1 - drive);
sample_t sag = 1 - max (0.0001, min (1, getport(7)));
sag = .6 * sag * sag; /* map to log space makes slider better */
sample_t * d = ports[8];
*ports[9] = OVERSAMPLE;
double g = current.g;
if (gain < 1)
current.g = max (gain, .001);
else
{
gain -= 1;
gain *= gain;
current.g = pow (10, gain);
}
/* recursive fade to prevent zipper noise from the 'gain' knob */
double one_over_n = frames > 0 ? 1. / frames : 1;
double gf = pow (current.g / g, one_over_n);
for (int i = 0; i < frames; ++i)
{
register double a = s[i];
register double v = 3 - supply;
v = v * v * .06 + .46;
a = tube.transfer (a);
a = tonestack.process (a + normal);
a = g * (a + supply * .001);
a = v * tube.transfer_clip (up.upsample (a));
a = power_transfer (dc_blocker.process (a));
a = down.process (a);
{
for (int o = 1; o < OVERSAMPLE; ++o)
down.store (
power_transfer (
dc_blocker.process (
normal + tube.transfer_clip (
up.pad (o)))));
}
F (d, i, a, adding_gain);
/* integrate for an approximation of cumulative output power */
supply += sag * fabs (a) + normal;
/* filter integrated power consumption */
for (int j = 0; j < 2; ++j)
supply = 0.9 * (power_cap[j].process (supply + normal));
g *= gf;
normal = -normal;
}
current.g = g;
}
/* //////////////////////////////////////////////////////////////////////// */
PortInfo
AmpVTS::port_info [] =
{
{
"in",
INPUT | AUDIO,
{BOUNDED, -1, 1}
}, {
"model",
INPUT | CONTROL,
{BOUNDED | DEFAULT_0 | INTEGER, 0, 5} /* no way to set dyn at compile t */
}, {
"gain",
INPUT | CONTROL,
{BOUNDED | DEFAULT_HIGH, 0, 3}
}, {
"bass",
INPUT | CONTROL,
{BOUNDED | DEFAULT_MID, 0, 1}
}, {
"mid",
INPUT | CONTROL,
{BOUNDED | DEFAULT_1, 0, 1}
}, {
"treble",
INPUT | CONTROL,
{BOUNDED | DEFAULT_HIGH, 0, 1}
}, {
"drive",
INPUT | CONTROL,
{BOUNDED | DEFAULT_LOW, 0.0001, 1}
}, {
"watts",
INPUT | CONTROL,
{BOUNDED | DEFAULT_HIGH, 0.0001, 1}
}, {
"out",
OUTPUT | AUDIO,
{0}
}, {
"latency",
OUTPUT | CONTROL,
{0}
}
};
template <> void
Descriptor<AmpVTS>::setup()
{
UniqueID = 2592;
Label = "AmpVTS";
Properties = HARD_RT;
Name = CAPS "AmpVTS - Tube amp + Tone stack";
Maker = "David Yeh <dtyeh@ccrma.stanford.edu> & Tim Goetze <tim@quitte.de>";
Copyright = "GPL, 2002-7";
/* fill port info and vtable */
autogen();
}