diff --git a/resources/neural_amp_modeler.ttl.in b/resources/neural_amp_modeler.ttl.in index 701cec4..977b204 100644 --- a/resources/neural_amp_modeler.ttl.in +++ b/resources/neural_amp_modeler.ttl.in @@ -7,6 +7,7 @@ @prefix ui: . @prefix units: . @prefix urid: . +@prefix opts: . @prefix param: . @prefix patch: . @prefix state: . @@ -33,8 +34,9 @@ ]; lv2:requiredFeature urid:map, work:schedule; - lv2:optionalFeature lv2:hardRTCapable; - lv2:extensionData work:interface, state:interface; + lv2:optionalFeature lv2:hardRTCapable, opts:options, state:threadSafeRestore; + lv2:extensionData work:interface, state:interface, options:interface; + opts:supportedOption ; rdfs:comment """ An LV2 implementation of Neural Amp Modeler. diff --git a/src/nam_lv2.cpp b/src/nam_lv2.cpp index 553dcf9..b836438 100644 --- a/src/nam_lv2.cpp +++ b/src/nam_lv2.cpp @@ -63,9 +63,12 @@ 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 }; + static const LV2_Options_Interface options = { NAM::Plugin::options_get, NAM::Plugin::options_set }; + 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_OPTIONS__interface)) + return &options; if (!strcmp(uri, LV2_STATE__interface)) return &state; if (!strcmp(uri, LV2_WORKER__interface)) diff --git a/src/nam_plugin.cpp b/src/nam_plugin.cpp index 6a2f3bf..79ac529 100644 --- a/src/nam_plugin.cpp +++ b/src/nam_plugin.cpp @@ -22,6 +22,9 @@ namespace NAM { bool Plugin::initialize(double rate, const LV2_Feature* const* features) noexcept { + // for fetching initial options, can be null + LV2_Options_Option* options = nullptr; + 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); @@ -29,6 +32,8 @@ namespace NAM { 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); + else if (std::string(features[i]->URI) == std::string(LV2_OPTIONS__options)) + options = static_cast(features[i]->data); } lv2_log_logger_set_map(&logger, map); @@ -54,6 +59,7 @@ namespace NAM { uris.atom_Int = map->map(map->handle, LV2_ATOM__Int); uris.atom_Path = map->map(map->handle, LV2_ATOM__Path); uris.atom_URID = map->map(map->handle, LV2_ATOM__URID); + uris.bufSize_maxBlockLength = map->map(map->handle, LV2_BUF_SIZE__maxBlockLength); 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); @@ -63,6 +69,9 @@ namespace NAM { uris.model_Path = map->map(map->handle, MODEL_URI); + if (options != nullptr) + options_set(this, options); + return true; } @@ -97,6 +106,18 @@ namespace NAM { // Enable model loudness normalization model->SetNormalize(true); + + // Pre-run model to ensure all needed buffers are allocated in advance + if (const int32_t numSamples = nam->maxBufferSize) + { + float* buffer = new float[numSamples]; + + std::unordered_map params = {}; + model->process(&buffer, &buffer, 1, numSamples, 1.0, 1.0, params); + model->finalize_(numSamples); + + delete[] buffer; + } } LV2SwitchModelMsg response = { kWorkTypeSwitch, {}, model }; @@ -245,6 +266,28 @@ namespace NAM { } } + uint32_t Plugin::options_get(LV2_Handle, LV2_Options_Option*) + { + // currently unused + return LV2_OPTIONS_ERR_UNKNOWN; + } + + uint32_t Plugin::options_set(LV2_Handle instance, const LV2_Options_Option* options) + { + auto nam = static_cast(instance); + + for (int i=0; options[i].key && options[i].type; ++i) + { + if (options[i].key == nam->uris.bufSize_maxBlockLength && options[i].type == nam->uris.atom_Int) + { + nam->maxBufferSize = *(const int32_t*)options[i].value; + break; + } + } + + return LV2_OPTIONS_SUCCESS; + } + 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) { @@ -385,5 +428,4 @@ namespace NAM { lv2_atom_forge_pop(&atom_forge, &frame); } - } diff --git a/src/nam_plugin.h b/src/nam_plugin.h index d48700f..9e83046 100644 --- a/src/nam_plugin.h +++ b/src/nam_plugin.h @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -80,6 +82,9 @@ namespace NAM { void write_current_path(); void write_state_changed(); + static uint32_t options_get(LV2_Handle instance, LV2_Options_Option* options); + static uint32_t options_set(LV2_Handle instance, const LV2_Options_Option* options); + 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); @@ -96,6 +101,7 @@ namespace NAM { LV2_URID atom_Int; LV2_URID atom_Path; LV2_URID atom_URID; + LV2_URID bufSize_maxBlockLength; LV2_URID patch_Set; LV2_URID patch_Get; LV2_URID patch_property; @@ -113,5 +119,6 @@ namespace NAM { float m_rate; float inputLevel = 0; float outputLevel = 0; + int32_t maxBufferSize = 0; }; }