diff --git a/CMakeLists.txt b/CMakeLists.txt index 3101337..5089f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ if(MSVC) set(CMAKE_CXX_FLAGS_DEBUG "/W4 /DEBUG:FASTLINK /fp:fast") set(CMAKE_CXX_FLAGS_RELEASE "/W4 /O2 /fp:fast /DNDEBUG") else() - set(CMAKE_CXX_FLAGS_DEBUG "-Wall -g -ffast-math") - set(CMAKE_CXX_FLAGS_RELEASE "-Wall -Ofast -DNDEBUG") + set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Wextra -Wdouble-promotion -g -ffast-math") + set(CMAKE_CXX_FLAGS_RELEASE "-Wall -Wextra -Wdouble-promotion -Ofast -DNDEBUG") endif() if(WIN32) @@ -53,3 +53,6 @@ target_include_directories(main PRIVATE lib/portmidi/porttime) target_link_libraries(main PRIVATE wxbase) target_link_libraries(main PRIVATE wxcore) + +add_custom_target(synth) +add_dependencies(synth main genluts) diff --git a/include/synth/channel.h b/include/synth/channel.h index 4ac9163..10cfac1 100644 --- a/include/synth/channel.h +++ b/include/synth/channel.h @@ -25,7 +25,7 @@ public: void control(int cc, int val); frame tick() { - frame out{0}; + frame out{0, 0}; float lfo = sin(lfoPhase); Voice** voices = voiceManager->getChannelVoices(number); for(Voice** voice = voices; *voice != NULL; ++voice) { @@ -34,7 +34,7 @@ public: out *= volume; lfoPhase += settings.lfoStep; - while(lfoPhase >= PIx2) { + while(lfoPhase >= (float) PIx2) { lfoPhase -= PIx2; } diff --git a/include/synth/dsp/adsr.h b/include/synth/dsp/adsr.h index e6f70eb..ea24c50 100644 --- a/include/synth/dsp/adsr.h +++ b/include/synth/dsp/adsr.h @@ -77,7 +77,7 @@ private: float last; float curve(float gain) { - return 0.5 - 0.5 * cos(clamp(gain * M_PI, 0, M_PI)); + return 0.5f - 0.5f * cosf(clamp(gain * (float) M_PI, 0.f, M_PI)); } }; diff --git a/include/synth/dsp/filter.h b/include/synth/dsp/filter.h index 077d68d..f993beb 100644 --- a/include/synth/dsp/filter.h +++ b/include/synth/dsp/filter.h @@ -10,12 +10,16 @@ #include "../globals.h" #include "util.h" -#define FILTER_K_BASE 6.214608098422192 +#define FILTER_OVERSAMPLE 4 +#define FILTER_K_MIN_FREQ 20.0f +#define FILTER_K_MAX_FREQ 15000.0f +#define FILTER_K_BASE logf(FILTER_K_MAX_FREQ / FILTER_K_MIN_FREQ) +#define FILTER_K_SCALE (2.f * FILTER_K_MIN_FREQ / (FILTER_OVERSAMPLE * SAMPLE_RATE)) +#define K_LUT_SIZE 64 #ifdef USE_LUTS - #define K_LUT_SIZE 64 - + extern float kLUT[K_LUT_SIZE]; inline float freqToK(float x) { @@ -27,13 +31,13 @@ #else inline float freqToK(float x) { - return tan(M_PI_4 * 0.002 * exp(x * FILTER_K_BASE)); + return tanf((float) M_PI_2 * std::min(0.5f, FILTER_K_SCALE * expf(x * FILTER_K_BASE))); } #endif inline float noteToFltFreq(float note) { - return ((note - 69) / 12.0) / (FILTER_K_BASE * M_LOG2E); + return ((note - 69) / 12.0f) / (FILTER_K_BASE * (float) M_LOG2E); } class SVF12 { @@ -64,6 +68,28 @@ public: } }; +class Oversampler { +private: + int times; + float kK, kQ = (float) M_SQRT1_2; + SVF12 aa1, aa2; + +public: + Oversampler(int times) : times(times), kK(tanf((float) M_PI_2 * 1.0f / times)) {} + + inline float tick(float in, std::function process) { + if(times < 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); + } + return out; + } + } +}; + // 12 and 24 dB/oct class Filter { public: @@ -88,27 +114,32 @@ public: private: Settings const * settings; SVF12 fltA, fltB; + Oversampler os{FILTER_OVERSAMPLE}; public: void assign(Settings const * settings); inline float tick(float in, float freqAdd) { - float freq = clamp(settings->freq + freqAdd, 0, 1); - //float kK = tan(M_PI_4 * clamp(0.002 * exp(freq * FILTER_K_BASE), 0, 1)); + float freq = clamp(settings->freq + freqAdd, 0.f, 1.f); float kK = freqToK(freq); - float kQ = M_SQRT1_2 + settings->res; - - SVF12::Output outA = fltA.tick(in, 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; - } + 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 index 974ead7..cd4b3cf 100644 --- a/include/synth/dsp/oscillator.h +++ b/include/synth/dsp/oscillator.h @@ -20,7 +20,7 @@ public: float driftAmount; float driftValue; - Oscillator() : phase(randPhase()), value(0), driftAmount(0.001), driftValue(0) {} + Oscillator() : phase(randPhase()), value(0), driftAmount(0.001f), driftValue(0) {} void assign(Mode const * mode); @@ -32,21 +32,22 @@ public: 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.0 * phase / PIx2) - 1.0; // Render naive waveshape + value = (2.0f * phase / PIx2) - 1.0f; // Render naive waveshape value -= polyBlep(t, dt); // Layer output of Poly BLEP on top } else if (*mode == MODE_SQUARE) { - if (phase < M_PI) { + 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.5, 1.0), dt); // Layer output of Poly BLEP on top (flop) + value -= polyBlep(fmodf(t + 0.5f, 1.0f), dt); // Layer output of Poly BLEP on top (flop) } - driftValue += 0.01 * (randFrac() - 0.5) - 0.00001 * driftValue; - phase += phaseStep * (1.0 + driftAmount * driftValue); - while(phase >= PIx2) { // wrap if phase angle >=360º + //driftValue += 0.01f * (randFrac() - 0.5f) - 0.00001f * driftValue; + //phase += phaseStep * (1.0f + driftAmount * driftValue); + phase += phaseStep; + while(phase >= (float) PIx2) { // wrap if phase angle >=360º phase -= PIx2; } @@ -59,15 +60,15 @@ public: // discontinuities between 0 & 1 if (t < dt) { t /= dt; - return t + t - t * t - 1.0; + return t + t - t * t - 1.0f; } // t^2/2 +t +1/2 // -1 <= t <= 0 // discontinuities between -1 & 0 - else if (t > 1.0 - dt) { - t = (t - 1.0) / dt; - return t * t + t + t + 1.0; + else if (t > 1.0f - dt) { + t = (t - 1.0f) / dt; + return t * t + t + t + 1.0f; } // no discontinuities diff --git a/include/synth/dsp/util.h b/include/synth/dsp/util.h index e9a8bce..ab3d1e1 100644 --- a/include/synth/dsp/util.h +++ b/include/synth/dsp/util.h @@ -6,12 +6,12 @@ #include "../globals.h" -#define PIx2 (2 * M_PI) +#define PIx2 ((float )(2 * M_PI)) -#ifdef USE_LUTS - - #define NOTE_LUT_SIZE 128 - #define CENT_LUT_SIZE 128 +//#define USE_NOTE_LUTS +#define NOTE_LUT_SIZE 128 +#define CENT_LUT_SIZE 128 +#ifdef USE_NOTE_LUTS extern float noteLUT[NOTE_LUT_SIZE]; extern float centLUT[CENT_LUT_SIZE]; @@ -25,7 +25,7 @@ #else inline float noteToFreq(float note) { - return 440 * pow(2, (note - 69) / 12.0); + return 440 * powf(2.f, (note - 69) / 12.0f); } #endif @@ -35,19 +35,19 @@ inline float clamp(float x, float a, float b) { } inline float triangle(float phase) { - if(phase < M_PI) { - return phase / M_PI_2 - 1; + if(phase < (float) M_PI) { + return phase / (float) M_PI_2 - 1; } else { - return 1 - (phase - M_PI) / M_PI_2; + return 1 - (phase - (float) M_PI) / (float) M_PI_2; } } inline float randFrac() { - return (float) rand() / RAND_MAX; + return rand() / (float) RAND_MAX; } inline float randPhase() { - return PIx2 * randFrac(); + return (float) PIx2 * randFrac(); } #endif \ No newline at end of file diff --git a/include/synth/globals.h b/include/synth/globals.h index c821608..795e9d0 100644 --- a/include/synth/globals.h +++ b/include/synth/globals.h @@ -3,6 +3,4 @@ #define SAMPLE_RATE 44100 -//#define USE_LUTS - #endif \ No newline at end of file diff --git a/include/synth/synth.h b/include/synth/synth.h index 8cadb02..e9d48c0 100644 --- a/include/synth/synth.h +++ b/include/synth/synth.h @@ -8,7 +8,7 @@ class Synth { public: Synth() { - for(int i = 0; i < 16; ++i) { + for(channel_t i = 0; i < 16; ++i) { channels[i].number = i; channels[i].voiceManager = &voiceManager; channels[i].loadPreset(&DEFAULT_PRESET); @@ -24,7 +24,7 @@ public: for(auto& channel : channels) { out += channel.tick(); } - out *= 0.100; + out *= 0.100f; return out; } diff --git a/include/synth/voice.h b/include/synth/voice.h index a860f2c..89999cc 100644 --- a/include/synth/voice.h +++ b/include/synth/voice.h @@ -40,16 +40,18 @@ private: public: int note; + float unisonPosition; void reset(); void assign(Settings const * settings); - void noteOn(int note); + void noteOn(int note, float unisonPosition = 0); void noteOff(); inline bool isIdle() { return adsrAmp.isIdle(); } inline float tick(float pitchAdd, float fltFreqAdd) { - const float osc1PhaseStep = noteToFreq(note + pitchAdd - settings->oscDetune) * PIx2 / SAMPLE_RATE; - const float osc2PhaseStep = noteToFreq(note + pitchAdd + settings->oscDetune + settings->osc2Pitch) * PIx2 / SAMPLE_RATE; + const float basePitch = note + pitchAdd + unisonPosition * 4 * settings->oscDetune; + const float osc1PhaseStep = noteToFreq(basePitch - settings->oscDetune) * PIx2 / SAMPLE_RATE; + const float osc2PhaseStep = noteToFreq(basePitch + settings->oscDetune + settings->osc2Pitch) * PIx2 / SAMPLE_RATE; float out = 0; out += (1 - settings->oscMix) * osc1.tick(osc1PhaseStep); out += settings->oscMix * osc2.tick(osc2PhaseStep); diff --git a/include/synth/voicemanager.h b/include/synth/voicemanager.h index 0358ddb..f37e57f 100644 --- a/include/synth/voicemanager.h +++ b/include/synth/voicemanager.h @@ -5,7 +5,7 @@ #include "synth/voice.h" -#define NUM_VOICES 16 +#define NUM_VOICES 32 typedef unsigned int channel_t; typedef unsigned int serial_t; diff --git a/src/genluts.cpp b/src/genluts.cpp index 21fc7bd..2cff62d 100644 --- a/src/genluts.cpp +++ b/src/genluts.cpp @@ -1,19 +1,17 @@ #include #include -#define USE_LUTS - #include "synth/dsp/filter.h" #include "synth/dsp/util.h" void dumpLUT(const char * filename, const char * identifier, float *lut, size_t size) { FILE * f = fopen(filename, "w"); fprintf(f, "float %s[] = {\n", identifier); - for(int i = 0; i < size; ++i) { + for(size_t i = 0; i < size; ++i) { if(i % 8 == 0) { fputs(" ", f); } - fprintf(f, "%13.7f", lut[i]); + fprintf(f, "%13.7ff", (double) lut[i]); if(i < size - 1) { fputs(", ", f); } @@ -28,19 +26,19 @@ void dumpLUT(const char * filename, const char * identifier, float *lut, size_t int main(int argc, char** argv) { float kLUT[K_LUT_SIZE]; for(int i = 0; i < K_LUT_SIZE; ++i) { - kLUT[i] = tan(M_PI_4 * 0.002 * exp(i / (K_LUT_SIZE - 1.0) * FILTER_K_BASE)); + kLUT[i] = tanf((float) M_PI_4 * FILTER_K_SCALE * expf(i / (K_LUT_SIZE - 1.0f) * FILTER_K_BASE)); } dumpLUT("src/synth/dsp/filterlut.cpp", "kLUT", kLUT, K_LUT_SIZE); float noteLUT[NOTE_LUT_SIZE]; for(int i = 0; i < NOTE_LUT_SIZE; ++i) { - noteLUT[i] = 440 * pow(2, (i - 69) / 12.0); + noteLUT[i] = 440 * powf(2.f, (i - 69.f) / 12.0f); } dumpLUT("src/synth/dsp/notelut.cpp", "noteLUT", noteLUT, NOTE_LUT_SIZE); float centLUT[CENT_LUT_SIZE]; for(int i = 0; i < CENT_LUT_SIZE; ++i) { - centLUT[i] = pow(2, (float) i / ((CENT_LUT_SIZE - 1) * 12.0)); + centLUT[i] = pow(2.f, i / ((CENT_LUT_SIZE - 1) * 12.0f)); } dumpLUT("src/synth/dsp/centlut.cpp", "centLUT", centLUT, CENT_LUT_SIZE); } \ No newline at end of file diff --git a/src/synth/channel.cpp b/src/synth/channel.cpp index a56b89f..143ee65 100644 --- a/src/synth/channel.cpp +++ b/src/synth/channel.cpp @@ -6,7 +6,7 @@ #include "synth/channel.h" float timeToStep(float t) { - return (1.0 / SAMPLE_RATE) / t; + return (1.0f / SAMPLE_RATE) / t; } float ccToA(int x) { @@ -18,7 +18,7 @@ float ccToDR(int x) { } float ccToLFOStep(int x) { - return PIx2 * 0.1 * exp((1 + x) * 6.907755278982137 / 128.0) / SAMPLE_RATE; + return PIx2 * 0.1f * expf((1 + x) * 6.907755278982137f / 128.0f) / SAMPLE_RATE; } float ccToLFOPitchMod(int x) { @@ -55,9 +55,11 @@ void Channel::loadPreset(const Preset * preset) { } void Channel::noteOn(int note, int velocity) { - Voice* const voice = voiceManager->get(number); - voice->assign(&settings); - voice->noteOn(note); + for(int i = -1; i <= 1; ++i) { + Voice* const voice = voiceManager->get(number); + voice->assign(&settings); + voice->noteOn(note, i); + } } void Channel::noteOff(int note) { @@ -93,6 +95,7 @@ void Channel::control(int code, int value) { case CC_FLT_Q: // Timbre / Harmonic Content (Standard MIDI) settings.filter.res = value / 31.75; + printf("Q=%f\n", M_SQRT1_2 + (double) settings.filter.res); break; case CC_AMP_REL: // Release Time (Standard MIDI) diff --git a/src/synth/preset.cpp b/src/synth/preset.cpp index 4893b6b..206ceac 100644 --- a/src/synth/preset.cpp +++ b/src/synth/preset.cpp @@ -22,10 +22,10 @@ const Preset DEFAULT_PRESET = { }, .modEnv = { - .attack = 48, - .decay = 100, + .attack = 0, + .decay = 106, .sustain = 0, - .release = 100 + .release = 106 }, .modEnvFltGain = 56, diff --git a/src/synth/voice.cpp b/src/synth/voice.cpp index b82e070..e394be4 100644 --- a/src/synth/voice.cpp +++ b/src/synth/voice.cpp @@ -14,8 +14,9 @@ void Voice::assign(Settings const * settings) { filter.assign(&settings->filter); } -void Voice::noteOn(int note) { +void Voice::noteOn(int note, float unisonPosition) { this->note = note; + this->unisonPosition = unisonPosition; adsrAmp.noteOn(); adsrMod.noteOn(); } diff --git a/src/synth/voicemanager.cpp b/src/synth/voicemanager.cpp index 76a1282..7378d83 100644 --- a/src/synth/voicemanager.cpp +++ b/src/synth/voicemanager.cpp @@ -1,4 +1,5 @@ #include +#include #include "synth/voicemanager.h" @@ -20,11 +21,11 @@ Voice * VoiceManager::get(channel_t channel) { voiceData.serial = serial++; voiceData.voice.reset(); - putchar('\n'); - for(VoiceData& vd : voices) { - printf("channel=%d serial=%d idle=%d note=%d\n", vd.channel, vd.serial, vd.voice.isIdle(), vd.voice.note); - } - putchar('\n'); + //putchar('\n'); + //for(VoiceData& vd : voices) { + // printf("channel=%d serial=%d idle=%d note=%d\n", vd.channel, vd.serial, vd.voice.isIdle(), vd.voice.note); + //} + //putchar('\n'); return &voiceData.voice; } diff --git a/src/synthapp.cpp b/src/synthapp.cpp index 97e86fa..6b5d89c 100644 --- a/src/synthapp.cpp +++ b/src/synthapp.cpp @@ -50,7 +50,7 @@ static int paCallback( float *out = (float*) outputBuffer; - for(int i = 0; i < framesPerBuffer; i++) { + for(unsigned long i = 0; i < framesPerBuffer; i++) { frame f = app->synth.tick(); *out++ = f.left; *out++ = f.right;