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/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

@ -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<uint8_t, std::set<Voice*>> notes;
};
#endif

@ -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();

@ -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

@ -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);

@ -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<size_t> idle;
std::unordered_set<size_t> active;
struct VoiceData {
channel_t channel;
serial_t serial;
Voice voice;
};
unsigned int serial;
VoiceData voices[NUM_VOICES];
};
#endif

@ -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});
}
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;

@ -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;
}
}

@ -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;
}

@ -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();
}

@ -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;
}
Loading…
Cancel
Save