nekobi-reworked/plugins/Nekobi/nekobee-src/nekobee_voice_render.c

126 lines
3.7 KiB
C

/* nekobee DSSI software synthesizer plugin
*
* Copyright (C) 2023 Gordonjcp, with attributions inline
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307, USA.
*/
// complete rewrite of the voice engine
#include <math.h>
#include <stdint.h>
#include "nekobee_synth.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];
void nekobee_init_tables(void) {
// create tables used by Nekobee to save on expensive calculations
// mostly involving exponentiation!
// tables are scaled to 128 values for ease of calculation with MIDI
// it's worth noting that a real 303 only responds over four octaves
// although in theory its DAC could do five
// it's a bit of a waste defining 128 MIDI notes in the expo scale
uint8_t i;
float x;
for (i = 0; i < 128; i++) {
// expo pitch scale (MIDI note number to VCO control current)
nekobee_pitch[i] = powf(2, (i - REF_NOTE) / 12.0f);
// 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;
}
return;
}
void vco(nekobee_synth_t *synth, uint32_t count) {
// generate a bandlimited oscillator
// uses polyblep for bandlimiting
// massive and endless thanks to Mystran
// https://www.kvraudio.com/forum/viewtopic.php?t=398553
nekobee_voice_t *voice = synth->voice;
blosc_t *osc = &voice->osc;
uint32_t i;
float phase = osc->phase; // current running phase 0..1
float delay = osc->delay; // delay sample for polyblep
float out, t; // output sample, temporary value for blep
// calculate omega for phase shift
float w = nekobee_pitch[voice->key] * 261.63 * synth->deltat;
// FIXME this only does saws
for (i = 0; i < count; i++) {
phase += w;
out = delay;
delay = 0;
if (phase > 1.0f) {
t = (phase - 1) / w;
out -= 0.5 * t * t; // other polynomials are available
t = 1 - t;
delay += 0.5 * t * t;
phase -= 1.0f;
}
delay += phase; // save value for next time
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) {
// 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;
}
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; i<count; i++) {
out[i] = synth->voice->osc_audio[i];
}
return;
}