From a8464e6d12f26cdc382bff4eaaccd5a728ddda74 Mon Sep 17 00:00:00 2001 From: Gordon JC Pearce Date: Thu, 1 Jan 2026 01:06:51 +0000 Subject: [PATCH] better noise, sticking with original filter, lfo cleanup --- plugin/chorus.cpp | 2 +- plugin/module.cpp | 55 ++++++++++++++---------- plugin/module.hpp | 19 +++++---- plugin/peacock.cpp | 12 +++--- plugin/voice.cpp | 101 ++++++--------------------------------------- 5 files changed, 63 insertions(+), 126 deletions(-) diff --git a/plugin/chorus.cpp b/plugin/chorus.cpp index 1a16739..0dad569 100644 --- a/plugin/chorus.cpp +++ b/plugin/chorus.cpp @@ -59,7 +59,7 @@ void Chorus::run(float* input, float** outputs, uint32_t frames) { // run highpass / bass boost and stereo chorus effect for one full block float s0 = 0, s1 = 0; - float lfoMod, dly1, frac, flt; + float dly1, frac, flt; uint16_t tap, delay; for (uint32_t i = 0; i < frames; i++) { diff --git a/plugin/module.cpp b/plugin/module.cpp index aff551c..1d4c3a6 100644 --- a/plugin/module.cpp +++ b/plugin/module.cpp @@ -32,6 +32,7 @@ Module::Module() { vcaBuf = new float[bufferSize]; subBuf = new float[bufferSize]; pwmBuf = new float[bufferSize]; + noiseBuf = new float[bufferSize]; } Module::~Module() { @@ -41,15 +42,21 @@ Module::~Module() { delete pwmBuf; } +void Module::genNoise() { + for (uint32_t i = 0; i < bufferSize; i++) { + noiseRNG *= 0x8088405; + noiseRNG++; + noiseBuf[i] = 2 - (noiseRNG & 0xffff) / 16384.0f; + } +} + void Module::lfoRampOn() { lfoDelayState = 1; lfoDelayTimer = 0; lfoDelay = 0; } -void Module::run(Voice* voices, uint32_t blockSize) { - // run updates for module board - +void Module::runLFO() { if (lfoDelayState == 1) { lfoDelayTimer += lfoDelayTable[patchRam.lfoDelay >> 4]; if (lfoDelayTimer & 0xc000) lfoDelayState = 2; @@ -63,41 +70,45 @@ void Module::run(Voice* voices, uint32_t blockSize) { lfoDelay = 0x3fff; } + lfoPhase += lfoRateTable[patchRam.lfoRate]; + if (lfoPhase & 0x4000) + lfo = 0x1fff - (lfoPhase & 0x3fff); + else + lfo = (lfoPhase & 0x3fff) - 0x1fff; + + pw = 0x3fff-(((0x2000 + lfo) * patchRam.pwmLfo) >> 7); + pw = (patchRam.switch2 & 0x01) ? 0x3fff - (patchRam.pwmLfo << 7 ) : pw; + lfo = (lfo * lfoDelay) >> 14; +} + +void Module::run(Voice* voices, uint32_t blockSize) { + // run updates for module board + // FIXME break these out to the patch setter a = attackTable[patchRam.env_a]; // attack time coeff looked up in table d = decayTable[patchRam.env_d]; // decay time coeff looked up in table r = decayTable[patchRam.env_r]; // release time coeff looked up in table s = patchRam.env_s << 7; // scale 0x00-0x7f to 0x0000-0x3f80 - square = (patchRam.switch1 & 0x08) ? 0.63 : 0; - saw = (patchRam.switch1 & 0x10) ? 0.8 : 0; - sub = patchRam.sub / 127.0f; - lfoPhase += lfoRateTable[patchRam.lfoRate]; + master = powf(2, (patchRam.vca / 31.75 - 4.0f)) * 0.1; + + square = (patchRam.switch1 & 0x08) ? 0.28 : 0; + saw = (patchRam.switch1 & 0x10) ? .36 : 0; + sub = (patchRam.sub / 127.0f) * 0.4; res = patchRam.vcfReso / 127.0; - noise = patchRam.noise / 127.0; + noise = (patchRam.noise / 127.0) * 0.4; // FIXME the exp in these is expensive, don't call it all the time chorus->setChorus(patchRam.switch1 & 0x60); chorus->setHpf(patchRam.switch2 & 0x18); - if (lfoPhase & 0x4000) - lfo = 0x1fff - (lfoPhase & 0x3fff); - else - lfo = (lfoPhase & 0x3fff) - 0x1fff; - - // FIXME represent PW as int until we calculate the block? - pw = 0.5 - ((0x2000 + lfo) * patchRam.pwmLfo) / (32768.0f * 128); - pw = (patchRam.switch2 & 0x01) ? 0.5 - (patchRam.pwmLfo / 256.0f) : pw; - - lfo = (lfo * lfoDelay) >> 14; - - float master = powf(2, (patchRam.vca / 31.75 - 4.0f)); - float sub = patchRam.sub / 127.0f; + runLFO(); + float pwf = pw / 32768.0f; for (uint32_t i = 0; i < blockSize; i++) { vcaRC = (master - vcaRC) * subTC + vcaRC; - pwmRC = (pw - pwmRC) * pwmTC + pwmRC; + pwmRC = (pwf - pwmRC) * pwmTC + pwmRC; subRC = (sub - subRC) * vcaTC + subRC; vcaBuf[i] = vcaRC; diff --git a/plugin/module.hpp b/plugin/module.hpp index 6e4ebf8..f23966e 100644 --- a/plugin/module.hpp +++ b/plugin/module.hpp @@ -33,20 +33,16 @@ class Module { Module(); ~Module(); + void genNoise(); void lfoRampOn(); - void run(Voice* voices, uint32_t blockLeft); float res = 0; - // precomputed values for all voices - float pw; //, saw, square, sub; // "internal state" values for patch parameters uint16_t a, d, s, r; - int16_t lfo; - uint32_t lfoPhase; - float saw = 0, square = 0, sub = 0, noise = 0; + float saw = 0, square = 0, sub = 0, noise = 0, master = 0; /* #if 0 @@ -124,12 +120,19 @@ class Module { float* vcaBuf; float* subBuf; float* pwmBuf; + float* noiseBuf; private: + void runLFO(); // precalculated coefficients for RC networks float pwmTC = 0, subTC = 0, mVcaTC = 0; float pwmRC = 0, subRC = 0, vcaRC = 0; + int16_t lfo, pw; + uint32_t lfoPhase; + + uint32_t noiseRNG = 1; + uint16_t lfoDelay = 0; uint8_t lfoDelayState = 0; uint16_t lfoDelayTimer = 0; @@ -142,7 +145,7 @@ class Voice { Voice(); void on(uint8_t midiNote); void off(); - void run(Module* m, float* buffer, uint32_t samples); + void run(Module* m, float* buffer, uint32_t framePos, uint32_t samples); private: float omega = 0, theta = 0; // phase increment and angle FIXME better names @@ -160,8 +163,6 @@ class Voice { // filter float y0 = 0, y1 = 0, y2 = 0, y3 = 0; - double s[4] = {0, 0, 0, 0}; - float zi = 0; }; #endif diff --git a/plugin/peacock.cpp b/plugin/peacock.cpp index 59ee016..0c6e194 100644 --- a/plugin/peacock.cpp +++ b/plugin/peacock.cpp @@ -70,9 +70,11 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve memset(outputs[0], 0, frames * sizeof(float)); memset(outputs[1], 0, frames * sizeof(float)); + m->genNoise(); + // if there were any events that happen between now and the end of this block, process them lastEvent = 0; - m->bufPtr = 0; + m->bufPtr = 0; // reset the output buffer pointer runMidi(midiEvents, midiEventCount, blockLeft); while (framePos < frames) { @@ -80,17 +82,17 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve // no more samples to calculate in this update period blockLeft = sampleRate / 238; // update rate in Hz runMidi(midiEvents, midiEventCount, framePos + blockLeft); - } // how many frames to do? Are we about to run off an update block sizeThisTime = (framesLeft < blockLeft) ? framesLeft : blockLeft; - m->run(voice, sizeThisTime); + // update the module board for this block + m->run(voice, sizeThisTime); // now run all the voices for this chunk of samples for (uint32_t i = 0; i < NUM_VOICES; i++) { - voice[i].run(m, outputs[0] + framePos, sizeThisTime); + voice[i].run(m, outputs[0], framePos, sizeThisTime); } framePos += sizeThisTime; @@ -99,7 +101,7 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve } // now we've assembled a full chunk of audio - //memcpy(outputs[0], m->vcaBuf, sizeof(float)* frames); + // memcpy(outputs[0], m->vcaBuf, sizeof(float)* frames); chorus->run(outputs[0], outputs, frames); } diff --git a/plugin/voice.cpp b/plugin/voice.cpp index 9b268c2..92ecf8f 100644 --- a/plugin/voice.cpp +++ b/plugin/voice.cpp @@ -54,22 +54,23 @@ void Voice::off() { // so might not be safe for very large feedback gains // [limit is 1/15 so very large means ~15 or +23dB] -double tanhXdX(double x) { +float tanhXdX(float x) { + return 1 - 0.05 * abs(x); float s = 0.0333, d = 30.0; return 1.0f - s * (d + 1.0f) * x * x / (d + x * x); - } -void Voice::run(Module* m, float* buffer, uint32_t samples) { +void Voice::run(Module* m, float* buffer, uint32_t framePos, uint32_t samples) { // carry out per-voice calculations for each block of samples float out, t, fb; // calculate cutoff frequency - float cut = 261.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f)); + float cut = 261.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f)); // FIXME explain magic numbers cut = M_PI * cut / sampleRate; cut = cut / (1 + cut); // correct tuning warp - // if (cut > 0.7) cut = 0.7; - double r = 5 * m->res; + if (cut > 0.7) cut = 0.7; + + float r = 5 * m->res; float amp = vcaEnv / 4096.0f; @@ -103,109 +104,31 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) { } } - // FIXME DC offset removal delay += m->saw * (1 - (2 * theta)); delay += m->square * ((pulseStage ? -1.f : 1.f) - m->pwmBuf[i] + 0.5); delay += m->subBuf[i] * subosc; - out += m->noise * (0.8 - 1.6 * (rand() & 0xffff) / 65536.0); - // out *= 0.1; + out += m->noise * m->noiseBuf[i + framePos]; // same time constant for both VCF and VCF RC circuits vcfRC = (cut - vcfRC) * m->vcaTC + vcfRC; -#if 1 - - //// LICENSE TERMS: Copyright 2012 Teemu Voipio - // - // You can use this however you like for pretty much any purpose, - // as long as you don't claim you wrote it. There is no warranty. - // - // Distribution of substantial portions of this code in source form - // must include this copyright notice and list of conditions. - // - - // input delay and state for member variables - - // cutoff as normalized frequency (eg 0.5 = Nyquist) - // resonance from 0 to 1, self-oscillates at settings over 0.9 - // void transistorLadder( - // double cutoff, double resonance, - // double * in, double * out, unsigned nsamples) - //{ - // tuning and feedback - - //------------------------------------------------------------------------------ sample loop - // for(unsigned n = 0; n < nsamples; ++n) - //{ - - out *= 0.025; - // input with half delay, for non-linearities - double ih = 0.5 * (out + zi); - zi = out; - - // double ih = out; - - // evaluate the non-linear gains - double t0 = tanhXdX((ih * (r + 1)) - r * s[3]); - - double t1 = tanhXdX(s[0]); - double t2 = tanhXdX(s[1]); - double t3 = tanhXdX(s[2]); - double t4 = tanhXdX(s[3]); - - double f = vcfRC; - - // g# the denominators for solutions of individual stages - double g0 = 1 / (1 + f * t1), g1 = 1 / (1 + f * t2); - double g2 = 1 / (1 + f * t3), g3 = 1 / (1 + f * t4); - - // f# are just factored out of the feedback solution - double f3 = f * t3 * g3, f2 = f * t2 * g2 * f3, f1 = f * t1 * g1 * f2, f0 = f * t0 * g0 * f1; - - // solve feedback - double y3 = (g3 * s[3] + f3 * g2 * s[2] + f2 * g1 * s[1] + f1 * g0 * s[0] + f0 * out) / (1 + r * f0); - - // then solve the remaining outputs (with the non-linear gains here) - double xx = t0 * ((out * (r + 1)) - r * y3); - double y0 = t1 * g0 * (s[0] + f * xx); - double y1 = t2 * g1 * (s[1] + f * y0); - double y2 = t3 * g2 * (s[2] + f * y1); - - // update state - s[0] += 2 * f * (xx - y0); - s[1] += 2 * f * (y0 - y1); - s[2] += 2 * f * (y1 - y2); - s[3] += 2 * f * (y2 - t4 * y3); - - // out[n] = y3; - // } - // out *= 0.1; - - out = y3; - -#else - - out *= 0.5; for (uint8_t ovs = 0; ovs < 2; ovs++) { fb = y3; // hard clip fb = ((out * 0.5) - fb) * r; - if (fb > 4) fb = 4; - if (fb < -4) fb = -4; - // fb = 1.5 * fb - 0.5 * fb * fb * fb; - // + if (fb > 2) fb = 2; + if (fb < -2) fb = -2; y0 = ((out + fb - y0) * vcfRC) + y0; y1 = ((y0 - y1) * vcfRC) + y1; y2 = ((y1 - y2) * vcfRC) + y2; y3 = ((y2 - y3) * vcfRC) + y3; } -#endif vcaRC = (amp - vcaRC) * m->vcaTC + vcaRC; - buffer[i] += m->vcaBuf[i] * vcaRC * out; + buffer[framePos + i] += m->vcaBuf[i] * vcaRC * y3; lastpw = m->pwmBuf[i]; } - // buffer[0] += 1; + // buffer[0] += 1; // buzzing noise to test }