Skip to content

Commit 367a93c

Browse files
committed
feat: Add embedded NAM model and IR file support (PR sdatkinson#595) - Store NAM model and IR file contents in plugin state for full preset recall - Add right-click context menu to save embedded data back to disk - Support loading embedded data from serialized state - Maintain backward compatibility with existing presets Changes in submodules: - AudioDSPTools: Add IR memory loading and WAV export - NeuralAmpModelerCore: Add string-based NAM model loading
1 parent 512f5c6 commit 367a93c

6 files changed

Lines changed: 407 additions & 13 deletions

File tree

NeuralAmpModeler/NeuralAmpModeler.cpp

Lines changed: 186 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <algorithm> // std::clamp, std::min
22
#include <cmath> // pow
33
#include <filesystem>
4+
#include <fstream>
45
#include <iostream>
56
#include <utility>
67

@@ -226,13 +227,13 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
226227
// Getting started page listing additional resources
227228
const char* const getUrl = "https://www.neuralampmodeler.com/users#comp-marb84o5";
228229
pGraphics->AttachControl(
229-
new NAMFileBrowserControl(modelArea, kMsgTagClearModel, defaultNamFileString.c_str(), "nam",
230+
new NAMFileBrowserControl(modelArea, kMsgTagClearModel, kMsgTagSaveEmbeddedModel, defaultNamFileString.c_str(), "nam",
230231
loadModelCompletionHandler, style, fileSVG, crossSVG, leftArrowSVG, rightArrowSVG,
231232
fileBackgroundBitmap, globeSVG, "Get NAM Models", getUrl),
232233
kCtrlTagModelFileBrowser);
233234
pGraphics->AttachControl(new ISVGSwitchControl(irSwitchArea, {irIconOffSVG, irIconOnSVG}, kIRToggle));
234235
pGraphics->AttachControl(
235-
new NAMFileBrowserControl(irArea, kMsgTagClearIR, defaultIRString.c_str(), "wav", loadIRCompletionHandler, style,
236+
new NAMFileBrowserControl(irArea, kMsgTagClearIR, kMsgTagSaveEmbeddedIR, defaultIRString.c_str(), "wav", loadIRCompletionHandler, style,
236237
fileSVG, crossSVG, leftArrowSVG, rightArrowSVG, fileBackgroundBitmap, globeSVG,
237238
"Get IRs", getUrl),
238239
kCtrlTagIRFileBrowser);
@@ -417,6 +418,24 @@ bool NeuralAmpModeler::SerializeState(IByteChunk& chunk) const
417418
// when we unserialize)
418419
chunk.PutStr(mNAMPath.Get());
419420
chunk.PutStr(mIRPath.Get());
421+
422+
// Embedded data (v0.7.13+)
423+
// Format: NAMDataSize (int), [NAMData bytes if size>0], IRDataSize (int), [IRData bytes if size>0]
424+
// This format is compatible with the original embed PR
425+
int namDataSize = static_cast<int>(mEmbeddedNAMData.size());
426+
chunk.Put(&namDataSize);
427+
if (namDataSize > 0)
428+
{
429+
chunk.PutBytes(mEmbeddedNAMData.data(), namDataSize);
430+
}
431+
432+
int irDataSize = static_cast<int>(mEmbeddedIRData.size());
433+
chunk.Put(&irDataSize);
434+
if (irDataSize > 0)
435+
{
436+
chunk.PutBytes(mEmbeddedIRData.data(), irDataSize);
437+
}
438+
420439
return SerializeParams(chunk);
421440
}
422441

@@ -507,6 +526,50 @@ bool NeuralAmpModeler::OnMessage(int msgTag, int ctrlTag, int dataSize, const vo
507526
{
508527
case kMsgTagClearModel: mShouldRemoveModel = true; return true;
509528
case kMsgTagClearIR: mShouldRemoveIR = true; return true;
529+
case kMsgTagSaveEmbeddedModel:
530+
{
531+
if (!mEmbeddedNAMData.empty() && GetUI())
532+
{
533+
WDL_String originalFileName(mNAMPath.get_filepart());
534+
GetUI()->PromptForDirectory(WDL_String(),
535+
[this, originalFileName](const WDL_String& fileName, const WDL_String& path) {
536+
if (path.GetLength())
537+
{
538+
WDL_String fullPath(path);
539+
fullPath.Append(originalFileName.Get());
540+
std::ofstream file(fullPath.Get());
541+
if (file.is_open())
542+
{
543+
file << mEmbeddedNAMData;
544+
file.close();
545+
}
546+
}
547+
});
548+
}
549+
return true;
550+
}
551+
case kMsgTagSaveEmbeddedIR:
552+
{
553+
if (!mEmbeddedIRData.empty() && GetUI())
554+
{
555+
WDL_String originalFileName(mIRPath.get_filepart());
556+
GetUI()->PromptForDirectory(WDL_String(),
557+
[this, originalFileName](const WDL_String& fileName, const WDL_String& path) {
558+
if (path.GetLength())
559+
{
560+
WDL_String fullPath(path);
561+
fullPath.Append(originalFileName.Get());
562+
std::ofstream file(fullPath.Get(), std::ios::binary);
563+
if (file.is_open())
564+
{
565+
file.write(reinterpret_cast<const char*>(mEmbeddedIRData.data()), mEmbeddedIRData.size());
566+
file.close();
567+
}
568+
}
569+
});
570+
}
571+
return true;
572+
}
510573
case kMsgTagHighlightColor:
511574
{
512575
mHighLightColor.Set((const char*)pData);
@@ -691,16 +754,48 @@ std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath)
691754
WDL_String previousNAMPath = mNAMPath;
692755
try
693756
{
757+
#ifdef _WIN32
758+
OutputDebugStringA("NAM _StageModel: Attempting to load: ");
759+
OutputDebugStringA(modelPath.Get());
760+
OutputDebugStringA("\n");
761+
#endif
694762
auto dspPath = std::filesystem::u8path(modelPath.Get());
763+
#ifdef _WIN32
764+
char msg[200];
765+
sprintf(msg, "NAM _StageModel: File exists = %d\n", std::filesystem::exists(dspPath) ? 1 : 0);
766+
OutputDebugStringA(msg);
767+
#endif
695768
std::unique_ptr<nam::DSP> model = nam::get_dsp(dspPath);
696769
std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move(model), GetSampleRate());
697770
temp->Reset(GetSampleRate(), GetBlockSize());
698771
mStagedModel = std::move(temp);
699772
mNAMPath = modelPath;
773+
774+
// Read file content for embedding in session
775+
mEmbeddedNAMData.clear();
776+
std::ifstream file(dspPath, std::ios::binary | std::ios::ate);
777+
if (file.is_open())
778+
{
779+
std::streamsize size = file.tellg();
780+
file.seekg(0, std::ios::beg);
781+
mEmbeddedNAMData.resize(static_cast<size_t>(size));
782+
file.read(reinterpret_cast<char*>(mEmbeddedNAMData.data()), size);
783+
}
784+
700785
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, mNAMPath.GetLength(), mNAMPath.Get());
786+
// Signal that embedded data is available for right-click save
787+
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagHasEmbeddedModel, mEmbeddedNAMData.empty() ? 0 : 1, nullptr);
788+
#ifdef _WIN32
789+
OutputDebugStringA("NAM _StageModel: SUCCESS\n");
790+
#endif
701791
}
702792
catch (std::runtime_error& e)
703793
{
794+
#ifdef _WIN32
795+
OutputDebugStringA("NAM _StageModel: EXCEPTION: ");
796+
OutputDebugStringA(e.what());
797+
OutputDebugStringA("\n");
798+
#endif
704799
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadFailed);
705800

706801
if (mStagedModel != nullptr)
@@ -738,7 +833,22 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIR(const WDL_String& irPath)
738833
if (wavState == dsp::wav::LoadReturnCode::SUCCESS)
739834
{
740835
mIRPath = irPath;
836+
837+
// Read file content for embedding in session
838+
mEmbeddedIRData.clear();
839+
auto irPathU8 = std::filesystem::u8path(irPath.Get());
840+
std::ifstream file(irPathU8, std::ios::binary | std::ios::ate);
841+
if (file.is_open())
842+
{
843+
std::streamsize size = file.tellg();
844+
file.seekg(0, std::ios::beg);
845+
mEmbeddedIRData.resize(static_cast<size_t>(size));
846+
file.read(reinterpret_cast<char*>(mEmbeddedIRData.data()), size);
847+
}
848+
741849
SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagLoadedIR, mIRPath.GetLength(), mIRPath.Get());
850+
// Signal that embedded data is available for right-click save
851+
SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagHasEmbeddedIR, mEmbeddedIRData.empty() ? 0 : 1, nullptr);
742852
}
743853
else
744854
{
@@ -753,6 +863,80 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIR(const WDL_String& irPath)
753863
return wavState;
754864
}
755865

