cutoff, resonance, decay, and env mod working

This commit is contained in:
Gordon JC Pearce 2023-06-20 20:47:48 +01:00
parent 3d0d07320a
commit 699600315c
6 changed files with 69 additions and 38 deletions

View File

@ -95,7 +95,7 @@ DistrhoPluginNekobi::DistrhoPluginNekobi()
// Default values // Default values
fParams.waveform = 0.0f; fParams.waveform = 0.0f;
fParams.tuning = 0.0f; fParams.tuning = 0.0f;
fParams.cutoff = 25.0f; fParams.cutoff = 63.0f;
fParams.resonance = 25.0f; fParams.resonance = 25.0f;
fParams.envMod = 50.0f; fParams.envMod = 50.0f;
fParams.decay = 75.0f; fParams.decay = 75.0f;
@ -105,10 +105,10 @@ DistrhoPluginNekobi::DistrhoPluginNekobi()
// Internal stuff // Internal stuff
fSynth.waveform = 0.0f; fSynth.waveform = 0.0f;
fSynth.tuning = 1.0f; fSynth.tuning = 1.0f;
fSynth.cutoff = 5.0f; fSynth.cutoff = 63.0f;
fSynth.resonance = 0.8f; fSynth.resonance = 0.8f;
fSynth.envmod = 0.3f; fSynth.envmod = 50.0f;
fSynth.decay = 0.0002f; fSynth.decay = 75.0f;
fSynth.accent = 0.3f; fSynth.accent = 0.3f;
fSynth.volume = 0.75f; fSynth.volume = 0.75f;
@ -180,7 +180,7 @@ void DistrhoPluginNekobi::initParameter(uint32_t index, Parameter& parameter)
parameter.unit = "%"; parameter.unit = "%";
parameter.ranges.def = 25.0f; parameter.ranges.def = 25.0f;
parameter.ranges.min = 0.0f; parameter.ranges.min = 0.0f;
parameter.ranges.max = 95.0f; parameter.ranges.max = 100.0f;
parameter.midiCC = 71; parameter.midiCC = 71;
break; break;
case paramEnvMod: case paramEnvMod:
@ -190,7 +190,7 @@ void DistrhoPluginNekobi::initParameter(uint32_t index, Parameter& parameter)
parameter.unit = "%"; parameter.unit = "%";
parameter.ranges.def = 50.0f; parameter.ranges.def = 50.0f;
parameter.ranges.min = 0.0f; parameter.ranges.min = 0.0f;
parameter.ranges.max = 100.0f; parameter.ranges.max = 127.0f;
parameter.midiCC = 1; //Mod Wheel parameter.midiCC = 1; //Mod Wheel
break; break;
case paramDecay: case paramDecay:
@ -200,7 +200,7 @@ void DistrhoPluginNekobi::initParameter(uint32_t index, Parameter& parameter)
parameter.unit = "%"; parameter.unit = "%";
parameter.ranges.def = 75.0f; parameter.ranges.def = 75.0f;
parameter.ranges.min = 0.0f; parameter.ranges.min = 0.0f;
parameter.ranges.max = 100.0f; parameter.ranges.max = 127.0f;
parameter.midiCC = 72; parameter.midiCC = 72;
break; break;
case paramAccent: case paramAccent:
@ -280,13 +280,13 @@ void DistrhoPluginNekobi::setParameterValue(uint32_t index, float value)
break; break;
case paramEnvMod: case paramEnvMod:
fParams.envMod = value; fParams.envMod = value;
fSynth.envmod = value/100.0f; fSynth.envmod = value;
DISTRHO_SAFE_ASSERT(fSynth.envmod >= 0.0f && fSynth.envmod <= 1.0f); DISTRHO_SAFE_ASSERT(fSynth.envmod >= 0.0f && fSynth.envmod <= 127.0f);
break; break;
case paramDecay: case paramDecay:
fParams.decay = value; fParams.decay = value;
fSynth.decay = value/100.0f * 0.000491f + 0.000009f; // FIXME: log? fSynth.decay = value;
DISTRHO_SAFE_ASSERT(fSynth.decay >= 0.000009f && fSynth.decay <= 0.0005f); DISTRHO_SAFE_ASSERT(fSynth.decay >= 0.0f && fSynth.decay <= 127.0f);
break; break;
case paramAccent: case paramAccent:
fParams.accent = value; fParams.accent = value;

