Skip to content

Commit

Permalink
ase/lv2device.cc: don't stall audio thread while loading new presets
Browse files Browse the repository at this point in the history
See tim-janik#37.

Signed-off-by: Stefan Westerfeld <[email protected]>
  • Loading branch information
swesterfeld committed Feb 16, 2024
1 parent 8df2bce commit 591e41b
Showing 1 changed file with 147 additions and 34 deletions.
181 changes: 147 additions & 34 deletions ase/lv2device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ typedef struct {

namespace Ase {

class LV2Processor;

namespace
{

Expand Down Expand Up @@ -587,6 +589,16 @@ class PluginInstance
LilvInstance *instance_ = nullptr;
vector<Port> plugin_ports_;
vector<PresetInfo> presets_;
LV2Processor *processor_;

enum PresetState {
PRESET_STATE_READY,
PRESET_STATE_LOAD,
PRESET_STATE_FINALIZE
};
std::atomic<int> preset_state_ { PRESET_STATE_READY };
int preset_to_load_ = 0;
std::unique_ptr<PortRestoreHelper> preset_port_restore_helper_;

ControlChangedCallback control_in_changed_callback_;

Expand All @@ -605,7 +617,7 @@ class PluginInstance
static constexpr auto ANKLANG_STATE_URI = "urn:anklang:state";
public:
PluginInstance (PluginHost& plugin_host, uint sample_rate, const LilvPlugin *plugin,
PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback);
PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback, LV2Processor *processor);
~PluginInstance();

static constexpr double ui_update_fps = 60;
Expand Down Expand Up @@ -642,6 +654,10 @@ class PluginInstance

bool restore_string (const String& str, PortRestoreHelper *helper, PathMap *path_map = nullptr);
void restore_preset (int preset, PortRestoreHelper *helper);
void restore_preset_async (int preset);
void finalize_preset_restore();
bool busy_loading_preset();
void gtk_idle_timer();

String save_string (PathMap *path_map);

Expand Down Expand Up @@ -794,6 +810,8 @@ class PluginHost

nodes.init (world);
}
vector<PluginInstance *> plugin_instances_;
uint timer_id_ = 0;
public:
static PluginHost&
the()
Expand All @@ -802,7 +820,10 @@ class PluginHost
return host;
}
bool have_display() { return suil_host != nullptr; }
PluginInstance *instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback);
PluginInstance *instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback, LV2Processor *processor);
void add_instance (PluginInstance *instance);
void remove_instance (PluginInstance *instance);
void post_load();

private:
DeviceInfoS devs;
Expand Down Expand Up @@ -1114,7 +1135,7 @@ Options::Options (PluginHost& plugin_host, float sample_rate) :
}

PluginInstance *
PluginHost::instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback)
PluginHost::instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback, LV2Processor *processor)
{
assert_return (this_thread_is_gtk(), nullptr);
LilvNode* uri = lilv_new_uri (world, plugin_uri);
Expand All @@ -1140,7 +1161,7 @@ PluginHost::instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHe
}
lilv_node_free (uri);

PluginInstance *plugin_instance = new PluginInstance (*this, sample_rate, plugin, port_restore_helper, callback);
PluginInstance *plugin_instance = new PluginInstance (*this, sample_rate, plugin, port_restore_helper, callback, processor);
if (!plugin_instance->init_ok())
{
printerr ("plugin instantiate failed\n");
Expand All @@ -1152,12 +1173,49 @@ PluginHost::instantiate (const char *plugin_uri, uint sample_rate, PortRestoreHe
return plugin_instance;
}

void
PluginHost::add_instance (PluginInstance *instance)
{
if (plugin_instances_.empty() && x11wrapper)
{
/* do this for the first plugin */
timer_id_ = x11wrapper->register_timer (
[this]
{
for (auto instance : plugin_instances_)
instance->gtk_idle_timer();
return true;
},
100);
}
plugin_instances_.push_back (instance);
}

void
PluginHost::remove_instance (PluginInstance *instance)
{
std::erase (plugin_instances_, instance);
if (plugin_instances_.empty() && timer_id_)
{
/* remove if this was the last plugin */
x11wrapper->remove_timer (timer_id_);
}
}

void
PluginHost::post_load()
{
for (auto instance : plugin_instances_)
instance->finalize_preset_restore();
}

PluginInstance::PluginInstance (PluginHost& plugin_host, uint sample_rate, const LilvPlugin *plugin,
PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback) :
PortRestoreHelper *port_restore_helper, const ControlChangedCallback& callback, LV2Processor *processor) :
options_ (plugin_host, sample_rate),
plugin_ (plugin),
sample_rate_ (sample_rate),
plugin_host_ (plugin_host),
processor_ (processor),
control_in_changed_callback_ (callback)
{
assert_return (this_thread_is_gtk());
Expand Down Expand Up @@ -1206,13 +1264,15 @@ PluginInstance::PluginInstance (PluginHost& plugin_host, uint sample_rate, const
lilv_state_free (default_state);
}
}
plugin_host_.add_instance (this);
init_ok_ = true;
}

PluginInstance::~PluginInstance()
{
assert_return (this_thread_is_gtk());

plugin_host_.remove_instance (this);
if (worker_)
worker_->stop();

Expand Down Expand Up @@ -1891,19 +1951,6 @@ PluginInstance::restore_string (const String& str, PortRestoreHelper *helper, Pa
}
}

void
PluginInstance::restore_preset (int preset, PortRestoreHelper *helper)
{
assert_return (this_thread_is_gtk());
assert_return (preset >= 0 && preset < int (presets_.size()));

if (LilvState *state = lilv_state_new_from_world (plugin_host_.world, plugin_host_.urid_map.lv2_map(), presets_[preset].preset))
{
restore_state (state, helper);
lilv_state_free (state);
}
}

