integrate audio config into engine and synth

This commit is contained in:
2026-07-05 00:02:53 -05:00
parent 9365e12bb4
commit 295a869839
16 changed files with 71 additions and 41 deletions

View File

@@ -57,8 +57,8 @@ add_library(sonobulus_core STATIC
src/synth/Synth.cpp src/synth/Synth.cpp
src/synth/Voice.cpp src/synth/Voice.cpp
src/synth/Filter.cpp src/synth/Filter.cpp
src/synth/Instruments/Instrument.cpp src/synth/instruments/Instrument.cpp
src/synth/Instruments/PianoString.cpp src/synth/instruments/PianoString.cpp
) )
if (WIN32) if (WIN32)

View File

@@ -29,7 +29,9 @@ AudioEngine = (
BufferSize = 512; BufferSize = 512;
PitchStandard = 440.0; PitchStandard = 440.0;
MidiHome = 69; MidiHome = 69;
NotesPerOctave = 12; NotesPerOctave = 12.0;
OctaveConstant = 2.0;
MaxVoices = 32;
} }
); );

View File

@@ -14,7 +14,7 @@ namespace fs = std::filesystem;
LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) { LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) {
if(!(config->getConfig<LoggerParams, LoggerConfig>("Logger", loggerId, &configuration_))) { if(!(config->getConfig<LoggerParams, LoggerConfig>("Logger", loggerId, &configuration_))) {
std::cout << "Failed to get logger configuration fom config service" << std::endl; std::cout << "Failed to get logger configuration from config service" << std::endl;
return; return;
} }

View File

@@ -1,18 +1,19 @@
#pragma once #pragma once
#include "IConfig.hpp" #include "IConfig.hpp"
struct AudioParams : IParams { struct AudioParams : IParams {
std::string id;
uint32_t sampleRate; uint32_t sampleRate;
uint32_t channels; uint32_t channels;
uint32_t stereoMode; uint32_t stereoMode;
uint32_t bufferSize; uint32_t bufferSize;
float pitchStandard; float pitchStandard;
int32_t midiHome; int32_t midiHome;
int32_t notesPerOctave; float notesPerOctave;
float octaveConstant;
size_t maxVoices;
}; };
@@ -31,9 +32,12 @@ public:
setting.lookupValue("SampleRate", params_->sampleRate); setting.lookupValue("SampleRate", params_->sampleRate);
setting.lookupValue("StereoMode", params_->stereoMode); setting.lookupValue("StereoMode", params_->stereoMode);
setting.lookupValue("BufferSize", params_->bufferSize); setting.lookupValue("BufferSize", params_->bufferSize);
setting.lookupValue("Channels", params_->channels);
setting.lookupValue("PitchStandard", params_->pitchStandard); setting.lookupValue("PitchStandard", params_->pitchStandard);
setting.lookupValue("MidiHome", params_->midiHome); setting.lookupValue("MidiHome", params_->midiHome);
setting.lookupValue("NotesPerOctave", params_->notesPerOctave); setting.lookupValue("NotesPerOctave", params_->notesPerOctave);
setting.lookupValue("OctaveConstant", params_->octaveConstant);
setting.lookupValue("MaxVoices", params_->maxVoices);
return true; return true;
} }

View File