866+
std::string NeuralAmpModeler::_StageModelFromData(const std::string& jsonContent, const WDL_String& originalPath)
867+
{
868+
try
869+
{
870+
std::unique_ptr<nam::DSP> model = nam::get_dsp_from_json(jsonContent);
871+
std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move(model), GetSampleRate());
872+
temp->Reset(GetSampleRate(), GetBlockSize());
873+
mStagedModel = std::move(temp);
874+
// Use original path if provided, otherwise mark as embedded
875+
if (originalPath.GetLength())
876+
mNAMPath = originalPath;
877+
else
878+
mNAMPath.Set("[Embedded]");
879+
mEmbeddedNAMData = jsonContent;
880+
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, mNAMPath.GetLength(), mNAMPath.Get());
881+
// Signal that embedded data is available for right-click save
882+
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagHasEmbeddedModel, 1, nullptr);
883+
}
884+
catch (std::runtime_error& e)
885+
{
886+
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadFailed);
887+
if (mStagedModel != nullptr)
888+
{
889+
mStagedModel = nullptr;
890+
}
891+
mEmbeddedNAMData.clear();
892+
std::cerr << "Failed to load embedded DSP module" << std::endl;
893+
std::cerr << e.what() << std::endl;
894+
return e.what();
895+
}
896+
return "";
897+
}
898+
899+
dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIRFromData(const std::vector<uint8_t>& wavData, const WDL_String& originalPath)
900+
{
901+
const double sampleRate = GetSampleRate();
902+
dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
903+
try
904+
{
905+
mStagedIR = std::make_unique<dsp::ImpulseResponse>(wavData, sampleRate);
906+
wavState = mStagedIR->GetWavState();
907+
}
908+
catch (std::runtime_error& e)
909+
{
910+
wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
911+
std::cerr << "Caught unhandled exception while attempting to load embedded IR:" << std::endl;
912+
std::cerr << e.what() << std::endl;
913+
}
914+
915+
if (wavState == dsp::wav::LoadReturnCode::SUCCESS)
916+
{
917+
// Use original path if provided, otherwise mark as embedded
918+
if (originalPath.GetLength())
919+
mIRPath = originalPath;
920+
else
921+
mIRPath.Set("[Embedded]");
922+
mEmbeddedIRData = wavData;
923+
SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagLoadedIR, mIRPath.GetLength(), mIRPath.Get());
924+
// Signal that embedded data is available for right-click save
925+
SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagHasEmbeddedIR, 1, nullptr);
926+
}
927+
else
928+
{
929+
if (mStagedIR != nullptr)
930+
{
931+
mStagedIR = nullptr;
932+
}
933+
mEmbeddedIRData.clear();
934+
SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagLoadFailed);
935+
}
936+
937+
return wavState;
938+
}
939+
756940
size_t NeuralAmpModeler::_GetBufferNumChannels() const
757941
{
758942
// Assumes input=output (no mono->stereo effects)

NeuralAmpModeler/NeuralAmpModeler.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,14 @@ enum EMsgTags
6969
kMsgTagClearModel = 0,
7070
kMsgTagClearIR,
7171
kMsgTagHighlightColor,
72+
kMsgTagSaveEmbeddedModel,
73+
kMsgTagSaveEmbeddedIR,
7274
// The following tags are from DSP -> UI
7375
kMsgTagLoadFailed,
7476
kMsgTagLoadedModel,
7577
kMsgTagLoadedIR,
78+
kMsgTagHasEmbeddedModel, // Signal that embedded model data is available
79+
kMsgTagHasEmbeddedIR, // Signal that embedded IR data is available
7680
kNumMsgTags
7781
};
7882

