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
@@ -689,14 +705,28 @@ void NeuralAmpModeler::_SetOutputGain()
689705std::string NeuralAmpModeler::_StageModel (const WDL_String& modelPath)
690706{
691707 WDL_String previousNAMPath = mNAMPath ;
708+ const double sampleRate = GetSampleRate ();
709+
692710 try
693711 {
694712 auto dspPath = std::filesystem::u8path (modelPath.Get ());
695713 std::unique_ptr<nam::DSP> model = nam::get_dsp (dspPath);
696- std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move (model), GetSampleRate () );
697- temp->Reset (GetSampleRate () , GetBlockSize ());
714+ std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move (model), sampleRate );
715+ temp->Reset (sampleRate , GetBlockSize ());
698716 mStagedModel = std::move (temp);
699717 mNAMPath = modelPath;
718+
719+ // Read file data for embedding in session
720+ mNAMData .clear ();
721+ std::ifstream file (dspPath, std::ios::binary | std::ios::ate);
722+ if (file.is_open ())
723+ {
724+ std::streamsize size = file.tellg ();
725+ file.seekg (0 , std::ios::beg);
726+ mNAMData .resize (static_cast <size_t >(size));
727+ file.read (reinterpret_cast <char *>(mNAMData .data ()), size);
728+ }
729+
700730 SendControlMsgFromDelegate (kCtrlTagModelFileBrowser , kMsgTagLoadedModel , mNAMPath .GetLength (), mNAMPath .Get ());
701731 }
702732 catch (std::runtime_error& e)
@@ -721,6 +751,7 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIR(const WDL_String& irPath)
721751 // path and the model got caught on opposite sides of the fence...
722752 WDL_String previousIRPath = mIRPath ;
723753 const double sampleRate = GetSampleRate ();
754+
724755 dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
725756 try
726757 {
@@ -738,6 +769,19 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIR(const WDL_String& irPath)
738769 if (wavState == dsp::wav::LoadReturnCode::SUCCESS)
739770 {
740771 mIRPath = irPath;
772+
773+ // Read file data for embedding in session
774+ mIRData .clear ();
775+ auto irPathU8 = std::filesystem::u8path (irPath.Get ());
776+ std::ifstream file (irPathU8, 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+ mIRData .resize (static_cast <size_t >(size));
782+ file.read (reinterpret_cast <char *>(mIRData .data ()), size);
783+ }
784+
741785 SendControlMsgFromDelegate (kCtrlTagIRFileBrowser , kMsgTagLoadedIR , mIRPath .GetLength (), mIRPath .Get ());
742786 }
743787 else
@@ -911,5 +955,207 @@ void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, sample** outputPoint
911955 mOutputSender .ProcessBlock (outputPointer, (int )nFrames, kCtrlTagOutputMeter , nChansHack);
912956}
913957
958+ std::string NeuralAmpModeler::_StageModelFromData (const std::vector<uint8_t >& data, const WDL_String& originalPath)
959+ {
960+ WDL_String previousNAMPath = mNAMPath ;
961+ const double sampleRate = GetSampleRate ();
962+
963+ try
964+ {
965+ // Parse the JSON from memory
966+ std::string jsonStr (data.begin (), data.end ());
967+ nlohmann::json j = nlohmann::json::parse (jsonStr);
968+
969+ // Build dspData structure
970+ nam::dspData dspData;
971+ dspData.version = j[" version" ];
972+ dspData.architecture = j[" architecture" ];
973+ dspData.config = j[" config" ];
974+ dspData.metadata = j[" metadata" ];
975+
976+ // Extract weights
977+ if (j.find (" weights" ) != j.end ())
978+ {
979+ dspData.weights = j[" weights" ].get <std::vector<float >>();
980+ }
981+
982+ // Extract sample rate
983+ if (j.find (" sample_rate" ) != j.end ())
984+ dspData.expected_sample_rate = j[" sample_rate" ];
985+ else
986+ dspData.expected_sample_rate = -1.0 ;
987+
988+ // Create DSP from dspData
989+ std::unique_ptr<nam::DSP> model = nam::get_dsp (dspData);
990+ std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move (model), sampleRate);
991+ temp->Reset (sampleRate, GetBlockSize ());
992+ mStagedModel = std::move (temp);
993+ mNAMPath = originalPath;
994+ mNAMData = data; // Store the embedded data
995+ SendControlMsgFromDelegate (kCtrlTagModelFileBrowser , kMsgTagLoadedModel , mNAMPath .GetLength (), mNAMPath .Get ());
996+ }
997+ catch (std::exception& e)
998+ {
999+ SendControlMsgFromDelegate (kCtrlTagModelFileBrowser , kMsgTagLoadFailed );
1000+
1001+ if (mStagedModel != nullptr )
1002+ {
1003+ mStagedModel = nullptr ;
1004+ }
1005+ mNAMPath = previousNAMPath;
1006+ std::cerr << " Failed to read DSP module from embedded data" << std::endl;
1007+ std::cerr << e.what () << std::endl;
1008+ return e.what ();
1009+ }
1010+ return " " ;
1011+ }
1012+
1013+ dsp::wav::LoadReturnCode NeuralAmpModeler::_StageIRFromData (const std::vector<uint8_t >& data,
1014+ const WDL_String& originalPath)
1015+ {
1016+ WDL_String previousIRPath = mIRPath ;
1017+ const double sampleRate = GetSampleRate ();
1018+
1019+ dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
1020+
1021+ try
1022+ {
1023+ // Parse WAV from memory
1024+ std::vector<float > audio;
1025+ double wavSampleRate = 0.0 ;
1026+
1027+ // Basic WAV parser for in-memory data
1028+ // WAV format: RIFF header (12 bytes) + fmt chunk + data chunk
1029+ if (data.size () < 44 ) // Minimum WAV file size
1030+ {
1031+ throw std::runtime_error (" IR data too small to be valid WAV" );
1032+ }
1033+
1034+ // Check RIFF header
1035+ if (data[0 ] != ' R' || data[1 ] != ' I' || data[2 ] != ' F' || data[3 ] != ' F' )
1036+ {
1037+ throw std::runtime_error (" Invalid WAV format - missing RIFF header" );
1038+ }
1039+
1040+ // Check WAVE format
1041+ if (data[8 ] != ' W' || data[9 ] != ' A' || data[10 ] != ' V' || data[11 ] != ' E' )
1042+ {
1043+ throw std::runtime_error (" Invalid WAV format - not a WAVE file" );
1044+ }
1045+
1046+ // Find fmt chunk
1047+ size_t pos = 12 ;
1048+ uint16_t audioFormat = 0 ;
1049+ uint16_t numChannels = 0 ;
1050+ uint32_t sampleRateInt = 0 ;
1051+ uint16_t bitsPerSample = 0 ;
1052+
1053+ while (pos < data.size () - 8 )
1054+ {
1055+ std::string chunkID (data.begin () + pos, data.begin () + pos + 4 );
1056+ uint32_t chunkSize = *reinterpret_cast <const uint32_t *>(&data[pos + 4 ]);
1057+
1058+ if (chunkID == " fmt " )
1059+ {
1060+ audioFormat = *reinterpret_cast <const uint16_t *>(&data[pos + 8 ]);
1061+ numChannels = *reinterpret_cast <const uint16_t *>(&data[pos + 10 ]);
1062+ sampleRateInt = *reinterpret_cast <const uint32_t *>(&data[pos + 12 ]);
1063+ bitsPerSample = *reinterpret_cast <const uint16_t *>(&data[pos + 22 ]);
1064+ wavSampleRate = static_cast <double >(sampleRateInt);
1065+ }
1066+ else if (chunkID == " data" )
1067+ {
1068+ // Found data chunk
1069+ size_t dataStart = pos + 8 ;
1070+ size_t numSamples = chunkSize / (bitsPerSample / 8 );
1071+
1072+ audio.resize (numSamples);
1073+
1074+ // Convert based on bits per sample
1075+ if (bitsPerSample == 16 && audioFormat == 1 ) // PCM 16-bit
1076+ {
1077+ for (size_t i = 0 ; i < numSamples; i++)
1078+ {
1079+ int16_t sample = *reinterpret_cast <const int16_t *>(&data[dataStart + i * 2 ]);
1080+ audio[i] = sample / 32768 .0f ;
1081+ }
1082+ }
1083+ else if (bitsPerSample == 24 && audioFormat == 1 ) // PCM 24-bit
1084+ {
1085+ for (size_t i = 0 ; i < numSamples; i++)
1086+ {
1087+ int32_t sample = 0 ;
1088+ sample |= static_cast <int32_t >(data[dataStart + i * 3 ]);
1089+ sample |= static_cast <int32_t >(data[dataStart + i * 3 + 1 ]) << 8 ;
1090+ sample |= static_cast <int32_t >(data[dataStart + i * 3 + 2 ]) << 16 ;
1091+ if (sample & 0x800000 )
1092+ sample |= 0xFF000000 ; // Sign extend
1093+ audio[i] = sample / 8388608 .0f ;
1094+ }
1095+ }
1096+ else if (bitsPerSample == 32 && audioFormat == 3 ) // IEEE float 32-bit
1097+ {
1098+ for (size_t i = 0 ; i < numSamples; i++)
1099+ {
1100+ audio[i] = *reinterpret_cast <const float *>(&data[dataStart + i * 4 ]);
1101+ }
1102+ }
1103+ else
1104+ {
1105+ throw std::runtime_error (" Unsupported WAV format" );
1106+ }
1107+
1108+ break ;
1109+ }
1110+
1111+ pos += 8 + chunkSize;
1112+ }
1113+
1114+ if (audio.empty ())
1115+ {
1116+ throw std::runtime_error (" No audio data found in WAV" );
1117+ }
1118+
1119+ // Layer 9: Validate that fmt chunk was actually found and sample rate is valid
1120+ // WAV files can have missing fmt chunks or chunks in wrong order
1121+ if (wavSampleRate <= 0.0 || wavSampleRate != wavSampleRate)
1122+ {
1123+ throw std::runtime_error (" Invalid or missing sample rate in WAV fmt chunk" );
1124+ }
1125+
1126+ // Create IR from the loaded data
1127+ dsp::ImpulseResponse::IRData irData;
1128+ irData.mRawAudio = audio;
1129+ irData.mRawAudioSampleRate = wavSampleRate;
1130+
1131+ mStagedIR = std::make_unique<dsp::ImpulseResponse>(irData, sampleRate);
1132+ wavState = dsp::wav::LoadReturnCode::SUCCESS;
1133+ }
1134+ catch (std::exception& e)
1135+ {
1136+ wavState = dsp::wav::LoadReturnCode::ERROR_OTHER;
1137+ std::cerr << " Failed to load IR from embedded data:" << std::endl;
1138+ std::cerr << e.what () << std::endl;
1139+ }
1140+
1141+ if (wavState == dsp::wav::LoadReturnCode::SUCCESS)
1142+ {
1143+ mIRPath = originalPath;
1144+ mIRData = data; // Store the embedded data
1145+ SendControlMsgFromDelegate (kCtrlTagIRFileBrowser , kMsgTagLoadedIR , mIRPath .GetLength (), mIRPath .Get ());
1146+ }
1147+ else
1148+ {
1149+ if (mStagedIR != nullptr )
1150+ {
1151+ mStagedIR = nullptr ;
1152+ }
1153+ mIRPath = previousIRPath;
1154+ SendControlMsgFromDelegate (kCtrlTagIRFileBrowser , kMsgTagLoadFailed );
1155+ }
1156+
1157+ return wavState;
1158+ }
1159+
9141160// HACK
9151161#include " Unserialization.cpp"
0 commit comments