diff --git a/resources/manifest.ttl.in b/resources/manifest.ttl.in index 3b4f359..b16a7e8 100644 --- a/resources/manifest.ttl.in +++ b/resources/manifest.ttl.in @@ -1,5 +1,6 @@ @prefix lv2: . @prefix rdfs: . +@prefix state: . <@NAM_LV2_ID@> a lv2:Plugin; diff --git a/resources/neural_amp_modeler.ttl.in b/resources/neural_amp_modeler.ttl.in index 4d33d87..9c1e316 100644 --- a/resources/neural_amp_modeler.ttl.in +++ b/resources/neural_amp_modeler.ttl.in @@ -31,7 +31,7 @@ lv2:requiredFeature urid:map, work:schedule; lv2:optionalFeature lv2:hardRTCapable; - lv2:extensionData work:interface; + lv2:extensionData work:interface, state:interface; rdfs:comment "An LV2 implementation of Neural Amp Modeler"; @@ -88,4 +88,7 @@ lv2:minimum -20.0; lv2:maximum 20.0; units:unit units:db; - ]. + ]; + state:state [ + <@NAM_LV2_ID@#model> ; + ]. diff --git a/src/nam_lv2.cpp b/src/nam_lv2.cpp index f01e6c8..0e6973d 100644 --- a/src/nam_lv2.cpp +++ b/src/nam_lv2.cpp @@ -63,8 +63,14 @@ static void cleanup(LV2_Handle instance) static const void* extension_data(const char* uri) { + static const LV2_State_Interface state = {NAM::Plugin::save, NAM::Plugin::restore}; static const LV2_Worker_Interface worker = { NAM::Plugin::work, NAM::Plugin::work_response, NULL }; + if (!strcmp(uri, LV2_STATE__interface)) { + return &state; + } + + if (!strcmp(uri, LV2_WORKER__interface)) return &worker; diff --git a/src/nam_plugin.cpp b/src/nam_plugin.cpp index 0b3567d..05d3515 100644 --- a/src/nam_plugin.cpp +++ b/src/nam_plugin.cpp @@ -14,12 +14,12 @@ namespace NAM { for (size_t i = 0; features[i]; ++i) { if (std::string(features[i]->URI) == std::string(LV2_URID__map)) map = static_cast(features[i]->data); - else if (!strcmp(features[i]->URI, LV2_WORKER__schedule)) - schedule = (LV2_Worker_Schedule*)features[i]->data; + else if (std::string(features[i]->URI) == std::string(LV2_WORKER__schedule)) + schedule = static_cast(features[i]->data); else if (std::string(features[i]->URI) == std::string(LV2_LOG__log)) logger.log = static_cast(features[i]->data); } - + lv2_log_logger_set_map(&logger, map); if (!map) @@ -44,6 +44,7 @@ namespace NAM { uris.atom_Path = map->map(map->handle, LV2_ATOM__Path); uris.atom_URID = map->map(map->handle, LV2_ATOM__URID); uris.patch_Set = map->map(map->handle, LV2_PATCH__Set); + uris.patch_Get = map->map(map->handle, LV2_PATCH__Get); uris.patch_property = map->map(map->handle, LV2_PATCH__property); uris.patch_value = map->map(map->handle, LV2_PATCH__value); @@ -55,6 +56,8 @@ namespace NAM { LV2_Worker_Status Plugin::work(LV2_Handle instance, LV2_Worker_Respond_Function respond, LV2_Worker_Respond_Handle handle, uint32_t size, const void* data) { + auto atom = static_cast(data); + auto nam = static_cast(instance); switch (*((const uint32_t*)data)) { case kWorkTypeLoad: @@ -70,7 +73,9 @@ namespace NAM { nam->deleteModel.reset(); } + lv2_log_trace(&nam->logger, "Staging model change: `%s`\n", msg->path); nam->stagedModel = get_dsp(msg->path); + nam->stagedModelPath = msg->path; LV2WorkType response = kWorkTypeSwitch; @@ -98,9 +103,12 @@ namespace NAM { auto nam = static_cast(instance); std::swap(nam->currentModel, nam->stagedModel); + nam->currentModelPath = nam->stagedModelPath; nam->deleteModel = std::move(nam->stagedModel); + nam->write_set_patch(nam->currentModelPath); + return LV2_WORKER_SUCCESS; } catch (std::exception& e) @@ -123,6 +131,10 @@ namespace NAM { if (event->body.type == uris.atom_Object) { const auto obj = reinterpret_cast(&event->body); + if (obj->body.otype == uris.patch_Get) { + lv2_atom_forge_frame_time(&atom_forge, 0); + write_set_patch(currentModelPath); + } if (obj->body.otype == uris.patch_Set) { @@ -131,6 +143,7 @@ namespace NAM { lv2_atom_object_get(obj, uris.patch_property, &property, 0); + if (property && (property->type == uris.atom_URID)) { if (((const LV2_Atom_URID*)property)->body == uris.model_Path) @@ -179,4 +192,80 @@ namespace NAM { ports.audio_out[i] = (float)(dblData[i] * outputLevel); } } + + LV2_State_Status Plugin::save(LV2_Handle instance, LV2_State_Store_Function store, LV2_State_Handle handle, + uint32_t flags, const LV2_Feature* const* features) + { + auto nam = static_cast(instance); + + lv2_log_trace(&nam->logger, "Saving state\n"); + + if (!nam->currentModel) { + return LV2_STATE_SUCCESS; + } + + LV2_State_Map_Path* map_path = (LV2_State_Map_Path*)lv2_features_data(features, LV2_STATE__mapPath); + + // Map absolute sample path to an abstract state path + char* apath = map_path->abstract_path(map_path->handle, nam->currentModelPath.c_str()); + + store(handle, nam->uris.model_Path, apath, strlen(apath) + 1, nam->uris.atom_Path, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + free(apath); + + return LV2_STATE_SUCCESS; + } + + LV2_State_Status Plugin::restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, + uint32_t flags, const LV2_Feature* const* features) + { + auto nam = static_cast(instance); + + // Get model_Path from state + size_t size = 0; + uint32_t type = 0; + uint32_t valflags = 0; + const void* value = retrieve(handle, nam->uris.model_Path, &size, &type, &valflags); + + lv2_log_trace(&nam->logger, "Restoring model '%s'\n", value); + + if (!value) { + lv2_log_error(&nam->logger, "Missing model_Path\n"); + return LV2_STATE_ERR_NO_PROPERTY; + } + + if (type != nam->uris.atom_Path) { + lv2_log_error(&nam->logger, "Non-path model_Path\n"); + return LV2_STATE_ERR_BAD_TYPE; + } + + LV2_State_Map_Path* map_path = (LV2_State_Map_Path*)lv2_features_data(features, LV2_STATE__mapPath); + // Map abstract state path to absolute path + char* path = map_path->absolute_path(map_path->handle, (const char *)value); + + // Schedule model to be loaded by the provided worker + NAM::LV2LoadModelMsg msg = { NAM::kWorkTypeLoad, {} }; + + memcpy(msg.path, path, size); + nam->schedule->schedule_work(nam->schedule->handle, sizeof(msg), &msg); + + free(path); + return LV2_STATE_SUCCESS; + } + + LV2_Atom_Forge_Ref Plugin::write_set_patch( std::string filename) + { + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref set = + lv2_atom_forge_object(&atom_forge, &frame, 0, uris.patch_Set); + + lv2_atom_forge_key(&atom_forge, uris.patch_property); + lv2_atom_forge_urid(&atom_forge, uris.model_Path); + lv2_atom_forge_key(&atom_forge, uris.patch_value); + lv2_atom_forge_path(&atom_forge, filename.c_str(), filename.length()); + + lv2_atom_forge_pop(&atom_forge, &frame); + return set; + } } diff --git a/src/nam_plugin.h b/src/nam_plugin.h index 5da8f33..98ba132 100644 --- a/src/nam_plugin.h +++ b/src/nam_plugin.h @@ -8,6 +8,7 @@ // LV2 #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include "dsp.h" @@ -54,6 +56,9 @@ namespace NAM { std::unique_ptr<::DSP> stagedModel; std::unique_ptr<::DSP> deleteModel; + std::string currentModelPath; + std::string stagedModelPath; + std::unordered_map mNAMParams = {}; Plugin(); @@ -61,12 +66,18 @@ namespace NAM { bool initialize(double rate, const LV2_Feature* const* features) noexcept; void process(uint32_t n_samples) noexcept; - + + LV2_Atom_Forge_Ref write_set_patch(std::string filename); static LV2_Worker_Status work(LV2_Handle instance, LV2_Worker_Respond_Function respond, LV2_Worker_Respond_Handle handle, uint32_t size, const void* data); static LV2_Worker_Status work_response(LV2_Handle instance, uint32_t size, const void* data); + static LV2_State_Status save(LV2_Handle instance, LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t flags, + const LV2_Feature* const* features); + static LV2_State_Status restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t flags, + const LV2_Feature* const* features); + private: struct URIs { LV2_URID atom_Object; @@ -75,12 +86,14 @@ namespace NAM { LV2_URID atom_Path; LV2_URID atom_URID; LV2_URID patch_Set; + LV2_URID patch_Get; LV2_URID patch_property; LV2_URID patch_value; LV2_URID model_Path; }; URIs uris = {}; + LV2_Atom_Forge atom_forge = {}; std::vector dblData;