polyphony checkpoint

This commit is contained in:
2026-06-12 23:13:18 -05:00
parent 347f585630
commit 9f79f11a19
10 changed files with 218 additions and 39 deletions

View File

@@ -10,6 +10,7 @@
#include "ConfigService.hpp"
#include "synth/AudioEngine.hpp"
#include "synth/KeyboardController.hpp"
//#include "synth/ScopeBuffer.hpp"
int main(int argc, char* argv[]) {
@@ -17,15 +18,16 @@ int main(int argc, char* argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// create app objects
ConfigService config = ConfigService("config/sonobulus.cfg");
LoggerService logger = LoggerService(&config, "Engine");
NoteQueue queue = NoteQueue();
//ScopeBuffer scope = ScopeBuffer();
KeyboardController keyboard(&config, &logger, &queue);
Synth synth(&config, &logger, nullptr, &queue);
// audio synthesizer doohickey
AudioEngine audioEngine = AudioEngine(&config, &logger, &queue);
AudioEngine audioEngine = AudioEngine(&config, &logger, &synth);
audioEngine.start();
// attach backend gui components

View File

@@ -6,7 +6,7 @@
#include <numbers>
#include <string>
AudioEngine::AudioEngine(ConfigService* config, LoggerService* logger, NoteQueue* noteQueue) : config_(config), logger_(logger), noteQueue_(noteQueue) {
AudioEngine::AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth) : config_(config), logger_(logger), synth_(synth) {
if(audioDevice_.getDeviceCount() < 1) {
std::cout << "No audio devices found" << std::endl;
@@ -70,29 +70,23 @@ int32_t AudioEngine::audioCallback(void* outputBuffer, void* inputBuffer, uint32
int32_t AudioEngine::process(float* out, size_t nFrames) {
NoteEvent noteEvent;
while(noteQueue_->pop(noteEvent)) {
//std::string msg = "[NoteEvent] Type: " + std::to_string(noteEvent.type) +" Velocity: " + std::to_string(noteEvent.velocity) + " Note:" + std::to_string(noteEvent.note);
//logger_->log("Audio", LogFlag::Debug, msg);
noteOn_ = (noteEvent.type == NoteEventType::NoteOn) ? true : false;
freq_ = 440.0f * pow(2.0f, (static_cast<float>(noteEvent.note) - 69.0f) / 12.0f);
}
synth_->process(out, nFrames);
const float twoPi = 2.0f*std::numbers::pi_v<float>;
float phaseIncrement = twoPi * freq_ / sampleRate_;
// const float twoPi = 2.0f*std::numbers::pi_v<float>;
// float phaseIncrement = twoPi * freq_ / sampleRate_;
for(size_t i = 0; i < nFrames; i++) {
// for(size_t i = 0; i < nFrames; i++) {
// simulate a sine wave
phase_ += phaseIncrement;
if(phase_ > twoPi) {
phase_ -= twoPi;
}
float outSample = std::sin(phase_) / 4.0f * noteOn_;
// // simulate a sine wave
// phase_ += phaseIncrement;
// if(phase_ > twoPi) {
// phase_ -= twoPi;
// }
// float outSample = std::sin(phase_) / 4.0f * noteOn_;
out[2*i] = outSample;
out[2*i+1] = outSample;
}
// out[2*i] = outSample;
// out[2*i+1] = outSample;
// }
return 0;

View File

@@ -7,6 +7,7 @@
#include "LoggerService.hpp"
#include "ConfigService.hpp"
#include "Synth.hpp"
#include "NoteQueue.hpp"
@@ -20,7 +21,7 @@ class AudioEngine {
public:
AudioEngine(ConfigService* config, LoggerService* logger, NoteQueue* noteQueue);
AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth);
~AudioEngine();
bool start();
@@ -39,12 +40,8 @@ private:
uint32_t bufferFrames_ = 512;
uint32_t channels_ = 2;
float phase_ = 0.0f;
float freq_ = 0.0f;
bool noteOn_ = false;
LoggerService* logger_;
ConfigService* config_;
NoteQueue* noteQueue_;
Synth* synth_;
};

View File

@@ -0,0 +1,36 @@
#include "Instrument.hpp"
#include "string"
Instrument::Instrument(ConfigService* config, LoggerService* logger) :
config_(config), logger_(logger) {
}
void Instrument::noteOn(float frequency, float velocity) {
// std::string msg = "NoteOn Frequency = " + std::to_string(frequency);
// if(logger_ != nullptr) logger_->log("Instrument", LogFlag::Debug, msg);
phaseIncrement_ = 2.0f * pi * frequency / sampleRate_;
active_ = true;
}
void Instrument::noteOff() {
active_ = false;
}
bool Instrument::isActive() {
return active_;
}
float Instrument::process(bool& scopeTrigger) {
phase_ += phaseIncrement_;
if(phase_ > 2.0f * pi) phase_ -= 2.0f * pi;
return sin(phase_);
}

View File

@@ -1,6 +1,9 @@
#pragma once
#include "ConfigService.hpp"
#include "LoggerService.hpp"
// an instrument is our state space model. it calculates discrete samples as a function of time, the current states, and the inputs
// an instrumeent is owned by a voice
class Instrument {
@@ -8,8 +11,26 @@ class Instrument {
public:
Instrument() = default;
Instrument(ConfigService* config, LoggerService* logger);
~Instrument() = default;
private:
void noteOn(float frequency, float velocity);
void noteOff();
};
bool isActive();
float process(bool& scopeTrigger);
private:
float sampleRate_ = 44100.0f;
bool active_ = false;
ConfigService* config_;
LoggerService* logger_;
static constexpr float pi = 3.14159265358979323846f;
float phase_ = 0.0f;
float phaseIncrement_ = 0.0f;
};

View File

@@ -7,8 +7,6 @@
#include <cstdint>
#include <chrono>
#define SYNTH_NOTE_QUEUE_SIZE 128
enum NoteEventType {
NoteOn = 0,
NoteOff
@@ -32,6 +30,8 @@ public:
private:
static constexpr size_t SYNTH_NOTE_QUEUE_SIZE = 128;
std::array<NoteEvent, SYNTH_NOTE_QUEUE_SIZE> buffer_;
std::atomic<size_t> head_{ 0 };
std::atomic<size_t> tail_{ 0 };

View File

@@ -0,0 +1,87 @@
#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_));
}
void Synth::handleNoteEvent(const NoteEvent& event) {
Voice* v = findVoiceByNote(event.note);
if(v != nullptr) v->noteOff();
if(event.type == NoteEventType::NoteOn) {
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;
// }
// }
float sampleOut = 0.0f;
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/4.0f); // prevent clipping
sampleOut = mix;
out[2*i] = sampleOut;
out[2*i+1] = sampleOut;
// if(scope_) scope_->push(sampleOut);
// if(triggered && !once) {
// scope_->setTrigger(i);
// once = true;
// }
}
}
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;
}

View File

@@ -13,10 +13,10 @@ class Synth {
public:
Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope);
~Synth();
Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope, NoteQueue* queue);
~Synth() = default;
void process(float* out, size_t nFrames, uint32_t sampleRate);
void process(float* out, size_t nFrames);
void handleNoteEvent(const NoteEvent& event);
private:
@@ -30,6 +30,9 @@ private:
static constexpr size_t MAX_VOICES = 32;
std::array<Voice, MAX_VOICES> voices_;
ConfigService* config_;
LoggerService* logger_;
ScopeBuffer* scope_ = nullptr;
NoteQueue* noteQueue_;
}
};

