NeuralAmpModelerPlugin

Plugin for Neural Amp Modeler
Log | Files | Refs | Submodules | README | LICENSE

commit d522019200d1e6d75b694751ac5b3c4eb23b1c3a
parent b321c9bb2cf6665e93ffe0bf8bf5133fe7560204
Author: Steven Atkinson <steven@atkinson.mn>
Date:   Sun, 29 Jan 2023 21:27:24 -0800

Support for single-file models (#50)

* Loading weights from JSON works.

This is a midpoitn commit where we hack `config.json` to be called
`model.nam` and get both to work. In the next commit, we move over to
picking the config directly and have a stopgap for old-style models.

* get_dsp takes path to the config file instead of the directory

* Load NAMs by picking a file instead of a directory

Inherits the much nicer file browser instead of the directory picker :)

* Error message telling users to upgrade directory-style models

* More careful loading of weights and better error messages

* A few more IR cases

* Legacy load button for Macs

* Cleaner model and IR load messages

* Fix up file SVG, add to VS resources

* Initialize mNAMPath and mNAMLegacyPath
Diffstat:
MNeuralAmpModeler/NeuralAmpModeler.cpp | 205++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
MNeuralAmpModeler/NeuralAmpModeler.h | 45++++++++++++++++++++++++++-------------------
MNeuralAmpModeler/config.h | 1+
MNeuralAmpModeler/dsp/cnpy.cpp | 10+++++++++-
MNeuralAmpModeler/dsp/dsp.h | 7++++---
MNeuralAmpModeler/dsp/get_dsp.cpp | 39+++++++++++++++++++++++++++++++--------
ANeuralAmpModeler/resources/img/file.svg | 39+++++++++++++++++++++++++++++++++++++++
MNeuralAmpModeler/resources/main.rc | 1+
8 files changed, 247 insertions(+), 100 deletions(-)

diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -65,7 +65,7 @@ public: const IVColorSpec activeColorSpec{ DEFAULT_BGCOLOR, // Background PluginColors::NAM_1, // Foreground - PluginColors::NAM_2.WithOpacity(0.4), // Pressed + PluginColors::NAM_2.WithOpacity(0.4f), // Pressed PluginColors::NAM_3, // Frame PluginColors::MOUSEOVER, // Highlight DEFAULT_SHCOLOR, // Shadow @@ -106,17 +106,18 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) : Plugin(info, MakeConfig(kNumParams, kNumPresets)), mInputPointers(nullptr), mOutputPointers(nullptr), - mDSP(nullptr), - mStagedDSP(nullptr), + mNAM(nullptr), + mStagedNAM(nullptr), mToneBass(), mToneMid(), mToneTreble(), mIR(), - mIRFileName(), + mNAMPath(), + mNAMLegacyPath(), mIRPath(), - mFlagRemoveDSP(false), + mFlagRemoveNAM(false), mFlagRemoveIR(false), - mDefaultModelString("Select model..."), + mDefaultNAMString("Select model..."), mDefaultIRString("Select IR...") { GetParam(kInputLevel)->InitGain("Input", 0.0, -20.0, 20.0, 0.1); @@ -142,6 +143,7 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) pGraphics->AttachPanelBackground(COLOR_BLACK); pGraphics->EnableMouseOver(true); auto helpSVG = pGraphics->LoadSVG(HELP_FN); + auto fileSVG = pGraphics->LoadSVG(FILE_FN); auto folderSVG = pGraphics->LoadSVG(FOLDER_FN); auto closeButtonSVG = pGraphics->LoadSVG(CLOSE_BUTTON_FN); pGraphics->LoadFont("Roboto-Regular", ROBOTO_FN); @@ -188,24 +190,36 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) .WithDrawFrame(false) .WithValueText({30, EAlign::Center, PluginColors::NAM_3}))); - // pGraphics->AttachControl(new IVBakedPresetManagerControl(modelArea, style.WithValueText({DEFAULT_TEXT_SIZE, EVAlign::Middle, COLOR_WHITE}))); - // Model loader button - auto loadModel = [&, pGraphics](IControl* pCaller) { - // TODO start from last directory on second load if possible. + auto loadNAM = [&, pGraphics](IControl* pCaller) { + WDL_String filename; + WDL_String path(this->mNAMPath.remove_filepart()); + pGraphics->PromptForFile(filename, path); + if (filename.GetLength()) { + // Sets mNAMPath and mStagedNAM + bool success = this->_GetNAM(filename); + // TODO error messages like the IR loader. + if (!success) + pGraphics->ShowMessageBox("Failed to load NAM model. If the model is an old \"directory-style\" model, it can be converted using the utility at https://github.com/sdatkinson/nam-model-utility", "Failed to load model!", kMB_OK); + } + }; +#if defined OS_MAC + // Legacy directory-based loader. + auto loadNAMLegacy = [&, pGraphics](IControl* pCaller) { WDL_String dir; pGraphics->PromptForDirectory(dir, [&](const WDL_String& fileName, const WDL_String& path){ if (path.GetLength()) - _GetDSP(path); + _GetNAMLegacy(path); }); }; - - auto getIRPath = [&, pGraphics](IControl* pCaller) { +#endif + // IR loader button + auto loadIR = [&, pGraphics](IControl* pCaller) { WDL_String fileName; - WDL_String path(this->mIRPath.Get()); + WDL_String path(this->mIRPath.remove_filepart()); pGraphics->PromptForFile(fileName, path); if (fileName.GetLength()) { - this->mIRPath = path; + this->mIRPath = fileName; const dsp::wav::LoadReturnCode retCode = this->_GetIR(fileName); if (retCode != dsp::wav::LoadReturnCode::SUCCESS) { std::stringstream message; @@ -241,33 +255,42 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) case (dsp::wav::LoadReturnCode::ERROR_NOT_MONO): message << "File is not mono."; break; + case (dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE): + message << "Unsupported bits per sample"; + break; case (dsp::wav::LoadReturnCode::ERROR_OTHER): message << "???"; break; + default: + message << "???"; + break; } pGraphics->ShowMessageBox(message.str().c_str(), "Failed to load IR!", kMB_OK); } } }; - // Model-clearing function - auto ClearModel = [&, pGraphics](IControl* pCaller) { - this->mFlagRemoveDSP = true; + auto ClearNAM = [&, pGraphics](IControl* pCaller) { + this->mFlagRemoveNAM = true; }; // IR-clearing function auto ClearIR = [&, pGraphics](IControl* pCaller) { this->mFlagRemoveIR = true; }; - // Graphics objects for what model is loaded + // Graphics objects for what NAM is loaded const float iconWidth = fileHeight; // Square icon pGraphics->AttachControl(new IVPanelControl(modelArea, "", style.WithColor(kFG, PluginColors::NAM_1))); - pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromLeft(iconWidth).GetPadded(-2.f), loadModel, folderSVG)); - pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromRight(iconWidth).GetPadded(-2.f), ClearModel, closeButtonSVG)); - pGraphics->AttachControl(new IVUpdateableLabelControl(modelArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultModelString.Get(), style.WithDrawFrame(false).WithValueText(style.valueText.WithVAlign(EVAlign::Middle))), kCtrlTagModelName); + pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromLeft(iconWidth).GetPadded(-2.f), loadNAM, fileSVG)); +#if defined OS_MAC + // Extra button for legacy model loading since Sandboxing prevent the Windows way of doing it. + pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromLeft(iconWidth).GetTranslated(iconWidth, 0.0f).GetPadded(-2.f), loadNAMLegacy, folderSVG)); +#endif + pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromRight(iconWidth).GetPadded(-2.f), ClearNAM, closeButtonSVG)); + pGraphics->AttachControl(new IVUpdateableLabelControl(modelArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultNAMString.Get(), style.WithDrawFrame(false).WithValueText(style.valueText.WithVAlign(EVAlign::Middle))), kCtrlTagModelName); // IR pGraphics->AttachControl(new IVPanelControl(irArea, "", style.WithColor(kFG, PluginColors::NAM_1))); - pGraphics->AttachControl(new IRolloverSVGButtonControl(irArea.GetFromLeft(iconWidth).GetPadded(-2.f), getIRPath, folderSVG)); + pGraphics->AttachControl(new IRolloverSVGButtonControl(irArea.GetFromLeft(iconWidth).GetPadded(-2.f), loadIR, fileSVG)); pGraphics->AttachControl(new IRolloverSVGButtonControl(irArea.GetFromRight(iconWidth).GetPadded(-2.f), ClearIR, closeButtonSVG)); pGraphics->AttachControl(new IVUpdateableLabelControl(irArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultIRString.Get(), style.WithDrawFrame(false).WithValueText(style.valueText.WithVAlign(EVAlign::Middle))), kCtrlTagIRName); @@ -334,7 +357,7 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) false, // draw frame // AttachFunc [](IContainerBase* pParent, const IRECT& r) { - pParent->AddChildControl(new IVPanelControl(IRECT(), "", style.WithColor(kFR, PluginColors::NAM_3.WithOpacity(0.1)).WithColor(kFG, PluginColors::NAM_1.WithOpacity(0.1)))); + pParent->AddChildControl(new IVPanelControl(IRECT(), "", style.WithColor(kFR, PluginColors::NAM_3.WithOpacity(0.1f)).WithColor(kFG, PluginColors::NAM_1.WithOpacity(0.1f)))); pParent->AddChildControl(new IVLabelControl(IRECT(), "Neural Amp Modeler", style .WithDrawFrame(false) @@ -379,13 +402,13 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp this->_ApplyDSPStaging(); const bool toneStackActive = this->GetParam(kEQActive)->Value() > 0; - if (mDSP != nullptr) + if (mNAM != nullptr) { // TODO remove input / output gains from here. const double inputGain = 1.0; const double outputGain = 1.0; - mDSP->process(this->mInputPointers, this->mOutputPointers, nChans, nFrames, inputGain, outputGain, mDSPParams); - mDSP->finalize_(nFrames); + mNAM->process(this->mInputPointers, this->mOutputPointers, nChans, nFrames, inputGain, outputGain, mNAMParams); + mNAM->finalize_(nFrames); } else { this->_FallbackDSP(nFrames); @@ -440,33 +463,39 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp bool NeuralAmpModeler::SerializeState(IByteChunk& chunk) const { // Model directory (don't serialize the model itself; we'll just load it again when we unserialize) - chunk.PutStr(mModelPath.Get()); - chunk.PutStr(this->mIRFileName.Get()); + chunk.PutStr(this->mNAMPath.Get()); + chunk.PutStr(this->mNAMLegacyPath.Get()); + chunk.PutStr(this->mIRPath.Get()); return SerializeParams(chunk); } int NeuralAmpModeler::UnserializeState(const IByteChunk& chunk, int startPos) { WDL_String dir; - startPos = chunk.GetStr(mModelPath, startPos); - startPos = chunk.GetStr(this->mIRFileName, startPos); - this->mDSP = nullptr; + startPos = chunk.GetStr(this->mNAMPath, startPos); + startPos = chunk.GetStr(this->mNAMLegacyPath, startPos); + startPos = chunk.GetStr(this->mIRPath, startPos); + this->mNAM = nullptr; this->mIR = nullptr; int retcode = UnserializeParams(chunk, startPos); - if (this->mModelPath.GetLength()) - this->_GetDSP(this->mModelPath); - if (this->mIRFileName.GetLength()) - this->_GetIR(this->mIRFileName); + if (this->mNAMLegacyPath.GetLength()) + this->_GetNAMLegacy(this->mNAMLegacyPath); + if (this->mNAMPath.GetLength()) + this->_GetNAM(this->mNAMPath); + if (this->mIRPath.GetLength()) + this->_GetIR(this->mIRPath); return retcode; } void NeuralAmpModeler::OnUIOpen() { Plugin::OnUIOpen(); - if (this->mModelPath.GetLength()) - this->_SetModelMsg(this->mModelPath); - if (this->mIRFileName.GetLength()) - this->_SetIRMsg(this->mIRFileName); + if (this->mNAMLegacyPath.GetLength()) + this->_SetModelMsg(this->mNAMLegacyPath); + if (this->mNAMPath.GetLength()) + this->_SetModelMsg(this->mNAMPath); + if (this->mIRPath.GetLength()) + this->_SetIRMsg(this->mIRPath); } // Private methods ============================================================ @@ -474,26 +503,27 @@ void NeuralAmpModeler::OnUIOpen() void NeuralAmpModeler::_ApplyDSPStaging() { // Move things from staged to live - if (this->mStagedDSP != nullptr) + if (this->mStagedNAM != nullptr) { // Move from staged to active DSP - this->mDSP = std::move(this->mStagedDSP); - this->mStagedDSP = nullptr; + this->mNAM = std::move(this->mStagedNAM); + this->mStagedNAM = nullptr; } if (this->mStagedIR != nullptr) { this->mIR = std::move(this->mStagedIR); this->mStagedIR = nullptr; } // Remove marked modules - if (this->mFlagRemoveDSP) { - this->mDSP = nullptr; - this->mModelPath.Set(""); + if (this->mFlagRemoveNAM) { + this->mNAM = nullptr; + this->mNAMPath.Set(""); + this->mNAMLegacyPath.Set(""); this->_UnsetModelMsg(); - this->mFlagRemoveDSP = false; + this->mFlagRemoveNAM = false; } if (this->mFlagRemoveIR) { this->mIR = nullptr; - this->mIRFileName.Set(""); + this->mIRPath.Set(""); this->_UnsetIRMsg(); this->mFlagRemoveIR = false; } @@ -507,37 +537,69 @@ void NeuralAmpModeler::_FallbackDSP(const int nFrames) this->mOutputArray[c][s] = this->mInputArray[c][s]; } -void NeuralAmpModeler::_GetDSP(const WDL_String& modelPath) +bool NeuralAmpModeler::_GetNAM(const WDL_String& modelPath) { - WDL_String previousModelPath; + WDL_String previousNAMPath = this->mNAMPath; + WDL_String previousNAMLegacyPath = this->mNAMLegacyPath; try { - previousModelPath = mModelPath; auto dspPath = std::filesystem::path(modelPath.Get()); - mStagedDSP = get_dsp(dspPath); + mStagedNAM = get_dsp(dspPath); this->_SetModelMsg(modelPath); + this->mNAMPath = modelPath; + this->mNAMLegacyPath.Set(""); } catch (std::exception& e) { std::stringstream ss; ss << "FAILED to load model"; SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); - if (mStagedDSP != nullptr) { - mStagedDSP = nullptr; + if (this->mStagedNAM != nullptr) { + this->mStagedNAM = nullptr; } - mModelPath = previousModelPath; + this->mNAMPath = previousNAMPath; + this->mNAMLegacyPath = previousNAMLegacyPath; std::cerr << "Failed to read DSP module" << std::endl; std::cerr << e.what() << std::endl; + return false; } + return true; } -dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irFileName) +bool NeuralAmpModeler::_GetNAMLegacy(const WDL_String& modelDir) { - WDL_String previousIRFileName; + WDL_String previousNAMLegacyPath = this->mNAMLegacyPath; + WDL_String previousNAMPath = this->mNAMPath; + try { + auto dspPath = std::filesystem::path(modelDir.Get()); + mStagedNAM = get_dsp_legacy(dspPath); + this->_SetModelMsg(modelDir); + this->mNAMLegacyPath = modelDir; + this->mNAMPath.Set(""); + } + catch (std::exception& e) { + std::stringstream ss; + ss << "FAILED to load legacy model"; + SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); + if (mStagedNAM != nullptr) { + mStagedNAM = nullptr; + } + this->mNAMLegacyPath = previousNAMLegacyPath; + this->mNAMPath = previousNAMLegacyPath; + std::cerr << "Failed to read DSP module" << std::endl; + std::cerr << e.what() << std::endl; + return false; + } + return true; +} - previousIRFileName = this->mIRFileName; +dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irPath) +{ + // FIXME it'd be better for the path to be "staged" as well. Just in case the + // path and the model got caught on opposite sides of the fence... + WDL_String previousIRPath = this->mIRPath; const double sampleRate = this->GetSampleRate(); dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER; try { - this->mStagedIR = std::make_unique<dsp::ImpulseResponse>(irFileName, sampleRate); + this->mStagedIR = std::make_unique<dsp::ImpulseResponse>(irPath, sampleRate); wavState = this->mStagedIR->GetWavState(); } catch (std::exception& e) { @@ -546,13 +608,15 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irFileName) std::cerr << e.what() << std::endl; } - if (wavState == dsp::wav::LoadReturnCode::SUCCESS) - this->_SetIRMsg(irFileName); + if (wavState == dsp::wav::LoadReturnCode::SUCCESS) { + this->_SetIRMsg(irPath); + this->mIRPath = irPath; + } else { if (this->mStagedIR != nullptr) { this->mStagedIR = nullptr; } - this->mIRFileName = previousIRFileName; + this->mIRPath = previousIRPath; std::stringstream ss; ss << "FAILED to load IR"; SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); @@ -636,24 +700,27 @@ void NeuralAmpModeler::_ProcessOutput(iplug::sample** inputs, iplug::sample **ou void NeuralAmpModeler::_SetModelMsg(const WDL_String& modelPath) { auto dspPath = std::filesystem::path(modelPath.Get()); - mModelPath = modelPath; std::stringstream ss; - ss << "Loaded " << dspPath.parent_path().filename(); + ss << "Loaded "; + if (dspPath.has_filename()) + ss << dspPath.filename().stem(); // /path/to/model.nam -> "model" + else + ss << dspPath.parent_path().filename(); // /path/to/model/ -> "model" SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); } -void NeuralAmpModeler::_SetIRMsg(const WDL_String& irFileName) +void NeuralAmpModeler::_SetIRMsg(const WDL_String& irPath) { - this->mIRFileName = irFileName; - auto dspPath = std::filesystem::path(irFileName.Get()); + this->mIRPath = irPath; // This might already be done elsewhere...need to dedup. + auto dspPath = std::filesystem::path(irPath.Get()); std::stringstream ss; - ss << "Loaded " << dspPath.filename(); + ss << "Loaded " << dspPath.filename().stem(); SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); } void NeuralAmpModeler::_UnsetModelMsg() { - this->_UnsetMsg(kCtrlTagModelName, this->mDefaultModelString); + this->_UnsetMsg(kCtrlTagModelName, this->mDefaultNAMString); } void NeuralAmpModeler::_UnsetIRMsg() diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h @@ -67,14 +67,19 @@ private: // Sizes based on mInputArray size_t _GetBufferNumChannels() const; size_t _GetBufferNumFrames() const; - // Gets a new DSP object and stores it to mStagedDSP - void _GetDSP(const WDL_String& dspPath); - // Gets the IR and stores to mStagedIR - dsp::wav::LoadReturnCode _GetIR(const WDL_String& irFileName); + // Gets a new Neural Amp Model object and stores it to mStagedNAM + // Returns a bool for whether the operation was successful. + bool _GetNAM(const WDL_String& dspFile); + // Legacy load from directory containing "config.json" and "weights.npy" + bool _GetNAMLegacy(const WDL_String& dspDirectory); + // Gets the IR and stores to mStagedIR. + // Return status code so that error messages can be relayed if + // it wasn't successful. + dsp::wav::LoadReturnCode _GetIR(const WDL_String& irPath); // Update the message about which model is loaded. void _SetModelMsg(const WDL_String& dspPath); bool _HaveModel() const { - return this->mDSP != nullptr; + return this->mNAM != nullptr; }; // Prepare the input & output buffers void _PrepareBuffers(const int nFrames); @@ -85,7 +90,7 @@ private: // Copy the output to the output buffer, applying output level. void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const int nFrames); // Update the text in the IR area to say what's loaded. - void _SetIRMsg(const WDL_String& modelPath); + void _SetIRMsg(const WDL_String& irPath); void _UnsetModelMsg(); void _UnsetIRMsg(); void _UnsetMsg(const int tag, const WDL_String& msg); @@ -96,24 +101,25 @@ private: // Member data - // Input arrays + // Input arrays to NAM std::vector<std::vector<iplug::sample>> mInputArray; - // Output arrays + // Output from NAM std::vector<std::vector<iplug::sample>> mOutputArray; // Pointer versions iplug::sample** mInputPointers; iplug::sample** mOutputPointers; - // The DSPs actually being used: - std::unique_ptr<DSP> mDSP; + // The Neural Amp Model (NAM) actually being used: + std::unique_ptr<DSP> mNAM; + // And the IR std::unique_ptr<dsp::ImpulseResponse> mIR; // Manages switching what DSP is being used. - std::unique_ptr<DSP> mStagedDSP; + std::unique_ptr<DSP> mStagedNAM; std::unique_ptr<dsp::ImpulseResponse> mStagedIR; // Flags to take away the modules at a safe time. - bool mFlagRemoveDSP; + bool mFlagRemoveNAM; bool mFlagRemoveIR; - const WDL_String mDefaultModelString; + const WDL_String mDefaultNAMString; const WDL_String mDefaultIRString; // Tone stack modules @@ -121,13 +127,14 @@ private: recursive_linear_filter::Peaking mToneMid; recursive_linear_filter::HighShelf mToneTreble; - // Paths to model and IR - WDL_String mModelPath; - WDL_String mIRFileName; - // Directory containing the file (for better file browsing on second load-in) - WDL_String mIRPath; // Do we need this, or can we manipualte mITFileName (".dirname") + // Path to model's config.json or model.nam + WDL_String mNAMPath; + // Legacy + WDL_String mNAMLegacyPath; + // Path to IR (.wav file) + WDL_String mIRPath; - std::unordered_map<std::string, double> mDSPParams = { + std::unordered_map<std::string, double> mNAMParams = { { "Input", 0.0 }, { "Output", 0.0 } }; diff --git a/NeuralAmpModeler/config.h b/NeuralAmpModeler/config.h @@ -56,6 +56,7 @@ #define ROBOTO_FN "Roboto-Regular.ttf" #define HELP_FN "help.svg" +#define FILE_FN "file.svg" #define FOLDER_FN "folder.svg" #define CLOSE_BUTTON_FN "close-button.svg" #define TOLEX_FN "tolex.jpeg" diff --git a/NeuralAmpModeler/dsp/cnpy.cpp b/NeuralAmpModeler/dsp/cnpy.cpp @@ -3,6 +3,7 @@ //license available in LICENSE file, or at http://www.opensource.org/licenses/mit-license.php #include"cnpy.h" +#include<cerrno> #include<complex> #include<cstdlib> #include<algorithm> @@ -10,6 +11,7 @@ #include<iomanip> #include<stdint.h> #include<stdexcept> +#include<iostream> #include <regex> char cnpy::BigEndianTest() { @@ -328,7 +330,13 @@ cnpy::NpyArray cnpy::npy_load(std::string fname) { FILE* fp = fopen(fname.c_str(), "rb"); - if(!fp) throw std::runtime_error("npy_load: Unable to open file "+fname); + if (!fp) { + std::stringstream ss; + ss << "npy_load: Unable to open file " << fname << ":\n" + << std::strerror(errno) << std::endl; + + throw std::runtime_error(ss.str()); + } NpyArray arr = load_the_npy_file(fp); diff --git a/NeuralAmpModeler/dsp/dsp.h b/NeuralAmpModeler/dsp/dsp.h @@ -365,9 +365,10 @@ namespace convnet { // this plugin version. void verify_config_version(const std::string version); -// Takes the directory, finds the required files, and uses them to instantiate -// an instance of DSP. -std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname); +// Takes the model file and uses it to instantiate an instance of DSP. +std::unique_ptr<DSP> get_dsp(const std::filesystem::path model_file); +// Legacy loader for directory-type DSPs +std::unique_ptr<DSP> get_dsp_legacy(const std::filesystem::path dirname); // Hard-coded model: std::unique_ptr<DSP> get_hard_dsp(); diff --git a/NeuralAmpModeler/dsp/get_dsp.cpp b/NeuralAmpModeler/dsp/get_dsp.cpp @@ -11,14 +11,41 @@ void verify_config_version(const std::string version) { - const std::unordered_set<std::string> supported_versions({"0.2.0", "0.2.1", "0.3.0", "0.3.1", "0.4.0"}); + const std::unordered_set<std::string> supported_versions({"0.2.0", "0.2.1", "0.3.0", "0.3.1", "0.4.0", "0.5.0"}); if (supported_versions.find(version) == supported_versions.end()) throw std::runtime_error("Unsupported config version"); } -std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname) +std::vector<float> _get_weights( + nlohmann::json const& j, + const std::filesystem::path config_path) +{ + if (j.find("weights") != j.end()) { // New-style model + auto weight_list = j["weights"]; + std::vector<float> weights; + for (auto it = weight_list.begin(); it != weight_list.end(); ++it) + weights.push_back(*it); + return weights; + } + else { // Old-style config.json + weights.npy + std::filesystem::path weights_path = config_path.parent_path() / std::filesystem::path("weights.npy"); + if (!std::filesystem::exists(weights_path)) { + std::stringstream s; + s << "No weights in model file, and could not find accompanying weights at expected location " << weights_path; + throw std::runtime_error(s.str()); + } + return numpy_util::load_to_vector(weights_path); + } +} + +std::unique_ptr<DSP> get_dsp_legacy(const std::filesystem::path model_dir) +{ + auto config_filename = model_dir / std::filesystem::path("config.json"); + return get_dsp(config_filename); +} + +std::unique_ptr<DSP> get_dsp(const std::filesystem::path config_filename) { - const std::filesystem::path config_filename = dirname / std::filesystem::path("config.json"); if (!std::filesystem::exists(config_filename)) throw std::runtime_error("Config JSON doesn't exist!\n"); std::ifstream i(config_filename); @@ -28,12 +55,12 @@ std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname) auto architecture = j["architecture"]; nlohmann::json config = j["config"]; + std::vector<float> params = _get_weights(j, config_filename); if (architecture == "Linear") { const int receptive_field = config["receptive_field"]; const bool _bias = config["bias"]; - std::vector<float> params = numpy_util::load_to_vector(dirname / std::filesystem::path("weights.npy")); return std::make_unique<Linear>(receptive_field, _bias, params); } else if (architecture == "ConvNet") @@ -44,7 +71,6 @@ std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname) for (int i = 0; i < config["dilations"].size(); i++) dilations.push_back(config["dilations"][i]); const std::string activation = config["activation"]; - std::vector<float> params = numpy_util::load_to_vector(dirname / std::filesystem::path("weights.npy")); return std::make_unique<convnet::ConvNet>(channels, dilations, batchnorm, activation, params); } else if (architecture == "LSTM") @@ -52,7 +78,6 @@ std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname) const int num_layers = config["num_layers"]; const int input_size = config["input_size"]; const int hidden_size = config["hidden_size"]; - std::vector<float> params = numpy_util::load_to_vector(dirname / std::filesystem::path("weights.npy")); auto json = nlohmann::json {}; return std::make_unique<lstm::LSTM>(num_layers, input_size, hidden_size, params, json); } @@ -61,7 +86,6 @@ std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname) const int num_layers = config["num_layers"]; const int input_size = config["input_size"]; const int hidden_size = config["hidden_size"]; - std::vector<float> params = numpy_util::load_to_vector(dirname / std::filesystem::path("weights.npy")); return std::make_unique<lstm::LSTM>(num_layers, input_size, hidden_size, params, config["parametric"]); } else if (architecture == "WaveNet" || architecture == "CatWaveNet") @@ -88,7 +112,6 @@ std::unique_ptr<DSP> get_dsp(const std::filesystem::path dirname) } const bool with_head = config["head"] == NULL; const float head_scale = config["head_scale"]; - std::vector<float> params = numpy_util::load_to_vector(dirname / std::filesystem::path("weights.npy")); // Solves compilation issue on macOS Error: No matching constructor for initialization of 'wavenet::WaveNet' // Solution from https://stackoverflow.com/a/73956681/3768284 auto parametric_json = architecture == "CatWaveNet" ? config["parametric"] : nlohmann::json{}; diff --git a/NeuralAmpModeler/resources/img/file.svg b/NeuralAmpModeler/resources/img/file.svg @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="24" + height="24" + viewBox="0 0 24 24" + version="1.1" + id="svg141" + sodipodi:docname="file.svg" + inkscape:version="1.2.2 (732a01da63, 2022-12-09)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs145" /> + <sodipodi:namedview + id="namedview143" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="42.791667" + inkscape:cx="12.12853" + inkscape:cy="12.011685" + inkscape:window-width="2550" + inkscape:window-height="1443" + inkscape:window-x="168" + inkscape:window-y="370" + inkscape:window-maximized="0" + inkscape:current-layer="svg141" /> + <path + d="m 13.842843,5.9454723 v 3.0389483 h 3.038949 V 18.101266 H 7.1571568 V 5.9454723 Z m 0.60779,-1.2155794 H 5.9415774 V 19.316845 H 18.097371 V 8.376631 Z" + id="path139" + style="fill:#ffffff;stroke-width:0.607789" /> +</svg> diff --git a/NeuralAmpModeler/resources/main.rc b/NeuralAmpModeler/resources/main.rc @@ -232,6 +232,7 @@ END ROBOTO_FN TTF ROBOTO_FN TOLEX_FN JPEG TOLEX_FN TOLEX2X_FN JPEG TOLEX2X_FN +FILE_FN SVG FILE_FN FOLDER_FN SVG FOLDER_FN CLOSE_BUTTON_FN SVG CLOSE_BUTTON_FN HELP_FN SVG HELP_FN