diff --git a/src/main.cpp b/src/main.cpp index 1006c2a..dc0c972 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 diff --git a/src/synth/AudioEngine.cpp b/src/synth/AudioEngine.cpp index c2000a3..9e55e71 100644 --- a/src/synth/AudioEngine.cpp +++ b/src/synth/AudioEngine.cpp @@ -6,7 +6,7 @@ #include #include -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(noteEvent.note) - 69.0f) / 12.0f); - } + synth_->process(out, nFrames); - const float twoPi = 2.0f*std::numbers::pi_v; - float phaseIncrement = twoPi * freq_ / sampleRate_; + // const float twoPi = 2.0f*std::numbers::pi_v; + // 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; diff --git a/src/synth/AudioEngine.hpp b/src/synth/AudioEngine.hpp index 0d8ce13..d09a318 100644 --- a/src/synth/AudioEngine.hpp +++ b/src/synth/AudioEngine.hpp @@ -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_; }; diff --git a/src/synth/Instrument.cpp b/src/synth/Instrument.cpp index e69de29..cd51a0c 100644 --- a/src/synth/Instrument.cpp +++ b/src/synth/Instrument.cpp @@ -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_); + +} diff --git a/src/synth/Instrument.hpp b/src/synth/Instrument.hpp index ed86b53..fbb9118 100644 --- a/src/synth/Instrument.hpp +++ b/src/synth/Instrument.hpp @@ -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(); -}; \ No newline at end of file + 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; + +}; diff --git a/src/synth/NoteQueue.hpp b/src/synth/NoteQueue.hpp index 6ea894e..519ea97 100644 --- a/src/synth/NoteQueue.hpp +++ b/src/synth/NoteQueue.hpp @@ -7,8 +7,6 @@ #include #include -#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 buffer_; std::atomic head_{ 0 }; std::atomic tail_{ 0 }; diff --git a/src/synth/Synth.cpp b/src/synth/Synth.cpp index e69de29..a5790df 100644 --- a/src/synth/Synth.cpp +++ b/src/synth/Synth.cpp @@ -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; +} \ No newline at end of file diff --git a/src/synth/Synth.hpp b/src/synth/Synth.hpp index 686be07..80f48cd 100644 --- a/src/synth/Synth.hpp +++ b/src/synth/Synth.hpp @@ -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 voices_; + ConfigService* config_; + LoggerService* logger_; ScopeBuffer* scope_ = nullptr; + NoteQueue* noteQueue_; -} \ No newline at end of file +}; diff --git a/src/synth/Voice.cpp b/src/synth/Voice.cpp index e69de29..6f96a5c 100644 --- a/src/synth/Voice.cpp +++ b/src/synth/Voice.cpp @@ -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); + +} diff --git a/src/synth/Voice.hpp b/src/synth/Voice.hpp index 994e814..9974965 100644 --- a/src/synth/Voice.hpp +++ b/src/synth/Voice.hpp @@ -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(note - 69) / static_cast(12)); + } uint8_t note_ = 0; float velocity_ = 1.0f; bool active_ = false; - Instrument* instrument; + ConfigService* config_; + LoggerService* logger_; + Instrument instrument_; };