Files
sonobulus/src/synth/Synth.cpp
2026-06-24 19:32:46 -05:00

103 lines
2.4 KiB
C++

#include "Synth.hpp"
Synth::Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope, NoteQueue* queue) :
config_(config), logger_(logger), scope_(scope), noteQueue_(queue) {
voices_.fill(Voice(config_, logger_));
filter_.setSampleRate(44100.0f);
filter_.setParams(Filter::Type::BiquadLowpass, 4000.0f, 0.707f);
}
void Synth::handleNoteEvent(const NoteEvent& event) {
// Voice* v = findVoiceByNote(event.note);
// if(v != nullptr) v->noteOff();
// stop all voices that are currently playing this note
for(Voice& v : voices_) {
if(v.isActive() && v.note() == event.note) {
v.noteOff();
}
}
if(event.type == NoteEventType::NoteOn) {
Voice* v = findFreeVoice();
if(v != nullptr) v->noteOn(event.note, event.velocity);
}
}
void Synth::process(float* out, size_t nFrames) {
NoteEvent noteEvent;
while(noteQueue_->pop(noteEvent)) {
handleNoteEvent(noteEvent);
}
size_t lowestVoice = 0;
float lowestFreq = 100000.0f;
for(size_t i = 0; i < voices_.size(); i++) {
if(!voices_[i].isActive()) continue;
float currentFreq = voices_[i].frequency();
if(currentFreq < lowestFreq) {
lowestVoice = i;
lowestFreq = currentFreq;
}
}
scope_->spinlock(true);
float sampleOut = 0.0f;
bool triggered = false;
bool once = false;
for(size_t i = 0; i < nFrames; i++) {
float mix = 0.0f;
for(size_t j = 0; j < voices_.size(); j++) {
bool temp = false;
if(!voices_[j].isActive()) continue;
mix += voices_[j].process(temp);
if(j == lowestVoice) triggered = temp;
}
mix = tanh(mix); // prevent clipping
sampleOut = filter_.biquadProcess(mix);
out[2*i] = sampleOut;
out[2*i+1] = sampleOut;
if(scope_) scope_->push(sampleOut);
if(triggered && !once) {
scope_->setTrigger(i);
once = true;
}
}
scope_->spinlock(false);
}
Voice* Synth::findFreeVoice() {
for(Voice& v : voices_) {
if(!v.isActive()) {
return &v;
}
}
return nullptr;
}
Voice* Synth::findVoiceByNote(uint8_t note) {
for(Voice& v : voices_) {
if(v.isActive() && v.note() == note) {
return &v;
}
}
return nullptr;
}