#include "types.h"
#include "synth.h"
#include <math.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#define BUG_V2_FM_RANGE 1
#define DEBUGSCOPES 0
#define COVERAGE 0
#if DEBUGSCOPES
#include "scope.h"
#define DEBUG_PLOT_OPEN(which, title, rate, w, h) scopeOpen((which), (title), (rate), (w), (h))
#define DEBUG_PLOT_VAL(which, value) do { float t=value; scopeSubmit((which), &t, 1); } while(0)
#define DEBUG_PLOT(which, data, nsamples) scopeSubmit((which), (data), (nsamples))
#define DEBUG_PLOT_STRIDED(which, data, stride, nsamples) scopeSubmitStrided((which), (data), (stride), (nsamples))
#define DEBUG_PLOT_UPDATE() scopeUpdateAll()
#else
#define DEBUG_PLOT_OPEN(which, title, rate, w, h)
#define DEBUG_PLOT_VAL(which, value)
#define DEBUG_PLOT(which, data, nsamples)
#define DEBUG_PLOT_STRIDED(which, data, stride, nsamples)
#define DEBUG_PLOT_UPDATE()
#endif
#define DEBUG_PLOT_CHAN(which, ch) ((unsigned char *)(which)+(ch))
#define DEBUG_PLOT_STEREO(which, data, nsamples) \
DEBUG_PLOT_STRIDED(DEBUG_PLOT_CHAN(which, 0), &(data)->l, 2, (nsamples)); \
DEBUG_PLOT_STRIDED(DEBUG_PLOT_CHAN(which, 1), &(data)->r, 2, (nsamples))
#if COVERAGE
#include <stdio.h>
static const char *code_coverage[500];
#define COVER(desc) code_coverage[__COUNTER__] = (desc)
static int synthGetNumCoverage();
#else
#define COVER(desc)
#endif
static const float fclowest = 1.220703125e-4f; static const float fcpi_2 = 1.5707963267948966192313216916398f;
static const float fcpi = 3.1415926535897932384626433832795f;
static const float fc1p5pi = 4.7123889803846898576939650749193f;
static const float fc2pi = 6.28318530717958647692528676655901f;
static const float fc32bit = 2147483648.0f;
static const float fcoscbase = 261.6255653f; static const float fcsrbase = 44100.0f; static const float fcboostfreq = 150.0f; static const float fcframebase = 128.0f; static const float fcdcflt = 126.0f;
static const float fccfframe = 11.0f;
static const float fcfmmax = 2.0f;
static const float fcattackmul = -0.09375f; static const float fcattackadd = 7.0f;
static const float fcsusmul = 0.0019375f;
static const float fcgain = 0.6f;
static const float fcgainh = 0.6f;
static const float fcmdlfomul = 1973915.49f;
static const float fccpdfalloff = 0.9998f;
static const float fcdcoffset = 3.814697265625e-6f;
#define COUNTOF(x) (int)(sizeof(x)/sizeof(*(x)))
union FloatBits
{
float f;
uint32_t u;
};
static float bits2float(uint32_t u)
{
FloatBits x;
x.u = u;
return x.f;
}
static float fastatan(float x)
{
float sign = 1.0f;
if (x < 0.0f)
{
sign = -1.0f;
x = -x;
}
static const float coeffs[2][6] = {
{ 1.0f, 0.43157974f, 1.0f, 0.05831938f, 0.76443945f, 0.0f },
{ -0.431597974f, -1.0f, 0.05831938f, 1.0f, 0.76443945f, 1.57079633f },
};
const float *c = coeffs[x >= 1.0f]; float x2 = x*x;
float r = (c[1]*x2 + c[0])*x / ((c[4]*x2 + c[3])*x2 + c[2]) + c[5];
return r * sign;
}
static float fastsin(float x)
{
float x2 = x*x;
return (((-0.00018542f*x2 + 0.0083143f)*x2 - 0.16666f)*x2 + 1.0f) * x;
}
static float fastsinrc(float x)
{
x = fmodf(x, fc2pi);
#if !BUG_V2_FM_RANGE
if (x < 0.0f)
x += fc2pi;
#endif
if (x > fc1p5pi) x -= fc2pi; else if (x > fcpi_2) x = fcpi - x;
return fastsin(x);
}
static float calcfreq(float x)
{
return powf(2.0f, (x - 1.0f)*10.0f);
}
static float calcfreq2(float x)
{
return powf(2.0f, (x - 1.0f)*fccfframe);
}
static inline float sqr(float x)
{
return x*x;
}
template<typename T>
static inline T min(T a, T b)
{
return a < b ? a : b;
}
template<typename T>
static inline T max(T a, T b)
{
return a > b ? a : b;
}
template<typename T>
static inline T clamp(T x, T min, T max)
{
return (x < min) ? min : (x > max) ? max : x;
}
static inline uint32_t urandom(uint32_t *seed)
{
*seed = *seed * 196314165 + 907633515;
return *seed;
}
static inline float frandom(uint32_t *seed)
{
uint32_t bits = urandom(seed); float f = bits2float((bits >> 9) | 0x40000000); return f - 3.0f; }
static inline float utof23(uint32_t x)
{
float f = bits2float((x >> 9) | 0x3f800000); return f - 1.0f;
}
static inline uint32_t ftou32(float v)
{
return 2u * (int)(v * fc32bit);
}
static inline float lerp(float a, float b, float t)
{
return a + t * (b-a);
}
#include <stdarg.h>
#include <stdio.h>
union StereoSample
{
struct
{
float l, r;
};
float ch[2];
};
struct V2LRC
{
float l, b;
void init()
{
l = b = 0.0f;
}
float step(float in, float freq, float reso)
{
l += freq * b;
float h = in - b*reso - l;
b += freq * h;
return h;
}
float step_2x(float in, float freq, float reso)
{
in += fcdcoffset;
l += freq * b - fcdcoffset; b += freq * (in - b*reso - l);
l += freq * b;
float h = in - b*reso - l;
b += freq * h;
return h;
}
};
struct V2Moog
{
float b[5];
void init()
{
b[0] = b[1] = b[2] = b[3] = b[4] = 0.0f;
}
float step(float realin, float f, float p, float q)
{
float in = realin + fcdcoffset; float t1, t2, t3, b4;
in -= q * b[4]; t1 = b[1]; b[1] = (in + b[0]) * p - b[1] * f;
t2 = b[2]; b[2] = (t1 + b[1]) * p - b[2] * f;
t3 = b[3]; b[3] = (t2 + b[2]) * p - b[3] * f;
b4 = (t3 + b[3]) * p - b[4] * f;
b4 -= b4*b4*b4 * (1.0f/6.0f); b4 -= fcdcoffset; b[4] = b4 - fcdcoffset;
b[0] = realin;
return b4;
}
};
struct V2DCF
{
float xm1; float ym1;
void init()
{
xm1 = ym1 = 0.0f;
}
float step(float in, float R)
{
float y = (fcdcoffset + R*ym1 - xm1 + in) - fcdcoffset;
xm1 = in;
ym1 = y;
return y;
}
};
class V2Delay
{
uint32_t pos, len;
float *buf;
public:
V2Delay()
: pos(0), len(0), buf(0)
{
}
void init(float *buf, uint32_t len)
{
this->buf = buf;
this->len = len;
reset();
}
template<uint32_t N>
void init(float (&buf)[N])
{
init(buf, N);
}
void reset()
{
memset(buf, 0, sizeof(*buf)*len);
pos = 0;
}
inline float fetch() const
{
return buf[pos];
}
inline void feed(float v)
{
buf[pos] = v;
if (++pos == len)
pos = 0;
}
};
struct V2Instance
{
static const int MAX_FRAME_SIZE = 280;
float SRfcsamplesperms;
float SRfcobasefrq;
float SRfclinfreq;
float SRfcBoostCos, SRfcBoostSin;
float SRfcdcfilter;
int SRcFrameSize;
float SRfciframe;
float vcebuf[MAX_FRAME_SIZE];
float vcebuf2[MAX_FRAME_SIZE];
StereoSample levelbuf[MAX_FRAME_SIZE]; StereoSample chanbuf[MAX_FRAME_SIZE];
float aux1buf[MAX_FRAME_SIZE];
float aux2buf[MAX_FRAME_SIZE];
StereoSample mixbuf[MAX_FRAME_SIZE];
StereoSample auxabuf[MAX_FRAME_SIZE];
StereoSample auxbbuf[MAX_FRAME_SIZE];
void calcNewSampleRate(int samplerate)
{
float sr = (float)samplerate;
SRfcsamplesperms = sr / 1000.0f;
SRfcobasefrq = (fcoscbase * fc32bit) / sr;
SRfclinfreq = fcsrbase / sr;
SRfcdcfilter = 1.0f - fcdcflt / sr;
SRcFrameSize = (int)(fcframebase * sr / fcsrbase + 0.5f);
SRfciframe = 1.0f / (float)SRcFrameSize;
assert(SRcFrameSize <= MAX_FRAME_SIZE);
float boost = (fcboostfreq * fc2pi) / sr;
SRfcBoostCos = cos(boost);
SRfcBoostSin = sin(boost);
}
};
struct syVOsc
{
float mode; float ring;
float pitch;
float detune;
float color;
float gain;
};
struct V2Osc
{
enum Mode
{
OSC_OFF = 0,
OSC_TRI_SAW = 1,
OSC_PULSE = 2,
OSC_SIN = 3,
OSC_NOISE = 4,
OSC_FM_SIN = 5,
OSC_AUXA = 6,
OSC_AUXB = 7,
};
int mode; bool ring; uint32_t cnt; int freq; uint32_t brpt; float nffrq, nfres; uint32_t nseed; float gain; V2LRC nf; float note;
float pitch;
V2Instance *inst;
void init(V2Instance *instance, int idx)
{
static const uint32_t seeds[] = { 0xdeadbeefu, 0xbaadf00du, 0xd3adc0deu };
assert(idx < COUNTOF(seeds));
cnt = 0;
nf.init();
nseed = seeds[idx];
inst = instance;
}
void noteOn()
{
chgPitch();
}
void chgPitch()
{
nffrq = inst->SRfclinfreq * calcfreq((pitch + 64.0f) / 128.0f);
freq = (int)(inst->SRfcobasefrq * powf(2.0f, (pitch + note - 60.0f) / 12.0f));
}
void set(const syVOsc *para)
{
mode = (int)para->mode;
ring = (((int)para->ring) & 1) != 0;
pitch = (para->pitch - 64.0f) + (para->detune - 64.0f) / 128.0f;
chgPitch();
gain = para->gain / 128.0f;
float col = para->color / 128.0f;
brpt = ftou32(col);
nfres = 1.0f - sqrtf(col);
}
void render(float *dest, int nsamples)
{
switch (mode & 7)
{
case OSC_OFF: break;
case OSC_TRI_SAW: renderTriSaw(dest, nsamples); break;
case OSC_PULSE: renderPulse(dest, nsamples); break;
case OSC_SIN: renderSin(dest, nsamples); break;
case OSC_NOISE: renderNoise(dest, nsamples); break;
case OSC_FM_SIN: renderFMSin(dest, nsamples); break;
case OSC_AUXA: renderAux(dest, inst->auxabuf, nsamples); break;
case OSC_AUXB: renderAux(dest, inst->auxbbuf, nsamples); break;
}
DEBUG_PLOT(this, dest, nsamples);
}
private:
inline void output(float *dest, float x)
{
if (ring)
*dest *= x;
else
*dest += x;
}
enum OSMTransitionCode {
OSMTC_DOWN = 0, OSMTC_UP_DOWN = 2, OSMTC_UP = 3, OSMTC_DOWN_UP_DOWN = 4, OSMTC_DOWN_UP = 5, OSMTC_UP_DOWN_UP = 7 };
inline uint32_t osm_init() {
return (cnt - freq) < brpt ? 3 : 0;
}
inline uint32_t osm_tick(uint32_t &state) {
state = ((state << 1) | (cnt < brpt)) & 3;
uint32_t transition_code = state | (cnt < (uint32_t)freq ? 4 : 0);
cnt += freq;
return transition_code;
}
void renderTriSaw(float *dest, int nsamples)
{
COVER("Osc tri/saw");
float f = utof23(freq);
float omf = 1.0f - f;
float rcpf = 1.0f / f;
float col = utof23(brpt);
float c1 = gain / col;
float c2 = -gain / (1.0f - col);
uint32_t state = osm_init();
for (int i=0; i < nsamples; i++)
{
float p = utof23(cnt) - col;
float y = 0.0f;
switch (osm_tick(state))
{
case OSMTC_UP: y = c1 * (p + p - f);
break;
case OSMTC_DOWN: y = c2 * (p + p - f);
break;
case OSMTC_UP_DOWN: y = rcpf * (c2 * sqr(p) - c1 * sqr(p-f));
break;
case OSMTC_DOWN_UP: y = -rcpf * (gain + c2*sqr(p + omf) - c1*sqr(p));
break;
case OSMTC_UP_DOWN_UP: y = -rcpf * (gain + c1*omf*(p + p + omf));
break;
case OSMTC_DOWN_UP_DOWN: y = -rcpf * (gain + c2*omf*(p + p + omf));
break;
default:
assert(false);
break;
}
output(dest + i, y + gain);
}
}
void renderPulse(float *dest, int nsamples)
{
COVER("Osc pulse");
float f = utof23(freq);
float gdf = gain / f;
float col = utof23(brpt);
float cc121 = gdf * 2.0f * (col - 1.0f) + gain;
float cc212 = gdf * 2.0f * col - gain;
uint32_t state = osm_init();
for (int i=0; i < nsamples; i++)
{
float p = utof23(cnt);
float out = 0.0f;
switch (osm_tick(state))
{
case OSMTC_UP:
out = gain;
break;
case OSMTC_DOWN:
out = -gain;
break;
case OSMTC_UP_DOWN:
out = gdf * 2.0f * (col - p) + gain;
break;
case OSMTC_DOWN_UP:
out = gdf * 2.0f * p - gain;
break;
case OSMTC_UP_DOWN_UP:
out = cc121;
break;
case OSMTC_DOWN_UP_DOWN:
out = cc212;
break;
default:
assert(false);
break;
}
output(dest + i, out);
}
}
void renderSin(float *dest, int nsamples)
{
COVER("Osc sin");
for (int i=0; i < nsamples; i++)
{
uint32_t phase = cnt + 0x40000000; cnt += freq;
if (phase & 0x80000000) phase = ~phase;
float t = bits2float((phase >> 8) | 0x3f800000);
t = t * fcpi - fc1p5pi;
output(dest + i, gain * fastsin(t));
}
}
void renderNoise(float *dest, int nsamples)
{
COVER("Osc noise");
V2LRC flt = nf;
uint32_t seed = nseed;
for (int i=0; i < nsamples; i++)
{
float n = frandom(&seed);
float h = flt.step(n, nffrq, nfres);
float x = nfres*(flt.l + h) + flt.b;
output(dest + i, gain * x);
}
flt = nf;
nseed = seed;
}
void renderFMSin(float *dest, int nsamples)
{
COVER("Osc FM");
for (int i=0; i < nsamples; i++)
{
float mod = dest[i] * fcfmmax;
float t = (utof23(cnt) + mod) * fc2pi;
cnt += freq;
float out = gain * fastsinrc(t);
if (ring)
dest[i] *= out;
else
dest[i] = out;
}
}
void renderAux(float *dest, const StereoSample *src, int nsamples)
{
COVER("Osc aux");
float g = gain * fcgain;
for (int i=0; i < nsamples; i++)
{
float aux = g * (src[i].l + src[i].r);
if (ring)
aux *= dest[i];
dest[i] = aux;
}
}
};
struct syVEnv
{
float ar; float dr; float sl; float sr; float rr; float vol; };
struct V2Env
{
enum State
{
OFF,
RELEASE,
ATTACK,
DECAY,
SUSTAIN,
};
float out;
State state;
float val; float atd; float dcf; float sul; float suf; float ref; float gain;
void init(V2Instance *)
{
state = OFF;
}
void set(const syVEnv *para)
{
atd = powf(2.0f, para->ar * fcattackmul + fcattackadd);
dcf = 1.0f - calcfreq2(1.0f - para->dr / 128.0f);
sul = para->sl;
suf = powf(2.0f, fcsusmul * (para->sr - 64.0f));
ref = 1.0f - calcfreq2(1.0f - para->rr / 128.0f);
gain = para->vol / 128.0f;
}
void tick(bool gate)
{
if (state <= RELEASE && gate) state = ATTACK;
else if (state >= ATTACK && !gate) state = RELEASE;
switch (state)
{
case OFF:
COVER("EG off");
val = 0.0f;
break;
case ATTACK:
COVER("EG atk");
val += atd;
if (val >= 128.0f)
{
val = 128.0f;
state = DECAY;
}
break;
case DECAY:
COVER("EG dcy");
val *= dcf;
if (val <= sul)
{
val = sul;
state = SUSTAIN;
}
break;
case SUSTAIN:
COVER("EG sus");
val *= suf;
if (val > 128.0f)
val = 128.0f;
break;
case RELEASE:
COVER("EG rel");
val *= ref;
break;
}
if (val <= fclowest)
{
val = 0.0f;
state = OFF;
}
out = val * gain;
DEBUG_PLOT_VAL(this, out / 128.0f);
}
};
struct syVFlt
{
float mode;
float cutoff;
float reso;
};
struct V2Flt
{
enum Mode
{
BYPASS,
LOW,
BAND,
HIGH,
NOTCH,
ALL,
MOOGL,
MOOGH
};
int mode;
float cfreq;
float res;
float moogf, moogp, moogq; V2LRC lrc;
V2Moog moog;
V2Instance *inst;
void init(V2Instance *instance)
{
lrc.init();
moog.init();
inst = instance;
}
void set(const syVFlt *para)
{
mode = (int)para->mode;
float f = calcfreq(para->cutoff / 128.0f) * inst->SRfclinfreq;
float r = para->reso / 128.0f;
if (mode < MOOGL)
{
COVER("VCF set regular");
res = 1.0f - r;
cfreq = f;
}
else
{
COVER("VCF set moog");
f *= 0.25f;
float t = 1.0f - f;
moogp = f + 0.8f * f * t;
moogf = 1.0f - moogp - moogp;
moogq = 4.0f * r * (1.0f + 0.5f * t * (1.0f - t + 5.6f * t * t));
}
}
void render(float *dest, const float *src, int nsamples, int step=1)
{
V2LRC flt;
V2Moog m;
switch (mode & 7)
{
case BYPASS:
COVER("VCF bypass");
if (dest != src)
memmove(dest, src, nsamples * sizeof(float));
break;
case LOW:
COVER("VCF low");
flt = lrc;
for (int i=0; i < nsamples; i++)
{
flt.step_2x(src[i*step], cfreq, res);
dest[i*step] = flt.l;
}
lrc = flt;
break;
case BAND:
COVER("VCF band");
flt = lrc;
for (int i=0; i < nsamples; i++)
{
flt.step_2x(src[i*step], cfreq, res);
dest[i*step] = flt.b;
}
lrc = flt;
break;
case HIGH:
COVER("VCF high");
flt = lrc;
for (int i=0; i < nsamples; i++)
{
float h = flt.step_2x(src[i*step], cfreq, res);
dest[i*step] = h;
}
lrc = flt;
break;
case NOTCH:
COVER("VCF notch");
flt = lrc;
for (int i=0; i < nsamples; i++)
{
float h = flt.step_2x(src[i*step], cfreq, res);
dest[i*step] = flt.l + h;
}
lrc = flt;
break;
case ALL:
COVER("VCF all");
flt = lrc;
for (int i=0; i < nsamples; i++)
{
float h = flt.step_2x(src[i*step], cfreq, res);
dest[i*step] = flt.l + flt.b + h;
}
lrc = flt;
break;
case MOOGL:
COVER("VCF moog low");
m = moog;
for (int i=0; i < nsamples; i++)
{
float in = src[i*step];
m.step(in, moogf, moogp, moogq);
dest[i*step] = m.step(in, moogf, moogp, moogq);
}
moog = m;
break;
case MOOGH:
COVER("VCF moog high");
m = moog;
for (int i=0; i < nsamples; i++)
{
float in = src[i*step];
m.step(in, moogf, moogp, moogq);
dest[i*step] = in - m.step(in, moogf, moogp, moogq);
}
moog = m;
break;
}
DEBUG_PLOT_STRIDED(this, dest, step, nsamples);
}
};
struct syVLFO
{
float mode; float sync; float egmode; float rate; float phase; float pol; float amp; };
struct V2LFO
{
enum Mode
{
SAW,
TRI,
PULSE,
SIN,
S_H
};
float out;
int mode; bool sync; bool eg; int freq; uint32_t cntr; uint32_t cphase; float gain; float dc; uint32_t nseed; uint32_t last;
void init(V2Instance *)
{
cntr = last = 0;
nseed = rand(); }
void set(const syVLFO *para)
{
mode = (int)para->mode;
sync = (int)para->sync != 0;
eg = (int)para->egmode != 0;
freq = (int)(0.5f * fc32bit * calcfreq(para->rate / 128.0f));
cphase = ftou32(para->phase / 128.0f);
switch ((int)para->pol)
{
case 0: COVER("LFO pol +");
gain = para->amp;
dc = 0.0f;
break;
case 1: COVER("LFO pol -");
gain = -para->amp;
dc = 0.0f;
break;
case 2: COVER("LFO pol +/-");
gain = para->amp;
dc = -0.5f * para->amp;
break;
}
}
void keyOn()
{
if (sync)
{
COVER("LFO sync");
cntr = cphase;
last = ~0u;
}
}
void tick()
{
float v;
uint32_t x;
switch (mode & 7)
{
case SAW:
default:
COVER("LFO saw");
v = utof23(cntr);
break;
case TRI:
COVER("LFO tri");
x = (cntr << 1) ^ (int32_t(cntr) >> 31);
v = utof23(x);
break;
case PULSE:
COVER("LFO pulse");
x = int32_t(cntr) >> 31;
v = utof23(x);
break;
case SIN:
COVER("LFO sin");
v = utof23(cntr);
v = fastsinrc(v * fc2pi) * 0.5f + 0.5f;
break;
case S_H:
COVER("LFO sample+hold");
if (cntr < last)
nseed = urandom(&nseed);
last = cntr;
v = utof23(nseed);
break;
}
out = v * gain + dc;
cntr += freq;
if (cntr < (uint32_t)freq && eg) cntr = ~0u;
DEBUG_PLOT_VAL(this, out / 128.0f);
}
};
struct syVDist
{
float mode; float ingain; float param1; float param2; };
struct V2Dist
{
enum Mode
{
OFF = 0,
OVERDRIVE,
CLIP,
BITCRUSHER,
DECIMATOR,
FLT_BASE = DECIMATOR,
FLT_LOW = FLT_BASE + V2Flt::LOW,
FLT_BAND = FLT_BASE + V2Flt::BAND,
FLT_HIGH = FLT_BASE + V2Flt::HIGH,
FLT_NOTCH = FLT_BASE + V2Flt::NOTCH,
FLT_ALL = FLT_BASE + V2Flt::ALL,
FLT_MOOGL = FLT_BASE + V2Flt::MOOGL,
FLT_MOOGH = FLT_BASE + V2Flt::MOOGH,
};
int mode;
float gain1; float gain2; float offs; float crush1; int crush2; int crxor; uint32_t dcount; uint32_t dfreq; float dvall; float dvalr; V2Flt fltl; V2Flt fltr;
void init(V2Instance *instance)
{
dcount = 0;
dvall = dvalr = 0.0f;
fltl.init(instance);
fltr.init(instance);
}
void set(const syVDist *para)
{
float x;
mode = (int)para->mode;
gain1 = powf(2.0f, (para->ingain - 32.0f) / 16.0f);
switch (mode)
{
case OFF:
break;
case OVERDRIVE:
gain2 = (para->param1 / 128.0f) / atan(gain1);
offs = gain1 * 2.0f * ((para->param2 / 128.0f) - 0.5f);
break;
case CLIP:
gain2 = para->param1 / 128.0f;
offs = gain1 * 2.0f * ((para->param2 / 128.0f) - 0.5f);
break;
case BITCRUSHER:
x = para->param1 * 256.0f + 1.0f;
crush2 = (int)x;
crush1 = gain1 * (32768.0f / x);
crxor = ((int)para->param2) << 9;
break;
case DECIMATOR:
dfreq = ftou32(calcfreq(para->param1 / 128.0f));
break;
default: {
syVFlt setup;
setup.cutoff = para->param1;
setup.reso = para->param2;
setup.mode = (float)(mode - FLT_BASE);
fltl.set(&setup);
fltr.set(&setup);
}
break;
}
}
void renderMono(float *dest, const float *src, int nsamples)
{
switch (mode)
{
case OFF:
if (dest != src)
memmove(dest, src, nsamples * sizeof(float));
break;
case OVERDRIVE:
COVER("DIST overdrive");
for (int i=0; i < nsamples; i++)
dest[i] = overdrive(src[i]);
break;
case CLIP:
COVER("DIST clip");
for (int i=0; i < nsamples; i++)
dest[i] = clip(src[i]);
break;
case BITCRUSHER:
COVER("DIST bitcrusher");
for (int i=0; i < nsamples; i++)
dest[i] = bitcrusher(src[i]);
break;
case DECIMATOR:
COVER("DIST mono decimator");
for (int i=0; i < nsamples; i++)
{
decimator_tick(src[i], 0.0f);
dest[i] = dvall;
}
break;
default: COVER("DIST mono filter");
fltl.render(dest, src, nsamples);
break;
}
DEBUG_PLOT(this, dest, nsamples);
}
void renderStereo(StereoSample *dest, const StereoSample *src, int nsamples)
{
switch (mode)
{
case DECIMATOR:
COVER("DIST stereo decimator");
for (int i = 0; i < nsamples; i++)
{
decimator_tick(src[i].l, src[i].r);
dest[i].l = dvall;
dest[i].r = dvalr;
}
break;
case FLT_LOW:
case FLT_BAND:
case FLT_HIGH:
case FLT_NOTCH:
case FLT_ALL:
COVER("DIST stereo filter");
fltl.render(&dest[0].l, &src[0].l, nsamples, 2);
fltr.render(&dest[0].r, &src[0].r, nsamples, 2);
break;
default:
renderMono(&dest[0].l, &src[0].l, nsamples*2);
}
DEBUG_PLOT_STEREO(this, dest, nsamples);
}
private:
inline float overdrive(float in)
{
return gain2 * fastatan(in * gain1 + offs);
}
inline float clip(float in)
{
return gain2 * clamp(in * gain1 + offs, -1.0f, 1.0f);
}
inline float bitcrusher(float in)
{
int t = (int)(in * crush1);
t = clamp(t * crush2, -0x7fff, 0x7fff) ^ crxor;
return (float)t / 32768.0f;
}
inline void decimator_tick(float l, float r)
{
dcount += dfreq;
if (dcount < dfreq) {
dvall = l;
dvalr = r;
}
}
};
struct V2DCFilter
{
V2DCF fl; V2DCF fr; V2Instance *inst;
void init(V2Instance *instance)
{
inst = instance;
fl.init();
fr.init();
}
void renderMono(float *dest, const float *src, int nsamples)
{
float R = inst->SRfcdcfilter;
V2DCF l = fl;
for (int i=0; i < nsamples; i++)
dest[i] = l.step(src[i], R);
fl = l;
}
void renderStereo(StereoSample *dest, const StereoSample *src, int nsamples)
{
float R = inst->SRfcdcfilter;
V2DCF l = fl;
V2DCF r = fr;
for (int i=0; i < nsamples; i++)
{
dest[i].l = l.step(src[i].l, R);
dest[i].r = r.step(src[i].r, R);
}
fl = l;
fr = r;
}
};
struct syVV2
{
static const int NOSC = 3; static const int NFLT = 2; static const int NENV = 2; static const int NLFO = 2;
float panning;
float transp; syVOsc osc[NOSC];
syVFlt flt[NFLT];
float routing; float fltbal; syVDist dist;
syVEnv env[NENV];
syVLFO lfo[NLFO];
float oscsync; };
struct V2Voice
{
enum FilterRouting
{
FLTR_SINGLE = 0,
FLTR_SERIAL,
FLTR_PARALLEL
};
enum KeySync
{
SYNC_NONE = 0,
SYNC_OSC,
SYNC_FULL
};
int note;
float velo;
bool gate;
float curvol;
float volramp;
float xpose; int fmode; float lvol; float rvol; float f1gain; float f2gain;
int keysync;
V2Osc osc[syVV2::NOSC];
V2Flt vcf[syVV2::NFLT];
V2Env env[syVV2::NENV];
V2LFO lfo[syVV2::NLFO];
V2Dist dist; V2DCFilter dcf;
V2Instance *inst;
void init(V2Instance *instance)
{
for (int i=0; i < syVV2::NOSC; i++)
osc[i].init(instance, i);
for (int i=0; i < syVV2::NFLT; i++)
vcf[i].init(instance);
for (int i=0; i < syVV2::NENV; i++)
env[i].init(instance);
for (int i=0; i < syVV2::NLFO; i++)
lfo[i].init(instance);
dist.init(instance);
dcf.init(instance);
inst = instance;
}
void tick()
{
for (int i=0; i < syVV2::NENV; i++)
env[i].tick(gate);
for (int i=0; i < syVV2::NLFO; i++)
lfo[i].tick();
volramp = (env[0].out / 128.0f - curvol) * inst->SRfciframe;
DEBUG_PLOT_VAL(&curvol, curvol);
}
void render(StereoSample *dest, int nsamples)
{
assert(nsamples <= V2Instance::MAX_FRAME_SIZE);
float *voice = inst->vcebuf;
float *voice2 = inst->vcebuf2;
memset(voice, 0, nsamples * sizeof(*voice));
for (int i=0; i < syVV2::NOSC; i++)
osc[i].render(voice, nsamples);
switch (fmode)
{
case FLTR_SINGLE:
COVER("VOICE filter single");
vcf[0].render(voice, voice, nsamples);
break;
case FLTR_SERIAL:
default:
COVER("VOICE filter serial");
vcf[0].render(voice, voice, nsamples);
vcf[1].render(voice, voice, nsamples);
break;
case FLTR_PARALLEL:
COVER("VOICE filter parallel");
vcf[1].render(voice2, voice, nsamples);
vcf[0].render(voice, voice, nsamples);
for (int i=0; i < nsamples; i++)
voice[i] = voice[i]*f1gain + voice2[i]*f2gain;
break;
}
dist.renderMono(voice, voice, nsamples);
dcf.renderMono(voice, voice, nsamples);
DEBUG_PLOT(this, voice, nsamples);
float cv = curvol;
for (int i=0; i < nsamples; i++)
{
float out = voice[i] * cv;
cv += volramp;
dest[i].l += lvol * out + fcdcoffset;
dest[i].r += rvol * out + fcdcoffset;
}
curvol = cv;
}
void set(const syVV2 *para)
{
xpose = para->transp - 64.0f;
updateNote();
fmode = (int)para->routing;
keysync = (int)para->oscsync;
float p = para->panning / 128.0f;
lvol = sqrtf(1.0f - p);
rvol = sqrtf(p);
float x = (para->fltbal - 64.0f) / 64.0f;
if (x >= 0.0f)
{
f2gain = 1.0f;
f1gain = 1.0f - x;
}
else
{
f1gain = 1.0f;
f2gain = 1.0f + x;
}
for (int i=0; i < syVV2::NOSC; i++)
osc[i].set(¶->osc[i]);
for (int i=0; i < syVV2::NENV; i++)
env[i].set(¶->env[i]);
for (int i=0; i < syVV2::NFLT; i++)
vcf[i].set(¶->flt[i]);
for (int i=0; i < syVV2::NLFO; i++)
lfo[i].set(¶->lfo[i]);
dist.set(¶->dist);
}
void noteOn(int note, int vel)
{
this->note = note;
updateNote();
velo = (float)vel;
gate = true;
for (int i=0; i < syVV2::NENV; i++)
env[i].state = V2Env::ATTACK;
switch (keysync)
{
case SYNC_FULL:
COVER("VOICE noteOn sync full");
for (int i=0; i < syVV2::NENV; i++)
env[i].val = 0.0f;
curvol = 0.0f;
for (int i=0; i < syVV2::NOSC; i++)
osc[i].init(inst, i);
for (int i=0; i < syVV2::NFLT; i++)
vcf[i].init(inst);
dist.init(inst);
case SYNC_OSC:
COVER("VOICE noteOn sync osc");
for (int i=0; i < syVV2::NOSC; i++)
osc[i].cnt = 0;
case SYNC_NONE:
default:
break;
}
for (int i=0; i < syVV2::NOSC; i++)
osc[i].chgPitch();
for (int i=0; i < syVV2::NLFO; i++)
lfo[i].keyOn();
dcf.init(inst);
}
void noteOff()
{
gate = false;
}
private:
void updateNote()
{
float n = xpose + (float)note;
for (int i=0; i < syVV2::NOSC; i++)
osc[i].note = n;
}
};
struct syVBoost
{
float amount; };
struct V2Boost
{
bool enabled;
float a1, a2; float b0, b1, b2;
float x1[2]; float x2[2];
float y1[2];
float y2[2];
V2Instance *inst;
void init(V2Instance *instance)
{
inst = instance;
}
void set(const syVBoost *para)
{
enabled = ((int)para->amount) != 0;
if (!enabled)
return;
COVER("BOOST set");
float A = powf(2.0f, para->amount / 128.0f);
float beta = sqrtf(2.0f * A);
float bs = beta * inst->SRfcBoostSin;
float Am1 = A - 1.0f;
float Ap1 = A + 1.0f;
float cAm1 = Am1 * inst->SRfcBoostCos;
float cAp1 = Ap1 * inst->SRfcBoostCos;
float ia0 = 1.0f / (Ap1 + cAm1 + bs);
b1 = 2.0f * A * (Am1 - cAp1) * ia0;
a1 = -2.0f * (Am1 + cAp1) * ia0;
a2 = (Ap1 + cAm1 - bs) * ia0;
b0 = A * (Ap1 - cAm1 + bs) * ia0;
b2 = A * (Ap1 - cAm1 - bs) * ia0;
}
void render(StereoSample *buf, int nsamples)
{
if (!enabled)
return;
COVER("BOOST render");
for (int ch=0; ch < 2; ch++)
{
float xm1 = x1[ch], xm2 = x2[ch];
float ym1 = y1[ch], ym2 = y2[ch];
for (int i=0; i < nsamples; i++)
{
float x = buf[i].ch[ch] + fcdcoffset;
float y = b0*x + b1*xm1 + b2*xm2 - a1*ym1 - a2*ym2;
ym2 = ym1; ym1 = y;
xm2 = xm1; xm1 = x;
buf[i].ch[ch] = y;
}
x1[ch] = xm1; x2[ch] = xm2;
y1[ch] = ym1; y2[ch] = ym2;
}
}
};
struct syVModDel
{
float amount; float fb; float llength; float rlength; float mrate; float mdepth; float mphase; };
struct V2ModDel
{
float *db[2]; uint32_t dbufmask;
uint32_t dbptr; uint32_t dboffs[2];
uint32_t mcnt; int mfreq; uint32_t mphase; uint32_t mmaxoffs;
float fbval; float dryout;
float wetout;
V2Instance *inst;
void init(V2Instance *instance, float *buf1, float *buf2, int buflen)
{
assert(buflen != 0 && (buflen & (buflen - 1)) == 0);
db[0] = buf1;
db[1] = buf2;
dbufmask = buflen - 1;
inst = instance;
reset();
}
void reset()
{
dbptr = 0;
mcnt = 0;
memset(db[0], 0, (dbufmask + 1) * sizeof(float));
memset(db[1], 0, (dbufmask + 1) * sizeof(float));
}
void set(const syVModDel *para)
{
wetout = (para->amount - 64.0f) / 64.0f;
dryout = 1.0f - fabsf(wetout);
fbval = (para->fb - 64.0f) / 64.0f;
float lenscale = ((float)dbufmask - 1023.0f) / 128.0f;
dboffs[0] = (int)(para->llength * lenscale);
dboffs[1] = (int)(para->rlength * lenscale);
mfreq = (int)(inst->SRfclinfreq * fcmdlfomul * calcfreq(para->mrate / 128.0f));
mmaxoffs = (int)(para->mdepth * 1023.0f / 128.0f);
mphase = ftou32((para->mphase - 64.0f) / 128.0f);
}
void renderAux2Main(StereoSample *dest, int nsamples)
{
if (!wetout)
return;
COVER("MODDEL aux->main");
for (int i=0; i < nsamples; i++)
{
StereoSample x;
float in = inst->aux2buf[i] + fcdcoffset;
processSample(&x, in, in, 0.0f);
dest[i].l += x.l;
dest[i].r += x.r;
}
}
void renderChan(StereoSample *chanbuf, int nsamples)
{
if (!wetout)
return;
COVER("MODDEL chan");
float dry = dryout;
for (int i=0; i < nsamples; i++)
processSample(&chanbuf[i], chanbuf[i].l + fcdcoffset, chanbuf[i].r + fcdcoffset, dry);
}
private:
inline float processChanSample(float in, int ch, float dry)
{
uint32_t counter = mcnt + (ch ? mphase : 0);
counter = (counter < 0x80000000u) ? counter*2 : 0xffffffffu - counter*2;
uint64_t offs32_32 = (uint64_t)counter * mmaxoffs; uint32_t offs_int = uint32_t(offs32_32 >> 32) + dboffs[ch];
uint32_t index = dbptr - offs_int;
float *delaybuf = db[ch];
float x = utof23((uint32_t)(offs32_32 & 0xffffffffu));
float delayed = lerp(delaybuf[(index - 0) & dbufmask], delaybuf[(index - 1) & dbufmask], x);
delaybuf[dbptr] = in + delayed*fbval;
return in*dry + delayed*wetout;
}
inline void processSample(StereoSample *out, float l, float r, float dry)
{
out->l = processChanSample(l, 0, dry);
out->r = processChanSample(r, 1, dry);
mcnt += mfreq;
dbptr = (dbptr + 1) & dbufmask;
}
};
struct syVComp
{
float mode; float stereo; float autogain; float lookahead; float threshold; float ratio; float attack; float release; float outgain; };
struct V2Comp
{
static const int COMPDLEN = 5700;
static const int RMSLEN = 8192;
enum Mode
{
MODE_OFF = 0,
MODE_PEAK,
MODE_RMS,
};
enum ModeBits
{
MODE_BIT_PEAK = 0,
MODE_BIT_RMS = 1,
MODE_BIT_MONO = 0,
MODE_BIT_STEREO = 2,
MODE_BIT_ON = 0,
MODE_BIT_OFF = 4,
};
int mode;
float invol; float ratio;
float outvol; float attack; float release;
uint32_t dblen; uint32_t dbcnt;
float curgain[2]; float peakval[2]; float rmsval[2]; uint32_t rmscnt;
StereoSample dbuf[COMPDLEN]; StereoSample rmsbuf[RMSLEN];
V2Instance *inst;
void init(V2Instance *instance)
{
mode = MODE_BIT_STEREO;
memset(dbuf, 0, sizeof(dbuf));
inst = instance;
reset();
}
void reset()
{
for (int i=0; i < 2; i++)
{
peakval[i] = 0.0f;
rmsval[i] = 0.0f;
curgain[i] = 1.0f;
}
memset(rmsbuf, 0, sizeof(rmsbuf));
rmscnt = 0;
}
void set(const syVComp *para)
{
int oldmode = mode;
switch ((int)para->mode)
{
case MODE_OFF: mode = MODE_BIT_OFF; break;
case MODE_PEAK: mode = MODE_BIT_PEAK | MODE_BIT_ON; break;
case MODE_RMS: mode = MODE_BIT_RMS | MODE_BIT_ON; break;
default: assert(false);
}
if (para->stereo != 0.0f)
mode |= MODE_BIT_STEREO;
if (mode != oldmode)
reset();
dblen = (int)(para->lookahead * inst->SRfcsamplesperms);
float thresh = 8.0f * calcfreq(para->threshold / 128.0f);
invol = 1.0f / thresh;
if (para->autogain != 0.0f)
thresh = 1.0f;
outvol = thresh * powf(2.0f, (para->outgain - 64.0f) / 16.0f);
ratio = para->ratio / 128.0f;
attack = powf(2.0f, -para->attack * 12.0f / 128.0f);
release = powf(2.0f, -para->release * 16.0f / 128.0f);
}
void render(StereoSample *buf, int nsamples)
{
if (mode & MODE_BIT_OFF)
return;
COVER("COMP render");
StereoSample *levels = inst->levelbuf;
switch (mode & (MODE_BIT_RMS | MODE_BIT_STEREO))
{
case MODE_BIT_PEAK | MODE_BIT_MONO:
COVER("COMP level peak mono");
for (int i=0; i < nsamples; i++)
levels[i].l = levels[i].r = invol * doPeak(0.5f * (buf[i].l + buf[i].r), 0);
break;
case MODE_BIT_RMS | MODE_BIT_MONO:
COVER("COMP level rms mono");
for (int i=0; i < nsamples; i++)
{
levels[i].l = levels[i].r = invol * doRMS(0.5f * (buf[i].l + buf[i].r), 0);
rmscnt = (rmscnt + 1) & (RMSLEN - 1);
}
break;
case MODE_BIT_PEAK | MODE_BIT_STEREO:
COVER("COMP level peak stereo");
for (int i=0; i < nsamples; i++)
{
levels[i].l = invol * doPeak(buf[i].l, 0);
levels[i].r = invol * doPeak(buf[i].r, 1);
}
break;
case MODE_BIT_RMS | MODE_BIT_STEREO:
COVER("COMP level rms stereo");
for (int i=0; i < nsamples; i++)
{
levels[i].l = invol * doRMS(buf[i].l, 0);
levels[i].r = invol * doRMS(buf[i].r, 1);
rmscnt = (rmscnt + 1) & (RMSLEN - 1);
}
break;
}
for (int ch=0; ch < 2; ch++)
{
float gain = curgain[ch];
uint32_t dbind = dbcnt;
for (int i=0; i < nsamples; i++)
{
float v = outvol * dbuf[dbind].ch[ch];
dbuf[dbind].ch[ch] = invol * buf[i].ch[ch];
if (++dbind >= dblen)
dbind = 0;
float dgain = 1.0f;
float lvl = levels[i].ch[ch];
if (lvl >= 1.0f)
dgain = 1.0f / (1.0f + ratio * (lvl - 1.0f));
gain += (dgain < gain ? attack : release) * (dgain - gain);
buf[i].ch[ch] = v * gain;
}
curgain[ch] = gain;
if (ch == 1)
dbcnt = dbind;
}
}
private:
inline float doPeak(float in, int ch)
{
peakval[ch] = max(peakval[ch] * fccpdfalloff + fcdcoffset, fabsf(in));
return peakval[ch];
}
inline float doRMS(float in, int ch)
{
float insq = sqr(in + fcdcoffset);
rmsval[ch] += insq - rmsbuf[rmscnt].ch[ch]; rmsbuf[rmscnt].ch[ch] = insq; return sqrtf(rmsval[ch] / (float)RMSLEN);
}
};
struct syVReverb
{
float revtime;
float highcut;
float lowcut;
float vol;
};
struct V2Reverb
{
float gainc[4]; float gaina[2]; float gainin; float damp; float lowcut;
V2Delay combd[2][4]; float combl[2][4]; V2Delay alld[2][2]; float hpf[2];
V2Instance *inst;
float bcombl0[1309];
float bcombl1[1635];
float bcombl2[1811];
float bcombl3[1926];
float balll0[220];
float balll1[74];
float bcombr0[1327];
float bcombr1[1631];
float bcombr2[1833];
float bcombr3[1901];
float ballr0[205];
float ballr1[77];
void init(V2Instance *instance)
{
combd[0][0].init(bcombl0);
combd[0][1].init(bcombl1);
combd[0][2].init(bcombl2);
combd[0][3].init(bcombl3);
alld[0][0].init(balll0);
alld[0][1].init(balll1);
combd[1][0].init(bcombr0);
combd[1][1].init(bcombr1);
combd[1][2].init(bcombr2);
combd[1][3].init(bcombr3);
alld[1][0].init(ballr0);
alld[1][1].init(ballr1);
inst = instance;
reset();
}
void reset()
{
for (int ch=0; ch < 2; ch++)
{
for (int i=0; i < 4; i++)
{
combd[ch][i].reset();
combl[ch][i] = 0.0f;
}
for (int i=0; i < 2; i++)
alld[ch][i].reset();
hpf[ch] = 0.0f;
}
}
void set(const syVReverb *para)
{
static const float gaincdef[4] = {
0.966384599f, 0.958186359f, 0.953783929f, 0.950933178f
};
static const float gainadef[2] = {
0.994260075f, 0.998044717f
};
float e = inst->SRfclinfreq * sqr(64.0f / (para->revtime + 1.0f));
for (int i=0; i < 4; i++)
gainc[i] = powf(gaincdef[i], e);
for (int i=0; i < 2; i++)
gaina[i] = powf(gainadef[i], e);
damp = inst->SRfclinfreq * (para->highcut / 128.0f);
gainin = para->vol / 128.0f;
lowcut = inst->SRfclinfreq * sqr(sqr(para->lowcut / 128.0f));
}
void render(StereoSample *dest, int nsamples)
{
const float *inbuf = inst->aux1buf;
COVER("RVB render");
for (int i=0; i < nsamples; i++)
{
float in = inbuf[i] * gainin + fcdcoffset;
for (int ch=0; ch < 2; ch++)
{
float cur = 0.0f;
for (int j=0; j < 4; j++)
{
float dv = gainc[j] * combd[ch][j].fetch();
float nv = (j & 1) ? (dv - in) : (dv + in); float lp = combl[ch][j] + damp * (nv - combl[ch][j]);
combd[ch][j].feed(lp);
cur += lp;
}
for (int j=0; j < 2; j++)
{
float dv = alld[ch][j].fetch();
float dz = cur + gaina[j] * dv;
alld[ch][j].feed(dz);
cur = dv - gaina[j] * dz;
}
hpf[ch] += lowcut * (cur - hpf[ch]);
dest[i].ch[ch] += cur - hpf[ch];
}
}
}
};
struct syVChan
{
float chanvol;
float auxarcv; float auxbrcv; float auxasnd; float auxbsnd; float aux1;
float aux2;
float fxroute;
syVBoost boost;
syVDist dist;
syVModDel chorus;
syVComp comp;
};
struct V2Chan
{
enum FXRouting
{
FXR_DIST_THEN_CHORUS = 0,
FXR_CHORUS_THEN_DIST,
};
float chgain; float a1gain; float a2gain; float aasnd; float absnd; float aarcv; float abrcv; int fxr;
V2DCFilter dcf1;
V2Boost boost;
V2Dist dist;
V2DCFilter dcf2;
V2ModDel chorus;
V2Comp comp;
V2Instance *inst;
void init(V2Instance *instance, float *delbuf1, float *delbuf2, int buflen)
{
inst = instance;
dcf1.init(inst);
boost.init(inst);
dist.init(inst);
dcf2.init(inst);
chorus.init(inst, delbuf1, delbuf2, buflen);
comp.init(inst);
}
void set(const syVChan *para)
{
aarcv = para->auxarcv / 128.0f;
abrcv = para->auxbrcv / 128.0f;
aasnd = fcgain * (para->auxasnd / 128.0f);
absnd = fcgain * (para->auxbsnd / 128.0f);
chgain = fcgain * (para->chanvol / 128.0f);
a1gain = chgain * fcgainh * (para->aux1 / 128.0f);
a2gain = chgain * fcgainh * (para->aux2 / 128.0f);
fxr = (int)para->fxroute;
dist.set(¶->dist);
chorus.set(¶->chorus);
comp.set(¶->comp);
boost.set(¶->boost);
}
void process(int nsamples)
{
StereoSample *chan = inst->chanbuf;
accumulate(chan, inst->auxabuf, nsamples, aarcv);
accumulate(chan, inst->auxbbuf, nsamples, abrcv);
dcf1.renderStereo(chan, chan, nsamples);
DEBUG_PLOT_STEREO(&dcf1, chan, nsamples);
comp.render(chan, nsamples);
boost.render(chan, nsamples);
if (fxr == FXR_DIST_THEN_CHORUS)
{
dist.renderStereo(chan, chan, nsamples);
dcf2.renderStereo(chan, chan, nsamples);
chorus.renderChan(chan, nsamples);
}
else {
chorus.renderChan(chan, nsamples);
dist.renderStereo(chan, chan, nsamples);
dcf2.renderStereo(chan, chan, nsamples);
}
accumulateMonoMix(inst->aux1buf, chan, nsamples, a1gain);
accumulateMonoMix(inst->aux2buf, chan, nsamples, a2gain);
accumulate(inst->auxabuf, chan, nsamples, aasnd);
accumulate(inst->auxbbuf, chan, nsamples, absnd);
accumulate(inst->mixbuf, chan, nsamples, chgain);
DEBUG_PLOT_STEREO(this, chan, nsamples);
}
private:
void accumulate(StereoSample *dest, const StereoSample *src, int nsamples, float gain)
{
if (gain == 0.0f)
return;
for (int i = 0; i < nsamples; i++)
{
dest[i].l += gain * src[i].l;
dest[i].r += gain * src[i].r;
}
}
void accumulateMonoMix(float *dest, const StereoSample *src, int nsamples, float gain)
{
if (gain == 0.0f)
return;
for (int i = 0; i < nsamples; i++)
dest[i] += gain * (src[i].l + src[i].r);
}
};
struct V2Mod
{
uint8_t source; uint8_t val; uint8_t dest; };
struct V2Sound
{
uint8_t voice[sizeof(syVV2) / sizeof(float)];
uint8_t chan[sizeof(syVChan) / sizeof(float)];
uint8_t maxpoly;
uint8_t modnum;
V2Mod modmatrix[1]; };
union V2PatchMap
{
uint32_t offsets[128]; uint8_t raw_data[1]; };
struct syWRonan
{
uint8_t mem[64*1024]; };
#ifdef RONAN
extern "C"
{
void ronanCBInit(syWRonan *pthis);
void ronanCBTick(syWRonan *pthis);
void ronanCBNoteOn(syWRonan *pthis);
void ronanCBNoteOff(syWRonan *pthis);
void ronanCBSetCtl(syWRonan *pthis, uint32_t ctl, uint32_t val);
void ronanCBProcess(syWRonan *pthis, float *buf, uint32_t len);
void ronanCBSetSR(syWRonan *pthis, int samplerate);
}
#else
static inline void ronanCBInit(syWRonan *) {}
static inline void ronanCBTick(syWRonan *) {}
static inline void ronanCBNoteOn(syWRonan *) {}
static inline void ronanCBNoteOff(syWRonan *) {}
static inline void ronanCBSetCtl(syWRonan *, uint32_t, uint32_t) {}
static inline void ronanCBProcess(syWRonan *, float *, uint32_t) {}
static inline void ronanCBSetSR(syWRonan *, int) {}
extern "C" void synthSetLyrics(void *, const char **) {}
#endif
struct V2ChanInfo
{
uint8_t pgm; uint8_t ctl[7]; };
struct V2Synth
{
static const int POLY = 64;
static const int CHANS = 16;
const V2PatchMap *patchmap;
uint32_t mrstat; uint32_t curalloc;
int samplerate;
int chanmap[POLY]; uint32_t allocpos[POLY];
int voicemap[CHANS]; int tickd;
V2ChanInfo chans[CHANS];
syVV2 voicesv[POLY];
V2Voice voicesw[POLY];
syVChan chansv[CHANS];
V2Chan chansw[CHANS];
struct Globals
{
syVReverb rvbparm;
syVModDel delparm;
float vlowcut;
float vhighcut;
syVComp cprparm;
uint8_t guicolor;
uint8_t _pad[3];
} globals;
V2Reverb reverb;
V2ModDel delay;
V2DCFilter dcf;
V2Comp compr;
float lcfreq; float lcbuf[2]; float hcfreq; float hcbuf[2];
bool initialized;
float maindelbuf[2][32768];
float chandelbuf[CHANS][2][2048];
V2Instance instance;
syWRonan ronan;
void init(const void *patchmap, int samplerate)
{
memset(this, 0, sizeof(*this));
this->samplerate = samplerate;
instance.calcNewSampleRate(samplerate);
ronanCBSetSR(&ronan, samplerate);
this->patchmap = (const V2PatchMap*)patchmap;
for (int i = 0; i < POLY; i++)
{
chanmap[i] = -1;
voicesw[i].init(&instance);
}
for (int i = 0; i < CHANS; i++)
{
chans[i].ctl[6] = 0x7f;
chansw[i].init(&instance, chandelbuf[i][0], chandelbuf[i][1], COUNTOF(chandelbuf[i][0]));
}
reverb.init(&instance);
delay.init(&instance, maindelbuf[0], maindelbuf[1], COUNTOF(maindelbuf[0]));
ronanCBInit(&ronan);
compr.init(&instance);
dcf.init(&instance);
initialized = true;
}
void render(float *buf, int nsamples, float *buf2, bool add)
{
int todo = nsamples;
while (todo)
{
if (!tickd)
tick();
const StereoSample *src = &instance.mixbuf[instance.SRcFrameSize - tickd];
int nread = min(todo, tickd);
if (!buf2) {
if (!add)
{
COVER("OUT interleaved set");
memcpy(buf, src, nread * sizeof(StereoSample));
} else
{
COVER("OUT interleaved add");
for (int i = 0; i < nread; i++)
{
buf[i*2 + 0] += src[i].l;
buf[i*2 + 1] += src[i].r;
}
}
buf += 2*nread;
}
else {
if (!add)
{
COVER("OUT separate set");
for (int i=0; i < nread; i++)
{
buf[i] = src[i].l;
buf2[i] = src[i].r;
}
}
else
{
COVER("OUT separate add");
for (int i = 0; i < nread; i++)
{
buf[i] += src[i].l;
buf2[i] += src[i].r;
}
}
buf += nread;
buf2 += nread;
}
todo -= nread;
tickd -= nread;
}
DEBUG_PLOT_UPDATE();
}
void processMIDI(const uint8_t *cmd)
{
while (*cmd != 0xfd) {
if (*cmd & 0x80) mrstat = *cmd++;
if (mrstat < 0x80) break;
int chan = mrstat & 0xf;
switch ((mrstat >> 4) & 7)
{
case 1: if (cmd[1] != 0) {
COVER("MIDI note on");
if (chan == CHANS - 1)
ronanCBNoteOn(&ronan);
const V2Sound *sound = getpatch(chans[chan].pgm);
int npoly = 0;
for (int i = 0; i < POLY; i++)
npoly += (chanmap[i] == chan);
int usevoice = -1;
int chanmask, chanfind;
if (!npoly || npoly < sound->maxpoly) {
for (int i=0; i < POLY; i++)
{
if (chanmap[i] < 0)
{
usevoice = i;
break;
}
}
COVER("SYN find voice any");
chanmask = 0;
chanfind = 0;
} else
{
COVER("SYN find voice channel");
chanmask = 0xf;
chanfind = chan;
}
if (usevoice < 0)
{
COVER("SYN replace voice gate off");
uint32_t oldest = curalloc;
for (int i = 0; i < POLY; i++)
{
if ((chanmap[i] & chanmask) == chanfind && !voicesw[i].gate && allocpos[i] < oldest)
{
oldest = allocpos[i];
usevoice = i;
}
}
}
if (usevoice < 0)
{
COVER("SYN replace voice oldest");
uint32_t oldest = curalloc;
for (int i = 0; i < POLY; i++)
{
if ((chanmap[i] & chanmask) == chanfind && allocpos[i] < oldest)
{
oldest = allocpos[i];
usevoice = i;
}
}
}
assert(usevoice >= 0);
chanmap[usevoice] = chan;
voicemap[chan] = usevoice;
allocpos[usevoice] = curalloc++;
storeV2Values(usevoice);
voicesw[usevoice].noteOn(cmd[0], cmd[1]);
cmd += 2;
break;
}
case 0: COVER("MIDI note off");
if (chan == CHANS - 1)
ronanCBNoteOff(&ronan);
for (int i = 0; i < POLY; i++)
{
if (chanmap[i] != chan)
continue;
V2Voice *voice = &voicesw[i];
if (voice->note == cmd[0] && voice->gate)
voice->noteOff();
}
cmd += 2;
break;
case 2: COVER("MIDI aftertouch");
cmd++; break;
case 3: {
COVER("MIDI controller change");
int ctrl = cmd[0];
uint8_t val = cmd[1];
if (ctrl >= 1 && ctrl <= 7)
{
chans[chan].ctl[ctrl - 1] = val;
if (chan == CHANS-1)
ronanCBSetCtl(&ronan, ctrl, val);
}
else if (ctrl == 120) {
COVER("MIDI CC all sound off");
for (int i=0; i < POLY; i++)
{
if (chanmap[i] != chan)
continue;
voicesw[i].init(&instance);
chanmap[i] = -1;
}
}
else if (ctrl == 123) {
COVER("MIDI CC all notes off");
if (chan == CHANS-1)
ronanCBNoteOff(&ronan);
for (int i=0; i < POLY; i++)
{
if (chanmap[i] == chan)
voicesw[i].noteOff();
}
}
cmd += 2;
break;
}
case 4: {
COVER("MIDI program change");
uint8_t pgm = *cmd++ & 0x7f;
if (chans[chan].pgm != pgm)
{
COVER("MIDI program change real");
chans[chan].pgm = pgm;
for (int i=0; i < POLY; i++)
{
if (chanmap[i] == chan)
chanmap[i] = -1;
}
}
for (int i=0; i < 6; i++)
chans[chan].ctl[i] = 0;
break;
}
case 5: COVER("MIDI pitch bend");
cmd += 2; break;
case 6: COVER("MIDI poly aftertouch");
cmd += 2; break;
case 7: COVER("MIDI system exclusive");
if (chan == 0xf) init(patchmap, samplerate);
break; }
}
}
void setGlobals(const uint8_t *para)
{
if (!initialized)
return;
float *globf = (float *)&globals;
for (int i = 0; i < (int)(sizeof(globals)/sizeof(float)); i++)
globf[i] = para[i];
reverb.set(&globals.rvbparm);
delay.set(&globals.delparm);
compr.set(&globals.cprparm);
lcfreq = sqr((globals.vlowcut + 1.0f) / 128.0f);
hcfreq = sqr((globals.vhighcut + 1.0f) / 128.0f);
}
void getPoly(int *dest)
{
for (int i = 0; i <= CHANS; i++)
dest[i] = 0;
for (int i = 0; i < POLY; i++)
{
int chan = chanmap[i];
if (chan < 0)
continue;
dest[chan]++;
dest[CHANS]++;
}
}
void getPgm(int *dest)
{
for (int i = 0; i < CHANS; i++)
dest[i] = chans[i].pgm;
}
private:
const V2Sound *getpatch(int pgm) const
{
assert(pgm >= 0 && pgm < 128);
return (const V2Sound *)&patchmap->raw_data[patchmap->offsets[pgm]];
}
float getmodsource(const V2Voice *voice, int chan, int source) const
{
float in = 0.0f;
switch (source)
{
case 0: COVER("MOD src vel");
in = voice->velo;
break;
case 1: case 2: case 3: case 4: case 5: case 6: case 7: COVER("MOD src ctl");
in = chans[chan].ctl[source-1];
break;
case 8: case 9: COVER("MOD src EG");
in = voice->env[source-8].out;
break;
case 10: case 11: COVER("MOD src LFO");
in = voice->lfo[source-10].out;
break;
default: COVER("MOD src note");
in = 2.0f * (voice->note - 48.0f);
break;
}
return in;
}
void storeV2Values(int vind)
{
assert(vind >= 0 && vind < POLY);
int chan = chanmap[vind];
if (chan < 0)
return;
const V2Sound *patch = getpatch(chans[chan].pgm);
syVV2 *vpara = &voicesv[vind];
float *vparaf = (float *)vpara;
V2Voice *voice = &voicesw[vind];
for (int i = 0; i < COUNTOF(patch->voice); i++)
vparaf[i] = (float)patch->voice[i];
for (int i = 0; i < patch->modnum; i++)
{
const V2Mod *mod = &patch->modmatrix[i];
if (mod->dest >= COUNTOF(patch->voice))
continue;
float scale = (mod->val - 64.0f) / 64.0f;
vparaf[mod->dest] = clamp(vparaf[mod->dest] + scale*getmodsource(voice, chan, mod->source), 0.0f, 128.0f);
}
voice->set(vpara);
}
void storeChanValues(int chan)
{
assert(chan >= 0 && chan < CHANS);
const V2Sound *patch = getpatch(chans[chan].pgm);
syVChan *cpara = &chansv[chan];
float *cparaf = (float *)cpara;
V2Chan *cwork = &chansw[chan];
V2Voice *voice = &voicesw[voicemap[chan]];
for (int i = 0; i < COUNTOF(patch->chan); i++)
cparaf[i] = (float)patch->chan[i];
for (int i = 0; i < patch->modnum; i++)
{
const V2Mod *mod = &patch->modmatrix[i];
int dest = mod->dest - COUNTOF(patch->voice);
if (dest < 0 || dest >= COUNTOF(patch->chan))
continue;
float scale = (mod->val - 64.0f) / 64.0f;
cparaf[dest] = clamp(cparaf[dest] + scale*getmodsource(voice, chan, mod->source), 0.0f, 128.0f);
}
cwork->set(cpara);
}
void tick()
{
for (int i = 0; i < POLY; i++)
{
if (chanmap[i] < 0)
continue;
storeV2Values(i);
voicesw[i].tick();
if (voicesw[i].env[0].state == V2Env::OFF)
chanmap[i] = -1;
}
for (int i = 0; i < CHANS; i++)
storeChanValues(i);
ronanCBTick(&ronan);
tickd = instance.SRcFrameSize;
renderFrame();
#if COVERAGE
static int cur_frame;
static const char *old_coverage[COUNTOF(code_coverage)];
int ccount = synthGetNumCoverage();
for (int i = 0; i < ccount; i++)
{
if (old_coverage[i] != code_coverage[i])
{
old_coverage[i] = code_coverage[i];
printf("[%5d,%3d] %s\n", cur_frame, i, code_coverage[i]);
}
}
cur_frame++;
#endif
}
void renderFrame()
{
int nsamples = instance.SRcFrameSize;
memset(instance.mixbuf, 0, nsamples * sizeof(StereoSample));
memset(instance.aux1buf, 0, nsamples * sizeof(float));
memset(instance.aux2buf, 0, nsamples * sizeof(float));
memset(instance.auxabuf, 0, nsamples * sizeof(StereoSample));
memset(instance.auxbbuf, 0, nsamples * sizeof(StereoSample));
for (int chan = 0; chan < CHANS; chan++)
{
int voice = 0;
while (voice < POLY && chanmap[voice] != chan)
voice++;
if (voice == POLY)
continue;
memset(instance.chanbuf, 0, nsamples * sizeof(StereoSample));
for (; voice < POLY; voice++)
{
if (chanmap[voice] != chan)
continue;
voicesw[voice].render(instance.chanbuf, nsamples);
}
if (chan == CHANS - 1)
ronanCBProcess(&ronan, &instance.chanbuf[0].l, nsamples);
chansw[chan].process(nsamples);
}
StereoSample *mix = instance.mixbuf;
reverb.render(mix, nsamples);
delay.renderAux2Main(mix, nsamples);
dcf.renderStereo(mix, mix, nsamples);
float lcf = lcfreq, hcf = hcfreq;
for (int i = 0; i < nsamples; i++)
{
for (int ch = 0; ch < 2; ch++)
{
float x = mix[i].ch[ch] - lcbuf[ch];
lcbuf[ch] += lcf * x;
if (hcf != 1.0f)
{
hcbuf[ch] += hcf * (x - hcbuf[ch]);
x = hcbuf[ch];
}
mix[i].ch[ch] = x;
}
}
compr.render(mix, nsamples);
DEBUG_PLOT_STEREO(mix, mix, nsamples);
}
};
unsigned int synthGetSize()
{
return sizeof(V2Synth);
}
void synthInit(void *pthis, const void *patchmap, int samplerate)
{
((V2Synth *)pthis)->init(patchmap, samplerate);
}
void synthRender(void *pthis, void *buf, int smp, void *buf2, int add)
{
((V2Synth *)pthis)->render((float *)buf, smp, (float *)buf2, add != 0);
}
void synthProcessMIDI(void *pthis, const void *ptr)
{
((V2Synth *)pthis)->processMIDI((const uint8_t *)ptr);
}
void synthSetGlobals(void *pthis, const void *ptr)
{
((V2Synth *)pthis)->setGlobals((const uint8_t *)ptr);
}
void synthGetPoly(void *pthis, void *dest)
{
((V2Synth *)pthis)->getPoly((int*)dest);
}
void synthGetPgm(void *pthis, void *dest)
{
((V2Synth *)pthis)->getPgm((int*)dest);
}
void synthSetVUMode(void *, int)
{
}
void synthGetChannelVU(void *, int, float *, float *)
{
}
void synthGetMainVU(void *, float *, float *)
{
}
long synthGetFrameSize(void *pthis)
{
return ((V2Synth *)pthis)->instance.SRcFrameSize;
}
extern "C" void *synthGetSpeechMem(void *pthis)
{
return &((V2Synth *)pthis)->ronan;
}
#if COVERAGE
void synthPrintCoverage()
{
int end = synthGetNumCoverage();
printf("synth coverage:\n");
for (int i = 0; i < end; i++)
printf("[%3d] %s\n", i, code_coverage[i] ? code_coverage[i] : "<not hit>");
}
static int synthGetNumCoverage()
{
return __COUNTER__;
}
#endif