diff --git a/CMakeLists.txt b/CMakeLists.txt index 803df54..bf17a19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ add_executable(main src/synthframe.cpp src/synth/synth.cpp src/synth/voicemanager.cpp - src/synth/part.cpp + src/synth/channel.cpp src/synth/preset.cpp src/synth/voice.cpp src/synth/dsp/oscillator.cpp diff --git a/include/synth/part.h b/include/synth/channel.h similarity index 65% rename from include/synth/part.h rename to include/synth/channel.h index 7b0c5ee..e76232c 100644 --- a/include/synth/part.h +++ b/include/synth/channel.h @@ -9,13 +9,13 @@ #include "voice.h" #include "preset.h" -class Part { +class Channel { public: + channel_t number; VoiceManager* voiceManager; - Voice::Settings settings; - Part() : volume(1), pitchBend(0), modulation(0), lfoPhase(randPhase()) {} + Channel() : volume(1), pitchBend(0), modulation(0), lfoPhase(randPhase()) {} void loadPreset(const Preset* preset); @@ -25,11 +25,9 @@ public: frame tick() { frame out{0}; - for(auto& kv : notes) { - int note = kv.first; - for(auto& voice : kv.second) { - out += voice->tick(note + pitchBend + settings.lfoPitchMod * sin(lfoPhase)); - } + Voice** voices = voiceManager->getChannelVoices(number); + for(Voice** voice = voices; *voice != NULL; ++voice) { + out += (*voice)->tick(pitchBend + settings.lfoPitchMod * sin(lfoPhase)); } out *= volume; @@ -47,8 +45,6 @@ private: float modulation; float lfoPhase; - - std::unordered_map> notes; }; #endif \ No newline at end of file diff --git a/include/synth/dsp/adsr.h b/include/synth/dsp/adsr.h index 6d31b5f..e6f70eb 100644 --- a/include/synth/dsp/adsr.h +++ b/include/synth/dsp/adsr.h @@ -17,6 +17,7 @@ public: Envelope const * env; ADSR() : state(IDLE), t(0), last(0) {}; + void reset(); void assign(Envelope const * env); void noteOn(); void noteOff(); diff --git a/include/synth/synth.h b/include/synth/synth.h index 2610b9d..da19201 100644 --- a/include/synth/synth.h +++ b/include/synth/synth.h @@ -2,14 +2,15 @@ #define __SYNTH_H__ #include "voicemanager.h" -#include "part.h" +#include "channel.h" class Synth { public: Synth() { for(int i = 0; i < 16; ++i) { - parts[i].voiceManager = &voiceManager; - parts[i].loadPreset(&DEFAULT_PRESET); + channels[i].number = i; + channels[i].voiceManager = &voiceManager; + channels[i].loadPreset(&DEFAULT_PRESET); } } @@ -19,8 +20,8 @@ public: frame tick() { frame out{}; - for(auto& part : parts) { - out += part.tick(); + for(auto& channel : channels) { + out += channel.tick(); } out *= 0.100; return out; @@ -28,7 +29,7 @@ public: private: VoiceManager voiceManager{}; - Part parts[16]; + Channel channels[16]; }; #endif \ No newline at end of file diff --git a/include/synth/voice.h b/include/synth/voice.h index 5808c61..7909281 100644 --- a/include/synth/voice.h +++ b/include/synth/voice.h @@ -39,18 +39,18 @@ private: Filter filter; public: - bool keyed; + int note; - Voice(): keyed(false) {} + void reset(); void assign(Settings const * settings); - void noteOn(); + void noteOn(int note); void noteOff(); - bool isIdle() { return adsrAmp.isIdle(); } + inline bool isIdle() { return adsrAmp.isIdle(); } - inline float tick(float note) { - const float keyTrackMul = 1 + settings->keyTrack * noteToFreq(note) / 440.0; - const float osc1PhaseStep = noteToFreq(note - settings->oscDetune) * PIx2 / SAMPLE_RATE; - const float osc2PhaseStep = noteToFreq(note + settings->oscDetune + settings->osc2Pitch) * PIx2 / SAMPLE_RATE; + inline float tick(float pitchMod) { + const float keyTrackMul = 1 + settings->keyTrack * noteToFreq(note + pitchMod) / 440.0; + const float osc1PhaseStep = noteToFreq(note + pitchMod - settings->oscDetune) * PIx2 / SAMPLE_RATE; + const float osc2PhaseStep = noteToFreq(note + pitchMod + 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 589db65..0358ddb 100644 --- a/include/synth/voicemanager.h +++ b/include/synth/voicemanager.h @@ -5,17 +5,26 @@ #include "synth/voice.h" +#define NUM_VOICES 16 + +typedef unsigned int channel_t; +typedef unsigned int serial_t; + class VoiceManager { public: VoiceManager(); - Voice * allocate(); - void free(Voice * voice); - + Voice* get(channel_t channel); + Voice** getChannelVoices(channel_t channel); private: - Voice voices[64]; - std::unordered_set idle; - std::unordered_set active; + struct VoiceData { + channel_t channel; + serial_t serial; + Voice voice; + }; + + unsigned int serial; + VoiceData voices[NUM_VOICES]; }; #endif \ No newline at end of file diff --git a/src/synth/part.cpp b/src/synth/channel.cpp similarity index 81% rename from src/synth/part.cpp rename to src/synth/channel.cpp index f708b7f..1b11d25 100644 --- a/src/synth/part.cpp +++ b/src/synth/channel.cpp @@ -3,7 +3,7 @@ #include "synth/globals.h" #include "synth/cc.h" -#include "synth/part.h" +#include "synth/channel.h" float timeToStep(float t) { return (1.0 / SAMPLE_RATE) / t; @@ -25,7 +25,7 @@ float ccToLFOPitchMod(int x) { return 0.01 * exp(x * 7.090076835776092 / 127.0); } -void Part::loadPreset(const Preset * preset) { +void Channel::loadPreset(const Preset * preset) { settings.osc1Mode = Oscillator::Mode(preset->osc1Mode); settings.osc2Mode = Oscillator::Mode(preset->osc2Mode); settings.oscMix = preset->oscMix / 127.0; @@ -54,42 +54,22 @@ void Part::loadPreset(const Preset * preset) { settings.lfoFltMod = preset->lfoFltMod / 127.0; } -void Part::noteOn(int note, int velocity) { - // Garbage collection - free up idle voices - std::erase_if(notes, [this](auto& note) { - std::erase_if(note.second, [this](auto& voice) { - if(voice->isIdle()) { - voiceManager->free(voice); - return true; - } - return false; - }); - return note.second.empty(); - }); - - Voice * const voice = voiceManager->allocate(); +void Channel::noteOn(int note, int velocity) { + Voice* const voice = voiceManager->get(number); voice->assign(&settings); - voice->noteOn(); - if(notes.count(note)) { - notes.at(note).insert(voice); - } else { - notes.emplace(note, std::set{voice}); - } + voice->noteOn(note); } -void Part::noteOff(int note) { - if(notes.count(note)) { - auto& voices = notes.at(note); - for (auto i = voices.rbegin(); i != voices.rend(); ++i ) { - if((*i)->keyed) { - (*i)->noteOff(); - break; - } - } +void Channel::noteOff(int note) { + Voice** voices = voiceManager->getChannelVoices(number); + for(Voice** voice = voices; *voice != NULL; ++voice) { + if((*voice)->note == note) { + (*voice)->noteOff(); + } } } -void Part::control(int code, int value) { +void Channel::control(int code, int value) { switch(code) { case CC_VOLUME: // Volume (Standard MIDI) volume = value / 127.0; diff --git a/src/synth/dsp/adsr.cpp b/src/synth/dsp/adsr.cpp index 8f78316..670b386 100644 --- a/src/synth/dsp/adsr.cpp +++ b/src/synth/dsp/adsr.cpp @@ -1,5 +1,10 @@ #include "synth/dsp/adsr.h" +void ADSR::reset() { + state = IDLE; + t = 0; +} + void ADSR::assign(Envelope const * env) { this->env = env; } @@ -10,6 +15,8 @@ void ADSR::noteOn() { } void ADSR::noteOff() { - state = RELEASE; - t = 1; + if(state != RELEASE) { + state = RELEASE; + t = 1; + } } \ No newline at end of file diff --git a/src/synth/synth.cpp b/src/synth/synth.cpp index 3ecc0a2..a909d7a 100644 --- a/src/synth/synth.cpp +++ b/src/synth/synth.cpp @@ -3,16 +3,16 @@ #include "synth/synth.h" void Synth::noteOn(int ch, int note, int vel) { - parts[ch].noteOn(note, vel); + channels[ch].noteOn(note, vel); std::cout << "Note On: ch=" << ch << " note=" << note << " vel=" << vel << std::endl; } void Synth::noteOff(int ch, int note) { - parts[ch].noteOff(note); + channels[ch].noteOff(note); std::cout << "Note Off: ch=" << ch << " note=" << note << std::endl; } void Synth::control(int ch, int cc, int val) { - parts[ch].control(cc, val); + channels[ch].control(cc, val); std::cout << "Controller: ch=" << ch << " cc=" << cc << " val=" << val << std::endl; } \ No newline at end of file diff --git a/src/synth/voice.cpp b/src/synth/voice.cpp index c403dbe..b82e070 100644 --- a/src/synth/voice.cpp +++ b/src/synth/voice.cpp @@ -1,5 +1,10 @@ #include "synth/voice.h" +void Voice::reset() { + adsrAmp.reset(); + adsrMod.reset(); +} + void Voice::assign(Settings const * settings) { this->settings = settings; adsrAmp.assign(&settings->ampEnv); @@ -9,14 +14,13 @@ void Voice::assign(Settings const * settings) { filter.assign(&settings->filter); } -void Voice::noteOn() { - keyed = true; +void Voice::noteOn(int note) { + this->note = note; adsrAmp.noteOn(); adsrMod.noteOn(); } void Voice::noteOff() { - keyed = false; adsrAmp.noteOff(); adsrMod.noteOff(); } \ No newline at end of file diff --git a/src/synth/voicemanager.cpp b/src/synth/voicemanager.cpp index dfd3ff3..bf4f4e6 100644 --- a/src/synth/voicemanager.cpp +++ b/src/synth/voicemanager.cpp @@ -2,23 +2,40 @@ #include "synth/voicemanager.h" -VoiceManager::VoiceManager() { - for(int i = 0; i < 64; ++i) { - idle.insert(i); +VoiceManager::VoiceManager() : serial(0) { + for(VoiceData& vd : voices) { + vd.channel = 0; + vd.serial = 0; } } -Voice * VoiceManager::allocate() { - int index = *idle.begin(); - idle.erase(index); - active.insert(index); - std::cout << "allocate(): active=" << active.size() << " idle=" << idle.size() << std::endl; - return &voices[index]; +Voice * VoiceManager::get(channel_t channel) { + // Sort idle voices first, then by highest channel and lowest serial + std::sort(std::begin(voices), std::end(voices), [](VoiceData& a, VoiceData& b) { + return (a.voice.isIdle() && !b.voice.isIdle()) || a.channel > b.channel || a.serial < b.serial; + }); + + VoiceData& voiceData = voices[0]; + voiceData.channel = channel; + voiceData.serial = serial++; + voiceData.voice.reset(); + + for(VoiceData& vd : voices) { + std::cout << "channel=" << vd.channel << " serial=" << vd.serial << " idle=" << vd.voice.isIdle() << " note=" << vd.voice.note << std::endl; + } + std::cout << std::endl; + + return &voiceData.voice; } -void VoiceManager::free(Voice * voice) { - int index = voice - voices; - active.erase(index); - idle.insert(index); - std::cout << "free(): active=" << active.size() << " idle=" << idle.size() << std::endl; +Voice** VoiceManager::getChannelVoices(channel_t channel) { + static Voice* result[NUM_VOICES + 1]; + Voice** voice = result; + for(VoiceData& voiceData : voices) { + if(voiceData.channel == channel && !voiceData.voice.isIdle()) { + *voice++ = &voiceData.voice; + } + } + *voice = NULL; + return result; } \ No newline at end of file