View File

@@ -0,0 +1,34 @@
#include "Voice.hpp"
Voice::Voice(ConfigService* config, LoggerService* logger) :
config_(config), logger_(logger) {
instrument_ = Instrument(config_, logger_);
}
void Voice::noteOn(uint8_t midiNote, float velocity) {
note_ = midiNote;
instrument_.noteOn(noteToFrequency(midiNote), velocity);
}
void Voice::noteOff() {
instrument_.noteOff();
}
bool Voice::isActive() {
return instrument_.isActive();
}
float Voice::process(bool& scopeTrigger) {
return instrument_.process(scopeTrigger);
}

View File

@@ -12,9 +12,10 @@ class Voice {
public:
Voice() = default;
Voice(ConfigService* config, LoggerService* logger);
~Voice() = default;
void noteOn(int8_t midiNote, float velocity);
void noteOn(uint8_t midiNote, float velocity);
void noteOff();
bool isActive();
@@ -25,12 +26,16 @@ private:
float sampleRate_ = 44100.0f;
inline float noteToFrequency(uint8_t note);
inline float noteToFrequency(uint8_t note) {
return 440.0f * pow(2.0f, static_cast<float>(note - 69) / static_cast<float>(12));
}
uint8_t note_ = 0;
float velocity_ = 1.0f;
bool active_ = false;
Instrument* instrument;
ConfigService* config_;
LoggerService* logger_;
Instrument instrument_;
};