polyphony checkpoint
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
@@ -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_);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user