mirror of
https://github.com/LMMS/lmms.git
synced 2026-02-05 04:04:10 -05:00
638 lines
13 KiB
C++
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();
|
|
}
|