View File

@ -83,7 +83,7 @@ DistrhoUINekobi::DistrhoUINekobi()
fKnobEnvMod = new ImageKnob(this, knobImage, ImageKnob::Vertical); fKnobEnvMod = new ImageKnob(this, knobImage, ImageKnob::Vertical);
fKnobEnvMod->setId(DistrhoPluginNekobi::paramEnvMod); fKnobEnvMod->setId(DistrhoPluginNekobi::paramEnvMod);
fKnobEnvMod->setAbsolutePos(329, 43); fKnobEnvMod->setAbsolutePos(329, 43);
fKnobEnvMod->setRange(0.0f, 100.0f); fKnobEnvMod->setRange(0.0f, 127.0f);
fKnobEnvMod->setDefault(50.0f); fKnobEnvMod->setDefault(50.0f);
fKnobEnvMod->setValue(50.0f); fKnobEnvMod->setValue(50.0f);
fKnobEnvMod->setRotationAngle(305); fKnobEnvMod->setRotationAngle(305);
@ -93,7 +93,7 @@ DistrhoUINekobi::DistrhoUINekobi()
fKnobDecay = new ImageKnob(this, knobImage, ImageKnob::Vertical); fKnobDecay = new ImageKnob(this, knobImage, ImageKnob::Vertical);
fKnobDecay->setId(DistrhoPluginNekobi::paramDecay); fKnobDecay->setId(DistrhoPluginNekobi::paramDecay);
fKnobDecay->setAbsolutePos(400, 43); fKnobDecay->setAbsolutePos(400, 43);
fKnobDecay->setRange(0.0f, 100.0f); fKnobDecay->setRange(0.0f, 127.0f);
fKnobDecay->setDefault(75.0f); fKnobDecay->setDefault(75.0f);
fKnobDecay->setValue(75.0f); fKnobDecay->setValue(75.0f);
fKnobDecay->setRotationAngle(305); fKnobDecay->setRotationAngle(305);

View File