@@ -220,10 +224,15 @@ class NeuralAmpModeler final : public iplug::Plugin
220224
// Loads a NAM model and stores it to mStagedNAM
221225
// Returns an empty string on success, or an error message on failure.
222226
std::string _StageModel(const WDL_String& dspFile);
227+
// Loads a NAM model from embedded JSON data
228+
// Returns an empty string on success, or an error message on failure.
229+
std::string _StageModelFromData(const std::string& jsonContent, const WDL_String& originalPath);
223230
// Loads an IR and stores it to mStagedIR.
224231
// Return status code so that error messages can be relayed if
225232
// it wasn't successful.
226233
dsp::wav::LoadReturnCode _StageIR(const WDL_String& irPath);
234+
// Loads an IR from embedded WAV data
235+
dsp::wav::LoadReturnCode _StageIRFromData(const std::vector<uint8_t>& wavData, const WDL_String& originalPath);
227236

228237
bool _HaveModel() const { return this->mModel != nullptr; };
229238
// Prepare the input & output buffers
@@ -307,6 +316,10 @@ class NeuralAmpModeler final : public iplug::Plugin
307316
// Path to IR (.wav file)
308317
WDL_String mIRPath;
309318

319+
// Embedded data (for presets saved with embedded NAM/IR)
320+
std::string mEmbeddedNAMData;
321+
std::vector<uint8_t> mEmbeddedIRData;
322+
310323
WDL_String mHighLightColor{PluginColors::NAM_THEMECOLOR.ToColorCode()};
311324

312325
std::unordered_map<std::string, double> mNAMParams = {{"Input", 0.0}, {"Output", 0.0}};

0 commit comments

Comments
 (0)