@@ -878,6 +878,82 @@ void XmlSerializationTest::test_toXmlFromXml_samplerDevice_relativePath_shouldLo
878878 QCOMPARE (samplerIn->absoluteFilePath (60 ), absolutePath);
879879}
880880
881+ void XmlSerializationTest::test_toXmlFromXml_samplerDevice_saveAs_shouldPreserveEmbeddedData ()
882+ {
883+ const std::string projectPath { " /tmp/noteahead_test" };
884+ const std::string relativePath { " samples/kick.wav" };
885+ const std::string absolutePath { QDir (QString::fromStdString (projectPath)).absoluteFilePath (QString::fromStdString (relativePath)).toStdString () };
886+
887+ // Create the dummy directory and file on disk so serializeDataToXml can open it
888+ QDir ().mkpath (QFileInfo (QString::fromStdString (absolutePath)).absolutePath ());
889+ QFile dummyFile { QString::fromStdString (absolutePath) };
890+ QVERIFY (dummyFile.open (QIODevice::WriteOnly));
891+ dummyFile.write (" dummy-wav-data" );
892+ dummyFile.close ();
893+
894+ const auto samplerName = " Noteahead Internal Device 1" ;
895+
896+ const auto engine = std::make_shared<AudioEngine>();
897+ const auto dataService = std::make_shared<DataService>();
898+ DeviceService deviceServiceOut { engine, dataService };
899+ deviceServiceOut.setProjectPath (projectPath);
900+
901+ const auto samplerOut = std::make_shared<SamplerDevice>(samplerName, std::make_unique<MockAudioFileReader>());
902+ samplerOut->loadSample (60 , absolutePath);
903+ samplerOut->setEmbedWaveData (true );
904+ deviceServiceOut.setDevice (0 , samplerOut);
905+
906+ EditorService editorServiceOut { std::make_shared<SelectionService>(), std::make_shared<SettingsService>(), std::make_shared<AutomationService>(std::make_shared<PropertyService>()), dataService };
907+ connect (&editorServiceOut, &EditorService::devicesSerializationRequested, &deviceServiceOut, &DeviceService::serializeToXml);
908+ connect (&editorServiceOut, &EditorService::dataSerializationRequested, [&deviceServiceOut, dataService](ProjectWriter & writer) {
909+ const auto files = deviceServiceOut.getFilesToEmbed ();
910+ dataService->serializeDataToXml (writer, files);
911+ });
912+
913+ const auto xml = editorServiceOut.toXml ();
914+
915+ // Now load it in a new setup
916+ const auto engine2 = std::make_shared<AudioEngine>();
917+ const auto dataService2 = std::make_shared<DataService>();
918+ DeviceService deviceServiceIn { engine2, dataService2 };
919+ deviceServiceIn.setProjectPath (projectPath);
920+
921+ const auto samplerIn = std::make_shared<SamplerDevice>(samplerName, std::make_unique<MockAudioFileReader>());
922+ deviceServiceIn.setDevice (0 , samplerIn);
923+
924+ EditorService editorServiceIn { std::make_shared<SelectionService>(), std::make_shared<SettingsService>(), std::make_shared<AutomationService>(std::make_shared<PropertyService>()), dataService2 };
925+ connect (&editorServiceIn, &EditorService::devicesDeserializationRequested, &deviceServiceIn, &DeviceService::deserializeFromXml);
926+ connect (&editorServiceIn, &EditorService::devicesSerializationRequested, &deviceServiceIn, &DeviceService::serializeToXml);
927+ connect (&editorServiceIn, &EditorService::dataSerializationRequested, [&deviceServiceIn, dataService2](ProjectWriter & writer) {
928+ const auto files = deviceServiceIn.getFilesToEmbed ();
929+ dataService2->serializeDataToXml (writer, files);
930+ });
931+
932+ editorServiceIn.fromXml (xml);
933+
934+ // Verify it is loaded and its path in memory is absolute (our fix!)
935+ QVERIFY (samplerIn->sample (60 ));
936+ const auto expectedMemoryPath = samplerIn->sample (60 )->filePath ;
937+ // It should start with nahd:// because it was deserialized as embedded
938+ QVERIFY (QString::fromStdString (expectedMemoryPath).startsWith (Constants::NahdXml::embeddedDataPathPrefix ()));
939+
940+ // Now simulate "Save As" by changing project path to a new location
941+ const std::string newProjectPath { " /tmp/noteahead_test_new" };
942+ deviceServiceIn.setProjectPath (newProjectPath);
943+
944+ // Serialize again! If the bug exists, this will fail or serialize empty data
945+ // because absoluteFilePath(60) would be resolved relative to the new path and point to a non-existent file
946+ const auto xml2 = editorServiceIn.toXml ();
947+
948+ // Verify the second XML has the embedded data block
949+ QVERIFY (xml2.contains (" <Data" ));
950+ QVERIFY (xml2.contains (" nahd://kick.wav" ));
951+
952+ // Cleanup
953+ QFile::remove (QString::fromStdString (absolutePath));
954+ QDir ().rmdir (QFileInfo (QString::fromStdString (absolutePath)).absolutePath ());
955+ }
956+
881957void XmlSerializationTest::test_toXmlFromXml_synthDevice_shouldPreserveValuesAndDiscreteFlags ()
882958{
883959 const auto synthName = " Noteahead Internal Device 1" ;
0 commit comments