polyphony checkpoint
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
#include "ConfigService.hpp"
|
#include "ConfigService.hpp"
|
||||||
#include "synth/AudioEngine.hpp"
|
#include "synth/AudioEngine.hpp"
|
||||||
#include "synth/KeyboardController.hpp"
|
#include "synth/KeyboardController.hpp"
|
||||||
|
//#include "synth/ScopeBuffer.hpp"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
@@ -17,15 +18,16 @@ int main(int argc, char* argv[]) {
|
|||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
|
|
||||||
// create app objects
|
// create app objects
|
||||||
ConfigService config = ConfigService("config/sonobulus.cfg");
|
ConfigService config = ConfigService("config/sonobulus.cfg");
|
||||||
LoggerService logger = LoggerService(&config, "Engine");
|
LoggerService logger = LoggerService(&config, "Engine");
|
||||||
NoteQueue queue = NoteQueue();
|
NoteQueue queue = NoteQueue();
|
||||||
|
//ScopeBuffer scope = ScopeBuffer();
|
||||||
KeyboardController keyboard(&config, &logger, &queue);
|
KeyboardController keyboard(&config, &logger, &queue);
|
||||||
|
Synth synth(&config, &logger, nullptr, &queue);
|
||||||
|
|
||||||
// audio synthesizer doohickey
|
// audio synthesizer doohickey
|
||||||
AudioEngine audioEngine = AudioEngine(&config, &logger, &queue);
|
AudioEngine audioEngine = AudioEngine(&config, &logger, &synth);
|
||||||
audioEngine.start();
|
audioEngine.start();
|
||||||
|
|
||||||
// attach backend gui components
|
// attach backend gui components
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <string>
|
#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) {
|
if(audioDevice_.getDeviceCount() < 1) {
|
||||||
std::cout << "No audio devices found" << std::endl;
|
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) {
|
int32_t AudioEngine::process(float* out, size_t nFrames) {
|
||||||
|
|
||||||
NoteEvent noteEvent;
|
synth_->process(out, nFrames);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
const float twoPi = 2.0f*std::numbers::pi_v<float>;
|
// const float twoPi = 2.0f*std::numbers::pi_v<float>;
|
||||||
float phaseIncrement = twoPi * freq_ / sampleRate_;
|
// 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
|
// // simulate a sine wave
|
||||||
phase_ += phaseIncrement;
|
// phase_ += phaseIncrement;
|
||||||
if(phase_ > twoPi) {
|
// if(phase_ > twoPi) {
|
||||||
phase_ -= twoPi;
|
// phase_ -= twoPi;
|
||||||
}
|
// }
|
||||||
float outSample = std::sin(phase_) / 4.0f * noteOn_;
|
// float outSample = std::sin(phase_) / 4.0f * noteOn_;
|
||||||
|
|
||||||
out[2*i] = outSample;
|
// out[2*i] = outSample;
|
||||||
out[2*i+1] = outSample;
|
// out[2*i+1] = outSample;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "LoggerService.hpp"
|
#include "LoggerService.hpp"
|
||||||
#include "ConfigService.hpp"
|
#include "ConfigService.hpp"
|
||||||
|
#include "Synth.hpp"
|
||||||
#include "NoteQueue.hpp"
|
#include "NoteQueue.hpp"
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ class AudioEngine {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
AudioEngine(ConfigService* config, LoggerService* logger, NoteQueue* noteQueue);
|
AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth);
|
||||||
~AudioEngine();
|
~AudioEngine();
|
||||||
|
|
||||||
bool start();
|
bool start();
|
||||||
@@ -39,12 +40,8 @@ private:
|
|||||||
uint32_t bufferFrames_ = 512;
|
uint32_t bufferFrames_ = 512;
|
||||||
uint32_t channels_ = 2;
|
uint32_t channels_ = 2;
|
||||||
|
|
||||||
float phase_ = 0.0f;
|
|
||||||
float freq_ = 0.0f;
|
|
||||||
bool noteOn_ = false;
|
|
||||||
|
|
||||||
LoggerService* logger_;
|
LoggerService* logger_;
|
||||||
ConfigService* config_;
|
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
|
#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 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
|
// an instrumeent is owned by a voice
|
||||||
class Instrument {
|
class Instrument {
|
||||||
@@ -8,8 +11,26 @@ class Instrument {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
Instrument() = default;
|
Instrument() = default;
|
||||||
|
Instrument(ConfigService* config, LoggerService* logger);
|
||||||
~Instrument() = default;
|
~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 <cstdint>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#define SYNTH_NOTE_QUEUE_SIZE 128
|
|
||||||
|
|
||||||
enum NoteEventType {
|
enum NoteEventType {
|
||||||
NoteOn = 0,
|
NoteOn = 0,
|
||||||
NoteOff
|
NoteOff
|
||||||
@@ -32,6 +30,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
static constexpr size_t SYNTH_NOTE_QUEUE_SIZE = 128;
|
||||||
|
|
||||||
std::array<NoteEvent, SYNTH_NOTE_QUEUE_SIZE> buffer_;
|
std::array<NoteEvent, SYNTH_NOTE_QUEUE_SIZE> buffer_;
|
||||||
std::atomic<size_t> head_{ 0 };
|
std::atomic<size_t> head_{ 0 };
|
||||||
std::atomic<size_t> tail_{ 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:
|
public:
|
||||||
|
|
||||||
Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope);
|
Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope, NoteQueue* queue);
|
||||||
~Synth();
|
~Synth() = default;
|
||||||
|
|
||||||
void process(float* out, size_t nFrames, uint32_t sampleRate);
|
void process(float* out, size_t nFrames);
|
||||||
void handleNoteEvent(const NoteEvent& event);
|
void handleNoteEvent(const NoteEvent& event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -30,6 +30,9 @@ private:
|
|||||||
static constexpr size_t MAX_VOICES = 32;
|
static constexpr size_t MAX_VOICES = 32;
|
||||||
std::array<Voice, MAX_VOICES> voices_;
|
std::array<Voice, MAX_VOICES> voices_;
|
||||||
|
|
||||||
|
ConfigService* config_;
|
||||||
|
LoggerService* logger_;
|
||||||
ScopeBuffer* scope_ = nullptr;
|
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:
|
public:
|
||||||
|
|
||||||
Voice() = default;
|
Voice() = default;
|
||||||
|
Voice(ConfigService* config, LoggerService* logger);
|
||||||
~Voice() = default;
|
~Voice() = default;
|
||||||
|
|
||||||
void noteOn(int8_t midiNote, float velocity);
|
void noteOn(uint8_t midiNote, float velocity);
|
||||||
void noteOff();
|
void noteOff();
|
||||||
bool isActive();
|
bool isActive();
|
||||||
|
|
||||||
@@ -25,12 +26,16 @@ private:
|
|||||||
|
|
||||||
float sampleRate_ = 44100.0f;
|
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;
|
uint8_t note_ = 0;
|
||||||
float velocity_ = 1.0f;
|
float velocity_ = 1.0f;
|
||||||
bool active_ = false;
|
bool active_ = false;
|
||||||
|
|
||||||
Instrument* instrument;
|
ConfigService* config_;
|
||||||
|
LoggerService* logger_;
|
||||||
|
Instrument instrument_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user