diff --git a/CMakeLists.txt b/CMakeLists.txt index da80eb8..ff93914 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/config/sonobulus.cfg b/config/sonobulus.cfg index bf0c026..1fe0fdf 100644 --- a/config/sonobulus.cfg +++ b/config/sonobulus.cfg @@ -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"; diff --git a/src/ConfigService.hpp b/src/ConfigService.hpp deleted file mode 100644 index 41b1cf4..0000000 --- a/src/ConfigService.hpp +++ /dev/null @@ -1,128 +0,0 @@ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -struct LoggerConfig { - std::string id; - std::vector 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(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 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(notesGroup[note]) % INT8_MAX; - int32_t keyId = static_cast(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 - 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_; -}; diff --git a/src/LoggerService.cpp b/src/LoggerService.cpp index 59bf9f6..b8e140b 100644 --- a/src/LoggerService.cpp +++ b/src/LoggerService.cpp @@ -13,16 +13,10 @@ namespace fs = std::filesystem; LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) { - if(!(config->getConfig("Logger", loggerId, &configuration_))) { + if(!(config->getConfig("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; @@ -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; diff --git a/src/LoggerService.hpp b/src/LoggerService.hpp index fea8281..4fb35f4 100644 --- a/src/LoggerService.hpp +++ b/src/LoggerService.hpp @@ -5,7 +5,8 @@ #include #include -#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 activeFlags_; - LoggerConfig configuration_; -}; \ No newline at end of file + LoggerParams configuration_; +}; diff --git a/src/config/AudioConfig.hpp b/src/config/AudioConfig.hpp new file mode 100644 index 0000000..13d59e5 --- /dev/null +++ b/src/config/AudioConfig.hpp @@ -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 { + +public: + + using IConfig::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; + } + +}; diff --git a/src/ConfigService.cpp b/src/config/ConfigService.cpp similarity index 100% rename from src/ConfigService.cpp rename to src/config/ConfigService.cpp diff --git a/src/config/ConfigService.hpp b/src/config/ConfigService.hpp new file mode 100644 index 0000000..3bbef86 --- /dev/null +++ b/src/config/ConfigService.hpp @@ -0,0 +1,56 @@ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#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 + 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_; +}; diff --git a/src/config/IConfig.hpp b/src/config/IConfig.hpp new file mode 100644 index 0000000..e5a6f8f --- /dev/null +++ b/src/config/IConfig.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#include + +struct IParams { + +}; + +template +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_; + +}; diff --git a/src/config/KeymapConfig.hpp b/src/config/KeymapConfig.hpp new file mode 100644 index 0000000..0b9bdea --- /dev/null +++ b/src/config/KeymapConfig.hpp @@ -0,0 +1,47 @@ + +#pragma once + +#include "IConfig.hpp" + +struct KeymapParams : IParams { + std::string id; + std::unordered_map keymap; +}; + +class KeymapConfig : public IConfig { + +public: + + using IConfig::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(notesGroup[note]) % INT8_MAX; + int32_t keyId = static_cast(keysGroup[key]) % INT32_MAX; + + params_->keymap.emplace(keyId, noteId); + } + + } catch (const libconfig::SettingException&) { + return false; + } + + return true; + } + +}; diff --git a/src/config/LoggerConfig.hpp b/src/config/LoggerConfig.hpp new file mode 100644 index 0000000..82a056d --- /dev/null +++ b/src/config/LoggerConfig.hpp @@ -0,0 +1,47 @@ + +#pragma once + +#include "IConfig.hpp" + +struct LoggerParams : IParams { + std::string id; + std::vector flagsEnabled; + bool showTime = false; + bool showSourceTrace = false; + bool coutEnabled = false; + bool fileEnabled = false; + std::string filePath; +}; + +class LoggerConfig : public IConfig { + +public: + + using IConfig::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(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; + } + +}; diff --git a/src/main.cpp b/src/main.cpp index 5b5d679..752418d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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" diff --git a/src/synth/AudioEngine.hpp b/src/synth/AudioEngine.hpp index d09a318..dcb7b50 100644 --- a/src/synth/AudioEngine.hpp +++ b/src/synth/AudioEngine.hpp @@ -6,7 +6,7 @@ #include #include "LoggerService.hpp" -#include "ConfigService.hpp" +#include "config/ConfigService.hpp" #include "Synth.hpp" #include "NoteQueue.hpp" diff --git a/src/synth/Instrument.cpp b/src/synth/Instruments/Instrument.cpp similarity index 100% rename from src/synth/Instrument.cpp rename to src/synth/Instruments/Instrument.cpp diff --git a/src/synth/Instrument.hpp b/src/synth/Instruments/Instrument.hpp similarity index 96% rename from src/synth/Instrument.hpp rename to src/synth/Instruments/Instrument.hpp index f504e67..dc8b523 100644 --- a/src/synth/Instrument.hpp +++ b/src/synth/Instruments/Instrument.hpp @@ -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 diff --git a/src/synth/Instruments/PianoString.cpp b/src/synth/Instruments/PianoString.cpp index 79daa1a..e192eef 100644 --- a/src/synth/Instruments/PianoString.cpp +++ b/src/synth/Instruments/PianoString.cpp @@ -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(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(80)); dx_ = L_ / static_cast(segmentCount_); diff --git a/src/synth/Instruments/PianoString.hpp b/src/synth/Instruments/PianoString.hpp index faa8440..d2d1fc6 100644 --- a/src/synth/Instruments/PianoString.hpp +++ b/src/synth/Instruments/PianoString.hpp @@ -3,7 +3,7 @@ #include -#include "synth/Instrument.hpp" +#include "synth/Instruments/Instrument.hpp" class PianoString : public Instrument { diff --git a/src/synth/KeyboardController.cpp b/src/synth/KeyboardController.cpp index 858375d..b4d20fe 100644 --- a/src/synth/KeyboardController.cpp +++ b/src/synth/KeyboardController.cpp @@ -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("KeyboardController", "Main", &configuration_))) { + if(!(config->getConfig("KeyboardController", "Main", &configuration_))) { logger_->log("Keyboard", LogFlag::Error, "Failed to get logger configuration fom config service"); return; } diff --git a/src/synth/KeyboardController.hpp b/src/synth/KeyboardController.hpp index 8ff6ccf..640173d 100644 --- a/src/synth/KeyboardController.hpp +++ b/src/synth/KeyboardController.hpp @@ -7,7 +7,8 @@ #include #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; diff --git a/src/synth/MidiController.hpp b/src/synth/MidiController.hpp index ef1ee9e..67e3bfb 100644 --- a/src/synth/MidiController.hpp +++ b/src/synth/MidiController.hpp @@ -7,7 +7,7 @@ #include #include "NoteQueue.hpp" -#include "ConfigService.hpp" +#include "config/ConfigService.hpp" #include "LoggerService.hpp" class MidiController { diff --git a/src/synth/NoteQueue.hpp b/src/synth/NoteQueue.hpp index 577dbc5..8deb8c0 100644 --- a/src/synth/NoteQueue.hpp +++ b/src/synth/NoteQueue.hpp @@ -7,7 +7,7 @@ #include #include -#include "ConfigService.hpp" +#include "config/ConfigService.hpp" #include "LoggerService.hpp" enum NoteEventType { diff --git a/src/synth/Scope.hpp b/src/synth/Scope.hpp index d012ea7..6200507 100644 --- a/src/synth/Scope.hpp +++ b/src/synth/Scope.hpp @@ -8,7 +8,7 @@ #include #include -#include "ConfigService.hpp" +#include "config/ConfigService.hpp" #include "LoggerService.hpp" class ScopeBuffer : public QObject { diff --git a/src/synth/Synth.cpp b/src/synth/Synth.cpp index ad616c1..268289c 100644 --- a/src/synth/Synth.cpp +++ b/src/synth/Synth.cpp @@ -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() { diff --git a/src/synth/Synth.hpp b/src/synth/Synth.hpp index bcea397..9f0858d 100644 --- a/src/synth/Synth.hpp +++ b/src/synth/Synth.hpp @@ -1,7 +1,7 @@ #pragma once -#include "ConfigService.hpp" +#include "config/ConfigService.hpp" #include "LoggerService.hpp" #include "NoteQueue.hpp" #include "Voice.hpp" diff --git a/src/synth/Voice.hpp b/src/synth/Voice.hpp index f215050..1c2e839 100644 --- a/src/synth/Voice.hpp +++ b/src/synth/Voice.hpp @@ -3,7 +3,7 @@ #include -#include "Instrument.hpp" +#include "Instruments/Instrument.hpp" #include "Instruments/PianoString.hpp" // a voice is a tone generator that the synth uses for polyphony diff --git a/ui/components/SmartSlider.qml b/ui/components/SmartSlider.qml new file mode 100644 index 0000000..095bdca --- /dev/null +++ b/ui/components/SmartSlider.qml @@ -0,0 +1,2 @@ + +haiii :3