From 9d642475989cb42a586fc336545d2975df67b40b Mon Sep 17 00:00:00 2001 From: Gordon JC Pearce Date: Tue, 23 Dec 2025 00:20:00 +0000 Subject: [PATCH] declick things, now just needs parameter tuning --- plugin/chorus.cpp | 8 +++++-- plugin/chorus.hpp | 4 ++++ plugin/module.cpp | 41 +++++++++++++++++++++++++++++--- plugin/module.hpp | 21 +++++++++++++---- plugin/peacock.cpp | 8 +++---- plugin/voice.cpp | 58 +++++++++++++++++++++++++--------------------- 6 files changed, 99 insertions(+), 41 deletions(-) diff --git a/plugin/chorus.cpp b/plugin/chorus.cpp index 9ec57f6..a20ac21 100644 --- a/plugin/chorus.cpp +++ b/plugin/chorus.cpp @@ -29,6 +29,8 @@ Chorus::Chorus() { lfoPhase = 1; lfoSpeed = 6.283 * 10.7 / sampleRate; // plainly silly value to show if it hasn't been set + gainTC = 1 - exp(-6.283 * 10 / sampleRate); + // not quite Butterworth but you'd never hear the difference // these are calculated from the real-world component values postFilter1l = new SVF(9688, .549); @@ -108,8 +110,10 @@ void Chorus::run(float* input, float** outputs, uint32_t frames) { for (uint32_t i = 0; i < frames; i++) { float y = input[i]; - outputs[0][i] = y + (gain * lpfOut1[i]); - outputs[1][i] = y + (gain * lpfOut2[i]); + gainRC = (gain - gainRC) * gainTC + gainRC; + + outputs[0][i] = y + (gainRC * lpfOut1[i]); + outputs[1][i] = y + (gainRC * lpfOut2[i]); } } diff --git a/plugin/chorus.hpp b/plugin/chorus.hpp index 49748ad..c55b72e 100644 --- a/plugin/chorus.hpp +++ b/plugin/chorus.hpp @@ -40,8 +40,12 @@ class Chorus { private: double lfoPhase = 0, lfoSpeed = 0; uint8_t lfoState=0; + float gain = 1.2; + float gainRC = 0; + float gainTC = 0; + uint16_t delayptr = 0; diff --git a/plugin/module.cpp b/plugin/module.cpp index 3c4d7a5..af93a7b 100644 --- a/plugin/module.cpp +++ b/plugin/module.cpp @@ -18,14 +18,29 @@ #include "module.hpp" +#include #include #include "tables.hpp" Module::Module() { + // cutoff frequencies for various RC networks + vcaTC = 1 - exp(-6.283 * 159 / sampleRate); // VCA and VCF 10k/0.1u time constant + subTC = 1 - exp(-6.283 * 15 / sampleRate); // Main VCA and Sub Level 1k + 10u time constant + pwmTC = 1 - exp(-6.283 * 40 / sampleRate); // integrator with 100k/0.047u time constant + + vcaBuf = new float[bufferSize]; + subBuf = new float[bufferSize]; + pwmBuf = new float[bufferSize]; } -void Module::run(Voice* voice) { +Module::~Module() { + printf("module destructor\n"); + delete vcaBuf; + delete subBuf; + delete pwmBuf; +} +void Module::run(Voice* voices, uint32_t blockSize) { // run updates for module board // FIXME break these out to the patch setter @@ -42,6 +57,7 @@ void Module::run(Voice* voice) { res = patchRam.vcfReso / 127.0 * 5; noise = patchRam.noise / 127.0; + // FIXME the exp in these is expensive, don't call it all the time chorus->setChorus(patchRam.switch1 & 0x60); chorus->setHpf(patchRam.switch2 & 0x18); @@ -50,9 +66,26 @@ void Module::run(Voice* voice) { 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; + float master = powf(2, (patchRam.vca / 31.75 - 4.0f)); + float sub = patchRam.sub/ 127.0f; + + for (uint32_t i = 0; i < blockSize; i++) { + vcaRC = (master - vcaRC) * subTC + vcaRC; + pwmRC = (pw - pwmRC) * pwmTC + pwmRC; + subRC = (sub - subRC) * vcaTC + subRC; + + vcaBuf[i] = vcaRC; + pwmBuf[i] = pwmRC; + subBuf[i] = subRC; + + + if (bufPtr < bufferSize) bufPtr++; + } + int16_t vcf = (patchRam.vcfEnv << 7) * ((patchRam.switch2 & 0x02) ? -1 : 1); int16_t pitchBase = 0x1818; @@ -60,7 +93,7 @@ void Module::run(Voice* voice) { for (uint32_t i = 0; i < NUM_VOICES; i++) { // maybe move all this into voice.cpp FIXME - Voice* v = &voice[i]; + Voice* v = &voices[i]; switch (v->envPhase) { case 0: // release phase FIXME use an enum I guess v->env = (v->env * r) >> 16; // "RC" decay to zero @@ -78,6 +111,7 @@ void Module::run(Voice* voice) { } // pitch + // FIXME clean this all up a bit int16_t pitch = pitchBase + (v->note << 8); int16_t semi = pitch >> 8; float frac = (pitch & 0xff) / 256.0; @@ -90,11 +124,12 @@ void Module::run(Voice* voice) { v->omega = px / (sampleRate * 4.0f); // fixme use proper scaler // per voice we need to calculate the key follow amount and envelope amount - v->vcfCut = (patchRam.vcfFreq << 7) + ((vcf * v->env) >> 16); + v->vcfCut = (patchRam.vcfFreq << 7) + ((vcf * v->env) >> 14); v->vcfCut += (int)(v->note * (patchRam.vcfKey << 1) * 0.375); if (v->vcfCut > 0x3fff) v->vcfCut = 0x3fff; if (v->vcfCut < 0) v->vcfCut = 0; + v->vcaEnv = (patchRam.switch2 & 0x04) ? (v->envPhase ? 0x3fff : 0) : v->env; } } diff --git a/plugin/module.hpp b/plugin/module.hpp index 91d4b50..057bea8 100644 --- a/plugin/module.hpp +++ b/plugin/module.hpp @@ -31,8 +31,9 @@ class Voice; class Module { public: Module(); + ~Module(); - void run(Voice* voice); + void run(Voice* voices, uint32_t blockLeft); float res = 0; // precomputed values for all voices @@ -45,6 +46,7 @@ class Module { float saw = 0, square = 0, sub = 0, noise = 0; + struct { uint8_t lfoRate = 0x18; uint8_t lfoDelay = 0x00; @@ -67,7 +69,18 @@ class Module { } patchRam; Chorus* chorus; + float vcaTC; + uint32_t bufPtr = 0; + + float* vcaBuf; + float* subBuf; + float* pwmBuf; + private: + // precalculated coefficients for RC networks + float pwmTC = 0, subTC = 0, mVcaTC = 0; + float pwmRC = 0, subRC = 0, vcaRC = 0; + // controls }; @@ -81,9 +94,6 @@ class Voice { void run(Module* m, float* buffer, uint32_t samples); private: - // control - float vcaRC = 0, vcfRC = 0; - float omega = 0, theta = 0; // phase increment and angle FIXME better names float delay = 0, lastpw = 0; // delay slots for antialiasing uint8_t pulseStage = 1; // pulse wave phase @@ -93,7 +103,8 @@ class Voice { int16_t env = 0; // output amplitude int16_t vcfCut; int16_t vcaEnv; - float vcaEnvRC = 0; + float vcaRC = 0, vcfRC = 0; + uint8_t note = 0; // filter diff --git a/plugin/peacock.cpp b/plugin/peacock.cpp index a714502..3b22de6 100644 --- a/plugin/peacock.cpp +++ b/plugin/peacock.cpp @@ -33,8 +33,6 @@ Peacock::Peacock() : Plugin(parameterCount, 0, 0) { ic1 = new Assigner; ic1->voice = voice; m->chorus = chorus; - - } Peacock::~Peacock() { @@ -73,6 +71,7 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve // if there were any events that happen between now and the end of this block, process them lastEvent = 0; + m->bufPtr = 0; runMidi(midiEvents, midiEventCount, blockLeft); while (framePos < frames) { @@ -81,11 +80,12 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve blockLeft = sampleRate / 238; // update rate in Hz runMidi(midiEvents, midiEventCount, framePos + blockLeft); - m->run(voice); } // how many frames to do? Are we about to run off an update block sizeThisTime = (framesLeft < blockLeft) ? framesLeft : blockLeft; + m->run(voice, sizeThisTime); + // now run all the voices for this chunk of samples for (uint32_t i = 0; i < NUM_VOICES; i++) { @@ -98,8 +98,8 @@ 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); chorus->run(outputs[0], outputs, frames); - } Plugin* createPlugin() { return new Peacock(); } diff --git a/plugin/voice.cpp b/plugin/voice.cpp index b734c13..460974d 100644 --- a/plugin/voice.cpp +++ b/plugin/voice.cpp @@ -40,9 +40,11 @@ Voice::Voice() { } void Voice::on(uint8_t midiNote) { - //omega = 261.63 * powf(2, (note - 60) / 12.0f) / 48000.0f; - if (midiNote>24) note = midiNote-24; - else note = 24; + // omega = 261.63 * powf(2, (note - 60) / 12.0f) / 48000.0f; + if (midiNote > 24) + note = midiNote - 24; + else + note = 24; envPhase = 1; } @@ -54,12 +56,11 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) { // carry out per-voice calculations for each block of samples float out, t, fb; - //float cut = 0.00513 + 0.0000075*env; - + // FIXME incorrect // calculate cutoff frequency float cut = 248.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f)); - cut = 0.25 * 6.2832 * cut / 48000.0f; // FIXME hardcoded values - cut = cut/(1+cut); // correct tuning warp + cut = 0.25 * 6.2832 * cut / 48000.0f; // FIXME hardcoded values + cut = cut / (1 + cut); // correct tuning warp float amp = vcaEnv / 4096.0f; @@ -71,8 +72,8 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) { while (true) { if (pulseStage == 0) { - if (theta < m->pw) break; - t = (theta - m->pw) / (lastpw - m->pw + omega); + if (theta < m->pwmBuf[i]) break; + t = (theta - m->pwmBuf[i]) / (lastpw - m->pwmBuf[i] + omega); out -= poly3blep0(t) * m->square; delay -= poly3blep1(t) * m->square; pulseStage = 1; @@ -84,8 +85,8 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) { out += poly3blep0(t) * (m->saw + m->square); delay += poly3blep1(t) * (m->saw + m->square); - out -= poly3blep0(t) * (m->sub * subosc); - delay -= poly3blep1(t) * (m->sub * subosc); + out -= poly3blep0(t) * (m->subBuf[i] * subosc); + delay -= poly3blep1(t) * (m->subBuf[i] * subosc); pulseStage = 0; subosc = -subosc; @@ -93,33 +94,36 @@ 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); - delay += m->sub * subosc; - // delay += (1-(m->noisegen/(float)(1<<30))) * m->noise; FIXME figure out what to do about noise - - out += m->noise * (0.8-1.6 *(rand() & 0xffff) / 65536.0); + 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.5; + // same time constant for both VCF and VCF RC circuits + vcfRC = (cut - vcfRC) * m->vcaTC + vcfRC; + for (uint8_t ovs = 0; ovs < 4; ovs++) { fb = b4; // hard clip - fb = ((out*0.5) - fb) * m->res; + fb = ((out * 0.5) - fb) * m->res; if (fb > 4) fb = 4; if (fb < -4) fb = -4; - // fb = 1.5 * fb - 0.5 * fb * fb * fb; -// + // fb = 1.5 * fb - 0.5 * fb * fb * fb; + // - b1 = ((out + fb - b1) * cut) + b1; - b2 = ((b1 - b2) * cut) + b2; - b3 = ((b2 - b3) * cut) + b3; - b4 = ((b3 - b4) * cut) + b4; + b1 = ((out + fb - b1) * vcfRC) + b1; + b2 = ((b1 - b2) * vcfRC) + b2; + b3 = ((b2 - b3) * vcfRC) + b3; + b4 = ((b3 - b4) * vcfRC) + b4; } - vcaEnvRC = (amp - vcaEnvRC) * 0.0203 + vcaEnvRC; - - buffer[i] += 0.0367 * vcaEnvRC * b4; - lastpw = m->pw; + vcaRC = (amp - vcaRC) * m->vcaTC + vcaRC; + buffer[i] += 0.09367 * m->vcaBuf[i] * vcaRC * b4; + + lastpw = m->pwmBuf[i]; } // buffer[0] += 1; }