From f0bc568030cbb84a6bfef112d1b462737064d390 Mon Sep 17 00:00:00 2001 From: Gordon JC Pearce Date: Mon, 19 Jun 2023 23:00:40 +0100 Subject: [PATCH] generates a bandlimited sawtooth, nothing else --- plugins/Nekobi/Makefile | 2 +- plugins/Nekobi/nekobee-src/nekobee_voice.c | 3 +- plugins/Nekobi/nekobee-src/nekobee_voice.h | 39 ++-------- .../Nekobi/nekobee-src/nekobee_voice_render.c | 72 ++++++++++++++++++- 4 files changed, 78 insertions(+), 38 deletions(-) diff --git a/plugins/Nekobi/Makefile b/plugins/Nekobi/Makefile index 0a62590..76e663e 100644 --- a/plugins/Nekobi/Makefile +++ b/plugins/Nekobi/Makefile @@ -7,7 +7,7 @@ # -------------------------------------------------------------- # Project name, used for binaries -NAME = Nekobi +NAME = Nekobi-v8 # -------------------------------------------------------------- # Files to build diff --git a/plugins/Nekobi/nekobee-src/nekobee_voice.c b/plugins/Nekobi/nekobee-src/nekobee_voice.c index d81d52b..7b3af6f 100644 --- a/plugins/Nekobi/nekobee-src/nekobee_voice.c +++ b/plugins/Nekobi/nekobee-src/nekobee_voice.c @@ -83,8 +83,7 @@ nekobee_voice_note_on(nekobee_synth_t *synth, nekobee_voice_t *voice, voice->delay4 = 0.0f; voice->c5 = 0.0f; voice->osc_index = 0; - voice->osc1.last_waveform = -1; - voice->osc1.pos = 0.0f; + voice->osc.phase = 0.0f; } voice->vca_eg_phase = 0; diff --git a/plugins/Nekobi/nekobee-src/nekobee_voice.h b/plugins/Nekobi/nekobee-src/nekobee_voice.h index a26ddb7..fc4c4ff 100644 --- a/plugins/Nekobi/nekobee-src/nekobee_voice.h +++ b/plugins/Nekobi/nekobee-src/nekobee_voice.h @@ -34,23 +34,6 @@ /* maximum size of a rendering burst */ #define XSYNTH_NUGGET_SIZE 64 -/* minBLEP constants */ -/* minBLEP table oversampling factor (must be a power of two): */ -#define MINBLEP_PHASES 64 -/* MINBLEP_PHASES minus one: */ -#define MINBLEP_PHASE_MASK 63 -/* length in samples of (truncated) step discontinuity delta: */ -#define STEP_DD_PULSE_LENGTH 72 -/* length in samples of (truncated) slope discontinuity delta: */ -#define SLOPE_DD_PULSE_LENGTH 71 -/* the longer of the two above: */ -#define LONGEST_DD_PULSE_LENGTH STEP_DD_PULSE_LENGTH -/* MINBLEP_BUFFER_LENGTH must be at least XSYNTH_NUGGET_SIZE plus - * LONGEST_DD_PULSE_LENGTH, and not less than twice LONGEST_DD_PULSE_LENGTH: */ -#define MINBLEP_BUFFER_LENGTH 512 -/* delay between start of DD pulse and the discontinuity, in samples: */ -#define DD_SAMPLE_DELAY 4 - struct _nekobee_patch_t { float tuning; unsigned char waveform; @@ -71,12 +54,9 @@ enum nekobee_voice_status { of envelopes */ }; -struct blosc { - int last_waveform, /* persistent */ - waveform, /* comes from LADSPA port each cycle */ - bp_high; /* persistent */ - float pos, /* persistent */ - pw; /* comes from LADSPA port each cycle */ +struct blosc_t { + float phase; // 0 - 1, with 1 being 2pi radians of phase rotation + float delay; // one sample delay for blep }; /* @@ -95,11 +75,11 @@ struct _nekobee_voice_t { /* persistent voice state */ float prev_pitch, target_pitch, lfo_pos; - struct blosc osc1; + struct blosc_t osc; float vca_eg, vcf_eg, accent_slug, delay1, delay2, delay3, delay4, c5; unsigned char vca_eg_phase, vcf_eg_phase; int osc_index; /* shared index into osc_audio */ - float osc_audio[MINBLEP_BUFFER_LENGTH]; + float osc_audio[XSYNTH_NUGGET_SIZE]; float freqcut_buf[XSYNTH_NUGGET_SIZE]; float vca_buf[XSYNTH_NUGGET_SIZE]; }; @@ -112,13 +92,6 @@ struct _nekobee_voice_t { extern float nekobee_pitch[128]; -typedef struct { - float value, delta; -} float_value_delta; -extern float_value_delta step_dd_table[]; - -extern float slope_dd_table[]; - /* nekobee_voice.c */ nekobee_voice_t *nekobee_voice_new(); void nekobee_voice_note_on(nekobee_synth_t *synth, nekobee_voice_t *voice, @@ -147,7 +120,7 @@ void nekobee_voice_render(nekobee_synth_t *synth, float *out, static inline void nekobee_voice_off(nekobee_voice_t *voice) { voice->status = XSYNTH_VOICE_OFF; /* silence the oscillator buffer for the next use */ - memset(voice->osc_audio, 0, MINBLEP_BUFFER_LENGTH * sizeof(float)); + memset(voice->osc_audio, 0, XSYNTH_NUGGET_SIZE * sizeof(float)); /* -FIX- decrement active voice count? */ } diff --git a/plugins/Nekobi/nekobee-src/nekobee_voice_render.c b/plugins/Nekobi/nekobee-src/nekobee_voice_render.c index 15f4324..b3339c6 100644 --- a/plugins/Nekobi/nekobee-src/nekobee_voice_render.c +++ b/plugins/Nekobi/nekobee-src/nekobee_voice_render.c @@ -20,25 +20,93 @@ // complete rewrite of the voice engine -#include #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 nekobee_voice_render(nekobee_synth_t *synth, float *out, uint32_t count) { // generate "count" samples into the buffer at out + vco(synth, count); + + for(uint32_t i=0; ivoice->osc_audio[i]; + } return; (void)synth; (void)out; (void)count; } -