basic profile loading

This commit is contained in:
2026-01-25 14:11:03 -06:00
parent a6ef39bb11
commit 21bf285aff
12 changed files with 134 additions and 67 deletions

View File

@@ -1,8 +1,6 @@
#include "ConfigInterface.h"
#include "yaml-cpp/yaml.h"
#include <fstream>
#include <filesystem>
@@ -14,6 +12,11 @@ ConfigInterface::ConfigInterface() {
}
ConfigInterface::ConfigInterface(ParameterStore* params): params_(params) {
}
// lots of checking in this to make this safe
int ConfigInterface::getValue(ConfigFile file, std::string key, int defaultVal) {
// assemble filepath
@@ -23,7 +26,7 @@ int ConfigInterface::getValue(ConfigFile file, std::string key, int defaultVal)
// attempt to open file
YAML::Node config;
try {
YAML::Node config = YAML::LoadFile(filepath);
// read key if it exists
@@ -42,3 +45,69 @@ int ConfigInterface::getValue(ConfigFile file, std::string key, int defaultVal)
return -1;
}
// ugly but if it works it works
void ConfigInterface::loadProfile(std::string filename) {
// load file
std::string filepath = "config/profiles/" + filename + ".yaml";
filepath = std::filesystem::absolute(filepath).string();
YAML::Node config;
try {
config = YAML::LoadFile(filepath);
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
return;
}
// check version
int version = config["version"].as<int>(); // yaml-cpp parses unquoted hex as integers
if(version < CONFIG_VERSION) {
std::cout << "Parameter profile version " << version << "is outdated below the compatible version " << CONFIG_VERSION << std::endl;
return;
} else {
std::cout << version << std::endl;
}
// extract values from the config file
std::array<ParamDefault, 5> osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume");
std::array<ParamDefault, 5> fCutoffProfile = loadEnvProfile(&config, "FilterCutoff");
std::array<ParamDefault, 5> fResonanceProfile = loadEnvProfile(&config, "FilterResonance");
// TODO: remove this once all the parameters are set properly
params_->resetToDefaults();
// set the values in the paramstore
params_->set(EnvelopeId::Osc1Volume, osc1VolumeProfile[0].def, osc1VolumeProfile[1].def, osc1VolumeProfile[2].def, osc1VolumeProfile[3].def, osc1VolumeProfile[4].def);
params_->set(EnvelopeId::FilterCutoff, fCutoffProfile[0].def, fCutoffProfile[1].def, fCutoffProfile[2].def, fCutoffProfile[3].def, fCutoffProfile[4].def);
params_->set(EnvelopeId::FilterResonance, fResonanceProfile[0].def, fResonanceProfile[1].def, fResonanceProfile[2].def, fResonanceProfile[3].def, fResonanceProfile[4].def);
// TODO: why do I bother passing in 5 values independently when I can just do an array ?
// VVV look down there its so easy
// TODO:
// load wavetable settings
// load oscillator pitch settings
}
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) {
YAML::Node envelopeNode = (*node)[profile];
std::array<ParamDefault, 5> paramProfile;
for(int i = 0; i < paramProfile.size(); i++) {
paramProfile[i] = { envelopeNode[i][0].as<float>(), envelopeNode[i][1].as<float>(), envelopeNode[i][2].as<float>() };
}
return paramProfile;
}
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(std::string filename, std::string profile) {
std::string filepath = "config/profiles/" + filename + ".yaml";
filepath = std::filesystem::absolute(filepath).string();
YAML::Node config = YAML::LoadFile(filepath);
return loadEnvProfile(&config, profile);
}

View File

