From 09df1b535f38d1e53a1390ce5b46fb73d89c0dd2 Mon Sep 17 00:00:00 2001 From: Thor Harald Johansen Date: Sat, 27 May 2023 15:44:01 +0200 Subject: [PATCH] Backing up changes --- include/synth/channel.h | 54 -------- include/synth/dsp/filter.h | 128 ------------------ include/synth/dsp/oscillator.h | 82 ----------- include/synth/globals.h | 7 - include/synth/synth.h | 45 ------ include/synth/voice.h | 74 ---------- include/synthapp.h | 5 +- lib/synth/CMakeLists.txt | 4 +- {include/synth => lib/synth/include}/cc.h | 0 lib/synth/include/channel.h | 75 ++++++++++ .../synth => lib/synth/include}/dsp/adsr.h | 9 +- lib/synth/include/dsp/delayline.h | 74 ++++++++++ lib/synth/include/dsp/filter.h | 124 +++++++++++++++++ lib/synth/include/dsp/fm.h | 107 +++++++++++++++ .../synth => lib/synth/include}/dsp/frame.h | 0 lib/synth/include/dsp/oscillator.h | 101 ++++++++++++++ lib/synth/include/dsp/phys.h | 34 +++++ .../synth => lib/synth/include}/dsp/reverb.h | 8 +- .../synth/include/dsp/stereodelayline.h | 19 ++- .../synth/include/dsp/stereosap.h | 8 +- lib/synth/include/globals.h | 9 ++ lib/synth/include/luts.h | 26 ++++ {include/synth => lib/synth/include}/preset.h | 0 lib/synth/include/synth.h | 65 +++++++++ .../synth/dsp => lib/synth/include}/util.h | 48 ++++--- lib/synth/include/voice.h | 127 +++++++++++++++++ .../synth/include}/voicemanager.h | 2 +- {src/synth => lib/synth/src}/channel.cpp | 34 +++-- {src/synth => lib/synth/src}/dsp/adsr.cpp | 2 +- {src/synth => lib/synth/src}/dsp/filter.cpp | 4 +- .../synth/src}/dsp/oscillator.cpp | 2 +- {src/synth => lib/synth/src}/dsp/reverb.cpp | 2 +- lib/synth/src/luts.cpp | 19 +++ {src/synth => lib/synth/src}/preset.cpp | 12 +- {src/synth => lib/synth/src}/synth.cpp | 8 +- {src/synth => lib/synth/src}/voice.cpp | 15 +- {src/synth => lib/synth/src}/voicemanager.cpp | 2 +- src/synth/dsp/centlut.cpp | 18 --- src/synth/dsp/filterlut.cpp | 10 -- src/synth/dsp/notelut.cpp | 18 --- src/synthapp.cpp | 16 +-- src/synthframe.cpp | 54 +++++++- 42 files changed, 927 insertions(+), 524 deletions(-) delete mode 100644 include/synth/channel.h delete mode 100644 include/synth/dsp/filter.h delete mode 100644 include/synth/dsp/oscillator.h delete mode 100644 include/synth/globals.h delete mode 100644 include/synth/synth.h delete mode 100644 include/synth/voice.h rename {include/synth => lib/synth/include}/cc.h (100%) create mode 100644 lib/synth/include/channel.h rename {include/synth => lib/synth/include}/dsp/adsr.h (87%) create mode 100644 lib/synth/include/dsp/delayline.h create mode 100644 lib/synth/include/dsp/filter.h create mode 100644 lib/synth/include/dsp/fm.h rename {include/synth => lib/synth/include}/dsp/frame.h (100%) create mode 100644 lib/synth/include/dsp/oscillator.h create mode 100644 lib/synth/include/dsp/phys.h rename {include/synth => lib/synth/include}/dsp/reverb.h (86%) rename include/synth/dsp/delayline.h => lib/synth/include/dsp/stereodelayline.h (62%) rename include/synth/dsp/sap.h => lib/synth/include/dsp/stereosap.h (70%) create mode 100644 lib/synth/include/globals.h create mode 100644 lib/synth/include/luts.h rename {include/synth => lib/synth/include}/preset.h (100%) create mode 100644 lib/synth/include/synth.h rename {include/synth/dsp => lib/synth/include}/util.h (56%) create mode 100644 lib/synth/include/voice.h rename {include/synth => lib/synth/include}/voicemanager.h (94%) rename {src/synth => lib/synth/src}/channel.cpp (86%) rename {src/synth => lib/synth/src}/dsp/adsr.cpp (90%) rename {src/synth => lib/synth/src}/dsp/filter.cpp (51%) rename {src/synth => lib/synth/src}/dsp/oscillator.cpp (66%) rename {src/synth => lib/synth/src}/dsp/reverb.cpp (94%) create mode 100644 lib/synth/src/luts.cpp rename {src/synth => lib/synth/src}/preset.cpp (80%) rename {src/synth => lib/synth/src}/synth.cpp (94%) rename {src/synth => lib/synth/src}/voice.cpp (58%) rename {src/synth => lib/synth/src}/voicemanager.cpp (98%) delete mode 100644 src/synth/dsp/centlut.cpp delete mode 100644 src/synth/dsp/filterlut.cpp delete mode 100644 src/synth/dsp/notelut.cpp diff --git a/include/synth/channel.h b/include/synth/channel.h deleted file mode 100644 index aea2cee..0000000 --- a/include/synth/channel.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef __PART_H__ -#define __PART_H__ - -#include -#include - -#include "globals.h" -#include "voicemanager.h" -#include "voice.h" -#include "preset.h" -#include "dsp/frame.h" - -class Channel { -public: - VoiceManager::channel_t number; - VoiceManager* voiceManager; - Voice::Settings settings; - - Channel() : volume(1), pitchBend(0), modulation(0), lfoPhase(randPhase()) {} - - void loadPreset(const Preset* preset); - - void noteOn(int note, int vel); - void noteOff(int note); - void control(int cc, int val); - - frame tick() { - frame out{0, 0}; - float lfo = sin(lfoPhase); - Voice** voices = voiceManager->getChannelVoices(number); - for(Voice** voice = voices; *voice != NULL; ++voice) { - float voiceOut = (*voice)->tick(pitchBend + settings.lfoPitchMod * lfo, settings.lfoFltMod * lfo); - out.l += sinf((1 - (*voice)->pan) * (float) M_PI_2) * voiceOut; - out.r += sinf((*voice)->pan * (float) M_PI_2) * voiceOut; - } - out *= volume; - - lfoPhase += settings.lfoStep; - if(lfoPhase >= (float) PIx2) { - lfoPhase -= PIx2; - } - - return out; - } - -private: - float volume; - float pitchBend; - float modulation; - - float lfoPhase; -}; - -#endif \ No newline at end of file diff --git a/include/synth/dsp/filter.h b/include/synth/dsp/filter.h deleted file mode 100644 index 8b548d3..0000000 --- a/include/synth/dsp/filter.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef __SVF_H__ -#define __SVF_H__ - -/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ - -#include -#include -#include -#include -#include - -#include "../globals.h" -#include "util.h" - -#define FILTER_OVERSAMPLE 4 -#define FILTER_K_SCALE (2 * CV_FREQ_MIN / (FILTER_OVERSAMPLE * SAMPLE_RATE)) -#define FILTER_CV_MAX 9.25 // MIDI note "135" (19912.126958213178287 Hz) - -inline float cvToK(float cv) { - return tanf((float) M_PI_2 * std::min(0.5f, std::min(1.f / FILTER_OVERSAMPLE, (float) FILTER_K_SCALE * exp2f(cv)))); -} - -class SVF12 { -public: - typedef struct { - float lp; // low-pass - float bp; // band-pass - float hp; // high-pass - } Output; - -protected: - float as1, as2; - -public: - SVF12() : as1(0), as2(0) {} - - inline Output tick(float in, float kK, float kQ) { - Output out; - float kdiv = 1 + kK/kQ + kK*kK; - out.hp = (in - (1/kQ + kK) * as1 - as2) / kdiv; - float au = out.hp * kK; - out.bp = au + as1; - as1 = au + out.bp; - au = out.bp * kK; - out.lp = au + as2; - as2 = au + out.lp; - return out; - } -}; - -class Oversampler { -private: - float kK, kQ = (float) M_SQRT1_2; - SVF12 aa1, aa2; - -public: - Oversampler() : kK(tanf((float) M_PI_2 * 1.0f / FILTER_OVERSAMPLE)) {} - - inline float tick(float in, std::function process) { - if(FILTER_OVERSAMPLE < 2) { - return process(in); - } else { -// float out = aa2.tick(aa1.tick(times * process(in), kK, kQ).lp, kK, kQ).lp; -// for(int i = 1; i < times; ++i) { -// aa2.tick(aa1.tick(process(0), kK, kQ).lp, kK, kQ); -// } - float out = process(aa2.tick(aa1.tick(FILTER_OVERSAMPLE * in, kK, kQ).lp, kK, kQ).lp); - for(int i = 1; i < FILTER_OVERSAMPLE; ++i) { - process(aa2.tick(aa1.tick(0, kK, kQ).lp, kK, kQ).lp); - } - - return out; - } - } -}; - -// 12 and 24 dB/oct -class Filter { -public: - enum Type { - TYPE_LP = 0, - TYPE_BP, - TYPE_HP - }; - - enum Slope { - SLOPE_24 = 0, - SLOPE_12, - }; - - typedef struct { - float freq; - float res; - Type type; - Slope slope; - } Settings; - -private: - Settings const * settings; - SVF12 fltA, fltB; - Oversampler os; - -public: - void assign(Settings const * settings); - - inline float tick(float in, float freqMod) { - float kK = cvToK(settings->freq + freqMod); - float kQ = (float) M_SQRT1_2 + settings->res; - - return os.tick(in, [this, kK, kQ](float oin) { - SVF12::Output outA = fltA.tick(oin, kK, kQ); - - switch(settings->type) { - case TYPE_LP: - return settings->slope == SLOPE_24 ? fltB.tick(outA.lp, kK, kQ).lp : outA.lp; - case TYPE_BP: - return settings->slope == SLOPE_24 ? fltB.tick(outA.bp, kK, kQ).bp : outA.bp; - case TYPE_HP: - return settings->slope == SLOPE_24 ? fltB.tick(outA.hp, kK, kQ).hp : outA.hp; - default: - return oin; - } - }); - } -}; - - -#endif \ No newline at end of file diff --git a/include/synth/dsp/oscillator.h b/include/synth/dsp/oscillator.h deleted file mode 100644 index 7da89bb..0000000 --- a/include/synth/dsp/oscillator.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef __OSCILLATOR_H__ -#define __OSCILLATOR_H__ - -/* PolyBLEP oscillator inspired by: https://www.metafunction.co.uk/post/all-about-digital-oscillators-part-2-blits-bleps - * Further info about BLEP at: https://pbat.ch/sndkit/blep/ - */ - -#include -#include - -#include "util.h" - -class Oscillator { -public: - enum Mode { MODE_SINE = 0, MODE_SAW, MODE_SQUARE }; - - Mode const * mode; - float phase; // current waveform phase angle (radians) - float value; // current amplitude value - float driftAmount; - float driftValue; - - Oscillator() : phase(randPhase()), value(0), driftAmount(0.001f), driftValue(0) {} - - void assign(Mode const * mode); - - // Generate next output sample and advance the phase angle - inline float tick(float cv) { - const float phaseStep = (float) (2 * CV_FREQ_MIN) * exp2f(cv) * (float) SAMPLE_RATE_INV; - - const float t = phase * (float) PIx2_INV; // Define half phase - const float dt = phaseStep * (float) PIx2_INV; - - if (*mode == MODE_SINE) { - value = sinf(phase); // No harmonics in sine so no aliasing!! No Poly BLEPs needed! - } else if (*mode == MODE_SAW) { - value = (2.0f * t) - 1.0f; // Render naive waveshape - value -= polyBlep(t, dt); // Layer output of Poly BLEP on top - } else if (*mode == MODE_SQUARE) { - if (phase < (float) M_PI) { - value = 1.0; // Flip - } else { - value = -1.0; // Flop - } - value += polyBlep(t, dt); // Layer output of Poly BLEP on top (flip) - value -= polyBlep(fmodf(t + 0.5f, 1.0f), dt); // Layer output of Poly BLEP on top (flop) - } - - driftValue += 0.005f * whiteNoise() - 0.00001f * driftValue; - phase += phaseStep * (1.0f + driftAmount * driftValue); - - if(phase >= (float) PIx2) { // wrap if phase angle >=360ยบ - phase -= PIx2; - } - - return value; - } - - inline float polyBlep(float t, float dt) { - // t-t^2/2 +1/2 - // 0 < t <= 1 - // discontinuities between 0 & 1 - if (t < dt) { - t /= dt; - return t + t - t * t - 1.0f; - } - - // t^2/2 +t +1/2 - // -1 <= t <= 0 - // discontinuities between -1 & 0 - else if (t > 1.0f - dt) { - t = (t - 1.0f) / dt; - return t * t + t + t + 1.0f; - } - - // no discontinuities - // 0 otherwise - else return 0.0; - } -}; - -#endif \ No newline at end of file diff --git a/include/synth/globals.h b/include/synth/globals.h deleted file mode 100644 index 04c52f8..0000000 --- a/include/synth/globals.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __GLOBALS_H__ -#define __GLOBALS_H__ - -#define SAMPLE_RATE 44100 -#define SAMPLE_RATE_INV (1.0 / SAMPLE_RATE) - -#endif \ No newline at end of file diff --git a/include/synth/synth.h b/include/synth/synth.h deleted file mode 100644 index e45af80..0000000 --- a/include/synth/synth.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef __SYNTH_H__ -#define __SYNTH_H__ - -#include "voicemanager.h" -#include "channel.h" -#include "dsp/frame.h" -#include "dsp/reverb.h" - -class Synth { -public: - Synth() { - for(VoiceManager::channel_t i = 0; i < 16; ++i) { - channels[i].number = i; - channels[i].voiceManager = &voiceManager; - channels[i].loadPreset(&DEFAULT_PRESET); - } - } - - void noteOn(int ch, int note, int vel); - void noteOff(int ch, int note); - void control(int ch, int cc, int val); - - inline frame tick() { - frame out{}; - frame reverbBus{}; - - for(auto& channel : channels) { - frame channelOut = channel.tick(); - out += channelOut; - reverbBus += channel.settings.reverb * channelOut; - } - - out += reverb.tick(reverbBus); - out *= 0.125f; - - return out; - } - -private: - VoiceManager voiceManager{}; - Channel channels[16]; - Reverb reverb; -}; - -#endif \ No newline at end of file diff --git a/include/synth/voice.h b/include/synth/voice.h deleted file mode 100644 index cb271b3..0000000 --- a/include/synth/voice.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef __VOICE_H__ -#define __VOICE_H__ - -#include "globals.h" -#include "preset.h" - -#include "dsp/oscillator.h" -#include "dsp/filter.h" -#include "dsp/adsr.h" - -class Voice { -public: - typedef struct { - float unison; - - Oscillator::Mode osc1Mode; - Oscillator::Mode osc2Mode; - float oscMix; - float oscDetune; - float osc2Pitch; - - float noiseMix; - - Filter::Settings filter; - - ADSR::Envelope ampEnv; - ADSR::Envelope modEnv; - - float modEnvFltGain; - float keyTrack; - float lfoStep; - float lfoPitchMod; - float lfoFltMod; - - float reverb; - } Settings; - -private: - Settings const * settings; - - ADSR adsrAmp; - ADSR adsrMod; - Oscillator osc1; - Oscillator osc2; - Filter filter; - -public: - int note; - float gain; - float pan; - float detune; - - void reset(); - void assign(Settings const * settings); - void noteOn(int note, float detune = 0, float gain = 1.0, float pan = 0.5); - void noteOff(); - inline bool isIdle() { return adsrAmp.isIdle(); } - - inline float tick(float pitchMod, float fltMod) { - const float basePitch = note + pitchMod + detune; - const float osc1CV = noteToCV(basePitch - settings->oscDetune); - const float osc2CV = noteToCV(basePitch + settings->oscDetune + settings->osc2Pitch); - float out = 0; - out += (1 - settings->oscMix) * osc1.tick(osc1CV); - out += settings->oscMix * osc2.tick(osc2CV); - out += settings->noiseMix * ((1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()) - out); - const float keyTrackVal = settings->keyTrack * (note - 72) / 12.f; - out = filter.tick(out, settings->modEnvFltGain * adsrMod.tick() + keyTrackVal + fltMod); - out *= gain * adsrAmp.tick(); - return out; - } -}; - -#endif \ No newline at end of file diff --git a/include/synthapp.h b/include/synthapp.h index b0a9896..c02537f 100644 --- a/include/synthapp.h +++ b/include/synthapp.h @@ -1,6 +1,8 @@ #ifndef __SYNTHAPP_H__ #define __SYNTHAPP_H__ +#include + #include #ifndef WX_PRECOMP @@ -10,13 +12,14 @@ #include #include -#include "synth/synth.h" +#include "synth.h" class SynthApp : public wxApp { public: PaStream* paStream; PmStream* pmStream; Synth synth; + std::mutex synthMutex; virtual bool OnInit(); virtual int OnExit(); diff --git a/lib/synth/CMakeLists.txt b/lib/synth/CMakeLists.txt index 2b9e2ae..4dc464f 100644 --- a/lib/synth/CMakeLists.txt +++ b/lib/synth/CMakeLists.txt @@ -17,16 +17,14 @@ if(MSVC) endif() add_library(synth + src/luts.cpp src/synth.cpp src/voicemanager.cpp src/channel.cpp src/preset.cpp src/voice.cpp - src/dsp/notelut.cpp - src/dsp/centlut.cpp src/dsp/oscillator.cpp src/dsp/filter.cpp - src/dsp/filterlut.cpp src/dsp/adsr.cpp src/dsp/reverb.cpp) target_include_directories(synth PUBLIC include) \ No newline at end of file diff --git a/include/synth/cc.h b/lib/synth/include/cc.h similarity index 100% rename from include/synth/cc.h rename to lib/synth/include/cc.h diff --git a/lib/synth/include/channel.h b/lib/synth/include/channel.h new file mode 100644 index 0000000..44fe43c --- /dev/null +++ b/lib/synth/include/channel.h @@ -0,0 +1,75 @@ +#ifndef __PART_H__ +#define __PART_H__ + +#include +#include + +#include "globals.h" +#include "voicemanager.h" +#include "voice.h" +#include "preset.h" +#include "dsp/frame.h" +#include "luts.h" + +class Channel { +public: + VoiceManager::channel_t number; + VoiceManager* voiceManager; + Voice::Settings settings; + + Channel() : volume(1), pitchBend(0), modulation(0), lfoPhase(randFrac()) {} + + void loadPreset(const Preset* preset); + + void noteOn(int note, int vel); + void noteOff(int note); + void control(int cc, int val); + + bool tick(float *out, const size_t bufferSize) { + Voice** voices = voiceManager->getChannelVoices(number); + if(*voices == NULL) { + return false; + } + + const size_t numFrames = bufferSize / 2; + + float lfo[numFrames]; + for(size_t i = 0; i < numFrames; ++i) { + lfo[i] = fastSin(lfoPhase); + + lfoPhase += settings.lfoStep; + lfoPhase -= floorf(lfoPhase); + } + + float pitchMod[numFrames]; + for(size_t i = 0; i < numFrames; ++i) { + pitchMod[i] = settings.lfoPitchMod * lfo[i]; + } + + float fltMod[numFrames]; + for(size_t i = 0; i < numFrames; ++i) { + fltMod[i] = settings.lfoFltMod * lfo[i]; + } + + std::fill(out, out + bufferSize, 0.f); + for(Voice** voice = voices; *voice != NULL; ++voice) { + float voiceOut[numFrames]; + (*voice)->tick(voiceOut, numFrames, pitchMod, fltMod); + + for(size_t i = 0; i < bufferSize; ++i) { + out[i] += voiceOut[i]; + } + } + + return true; + } + +private: + float volume; + float pitchBend; + float modulation; + + float lfoPhase; +}; + +#endif \ No newline at end of file diff --git a/include/synth/dsp/adsr.h b/lib/synth/include/dsp/adsr.h similarity index 87% rename from include/synth/dsp/adsr.h rename to lib/synth/include/dsp/adsr.h index ea24c50..70740ec 100644 --- a/include/synth/dsp/adsr.h +++ b/lib/synth/include/dsp/adsr.h @@ -3,7 +3,7 @@ #include -#include "util.h" +#include "../util.h" class ADSR { public: @@ -33,7 +33,7 @@ public: case ATTACK: if(t < 1) { t += env->attackStep; - last = curve(t); + last = t*t; } else { state = DECAY; t = 1; @@ -44,7 +44,8 @@ public: case DECAY: if(t > 0) { t -= env->decayStep; - last = env->sustain + (1 - env->sustain) * curve(t); + //last = env->sustain + (1 - env->sustain) * curve(t); + last = env->sustain + (1 - env->sustain) * t*t; } else { state = SUSTAIN; t = 1; @@ -59,7 +60,7 @@ public: case RELEASE: if(t > 0) { t -= env->releaseStep; - return last * curve(t); + return last * t*t; } else { state = IDLE; t = 0; diff --git a/lib/synth/include/dsp/delayline.h b/lib/synth/include/dsp/delayline.h new file mode 100644 index 0000000..f7a5544 --- /dev/null +++ b/lib/synth/include/dsp/delayline.h @@ -0,0 +1,74 @@ +#ifndef __DELAYLINE_H__ +#define __DELAYLINE_H__ + +#include + +template +class DelayLine { +private: + float samples[CAPACITY] = {0}; + size_t cursor; + +public: + size_t size; + + DelayLine(size_t size = CAPACITY) : size(size) {} + + inline float get() { + while(cursor >= size) { + cursor -= size; + } + return samples[cursor]; + } + + inline float tick() { + ++cursor; + while(cursor >= size) { + cursor -= size; + } + return samples[cursor]; + } + + inline float tick(float in) { + while(cursor >= size) { + cursor -= size; + } + samples[cursor++] = in; + if(cursor == size) { + cursor = 0; + } + return samples[cursor]; + } + + inline void inject(float in, size_t pos) { + size_t index = cursor + pos; + while(index >= size) { + index -= size; + } + samples[index] = in; + } + + inline void inject(float* in, size_t num, size_t pos = 0) { + size_t index = cursor + pos; + while(index >= size) { + index -= size; + } + for(size_t i = 0; i < num; ++i) { + samples[index++] = *(in++); + if(index == size) { + index = 0; + } + } + } + + inline float tap(size_t n) { + size_t index = cursor + n; + if(index < size) { + return samples[index]; + } else { + return samples[index - size]; + } + } +}; + +#endif \ No newline at end of file diff --git a/lib/synth/include/dsp/filter.h b/lib/synth/include/dsp/filter.h new file mode 100644 index 0000000..714e7c5 --- /dev/null +++ b/lib/synth/include/dsp/filter.h @@ -0,0 +1,124 @@ +#ifndef __SVF_H__ +#define __SVF_H__ + +/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ + +#include +#include +#include +#include +#include + +#include "../globals.h" +#include "../util.h" +#include "../luts.h" + +#define FILTER_K_SCALE (2 * CV_FREQ_MIN / SAMPLE_RATE) + +inline float cvToK(float cv) { + return tanf((float) M_PI_2 * std::min(0.5f, (float) FILTER_K_SCALE * exp2f(cv))); +} + +class SVF12 { +protected: + float hp, bp, lp; + float as1, as2; + +public: + SVF12() : hp(0), bp(0), lp(0), as1(0), as2(0) {} + + inline void tick(float *in, float *hpo, float *bpo, float *lpo, size_t bufferSize, float *kK, float *kQ) { + float kQ_1[bufferSize]; + for(size_t i = 0; i < bufferSize; ++i) { + kQ_1[i] = 1.f / kQ[i]; + } + float kMul[bufferSize]; + for(size_t i = 0; i < bufferSize; ++i) { + kMul[i] = 1.f / (1.f + kK[i]*kQ_1[i] + kK[i]*kK[i]); + } + for(size_t i = 0; i < bufferSize; ++i) { + hp = (in[i] - (kQ_1[i] + kK[i]) * as1 - as2) * kMul[i]; + float au = hp * kK[i]; + bp = au + as1; + as1 = au + bp; + au = bp * kK[i]; + lp = au + as2; + as2 = au + lp; + + hpo && (hpo[i] = hp); + bpo && (bpo[i] = bp); + lpo && (lpo[i] = lp); + } + } +}; + +// 12 and 24 dB/oct +class Filter { +public: + enum Type { + TYPE_LP = 0, + TYPE_BP, + TYPE_HP + }; + + enum Slope { + SLOPE_12 = 0, + SLOPE_24, + }; + + typedef struct { + float freq; + float res; + Type type; + Slope slope; + } Settings; + +private: + Settings const * settings; + SVF12 fltA, fltB; + float freq, res; + +public: + void assign(Settings const * settings); + + inline void tick(float *in, float *out, size_t bufferSize, float *freqMod) { + float dt = 1.f / (float) bufferSize; + + float kK[bufferSize]; + float dFreq = dt * (settings->freq - freq); + for(size_t i = 0; i < bufferSize; i++) { + kK[i] = fastCVtoK(freq + freqMod[i]); + freq += dFreq; + } + float kQ[bufferSize]; + float dRes = dt * (settings->res - res); + for(size_t i = 0; i < bufferSize; i++) { + kQ[i] = (float) M_SQRT1_2 + res; + res += dRes; + } + + switch(settings->type) { + case TYPE_LP: + fltA.tick(in, NULL, NULL, out, bufferSize, kK, kQ); + if(settings->slope == SLOPE_24) { + fltB.tick(out, NULL, NULL, out, bufferSize, kK, kQ); + } + break; + case TYPE_BP: + fltA.tick(in, NULL, out, NULL, bufferSize, kK, kQ); + if(settings->slope == SLOPE_24) { + fltB.tick(out, NULL, out, NULL, bufferSize, kK, kQ); + } + break; + case TYPE_HP: + fltA.tick(in, out, NULL, NULL, bufferSize, kK, kQ); + if(settings->slope == SLOPE_24) { + fltB.tick(out, out, NULL, NULL, bufferSize, kK, kQ); + } + break; + } + } +}; + + +#endif \ No newline at end of file diff --git a/lib/synth/include/dsp/fm.h b/lib/synth/include/dsp/fm.h new file mode 100644 index 0000000..19ea385 --- /dev/null +++ b/lib/synth/include/dsp/fm.h @@ -0,0 +1,107 @@ +#ifndef __FM_H__ +#define __FM_H__ + +#include + +#include "../util.h" + +class FM { +private: + float phase1 = randPhase(), phase2 = randPhase(), phase3 = randPhase(), phase4 = randPhase(), phase5 = randPhase(), phase6 = randPhase(); + float fb1 = 0.f, fb2 = 0.f; + +public: + struct Settings { + struct { + float pitch; + float level; + } ops[6]; + }; + Settings settings; + + float tick(float pitch, float fmEnv) { + float step3 = cvToStep(pitch); + float step2 = cvToStep(pitch + 3.807354922057604f); // Frequency Coarse = 14 + float step1 = cvToStep(pitch + 0.007195501404204f); // Detune = +7 + float step6 = cvToStep(pitch); + float step5 = cvToStep(pitch - 0.007231569231076f); // Detune = -7 + float step4 = cvToStep(pitch); + + float osc3 = fmEnv * 0.333f * sinf(phase3); + float osc2 = fmEnv * 0.333f * sinf(phase2 + (float) M_PI * osc3); + float osc1 = sinf(phase1 + (float) M_PI * osc2); + + float osc6 = fmEnv * 1.0f * sinf(phase6 + 0.0f * (float) M_PI * (fb1 + fb2)); + float osc5 = fmEnv * 1.0f * sinf(phase5 + (float) M_PI * osc6); + float osc4 = sinf(phase4 + (float) M_PI * osc5); + fb2 = fb1; + fb1 = osc4; + + phase1 += step1; + if(phase1 >= (float) PIx2) { + phase1 -= PIx2; + } + phase2 += step2; + if(phase2 >= (float) PIx2) { + phase2 -= PIx2; + } + phase3 += step3; + if(phase3 >= (float) PIx2) { + phase3 -= PIx2; + } + phase4 += step4; + if(phase4 >= (float) PIx2) { + phase4 -= PIx2; + } + phase5 += step5; + if(phase5 >= (float) PIx2) { + phase5 -= PIx2; + } + phase6 += step6; + if(phase6 >= (float) PIx2) { + phase6 -= PIx2; + } + + return osc1 + osc4; + } +}; + +/* +op6.detune 14 +op6.output_level 79 +op6.frequency_coarse 1 + +op5.detune 0 +op5.output_level 99 +op5.frequency_coarse 1 + +op4.detune 7 +op4.output_level 89 +op4.frequency_coarse 1 + +op3.detune 7 +op3.output_level 99 +op3.frequency_coarse 1 + +op2.detune 7 +op2.output_level 58 +op2.frequency_coarse 14 + +op1.detune 10 +op1.output_level 99 +op1.frequency_coarse 1 + +algorithm 4 +feedback 6 [0..7] + +RANGES + +opx.output_level [0, 99] +opX.frequency_coarse [0, 31] 0.50 - 31.00 X +op6.frequency_fine [0, 99] 1.00 - 1.99 X +opX.detune [0, 14] [-7, +7] +feedback [0, 7] + +*/ + +#endif \ No newline at end of file diff --git a/include/synth/dsp/frame.h b/lib/synth/include/dsp/frame.h similarity index 100% rename from include/synth/dsp/frame.h rename to lib/synth/include/dsp/frame.h diff --git a/lib/synth/include/dsp/oscillator.h b/lib/synth/include/dsp/oscillator.h new file mode 100644 index 0000000..99b7a58 --- /dev/null +++ b/lib/synth/include/dsp/oscillator.h @@ -0,0 +1,101 @@ +#ifndef __OSCILLATOR_H__ +#define __OSCILLATOR_H__ + +/* PolyBLEP oscillator inspired by: https://www.metafunction.co.uk/post/all-about-digital-oscillators-part-2-blits-bleps + * Further info about BLEP at: https://pbat.ch/sndkit/blep/ + */ + +#include +#include + +#include "../util.h" + +#include "../luts.h" + +class Oscillator { +public: + enum Mode { MODE_SINE = 0, MODE_SAW, MODE_SQUARE }; + + Mode const * mode; + float phase; // current waveform phase angle (radians) + float value; // current amplitude value + float driftAmount; + float driftValue; + + Oscillator() : phase(randFrac()), value(0), driftAmount(0.001f), driftValue(0) {} + + void assign(Mode const * mode); + + // Generate next output sample and advance the phase angle + inline void tick(float* out, size_t bufferSize, float *pitch, float* gain) { + size_t frameCount = bufferSize / 2; + + float driftBuf[frameCount]; + for(size_t i = 0; i < frameCount; ++i) { + driftValue += 0.005f * whiteNoise() - 0.00001f * driftValue; + driftBuf[i] = 1.0f + driftAmount * driftValue; + } + float phaseStepBuf[frameCount]; + for(size_t i = 0; i < frameCount; ++i) { + phaseStepBuf[i] = cvToStep(pitch[i]) * driftBuf[i]; + } + + float phaseBuf[frameCount]; + for(size_t i = 0; i < frameCount; ++i) { + phaseBuf[i] = phase; + phase += phaseStepBuf[i]; + phase -= floorf(phase); + } + + if(*mode == MODE_SINE) { + for(size_t i = 0, j = 0; i < frameCount; ++i) { + float value = fastSin(phaseBuf[i]); + out[j++] += gain[j & 1] * value; + out[j++] += gain[j & 1] * value; + } + } else if(*mode == MODE_SAW) { + for(size_t i = 0, j = 0; i < frameCount; ++i) { + float value = (2.0f * phaseBuf[i]) - 1.0f; // Render naive waveshape + value -= polyBlep(phaseBuf[i], phaseStepBuf[i]); // Layer output of Poly BLEP on top + out[j++] += gain[j & 1] * value; + out[j++] += gain[j & 1] * value; + } + } else if (*mode == MODE_SQUARE) { + for(size_t i = 0, j = 0; i < frameCount; ++i) { + float value = phaseBuf[i] < 0.5f ? 1.f : -1.f; // Render naive waveshape + + value += polyBlep(phaseBuf[i], phaseStepBuf[i]); // Layer output of Poly BLEP on top (flip) + value -= polyBlep(fmodf(phaseBuf[i] + 0.5f, 1.0f), phaseStepBuf[i]); // Layer output of Poly BLEP on top (flop) + + out[j++] = gain[j & 1] * value; + out[j++] = gain[j & 1] * value; + } + } + } + + inline float polyBlep(float t, float dt) { + // t-t^2/2 +1/2 + // 0 < t <= 1 + // discontinuities between 0 & 1 + if(t < dt) { + t /= dt; + return t + t - t * t - 1.0f; + } + + // t^2/2 +t +1/2 + // -1 <= t <= 0 + // discontinuities between -1 & 0 + else if(t > 1.0f - dt) { + t = (t - 1.0f) / dt; + return t * t + t + t + 1.0f; + } + + // no discontinuities + // 0 otherwise + else { + return 0.0; + } + } +}; + +#endif \ No newline at end of file diff --git a/lib/synth/include/dsp/phys.h b/lib/synth/include/dsp/phys.h new file mode 100644 index 0000000..b30e2ea --- /dev/null +++ b/lib/synth/include/dsp/phys.h @@ -0,0 +1,34 @@ +#ifndef __PHYS_H__ +#define __PHYS_H__ + +#include "../util.h" +#include "delayline.h" + +class Physical { +private: + DelayLine dl; + float lp = 0, lp2 = 0; + +public: + inline float tick(float pitch) { + int period = roundf(cvToPeriod(pitch)); + dl.size = period; + float out = dl.get(); + lp *= 0.99f; + lp += 0.90f * (out - lp); + dl.inject(lp, 0); + dl.tick(); + lp2 += 0.01f * (out - lp2); + return lp2; + } + + inline void pluck() { + float tmp[512]; + for(int i = 0; i < 512; ++i) { + tmp[i] = 8.f * (whiteNoise() + whiteNoise() + whiteNoise() + whiteNoise()); + } + dl.inject(tmp, 512); + } +}; + +#endif \ No newline at end of file diff --git a/include/synth/dsp/reverb.h b/lib/synth/include/dsp/reverb.h similarity index 86% rename from include/synth/dsp/reverb.h rename to lib/synth/include/dsp/reverb.h index 35e6bb0..372a284 100644 --- a/include/synth/dsp/reverb.h +++ b/lib/synth/include/dsp/reverb.h @@ -5,11 +5,11 @@ #include +#include "globals.h" #include "frame.h" -#include "delayline.h" -#include "sap.h" +#include "stereodelayline.h" -#define REVERB_FRAMES 131072 +#define REVERB_FRAMES (3 * SAMPLE_RATE) #define REVERB_TAPS 256 class Reverb { @@ -19,7 +19,7 @@ private: float lg, rg; }; - DelayLine dl; + StereoDelayLine dl; Tap taps[REVERB_TAPS]; float lpfL = 0.f, lpfR = 0.f; float hpfL = 0.f, hpfR = 0.f; diff --git a/include/synth/dsp/delayline.h b/lib/synth/include/dsp/stereodelayline.h similarity index 62% rename from include/synth/dsp/delayline.h rename to lib/synth/include/dsp/stereodelayline.h index 1d36924..9d255ff 100644 --- a/include/synth/dsp/delayline.h +++ b/lib/synth/include/dsp/stereodelayline.h @@ -1,19 +1,28 @@ -#ifndef __DELAYLINE_H__ -#define __DELAYLINE_H__ +#ifndef __STEREODELAYLINE_H__ +#define __STEREODELAYLINE_H__ #include #include "frame.h" template -class DelayLine { +class StereoDelayLine { private: - const size_t size; frame samples[CAPACITY] = {{0, 0}}; size_t cursor; public: - DelayLine(size_t size = CAPACITY) : size(size) {} + size_t size; + + StereoDelayLine(size_t size = CAPACITY) : size(size) {} + + inline frame tick() { + ++cursor; + if(cursor == size) { + cursor = 0; + } + return samples[cursor]; + } inline frame tick(frame in) { samples[cursor++] = in; diff --git a/include/synth/dsp/sap.h b/lib/synth/include/dsp/stereosap.h similarity index 70% rename from include/synth/dsp/sap.h rename to lib/synth/include/dsp/stereosap.h index 0f1aee1..ba05e6f 100644 --- a/include/synth/dsp/sap.h +++ b/lib/synth/include/dsp/stereosap.h @@ -5,18 +5,18 @@ #include -#include "delayline.h" +#include "stereodelayline.h" template -class SAP { +class StereoSAP { private: const size_t size; - DelayLine dl; + StereoDelayLine dl; public: float gain = 0.7; - SAP(size_t size = CAPACITY) : size(size) {} + StereoSAP(size_t size = CAPACITY) : size(size) {} inline frame tick(frame in) { frame fb = in + dl.tap(0) * gain; diff --git a/lib/synth/include/globals.h b/lib/synth/include/globals.h new file mode 100644 index 0000000..ff96702 --- /dev/null +++ b/lib/synth/include/globals.h @@ -0,0 +1,9 @@ +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +#define SAMPLE_RATE 96000 +#define SAMPLE_RATE_INV (1.0 / SAMPLE_RATE) + +#define FILTER_CV_MAX 9.25 // MIDI note "135" (19912.126958213178287 Hz) + +#endif \ No newline at end of file diff --git a/lib/synth/include/luts.h b/lib/synth/include/luts.h new file mode 100644 index 0000000..4720520 --- /dev/null +++ b/lib/synth/include/luts.h @@ -0,0 +1,26 @@ +#ifndef __LUTS_H__ +#define __LUTS_H__ + +#include + +#include "globals.h" +#include "util.h" + +#define SIN_LUT_SIZE 256 +#define K_LUT_SIZE 256 + +extern float sinLUT[SIN_LUT_SIZE]; +extern float kLUT[K_LUT_SIZE]; + +void genLUTs(); + +inline float fastSin(float t) { + t += randFrac() * (1.f / SIN_LUT_SIZE); + return sinLUT[(size_t) (t * SIN_LUT_SIZE) & (SIN_LUT_SIZE - 1)]; +} + +inline float fastCVtoK(float cv) { + return kLUT[std::min(K_LUT_SIZE - (size_t) 1, (size_t) (cv * (K_LUT_SIZE - 1) * (1.f / (float) FILTER_CV_MAX)))]; +} + +#endif \ No newline at end of file diff --git a/include/synth/preset.h b/lib/synth/include/preset.h similarity index 100% rename from include/synth/preset.h rename to lib/synth/include/preset.h diff --git a/lib/synth/include/synth.h b/lib/synth/include/synth.h new file mode 100644 index 0000000..d2746e5 --- /dev/null +++ b/lib/synth/include/synth.h @@ -0,0 +1,65 @@ +#ifndef __SYNTH_H__ +#define __SYNTH_H__ + +#include "voicemanager.h" +#include "channel.h" +#include "dsp/frame.h" +#include "dsp/reverb.h" +#include "luts.h" + +class Synth { +public: + Synth() { + genLUTs(); + + for(VoiceManager::channel_t i = 0; i < 16; ++i) { + channels[i].number = i; + channels[i].voiceManager = &voiceManager; + channels[i].loadPreset(&DEFAULT_PRESET); + } + } + + void noteOn(int ch, int note, int vel); + void noteOff(int ch, int note); + void control(int ch, int cc, int val); + + inline void tick(float* out, const size_t bufferSize) { + std::fill(out, out + bufferSize, 0.f); + + float reverbBus[bufferSize]; + std::fill(reverbBus, reverbBus + bufferSize, 0.f); + + for(auto& channel : channels) { + float chOut[bufferSize]; + if(!channel.tick(chOut, bufferSize)) { + continue; + } + + for(size_t i = 0; i < bufferSize; ++i) { + out[i] += chOut[i]; + } + + for(size_t i = 0; i < bufferSize; ++i) { + reverbBus[i] += channel.settings.reverb * chOut[i]; + } + } + + for(size_t i = 0; i < bufferSize; i += 2) { + frame f = {reverbBus[i], reverbBus[i + 1]}; + f = reverb.tick(f); + out[i] += f.l; + out[i + 1] += f.r; + } + + for(size_t i = 0; i < bufferSize; ++i) { + out[i] *= 0.125f; + } + } + +private: + VoiceManager voiceManager{}; + Channel channels[16]; + Reverb reverb; +}; + +#endif \ No newline at end of file diff --git a/include/synth/dsp/util.h b/lib/synth/include/util.h similarity index 56% rename from include/synth/dsp/util.h rename to lib/synth/include/util.h index 310ee81..9b83a4b 100644 --- a/include/synth/dsp/util.h +++ b/lib/synth/include/util.h @@ -1,32 +1,35 @@ #ifndef __UTIL_H__ #define __UTIL_H__ -#include #include #include #include -#include "../globals.h" +#include "globals.h" + +#define unlikely(cond) __builtin_expect((cond), 0) +#define likely(cond) __builtin_expect((cond), 1) #define PIx2 (2 * M_PI) #define PIx2_INV (1.0 / PIx2) #define CV_FREQ_MIN 32.703195662574829 // MIDI note 24 (C1) +#define MAX_SAMPLE_PERIOD ((int) (SAMPLE_RATE / CV_FREQ_MIN + 0.5)) -inline float noteToCV(float note) { - return (note - 24) / 12.f; +inline float fakeSin(float x) { + x = 4.f * (x - 0.5f); + return x * (2.f - fabsf(x)); } inline float clamp(float x, float a, float b) { return fminf(b, fmaxf(a, x)); } -inline float triangle(float phase) { - if(phase < (float) M_PI) { - return phase / (float) M_PI_2 - 1; - } else { - return 1 - (phase - (float) M_PI) / (float) M_PI_2; - } +// Get timestamp in nanoseconds +inline uint64_t nanos() { + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); } inline float whiteNoise() { @@ -45,21 +48,30 @@ inline float randFrac() { static uint32_t x2 = 0xE1E9F0A7; x1 ^= x2; - float value = x2 / (float) 0x100000000; + float value = x2 * (1.f / 0x100000000); x2 += x1; return value; } -inline float randPhase() { - return (float) PIx2 * randFrac(); +inline float triangle(float phase) { + if(phase < 0.5f) { + return 2.f * phase - 1; + } else { + return 1 - 2.f * (phase - 0.5f); + } } -// Get time stamp in nanoseconds. -inline uint64_t nanos() { - return std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); +inline float cvToStep(float cv) { + return (float) CV_FREQ_MIN * exp2f(cv) * (float) SAMPLE_RATE_INV; +} + +inline float noteToCV(float note) { + return note / 12.f; +} + +inline float cvToPeriod(float cv) { + return (float) SAMPLE_RATE / ((float) CV_FREQ_MIN * exp2f(cv)); } #endif \ No newline at end of file diff --git a/lib/synth/include/voice.h b/lib/synth/include/voice.h new file mode 100644 index 0000000..9ad311f --- /dev/null +++ b/lib/synth/include/voice.h @@ -0,0 +1,127 @@ +#ifndef __VOICE_H__ +#define __VOICE_H__ + +#include "globals.h" +#include "preset.h" + +#include "dsp/oscillator.h" +#include "dsp/filter.h" +#include "dsp/adsr.h" + +#define MAX_OSCS 8 + +class Voice { +public: + typedef struct { + size_t unison; + + Oscillator::Mode osc1Mode; + Oscillator::Mode osc2Mode; + float oscMix; + float oscDetune; + float osc2Pitch; + + float noiseMix; + + Filter::Settings filter; + + ADSR::Envelope ampEnv; + ADSR::Envelope modEnv; + + float modEnvFltGain; + float keyTrack; + float lfoStep; + float lfoPitchMod; + float lfoFltMod; + + float reverb; + } Settings; + +private: + Settings const * settings; + + ADSR adsrAmp; + ADSR adsrMod; + Oscillator osc[MAX_OSCS]; + Filter filter; + +public: + int note; + float velocity; + float gain; + + void reset(); + void assign(Settings const * settings); + void noteOn(int note, float velocity = 1, float gain = 1.0f); + void noteOff(); + inline bool isIdle() { return adsrAmp.isIdle(); } + + inline void tick(float *out, const size_t bufferSize, float *pitchMod, float *fltMod) { + const size_t frameCount = bufferSize / 2; + + float basePitchBuf[frameCount]; + for(size_t i = 0; i < frameCount; ++i) { + basePitchBuf[i] = note + pitchMod[i]; + } + + float oscCVBuf[MAX_OSCS][frameCount]; + float detune = -settings->oscDetune * 0.5f * (settings->unison - 1); + for(size_t i = 0; i < settings->unison; ++i) { + float transpose = (i & 1) ? settings->osc2Pitch : 0; + for(size_t j = 0; j < frameCount; ++j) { + oscCVBuf[i][j] = noteToCV(basePitchBuf[i] + detune + transpose); + } + detune += settings->oscDetune; + } + + float pan, panStep; + if(settings->unison > 1) { + pan = 0.f; + panStep = 1.f / (settings->unison - 1); + } else { + pan = 0.5f; + panStep = 0.f; + } + + float oscBuf[bufferSize]; + std::fill(oscBuf, oscBuf + bufferSize, 0.f); + for(size_t i = 0; i < settings->unison; ++i) { + float baseGain = (i & 1) ? settings->oscMix : (1 - settings->oscMix); + float gain[2]; + gain[0] = sinf((1.f - pan) * (float) M_PI_2); + gain[1] = sinf(pan * (float) M_PI_2); + pan += panStep; + osc[i].tick(oscBuf, bufferSize, oscCVBuf[i], gain); + } + + float noiseBuf[bufferSize]; + if(settings->unison > 1) { // Stereo noise + for(size_t i = 0; i < bufferSize; ++i) { + noiseBuf[i] = (1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()); + } + } else { // Mono noise + for(size_t i = 0; i < bufferSize; ) { + float noise = (1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()); + noiseBuf[i++] = noise; + noiseBuf[i++] = noise; + } + } + + for(size_t i = 0; i < bufferSize; ++i) { + oscBuf[i] += settings->noiseMix * (noiseBuf[i] - oscBuf[i]); + } + + const float keyTrackVal = settings->keyTrack * (note - 24) / 12.f; + + float fltModBuf[frameCount]; + for(size_t i = 0; i < frameCount; ++i) { + fltModBuf[i] = settings->modEnvFltGain * adsrMod.tick() + keyTrackVal + fltMod[i]; + } + filter.tick(oscBuf, out, bufferSize, fltModBuf); + for(size_t i = 0; i < bufferSize; ++i) { + out[i] *= gain * adsrAmp.tick(); + } + } +}; + +#endif \ No newline at end of file diff --git a/include/synth/voicemanager.h b/lib/synth/include/voicemanager.h similarity index 94% rename from include/synth/voicemanager.h rename to lib/synth/include/voicemanager.h index 431361d..bb2e6cd 100644 --- a/include/synth/voicemanager.h +++ b/lib/synth/include/voicemanager.h @@ -3,7 +3,7 @@ #include -#include "synth/voice.h" +#include "voice.h" #define NUM_VOICES 32 diff --git a/src/synth/channel.cpp b/lib/synth/src/channel.cpp similarity index 86% rename from src/synth/channel.cpp rename to lib/synth/src/channel.cpp index 5100ad8..dced626 100644 --- a/src/synth/channel.cpp +++ b/lib/synth/src/channel.cpp @@ -1,16 +1,16 @@ #include -#include "synth/globals.h" -#include "synth/cc.h" +#include "globals.h" +#include "cc.h" -#include "synth/channel.h" +#include "channel.h" -inline float midiToNote(int note) { - return note + 24; +inline int midiToNote(int note) { + return note - 24; } inline float timeToStep(float t) { - return SAMPLE_RATE_INV / t; + return (float) SAMPLE_RATE_INV / t; } inline float ccToA(int x) { @@ -22,7 +22,7 @@ inline float ccToDR(int x) { } inline float ccToLFOStep(int x) { - return (float) PIx2 * 0.1f * expf((1 + x) * 6.907755278982137f / 128.0f) * SAMPLE_RATE_INV; + return (float) 0.1f * expf((1 + x) * 6.907755278982137f / 128.0f) * (float) SAMPLE_RATE_INV; } inline float ccToLFOPitchMod(int x) { @@ -34,7 +34,7 @@ inline float ccToLFOFltMod(int x) { } void Channel::loadPreset(const Preset * preset) { - settings.unison = 1 + floorf(preset->unison * 4.f / 128.f); + settings.unison = 1 + floorf(preset->unison * 8.f / 128.f); settings.osc1Mode = Oscillator::Mode(preset->osc1Mode); settings.osc2Mode = Oscillator::Mode(preset->osc2Mode); @@ -43,8 +43,8 @@ void Channel::loadPreset(const Preset * preset) { settings.oscMix = preset->oscMix / 127.f; settings.noiseMix = preset->noiseMix / 127.f; - settings.filter.type = Filter::Type(preset->filter.type); - settings.filter.slope = Filter::Slope(preset->filter.slope); + settings.filter.type = Filter::Type(floorf(3.f * preset->filter.type / 128.f)); + settings.filter.slope = Filter::Slope(floorf(2.f * preset->filter.slope / 128.f)); settings.filter.freq = (float) FILTER_CV_MAX * preset->filter.freq / 127.f; settings.filter.res = preset->filter.Q / 31.75f; @@ -68,18 +68,17 @@ void Channel::loadPreset(const Preset * preset) { } void Channel::noteOn(int note, int velocity) { - int range = 2 * (settings.unison - 1); - for(int i = -range; i <= range; i += 4) { - Voice* const voice = voiceManager->get(number); - voice->assign(&settings); - voice->noteOn(midiToNote(note), i * settings.oscDetune, 1.f / sqrtf(settings.unison), range ? 0.5f + 0.5f * i / range : 0.5f); - } + //printf("on: midi=%d note=%d\n", note, midiToNote(note)); + Voice* const voice = voiceManager->get(number); + voice->assign(&settings); + voice->noteOn(midiToNote(note), velocity / 127.f, 1.f, 0.5f); } void Channel::noteOff(int note) { Voice** voices = voiceManager->getChannelVoices(number); for(Voice** voice = voices; *voice != NULL; ++voice) { if((*voice)->note == midiToNote(note)) { + //printf("off: midi=%d note=%d\n", note, midiToNote(note)); (*voice)->noteOff(); } } @@ -109,7 +108,6 @@ void Channel::control(int code, int value) { case CC_FLT_Q: // Timbre / Harmonic Content (Standard MIDI) settings.filter.res = value / 31.75f; - printf("Q=%f\n", M_SQRT1_2 + (double) settings.filter.res); break; case CC_AMP_REL: // Release Time (Standard MIDI) @@ -161,7 +159,7 @@ void Channel::control(int code, int value) { break; case CC_UNISON: // Unison Amount - settings.unison = 1 + floorf(value * 4.f / 128.f); + settings.unison = 1 + floorf(value * 8.f / 128.f); break; case CC_OSC_MIX: // Oscillator 1/2 Mix diff --git a/src/synth/dsp/adsr.cpp b/lib/synth/src/dsp/adsr.cpp similarity index 90% rename from src/synth/dsp/adsr.cpp rename to lib/synth/src/dsp/adsr.cpp index 670b386..38109a4 100644 --- a/src/synth/dsp/adsr.cpp +++ b/lib/synth/src/dsp/adsr.cpp @@ -1,4 +1,4 @@ -#include "synth/dsp/adsr.h" +#include "dsp/adsr.h" void ADSR::reset() { state = IDLE; diff --git a/src/synth/dsp/filter.cpp b/lib/synth/src/dsp/filter.cpp similarity index 51% rename from src/synth/dsp/filter.cpp rename to lib/synth/src/dsp/filter.cpp index a5b3dd0..b983d74 100644 --- a/src/synth/dsp/filter.cpp +++ b/lib/synth/src/dsp/filter.cpp @@ -1,5 +1,7 @@ -#include "synth/dsp/filter.h" +#include "dsp/filter.h" void Filter::assign(Settings const * settings) { this->settings = settings; + freq = settings->freq; + res = settings->res; } \ No newline at end of file diff --git a/src/synth/dsp/oscillator.cpp b/lib/synth/src/dsp/oscillator.cpp similarity index 66% rename from src/synth/dsp/oscillator.cpp rename to lib/synth/src/dsp/oscillator.cpp index 9d1e371..c58930c 100644 --- a/src/synth/dsp/oscillator.cpp +++ b/lib/synth/src/dsp/oscillator.cpp @@ -1,4 +1,4 @@ -#include "synth/dsp/oscillator.h" +#include "dsp/oscillator.h" void Oscillator::assign(Mode const * mode) { this->mode = mode; diff --git a/src/synth/dsp/reverb.cpp b/lib/synth/src/dsp/reverb.cpp similarity index 94% rename from src/synth/dsp/reverb.cpp rename to lib/synth/src/dsp/reverb.cpp index 67425b5..a3de5ba 100644 --- a/src/synth/dsp/reverb.cpp +++ b/lib/synth/src/dsp/reverb.cpp @@ -1,6 +1,6 @@ #include -#include "synth/dsp/reverb.h" +#include "dsp/reverb.h" Reverb::Reverb() { size_t interval = REVERB_FRAMES / REVERB_TAPS - 4096 / REVERB_TAPS; diff --git a/lib/synth/src/luts.cpp b/lib/synth/src/luts.cpp new file mode 100644 index 0000000..c43c962 --- /dev/null +++ b/lib/synth/src/luts.cpp @@ -0,0 +1,19 @@ +#include +//#include + +#include "luts.h" + +#include "dsp/filter.h" + +float sinLUT[SIN_LUT_SIZE]; +float kLUT[K_LUT_SIZE]; + +void genLUTs() { + for(size_t i = 0; i < SIN_LUT_SIZE; ++i) { + sinLUT[i] = sin((i * 2 * M_PI) / SIN_LUT_SIZE); + } + + for(size_t i = 0; i < K_LUT_SIZE; ++i) { + kLUT[i] = cvToK(i * FILTER_CV_MAX / (K_LUT_SIZE - 1)); + } +} \ No newline at end of file diff --git a/src/synth/preset.cpp b/lib/synth/src/preset.cpp similarity index 80% rename from src/synth/preset.cpp rename to lib/synth/src/preset.cpp index 880953f..c9059ba 100644 --- a/src/synth/preset.cpp +++ b/lib/synth/src/preset.cpp @@ -1,4 +1,4 @@ -#include "synth/preset.h" +#include "preset.h" const Preset DEFAULT_PRESET = { .unison = 1, @@ -6,7 +6,7 @@ const Preset DEFAULT_PRESET = { .osc1Mode = Oscillator::MODE_SAW, .osc2Mode = Oscillator::MODE_SAW, .oscMix = 64, - .oscDetune = 10, + .oscDetune = 6, .osc2Pitch = 0, .noiseMix = 0, @@ -20,9 +20,9 @@ const Preset DEFAULT_PRESET = { .ampEnv = { .attack = 0, - .decay = 80, + .decay = 56, .sustain = 64, - .release = 0 + .release = 56 }, .modEnv = { @@ -35,8 +35,8 @@ const Preset DEFAULT_PRESET = { .modEnvFltGain = 56, .keyTrack = 127, .lfoFreq = 64, - .lfoPitchMod = 16, + .lfoPitchMod = 0, .lfoFltMod = 64, - .reverb = 32 + .reverb = 16 }; \ No newline at end of file diff --git a/src/synth/synth.cpp b/lib/synth/src/synth.cpp similarity index 94% rename from src/synth/synth.cpp rename to lib/synth/src/synth.cpp index 47b2275..33972eb 100644 --- a/src/synth/synth.cpp +++ b/lib/synth/src/synth.cpp @@ -1,18 +1,18 @@ #include -#include "synth/synth.h" +#include "synth.h" void Synth::noteOn(int ch, int note, int vel) { - channels[ch].noteOn(note, vel); printf("Note On: ch=%d note=%d vel=%d\n", ch, note, vel); + channels[ch].noteOn(note, vel); } void Synth::noteOff(int ch, int note) { - channels[ch].noteOff(note); printf("Note Off: ch=%d note=%d\n", ch, note); + channels[ch].noteOff(note); } void Synth::control(int ch, int cc, int val) { - channels[ch].control(cc, val); printf("Controller: ch=%d cc=%d val=%d\n", ch, cc, val); + channels[ch].control(cc, val); } \ No newline at end of file diff --git a/src/synth/voice.cpp b/lib/synth/src/voice.cpp similarity index 58% rename from src/synth/voice.cpp rename to lib/synth/src/voice.cpp index ccd04cd..23b70ed 100644 --- a/src/synth/voice.cpp +++ b/lib/synth/src/voice.cpp @@ -1,4 +1,4 @@ -#include "synth/voice.h" +#include "voice.h" void Voice::reset() { adsrAmp.reset(); @@ -9,15 +9,20 @@ void Voice::assign(Settings const * settings) { this->settings = settings; adsrAmp.assign(&settings->ampEnv); adsrMod.assign(&settings->modEnv); - osc1.assign(&settings->osc1Mode); - osc2.assign(&settings->osc2Mode); + for(size_t i = 0; i < MAX_OSCS; ++i) { + if(i & 1) { + osc[i].assign(&settings->osc2Mode); + } else { + osc[i].assign(&settings->osc1Mode); + } + } filter.assign(&settings->filter); } -void Voice::noteOn(int note, float detune, float gain, float pan) { +void Voice::noteOn(int note, float velocity, float gain, float pan) { this->note = note; + this->velocity = velocity; this->gain = gain; - this->detune = detune; this->pan = pan; adsrAmp.noteOn(); adsrMod.noteOn(); diff --git a/src/synth/voicemanager.cpp b/lib/synth/src/voicemanager.cpp similarity index 98% rename from src/synth/voicemanager.cpp rename to lib/synth/src/voicemanager.cpp index 12acd43..1f26bc0 100644 --- a/src/synth/voicemanager.cpp +++ b/lib/synth/src/voicemanager.cpp @@ -1,7 +1,7 @@ #include #include -#include "synth/voicemanager.h" +#include "voicemanager.h" #define VOICE_SCORE_MSB (8 * sizeof(score_t) - 1) diff --git a/src/synth/dsp/centlut.cpp b/src/synth/dsp/centlut.cpp deleted file mode 100644 index efcdb00..0000000 --- a/src/synth/dsp/centlut.cpp +++ /dev/null @@ -1,18 +0,0 @@ -float centLUT[] = { - 1.0000000, 1.0004549, 1.0009100, 1.0013654, 1.0018209, 1.0022767, 1.0027326, 1.0031888, - 1.0036452, 1.0041018, 1.0045586, 1.0050156, 1.0054728, 1.0059302, 1.0063878, 1.0068456, - 1.0073037, 1.0077620, 1.0082204, 1.0086790, 1.0091379, 1.0095969, 1.0100563, 1.0105158, - 1.0109755, 1.0114354, 1.0118955, 1.0123559, 1.0128164, 1.0132772, 1.0137382, 1.0141994, - 1.0146607, 1.0151223, 1.0155841, 1.0160462, 1.0165083, 1.0169708, 1.0174334, 1.0178963, - 1.0183593, 1.0188227, 1.0192862, 1.0197498, 1.0202137, 1.0206778, 1.0211421, 1.0216067, - 1.0220715, 1.0225364, 1.0230016, 1.0234669, 1.0239326, 1.0243984, 1.0248644, 1.0253307, - 1.0257971, 1.0262637, 1.0267307, 1.0271977, 1.0276650, 1.0281326, 1.0286002, 1.0290682, - 1.0295364, 1.0300047, 1.0304732, 1.0309421, 1.0314111, 1.0318803, 1.0323497, 1.0328194, - 1.0332892, 1.0337592, 1.0342295, 1.0347000, 1.0351708, 1.0356417, 1.0361128, 1.0365841, - 1.0370557, 1.0375276, 1.0379995, 1.0384717, 1.0389441, 1.0394168, 1.0398897, 1.0403627, - 1.0408360, 1.0413095, 1.0417832, 1.0422572, 1.0427313, 1.0432056, 1.0436803, 1.0441550, - 1.0446301, 1.0451053, 1.0455807, 1.0460564, 1.0465323, 1.0470084, 1.0474846, 1.0479612, - 1.0484380, 1.0489149, 1.0493921, 1.0498694, 1.0503471, 1.0508249, 1.0513029, 1.0517812, - 1.0522597, 1.0527384, 1.0532173, 1.0536964, 1.0541759, 1.0546554, 1.0551351, 1.0556152, - 1.0560954, 1.0565759, 1.0570565, 1.0575374, 1.0580184, 1.0584998, 1.0589813, 1.0594631 -}; diff --git a/src/synth/dsp/filterlut.cpp b/src/synth/dsp/filterlut.cpp deleted file mode 100644 index 4ea7d28..0000000 --- a/src/synth/dsp/filterlut.cpp +++ /dev/null @@ -1,10 +0,0 @@ -float kLUT[] = { - 0.0015708, 0.0017336, 0.0019134, 0.0021118, 0.0023307, 0.0025723, 0.0028390, 0.0031333, - 0.0034582, 0.0038167, 0.0042124, 0.0046491, 0.0051311, 0.0056631, 0.0062502, 0.0068982, - 0.0076134, 0.0084028, 0.0092740, 0.0102355, 0.0112968, 0.0124681, 0.0137608, 0.0151877, - 0.0167625, 0.0185007, 0.0204193, 0.0225369, 0.0248743, 0.0274544, 0.0303024, 0.0334462, - 0.0369167, 0.0407480, 0.0449779, 0.0496483, 0.0548053, 0.0605004, 0.0667905, 0.0737388, - 0.0814158, 0.0898997, 0.0992782, 0.1096492, 0.1211226, 0.1338222, 0.1478880, 0.1634790, - 0.1807770, 0.1999912, 0.2213640, 0.2451794, 0.2717728, 0.3015462, 0.3349871, 0.3726965, - 0.4154281, 0.4641461, 0.5201120, 0.5850177, 0.6611994, 0.7519926, 0.8623521, 1.0000000 -}; diff --git a/src/synth/dsp/notelut.cpp b/src/synth/dsp/notelut.cpp deleted file mode 100644 index 952bca2..0000000 --- a/src/synth/dsp/notelut.cpp +++ /dev/null @@ -1,18 +0,0 @@ -float noteLUT[] = { - 8.1757994, 8.6619568, 9.1770239, 9.7227182, 10.3008614, 10.9133825, 11.5623255, 12.2498569, - 12.9782715, 13.7500000, 14.5676174, 15.4338531, 16.3515987, 17.3239136, 18.3540478, 19.4454365, - 20.6017227, 21.8267651, 23.1246510, 24.4997139, 25.9565430, 27.5000000, 29.1352348, 30.8677063, - 32.7031975, 34.6478271, 36.7080956, 38.8908730, 41.2034454, 43.6535301, 46.2493019, 48.9994278, - 51.9130859, 55.0000000, 58.2704697, 61.7354126, 65.4063950, 69.2956543, 73.4161911, 77.7817459, - 82.4068909, 87.3070602, 92.4986038, 97.9988556, 103.8261719, 110.0000000, 116.5409393, 123.4708252, - 130.8127899, 138.5913086, 146.8323822, 155.5634918, 164.8137817, 174.6141205, 184.9972076, 195.9977112, - 207.6523438, 220.0000000, 233.0818787, 246.9416504, 261.6255798, 277.1826172, 293.6647644, 311.1269836, - 329.6275635, 349.2282410, 369.9944153, 391.9954224, 415.3046875, 440.0000000, 466.1637573, 493.8833008, - 523.2511597, 554.3652344, 587.3295288, 622.2539673, 659.2551270, 698.4564819, 739.9888306, 783.9908447, - 830.6093750, 880.0000000, 932.3275146, 987.7666016, 1046.5023193, 1108.7304688, 1174.6590576, 1244.5079346, - 1318.5102539, 1396.9129639, 1479.9776611, 1567.9816895, 1661.2187500, 1760.0000000, 1864.6550293, 1975.5332031, - 2093.0046387, 2217.4609375, 2349.3181152, 2489.0158691, 2637.0205078, 2793.8259277, 2959.9553223, 3135.9633789, - 3322.4375000, 3520.0000000, 3729.3100586, 3951.0664062, 4186.0092773, 4434.9218750, 4698.6362305, 4978.0317383, - 5274.0410156, 5587.6518555, 5919.9106445, 6271.9267578, 6644.8750000, 7040.0000000, 7458.6201172, 7902.1328125, - 8372.0185547, 8869.8437500, 9397.2724609, 9956.0634766, 10548.0820312, 11175.3037109, 11839.8212891, 12543.8535156 -}; diff --git a/src/synthapp.cpp b/src/synthapp.cpp index eb6e54f..7610ccf 100644 --- a/src/synthapp.cpp +++ b/src/synthapp.cpp @@ -1,9 +1,10 @@ #include #include #include +#include -#include "synth/globals.h" -#include "synth/dsp/util.h" +#include "globals.h" +#include "util.h" #include "synthapp.h" #include "synthframe.h" @@ -30,13 +31,15 @@ static int paCallback( firstPACallback = false; } + app->synthMutex.lock(); PmEvent events[16]; int numEvents = Pm_Read(app->pmStream, events, 16); for(int i = 0; i < numEvents; ++i) { PmMessage msg = events[i].message; int type = (msg >> 4) & 0xF; - int ch = msg & 0xF; + //int ch = msg & 0xF; + int ch = 0; if(type == 0x8 || (type == 0x9 && Pm_MessageData2(msg) == 0)) { // Note Off int note = Pm_MessageData1(msg); @@ -55,12 +58,9 @@ static int paCallback( float *out = (float*) outputBuffer; uint64_t started = nanos(); - for(unsigned long i = 0; i < framesPerBuffer; i++) { - frame f = app->synth.tick(); - *out++ = f.l; - *out++ = f.r; - } + app->synth.tick(out, framesPerBuffer * 2); uint64_t finished = nanos(); + app->synthMutex.unlock(); synthNanos += finished - started; synthSamples += framesPerBuffer; diff --git a/src/synthframe.cpp b/src/synthframe.cpp index 1f565f5..c76e82d 100644 --- a/src/synthframe.cpp +++ b/src/synthframe.cpp @@ -1,7 +1,7 @@ #include -#include "synth/cc.h" -#include "synth/preset.h" +#include "cc.h" +#include "preset.h" #include "synthframe.h" #include "synthapp.h" @@ -83,127 +83,177 @@ void SynthFrame::OnAbout(wxCommandEvent& event) { void SynthFrame::OnUnisonScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_UNISON, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnOsc1ModeScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_OSC1MDE, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnOsc2ModeScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_OSC2MDE, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnOscDetScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_OSC_DET, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnOsc2PitScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_OSC2PIT, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnOscMixScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_OSC_MIX, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnNoiseMixScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_NOI_MIX, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltTypeScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_TYP, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltSlopeScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_SLP, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltFreqScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_FRQ, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltQScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_Q, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnKeyTrackScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_KEY_TRK, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnAmpAttackScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_AMP_ATK, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnAmpDecayScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_AMP_DEC, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnAmpSustainScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_AMP_SUS, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnAmpReleaseScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_AMP_REL, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltAttackScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_ATK, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltDecayScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_DEC, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltSustainScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_SUS, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnFltReleaseScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_FLT_REL, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnModEnvFltGainScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_MOD_FLT, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnLFOFreqScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_LFO_FRQ, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnLFOPitchScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_LFO_PIT, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnLFOFilterScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_LFO_FLT, event.GetPosition()); + app.synthMutex.unlock(); } void SynthFrame::OnReverbScroll(wxScrollEvent& event) { SynthApp& app = wxGetApp(); + app.synthMutex.lock(); app.synth.control(0, CC_RVB_SND, event.GetPosition()); + app.synthMutex.unlock(); } wxBEGIN_EVENT_TABLE(SynthFrame, wxFrame)