refactor config classes

This commit is contained in:
2026-07-04 13:22:25 -05:00
parent 269648e021
commit 9365e12bb4
26 changed files with 266 additions and 172 deletions

View File

@@ -46,7 +46,7 @@ find_package(Qt6 REQUIRED COMPONENTS
qt_standard_project_setup()
add_library(sonobulus_core STATIC
src/ConfigService.cpp
src/config/ConfigService.cpp
src/LoggerService.cpp
src/TimerComponent.cpp
src/synth/AudioEngine.cpp
@@ -57,7 +57,7 @@ add_library(sonobulus_core STATIC
src/synth/Synth.cpp
src/synth/Voice.cpp
src/synth/Filter.cpp
src/synth/Instrument.cpp
src/synth/Instruments/Instrument.cpp
src/synth/Instruments/PianoString.cpp
)

View File

@@ -19,6 +19,20 @@ Logger = (
}
);
AudioEngine = (
{
Id = "Main";
SampleRate = 44100;
Channels = 2;
StereoMode = 1;
BufferSize = 512;
PitchStandard = 440.0;
MidiHome = 69;
NotesPerOctave = 12;
}
);
KeyboardController = (
{
Id = "Main";

View File

@@ -1,128 +0,0 @@
#pragma once
#include <libconfig.h++>
#include <optional>
#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
struct LoggerConfig {
std::string id;
std::vector<std::string> flagsEnabled;
bool showTime = false;
bool showSourceTrace = false;
bool coutEnabled = false;
bool fileEnabled = false;
std::string filePath;
bool parseConfig(const libconfig::Setting& setting) {
if (!setting.lookupValue("Id", id)) {
return false;
}
try {
const libconfig::Setting& flags = setting.lookup("FlagsEnabled");
for (int index = 0; index < flags.getLength(); ++index) {
flagsEnabled.push_back(static_cast<const char*>(flags[index]));
}
} catch (const libconfig::SettingException&) {
flagsEnabled.clear();
}
setting.lookupValue("ShowTime", showTime);
setting.lookupValue("ShowSourceTrace", showSourceTrace);
setting.lookupValue("CoutEnabled", coutEnabled);
setting.lookupValue("FileEnabled", fileEnabled);
setting.lookupValue("FilePath", filePath);
return true;
}
};
struct AudioConfig {
uint32_t sampleRate;
uint32_t channels;
uint32_t stereoMode;
uint32_t bufferSize;
float pitchStandard;
int32_t midiHome;
int32_t notesPerOctave;
};
struct KeymapConfig {
std::string id;
std::unordered_map<int32_t, uint8_t> keymap;
bool parseConfig(const libconfig::Setting& setting) {
if (!setting.lookupValue("Id", id)) {
return false;
}
try {
const libconfig::Setting& keymapGroup = setting.lookup("Keymap");
const libconfig::Setting& notesGroup = setting.lookup("Notes");
const libconfig::Setting& keysGroup = setting.lookup("Keys");
for(size_t i = 0; i < keymapGroup.getLength(); i++) {
libconfig::Setting& item = keymapGroup[i];
std::string key = item.getName();
std::string note = "";
if(item.getType() == libconfig::Setting::TypeString) note = std::string(item.c_str());
int8_t noteId = static_cast<int>(notesGroup[note]) % INT8_MAX;
int32_t keyId = static_cast<int>(keysGroup[key]) % INT32_MAX;
keymap.emplace(keyId, noteId);
}
} catch (const libconfig::SettingException&) {
return false;
}
return true;
}
};
class ConfigService {
public:
ConfigService(const std::string& filePath);
~ConfigService() = default;
bool loadFromFile(const std::string& filePath);
template<typename T>
bool getConfig(const std::string& type, const std::string& id, T* config) const {
try {
const libconfig::Setting& configs = config_.lookup(type);
for (int index = 0; index < configs.getLength(); ++index) {
const libconfig::Setting& configSetting = configs[index];
std::string configId;
if (!configSetting.lookupValue("Id", configId) || configId != id) {
continue;
}
return config->parseConfig(configSetting);
}
} catch (const libconfig::SettingException& ex) {
std::cout << "libconfig setting exception: " << ex.what() << std::endl;
return false;
}
return false;
}
const std::string& lastError() const;
private:
libconfig::Config config_;
std::string lastError_;
};

View File

@@ -13,17 +13,11 @@ namespace fs = std::filesystem;
LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) {
if(!(config->getConfig<LoggerConfig>("Logger", loggerId, &configuration_))) {
if(!(config->getConfig<LoggerParams, LoggerConfig>("Logger", loggerId, &configuration_))) {
std::cout << "Failed to get logger configuration fom config service" << std::endl;
return;
}
standardOutputEnabled_ = configuration_.coutEnabled;
fileOutputEnabled_ = configuration_.fileEnabled;
additionaldetailsEnabled_ = configuration_.showSourceTrace;
timeEnabled_ = configuration_.showTime;
id_ = configuration_.id;
for(std::string& flag : configuration_.flagsEnabled) {
bool found = false;
for(const char* validFlag : LogFlagStrings) {
@@ -55,7 +49,7 @@ LoggerService::LoggerService(ConfigService* config, const std::string& loggerId)
fs::create_directories(std::string(BINARY_DIR) + "/" + finaltime);
std::string logPath = std::string(BINARY_DIR) + "/" + finaltime + "/" + configuration_.filePath;
if(fileOutputEnabled_) outfile_.open(logPath);
if(configuration_.fileEnabled) outfile_.open(logPath);
log("Logger", LogFlag::Info, "Logger initialized.");
}
@@ -78,11 +72,11 @@ void LoggerService::log(std::string component, LogFlag flag, std::string message
std::string finalmessage = "";
if(timeEnabled_) {
if(configuration_.showTime) {
finalmessage = finalmessage + "[" + "Not Implemented" + "] ";
}
std::string componentTrace = "[" + id_ + ": " + component + "] ";
std::string componentTrace = "[" + configuration_.id + ": " + component + "] ";
finalmessage += componentTrace; // component is the section of the program (For example Mesh or Engine) that is calling the logger
std::string level = "";
@@ -94,15 +88,15 @@ void LoggerService::log(std::string component, LogFlag flag, std::string message
finalmessage = finalmessage + "[" + level + "] ";
finalmessage = finalmessage + message + " ";
if (additionaldetailsEnabled_) {
if (configuration_.showSourceTrace) {
finalmessage = finalmessage + "[Function: " + Source.function_name() + "]" + " " + "[Line: " + std::to_string(Source.line()) + "]" + " " + "[File: " + Source.file_name() + "]";
}
if(standardOutputEnabled_) {
if(configuration_.coutEnabled) {
std::cout << finalmessage << std::endl;
}
if(fileOutputEnabled_) {
if(configuration_.fileEnabled) {
outfile_ << finalmessage << std::endl;
}
return;

View File

@@ -5,7 +5,8 @@
#include <fstream>
#include <source_location>
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "config/LoggerConfig.hpp"
enum LogFlag {
Debug,
@@ -33,14 +34,8 @@ public:
private:
bool standardOutputEnabled_;
bool fileOutputEnabled_;
bool additionaldetailsEnabled_;
bool timeEnabled_;
std::ofstream outfile_;
std::string id_;
std::vector<LogFlag> activeFlags_;
LoggerConfig configuration_;
LoggerParams configuration_;
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "IConfig.hpp"
struct AudioParams : IParams {
uint32_t sampleRate;
uint32_t channels;
uint32_t stereoMode;
uint32_t bufferSize;
float pitchStandard;
int32_t midiHome;
int32_t notesPerOctave;
};
class AudioConfig : public IConfig<AudioParams> {
public:
using IConfig<AudioParams>::IConfig;
bool parseConfig(const libconfig::Setting& setting) override {
if (!setting.lookupValue("Id", params_->id)) {
return false;
}
setting.lookupValue("SampleRate", params_->sampleRate);
setting.lookupValue("StereoMode", params_->stereoMode);
setting.lookupValue("BufferSize", params_->bufferSize);
setting.lookupValue("PitchStandard", params_->pitchStandard);
setting.lookupValue("MidiHome", params_->midiHome);
setting.lookupValue("NotesPerOctave", params_->notesPerOctave);
return true;
}
};

View File

@@ -0,0 +1,56 @@
#pragma once
#include <libconfig.h++>
#include <optional>
#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
#include "config/IConfig.hpp"
// TODO: would be cool for the config file to include other config files so theyre a bit more compartmentalized
// then the main file will be like a hub and would be more like each component having its own config file
class ConfigService {
public:
ConfigService(const std::string& filePath);
~ConfigService() = default;
bool loadFromFile(const std::string& filePath);
template<typename T1, typename T2>
bool getConfig(const std::string& type, const std::string& id, T1* params) const {
T2 config { params };
try {
const libconfig::Setting& configs = config_.lookup(type);
for (int index = 0; index < configs.getLength(); ++index) {
const libconfig::Setting& configSetting = configs[index];
std::string configId;
if (!configSetting.lookupValue("Id", configId) || configId != id) {
continue;
}
return config.parseConfig(configSetting);
}
} catch (const libconfig::SettingException& ex) {
std::cout << "libconfig setting exception: " << ex.what() << std::endl;
return false;
}
return false;
}
const std::string& lastError() const;
private:
libconfig::Config config_;
std::string lastError_;
};

24
src/config/IConfig.hpp Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <libconfig.h++>
struct IParams {
};
template <typename T>
class IConfig {
public:
IConfig(T* params) : params_(params) { }
~IConfig() = default;
virtual bool parseConfig(const libconfig::Setting& setting) = 0;
IParams* params() { return params_; }
protected:
T* params_;
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "IConfig.hpp"
struct KeymapParams : IParams {
std::string id;
std::unordered_map<int32_t, uint8_t> keymap;
};
class KeymapConfig : public IConfig<KeymapParams> {
public:
using IConfig<KeymapParams>::IConfig;
bool parseConfig(const libconfig::Setting& setting) override {
if (!setting.lookupValue("Id", params_->id)) {
return false;
}
try {
const libconfig::Setting& keymapGroup = setting.lookup("Keymap");
const libconfig::Setting& notesGroup = setting.lookup("Notes");
const libconfig::Setting& keysGroup = setting.lookup("Keys");
for(size_t i = 0; i < keymapGroup.getLength(); i++) {
libconfig::Setting& item = keymapGroup[i];
std::string key = item.getName();
std::string note = "";
if(item.getType() == libconfig::Setting::TypeString) note = std::string(item.c_str());
int8_t noteId = static_cast<int>(notesGroup[note]) % INT8_MAX;
int32_t keyId = static_cast<int>(keysGroup[key]) % INT32_MAX;
params_->keymap.emplace(keyId, noteId);
}
} catch (const libconfig::SettingException&) {
return false;
}
return true;
}
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "IConfig.hpp"
struct LoggerParams : IParams {
std::string id;
std::vector<std::string> flagsEnabled;
bool showTime = false;
bool showSourceTrace = false;
bool coutEnabled = false;
bool fileEnabled = false;
std::string filePath;
};
class LoggerConfig : public IConfig<LoggerParams> {
public:
using IConfig<LoggerParams>::IConfig;
bool parseConfig(const libconfig::Setting& setting) override {
if (!setting.lookupValue("Id", params_->id)) {
return false;
}
try {
const libconfig::Setting& flags = setting.lookup("FlagsEnabled");
for (int index = 0; index < flags.getLength(); ++index) {
params_->flagsEnabled.push_back(static_cast<const char*>(flags[index]));
}
} catch (const libconfig::SettingException&) {
params_->flagsEnabled.clear();
return false;
}
setting.lookupValue("ShowTime", params_->showTime);
setting.lookupValue("ShowSourceTrace", params_->showSourceTrace);
setting.lookupValue("CoutEnabled", params_->coutEnabled);
setting.lookupValue("FileEnabled", params_->fileEnabled);
setting.lookupValue("FilePath", params_->filePath);
return true;
}
};

View File

@@ -7,7 +7,7 @@
#include "TimerComponent.hpp"
#include "LoggerService.hpp"
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "synth/AudioEngine.hpp"
#include "synth/KeyboardController.hpp"
#include "synth/Scope.hpp"

View File

@@ -6,7 +6,7 @@
#include <RtAudio.h>
#include "LoggerService.hpp"
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "Synth.hpp"
#include "NoteQueue.hpp"

View File

@@ -1,7 +1,7 @@
#pragma once
#include "ConfigService.hpp"
#include "config/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

View File

@@ -103,6 +103,7 @@ void PianoString::recalculateConstants() {
waveVelocity_ = 2.0f * frequency_ * L_;
// calculate number of super-sampling steps needed to maintain string stability
samplingSteps_ = 0;
do {
samplingSteps_++;
@@ -117,8 +118,8 @@ void PianoString::recalculateConstants() {
segmentCount_ = static_cast<size_t>(std::sqrt((-a+std::sqrt(a*a+4.0*b))/(2.0*b))) - 1;
}
} while (segmentCount_ < 60);
} while (segmentCount_ < 80);
// cap segment count at 80 if above
segmentCount_ = std::min(segmentCount_, static_cast<size_t>(80));
dx_ = L_ / static_cast<float>(segmentCount_);

View File

@@ -3,7 +3,7 @@
#include <cmath>
#include "synth/Instrument.hpp"
#include "synth/Instruments/Instrument.hpp"
class PianoString : public Instrument {

View File

@@ -12,7 +12,7 @@ KeyboardController::KeyboardController(QObject* parent) : QObject(parent) {
KeyboardController::KeyboardController(ConfigService* config, LoggerService* logger, NoteQueue* queue) : config_(config), logger_(logger), queue_(queue) {
// load keymap from config service
if(!(config->getConfig<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");
return;
}

View File

@@ -7,7 +7,8 @@
#include <unordered_map>
#include "NoteQueue.hpp"
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "config/KeymapConfig.hpp"
#include "LoggerService.hpp"
// The keyboardcontroller handles user inputs from a keyboard and maps them to note events
@@ -32,7 +33,7 @@ private:
LoggerService* logger_;
// keymap is key -> midi note id
KeymapConfig configuration_;
KeymapParams configuration_;
static constexpr float defaultVelocity_ = 0.8f;

View File

@@ -7,7 +7,7 @@
#include <unordered_set>
#include "NoteQueue.hpp"
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "LoggerService.hpp"
class MidiController {

View File

@@ -7,7 +7,7 @@
#include <cstdint>
#include <chrono>
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "LoggerService.hpp"
enum NoteEventType {

View File

@@ -8,7 +8,7 @@
#include <vector>
#include <atomic>
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "LoggerService.hpp"
class ScopeBuffer : public QObject {

View File

@@ -13,9 +13,6 @@ Synth::Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope, N
void Synth::handleNoteEvent(const NoteEvent& event) {
// Voice* v = findVoiceByNote(event.note);
// if(v != nullptr) v->noteOff();
// stop all voices that are currently playing this note
for(Voice& v : voices_) {
if(v.isActive() && v.note() == event.note) {
@@ -23,8 +20,8 @@ void Synth::handleNoteEvent(const NoteEvent& event) {
}
}
// call note-on for an idle voice for the note-event
if(event.type == NoteEventType::NoteOn) {
Voice* v = findFreeVoice();
if(v != nullptr) v->noteOn(event.note, event.velocity);
}
@@ -38,8 +35,9 @@ void Synth::process(float* out, size_t nFrames) {
handleNoteEvent(noteEvent);
}
// find lowest frequency voice for scope triggering
size_t lowestVoice = 0;
float lowestFreq = 100000.0f;
float lowestFreq = FLT_MAX;
for(size_t i = 0; i < voices_.size(); i++) {
if(!voices_[i].isActive()) continue;
float currentFreq = voices_[i].frequency();
@@ -54,8 +52,11 @@ void Synth::process(float* out, size_t nFrames) {
float sampleOut = 0.0f;
bool triggered = false;
bool once = false;
// generate n samples for the output buffer
for(size_t i = 0; i < nFrames; i++) {
// process all active voices and mix their produced samples
float mix = 0.0f;
for(size_t j = 0; j < voices_.size(); j++) {
bool temp = false;
@@ -78,7 +79,6 @@ void Synth::process(float* out, size_t nFrames) {
scope_->spinlock(false);
}
Voice* Synth::findFreeVoice() {

View File

@@ -1,7 +1,7 @@
#pragma once
#include "ConfigService.hpp"
#include "config/ConfigService.hpp"
#include "LoggerService.hpp"
#include "NoteQueue.hpp"
#include "Voice.hpp"

View File

@@ -3,7 +3,7 @@
#include <stdint.h>
#include "Instrument.hpp"
#include "Instruments/Instrument.hpp"
#include "Instruments/PianoString.hpp"
// a voice is a tone generator that the synth uses for polyphony

View File

@@ -0,0 +1,2 @@
haiii :3