/* 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 #include #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; ivoice->osc_audio[i]; } return; }