diff --git a/plugins/Nekobi/nekobee-src/nekobee_voice.h b/plugins/Nekobi/nekobee-src/nekobee_voice.h index fc4c4ff..402a937 100644 --- a/plugins/Nekobi/nekobee-src/nekobee_voice.h +++ b/plugins/Nekobi/nekobee-src/nekobee_voice.h @@ -90,7 +90,7 @@ struct _nekobee_voice_t { #define _RELEASED(voice) ((voice)->status == XSYNTH_VOICE_RELEASED) #define _AVAILABLE(voice) ((voice)->status == XSYNTH_VOICE_OFF) -extern float nekobee_pitch[128]; +extern float nekobee_pitch[129]; /* nekobee_voice.c */ nekobee_voice_t *nekobee_voice_new(); diff --git a/plugins/Nekobi/nekobee-src/nekobee_voice_render.c b/plugins/Nekobi/nekobee-src/nekobee_voice_render.c index bb3ac03..f97e481 100644 --- a/plugins/Nekobi/nekobee-src/nekobee_voice_render.c +++ b/plugins/Nekobi/nekobee-src/nekobee_voice_render.c @@ -24,13 +24,14 @@ #include #include "nekobee_synth.h" +#include "nekobee_voice.h" // centre oscillator around Middle C // conveniently the middle of the 303's range #define REF_NOTE 60 -float nekobee_pitch[128]; -float logpot[128]; +float nekobee_pitch[129]; +float logpot[129]; void nekobee_init_tables(void) { // create tables used by Nekobee to save on expensive calculations @@ -51,9 +52,13 @@ void nekobee_init_tables(void) { // log pot scale used for volume, decay, cutoff, and env mod // for a range of "0 to 1" scaled to 0-127, gives a log response // with 50% of "pot rotation" giving 15% output - x = i / 128.0f; // pot input from 0 to 1 - logpot[i] = 0.0323 * powf(32, x) - 0.0323; + x = i / 127.0f; // pot input from 0 to 1 + logpot[i] = 0.03225 * powf(32, x) - 0.03225; } + // one extra value so we don't need to bounds check the linear interpolator + logpot[128] = logpot[127]; + nekobee_pitch[128] = nekobee_pitch[127]; + return; } @@ -90,36 +95,84 @@ void vco(nekobee_synth_t *synth, uint32_t count) { phase -= 1.0f; } delay += phase; // save value for next time - voice->osc_audio[i] = 0.5 - out; // save output in buffer, remove DC offset + voice->osc_audio[i] = + 0.5 - out; // save output in buffer, remove DC offset } osc->phase = phase; osc->delay = delay; } -void vcf(nekobee_synth_t *synth, uint32_t count) { +void vcf(nekobee_synth_t *synth, float *out, uint32_t count) { // run a 4-pole ladder filter over a block // this is a crude implementation that only approximates the complex // behaviour of the "real" ladder filter - nekobee_voice_t *voice = synth->voice; - printf("cutoff set to %f\n", synth->cutoff); - (void)voice; - (void)count; + // to calculate the cutoff frequency we need to solve the expo converter + // not as bad as it sounds! + // the equation is IcQ11 = IcQ10 * exp(-VbQ10 / 26) + // VbQ10 is the voltage on Q10's base, IcQ10 is the collector current + // this is supplied from the cutoff pot + nekobee_voice_t *voice = synth->voice; + + float delay1 = voice->delay1, delay2 = voice->delay2, + delay3 = voice->delay3, delay4 = voice->delay4; + + // to get the correct cutoff first we need Q10's collector current + // The top of VR3 Cutoff is fed from 12V, the bottom from the emitter + // of Q9 at around 3.2V through a 10k resistor. So, 8.8V between "rails" + // gives us (8.8*10k)/(10k+50k) = 1.47V at the bottom + // The wiper of VR3 goes through R73 100k and TM3 470k, which we'll assume + // is set to about half, call it 300k in total + // So IcQ10 is given by (Vcutoff - Vbias - Vbe) / 300 + // For ease of working I just assume that Vbias is 0V and that the envelope + // can go negative, from about 7V to about -3V the range of Vcutoff is + // then 1.47 to 8.88-1.47 so 7.41V max + + float Vcutoff = 1.47 + 7.41 * logpot[(int)floor(synth->cutoff)]; + + float Vbe1 = -5.077; // mV + + // .3 is 300k expressed as MOhm + // if we expressed it in Ohms output would be in A + float IcQ10 = (Vcutoff - 0.65) / .3; // 100k + TM3, IcQ10 in uA + float IcQ11 = IcQ10 * exp(Vbe1 / 26.0); // in uA + + // printf("Vbe1 = %04f, IcQ10 = %04f, IcQ11 = %04f\n", Vbe1, IcQ10, IcQ11); + + float cutoff = IcQ11 * 96.67; // approximate Hz-per-uA + + float ct = 6.2832 * cutoff * synth->deltat; + + ct *= 0.25; // 4x oversampling + ct = ct / (1 + ct); + // printf("cutoff = %04fHz, ct=%f\n", cutoff, ct); + + for (uint32_t i = 0; i < count; i++) { + for (uint32_t ovs = 0; ovs < 4; ovs++) { + float in = voice->osc_audio[i]; + float fb = in - (delay4 * synth->resonance * 4); + if (fb > 3) fb = 3; + if (fb < -3) fb = -3; + delay1 = ((fb - delay1) * ct) + delay1; + delay2 = ((delay1 - delay2) * ct) + delay2; + delay3 = ((delay2 - delay3) * ct) + delay3; + delay4 = ((delay3 - delay4) * ct) + delay4; + } + out[i] = delay4; + } + + voice->delay1 = delay1; + voice->delay2 = delay2; + voice->delay3 = delay3; + voice->delay4 = delay4; } - + void nekobee_voice_render(nekobee_synth_t *synth, float *out, uint32_t count) { // generate "count" samples into the buffer at out vco(synth, count); - vcf(synth, count); - - for(uint32_t i=0; ivoice->osc_audio[i]; - } - - - + vcf(synth, out, count); return; }