const void*
PluginInstance::get_port_value_for_save (const char *port_symbol,
void *user_data,
Expand Down Expand Up @@ -2024,7 +2071,7 @@ class LV2Processor : public AudioProcessor {
set_param_from_render (PID_CONTROL_OFFSET + port->control_in_idx, port->param_from_lv2 (port->control));
};

gtk_thread ([&] { plugin_instance_ = plugin_host_.instantiate (lv2_uri_.c_str(), sample_rate(), &port_restore_helper, control_in_changed_callback); });
gtk_thread ([&] { plugin_instance_ = plugin_host_.instantiate (lv2_uri_.c_str(), sample_rate(), &port_restore_helper, control_in_changed_callback, this); });

// TODO: this is probably not the right location for restoring the parameter values
restore_params (port_restore_helper);
Expand Down Expand Up @@ -2135,20 +2182,10 @@ class LV2Processor : public AudioProcessor {
if (int (tag) == PID_PRESET)
{
int want_preset = irintf (get_param (tag));
if (current_preset_ != want_preset)
if (current_preset_ != want_preset && !plugin_instance_->busy_loading_preset())
{
current_preset_ = want_preset;

// TODO: blocking the audio thread here is a bad idea
PortRestoreHelper port_restore_helper (plugin_host_);
gtk_thread ([&] { plugin_instance_->restore_preset (want_preset - 1, &port_restore_helper); });

// TODO: evil (possibly crashing) broken hack to set the parameters:
// -> should be replaced by something else once presets are loaded outside the audio thread
main_loop->exec_idle ([port_restore_helper, this] () // <- delete source required if processor is destroyed
{
restore_params (port_restore_helper);
});
plugin_instance_->restore_preset_async (want_preset - 1);
}
}

Expand Down Expand Up @@ -2199,7 +2236,25 @@ class LV2Processor : public AudioProcessor {
}
}

uint n_audio_inputs = plugin_instance_->n_audio_inputs();
const uint n_audio_inputs = plugin_instance_->n_audio_inputs();
const uint n_audio_outputs = plugin_instance_->n_audio_outputs();

if (plugin_instance_->busy_loading_preset())
{
/* we need to guaranee that run() is not called while loading a preset */
if (n_audio_outputs == 2)
{
floatfill (oblock (stereo_out_, 0), 0, n_frames);
floatfill (oblock (stereo_out_, 1), 0, n_frames);
}
else
{
for (uint i = 0; i < n_audio_outputs; i++)
floatfill (oblock (mono_outs_[i], 0), 0, n_frames);
}
return;
}

if (n_audio_inputs == 2)
{
plugin_instance_->connect_audio_in (0, ifloats (stereo_in_, 0));
Expand All @@ -2211,7 +2266,6 @@ class LV2Processor : public AudioProcessor {
plugin_instance_->connect_audio_in (i, ifloats (mono_ins_[i], 0));
}

uint n_audio_outputs = plugin_instance_->n_audio_outputs();
if (n_audio_outputs == 2)
{
plugin_instance_->connect_audio_out (0, oblock (stereo_out_, 0));
Expand Down Expand Up @@ -2259,6 +2313,7 @@ class LV2Processor : public AudioProcessor {
}
return AudioProcessor::param_value_from_text (paramid, text);
}
public:
void
restore_params (const PortRestoreHelper &port_restore_helper)
{
Expand All @@ -2270,7 +2325,6 @@ class LV2Processor : public AudioProcessor {
send_param (i + PID_CONTROL_OFFSET, port.param_from_lv2 (it->second));
}
}
public:
LV2Processor (const ProcessorSetup &psetup) :
AudioProcessor (psetup),
plugin_host_ (PluginHost::the())
Expand Down Expand Up @@ -2392,6 +2446,65 @@ class LV2Processor : public AudioProcessor {
}
};

void
PluginInstance::restore_preset (int preset, PortRestoreHelper *helper)
{
assert_return (this_thread_is_gtk());
assert_return (preset >= 0 && preset < int (presets_.size()));

if (LilvState *state = lilv_state_new_from_world (plugin_host_.world, plugin_host_.urid_map.lv2_map(), presets_[preset].preset))
{
restore_state (state, helper);
lilv_state_free (state);
}
}

void
PluginInstance::restore_preset_async (int preset)
{
preset_to_load_ = preset;
preset_state_.store (PRESET_STATE_LOAD);
}

bool
PluginInstance::busy_loading_preset()
{
return preset_state_.load() != PRESET_STATE_READY;
}

void
PluginInstance::gtk_idle_timer()
{
if (preset_state_.load() == PRESET_STATE_LOAD)
{
assert_return (this_thread_is_gtk());

preset_port_restore_helper_ = std::make_unique<PortRestoreHelper> (plugin_host_);
restore_preset (preset_to_load_, preset_port_restore_helper_.get());

// send new parameters from ASE thread
preset_state_.store (PRESET_STATE_FINALIZE);

// TODO: it may be possible to restore the parameters here as well, however
// this would mean that AudioProcessor::send_param should be supported from
// the gtk thread as well
main_loop->exec_idle ([] { PluginHost::the().post_load(); });
}
}

void
PluginInstance::finalize_preset_restore()
{
if (preset_state_.load() == PRESET_STATE_FINALIZE)
{
assert_return (this_thread_is_ase());

processor_->restore_params (*preset_port_restore_helper_.get());
preset_port_restore_helper_.reset();
preset_state_.store (PRESET_STATE_READY);
}
}

static auto lv2processor_aseid = register_audio_processor<LV2Processor>();

DeviceInfoS
Expand Down

0 comments on commit 591e41b

Please sign in to comment.