refactor parameterstore

This commit is contained in:
2026-02-08 14:11:16 -06:00
parent a0ac5848e3
commit e2f5cb13e0
12 changed files with 66 additions and 63 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
build/*
.vscode/*
scripts/_pycache_/*
scripts/__pycache__/*

View File

@@ -60,7 +60,7 @@ Build. The script will build and install dependencies automatically
On Windows (MSVC):
```PowerShell
.\scripts\build.ps1 # builds in build/Debug/
.\scripts\build.ps1
```
On Linux (GCC):
@@ -68,7 +68,7 @@ On Linux (GCC):
./scripts/build.sh
```
TODO: right now you need to run the executable from the executable's directory because of CWD paths and such. Needs to be fixed because annoying
Note: executing the app from the root directory will make the app use the default root level configs. Run the app from the build directory to customize your configurations with the build/config configurations.
Configure the CMake/build script if you have issues
@@ -80,9 +80,6 @@ To clean:
Note: dependencies are built into build/lib, so don't delete unless you need to rebuild the libraries
Use the install_dependencies script to manually install dependencies.
Build troubleshooting:
On windows, `bcdedit /set IncreaseUserVa 3072` solved cc1plus.exe: out of memory errors while building qt for me
## Configurations
Default config files are located in the config/ directory, and they are replicated into build/config/ if they dont already exist there. To edit the configurations, edit the config files in the build directory, not the defaults. Most config files are loaded/parsed at startup (TODO: investigate some reloading functions), so the program must be restarted, although not recompiled, for new configs to take effect. \
Voice profiles are saved into config files into a human-readable format (YAML) and can be edited manually or by saving within the app. \

View File

@@ -58,6 +58,8 @@ Write-Host "Building metabolus..."
cmake --build $BUILD_DIR --config $CONFIG
# TODO: install
cd $BUILD_DIR
cmake --install . --prefix ".\"
# link dlls
Write-Host "Deploying metabolus..."

View File

@@ -1,8 +1,13 @@
import math
from generate_wavetable import generateWavetable
WAVETABLE_FILE_NAME = "sharkFin"
# process expects a waveform from x=[0, 2pi) centered around f(x)=0
# normalization is handled by the wavetableGenerator
def sine(phase):
return math.sin(phase)
@@ -36,8 +41,9 @@ def sharkFin(phase):
def sphere(phase):
return 1
# process get called by generate_wavetable.py
# it calculates a single sample at a specified phase
# normalization is handled by generate_wavetable.py
def process(phase):
return sharkFin(phase)
# pass in the name of your wavtable file and the process function
def main():
generateWavetable(WAVETABLE_FILE_NAME, process)
if __name__ == "__main__":
main()

View File

@@ -12,16 +12,15 @@
# this script uses the function defined in example_wavetable.py to calculate samples
# if you want a custom wavetable, copy/edit/modify the example function (desmos is great for brainstorming)
# import this script and call generateWavetable(processFunction) to generate a custom wavetable
from array import array
import math
import example_wavetable
wavetableLength = 2048
def createFile():
filename = example_wavetable.WAVETABLE_FILE_NAME + ".wt"
def createFile(name):
filename = name + ".wt"
print("creating file " + filename)
file = open(filename, "wb")
return file
@@ -29,7 +28,7 @@ def createFile():
def writeMetadata(file):
print(">> im writing metadata")
def generateWavetable(file):
def writeData(file, processFunction):
print(">> im generating the wavetable")
# init variables
@@ -40,7 +39,7 @@ def generateWavetable(file):
# generate each discrete sample
for i in range(wavetableLength):
sample = example_wavetable.process(x)
sample = processFunction(x)
accumulator += sample * sample
x += phaseInc
data_list[i] = sample
@@ -59,14 +58,11 @@ def closeFile(file):
print(">> finishing up")
file.close()
def main():
def generateWavetable(name, processFunction):
print("Hello main")
file = createFile()
file = createFile(name)
writeMetadata(file)
generateWavetable(file)
writeData(file, processFunction)
closeFile(file)
print("done")
if __name__ == "__main__":
main()

View File

@@ -70,48 +70,48 @@ YAML::Node ConfigInterface::loadProfile(std::string filename) {
}
// 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");
std::array<Param, 5> osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume");
std::array<Param, 5> fCutoffProfile = loadEnvProfile(&config, "FilterCutoff");
std::array<Param, 5> fResonanceProfile = loadEnvProfile(&config, "FilterResonance");
std::array<ParamDefault, 3> masterPitchOffsets = {{
std::array<Param, 3> masterPitchOffsets = {{
{ config["MasterOctaveOffset"][0].as<float>(), config["MasterOctaveOffset"][1].as<float>(), config["MasterOctaveOffset"][2].as<float>() },
{ config["MasterSemitoneOffset"][0].as<float>(), config["MasterSemitoneOffset"][1].as<float>(), config["MasterSemitoneOffset"][2].as<float>() },
{ config["MasterPitchOffset"][0].as<float>(), config["MasterPitchOffset"][1].as<float>(), config["MasterPitchOffset"][2].as<float>() },
}};
std::array<ParamDefault, 3> osc1PitchOffsets = {{
std::array<Param, 3> osc1PitchOffsets = {{
{ config["Osc1OctaveOffset"][0].as<float>(), config["Osc1OctaveOffset"][1].as<float>(), config["Osc1OctaveOffset"][2].as<float>() },
{ config["Osc1SemitoneOffset"][0].as<float>(), config["Osc1SemitoneOffset"][1].as<float>(), config["Osc1SemitoneOffset"][2].as<float>() },
{ config["Osc1PitchOffset"][0].as<float>(), config["Osc1PitchOffset"][1].as<float>(), config["Osc1PitchOffset"][2].as<float>() },
}};
std::array<ParamDefault, 3> osc2PitchOffsets = {{
std::array<Param, 3> osc2PitchOffsets = {{
{ config["Osc2OctaveOffset"][0].as<float>(), config["Osc2OctaveOffset"][1].as<float>(), config["Osc2OctaveOffset"][2].as<float>() },
{ config["Osc2SemitoneOffset"][0].as<float>(), config["Osc2SemitoneOffset"][1].as<float>(), config["Osc2SemitoneOffset"][2].as<float>() },
{ config["Osc2PitchOffset"][0].as<float>(), config["Osc2PitchOffset"][1].as<float>(), config["Osc2PitchOffset"][2].as<float>() },
}};
std::array<ParamDefault, 3> osc3PitchOffsets = {{
std::array<Param, 3> osc3PitchOffsets = {{
{ config["Osc3OctaveOffset"][0].as<float>(), config["Osc3OctaveOffset"][1].as<float>(), config["Osc3OctaveOffset"][2].as<float>() },
{ config["Osc3SemitoneOffset"][0].as<float>(), config["Osc3SemitoneOffset"][1].as<float>(), config["Osc3SemitoneOffset"][2].as<float>() },
{ config["Osc3PitchOffset"][0].as<float>(), config["Osc3PitchOffset"][1].as<float>(), config["Osc3PitchOffset"][2].as<float>() },
}};
// 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);
params_->set(EnvelopeId::Osc1Volume, osc1VolumeProfile[0].val, osc1VolumeProfile[1].val, osc1VolumeProfile[2].val, osc1VolumeProfile[3].val, osc1VolumeProfile[4].val);
params_->set(EnvelopeId::FilterCutoff, fCutoffProfile[0].val, fCutoffProfile[1].val, fCutoffProfile[2].val, fCutoffProfile[3].val, fCutoffProfile[4].val);
params_->set(EnvelopeId::FilterResonance, fResonanceProfile[0].val, fResonanceProfile[1].val, fResonanceProfile[2].val, fResonanceProfile[3].val, fResonanceProfile[4].val);
// TODO: why do I bother passing in 5 values independently when I can just do an array like in loadEnvProfile ?
params_->set(ParamId::MasterOctaveOffset, masterPitchOffsets[0].def);
params_->set(ParamId::MasterSemitoneOffset, masterPitchOffsets[1].def);
params_->set(ParamId::MasterPitchOffset, masterPitchOffsets[2].def);
params_->set(ParamId::Osc1OctaveOffset, osc1PitchOffsets[0].def);
params_->set(ParamId::Osc1SemitoneOffset, osc1PitchOffsets[1].def);
params_->set(ParamId::Osc1PitchOffset, osc1PitchOffsets[2].def);
params_->set(ParamId::Osc2OctaveOffset, osc2PitchOffsets[0].def);
params_->set(ParamId::Osc2SemitoneOffset, osc2PitchOffsets[1].def);
params_->set(ParamId::Osc2PitchOffset, osc2PitchOffsets[2].def);
params_->set(ParamId::Osc3OctaveOffset, osc3PitchOffsets[0].def);
params_->set(ParamId::Osc3SemitoneOffset, osc3PitchOffsets[1].def);
params_->set(ParamId::Osc3PitchOffset, osc3PitchOffsets[2].def);
params_->set(ParamId::MasterOctaveOffset, masterPitchOffsets[0].val);
params_->set(ParamId::MasterSemitoneOffset, masterPitchOffsets[1].val);
params_->set(ParamId::MasterPitchOffset, masterPitchOffsets[2].val);
params_->set(ParamId::Osc1OctaveOffset, osc1PitchOffsets[0].val);
params_->set(ParamId::Osc1SemitoneOffset, osc1PitchOffsets[1].val);
params_->set(ParamId::Osc1PitchOffset, osc1PitchOffsets[2].val);
params_->set(ParamId::Osc2OctaveOffset, osc2PitchOffsets[0].val);
params_->set(ParamId::Osc2SemitoneOffset, osc2PitchOffsets[1].val);
params_->set(ParamId::Osc2PitchOffset, osc2PitchOffsets[2].val);
params_->set(ParamId::Osc3OctaveOffset, osc3PitchOffsets[0].val);
params_->set(ParamId::Osc3SemitoneOffset, osc3PitchOffsets[1].val);
params_->set(ParamId::Osc3PitchOffset, osc3PitchOffsets[2].val);
params_->set(ParamId::Osc1WaveSelector1, static_cast<float>(config["OscWaveSelector1"].as<int>()));
params_->set(ParamId::Osc1WaveSelector2, static_cast<float>(config["OscWaveSelector2"].as<int>()));
@@ -120,11 +120,11 @@ YAML::Node ConfigInterface::loadProfile(std::string filename) {
}
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) {
std::array<Param, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) {
YAML::Node envelopeNode = (*node)[profile];
std::array<ParamDefault, 5> paramProfile;
std::array<Param, 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>() };
@@ -133,7 +133,7 @@ std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, st
return paramProfile;
}
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(std::string filename, std::string profile) {
std::array<Param, 5> ConfigInterface::loadEnvProfile(std::string filename, std::string profile) {
std::string filepath = "config/profiles/" + filename + ".yaml";
filepath = std::filesystem::absolute(filepath).string();

View File

@@ -33,8 +33,8 @@ public:
int getValue(ConfigFile file, std::string key, int defaultVal);
YAML::Node 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);
std::array<Param, 5> loadEnvProfile(YAML::Node* node, std::string profile);
std::array<Param, 5> loadEnvProfile(std::string filename, std::string profile);
private:

View File

@@ -11,7 +11,9 @@ ParameterStore::ParameterStore() {
// set parameter value
void ParameterStore::set(ParamId id, float value) {
values_[static_cast<size_t>(id)].store(value, std::memory_order_relaxed);
Param oldParam = values_[static_cast<size_t>(id)].load(std::memory_order_relaxed);
Param updatedParam = { value, oldParam.min, oldParam.max };
values_[static_cast<size_t>(id)].store(updatedParam, std::memory_order_relaxed);
}
// set a whole envelope of parameters
@@ -27,5 +29,5 @@ void ParameterStore::set(EnvelopeId id, float depth, float a, float d, float s,
// get a single parameter
float ParameterStore::get(ParamId id) const {
return values_[static_cast<size_t>(id)].load(std::memory_order_relaxed);
return values_[static_cast<size_t>(id)].load(std::memory_order_relaxed).val;
}

View File

@@ -66,8 +66,8 @@ constexpr std::array<EnvelopeParam, static_cast<size_t>(EnvelopeId::Count)> ENV_
{ ParamId::FilterResonanceDepth, ParamId::FilterResonanceEnvA, ParamId::FilterResonanceEnvD, ParamId::FilterResonanceEnvS, ParamId::FilterResonanceEnvR }, // FilterResonance
}};
struct ParamDefault {
float def;
struct Param {
float val;
float min;
float max;
};
@@ -89,6 +89,6 @@ public:
private:
std::array<std::atomic<float>, PARAM_COUNT> values_;
std::array<std::atomic<Param>, PARAM_COUNT> values_;
};

View File

@@ -75,7 +75,7 @@ void EnvelopeGenerator::emitEnvelope() {
);
}
void EnvelopeGenerator::init(EnvelopeId id, std::array<ParamDefault, 5> profile) {
void EnvelopeGenerator::init(EnvelopeId id, std::array<Param, 5> profile) {
EnvelopeParam params = ENV_PARAMS[static_cast<size_t>(id)];
@@ -85,10 +85,10 @@ void EnvelopeGenerator::init(EnvelopeId id, std::array<ParamDefault, 5> profile)
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);
setDepth(profile[0].val);
setAttack(profile[1].val);
setDecay(profile[2].val);
setSustain(profile[3].val);
setRelease(profile[4].val);
}

View File

@@ -17,7 +17,7 @@ public:
~EnvelopeGenerator();
// connects signals, sets parameters to a provided profile
void init(EnvelopeId id, std::array<ParamDefault, 5> profile);
void init(EnvelopeId id, std::array<Param, 5> profile);
// setters
void setDepth(float v);