declick things, now just needs parameter tuning

This commit is contained in:
Gordon JC Pearce 2025-12-23 00:20:00 +00:00
parent 132b684db7
commit 9d64247598
6 changed files with 99 additions and 41 deletions

View File

@ -29,6 +29,8 @@ Chorus::Chorus() {
lfoPhase = 1; lfoPhase = 1;
lfoSpeed = 6.283 * 10.7 / sampleRate; // plainly silly value to show if it hasn't been set 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 // not quite Butterworth but you'd never hear the difference
// these are calculated from the real-world component values // these are calculated from the real-world component values
postFilter1l = new SVF(9688, .549); 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++) { for (uint32_t i = 0; i < frames; i++) {
float y = input[i]; float y = input[i];
outputs[0][i] = y + (gain * lpfOut1[i]); gainRC = (gain - gainRC) * gainTC + gainRC;
outputs[1][i] = y + (gain * lpfOut2[i]);
outputs[0][i] = y + (gainRC * lpfOut1[i]);
outputs[1][i] = y + (gainRC * lpfOut2[i]);
} }
} }

View File

@ -40,8 +40,12 @@ class Chorus {
private: private:
double lfoPhase = 0, lfoSpeed = 0; double lfoPhase = 0, lfoSpeed = 0;
uint8_t lfoState=0; uint8_t lfoState=0;
float gain = 1.2; float gain = 1.2;
float gainRC = 0;
float gainTC = 0;
uint16_t delayptr = 0; uint16_t delayptr = 0;

View File

@ -18,14 +18,29 @@
#include "module.hpp" #include "module.hpp"
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include "tables.hpp" #include "tables.hpp"
Module::Module() { 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 // run updates for module board
// FIXME break these out to the patch setter // FIXME break these out to the patch setter
@ -42,6 +57,7 @@ void Module::run(Voice* voice) {
res = patchRam.vcfReso / 127.0 * 5; res = patchRam.vcfReso / 127.0 * 5;
noise = patchRam.noise / 127.0; 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->setChorus(patchRam.switch1 & 0x60);
chorus->setHpf(patchRam.switch2 & 0x18); chorus->setHpf(patchRam.switch2 & 0x18);
@ -50,9 +66,26 @@ void Module::run(Voice* voice) {
else else
lfo = (lfoPhase & 0x3fff) - 0x1fff; 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 = 0.5 - ((0x2000 + lfo) * patchRam.pwmLfo) / (32768.0f * 128);
pw = (patchRam.switch2 & 0x01) ? 0.5 - (patchRam.pwmLfo / 256.0f) : pw; 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 vcf = (patchRam.vcfEnv << 7) * ((patchRam.switch2 & 0x02) ? -1 : 1);
int16_t pitchBase = 0x1818; int16_t pitchBase = 0x1818;
@ -60,7 +93,7 @@ void Module::run(Voice* voice) {
for (uint32_t i = 0; i < NUM_VOICES; i++) { for (uint32_t i = 0; i < NUM_VOICES; i++) {
// maybe move all this into voice.cpp FIXME // maybe move all this into voice.cpp FIXME
Voice* v = &voice[i]; Voice* v = &voices[i];
switch (v->envPhase) { switch (v->envPhase) {
case 0: // release phase FIXME use an enum I guess case 0: // release phase FIXME use an enum I guess
v->env = (v->env * r) >> 16; // "RC" decay to zero v->env = (v->env * r) >> 16; // "RC" decay to zero
@ -78,6 +111,7 @@ void Module::run(Voice* voice) {
} }
// pitch // pitch
// FIXME clean this all up a bit
int16_t pitch = pitchBase + (v->note << 8); int16_t pitch = pitchBase + (v->note << 8);
int16_t semi = pitch >> 8; int16_t semi = pitch >> 8;
float frac = (pitch & 0xff) / 256.0; 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 v->omega = px / (sampleRate * 4.0f); // fixme use proper scaler
// per voice we need to calculate the key follow amount and envelope amount // 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); v->vcfCut += (int)(v->note * (patchRam.vcfKey << 1) * 0.375);
if (v->vcfCut > 0x3fff) v->vcfCut = 0x3fff; if (v->vcfCut > 0x3fff) v->vcfCut = 0x3fff;
if (v->vcfCut < 0) v->vcfCut = 0; if (v->vcfCut < 0) v->vcfCut = 0;
v->vcaEnv = (patchRam.switch2 & 0x04) ? (v->envPhase ? 0x3fff : 0) : v->env; v->vcaEnv = (patchRam.switch2 & 0x04) ? (v->envPhase ? 0x3fff : 0) : v->env;
} }
} }

View File

@ -31,8 +31,9 @@ class Voice;
class Module { class Module {
public: public:
Module(); Module();
~Module();
void run(Voice* voice); void run(Voice* voices, uint32_t blockLeft);
float res = 0; float res = 0;
// precomputed values for all voices // precomputed values for all voices
@ -45,6 +46,7 @@ class Module {
float saw = 0, square = 0, sub = 0, noise = 0; float saw = 0, square = 0, sub = 0, noise = 0;
struct { struct {
uint8_t lfoRate = 0x18; uint8_t lfoRate = 0x18;
uint8_t lfoDelay = 0x00; uint8_t lfoDelay = 0x00;
@ -67,7 +69,18 @@ class Module {
} patchRam; } patchRam;
Chorus* chorus; Chorus* chorus;
float vcaTC;
uint32_t bufPtr = 0;
float* vcaBuf;
float* subBuf;
float* pwmBuf;
private: private:
// precalculated coefficients for RC networks
float pwmTC = 0, subTC = 0, mVcaTC = 0;
float pwmRC = 0, subRC = 0, vcaRC = 0;
// controls // controls
}; };
@ -81,9 +94,6 @@ class Voice {
void run(Module* m, float* buffer, uint32_t samples); void run(Module* m, float* buffer, uint32_t samples);
private: private:
// control
float vcaRC = 0, vcfRC = 0;
float omega = 0, theta = 0; // phase increment and angle FIXME better names float omega = 0, theta = 0; // phase increment and angle FIXME better names
float delay = 0, lastpw = 0; // delay slots for antialiasing float delay = 0, lastpw = 0; // delay slots for antialiasing
uint8_t pulseStage = 1; // pulse wave phase uint8_t pulseStage = 1; // pulse wave phase
@ -93,7 +103,8 @@ class Voice {
int16_t env = 0; // output amplitude int16_t env = 0; // output amplitude
int16_t vcfCut; int16_t vcfCut;
int16_t vcaEnv; int16_t vcaEnv;
float vcaEnvRC = 0; float vcaRC = 0, vcfRC = 0;
uint8_t note = 0; uint8_t note = 0;
// filter // filter

View File

@ -33,8 +33,6 @@ Peacock::Peacock() : Plugin(parameterCount, 0, 0) {
ic1 = new Assigner; ic1 = new Assigner;
ic1->voice = voice; ic1->voice = voice;
m->chorus = chorus; m->chorus = chorus;
} }
Peacock::~Peacock() { 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 // if there were any events that happen between now and the end of this block, process them
lastEvent = 0; lastEvent = 0;
m->bufPtr = 0;
runMidi(midiEvents, midiEventCount, blockLeft); runMidi(midiEvents, midiEventCount, blockLeft);
while (framePos < frames) { 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 blockLeft = sampleRate / 238; // update rate in Hz
runMidi(midiEvents, midiEventCount, framePos + blockLeft); runMidi(midiEvents, midiEventCount, framePos + blockLeft);
m->run(voice);
} }
// how many frames to do? Are we about to run off an update block // how many frames to do? Are we about to run off an update block
sizeThisTime = (framesLeft < blockLeft) ? framesLeft : blockLeft; sizeThisTime = (framesLeft < blockLeft) ? framesLeft : blockLeft;
m->run(voice, sizeThisTime);
// now run all the voices for this chunk of samples // now run all the voices for this chunk of samples
for (uint32_t i = 0; i < NUM_VOICES; i++) { 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 // now we've assembled a full chunk of audio
//memcpy(outputs[0], m->vcaBuf, sizeof(float)* frames);
chorus->run(outputs[0], outputs, frames); chorus->run(outputs[0], outputs, frames);
} }
Plugin* createPlugin() { return new Peacock(); } Plugin* createPlugin() { return new Peacock(); }

View File

@ -41,8 +41,10 @@ Voice::Voice() {
void Voice::on(uint8_t midiNote) { void Voice::on(uint8_t midiNote) {
// omega = 261.63 * powf(2, (note - 60) / 12.0f) / 48000.0f; // omega = 261.63 * powf(2, (note - 60) / 12.0f) / 48000.0f;
if (midiNote>24) note = midiNote-24; if (midiNote > 24)
else note = 24; note = midiNote - 24;
else
note = 24;
envPhase = 1; envPhase = 1;
} }
@ -54,8 +56,7 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) {
// carry out per-voice calculations for each block of samples // carry out per-voice calculations for each block of samples
float out, t, fb; float out, t, fb;
//float cut = 0.00513 + 0.0000075*env; // FIXME incorrect
// calculate cutoff frequency // calculate cutoff frequency
float cut = 248.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f)); float cut = 248.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f));
cut = 0.25 * 6.2832 * cut / 48000.0f; // FIXME hardcoded values cut = 0.25 * 6.2832 * cut / 48000.0f; // FIXME hardcoded values
@ -71,8 +72,8 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) {
while (true) { while (true) {
if (pulseStage == 0) { if (pulseStage == 0) {
if (theta < m->pw) break; if (theta < m->pwmBuf[i]) break;
t = (theta - m->pw) / (lastpw - m->pw + omega); t = (theta - m->pwmBuf[i]) / (lastpw - m->pwmBuf[i] + omega);
out -= poly3blep0(t) * m->square; out -= poly3blep0(t) * m->square;
delay -= poly3blep1(t) * m->square; delay -= poly3blep1(t) * m->square;
pulseStage = 1; pulseStage = 1;
@ -84,8 +85,8 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) {
out += poly3blep0(t) * (m->saw + m->square); out += poly3blep0(t) * (m->saw + m->square);
delay += poly3blep1(t) * (m->saw + m->square); delay += poly3blep1(t) * (m->saw + m->square);
out -= poly3blep0(t) * (m->sub * subosc); out -= poly3blep0(t) * (m->subBuf[i] * subosc);
delay -= poly3blep1(t) * (m->sub * subosc); delay -= poly3blep1(t) * (m->subBuf[i] * subosc);
pulseStage = 0; pulseStage = 0;
subosc = -subosc; subosc = -subosc;
@ -93,14 +94,17 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) {
} }
} }
// FIXME DC offset removal
delay += m->saw * (1 - (2 * theta)); delay += m->saw * (1 - (2 * theta));
delay += m->square * (pulseStage ? -1.f : 1.f); delay += m->square * ((pulseStage ? -1.f : 1.f) - m->pwmBuf[i] + 0.5);
delay += m->sub * subosc; delay += m->subBuf[i] * 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); out += m->noise * (0.8 - 1.6 * (rand() & 0xffff) / 65536.0);
out *= 0.5; 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++) { for (uint8_t ovs = 0; ovs < 4; ovs++) {
fb = b4; fb = b4;
// hard clip // hard clip
@ -110,16 +114,16 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) {
// fb = 1.5 * fb - 0.5 * fb * fb * fb; // fb = 1.5 * fb - 0.5 * fb * fb * fb;
// //
b1 = ((out + fb - b1) * cut) + b1; b1 = ((out + fb - b1) * vcfRC) + b1;
b2 = ((b1 - b2) * cut) + b2; b2 = ((b1 - b2) * vcfRC) + b2;
b3 = ((b2 - b3) * cut) + b3; b3 = ((b2 - b3) * vcfRC) + b3;
b4 = ((b3 - b4) * cut) + b4; b4 = ((b3 - b4) * vcfRC) + b4;
} }
vcaEnvRC = (amp - vcaEnvRC) * 0.0203 + vcaEnvRC; vcaRC = (amp - vcaRC) * m->vcaTC + vcaRC;
buffer[i] += 0.09367 * m->vcaBuf[i] * vcaRC * b4;
buffer[i] += 0.0367 * vcaEnvRC * b4; lastpw = m->pwmBuf[i];
lastpw = m->pw;
} }
// buffer[0] += 1; // buffer[0] += 1;
} }