Better polyphony handling

main
Thor 1 year ago
parent 9f9618c465
commit 055e447ca0
  1. 2
      CMakeLists.txt
  2. 16
      include/synth/channel.h
  3. 1
      include/synth/dsp/adsr.h
  4. 13
      include/synth/synth.h
  5. 16
      include/synth/voice.h
  6. 21
      include/synth/voicemanager.h
  7. 44
      src/synth/channel.cpp
  8. 11
      src/synth/dsp/adsr.cpp
  9. 6
      src/synth/synth.cpp
  10. 10
      src/synth/voice.cpp
  11. 45
      src/synth/voicemanager.cpp

@ -22,7 +22,7 @@ add_executable(main
src/synthframe.cpp src/synthframe.cpp
src/synth/synth.cpp src/synth/synth.cpp
src/synth/voicemanager.cpp src/synth/voicemanager.cpp
src/synth/part.cpp src/synth/channel.cpp
src/synth/preset.cpp src/synth/preset.cpp
src/synth/voice.cpp src/synth/voice.cpp
src/synth/dsp/oscillator.cpp src/synth/dsp/oscillator.cpp

@ -9,13 +9,13 @@
#include "voice.h" #include "voice.h"
#include "preset.h" #include "preset.h"
class Part { class Channel {
public: public:
channel_t number;
VoiceManager* voiceManager; VoiceManager* voiceManager;
Voice::Settings settings; 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); void loadPreset(const Preset* preset);
@ -25,11 +25,9 @@ public:
frame tick() { frame tick() {
frame out{0}; frame out{0};
for(auto& kv : notes) { Voice** voices = voiceManager->getChannelVoices(number);
int note = kv.first; for(Voice** voice = voices; *voice != NULL; ++voice) {
for(auto& voice : kv.second) { out += (*voice)->tick(pitchBend + settings.lfoPitchMod * sin(lfoPhase));
out += voice->tick(note + pitchBend + settings.lfoPitchMod * sin(lfoPhase));
}
} }
out *= volume; out *= volume;
@ -47,8 +45,6 @@ private:
float modulation; float modulation;
float lfoPhase; float lfoPhase;
std::unordered_map<uint8_t, std::set<Voice*>> notes;
}; };
#endif #endif

@ -17,6 +17,7 @@ public:
Envelope const * env; Envelope const * env;
ADSR() : state(IDLE), t(0), last(0) {}; ADSR() : state(IDLE), t(0), last(0) {};
void reset();
void assign(Envelope const * env); void assign(Envelope const * env);
void noteOn(); void noteOn();
void noteOff(); void noteOff();

@ -2,14 +2,15 @@
#define __SYNTH_H__ #define __SYNTH_H__
#include "voicemanager.h" #include "voicemanager.h"
#include "part.h" #include "channel.h"
class Synth { class Synth {
public: public:
Synth() { Synth() {
for(int i = 0; i < 16; ++i) { for(int i = 0; i < 16; ++i) {
parts[i].voiceManager = &voiceManager; channels[i].number = i;
parts[i].loadPreset(&DEFAULT_PRESET); channels[i].voiceManager = &voiceManager;
channels[i].loadPreset(&DEFAULT_PRESET);
} }
} }
@ -19,8 +20,8 @@ public:
frame tick() { frame tick() {
frame out{}; frame out{};
for(auto& part : parts) { for(auto& channel : channels) {
out += part.tick(); out += channel.tick();
} }
out *= 0.100; out *= 0.100;
return out; return out;
@ -28,7 +29,7 @@ public:
private: private:
VoiceManager voiceManager{}; VoiceManager voiceManager{};
Part parts[16]; Channel channels[16];
}; };
#endif #endif

@ -39,18 +39,18 @@ private:
Filter filter; Filter filter;
public: public:
bool keyed; int note;
Voice(): keyed(false) {} void reset();
void assign(Settings const * settings); void assign(Settings const * settings);
void noteOn(); void noteOn(int note);
void noteOff(); void noteOff();
bool isIdle() { return adsrAmp.isIdle(); } inline bool isIdle() { return adsrAmp.isIdle(); }
inline float tick(float note) { inline float tick(float pitchMod) {
const float keyTrackMul = 1 + settings->keyTrack * noteToFreq(note) / 440.0; const float keyTrackMul = 1 + settings->keyTrack * noteToFreq(note + pitchMod) / 440.0;
const float osc1PhaseStep = noteToFreq(note - settings->oscDetune) * PIx2 / SAMPLE_RATE; const float osc1PhaseStep = noteToFreq(note + pitchMod - settings->oscDetune) * PIx2 / SAMPLE_RATE;
const float osc2PhaseStep = noteToFreq(note + settings->oscDetune + settings->osc2Pitch) * PIx2 / SAMPLE_RATE; const float osc2PhaseStep = noteToFreq(note + pitchMod + settings->oscDetune + settings->osc2Pitch) * PIx2 / SAMPLE_RATE;
float out = 0; float out = 0;
out += (1 - settings->oscMix) * osc1.tick(osc1PhaseStep); out += (1 - settings->oscMix) * osc1.tick(osc1PhaseStep);
out += settings->oscMix * osc2.tick(osc2PhaseStep); out += settings->oscMix * osc2.tick(osc2PhaseStep);

@ -5,17 +5,26 @@
#include "synth/voice.h" #include "synth/voice.h"
#define NUM_VOICES 16
typedef unsigned int channel_t;
typedef unsigned int serial_t;
class VoiceManager { class VoiceManager {
public: public:
VoiceManager(); VoiceManager();
Voice * allocate(); Voice* get(channel_t channel);
void free(Voice * voice); Voice** getChannelVoices(channel_t channel);
private: private:
Voice voices[64]; struct VoiceData {
std::unordered_set<size_t> idle; channel_t channel;
std::unordered_set<size_t> active; serial_t serial;
Voice voice;
};
unsigned int serial;
VoiceData voices[NUM_VOICES];
}; };
#endif #endif

@ -3,7 +3,7 @@
#include "synth/globals.h" #include "synth/globals.h"
#include "synth/cc.h" #include "synth/cc.h"
#include "synth/part.h" #include "synth/channel.h"
float timeToStep(float t) { float timeToStep(float t) {
return (1.0 / SAMPLE_RATE) / t; return (1.0 / SAMPLE_RATE) / t;
@ -25,7 +25,7 @@ float ccToLFOPitchMod(int x) {
return 0.01 * exp(x * 7.090076835776092 / 127.0); 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.osc1Mode = Oscillator::Mode(preset->osc1Mode);
settings.osc2Mode = Oscillator::Mode(preset->osc2Mode); settings.osc2Mode = Oscillator::Mode(preset->osc2Mode);
settings.oscMix = preset->oscMix / 127.0; settings.oscMix = preset->oscMix / 127.0;
@ -54,42 +54,22 @@ void Part::loadPreset(const Preset * preset) {
settings.lfoFltMod = preset->lfoFltMod / 127.0; settings.lfoFltMod = preset->lfoFltMod / 127.0;
} }
void Part::noteOn(int note, int velocity) { void Channel::noteOn(int note, int velocity) {
// Garbage collection - free up idle voices Voice* const voice = voiceManager->get(number);
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();
voice->assign(&settings); voice->assign(&settings);
voice->noteOn(); voice->noteOn(note);
if(notes.count(note)) {
notes.at(note).insert(voice);
} else {
notes.emplace(note, std::set<Voice *>{voice});
}
} }
void Part::noteOff(int note) { void Channel::noteOff(int note) {
if(notes.count(note)) { Voice** voices = voiceManager->getChannelVoices(number);
auto& voices = notes.at(note); for(Voice** voice = voices; *voice != NULL; ++voice) {
for (auto i = voices.rbegin(); i != voices.rend(); ++i ) { if((*voice)->note == note) {
if((*i)->keyed) { (*voice)->noteOff();
(*i)->noteOff(); }
break;
}
}
} }
} }
void Part::control(int code, int value) { void Channel::control(int code, int value) {
switch(code) { switch(code) {
case CC_VOLUME: // Volume (Standard MIDI) case CC_VOLUME: // Volume (Standard MIDI)
volume = value / 127.0; volume = value / 127.0;

@ -1,5 +1,10 @@
#include "synth/dsp/adsr.h" #include "synth/dsp/adsr.h"
void ADSR::reset() {
state = IDLE;
t = 0;
}
void ADSR::assign(Envelope const * env) { void ADSR::assign(Envelope const * env) {
this->env = env; this->env = env;
} }
@ -10,6 +15,8 @@ void ADSR::noteOn() {
} }
void ADSR::noteOff() { void ADSR::noteOff() {
state = RELEASE; if(state != RELEASE) {
t = 1; state = RELEASE;
t = 1;
}
} }

@ -3,16 +3,16 @@
#include "synth/synth.h" #include "synth/synth.h"
void Synth::noteOn(int ch, int note, int vel) { 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; std::cout << "Note On: ch=" << ch << " note=" << note << " vel=" << vel << std::endl;
} }
void Synth::noteOff(int ch, int note) { 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; std::cout << "Note Off: ch=" << ch << " note=" << note << std::endl;
} }
void Synth::control(int ch, int cc, int val) { 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; std::cout << "Controller: ch=" << ch << " cc=" << cc << " val=" << val << std::endl;
} }

@ -1,5 +1,10 @@
#include "synth/voice.h" #include "synth/voice.h"
void Voice::reset() {
adsrAmp.reset();
adsrMod.reset();
}
void Voice::assign(Settings const * settings) { void Voice::assign(Settings const * settings) {
this->settings = settings; this->settings = settings;
adsrAmp.assign(&settings->ampEnv); adsrAmp.assign(&settings->ampEnv);
@ -9,14 +14,13 @@ void Voice::assign(Settings const * settings) {
filter.assign(&settings->filter); filter.assign(&settings->filter);
} }
void Voice::noteOn() { void Voice::noteOn(int note) {
keyed = true; this->note = note;
adsrAmp.noteOn(); adsrAmp.noteOn();
adsrMod.noteOn(); adsrMod.noteOn();
} }
void Voice::noteOff() { void Voice::noteOff() {
keyed = false;
adsrAmp.noteOff(); adsrAmp.noteOff();
adsrMod.noteOff(); adsrMod.noteOff();
} }

@ -2,23 +2,40 @@
#include "synth/voicemanager.h" #include "synth/voicemanager.h"
VoiceManager::VoiceManager() { VoiceManager::VoiceManager() : serial(0) {
for(int i = 0; i < 64; ++i) { for(VoiceData& vd : voices) {
idle.insert(i); vd.channel = 0;
vd.serial = 0;
} }
} }
Voice * VoiceManager::allocate() { Voice * VoiceManager::get(channel_t channel) {
int index = *idle.begin(); // Sort idle voices first, then by highest channel and lowest serial
idle.erase(index); std::sort(std::begin(voices), std::end(voices), [](VoiceData& a, VoiceData& b) {
active.insert(index); return (a.voice.isIdle() && !b.voice.isIdle()) || a.channel > b.channel || a.serial < b.serial;
std::cout << "allocate(): active=" << active.size() << " idle=" << idle.size() << std::endl; });
return &voices[index];
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) { Voice** VoiceManager::getChannelVoices(channel_t channel) {
int index = voice - voices; static Voice* result[NUM_VOICES + 1];
active.erase(index); Voice** voice = result;
idle.insert(index); for(VoiceData& voiceData : voices) {
std::cout << "free(): active=" << active.size() << " idle=" << idle.size() << std::endl; if(voiceData.channel == channel && !voiceData.voice.isIdle()) {
*voice++ = &voiceData.voice;
}
}
*voice = NULL;
return result;
} }
Loading…
Cancel
Save