@ -189,6 +189,8 @@ void nekobee_synth_render_voices(nekobee_synth_t *synth, float *out,
wow = res * res; wow = res * res;
wow = wow / 10.0f; wow = wow / 10.0f;
// FIXME just... fix this
// as the resonance is increased, "wow" slows down the accent attack // as the resonance is increased, "wow" slows down the accent attack
if ((synth->voice->velocity > 90) && if ((synth->voice->velocity > 90) &&
(synth->vcf_accent < synth->voice->vcf_eg)) { (synth->vcf_accent < synth->voice->vcf_eg)) {
@ -205,10 +207,9 @@ void nekobee_synth_render_voices(nekobee_synth_t *synth, float *out,
synth->vca_accent = synth->vca_accent =
0.95 * synth->vca_accent; // accent off with time constant 0.95 * synth->vca_accent; // accent off with time constant
} }
#if defined(XSYNTH_DEBUG) && (XSYNTH_DEBUG & XDB_AUDIO)
out[0] += 0.10f; /* add a 'buzz' to output so there's something audible even //out[0] += 0.10f; /* add a 'buzz' to output so there's something audible even
when quiescent */
#endif /* defined(XSYNTH_DEBUG) && (XSYNTH_DEBUG & XDB_AUDIO) */
if (_PLAYING(synth->voice)) { if (_PLAYING(synth->voice)) {
nekobee_voice_render(synth, out, sample_count); nekobee_voice_render(synth, out, sample_count);
} }

View File

@ -58,6 +58,7 @@ nekobee_voice_note_on(nekobee_synth_t *synth, nekobee_voice_t *voice,
int i; int i;
voice->key = key; voice->key = key;
voice->velocity = velocity; voice->velocity = velocity;
voice->vcf_eg = 9.8f;
if (!synth->monophonic || !(_ON(voice) || _SUSTAINED(voice))) { if (!synth->monophonic || !(_ON(voice) || _SUSTAINED(voice))) {
@ -74,14 +75,14 @@ nekobee_voice_note_on(nekobee_synth_t *synth, nekobee_voice_t *voice,
} }
if (!_PLAYING(voice)) { if (!_PLAYING(voice)) {
//printf("resetting voice\n");
voice->lfo_pos = 0.0f; voice->lfo_pos = 0.0f;
voice->vca_eg = 0.0f; voice->vca_eg = 0.0f;
voice->vcf_eg = 0.0f; voice->vcf_eg = 9.8f;
voice->delay1 = 0.0f; voice->delay1 = 0.0f;
voice->delay2 = 0.0f; voice->delay2 = 0.0f;
voice->delay3 = 0.0f; voice->delay3 = 0.0f;
voice->delay4 = 0.0f; voice->delay4 = 0.0f;
voice->c5 = 0.0f;
voice->osc_index = 0; voice->osc_index = 0;
voice->osc.phase = 0.0f; voice->osc.phase = 0.0f;
@ -127,9 +128,10 @@ nekobee_voice_note_on(nekobee_synth_t *synth, nekobee_voice_t *voice,
} }
synth->held_keys[0] = key; synth->held_keys[0] = key;
// FIXME check if this mess is necessary
if (!_PLAYING(voice)) { if (!_PLAYING(voice)) {
voice->status = XSYNTH_VOICE_ON;
nekobee_voice_start_voice(voice); //nekobee_voice_start_voice(voice);
} else if (!_ON(voice)) { /* must be XSYNTH_VOICE_SUSTAINED or XSYNTH_VOICE_RELEASED */ } else if (!_ON(voice)) { /* must be XSYNTH_VOICE_SUSTAINED or XSYNTH_VOICE_RELEASED */

View File

@ -76,12 +76,13 @@ struct _nekobee_voice_t {
/* persistent voice state */ /* persistent voice state */
float prev_pitch, target_pitch, lfo_pos; float prev_pitch, target_pitch, lfo_pos;
struct blosc_t osc; struct blosc_t osc;
float vca_eg, vcf_eg, accent_slug, delay1, delay2, delay3, delay4, c5; float vca_eg, vcf_eg, accent_slug, delay1, delay2, delay3, delay4;
float vca_tc, vcf_tc; // VCA and VCF time constants
unsigned char vca_eg_phase, vcf_eg_phase; unsigned char vca_eg_phase, vcf_eg_phase;
int osc_index; /* shared index into osc_audio */ int osc_index; /* shared index into osc_audio */
float osc_audio[XSYNTH_NUGGET_SIZE]; float osc_audio[XSYNTH_NUGGET_SIZE];
float freqcut_buf[XSYNTH_NUGGET_SIZE]; float delayhp;
float vca_buf[XSYNTH_NUGGET_SIZE];
}; };
#define _PLAYING(voice) ((voice)->status != XSYNTH_VOICE_OFF) #define _PLAYING(voice) ((voice)->status != XSYNTH_VOICE_OFF)
@ -124,12 +125,4 @@ static inline void nekobee_voice_off(nekobee_voice_t *voice) {
/* -FIX- decrement active voice count? */ /* -FIX- decrement active voice count? */
} }
/*
* nekobee_voice_start_voice
*/
static inline void nekobee_voice_start_voice(nekobee_voice_t *voice) {
voice->status = XSYNTH_VOICE_ON;
/* -FIX- increment active voice count? */
}
#endif /* _XSYNTH_VOICE_H */ #endif /* _XSYNTH_VOICE_H */

View File

@ -118,6 +118,9 @@ void vcf(nekobee_synth_t *synth, float *out, uint32_t count) {
float delay1 = voice->delay1, delay2 = voice->delay2, float delay1 = voice->delay1, delay2 = voice->delay2,
delay3 = voice->delay3, delay4 = voice->delay4; delay3 = voice->delay3, delay4 = voice->delay4;
float vcf_eg = voice->vcf_eg;
float delayhp = voice->delayhp;
// to get the correct cutoff first we need Q10's collector current // 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 // 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" // of Q9 at around 3.2V through a 10k resistor. So, 8.8V between "rails"
@ -131,7 +134,20 @@ void vcf(nekobee_synth_t *synth, float *out, uint32_t count) {
float Vcutoff = 1.47 + 7.41 * logpot[(int)floor(synth->cutoff)]; float Vcutoff = 1.47 + 7.41 * logpot[(int)floor(synth->cutoff)];
float Vbe1 = -5.077; // mV // similarly the envelope modulation pot is 50k log in series with 10k
// but the top of the pot is fed with the envelope voltage, about 10V at
// peak
float Renvmod = .167 + .833 * logpot[(int)floor(synth->envmod)];
// subtract the 3.2V offset from Q9
float Venvmod = (voice->vcf_eg - 3.2) * Renvmod;
// R63 and R71 form a voltage divider, 2.2k / (220k + 2.2k) = 0.0099
// multiply by 1000 to get a voltage in mV
Venvmod *= 9.901;
float Vbe1 = Venvmod;
// .3 is 300k expressed as MOhm // .3 is 300k expressed as MOhm
// if we expressed it in Ohms output would be in A // if we expressed it in Ohms output would be in A
@ -148,29 +164,48 @@ void vcf(nekobee_synth_t *synth, float *out, uint32_t count) {
ct = ct / (1 + ct); ct = ct / (1 + ct);
// printf("cutoff = %04fHz, ct=%f\n", cutoff, ct); // printf("cutoff = %04fHz, ct=%f\n", cutoff, ct);
float hpc = 6.28 * 16 * synth->deltat;
float fout, hp;
for (uint32_t i = 0; i < count; i++) { for (uint32_t i = 0; i < count; i++) {
for (uint32_t ovs = 0; ovs < 4; ovs++) { for (uint32_t ovs = 0; ovs < 4; ovs++) {
float in = voice->osc_audio[i]; float in = voice->osc_audio[i];
float fb = in - (delay4 * synth->resonance * 4); float fb = tanh(in - ((fout - 0.25*in) * synth->resonance * 6));
if (fb > 3) fb = 3;
if (fb < -3) fb = -3;
delay1 = ((fb - delay1) * ct) + delay1; delay1 = ((fb - delay1) * ct) + delay1;
delay2 = ((delay1 - delay2) * ct) + delay2; delay2 = ((delay1 - delay2) * ct) + delay2;
delay3 = ((delay2 - delay3) * ct) + delay3; delay3 = ((delay2 - delay3) * ct) + delay3;
delay4 = ((delay3 - delay4) * ct) + delay4; delay4 = ((delay3 - delay4) * ct) + delay4;
hp = ((delay4 - delayhp) * hpc ) + delayhp;
delayhp = hp;
fout = delay4-hp;
} }
out[i] = delay4; out[i] = fout;
vcf_eg *= 1 - voice->vcf_tc;
} }
voice->delay1 = delay1; voice->delay1 = delay1;
voice->delay2 = delay2; voice->delay2 = delay2;
voice->delay3 = delay3; voice->delay3 = delay3;
voice->delay4 = delay4; voice->delay4 = delay4;
voice->vcf_eg = vcf_eg;
voice->delayhp = delayhp;
} }
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
// FIXME factor this out into the control code
// Decay is about 2.5 seconds with the pot all the way down, and 200ms with
// it all the way up this is set by a 1M log pot in series with a 68k
// resistor, a 1uF capacitor, and a bunch of other stuff to give a correct
// DC offset and a log tailoff right at the very bottom
synth->voice->vcf_tc =
(1 / ((68 + 1000 * logpot[(int)synth->decay]) * 0.001)) * synth->deltat;
// printf("tc = %f deltat=%f pot=%f\n",synth->voice->vcf_tc,
// synth->deltat,logpot[(int)synth->decay]);
vco(synth, count); vco(synth, count);
vcf(synth, out, count); vcf(synth, out, count);