11#include < algorithm> // std::clamp, std::min
22#include < cmath> // pow
33#include < filesystem>
4+ #include < fstream> // std::ifstream for file reading
45#include < iostream>
56#include < utility>
67
@@ -74,7 +75,6 @@ const bool kDefaultCalibrateInput = false;
7475const std::string kInputCalibrationLevelParamName = " InputCalibrationLevel" ;
7576const double kDefaultInputCalibrationLevel = 12.0 ;
7677
77-
7878NeuralAmpModeler::NeuralAmpModeler (const InstanceInfo& info)
7979: Plugin(info, MakeConfig(kNumParams , kNumPresets ))
8080{
@@ -183,7 +183,6 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
183183 {
184184 // Sets mNAMPath and mStagedNAM
185185 const std::string msg = _StageModel (fileName);
186- // TODO error messages like the IR loader.
187186 if (msg.size ())
188187 {
189188 std::stringstream ss;
@@ -407,16 +406,33 @@ void NeuralAmpModeler::OnIdle()
407406
408407bool NeuralAmpModeler::SerializeState (IByteChunk& chunk) const
409408{
410- // If this isn't here when unserializing, then we know we're dealing with something before v0.8.0 .
409+ // If this isn't here when unserializing, then we know we're dealing with something before v0.7.13 .
411410 WDL_String header (" ###NeuralAmpModeler###" ); // Don't change this!
412411 chunk.PutStr (header.Get ());
413412 // Plugin version, so we can load legacy serialized states in the future!
414413 WDL_String version (PLUG_VERSION_STR);
415414 chunk.PutStr (version.Get ());
416- // Model directory (don't serialize the model itself; we'll just load it again
417- // when we unserialize)
415+
416+ // Serialize file paths for backward compatibility
418417 chunk.PutStr (mNAMPath .Get ());
419418 chunk.PutStr (mIRPath .Get ());
419+
420+ // Embed the actual file data for portability
421+ // Data was read when model/IR was loaded
422+ int namDataSize = static_cast <int >(mNAMData .size ());
423+ chunk.Put (&namDataSize);
424+ if (namDataSize > 0 )
425+ {
426+ chunk.PutBytes (mNAMData .data (), namDataSize);
427+ }
428+
429+ int irDataSize = static_cast <int >(mIRData .size ());
430+ chunk.Put (&irDataSize);
431+ if (irDataSize > 0 )
432+ {
433+ chunk.PutBytes (mIRData .data (), irDataSize);
434+ }
435+
420436 return SerializeParams (chunk);
421437}
422438
@@ -697,6 +713,18 @@ std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath)
697713 temp->Reset (GetSampleRate (), GetBlockSize ());
698714 mStagedModel = std::move (temp);
699715 mNAMPath = modelPath;
716+
717+ // Read file data for embedding in session
718+ mNAMData .clear ();
719+ std::ifstream file (dspPath, std::ios::binary | std::ios::ate);
720+ if (file.is_open ())
721+ {
722+ std::streamsize size = file.tellg ();
723+ file.seekg (0 , std::ios::beg);
724+ mNAMData .resize (static_cast <size_t >(size));
725+ file.read (reinterpret_cast <char *>(mNAMData .data ()), size);
726+ }
727+
700728 SendControlMsgFromDelegate (kCtrlTagModelFileBrowser , kMsgTagLoadedModel , mNAMPath .GetLength (), mNAMPath .Get ());
701729 }
702730 catch (std::runtime_error& e)
@@ -721,6 +749,7 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIR(const WDL_String& irPath)
721749 // path and the model got caught on opposite sides of the fence...
722750 WDL_String previousIRPath = mIRPath ;
723751 const double sampleRate = GetSampleRate ();
752+
724753 dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
725754 try
726755 {
@@ -738,6 +767,19 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIR(const WDL_String& irPath)
738767 if (wavState == dsp::wav::LoadReturnCode::SUCCESS)
739768 {
740769 mIRPath = irPath;
770+
771+ // Read file data for embedding in session
772+ mIRData .clear ();
773+ auto irPathU8 = std::filesystem::u8path (irPath.Get ());
774+ std::ifstream file (irPathU8, std::ios::binary | std::ios::ate);
775+ if (file.is_open ())
776+ {
777+ std::streamsize size = file.tellg ();
778+ file.seekg (0 , std::ios::beg);
779+ mIRData .resize (static_cast <size_t >(size));
780+ file.read (reinterpret_cast <char *>(mIRData .data ()), size);
781+ }
782+
741783 SendControlMsgFromDelegate (kCtrlTagIRFileBrowser , kMsgTagLoadedIR , mIRPath .GetLength (), mIRPath .Get ());
742784 }
743785 else
@@ -911,5 +953,207 @@ void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, sample** outputPoint
911953 mOutputSender .ProcessBlock (outputPointer, (int )nFrames, kCtrlTagOutputMeter , nChansHack);
912954}
913955
956+ std::string NeuralAmpModeler::_StageModelFromData (const std::vector<uint8_t >& data, const WDL_String& originalPath)
957+ {
958+ WDL_String previousNAMPath = mNAMPath ;
959+ const double sampleRate = GetSampleRate ();
960+
961+ try
962+ {
963+ // Parse the JSON from memory
964+ std::string jsonStr (data.begin (), data.end ());
965+ nlohmann::json j = nlohmann::json::parse (jsonStr);
966+
967+ // Build dspData structure
968+ nam::dspData dspData;
969+ dspData.version = j[" version" ];
970+ dspData.architecture = j[" architecture" ];
971+ dspData.config = j[" config" ];
972+ dspData.metadata = j[" metadata" ];
973+
974+ // Extract weights
975+ if (j.find (" weights" ) != j.end ())
976+ {
977+ dspData.weights = j[" weights" ].get <std::vector<float >>();
978+ }
979+
980+ // Extract sample rate
981+ if (j.find (" sample_rate" ) != j.end ())
982+ dspData.expected_sample_rate = j[" sample_rate" ];
983+ else
984+ dspData.expected_sample_rate = -1.0 ;
985+
986+ // Create DSP from dspData
987+ std::unique_ptr<nam::DSP> model = nam::get_dsp (dspData);
988+ std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move (model), sampleRate);
989+ temp->Reset (sampleRate, GetBlockSize ());
990+ mStagedModel = std::move (temp);
991+ mNAMPath = originalPath;
992+ mNAMData = data; // Store the embedded data
993+ SendControlMsgFromDelegate (kCtrlTagModelFileBrowser , kMsgTagLoadedModel , mNAMPath .GetLength (), mNAMPath .Get ());
994+ }
995+ catch (std::exception& e)
996+ {
997+ SendControlMsgFromDelegate (kCtrlTagModelFileBrowser , kMsgTagLoadFailed );
998+
999+ if (mStagedModel != nullptr )
1000+ {
1001+ mStagedModel = nullptr ;
1002+ }
1003+ mNAMPath = previousNAMPath;
1004+ std::cerr << " Failed to read DSP module from embedded data" << std::endl;
1005+ std::cerr << e.what () << std::endl;
1006+ return e.what ();
1007+ }
1008+ return " " ;
1009+ }
1010+
1011+ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIRFromData (const std::vector<uint8_t >& data,
1012+ const WDL_String& originalPath)
1013+ {
1014+ WDL_String previousIRPath = mIRPath ;
1015+ const double sampleRate = GetSampleRate ();
1016+
1017+ dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
1018+
1019+ try
1020+ {
1021+ // Parse WAV from memory
1022+ std::vector<float > audio;
1023+ double wavSampleRate = 0.0 ;
1024+
1025+ // Basic WAV parser for in-memory data
1026+ // WAV format: RIFF header (12 bytes) + fmt chunk + data chunk
1027+ if (data.size () < 44 ) // Minimum WAV file size
1028+ {
1029+ throw std::runtime_error (" IR data too small to be valid WAV" );
1030+ }
1031+
1032+ // Check RIFF header
1033+ if (data[0 ] != ' R' || data[1 ] != ' I' || data[2 ] != ' F' || data[3 ] != ' F' )
1034+ {
1035+ throw std::runtime_error (" Invalid WAV format - missing RIFF header" );
1036+ }
1037+
1038+ // Check WAVE format
1039+ if (data[8 ] != ' W' || data[9 ] != ' A' || data[10 ] != ' V' || data[11 ] != ' E' )
1040+ {
1041+ throw std::runtime_error (" Invalid WAV format - not a WAVE file" );
1042+ }
1043+
1044+ // Find fmt chunk
1045+ size_t pos = 12 ;
1046+ uint16_t audioFormat = 0 ;
1047+ uint16_t numChannels = 0 ;
1048+ uint32_t sampleRateInt = 0 ;
1049+ uint16_t bitsPerSample = 0 ;
1050+
1051+ while (pos < data.size () - 8 )
1052+ {
1053+ std::string chunkID (data.begin () + pos, data.begin () + pos + 4 );
1054+ uint32_t chunkSize = *reinterpret_cast <const uint32_t *>(&data[pos + 4 ]);
1055+
1056+ if (chunkID == " fmt " )
1057+ {
1058+ audioFormat = *reinterpret_cast <const uint16_t *>(&data[pos + 8 ]);
1059+ numChannels = *reinterpret_cast <const uint16_t *>(&data[pos + 10 ]);
1060+ sampleRateInt = *reinterpret_cast <const uint32_t *>(&data[pos + 12 ]);
1061+ bitsPerSample = *reinterpret_cast <const uint16_t *>(&data[pos + 22 ]);
1062+ wavSampleRate = static_cast <double >(sampleRateInt);
1063+ }
1064+ else if (chunkID == " data" )
1065+ {
1066+ // Found data chunk
1067+ size_t dataStart = pos + 8 ;
1068+ size_t numSamples = chunkSize / (bitsPerSample / 8 );
1069+
1070+ audio.resize (numSamples);
1071+
1072+ // Convert based on bits per sample
1073+ if (bitsPerSample == 16 && audioFormat == 1 ) // PCM 16-bit
1074+ {
1075+ for (size_t i = 0 ; i < numSamples; i++)
1076+ {
1077+ int16_t sample = *reinterpret_cast <const int16_t *>(&data[dataStart + i * 2 ]);
1078+ audio[i] = sample / 32768 .0f ;
1079+ }
1080+ }
1081+ else if (bitsPerSample == 24 && audioFormat == 1 ) // PCM 24-bit
1082+ {
1083+ for (size_t i = 0 ; i < numSamples; i++)
1084+ {
1085+ int32_t sample = 0 ;
1086+ sample |= static_cast <int32_t >(data[dataStart + i * 3 ]);
1087+ sample |= static_cast <int32_t >(data[dataStart + i * 3 + 1 ]) << 8 ;
1088+ sample |= static_cast <int32_t >(data[dataStart + i * 3 + 2 ]) << 16 ;
1089+ if (sample & 0x800000 )
1090+ sample |= 0xFF000000 ; // Sign extend
1091+ audio[i] = sample / 8388608 .0f ;
1092+ }
1093+ }
1094+ else if (bitsPerSample == 32 && audioFormat == 3 ) // IEEE float 32-bit
1095+ {
1096+ for (size_t i = 0 ; i < numSamples; i++)
1097+ {
1098+ audio[i] = *reinterpret_cast <const float *>(&data[dataStart + i * 4 ]);
1099+ }
1100+ }
1101+ else
1102+ {
1103+ throw std::runtime_error (" Unsupported WAV format" );
1104+ }
1105+
1106+ break ;
1107+ }
1108+
1109+ pos += 8 + chunkSize;
1110+ }
1111+
1112+ if (audio.empty ())
1113+ {
1114+ throw std::runtime_error (" No audio data found in WAV" );
1115+ }
1116+
1117+ // Layer 9: Validate that fmt chunk was actually found and sample rate is valid
1118+ // WAV files can have missing fmt chunks or chunks in wrong order
1119+ if (wavSampleRate <= 0.0 || wavSampleRate != wavSampleRate)
1120+ {
1121+ throw std::runtime_error (" Invalid or missing sample rate in WAV fmt chunk" );
1122+ }
1123+
1124+ // Create IR from the loaded data
1125+ dsp::ImpulseResponse::IRData irData;
1126+ irData.mRawAudio = audio;
1127+ irData.mRawAudioSampleRate = wavSampleRate;
1128+
1129+ mStagedIR = std::make_unique<dsp::ImpulseResponse>(irData, sampleRate);
1130+ wavState = dsp::wav::LoadReturnCode::SUCCESS;
1131+ }
1132+ catch (std::exception& e)
1133+ {
1134+ wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
1135+ std::cerr << " Failed to load IR from embedded data:" << std::endl;
1136+ std::cerr << e.what () << std::endl;
1137+ }
1138+
1139+ if (wavState == dsp::wav::LoadReturnCode::SUCCESS)
1140+ {
1141+ mIRPath = originalPath;
1142+ mIRData = data; // Store the embedded data
1143+ SendControlMsgFromDelegate (kCtrlTagIRFileBrowser , kMsgTagLoadedIR , mIRPath .GetLength (), mIRPath .Get ());
1144+ }
1145+ else
1146+ {
1147+ if (mStagedIR != nullptr )
1148+ {
1149+ mStagedIR = nullptr ;
1150+ }
1151+ mIRPath = previousIRPath;
1152+ SendControlMsgFromDelegate (kCtrlTagIRFileBrowser , kMsgTagLoadFailed );
1153+ }
1154+
1155+ return wavState;
1156+ }
1157+
9141158// HACK
9151159#include " Unserialization.cpp"
0 commit comments