/* nekobee DSSI software synthesizer plugin */ #define _BSD_SOURCE 1 #define _SVID_SOURCE 1 #define _ISOC99_SOURCE 1 #include #include "nekobee.h" #include "nekobee_synth.h" #include "nekobee_voice.h" #define M_2PI_F (2.0f * (float)M_PI) #define M_PI_F (float)M_PI #define VCF_FREQ_MAX (0.825f) /* original filters only stable to this frequency */ static int tables_initialized = 0; float nekobee_pitch[128]; #define pitch_ref_note 69 #define volume_to_amplitude_scale 128 static float volume_to_amplitude_table[4 + volume_to_amplitude_scale + 2]; static float velocity_to_attenuation[128]; static float qdB_to_amplitude_table[4 + 256 + 0]; void nekobee_init_tables(void) { int i; float pexp; float volume, volume_exponent; float ol, amp; if (tables_initialized) return; /* MIDI note to pitch */ for (i = 0; i < 128; ++i) { pexp = (float)(i - pitch_ref_note) / 12.0f; nekobee_pitch[i] = powf(2.0f, pexp); } /* volume to amplitude * * This generates a curve which is: * volume_to_amplitude_table[128 + 4] = 0.25 * 3.16... ~= -2dB * volume_to_amplitude_table[64 + 4] = 0.25 * 1.0 ~= -12dB * volume_to_amplitude_table[32 + 4] = 0.25 * 0.316... ~= -22dB * volume_to_amplitude_table[16 + 4] = 0.25 * 0.1 ~= -32dB * etc. */ volume_exponent = 1.0f / (2.0f * log10f(2.0f)); for (i = 0; i <= volume_to_amplitude_scale; i++) { volume = (float)i / (float)volume_to_amplitude_scale; volume_to_amplitude_table[i + 4] = powf(2.0f * volume, volume_exponent) / 4.0f; } volume_to_amplitude_table[ -1 + 4] = 0.0f; volume_to_amplitude_table[129 + 4] = volume_to_amplitude_table[128 + 4]; /* velocity to attenuation * * Creates the velocity to attenuation lookup table, for converting * velocities [1, 127] to full-velocity-sensitivity attenuation in * quarter decibels. Modeled after my TX-7's velocity response.*/ velocity_to_attenuation[0] = 253.9999f; for (i = 1; i < 127; i++) { if (i >= 10) { ol = (powf(((float)i / 127.0f), 0.32f) - 1.0f) * 100.0f; amp = powf(2.0f, ol / 8.0f); } else { ol = (powf(((float)10 / 127.0f), 0.32f) - 1.0f) * 100.0f; amp = powf(2.0f, ol / 8.0f) * (float)i / 10.0f; } velocity_to_attenuation[i] = log10f(amp) * -80.0f; } velocity_to_attenuation[127] = 0.0f; /* quarter-decibel attenuation to amplitude */ qdB_to_amplitude_table[-1 + 4] = 1.0f; for (i = 0; i <= 255; i++) { qdB_to_amplitude_table[i + 4] = powf(10.0f, (float)i / -80.0f); } tables_initialized = 1; } static inline float volume(float level) { unsigned char segment; float fract; level *= (float)volume_to_amplitude_scale; segment = lrintf(level - 0.5f); fract = level - (float)segment; return volume_to_amplitude_table[segment + 4] + fract * (volume_to_amplitude_table[segment + 5] - volume_to_amplitude_table[segment + 4]); } static inline float qdB_to_amplitude(float qdB) { int i = lrintf(qdB - 0.5f); float f = qdB - (float)i; return qdB_to_amplitude_table[i + 4] + f * (qdB_to_amplitude_table[i + 5] - qdB_to_amplitude_table[i + 4]); } void blosc_place_step_dd(float *buffer, int index, float phase, float w, float scale){ float r; int i; r = MINBLEP_PHASES * phase / w; i = lrintf(r - 0.5f); r -= (float)i; i &= MINBLEP_PHASE_MASK; /* port changes can cause i to be out-of-range */ /* This would be better than the above, but more expensive: * while (i < 0) { * i += MINBLEP_PHASES; * index++; * } */ while (i < MINBLEP_PHASES * STEP_DD_PULSE_LENGTH) { buffer[index] += scale * (step_dd_table[i].value + r * step_dd_table[i].delta); i += MINBLEP_PHASES; index++; } } void vco(unsigned long sample_count, nekobee_voice_t *voice, struct blosc *osc, int index, float w) { unsigned long sample; float pos = osc->pos; float pw, gain, halfgain, out; pw=0.46f; gain=1.0f; halfgain=gain*0.5f; int bp_high = osc->bp_high; out=(bp_high ? halfgain : -halfgain); switch (osc->waveform) { default: case 0: { for (sample = 0; sample < sample_count; sample++) { pos += w; if (bp_high) { if (pos >= pw) { blosc_place_step_dd(voice->osc_audio, index, pos - pw, w, -gain); bp_high = 0; out = -halfgain; } if (pos >= 1.0f) { pos -= 1.0f; blosc_place_step_dd(voice->osc_audio, index, pos, w, gain); bp_high = 1; out = halfgain; } } else { if (pos >= 1.0f) { pos -= 1.0f; blosc_place_step_dd(voice->osc_audio, index, pos, w, gain); bp_high = 1; out = halfgain; } if (bp_high && pos >= pw) { blosc_place_step_dd(voice->osc_audio, index, pos - pw, w, -gain); bp_high = 0; out = -halfgain; } } voice->osc_audio[index + DD_SAMPLE_DELAY] += out; index++; } osc->pos = pos; osc->bp_high = bp_high; break; } case 1: // sawtooth wave { for (sample=0; sample < sample_count; sample++) { pos += w; if (pos >= 1.0f) { pos -= 1.0f; blosc_place_step_dd(voice->osc_audio, index, pos, w, gain); } voice->osc_audio[index + DD_SAMPLE_DELAY] += gain * (0.5f - pos); index++; } break; } } osc->pos=pos; } static inline void vcf_4pole(nekobee_voice_t *voice, unsigned long sample_count, float *in, float *out, float *cutoff, float qres, float *amp) { unsigned long sample; float freqcut, freqcut2, highpass, delay1 = voice->delay1, delay2 = voice->delay2, delay3 = voice->delay3, delay4 = voice->delay4; qres = 2.0f - qres * 1.995f; for (sample = 0; sample < sample_count; sample++) { /* Hal Chamberlin's state variable filter */ freqcut = cutoff[sample] * 2.0f; freqcut2 = cutoff[sample] * 4.0f; if (freqcut > VCF_FREQ_MAX) freqcut = VCF_FREQ_MAX; if (freqcut2 > VCF_FREQ_MAX) freqcut2 = VCF_FREQ_MAX; delay2 = delay2 + freqcut * delay1; /* delay2/4 = lowpass output */ highpass = in[sample] - delay2 - qres * delay1; delay1 = freqcut * highpass + delay1; /* delay1/3 = bandpass output */ delay4 = delay4 + freqcut2 * delay3; highpass = delay2 - delay4 - qres * delay3; delay3 = freqcut2 * highpass + delay3; /* mix filter output into output buffer */ out[sample] += 0.1*atan(3*delay4 * amp[sample]); } voice->delay1 = delay1; voice->delay2 = delay2; voice->delay3 = delay3; voice->delay4 = delay4; voice->c5 = 0.0f; } /* * nekobee_voice_render * * generate the actual sound data for this voice */ void nekobee_voice_render(nekobee_synth_t *synth, nekobee_voice_t *voice, float *out, unsigned long sample_count, int do_control_update) { unsigned long sample; /* state variables saved in voice */ float lfo_pos = voice->lfo_pos, vca_eg = voice->vca_eg, vcf_eg = voice->vcf_eg; unsigned char vca_eg_phase = voice->vca_eg_phase, vcf_eg_phase = voice->vcf_eg_phase; int osc_index = voice->osc_index; /* temporary variables used in calculating voice */ float fund_pitch; float deltat = synth->deltat; float freq, cutoff, vcf_amt; float vcf_acc_amt; /* set up synthesis variables from patch */ float omega; float vca_eg_amp = qdB_to_amplitude(velocity_to_attenuation[voice->velocity] * 0); float vca_eg_rate_level[3], vca_eg_one_rate[3]; float vcf_eg_amp = qdB_to_amplitude(velocity_to_attenuation[voice->velocity] * 0); float vcf_eg_rate_level[3], vcf_eg_one_rate[3]; float qres = synth->resonance; float vol_out = volume(synth->volume); float velocity = (voice->velocity); float vcf_egdecay = synth->decay; fund_pitch = 0.1f*voice->target_pitch +0.9 * voice->prev_pitch; /* glide */ if (do_control_update) { voice->prev_pitch = fund_pitch; /* save pitch for next time */ } fund_pitch *= 440.0f; omega = synth->tuning * fund_pitch; // if we have triggered ACCENT // we need a shorter decay // we should probably have something like this in the note on code // that could trigger an ACCENT light if (velocity>90) { vcf_egdecay=.0005; } // VCA - In a real 303, it is set for around 2 seconds vca_eg_rate_level[0] = 0.1f * vca_eg_amp; // instant on attack vca_eg_one_rate[0] = 0.9f; // very fast vca_eg_rate_level[1] = 0.0f; // sustain is zero vca_eg_one_rate[1] = 1.0f - 0.00001f; // decay time is very slow vca_eg_rate_level[2] = 0.0f; // decays to zero vca_eg_one_rate[2] = 0.975f; // very fast release // VCF - funny things go on with the accent vcf_eg_rate_level[0] = 0.1f * vcf_eg_amp; vcf_eg_one_rate[0] = 1-0.1f; //0.9f; vcf_eg_rate_level[1] = 0.0f; // vcf_egdecay * *(synth->vcf_eg_sustain_level) * vcf_eg_amp; vcf_eg_one_rate[1] = 1.0f - vcf_egdecay; vcf_eg_rate_level[2] = 0.0f; vcf_eg_one_rate[2] = 0.9995f; // 1.0f - *(synth->vcf_eg_release_time); vca_eg_amp *= 0.99f; vcf_eg_amp *= 0.99f; freq = M_PI_F * deltat * fund_pitch * synth->mod_wheel; /* now (0 to 1) * pi */ cutoff = 0.008f * synth->cutoff; // 303 always has slight VCF mod vcf_amt = 0.05f+(synth->envmod*0.75); /* copy some things so oscillator functions can see them */ voice->osc1.waveform = lrintf(synth->waveform); // work out how much the accent will affect the filter vcf_acc_amt=.333f+ (synth->resonance/1.5f); for (sample = 0; sample < sample_count; sample++) { vca_eg = vca_eg_rate_level[vca_eg_phase] + vca_eg_one_rate[vca_eg_phase] * vca_eg; vcf_eg = vcf_eg_rate_level[vcf_eg_phase] + vcf_eg_one_rate[vcf_eg_phase] * vcf_eg; voice->freqcut_buf[sample] = (cutoff + (vcf_amt * vcf_eg/2.0f) + (synth->vcf_accent * synth->accent*0.5f)); voice->vca_buf[sample] = vca_eg * vol_out*(1.0f + synth->accent*synth->vca_accent); if (!vca_eg_phase && vca_eg > vca_eg_amp) vca_eg_phase = 1; /* flip from attack to decay */ if (!vcf_eg_phase && vcf_eg > vcf_eg_amp) vcf_eg_phase = 1; /* flip from attack to decay */ } // oscillator vco(sample_count, voice, &voice->osc1, osc_index, deltat * omega); // VCF and VCA vcf_4pole(voice, sample_count, voice->osc_audio + osc_index, out, voice->freqcut_buf, qres, voice->vca_buf); osc_index += sample_count; if (do_control_update) { /* do those things should be done only once per control-calculation * interval ("nugget"), such as voice check-for-dead, pitch envelope * calculations, volume envelope phase transition checks, etc. */ /* check if we've decayed to nothing, turn off voice if so */ if (vca_eg_phase == 2 && voice->vca_buf[sample_count - 1] < 6.26e-6f) { // sound has completed its release phase (>96dB below volume '5' max) XDB_MESSAGE(XDB_NOTE, " nekobee_voice_render check for dead: killing note id %d\n", voice->note_id); nekobee_voice_off(voice); return; // we're dead now, so return } /* already saved prev_pitch above */ /* check oscillator audio buffer index, shift buffer if necessary */ if (osc_index > MINBLEP_BUFFER_LENGTH - (XSYNTH_NUGGET_SIZE + LONGEST_DD_PULSE_LENGTH)) { memcpy(voice->osc_audio, voice->osc_audio + osc_index, LONGEST_DD_PULSE_LENGTH * sizeof (float)); memset(voice->osc_audio + LONGEST_DD_PULSE_LENGTH, 0, (MINBLEP_BUFFER_LENGTH - LONGEST_DD_PULSE_LENGTH) * sizeof (float)); osc_index = 0; } } /* save things for next time around */ voice->lfo_pos = lfo_pos; voice->vca_eg = vca_eg; voice->vca_eg_phase = vca_eg_phase; voice->vcf_eg = vcf_eg; voice->vcf_eg_phase = vcf_eg_phase; voice->osc_index = osc_index; return; (void)freq; (void)vcf_acc_amt; }