vcf, and expo converter calculation
This commit is contained in:
parent
bb5e064e1a
commit
3d0d07320a
|
@ -90,7 +90,7 @@ struct _nekobee_voice_t {
|
||||||
#define _RELEASED(voice) ((voice)->status == XSYNTH_VOICE_RELEASED)
|
#define _RELEASED(voice) ((voice)->status == XSYNTH_VOICE_RELEASED)
|
||||||
#define _AVAILABLE(voice) ((voice)->status == XSYNTH_VOICE_OFF)
|
#define _AVAILABLE(voice) ((voice)->status == XSYNTH_VOICE_OFF)
|
||||||
|
|
||||||
extern float nekobee_pitch[128];
|
extern float nekobee_pitch[129];
|
||||||
|
|
||||||
/* nekobee_voice.c */
|
/* nekobee_voice.c */
|
||||||
nekobee_voice_t *nekobee_voice_new();
|
nekobee_voice_t *nekobee_voice_new();
|
||||||
|
|
|
@ -24,13 +24,14 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "nekobee_synth.h"
|
#include "nekobee_synth.h"
|
||||||
|
#include "nekobee_voice.h"
|
||||||
|
|
||||||
// centre oscillator around Middle C
|
// centre oscillator around Middle C
|
||||||
// conveniently the middle of the 303's range
|
// conveniently the middle of the 303's range
|
||||||
#define REF_NOTE 60
|
#define REF_NOTE 60
|
||||||
|
|
||||||
float nekobee_pitch[128];
|
float nekobee_pitch[129];
|
||||||
float logpot[128];
|
float logpot[129];
|
||||||
|
|
||||||
void nekobee_init_tables(void) {
|
void nekobee_init_tables(void) {
|
||||||
// create tables used by Nekobee to save on expensive calculations
|
// 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
|
// 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
|
// for a range of "0 to 1" scaled to 0-127, gives a log response
|
||||||
// with 50% of "pot rotation" giving 15% output
|
// with 50% of "pot rotation" giving 15% output
|
||||||
x = i / 128.0f; // pot input from 0 to 1
|
x = i / 127.0f; // pot input from 0 to 1
|
||||||
logpot[i] = 0.0323 * powf(32, x) - 0.0323;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,36 +95,84 @@ void vco(nekobee_synth_t *synth, uint32_t count) {
|
||||||
phase -= 1.0f;
|
phase -= 1.0f;
|
||||||
}
|
}
|
||||||
delay += phase; // save value for next time
|
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->phase = phase;
|
||||||
osc->delay = delay;
|
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
|
// run a 4-pole ladder filter over a block
|
||||||
// this is a crude implementation that only approximates the complex
|
// this is a crude implementation that only approximates the complex
|
||||||
// behaviour of the "real" ladder filter
|
// behaviour of the "real" ladder filter
|
||||||
|
|
||||||
nekobee_voice_t *voice = synth->voice;
|
// to calculate the cutoff frequency we need to solve the expo converter
|
||||||
printf("cutoff set to %f\n", synth->cutoff);
|
// not as bad as it sounds!
|
||||||
(void)voice;
|
// the equation is IcQ11 = IcQ10 * exp(-VbQ10 / 26)
|
||||||
(void)count;
|
// 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) {
|
void nekobee_voice_render(nekobee_synth_t *synth, float *out, uint32_t count) {
|
||||||
// generate "count" samples into the buffer at out
|
// generate "count" samples into the buffer at out
|
||||||
|
|
||||||
vco(synth, count);
|
vco(synth, count);
|
||||||
|
|
||||||
vcf(synth, count);
|
vcf(synth, out, count);
|
||||||
|
|
||||||
for(uint32_t i=0; i<count; i++) {
|
|
||||||
out[i] = synth->voice->osc_audio[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue