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:
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