@@ -4,6 +4,11 @@
#include <string>
#include <vector>
#include <iostream>
#include "yaml-cpp/yaml.h"
#include "ParameterStore.h"
#define CONFIG_VERSION 0x0002
enum class ConfigFile {
Audio = 0
@@ -15,17 +20,27 @@ const std::vector<std::string> filePaths = {
"audio.yaml"
};
// Reads from yaml config files
// Handles things like profile loading
class ConfigInterface {
public:
ConfigInterface();
ConfigInterface(ParameterStore* params);
~ConfigInterface() = default;
int getValue(ConfigFile file, std::string key, int defaultVal);
void loadProfile(std::string filename);
std::array<ParamDefault, 5> loadEnvProfile(YAML::Node* node, std::string profile);
std::array<ParamDefault, 5> loadEnvProfile(std::string filename, std::string profile);
private:
const std::string configRoot = "config";
// loading parameters
ParameterStore* params_;
};

View File

@@ -5,8 +5,8 @@
#include "yaml-cpp/yaml.h" // TODO: using yaml.h outside of ConfigInterface feels spaghetti to me
#include <filesystem>
ParameterStore::ParameterStore(ConfigInterface* config) : config_(config) {
resetToDefaults();
ParameterStore::ParameterStore() {
//resetToDefaults();
}
// set parameter value
@@ -31,45 +31,9 @@ float ParameterStore::get(ParamId id) const {
}
void ParameterStore::resetToDefaults() {
for(size_t i = 0; i < PARAM_COUNT; i++) {
values_[i].store(PARAM_DEFS[i].def, std::memory_order_relaxed);
}
loadParameterProfile("config/profiles/default.yaml");
}
void ParameterStore::loadParameterProfile(std::string filepath) {
// TODO: abstract the actual yaml interfacing to the ConfigInterface instead of here
// TODO: update ui based on changes that happen not in the ui
// it will require some architecture rework :(
// load file
filepath = std::filesystem::absolute(filepath).string();
YAML::Node config;
try {
config = YAML::LoadFile(filepath);
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
return;
}
// check version
int version = config["version"].as<int>(); // yaml-cpp parses unquoted hex as integers
if(version < CONFIG_VERSION) {
std::cout << "Parameter profile version " << version << "is outdated below the compatible version " << CONFIG_VERSION << std::endl;
return;
} else {
//std::cout << version << std::endl;
}
// start setting some values
YAML::Node osc1Volume = config["Osc1Volume"];
YAML::Node fCutoff = config["FilterCutoff"];
YAML::Node fResonance = config["FilterResonance"];
set(EnvelopeId::Osc1Volume, osc1Volume[0][0].as<float>(), osc1Volume[1][0].as<float>(), osc1Volume[2][0].as<float>(), osc1Volume[3][0].as<float>(), osc1Volume[4][0].as<float>());
set(EnvelopeId::FilterCutoff, fCutoff[0][0].as<float>(), fCutoff[1][0].as<float>(), fCutoff[2][0].as<float>(), fCutoff[3][0].as<float>(), fCutoff[4][0].as<float>());
set(EnvelopeId::FilterResonance, fResonance[0][0].as<float>(), fResonance[1][0].as<float>(), fResonance[2][0].as<float>(), fResonance[3][0].as<float>(), fResonance[4][0].as<float>());
}

View File

@@ -1,14 +1,10 @@
#pragma once
#include "ConfigInterface.h"
#include <cstdint>
#include <array>
#include <atomic>
#define CONFIG_VERSION 0x0001
enum class ParamId : uint16_t {
Osc1Frequency,
Osc1WaveSelector1,
@@ -111,7 +107,7 @@ class ParameterStore {
public:
ParameterStore(ConfigInterface* config);
ParameterStore();
~ParameterStore() = default;
void set(ParamId id, float value);
@@ -123,10 +119,6 @@ public:
private:
void loadParameterProfile(std::string filepath);
std::array<std::atomic<float>, PARAM_COUNT> values_;
ConfigInterface* config_;
};

View File

@@ -3,7 +3,8 @@
#include <iostream>
AudioEngine::AudioEngine(ConfigInterface* config) : params_(ParameterStore(config)), synth_(params_), config_(config) {
AudioEngine::AudioEngine(ConfigInterface* config, ParameterStore* params) : params_(params), synth_(params), config_(config) {
if(audio_.getDeviceCount() < 1) {
throw std::runtime_error("No audio devices found");
}

View File

@@ -5,9 +5,9 @@
#include <stdint.h>
#include <atomic>
#include "../ConfigInterface.h"
#include "Synth.h"
#include "../KeyboardController.h"
#include "../ConfigInterface.h"
#if defined(_WIN32)
#define AUDIO_API RtAudio::WINDOWS_WASAPI
@@ -18,7 +18,9 @@
class AudioEngine {
public:
AudioEngine(ConfigInterface* config);
AudioEngine() = default;
AudioEngine(ConfigInterface* config, ParameterStore* params);
~AudioEngine();
// starts the audio stream. returns true on success and false on failure
@@ -28,7 +30,7 @@ public:
void stop();
// getters
ParameterStore* parameters() { return &params_; }
ParameterStore* parameters() { return params_; }
NoteQueue& noteQueue() { return noteQueue_; }
ScopeBuffer& scopeBuffer() { return scope_; }
@@ -41,7 +43,7 @@ private:
int32_t process(float* out, uint32_t nFrames);
ConfigInterface* config_; // access to config files
ParameterStore params_; // stores the control parameters
ParameterStore* params_; // stores the control parameters
NoteQueue noteQueue_; // stores note events for passing between threads
Synth synth_; // generates audio
ScopeBuffer scope_ { 1024 }; // stores audio samples for visualization

View File

@@ -8,13 +8,13 @@
#define M_PI 3.14159265358979323846
#endif
Synth::Synth(const ParameterStore& params) : paramStore_(params) {
Synth::Synth(ParameterStore* params) : paramStore_(params) {
voices_.fill(Voice(params_.data(), &wavetable_));
}
void Synth::updateParams() {
for(size_t i = 0; i < PARAM_COUNT; i++) {
params_[i].target = paramStore_.get(static_cast<ParamId>(i));
params_[i].target = paramStore_->get(static_cast<ParamId>(i));
}
}

View File

@@ -15,7 +15,8 @@
class Synth {
public:
Synth(const ParameterStore& params);
Synth() = default;
Synth(ParameterStore* params);
~Synth() = default;
// generates a buffer of audio samples nFrames long
@@ -39,7 +40,7 @@ private:
Voice* findFreeVoice();
Voice* findVoiceByNote(uint8_t note);
const ParameterStore& paramStore_;
ParameterStore* paramStore_;
// smoothed params creates a buffer in case the thread controlling paramStore gets blocked
std::array<SmoothedParam, PARAM_COUNT> params_;

View File

@@ -8,7 +8,8 @@
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui_(new Ui::MainWindow),
audio_(new AudioEngine(&config_)),
config_(ConfigInterface(&params_)),
audio_(new AudioEngine(&config_, &params_)),
keyboard_(audio_->noteQueue()),
midi_(audio_->noteQueue()) {
@@ -74,12 +75,13 @@ void MainWindow::onResetClicked() {
// initialize to defaults
// envelopeGenerators
ui_->envelopeOsc1Volume->init(EnvelopeId::Osc1Volume);
ui_->envelopeFilterCutoff->init(EnvelopeId::FilterCutoff);
ui_->envelopeFilterResonance->init(EnvelopeId::FilterResonance);
config_.loadProfile("default");
// update ui from the paramstore
ui_->envelopeOsc1Volume->init(EnvelopeId::Osc1Volume, config_.loadEnvProfile("default", "Osc1Volume"));
ui_->envelopeFilterCutoff->init(EnvelopeId::FilterCutoff, config_.loadEnvProfile("default", "FilterCutoff"));
ui_->envelopeFilterResonance->init(EnvelopeId::FilterResonance, config_.loadEnvProfile("default", "FilterResonance"));
// comboBoxes
ui_->comboOsc1WaveSelector1->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector1)].def));
ui_->comboOsc1WaveSelector2->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector2)].def));

View File

@@ -4,9 +4,9 @@
#include <QMainWindow>
#include <QKeyEvent>
#include "../ConfigInterface.h"
#include "../synth/AudioEngine.h"
#include "../MidiController.h"
#include "../ConfigInterface.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
@@ -28,8 +28,10 @@ private slots:
void onResetClicked();
private:
Ui::MainWindow *ui_;
ParameterStore params_;
ConfigInterface config_;
AudioEngine* audio_ = nullptr;
KeyboardController keyboard_;

View File

@@ -92,3 +92,21 @@ void EnvelopeGenerator::init(EnvelopeId id) {
setRelease(PARAM_DEFS[static_cast<size_t>(params.r)].def);
}
void EnvelopeGenerator::init(EnvelopeId id, std::array<ParamDefault, 5> profile) {
EnvelopeParam params = ENV_PARAMS[static_cast<size_t>(id)];
ui_->sliderDepth->setRange(profile[0].min, profile[0].max);
ui_->sliderAttack->setRange(profile[1].min, profile[1].max);
ui_->sliderDecay->setRange(profile[2].min, profile[2].max);
ui_->sliderSustain->setRange(profile[3].min, profile[3].max);
ui_->sliderRelease->setRange(profile[4].min, profile[4].max);
setDepth(profile[0].def);
setAttack(profile[1].def);
setDecay(profile[2].def);
setSustain(profile[3].def);
setRelease(profile[4].def);
}

View File

@@ -18,6 +18,7 @@ public:
// connects signals, sets parameters to the defaults defined in paramStore
void init(EnvelopeId id);
void init(EnvelopeId id, std::array<ParamDefault, 5> profile);
// setters
void setDepth(float v);