From e2f5cb13e06081e862988b8049332d6cc484dff8 Mon Sep 17 00:00:00 2001 From: Bliblank Date: Sun, 8 Feb 2026 14:11:16 -0600 Subject: [PATCH] refactor parameterstore --- .gitignore | 2 +- README.md | 7 +-- .../example_wavetable.cpython-311.pyc | Bin 354 -> 0 bytes scripts/build.ps1 | 2 + scripts/example_wavetable.py | 16 ++++-- scripts/generate_wavetable.py | 22 ++++---- src/ConfigInterface.cpp | 50 +++++++++--------- src/ConfigInterface.h | 4 +- src/ParameterStore.cpp | 6 ++- src/ParameterStore.h | 6 +-- .../EnvelopeGenerator/EnvelopeGenerator.cpp | 12 ++--- .../EnvelopeGenerator/EnvelopeGenerator.h | 2 +- 12 files changed, 66 insertions(+), 63 deletions(-) delete mode 100644 scripts/__pycache__/example_wavetable.cpython-311.pyc diff --git a/.gitignore b/.gitignore index b29571d..e375dad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ build/* .vscode/* -scripts/_pycache_/* \ No newline at end of file +scripts/__pycache__/* \ No newline at end of file diff --git a/README.md b/README.md index 1eebec7..ecbc8d3 100644 --- a/README.md +++ b/README.md @@ -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. \ diff --git a/scripts/__pycache__/example_wavetable.cpython-311.pyc b/scripts/__pycache__/example_wavetable.cpython-311.pyc deleted file mode 100644 index 467c7f9ec231b035040b8c308574fd1ed7b544af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354 zcmZ3^%ge<81o@rqnYuvwF^B^Lj8MjB9w1{nLkdF+Lli>_V=#jzQ!-2qh-QZ3&niI1 zbcPa`J|HiJaT$=e8ZN>JGzFyJuZll2S0Sw^KUblkC_g#1xLA|%7HdIKW?sokhR;Ba z48I)pGxBp&^^+2dQuJN&lS^|`^Gb^Ki&6{ni}iC;OA?b3b4rW#i<65o3xMLO6^Xe8 zIjQmGiDe+UoK(Gn%3JJEvy0e)1~LJ0u{e-uV7SY|d4Ws%B8$uw7MTk$RKyCD^V4Jm zD=uOIa=;=*Y(Um84x8Nkl+v73yCM!C7v!B{IUw<0!+0<0eZjbTkK diff --git a/scripts/build.ps1 b/scripts/build.ps1 index aa98e78..f0102f0 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -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..." diff --git a/scripts/example_wavetable.py b/scripts/example_wavetable.py index f08838b..c5bad20 100644 --- a/scripts/example_wavetable.py +++ b/scripts/example_wavetable.py @@ -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() diff --git a/scripts/generate_wavetable.py b/scripts/generate_wavetable.py index 28dc616..a3f55f3 100644 --- a/scripts/generate_wavetable.py +++ b/scripts/generate_wavetable.py @@ -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() + \ No newline at end of file diff --git a/src/ConfigInterface.cpp b/src/ConfigInterface.cpp index da134af..84e39e4 100644 --- a/src/ConfigInterface.cpp +++ b/src/ConfigInterface.cpp @@ -70,48 +70,48 @@ YAML::Node ConfigInterface::loadProfile(std::string filename) { } // extract values from the config file - std::array osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume"); - std::array fCutoffProfile = loadEnvProfile(&config, "FilterCutoff"); - std::array fResonanceProfile = loadEnvProfile(&config, "FilterResonance"); + std::array osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume"); + std::array fCutoffProfile = loadEnvProfile(&config, "FilterCutoff"); + std::array fResonanceProfile = loadEnvProfile(&config, "FilterResonance"); - std::array masterPitchOffsets = {{ + std::array masterPitchOffsets = {{ { config["MasterOctaveOffset"][0].as(), config["MasterOctaveOffset"][1].as(), config["MasterOctaveOffset"][2].as() }, { config["MasterSemitoneOffset"][0].as(), config["MasterSemitoneOffset"][1].as(), config["MasterSemitoneOffset"][2].as() }, { config["MasterPitchOffset"][0].as(), config["MasterPitchOffset"][1].as(), config["MasterPitchOffset"][2].as() }, }}; - std::array osc1PitchOffsets = {{ + std::array osc1PitchOffsets = {{ { config["Osc1OctaveOffset"][0].as(), config["Osc1OctaveOffset"][1].as(), config["Osc1OctaveOffset"][2].as() }, { config["Osc1SemitoneOffset"][0].as(), config["Osc1SemitoneOffset"][1].as(), config["Osc1SemitoneOffset"][2].as() }, { config["Osc1PitchOffset"][0].as(), config["Osc1PitchOffset"][1].as(), config["Osc1PitchOffset"][2].as() }, }}; - std::array osc2PitchOffsets = {{ + std::array osc2PitchOffsets = {{ { config["Osc2OctaveOffset"][0].as(), config["Osc2OctaveOffset"][1].as(), config["Osc2OctaveOffset"][2].as() }, { config["Osc2SemitoneOffset"][0].as(), config["Osc2SemitoneOffset"][1].as(), config["Osc2SemitoneOffset"][2].as() }, { config["Osc2PitchOffset"][0].as(), config["Osc2PitchOffset"][1].as(), config["Osc2PitchOffset"][2].as() }, }}; - std::array osc3PitchOffsets = {{ + std::array osc3PitchOffsets = {{ { config["Osc3OctaveOffset"][0].as(), config["Osc3OctaveOffset"][1].as(), config["Osc3OctaveOffset"][2].as() }, { config["Osc3SemitoneOffset"][0].as(), config["Osc3SemitoneOffset"][1].as(), config["Osc3SemitoneOffset"][2].as() }, { config["Osc3PitchOffset"][0].as(), config["Osc3PitchOffset"][1].as(), config["Osc3PitchOffset"][2].as() }, }}; // 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(config["OscWaveSelector1"].as())); params_->set(ParamId::Osc1WaveSelector2, static_cast(config["OscWaveSelector2"].as())); @@ -120,11 +120,11 @@ YAML::Node ConfigInterface::loadProfile(std::string filename) { } -std::array ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) { +std::array ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) { YAML::Node envelopeNode = (*node)[profile]; - std::array paramProfile; + std::array paramProfile; for(int i = 0; i < paramProfile.size(); i++) { paramProfile[i] = { envelopeNode[i][0].as(), envelopeNode[i][1].as(), envelopeNode[i][2].as() }; @@ -133,7 +133,7 @@ std::array ConfigInterface::loadEnvProfile(YAML::Node* node, st return paramProfile; } -std::array ConfigInterface::loadEnvProfile(std::string filename, std::string profile) { +std::array ConfigInterface::loadEnvProfile(std::string filename, std::string profile) { std::string filepath = "config/profiles/" + filename + ".yaml"; filepath = std::filesystem::absolute(filepath).string(); diff --git a/src/ConfigInterface.h b/src/ConfigInterface.h index bbf3280..2a9f91c 100644 --- a/src/ConfigInterface.h +++ b/src/ConfigInterface.h @@ -33,8 +33,8 @@ public: int getValue(ConfigFile file, std::string key, int defaultVal); YAML::Node loadProfile(std::string filename); - std::array loadEnvProfile(YAML::Node* node, std::string profile); - std::array loadEnvProfile(std::string filename, std::string profile); + std::array loadEnvProfile(YAML::Node* node, std::string profile); + std::array loadEnvProfile(std::string filename, std::string profile); private: diff --git a/src/ParameterStore.cpp b/src/ParameterStore.cpp index d27d1c0..3c124ce 100644 --- a/src/ParameterStore.cpp +++ b/src/ParameterStore.cpp @@ -11,7 +11,9 @@ ParameterStore::ParameterStore() { // set parameter value void ParameterStore::set(ParamId id, float value) { - values_[static_cast(id)].store(value, std::memory_order_relaxed); + Param oldParam = values_[static_cast(id)].load(std::memory_order_relaxed); + Param updatedParam = { value, oldParam.min, oldParam.max }; + values_[static_cast(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(id)].load(std::memory_order_relaxed); + return values_[static_cast(id)].load(std::memory_order_relaxed).val; } diff --git a/src/ParameterStore.h b/src/ParameterStore.h index b75ed4f..de85b01 100644 --- a/src/ParameterStore.h +++ b/src/ParameterStore.h @@ -66,8 +66,8 @@ constexpr std::array(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, PARAM_COUNT> values_; + std::array, PARAM_COUNT> values_; }; diff --git a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp index 2935cb9..5cc2994 100644 --- a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp +++ b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp @@ -75,7 +75,7 @@ void EnvelopeGenerator::emitEnvelope() { ); } -void EnvelopeGenerator::init(EnvelopeId id, std::array profile) { +void EnvelopeGenerator::init(EnvelopeId id, std::array profile) { EnvelopeParam params = ENV_PARAMS[static_cast(id)]; @@ -85,10 +85,10 @@ void EnvelopeGenerator::init(EnvelopeId id, std::array 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); } diff --git a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h index 1f9af26..230eea5 100644 --- a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h +++ b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h @@ -17,7 +17,7 @@ public: ~EnvelopeGenerator(); // connects signals, sets parameters to a provided profile - void init(EnvelopeId id, std::array profile); + void init(EnvelopeId id, std::array profile); // setters void setDepth(float v);