@@ -8,6 +8,12 @@
AudioEngine::AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth) : config_(config), logger_(logger), synth_(synth) { AudioEngine::AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth) : config_(config), logger_(logger), synth_(synth) {
// load audio settings from config service
if(!(config->getConfig<AudioParams, AudioConfig>("AudioEngine", "Main", &configuration_))) {
logger_->log("Audio", LogFlag::Error, "Failed to get logger configuration from config service");
return;
}
if(audioDevice_.getDeviceCount() < 1) { if(audioDevice_.getDeviceCount() < 1) {
logger_->log("Audio", LogFlag::Error, "No audio devices found."); logger_->log("Audio", LogFlag::Error, "No audio devices found.");
} }
@@ -22,17 +28,26 @@ bool AudioEngine::start() {
// initialize the audio engine // initialize the audio engine
RtAudio::StreamParameters params; RtAudio::StreamParameters params;
params.deviceId = audioDevice_.getDefaultOutputDevice(); params.deviceId = audioDevice_.getDefaultOutputDevice();
params.nChannels = channels_; // we're doing two duplicate channels for pseudo-mono params.nChannels = configuration_.channels; // we're doing two duplicate channels for pseudo-mono
params.firstChannel = 0; params.firstChannel = 0;
RtAudio::StreamOptions options; RtAudio::StreamOptions options;
options.flags = RTAUDIO_MINIMIZE_LATENCY; options.flags = RTAUDIO_MINIMIZE_LATENCY;
RtAudioErrorType status = audioDevice_.openStream(&params, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options); RtAudioErrorType status = audioDevice_.openStream(
&params,
nullptr,
RTAUDIO_FLOAT32,
configuration_.sampleRate,
&configuration_.bufferSize,
&AudioEngine::audioCallback,
this,
&options
);
if(status != RTAUDIO_NO_ERROR) { if(status != RTAUDIO_NO_ERROR) {
logger_->log("Audio", LogFlag::Error, "Error opening RtAudio stream."); logger_->log("Audio", LogFlag::Error, "Error opening RtAudio stream.");
return false; return false;
} }
status = audioDevice_.startStream(); status = audioDevice_.startStream();
if(status != RTAUDIO_NO_ERROR) { if(status != RTAUDIO_NO_ERROR) {
@@ -41,7 +56,7 @@ bool AudioEngine::start() {
} }
// sanity check // sanity check
std::string msg = "sample rate: " + std::to_string(sampleRate_) + ", buffer frames: " + std::to_string(bufferFrames_); std::string msg = "sample rate: " + std::to_string(configuration_.sampleRate) + ", buffer frames: " + std::to_string(configuration_.bufferSize);
logger_->log("AudioEngine", LogFlag::Info, msg); logger_->log("AudioEngine", LogFlag::Info, msg);
return true; return true;

View File

@@ -7,6 +7,7 @@
#include "LoggerService.hpp" #include "LoggerService.hpp"
#include "config/ConfigService.hpp" #include "config/ConfigService.hpp"
#include "config/AudioConfig.hpp"
#include "Synth.hpp" #include "Synth.hpp"
#include "NoteQueue.hpp" #include "NoteQueue.hpp"
@@ -35,13 +36,10 @@ private:
RtAudio audioDevice_ { AUDIO_API }; RtAudio audioDevice_ { AUDIO_API };
// TODO: make these configurable
uint32_t sampleRate_ = 44100;
uint32_t bufferFrames_ = 512;
uint32_t channels_ = 2;
LoggerService* logger_; LoggerService* logger_;
ConfigService* config_; ConfigService* config_;
AudioParams configuration_;
Synth* synth_; Synth* synth_;
}; };

View File

@@ -3,8 +3,8 @@
#include "string" #include "string"
Instrument::Instrument(ConfigService* config, LoggerService* logger) : Instrument::Instrument(ConfigService* config, LoggerService* logger, float sampleRate) :
config_(config), logger_(logger) { config_(config), logger_(logger), sampleRate_(sampleRate) {
} }

View File

@@ -5,13 +5,13 @@
#include "LoggerService.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 instrument is owned by a voice
class Instrument { class Instrument {
public: public:
Instrument() = default; Instrument() = default;
Instrument(ConfigService* config, LoggerService* logger); Instrument(ConfigService* config, LoggerService* logger, float sampleRate);
~Instrument() = default; ~Instrument() = default;
virtual void noteOn(float frequency, float velocity); virtual void noteOn(float frequency, float velocity);
@@ -23,7 +23,7 @@ public:
protected: protected:
float sampleRate_ = 44100.0f; float sampleRate_ = 1.0f;
bool active_ = false; bool active_ = false;
ConfigService* config_; ConfigService* config_;

View File

@@ -1,7 +1,7 @@
#include "PianoString.hpp" #include "PianoString.hpp"
PianoString::PianoString(ConfigService* config, LoggerService* logger) : Instrument(config, logger) { PianoString::PianoString(ConfigService* config, LoggerService* logger, float sampleRate) : Instrument(config, logger, sampleRate) {
stringY_current_.resize(segmentCount_ + 1); stringY_current_.resize(segmentCount_ + 1);
stringY_previous_.resize(segmentCount_ + 1); stringY_previous_.resize(segmentCount_ + 1);

View File

@@ -3,14 +3,14 @@
#include <cmath> #include <cmath>
#include "synth/Instruments/Instrument.hpp" #include "synth/instruments/Instrument.hpp"
class PianoString : public Instrument { class PianoString : public Instrument {
public: public:
PianoString() = default; PianoString() = default;
PianoString(ConfigService* config, LoggerService* logger); PianoString(ConfigService* config, LoggerService* logger, float sampleRate);
~PianoString() = default; ~PianoString() = default;
void noteOn(float frequency, float velocity) override; void noteOn(float frequency, float velocity) override;

View File

@@ -13,7 +13,7 @@ KeyboardController::KeyboardController(ConfigService* config, LoggerService* log
// load keymap from config service // load keymap from config service
if(!(config->getConfig<KeymapParams, KeymapConfig>("KeyboardController", "Main", &configuration_))) { if(!(config->getConfig<KeymapParams, KeymapConfig>("KeyboardController", "Main", &configuration_))) {
logger_->log("Keyboard", LogFlag::Error, "Failed to get logger configuration fom config service"); logger_->log("Keyboard", LogFlag::Error, "Failed to get logger configuration from config service");
return; return;
} }

View File

@@ -4,7 +4,7 @@
#include <iostream> #include <iostream>
ScopeBuffer::ScopeBuffer(QObject *parent) : QObject(parent) { ScopeBuffer::ScopeBuffer(QObject *parent) : QObject(parent) {
// timer is set in qml
} }
ScopeBuffer::ScopeBuffer(ConfigService* config, LoggerService* logger, size_t size) : ScopeBuffer::ScopeBuffer(ConfigService* config, LoggerService* logger, size_t size) :
@@ -45,7 +45,7 @@ void Scope::paint(QPainter *painter) {
if(scopeBuffer_ != nullptr) scopeBuffer_->read(buffer_); if(scopeBuffer_ != nullptr) scopeBuffer_->read(buffer_);
// TODO: scale to max amplitude ? // TODO: scale to max amplitude ? probably make it configurable if you want to make scope options
float maxAmp = 1.0f; float maxAmp = 1.0f;
/* /*
for(float s : buffer_) { for(float s : buffer_) {

View File

@@ -4,9 +4,17 @@
Synth::Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope, NoteQueue* queue) : Synth::Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope, NoteQueue* queue) :
config_(config), logger_(logger), scope_(scope), noteQueue_(queue) { config_(config), logger_(logger), scope_(scope), noteQueue_(queue) {
voices_.fill(Voice(config_, logger_)); // load audio settings from config service
if(!(config->getConfig<AudioParams, AudioConfig>("AudioEngine", "Main", &engineConfiguration_))) {
logger_->log("Audio", LogFlag::Error, "Failed to get logger configuration from config service");
return;
}
// note: this is the audio engine's configuration so we can grab sample rate
// it includes some synth params as well, so when synth gets its own config node they all need to be moved
voices_.assign(engineConfiguration_.maxVoices, Voice(config_, logger_, static_cast<float>(engineConfiguration_.sampleRate), &engineConfiguration_));
filter_.setSampleRate(44100.0f); filter_.setSampleRate(engineConfiguration_.sampleRate);
filter_.setParams(Filter::Type::BiquadLowpass, 4000.0f, 0.707f); filter_.setParams(Filter::Type::BiquadLowpass, 4000.0f, 0.707f);
} }

View File

@@ -2,6 +2,7 @@
#pragma once #pragma once
#include "config/ConfigService.hpp" #include "config/ConfigService.hpp"
#include "config/AudioConfig.hpp"
#include "LoggerService.hpp" #include "LoggerService.hpp"
#include "NoteQueue.hpp" #include "NoteQueue.hpp"
#include "Voice.hpp" #include "Voice.hpp"
@@ -29,10 +30,11 @@ private:
Filter filter_; Filter filter_;
// voices // voices
static constexpr size_t MAX_VOICES = 32; size_t maxVoices;
std::array<Voice, MAX_VOICES> voices_; std::vector<Voice> voices_;
ConfigService* config_; ConfigService* config_;
AudioParams engineConfiguration_;
LoggerService* logger_; LoggerService* logger_;
ScopeBuffer* scope_;; ScopeBuffer* scope_;;
NoteQueue* noteQueue_; NoteQueue* noteQueue_;

View File

@@ -1,11 +1,11 @@
#include "Voice.hpp" #include "Voice.hpp"
Voice::Voice(ConfigService* config, LoggerService* logger) : Voice::Voice(ConfigService* config, LoggerService* logger, float sampleRate, AudioParams* params) :
config_(config), logger_(logger) { config_(config), logger_(logger), sampleRate_(sampleRate), params_(params) {
// TODO: instrument factory // TODO: instrument factory << WHAT DO YOU MEAN JUST TODO A WHOLE FACTORY
instrument_ = PianoString(config_, logger_); instrument_ = PianoString(config_, logger_, sampleRate_);
} }

View File

@@ -3,8 +3,9 @@
#include <stdint.h> #include <stdint.h>
#include "Instruments/Instrument.hpp" #include "instruments/Instrument.hpp"
#include "Instruments/PianoString.hpp" #include "instruments/PianoString.hpp"
#include "config/AudioConfig.hpp"
// a voice is a tone generator that the synth uses for polyphony // a voice is a tone generator that the synth uses for polyphony
// the synth mixes multiple voices together into a polyphonic audio. calculations for samples are handled in the instrument // the synth mixes multiple voices together into a polyphonic audio. calculations for samples are handled in the instrument
@@ -13,7 +14,7 @@ class Voice {
public: public:
Voice() = default; Voice() = default;
Voice(ConfigService* config, LoggerService* logger); Voice(ConfigService* config, LoggerService* logger, float sampleRate, AudioParams* params);
~Voice() = default; ~Voice() = default;
void noteOn(uint8_t midiNote, float velocity); void noteOn(uint8_t midiNote, float velocity);
@@ -26,17 +27,17 @@ public:
private: 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)); return params_->pitchStandard * pow(params_->octaveConstant, static_cast<float>(note - params_->midiHome) / params_->notesPerOctave);
} }
float sampleRate_ = 1.0f;
uint8_t note_ = 0; uint8_t note_ = 0;
float velocity_ = 1.0f; float velocity_ = 1.0f;
bool active_ = false; bool active_ = false;
ConfigService* config_; ConfigService* config_;
AudioParams* params_;
LoggerService* logger_; LoggerService* logger_;
PianoString instrument_; PianoString instrument_;