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+
756940size_t NeuralAmpModeler::_GetBufferNumChannels () const
757941{
758942 // Assumes input=output (no mono->stereo effects)
0 commit comments