diff options
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | src/jack.c | 121 | ||||
-rw-r--r-- | src/jalv.c | 251 | ||||
-rw-r--r-- | src/jalv.h | 72 | ||||
-rw-r--r-- | src/jalv_console.c | 9 | ||||
-rw-r--r-- | src/jalv_qt.cpp | 8 | ||||
-rw-r--r-- | src/port.h | 15 | ||||
-rw-r--r-- | src/portaudio.c | 39 | ||||
-rw-r--r-- | src/process.c | 78 | ||||
-rw-r--r-- | src/process.h | 63 | ||||
-rw-r--r-- | src/process_setup.c | 198 | ||||
-rw-r--r-- | src/process_setup.h | 70 | ||||
-rw-r--r-- | src/state.c | 36 |
13 files changed, 589 insertions, 374 deletions
@@ -7,6 +7,7 @@ jalv (1.6.9) unstable; urgency=medium * Build Qt UI with -fPIC * Clean up and strengthen code * Clean up command line help output + * Cleanly separate audio thread from the rest of the application * Fix Jack latency recomputation when plugin latency changes * Fix clashing command line options * Fix minor memory leaks @@ -21,7 +22,7 @@ jalv (1.6.9) unstable; urgency=medium * Use fewer platform-specific APIs * Use portable zix filesystem API - -- David Robillard <d@drobilla.net> Sat, 16 Nov 2024 01:37:46 +0000 + -- David Robillard <d@drobilla.net> Fri, 22 Nov 2024 18:13:15 +0000 jalv (1.6.8) stable; urgency=medium @@ -10,7 +10,6 @@ #include "jalv_config.h" #include "log.h" #include "lv2_evbuf.h" -#include "port.h" #include "process.h" #include "process_setup.h" #include "settings.h" @@ -54,14 +53,15 @@ buffer_size_cb(jack_nframes_t nframes, void* data) { Jalv* const jalv = (Jalv*)data; JalvSettings* const settings = &jalv->settings; + JalvProcess* const proc = &jalv->process; settings->block_length = nframes; #if USE_JACK_PORT_TYPE_GET_BUFFER_SIZE settings->midi_buf_size = jack_port_type_get_buffer_size( jalv->backend->client, JACK_DEFAULT_MIDI_TYPE); #endif - if (jalv->run_state == JALV_RUNNING) { - jalv_allocate_port_buffers(jalv); + if (proc->run_state == JALV_RUNNING) { + jalv_process_activate(proc, &jalv->urids, proc->instance, &jalv->settings); } return 0; } @@ -102,11 +102,11 @@ forge_position(LV2_Atom_Forge* const forge, } static int -process_silent(Jalv* const jalv, const jack_nframes_t nframes) +process_silent(JalvProcess* const proc, const jack_nframes_t nframes) { - for (uint32_t p = 0U; p < jalv->num_ports; ++p) { - JalvPort* const port = &jalv->ports[p]; - jack_port_t* const jport = (jack_port_t*)jalv->ports[p].sys_port; + for (uint32_t p = 0U; p < proc->num_ports; ++p) { + JalvProcessPort* const port = &proc->ports[p]; + jack_port_t* const jport = (jack_port_t*)proc->ports[p].sys_port; if (jport && port->flow == FLOW_OUTPUT) { void* const buf = jack_port_get_buffer(jport, nframes); if (port->type == TYPE_EVENT) { @@ -117,11 +117,11 @@ process_silent(Jalv* const jalv, const jack_nframes_t nframes) } } - return jalv_bypass(jalv, nframes); + return jalv_bypass(proc, nframes); } static bool -process_transport(Jalv* const jalv, +process_transport(JalvProcess* const proc, const jack_transport_state_t state, const jack_position_t pos, const jack_nframes_t nframes) @@ -130,13 +130,13 @@ process_transport(Jalv* const jalv, const bool rolling = state == JackTransportRolling; const bool has_bbt = (pos.valid & JackPositionBBT); const bool xport_changed = - (rolling != jalv->rolling || pos.frame != jalv->position || - (has_bbt && pos.beats_per_minute != jalv->bpm)); + (rolling != proc->rolling || pos.frame != proc->position || + (has_bbt && pos.beats_per_minute != proc->bpm)); // Update transport state to expected values for next cycle - jalv->position = rolling ? pos.frame + nframes : pos.frame; - jalv->bpm = has_bbt ? pos.beats_per_minute : jalv->bpm; - jalv->rolling = rolling; + proc->position = rolling ? pos.frame + nframes : pos.frame; + proc->bpm = has_bbt ? pos.beats_per_minute : proc->bpm; + proc->rolling = rolling; return xport_changed; } @@ -147,13 +147,14 @@ process_cb(jack_nframes_t nframes, void* data) { Jalv* const jalv = (Jalv*)data; const JalvURIDs* const urids = &jalv->urids; + JalvProcess* const proc = &jalv->process; jack_client_t* const client = jalv->backend->client; uint64_t pos_buf[64] = {0U}; LV2_Atom* const lv2_pos = (LV2_Atom*)pos_buf; // If execution is paused, emit silence and return - if (jalv->run_state == JALV_PAUSED) { - return process_silent(jalv, nframes); + if (proc->run_state == JALV_PAUSED) { + return process_silent(proc, nframes); } // Get transport state and position @@ -161,20 +162,20 @@ process_cb(jack_nframes_t nframes, void* data) const jack_transport_state_t state = jack_transport_query(client, &pos); // Check if transport is discontinuous from last time and update state - const bool xport_changed = process_transport(jalv, state, pos, nframes); + const bool xport_changed = process_transport(proc, state, pos, nframes); if (xport_changed) { // Build an LV2 position object to report change to plugin - lv2_atom_forge_set_buffer(&jalv->forge, (uint8_t*)pos_buf, sizeof(pos_buf)); - forge_position(&jalv->forge, urids, state, pos); + lv2_atom_forge_set_buffer(&proc->forge, (uint8_t*)pos_buf, sizeof(pos_buf)); + forge_position(&proc->forge, urids, state, pos); } // Prepare port buffers - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - JalvPort* const port = &jalv->ports[p]; + for (uint32_t p = 0; p < proc->num_ports; ++p) { + JalvProcessPort* const port = &proc->ports[p]; if (port->sys_port && (port->type == TYPE_AUDIO || port->type == TYPE_CV)) { // Connect plugin port directly to Jack port buffer lilv_instance_connect_port( - jalv->instance, p, jack_port_get_buffer(port->sys_port, nframes)); + proc->instance, p, jack_port_get_buffer(port->sys_port, nframes)); } else if (port->type == TYPE_EVENT && port->flow == FLOW_INPUT) { lv2_evbuf_reset(port->evbuf, true); LV2_Evbuf_Iterator iter = lv2_evbuf_begin(port->evbuf); @@ -202,26 +203,26 @@ process_cb(jack_nframes_t nframes, void* data) } // Run plugin for this cycle - const bool send_ui_updates = jalv_run(jalv, nframes); + const bool send_ui_updates = jalv_run(proc, nframes); // Deliver MIDI output and UI events - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - JalvPort* const port = &jalv->ports[p]; + for (uint32_t p = 0; p < proc->num_ports; ++p) { + JalvProcessPort* const port = &proc->ports[p]; if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && port->reports_latency) { // Get the latency in frames from the control output truncated to integer - const float value = jalv->controls_buf[p]; + const float value = proc->controls_buf[p]; const uint32_t frames = (value >= 0.0f && value <= max_latency) ? (uint32_t)value : 0U; - if (jalv->plugin_latency != frames) { + if (proc->plugin_latency != frames) { // Update the cached value and notify the UI if the latency changed - jalv->plugin_latency = frames; + proc->plugin_latency = frames; const JalvLatencyChange body = {frames}; const JalvMessageHeader header = {LATENCY_CHANGE, sizeof(body)}; jalv_write_split_message( - jalv->plugin_to_ui, &header, sizeof(header), &body, sizeof(body)); + proc->plugin_to_ui, &header, sizeof(header), &body, sizeof(body)); } } else if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { void* buf = NULL; @@ -246,14 +247,14 @@ process_cb(jack_nframes_t nframes, void* data) jack_midi_event_write(buf, frames, body, size); } - if (jalv->has_ui) { + if (proc->has_ui) { // Forward event to UI - jalv_write_event(jalv->plugin_to_ui, p, size, type, body); + jalv_write_event(proc->plugin_to_ui, p, size, type, body); } } } else if (send_ui_updates && port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL) { - jalv_write_control(jalv->plugin_to_ui, p, jalv->controls_buf[p]); + jalv_write_control(proc->plugin_to_ui, p, proc->controls_buf[p]); } } @@ -266,15 +267,16 @@ latency_cb(const jack_latency_callback_mode_t mode, void* const data) { // Calculate latency assuming all ports depend on each other - const Jalv* const jalv = (const Jalv*)data; - const PortFlow flow = + const Jalv* const jalv = (const Jalv*)data; + const JalvProcess* const proc = &jalv->process; + const PortFlow flow = ((mode == JackCaptureLatency) ? FLOW_INPUT : FLOW_OUTPUT); // First calculate the min/max latency of all feeding ports uint32_t ports_found = 0; jack_latency_range_t range = {UINT32_MAX, 0}; - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - JalvPort* const port = &jalv->ports[p]; + for (uint32_t p = 0; p < proc->num_ports; ++p) { + JalvProcessPort* const port = &proc->ports[p]; if (port->sys_port && port->flow == flow) { jack_latency_range_t r; jack_port_get_latency_range(port->sys_port, mode, &r); @@ -293,12 +295,12 @@ latency_cb(const jack_latency_callback_mode_t mode, void* const data) } // Add the plugin's own latency - range.min += jalv->plugin_latency; - range.max += jalv->plugin_latency; + range.min += proc->plugin_latency; + range.max += proc->plugin_latency; // Tell Jack about it - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - const JalvPort* const port = &jalv->ports[p]; + for (uint32_t p = 0; p < proc->num_ports; ++p) { + const JalvProcessPort* const port = &proc->ports[p]; if (port->sys_port && port->flow == flow) { jack_port_set_latency_range(port->sys_port, mode, &range); } @@ -408,14 +410,13 @@ jalv_backend_deactivate(Jalv* jalv) void jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) { - jack_client_t* client = jalv->backend->client; - JalvPort* const port = &jalv->ports[port_index]; - - const LilvNode* sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port); + jack_client_t* const client = jalv->backend->client; + JalvProcess* const proc = &jalv->process; + JalvProcessPort* const port = &proc->ports[port_index]; // Connect unsupported ports to NULL (known to be optional by this point) if (port->flow == FLOW_UNKNOWN || port->type == TYPE_UNKNOWN) { - lilv_instance_connect_port(jalv->instance, port_index, NULL); + lilv_instance_connect_port(proc->instance, port_index, NULL); return; } @@ -429,16 +430,16 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) break; case TYPE_CONTROL: lilv_instance_connect_port( - jalv->instance, port_index, &jalv->controls_buf[port_index]); + proc->instance, port_index, &proc->controls_buf[port_index]); break; case TYPE_AUDIO: port->sys_port = jack_port_register( - client, lilv_node_as_string(sym), JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); + client, port->symbol, JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); break; #if USE_JACK_METADATA case TYPE_CV: port->sys_port = jack_port_register( - client, lilv_node_as_string(sym), JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); + client, port->symbol, JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); if (port->sys_port) { jack_set_property(client, jack_port_uuid(port->sys_port), @@ -449,13 +450,9 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) break; #endif case TYPE_EVENT: - if (lilv_port_supports_event( - jalv->plugin, port->lilv_port, jalv->nodes.midi_MidiEvent)) { - port->sys_port = jack_port_register(client, - lilv_node_as_string(sym), - JACK_DEFAULT_MIDI_TYPE, - jack_flags, - 0); + if (port->supports_midi) { + port->sys_port = jack_port_register( + client, port->symbol, JACK_DEFAULT_MIDI_TYPE, jack_flags, 0); } break; } @@ -472,13 +469,13 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) "http://www.w3.org/2001/XMLSchema#integer"); // Set port pretty name to label - LilvNode* name = lilv_port_get_name(jalv->plugin, port->lilv_port); - jack_set_property(client, - jack_port_uuid(port->sys_port), - JACK_METADATA_PRETTY_NAME, - lilv_node_as_string(name), - "text/plain"); - lilv_node_free(name); + if (port->label) { + jack_set_property(client, + jack_port_uuid(port->sys_port), + JACK_METADATA_PRETTY_NAME, + port->label, + "text/plain"); + } } #endif } @@ -1,4 +1,4 @@ -// Copyright 2007-2022 David Robillard <d@drobilla.net> +// Copyright 2007-2024 David Robillard <d@drobilla.net> // SPDX-License-Identifier: ISC #include "jalv.h" @@ -16,8 +16,8 @@ #include "nodes.h" #include "options.h" #include "port.h" +#include "process.h" #include "process_setup.h" -#include "query.h" #include "settings.h" #include "state.h" #include "string_utils.h" @@ -101,36 +101,27 @@ create_port(Jalv* jalv, uint32_t port_index) JalvPort* const port = &jalv->ports[port_index]; port->lilv_port = lilv_plugin_get_port_by_index(jalv->plugin, port_index); - port->sys_port = NULL; - port->evbuf = NULL; - port->buf_size = 0; port->index = port_index; port->flow = FLOW_UNKNOWN; - const bool optional = lilv_port_has_property( - jalv->plugin, port->lilv_port, jalv->nodes.lv2_connectionOptional); - - // Set the port flow (input or output) - if (lilv_port_is_a( - jalv->plugin, port->lilv_port, jalv->nodes.lv2_InputPort)) { - port->flow = FLOW_INPUT; - } else if (lilv_port_is_a( - jalv->plugin, port->lilv_port, jalv->nodes.lv2_OutputPort)) { - port->flow = FLOW_OUTPUT; - } else if (!optional) { - jalv_log(JALV_LOG_ERR, "Mandatory port is neither input nor output\n"); + JalvProcessPort* const pport = &jalv->process.ports[port_index]; + if (jalv_process_port_init(&jalv->process.ports[port_index], + &jalv->nodes, + jalv->plugin, + port->lilv_port)) { return 1; } - const bool hidden = !jalv->opts.show_hidden && - lilv_port_has_property(jalv->plugin, - port->lilv_port, - jalv->nodes.pprops_notOnGUI); + port->type = pport->type; + port->flow = pport->flow; - // Set control values if (lilv_port_is_a( jalv->plugin, port->lilv_port, jalv->nodes.lv2_ControlPort)) { - port->type = TYPE_CONTROL; + const bool hidden = !jalv->opts.show_hidden && + lilv_port_has_property(jalv->plugin, + port->lilv_port, + jalv->nodes.pprops_notOnGUI); + if (!hidden) { add_control(&jalv->controls, new_port_control(jalv->world, @@ -141,52 +132,19 @@ create_port(Jalv* jalv, uint32_t port_index) &jalv->nodes, &jalv->forge)); } - } else if (lilv_port_is_a( - jalv->plugin, port->lilv_port, jalv->nodes.lv2_AudioPort)) { - port->type = TYPE_AUDIO; -#if USE_JACK_METADATA - } else if (lilv_port_is_a( - jalv->plugin, port->lilv_port, jalv->nodes.lv2_CVPort)) { - port->type = TYPE_CV; -#endif - } else if (lilv_port_is_a( - jalv->plugin, port->lilv_port, jalv->nodes.atom_AtomPort)) { - port->type = TYPE_EVENT; - } else if (!optional) { - jalv_log(JALV_LOG_ERR, "Mandatory port has unknown data type\n"); - return 2; } - // Set buffer size - LilvNode* min_size = - lilv_port_get(jalv->plugin, port->lilv_port, jalv->nodes.rsz_minimumSize); - if (min_size && lilv_node_is_int(min_size)) { - port->buf_size = lilv_node_as_int(min_size); - jalv->opts.ring_size = - MAX(jalv->opts.ring_size, port->buf_size * N_BUFFER_CYCLES); - jalv->msg_buf_size = MAX(jalv->msg_buf_size, port->buf_size); - } - lilv_node_free(min_size); - - // Set primary flag for designated control port - if (port->type == TYPE_EVENT && - jalv_port_has_designation( - &jalv->nodes, jalv->plugin, port->lilv_port, jalv->nodes.lv2_control)) { - port->is_primary = true; - if (port->flow == FLOW_INPUT && jalv->control_in == UINT32_MAX) { - jalv->control_in = port->index; - } + // Store index if this is the designated control input port + if (jalv->process.control_in == UINT32_MAX && pport->is_primary && + port->flow == FLOW_INPUT && port->type == TYPE_EVENT) { + jalv->process.control_in = port_index; } - // Set reports_latency flag - if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && - (lilv_port_has_property( - jalv->plugin, port->lilv_port, jalv->nodes.lv2_reportsLatency) || - jalv_port_has_designation(&jalv->nodes, - jalv->plugin, - port->lilv_port, - jalv->nodes.lv2_latency))) { - port->reports_latency = true; + // Update maximum buffer sizes + const uint32_t buf_size = pport->buf_size; + jalv->opts.ring_size = MAX(jalv->opts.ring_size, buf_size * N_BUFFER_CYCLES); + if (port->flow == FLOW_OUTPUT) { + jalv->ui_msg_size = MAX(jalv->ui_msg_size, buf_size); } return 0; @@ -196,13 +154,18 @@ create_port(Jalv* jalv, uint32_t port_index) static int jalv_create_ports(Jalv* jalv) { - jalv->num_ports = lilv_plugin_get_num_ports(jalv->plugin); - jalv->ports = (JalvPort*)calloc(jalv->num_ports, sizeof(JalvPort)); + const uint32_t n_ports = lilv_plugin_get_num_ports(jalv->plugin); + + jalv->num_ports = n_ports; + jalv->ports = (JalvPort*)calloc(n_ports, sizeof(JalvPort)); + jalv->process.num_ports = n_ports; + jalv->process.ports = + (JalvProcessPort*)calloc(n_ports, sizeof(JalvProcessPort)); // Allocate control port buffers array and set to default values - jalv->controls_buf = (float*)calloc(jalv->num_ports, sizeof(float)); + jalv->process.controls_buf = (float*)calloc(n_ports, sizeof(float)); lilv_plugin_get_port_ranges_float( - jalv->plugin, NULL, NULL, jalv->controls_buf); + jalv->plugin, NULL, NULL, jalv->process.controls_buf); for (uint32_t i = 0; i < jalv->num_ports; ++i) { if (create_port(jalv, i)) { @@ -314,8 +277,9 @@ jalv_send_to_plugin(void* const jalv_handle, const uint32_t protocol, const void* const buffer) { - Jalv* const jalv = (Jalv*)jalv_handle; - ZixStatus st = ZIX_STATUS_SUCCESS; + Jalv* const jalv = (Jalv*)jalv_handle; + JalvProcess* const proc = &jalv->process; + ZixStatus st = ZIX_STATUS_SUCCESS; if (port_index >= jalv->num_ports) { jalv_log(JALV_LOG_ERR, "UI wrote to invalid port index %u\n", port_index); @@ -325,7 +289,7 @@ jalv_send_to_plugin(void* const jalv_handle, st = ZIX_STATUS_BAD_ARG; } else { const float value = *(const float*)buffer; - st = jalv_write_control(jalv->ui_to_plugin, port_index, value); + st = jalv_write_control(proc->ui_to_plugin, port_index, value); } } else if (protocol == jalv->urids.atom_eventTransfer) { @@ -336,7 +300,7 @@ jalv_send_to_plugin(void* const jalv_handle, } else { jalv_dump_atom(jalv->dumper, stdout, "UI => Plugin", atom, 36); st = jalv_write_event( - jalv->ui_to_plugin, port_index, atom->size, atom->type, atom + 1U); + proc->ui_to_plugin, port_index, atom->size, atom->type, atom + 1U); } } else { @@ -361,23 +325,22 @@ jalv_set_control(Jalv* jalv, const void* body) { if (control->type == PORT && type == jalv->forge.Float) { - jalv->controls_buf[control->index] = *(const float*)body; - } else if (control->type == PROPERTY && jalv->control_in != UINT32_MAX) { - // Copy forge since it is used by process thread - LV2_Atom_Forge forge = jalv->forge; + jalv->process.controls_buf[control->index] = *(const float*)body; + } else if (control->type == PROPERTY && + jalv->process.control_in != UINT32_MAX) { LV2_Atom_Forge_Frame frame; - lv2_atom_forge_set_buffer(&forge, jalv->ui_msg, jalv->msg_buf_size); + lv2_atom_forge_set_buffer(&jalv->forge, jalv->ui_msg, jalv->ui_msg_size); - lv2_atom_forge_object(&forge, &frame, 0, jalv->urids.patch_Set); - lv2_atom_forge_key(&forge, jalv->urids.patch_property); - lv2_atom_forge_urid(&forge, control->property); - lv2_atom_forge_key(&forge, jalv->urids.patch_value); - lv2_atom_forge_atom(&forge, size, type); - lv2_atom_forge_write(&forge, body, size); + lv2_atom_forge_object(&jalv->forge, &frame, 0, jalv->urids.patch_Set); + lv2_atom_forge_key(&jalv->forge, jalv->urids.patch_property); + lv2_atom_forge_urid(&jalv->forge, control->property); + lv2_atom_forge_key(&jalv->forge, jalv->urids.patch_value); + lv2_atom_forge_atom(&jalv->forge, size, type); + lv2_atom_forge_write(&jalv->forge, body, size); - const LV2_Atom* atom = lv2_atom_forge_deref(&forge, frame.ref); + const LV2_Atom* atom = lv2_atom_forge_deref(&jalv->forge, frame.ref); jalv_send_to_plugin(jalv, - jalv->control_in, + jalv->process.control_in, lv2_atom_total_size(atom), jalv->urids.atom_eventTransfer, atom); @@ -399,13 +362,15 @@ void jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) { #if USE_SUIL + LilvInstance* const instance = jalv->process.instance; + jalv->ui_host = suil_host_new(jalv_send_to_plugin, jalv_ui_port_index, NULL, NULL); const LV2_Feature parent_feature = {LV2_UI__parent, parent}; - const LV2_Feature instance_feature = { - LV2_INSTANCE_ACCESS_URI, lilv_instance_get_handle(jalv->instance)}; + const LV2_Feature instance_feature = {LV2_INSTANCE_ACCESS_URI, + lilv_instance_get_handle(instance)}; const LV2_Feature data_feature = {LV2_DATA_ACCESS_URI, &jalv->features.ext_data}; @@ -455,25 +420,24 @@ jalv_init_ui(Jalv* jalv) for (uint32_t i = 0; i < jalv->num_ports; ++i) { if (jalv->ports[i].type == TYPE_CONTROL) { jalv_frontend_port_event( - jalv, i, sizeof(float), 0, &jalv->controls_buf[i]); + jalv, i, sizeof(float), 0, &jalv->process.controls_buf[i]); } } - if (jalv->control_in != UINT32_MAX) { + if (jalv->process.control_in != UINT32_MAX) { // Send patch:Get message for initial parameters/etc - LV2_Atom_Forge forge = jalv->forge; LV2_Atom_Forge_Frame frame; uint64_t buf[4U] = {0U, 0U, 0U, 0U}; - lv2_atom_forge_set_buffer(&forge, (uint8_t*)buf, sizeof(buf)); - lv2_atom_forge_object(&forge, &frame, 0, jalv->urids.patch_Get); + lv2_atom_forge_set_buffer(&jalv->forge, (uint8_t*)buf, sizeof(buf)); + lv2_atom_forge_object(&jalv->forge, &frame, 0, jalv->urids.patch_Get); - const LV2_Atom* atom = lv2_atom_forge_deref(&forge, frame.ref); + const LV2_Atom* atom = lv2_atom_forge_deref(&jalv->forge, frame.ref); jalv_send_to_plugin(jalv, - jalv->control_in, + jalv->process.control_in, lv2_atom_total_size(atom), jalv->urids.atom_eventTransfer, atom); - lv2_atom_forge_pop(&forge, &frame); + lv2_atom_forge_pop(&jalv->forge, &frame); } } @@ -494,7 +458,7 @@ jalv_update(Jalv* jalv) } // Emit UI events - ZixRing* const ring = jalv->plugin_to_ui; + ZixRing* const ring = jalv->process.plugin_to_ui; JalvMessageHeader header = {NO_MESSAGE, 0U}; const size_t space = zix_ring_read_space(ring); for (size_t i = 0; i < space; i += sizeof(header) + header.size) { @@ -744,14 +708,10 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) LilvWorld* const world = lilv_world_new(); lilv_world_load_all(world); - jalv->world = world; - jalv->mapper = jalv_mapper_new(); - jalv->msg_buf_size = 1024U; - jalv->run_state = JALV_PAUSED; - jalv->bpm = 120.0f; - jalv->control_in = UINT32_MAX; - jalv->log.urids = &jalv->urids; - jalv->log.tracing = jalv->opts.trace; + jalv->world = world; + jalv->mapper = jalv_mapper_new(); + jalv->log.urids = &jalv->urids; + jalv->log.tracing = jalv->opts.trace; // Set up atom dumping for debugging if enabled LV2_URID_Map* const urid_map = jalv_mapper_urid_map(jalv->mapper); @@ -762,8 +722,6 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) zix_sem_init(&jalv->work_lock, 1); zix_sem_init(&jalv->done, 0); - zix_sem_init(&jalv->paused, 0); - jalv_init_urids(jalv->mapper, &jalv->urids); jalv_init_nodes(world, &jalv->nodes); jalv_init_features(jalv); @@ -812,11 +770,11 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) // Create workers if necessary if (lilv_plugin_has_extension_data(jalv->plugin, jalv->nodes.work_interface)) { - jalv->worker = jalv_worker_new(&jalv->work_lock, true); - jalv->features.sched.handle = jalv->worker; + jalv->process.worker = jalv_worker_new(&jalv->work_lock, true); + jalv->features.sched.handle = jalv->process.worker; if (jalv->safe_restore) { - jalv->state_worker = jalv_worker_new(&jalv->work_lock, false); - jalv->features.ssched.handle = jalv->state_worker; + jalv->process.state_worker = jalv_worker_new(&jalv->work_lock, false); + jalv->features.ssched.handle = jalv->process.state_worker; } } @@ -868,6 +826,11 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) } } + // Initialize process thread + const uint32_t update_frames = + (uint32_t)(settings->sample_rate / settings->ui_update_hz); + jalv_process_init(&jalv->process, &jalv->urids, jalv->mapper, update_frames); + // Create port structures if (jalv_create_ports(jalv)) { return -10; @@ -888,15 +851,10 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) jalv_log(JALV_LOG_INFO, "MIDI buffers: %zu bytes\n", settings->midi_buf_size); jalv_init_ui_settings(jalv); - jalv_init_lv2_options(&jalv->features, &jalv->urids, &jalv->settings); + jalv_init_lv2_options(&jalv->features, &jalv->urids, settings); - // Create Plugin <=> UI communication buffers - jalv->audio_msg = zix_aligned_alloc(NULL, 8U, jalv->msg_buf_size); - jalv->ui_msg = zix_aligned_alloc(NULL, 8U, jalv->msg_buf_size); - jalv->ui_to_plugin = zix_ring_new(NULL, jalv->opts.ring_size); - jalv->plugin_to_ui = zix_ring_new(NULL, jalv->opts.ring_size); - zix_ring_mlock(jalv->ui_to_plugin); - zix_ring_mlock(jalv->plugin_to_ui); + // Create Plugin => UI communication buffers + jalv->ui_msg = zix_aligned_alloc(NULL, 8U, jalv->ui_msg_size); // Build feature list for passing to plugins const LV2_Feature* const features[] = {&jalv->features.map_feature, @@ -929,28 +887,31 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) lilv_nodes_free(req_feats); // Instantiate the plugin - jalv->instance = lilv_plugin_instantiate( + LilvInstance* const instance = lilv_plugin_instantiate( jalv->plugin, settings->sample_rate, jalv->feature_list); - if (!jalv->instance) { + if (!instance) { jalv_log(JALV_LOG_ERR, "Failed to instantiate plugin\n"); return -9; } // Point things to the instance that require it + // jalv->process.instance = instance; jalv->features.ext_data.data_access = - lilv_instance_get_descriptor(jalv->instance)->extension_data; + lilv_instance_get_descriptor(instance)->extension_data; const LV2_Worker_Interface* worker_iface = (const LV2_Worker_Interface*)lilv_instance_get_extension_data( - jalv->instance, LV2_WORKER__interface); + instance, LV2_WORKER__interface); - jalv_worker_attach(jalv->worker, worker_iface, jalv->instance->lv2_handle); + jalv_worker_attach(jalv->process.worker, worker_iface, instance->lv2_handle); jalv_worker_attach( - jalv->state_worker, worker_iface, jalv->instance->lv2_handle); - + jalv->process.state_worker, worker_iface, instance->lv2_handle); jalv_log(JALV_LOG_INFO, "\n"); - jalv_allocate_port_buffers(jalv); + + // Allocate port buffers + jalv_process_activate( + &jalv->process, &jalv->urids, instance, &jalv->settings); // Apply loaded state to plugin instance if necessary if (state) { @@ -971,20 +932,20 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) } // Discover UI - jalv->has_ui = jalv_frontend_discover(jalv); + jalv->process.has_ui = jalv_frontend_discover(jalv); return 0; } int jalv_activate(Jalv* const jalv) { - jalv->run_state = JALV_RUNNING; + jalv->process.run_state = JALV_RUNNING; if (jalv->backend) { - if (jalv->worker) { - jalv_worker_launch(jalv->worker); + if (jalv->process.worker) { + jalv_worker_launch(jalv->process.worker); } - lilv_instance_activate(jalv->instance); + lilv_instance_activate(jalv->process.instance); jalv_backend_activate(jalv); } @@ -997,14 +958,14 @@ jalv_deactivate(Jalv* const jalv) if (jalv->backend) { jalv_backend_deactivate(jalv); } - if (jalv->instance) { - lilv_instance_deactivate(jalv->instance); + if (jalv->process.instance) { + lilv_instance_deactivate(jalv->process.instance); } - if (jalv->worker) { - jalv_worker_exit(jalv->worker); + if (jalv->process.worker) { + jalv_worker_exit(jalv->process.worker); } - jalv->run_state = JALV_PAUSED; + jalv->process.run_state = JALV_PAUSED; return 0; } @@ -1013,31 +974,25 @@ jalv_close(Jalv* const jalv) { // Stop audio processing, free event port buffers, and close backend jalv_deactivate(jalv); - jalv_free_port_buffers(jalv); + jalv_process_deactivate(&jalv->process); if (jalv->backend) { jalv_backend_close(jalv); } - // Destroy the worker - jalv_worker_free(jalv->worker); - jalv_worker_free(jalv->state_worker); - // Free UI and plugin instances #if USE_SUIL suil_instance_free(jalv->ui_instance); #endif - if (jalv->instance) { - lilv_instance_free(jalv->instance); + if (jalv->process.instance) { + lilv_instance_free(jalv->process.instance); } // Clean up lilv_state_free(jalv->preset); free(jalv->ports); - zix_ring_free(jalv->ui_to_plugin); - zix_ring_free(jalv->plugin_to_ui); + jalv_process_cleanup(&jalv->process); zix_free(NULL, jalv->ui_msg); - zix_free(NULL, jalv->audio_msg); - free(jalv->controls_buf); + free(jalv->process.controls_buf); jalv_free_nodes(&jalv->nodes); #if USE_SUIL suil_host_free(jalv->ui_host); @@ -14,10 +14,10 @@ #include "nodes.h" #include "options.h" #include "port.h" +#include "process.h" #include "settings.h" #include "types.h" #include "urids.h" -#include "worker.h" #if USE_SUIL # include <suil/suil.h> @@ -27,7 +27,6 @@ #include <lv2/atom/forge.h> #include <lv2/core/lv2.h> #include <lv2/urid/urid.h> -#include <zix/ring.h> #include <zix/sem.h> #include <stdbool.h> @@ -39,52 +38,37 @@ JALV_BEGIN_DECLS /// Internal application state struct JalvImpl { - JalvOptions opts; ///< Command-line options - LilvWorld* world; ///< Lilv World - JalvMapper* mapper; ///< URI mapper/unmapper - JalvURIDs urids; ///< URIDs - JalvNodes nodes; ///< Nodes - JalvLog log; ///< Log for error/warning/debug messages - LV2_Atom_Forge forge; ///< Atom forge - JalvDumper* dumper; ///< Atom dumper (console debug output) - JalvBackend* backend; ///< Audio system backend - JalvSettings settings; ///< Processing settings - ZixRing* ui_to_plugin; ///< Port events from UI - ZixRing* plugin_to_ui; ///< Port events from plugin - void* audio_msg; ///< Buffer for messages in the audio thread - void* ui_msg; ///< Buffer for messages in the UI thread - JalvWorker* worker; ///< Worker thread implementation - JalvWorker* state_worker; ///< Synchronous worker for state restore - ZixSem work_lock; ///< Lock for plugin work() method - ZixSem done; ///< Exit semaphore - ZixSem paused; ///< Paused signal from process thread - JalvRunState run_state; ///< Current process thread run state - char* temp_dir; ///< Temporary plugin state directory - char* save_dir; ///< Plugin save directory - const LilvPlugin* plugin; ///< Plugin class (RDF data) - LilvState* preset; ///< Current preset - LilvUIs* uis; ///< All plugin UIs (RDF data) - const LilvUI* ui; ///< Plugin UI (RDF data) - const LilvNode* ui_type; ///< Plugin UI type (unwrapped) - LilvInstance* instance; ///< Plugin instance (shared library) + JalvOptions opts; ///< Command-line options + LilvWorld* world; ///< Lilv World + JalvMapper* mapper; ///< URI mapper/unmapper + JalvURIDs urids; ///< URIDs + JalvNodes nodes; ///< Nodes + JalvLog log; ///< Log for error/warning/debug messages + LV2_Atom_Forge forge; ///< Atom forge + JalvDumper* dumper; ///< Atom dumper (console debug output) + JalvBackend* backend; ///< Audio system backend + JalvSettings settings; ///< Processing settings + void* ui_msg; ///< Buffer for messages in the UI thread + ZixSem work_lock; ///< Lock for plugin work() method + ZixSem done; ///< Exit semaphore + char* temp_dir; ///< Temporary plugin state directory + char* save_dir; ///< Plugin save directory + const LilvPlugin* plugin; ///< Plugin class (RDF data) + LilvState* preset; ///< Current preset + LilvUIs* uis; ///< All plugin UIs (RDF data) + const LilvUI* ui; ///< Plugin UI (RDF data) + const LilvNode* ui_type; ///< Plugin UI type (unwrapped) + JalvProcess process; ///< Process thread state #if USE_SUIL SuilHost* ui_host; ///< Plugin UI host support SuilInstance* ui_instance; ///< Plugin UI instance (shared library) #endif - void* window; ///< Window (if applicable) - JalvPort* ports; ///< Port array of size num_ports - Controls controls; ///< Available plugin controls - float* controls_buf; ///< Control port buffers array - size_t msg_buf_size; ///< Maximum size of a single message - uint32_t control_in; ///< Index of control input port - uint32_t num_ports; ///< Total number of ports on the plugin - uint32_t plugin_latency; ///< Latency reported by plugin (if any) - uint32_t event_delta_t; ///< Frames since last update sent to UI - uint32_t position; ///< Transport position in frames - float bpm; ///< Transport tempo in beats per minute - bool rolling; ///< Transport speed (0=stop, 1=play) - bool has_ui; ///< True iff a control UI is present - bool safe_restore; ///< Plugin restore() is thread-safe + void* window; ///< Window (if applicable) + JalvPort* ports; ///< Port array of size num_ports + Controls controls; ///< Available plugin controls + size_t ui_msg_size; ///< Maximum size of a single message + uint32_t num_ports; ///< Total number of ports on the plugin + bool safe_restore; ///< Plugin restore() is thread-safe JalvFeatures features; const LV2_Feature** feature_list; }; diff --git a/src/jalv_console.c b/src/jalv_console.c index 0ea85b1..664d175 100644 --- a/src/jalv_console.c +++ b/src/jalv_console.c @@ -198,7 +198,7 @@ print_controls(const Jalv* const jalv, const bool writable, const bool readable) jalv_log(JALV_LOG_INFO, "%s = %f\n", lilv_node_as_string(control->symbol), - jalv->controls_buf[control->index]); + jalv->process.controls_buf[control->index]); } } @@ -247,7 +247,7 @@ jalv_process_command(Jalv* jalv, const char* cmd) print_controls(jalv, false, true); } else if (sscanf(cmd, "set %u %f", &index, &value) == 2) { if (index < jalv->num_ports) { - jalv->controls_buf[index] = value; + jalv->process.controls_buf[index] = value; print_control_port(jalv, &jalv->ports[index], value); } else { fprintf(stderr, "error: port index out of range\n"); @@ -256,7 +256,7 @@ jalv_process_command(Jalv* jalv, const char* cmd) sscanf(cmd, "%1023[a-zA-Z0-9_] = %f", sym, &value) == 2) { JalvPort* const port = jalv_port_by_symbol(jalv, sym); if (port) { - jalv->controls_buf[port->index] = value; + jalv->process.controls_buf[port->index] = value; print_control_port(jalv, port, value); } else { fprintf(stderr, "error: no control named `%s'\n", sym); @@ -341,7 +341,8 @@ jalv_frontend_open(Jalv* jalv) ControlID* control = jalv->controls.controls[i]; if (control->type == PORT && control->is_writable) { const JalvPort* const port = &jalv->ports[control->index]; - print_control_port(jalv, port, jalv->controls_buf[control->index]); + print_control_port( + jalv, port, jalv->process.controls_buf[control->index]); } } diff --git a/src/jalv_qt.cpp b/src/jalv_qt.cpp index 8fd5495..9b3b145 100644 --- a/src/jalv_qt.cpp +++ b/src/jalv_qt.cpp @@ -365,9 +365,9 @@ Control::Control(PortContainer portContainer, QWidget* parent) } // Find and set min, max and default values for port - const float defaultValue = ndef - ? lilv_node_as_float(ndef) - : portContainer.jalv->controls_buf[_port->index]; + const float defaultValue = + ndef ? lilv_node_as_float(ndef) + : portContainer.jalv->process.controls_buf[_port->index]; setRange(lilv_node_as_float(nmin), lilv_node_as_float(nmax)); setValue(defaultValue); @@ -499,7 +499,7 @@ Control::dialChanged(int) const float value = getValue(); _label->setText(getValueLabel(value)); - _jalv->controls_buf[_port->index] = value; + _jalv->process.controls_buf[_port->index] = value; } namespace { @@ -17,16 +17,11 @@ JALV_BEGIN_DECLS typedef struct { - const LilvPort* lilv_port; ///< LV2 port - PortType type; ///< Data type - PortFlow flow; ///< Data flow direction - void* sys_port; ///< For audio/MIDI ports, otherwise NULL - LV2_Evbuf* evbuf; ///< For MIDI ports, otherwise NULL - void* widget; ///< Control widget, if applicable - size_t buf_size; ///< Custom buffer size, or 0 - uint32_t index; ///< Port index - bool reports_latency; ///< For control port outputs - bool is_primary; ///< True for main control/reponse channel + const LilvPort* lilv_port; ///< LV2 port + PortType type; ///< Data type + PortFlow flow; ///< Data flow direction + void* widget; ///< Control widget, if applicable + uint32_t index; ///< Port index } JalvPort; JALV_END_DECLS diff --git a/src/portaudio.c b/src/portaudio.c index c61fb37..d39b7e8 100644 --- a/src/portaudio.c +++ b/src/portaudio.c @@ -6,7 +6,6 @@ #include "jalv.h" #include "log.h" #include "lv2_evbuf.h" -#include "port.h" #include "process.h" #include "types.h" @@ -25,15 +24,15 @@ struct JalvBackendImpl { }; static int -process_silent(Jalv* const jalv, +process_silent(JalvProcess* const proc, void* const outputs, const unsigned long nframes) { - for (uint32_t i = 0; i < jalv->num_ports; ++i) { + for (uint32_t i = 0; i < proc->num_ports; ++i) { memset(((float**)outputs)[i], '\0', nframes * sizeof(float)); } - return jalv_bypass(jalv, nframes); + return jalv_bypass(proc, nframes); } static int @@ -47,25 +46,26 @@ process_cb(const void* inputs, (void)time; (void)flags; - Jalv* jalv = (Jalv*)handle; + Jalv* const jalv = (Jalv*)handle; + JalvProcess* const proc = &jalv->process; // If execution is paused, emit silence and return - if (jalv->run_state == JALV_PAUSED) { - return process_silent(jalv, outputs, nframes); + if (proc->run_state == JALV_PAUSED) { + return process_silent(proc, outputs, nframes); } // Prepare port buffers uint32_t in_index = 0; uint32_t out_index = 0; - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - JalvPort* const port = &jalv->ports[i]; + for (uint32_t i = 0; i < proc->num_ports; ++i) { + JalvProcessPort* const port = &proc->ports[i]; if (port->type == TYPE_AUDIO) { if (port->flow == FLOW_INPUT) { lilv_instance_connect_port( - jalv->instance, i, ((float**)inputs)[in_index++]); + proc->instance, i, ((float**)inputs)[in_index++]); } else if (port->flow == FLOW_OUTPUT) { lilv_instance_connect_port( - jalv->instance, i, ((float**)outputs)[out_index++]); + proc->instance, i, ((float**)outputs)[out_index++]); } } else if (port->type == TYPE_EVENT) { lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT); @@ -73,11 +73,11 @@ process_cb(const void* inputs, } // Run plugin for this cycle - const bool send_ui_updates = jalv_run(jalv, nframes); + const bool send_ui_updates = jalv_run(proc, nframes); // Deliver UI events - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - JalvPort* const port = &jalv->ports[p]; + for (uint32_t p = 0; p < proc->num_ports; ++p) { + JalvProcessPort* const port = &proc->ports[p]; if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(port->evbuf); lv2_evbuf_is_valid(i); @@ -90,14 +90,14 @@ process_cb(const void* inputs, void* body = NULL; lv2_evbuf_get(i, &frames, &subframes, &type, &size, &body); - if (jalv->has_ui) { + if (proc->has_ui) { // Forward event to UI - jalv_write_event(jalv->plugin_to_ui, p, size, type, body); + jalv_write_event(proc->plugin_to_ui, p, size, type, body); } } } else if (send_ui_updates && port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL) { - jalv_write_control(jalv->plugin_to_ui, p, jalv->controls_buf[p]); + jalv_write_control(proc->plugin_to_ui, p, proc->controls_buf[p]); } } @@ -230,11 +230,12 @@ jalv_backend_deactivate(Jalv* jalv) void jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) { - JalvPort* const port = &jalv->ports[port_index]; + JalvProcess* const proc = &jalv->process; + JalvProcessPort* const port = &proc->ports[port_index]; if (port->type == TYPE_CONTROL) { lilv_instance_connect_port( - jalv->instance, port_index, &jalv->controls_buf[port_index]); + proc->instance, port_index, &proc->controls_buf[port_index]); } } diff --git a/src/process.c b/src/process.c index 3348877..efb2602 100644 --- a/src/process.c +++ b/src/process.c @@ -4,10 +4,8 @@ #include "process.h" #include "comm.h" -#include "jalv.h" #include "log.h" #include "lv2_evbuf.h" -#include "port.h" #include "types.h" #include "worker.h" @@ -28,13 +26,9 @@ ring_error(const char* const message) } static int -apply_ui_events(Jalv* const jalv, const uint32_t nframes) +apply_ui_events(JalvProcess* const proc, const uint32_t nframes) { - if (!jalv->has_ui) { - return 0; - } - - ZixRing* const ring = jalv->ui_to_plugin; + ZixRing* const ring = proc->ui_to_plugin; JalvMessageHeader header = {NO_MESSAGE, 0U}; const size_t space = zix_ring_read_space(ring); for (size_t i = 0; i < space; i += sizeof(header) + header.size) { @@ -50,38 +44,37 @@ apply_ui_events(Jalv* const jalv, const uint32_t nframes) return ring_error("Failed to read control value from UI ring\n"); } - assert(msg.port_index < jalv->num_ports); - jalv->controls_buf[msg.port_index] = msg.value; + assert(msg.port_index < proc->num_ports); + proc->controls_buf[msg.port_index] = msg.value; } else if (header.type == EVENT_TRANSFER) { - assert(header.size <= jalv->msg_buf_size); - void* const body = jalv->audio_msg; + assert(header.size <= proc->process_msg_size); + void* const body = proc->process_msg; if (zix_ring_read(ring, body, header.size) != header.size) { return ring_error("Failed to read event from UI ring\n"); } const JalvEventTransfer* const msg = (const JalvEventTransfer*)body; - assert(msg->port_index < jalv->num_ports); - JalvPort* const port = &jalv->ports[msg->port_index]; - LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); - const LV2_Atom* const atom = &msg->atom; + assert(msg->port_index < proc->num_ports); + JalvProcessPort* const port = &proc->ports[msg->port_index]; + LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); + const LV2_Atom* const atom = &msg->atom; lv2_evbuf_write( &e, nframes, 0U, atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); } else if (header.type == STATE_REQUEST) { - JalvPort* const port = &jalv->ports[jalv->control_in]; + JalvProcessPort* const port = &proc->ports[proc->control_in]; assert(port->type == TYPE_EVENT); assert(port->flow == FLOW_INPUT); assert(port->evbuf); - LV2_Evbuf_Iterator iter = lv2_evbuf_end(port->evbuf); - const LV2_Atom_Object get = { - {sizeof(LV2_Atom_Object_Body), jalv->urids.atom_Object}, - {0U, jalv->urids.patch_Get}, - }; - - lv2_evbuf_write( - &iter, nframes, 0U, get.atom.type, get.atom.size, &get.body); + LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); + lv2_evbuf_write(&e, + nframes, + 0U, + proc->get_msg.atom.type, + proc->get_msg.atom.size, + &proc->get_msg.body); } else if (header.type == RUN_STATE_CHANGE) { assert(header.size == sizeof(JalvRunStateChange)); @@ -90,9 +83,9 @@ apply_ui_events(Jalv* const jalv, const uint32_t nframes) return ring_error("Failed to read run state change from UI ring\n"); } - jalv->run_state = msg.state; + proc->run_state = msg.state; if (msg.state == JALV_PAUSED) { - zix_sem_post(&jalv->paused); + zix_sem_post(&proc->paused); } } else { @@ -104,37 +97,34 @@ apply_ui_events(Jalv* const jalv, const uint32_t nframes) } bool -jalv_run(Jalv* const jalv, const uint32_t nframes) +jalv_run(JalvProcess* const proc, const uint32_t nframes) { // Read and apply control change events from UI - apply_ui_events(jalv, nframes); + apply_ui_events(proc, nframes); // Run plugin for this cycle - lilv_instance_run(jalv->instance, nframes); + lilv_instance_run(proc->instance, nframes); // Process any worker replies and end the cycle - LV2_Handle handle = lilv_instance_get_handle(jalv->instance); - jalv_worker_emit_responses(jalv->state_worker, handle); - jalv_worker_emit_responses(jalv->worker, handle); - jalv_worker_end_run(jalv->worker); + LV2_Handle handle = lilv_instance_get_handle(proc->instance); + jalv_worker_emit_responses(proc->state_worker, handle); + jalv_worker_emit_responses(proc->worker, handle); + jalv_worker_end_run(proc->worker); // Check if it's time to send updates to the UI - jalv->event_delta_t += nframes; - bool send_ui_updates = false; - const uint32_t update_frames = - (uint32_t)(jalv->settings.sample_rate / jalv->settings.ui_update_hz); - if (jalv->has_ui && (jalv->event_delta_t > update_frames)) { - send_ui_updates = true; - jalv->event_delta_t = 0U; + proc->pending_frames += nframes; + if (proc->update_frames && proc->pending_frames > proc->update_frames) { + proc->pending_frames = 0U; + return true; } - return send_ui_updates; + return false; } int -jalv_bypass(Jalv* const jalv, const uint32_t nframes) +jalv_bypass(JalvProcess* const proc, const uint32_t nframes) { // Read and apply control change events from UI - apply_ui_events(jalv, nframes); + apply_ui_events(proc, nframes); return 0; } diff --git a/src/process.h b/src/process.h index 3dba625..c5d049b 100644 --- a/src/process.h +++ b/src/process.h @@ -5,26 +5,81 @@ #define JALV_PROCESS_H #include "attributes.h" +#include "lv2_evbuf.h" #include "types.h" +#include "worker.h" + +#include <lilv/lilv.h> +#include <lv2/atom/atom.h> +#include <lv2/atom/forge.h> +#include <zix/ring.h> +#include <zix/sem.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> // Code and data used in the realtime process thread JALV_BEGIN_DECLS +/// Port state used in the process thread +typedef struct { + PortType type; ///< Data type + PortFlow flow; ///< Data flow direction + void* sys_port; ///< For audio/MIDI ports, otherwise NULL + char* symbol; ///< Port symbol (stable/unique C-like identifier) + char* label; ///< Human-readable label + LV2_Evbuf* evbuf; ///< Sequence port event buffer + uint32_t buf_size; ///< Custom buffer size, or 0 + bool reports_latency; ///< Whether control port reports latency + bool is_primary; ///< True for main control/reponse channel + bool supports_midi; ///< Whether event port supports MIDI +} JalvProcessPort; + +/** + State accessed in the process thread. + + Everything accessed by the process thread is stored here, to keep it + somewhat insulated from the UI and to make references to it stand out in the + code. +*/ +typedef struct { + LilvInstance* instance; ///< Plugin instance + ZixRing* ui_to_plugin; ///< Messages from UI to plugin/process + ZixRing* plugin_to_ui; ///< Messages from plugin/process to UI + JalvWorker* worker; ///< Worker thread implementation + JalvWorker* state_worker; ///< Synchronous worker for state restore + JalvProcessPort* ports; ///< Port array of size num_ports + LV2_Atom_Forge forge; ///< Atom forge + LV2_Atom_Object get_msg; ///< General patch:Get message + float* controls_buf; ///< Control port buffers array + size_t process_msg_size; ///< Maximum size of a single message + void* process_msg; ///< Buffer for receiving messages + ZixSem paused; ///< Paused signal from process thread + JalvRunState run_state; ///< Current run state + uint32_t control_in; ///< Index of control input port + uint32_t num_ports; ///< Total number of ports on the plugin + uint32_t pending_frames; ///< Frames since last UI update sent + uint32_t update_frames; ///< UI update period in frames, or zero + uint32_t plugin_latency; ///< Latency reported by plugin (if any) + uint32_t position; ///< Transport position in frames + float bpm; ///< Transport tempo in beats per minute + bool rolling; ///< Transport speed (0=stop, 1=play) + bool has_ui; ///< True iff a control UI is present +} JalvProcess; + /** Run the plugin for a block of frames. Applies any pending messages from the UI, runs the plugin instance, and processes any worker replies. - @param jalv Application state. + @param proc Process thread state. @param nframes Number of frames to process. @return Whether output value updates should be sent to the UI now. */ bool -jalv_run(Jalv* jalv, uint32_t nframes); +jalv_run(JalvProcess* proc, uint32_t nframes); /** Bypass the plugin for a block of frames. @@ -32,12 +87,12 @@ jalv_run(Jalv* jalv, uint32_t nframes); This is like jalv_run(), but doesn't actually run the plugin and only does the minimum necessary internal work for the cycle. - @param jalv Application state. + @param proc Process thread state. @param nframes Number of frames to bypass. @return Zero. */ int -jalv_bypass(Jalv* jalv, uint32_t nframes); +jalv_bypass(JalvProcess* proc, uint32_t nframes); JALV_END_DECLS diff --git a/src/process_setup.c b/src/process_setup.c index 3f64d02..0e03932 100644 --- a/src/process_setup.c +++ b/src/process_setup.c @@ -3,27 +3,94 @@ #include "process_setup.h" -#include "jalv.h" +#include "jalv_config.h" +#include "log.h" #include "lv2_evbuf.h" -#include "port.h" +#include "macros.h" +#include "mapper.h" +#include "nodes.h" +#include "process.h" +#include "query.h" +#include "settings.h" +#include "string_utils.h" #include "types.h" #include "urids.h" +#include "worker.h" #include <lilv/lilv.h> +#include <lv2/atom/atom.h> +#include <lv2/atom/forge.h> +#include <zix/allocator.h> +#include <zix/ring.h> +#include <zix/sem.h> -#include <stddef.h> +#include <stdbool.h> #include <stdint.h> +#include <stdlib.h> + +int +jalv_process_init(JalvProcess* const proc, + const JalvURIDs* const urids, + JalvMapper* const mapper, + const uint32_t update_frames) +{ + proc->get_msg.atom.size = sizeof(LV2_Atom_Object_Body); + proc->get_msg.atom.type = urids->atom_Object; + proc->get_msg.body.id = 0U; + proc->get_msg.body.otype = urids->patch_Get; + + proc->instance = NULL; + proc->ui_to_plugin = NULL; + proc->plugin_to_ui = NULL; + proc->worker = NULL; + proc->state_worker = NULL; + proc->ports = NULL; + proc->process_msg_size = 1024U; + proc->process_msg = NULL; + proc->run_state = JALV_PAUSED; + proc->control_in = UINT32_MAX; + proc->num_ports = 0U; + proc->pending_frames = 0U; + proc->update_frames = update_frames; + proc->position = 0U; + proc->bpm = 120.0f; + proc->rolling = false; + proc->has_ui = false; + + zix_sem_init(&proc->paused, 0); + lv2_atom_forge_init(&proc->forge, jalv_mapper_urid_map(mapper)); + + return 0; +} void -jalv_allocate_port_buffers(Jalv* const jalv) +jalv_process_cleanup(JalvProcess* const proc) { - const JalvURIDs* const urids = &jalv->urids; + zix_sem_destroy(&proc->paused); + jalv_worker_free(proc->worker); + jalv_worker_free(proc->state_worker); + zix_ring_free(proc->ui_to_plugin); + zix_ring_free(proc->plugin_to_ui); + zix_free(NULL, proc->process_msg); - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - JalvPort* const port = &jalv->ports[i]; + for (uint32_t i = 0U; i < proc->num_ports; ++i) { + jalv_process_port_cleanup(&proc->ports[i]); + } +} + +void +jalv_process_activate(JalvProcess* const proc, + const JalvURIDs* const urids, + LilvInstance* const instance, + const JalvSettings* const settings) +{ + proc->instance = instance; + + for (uint32_t i = 0U; i < proc->num_ports; ++i) { + JalvProcessPort* const port = &proc->ports[i]; if (port->type == TYPE_EVENT) { const size_t size = - port->buf_size ? port->buf_size : jalv->settings.midi_buf_size; + port->buf_size ? port->buf_size : settings->midi_buf_size; lv2_evbuf_free(port->evbuf); port->evbuf = @@ -31,16 +98,121 @@ jalv_allocate_port_buffers(Jalv* const jalv) lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT); lilv_instance_connect_port( - jalv->instance, i, lv2_evbuf_get_buffer(port->evbuf)); + proc->instance, i, lv2_evbuf_get_buffer(port->evbuf)); + + if (port->flow == FLOW_INPUT) { + proc->process_msg_size = MAX(proc->process_msg_size, port->buf_size); + } } } + + // Allocate UI<=>process communication rings and process receive buffer + proc->ui_to_plugin = zix_ring_new(NULL, settings->ring_size); + proc->plugin_to_ui = zix_ring_new(NULL, settings->ring_size); + proc->process_msg = zix_aligned_alloc(NULL, 8U, proc->process_msg_size); + zix_ring_mlock(proc->ui_to_plugin); + zix_ring_mlock(proc->plugin_to_ui); + zix_ring_mlock(proc->process_msg); +} + +void +jalv_process_deactivate(JalvProcess* const proc) +{ + zix_free(NULL, proc->process_msg); + proc->process_msg = NULL; + + for (uint32_t i = 0U; i < proc->num_ports; ++i) { + lv2_evbuf_free(proc->ports[i].evbuf); + lilv_instance_connect_port(proc->instance, i, NULL); + proc->ports[i].evbuf = NULL; + } +} + +int +jalv_process_port_init(JalvProcessPort* const port, + const JalvNodes* const nodes, + const LilvPlugin* const lilv_plugin, + const LilvPort* const lilv_port) +{ + const LilvNode* const symbol = lilv_port_get_symbol(lilv_plugin, lilv_port); + + port->type = TYPE_UNKNOWN; + port->flow = FLOW_UNKNOWN; + port->sys_port = NULL; + port->evbuf = NULL; + port->buf_size = 0U; + port->reports_latency = false; + + const bool optional = lilv_port_has_property( + lilv_plugin, lilv_port, nodes->lv2_connectionOptional); + + // Set port flow (input or output) + if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_InputPort)) { + port->flow = FLOW_INPUT; + } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_OutputPort)) { + port->flow = FLOW_OUTPUT; + } else if (!optional) { + jalv_log(JALV_LOG_ERR, + "Mandatory port \"%s\" is neither input nor output\n", + lilv_node_as_string(symbol)); + return 1; + } + + // Set port type + if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_ControlPort)) { + port->type = TYPE_CONTROL; + } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_AudioPort)) { + port->type = TYPE_AUDIO; +#if USE_JACK_METADATA + } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_CVPort)) { + port->type = TYPE_CV; +#endif + } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->atom_AtomPort)) { + port->type = TYPE_EVENT; + } else if (!optional) { + jalv_log(JALV_LOG_ERR, + "Mandatory port \"%s\" has unknown data type\n", + lilv_node_as_string(symbol)); + return 1; + } + + // Set symbol and label + LilvNode* const name = lilv_port_get_name(lilv_plugin, lilv_port); + port->symbol = symbol ? jalv_strdup(lilv_node_as_string(symbol)) : NULL; + port->label = name ? jalv_strdup(lilv_node_as_string(name)) : NULL; + + // Set buffer size + LilvNode* const min_size = + lilv_port_get(lilv_plugin, lilv_port, nodes->rsz_minimumSize); + if (min_size && lilv_node_is_int(min_size)) { + port->buf_size = (uint32_t)MAX(lilv_node_as_int(min_size), 0); + } + lilv_node_free(min_size); + + // Set reports_latency flag + if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && + (lilv_port_has_property( + lilv_plugin, lilv_port, nodes->lv2_reportsLatency) || + jalv_port_has_designation( + nodes, lilv_plugin, lilv_port, nodes->lv2_latency))) { + port->reports_latency = true; + } + + // Set supports_midi flag + port->supports_midi = + lilv_port_supports_event(lilv_plugin, lilv_port, nodes->midi_MidiEvent); + + return 0; } void -jalv_free_port_buffers(Jalv* const jalv) +jalv_process_port_cleanup(JalvProcessPort* const port) { - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - lv2_evbuf_free(jalv->ports[i].evbuf); - lilv_instance_connect_port(jalv->instance, i, NULL); + if (port) { + if (port->evbuf) { + lv2_evbuf_free(port->evbuf); + } + free(port->label); + free(port->symbol); } } diff --git a/src/process_setup.h b/src/process_setup.h index 07a76c2..8c99312 100644 --- a/src/process_setup.h +++ b/src/process_setup.h @@ -5,18 +5,78 @@ #define JALV_PROCESS_SETUP_H #include "attributes.h" -#include "types.h" +#include "mapper.h" +#include "nodes.h" +#include "process.h" +#include "settings.h" +#include "urids.h" + +#include <lilv/lilv.h> + +#include <stdint.h> // Code for setting up the realtime process thread (but that isn't used in it) JALV_BEGIN_DECLS -/// Allocate appropriately-sized port buffers and connect the plugin to them +/** + Initialize process thread and allocate necessary structures. + + This only initializes the state structure, it doesn't create any threads or + start plugin execution. +*/ +int +jalv_process_init(JalvProcess* proc, + const JalvURIDs* urids, + JalvMapper* mapper, + uint32_t update_frames); + +/** + Clean up process thread. + + This frees everything allocated by jalv_process_init() and + jalv_process_activate(). +*/ void -jalv_allocate_port_buffers(Jalv* jalv); +jalv_process_cleanup(JalvProcess* proc); + +/** + Allocate necessary buffers, connect the plugin to them, and prepare to run. + + @param proc Process thread state. + @param urids Application vocabulary. + @param instance Plugin instance to run. + @param settings Process thread settings. +*/ +void +jalv_process_activate(JalvProcess* proc, + const JalvURIDs* urids, + LilvInstance* instance, + const JalvSettings* settings); + +/** + Clean up after jalv_process_activate() and disconnect plugin. + + @param proc Process thread state. +*/ +void +jalv_process_deactivate(JalvProcess* proc); + +/** + Initialize the process thread state for a port. + + @return Zero on success. +*/ +int +jalv_process_port_init(JalvProcessPort* port, + const JalvNodes* nodes, + const LilvPlugin* lilv_plugin, + const LilvPort* lilv_port); -/// Clean up memory allocated by jalv_process_activate() and disconnect plugin +/** + Free memory allocated by jalv_setup_init_port(). +*/ void -jalv_free_port_buffers(Jalv* jalv); +jalv_process_port_cleanup(JalvProcessPort* port); JALV_END_DECLS diff --git a/src/state.c b/src/state.c index b0ba53b..85b85bd 100644 --- a/src/state.c +++ b/src/state.c @@ -8,6 +8,7 @@ #include "log.h" #include "mapper.h" #include "port.h" +#include "process.h" #include "string_utils.h" #include "types.h" @@ -45,7 +46,7 @@ get_port_value(const char* port_symbol, if (port && port->flow == FLOW_INPUT && port->type == TYPE_CONTROL) { *size = sizeof(float); *type = jalv->forge.Float; - return &jalv->controls_buf[port->index]; + return &jalv->process.controls_buf[port->index]; } *size = *type = 0; return NULL; @@ -61,7 +62,7 @@ jalv_save(Jalv* jalv, const char* dir) LilvState* const state = lilv_state_new_from_instance(jalv->plugin, - jalv->instance, + jalv->process.instance, map, jalv->temp_dir, dir, @@ -128,8 +129,9 @@ set_port_value(const char* port_symbol, uint32_t ZIX_UNUSED(size), uint32_t type) { - Jalv* const jalv = (Jalv*)user_data; - JalvPort* const port = jalv_port_by_symbol(jalv, port_symbol); + Jalv* const jalv = (Jalv*)user_data; + JalvProcess* const proc = &jalv->process; + JalvPort* const port = jalv_port_by_symbol(jalv, port_symbol); if (!port) { jalv_log(JALV_LOG_ERR, "Preset port `%s' is missing\n", port_symbol); return; @@ -153,17 +155,17 @@ set_port_value(const char* port_symbol, } ZixStatus st = ZIX_STATUS_SUCCESS; - if (jalv->run_state != JALV_RUNNING) { + if (proc->run_state != JALV_RUNNING) { // Set value on port struct directly - jalv->controls_buf[port->index] = fvalue; + proc->controls_buf[port->index] = fvalue; } else { // Send value to plugin (as if from UI) - st = jalv_write_control(jalv->ui_to_plugin, port->index, fvalue); + st = jalv_write_control(proc->ui_to_plugin, port->index, fvalue); } - if (jalv->has_ui) { + if (proc->has_ui) { // Update UI (as if from plugin) - st = jalv_write_control(jalv->plugin_to_ui, port->index, fvalue); + st = jalv_write_control(proc->plugin_to_ui, port->index, fvalue); } if (st) { @@ -175,19 +177,21 @@ set_port_value(const char* port_symbol, void jalv_apply_state(Jalv* jalv, const LilvState* state) { + JalvProcess* const proc = &jalv->process; + typedef struct { JalvMessageHeader head; JalvRunStateChange body; } PauseMessage; const bool must_pause = - !jalv->safe_restore && jalv->run_state == JALV_RUNNING; + !jalv->safe_restore && proc->run_state == JALV_RUNNING; if (must_pause) { const PauseMessage pause_msg = { {RUN_STATE_CHANGE, sizeof(JalvRunStateChange)}, {JALV_PAUSED}}; - zix_ring_write(jalv->ui_to_plugin, &pause_msg, sizeof(pause_msg)); + zix_ring_write(proc->ui_to_plugin, &pause_msg, sizeof(pause_msg)); - zix_sem_wait(&jalv->paused); + zix_sem_wait(&proc->paused); } const LV2_Feature* state_features[9] = { @@ -202,15 +206,15 @@ jalv_apply_state(Jalv* jalv, const LilvState* state) }; lilv_state_restore( - state, jalv->instance, set_port_value, jalv, 0, state_features); + state, proc->instance, set_port_value, jalv, 0, state_features); if (must_pause) { const JalvMessageHeader state_msg = {STATE_REQUEST, 0U}; - zix_ring_write(jalv->ui_to_plugin, &state_msg, sizeof(state_msg)); + zix_ring_write(proc->ui_to_plugin, &state_msg, sizeof(state_msg)); const PauseMessage run_msg = { {RUN_STATE_CHANGE, sizeof(JalvRunStateChange)}, {JALV_RUNNING}}; - zix_ring_write(jalv->ui_to_plugin, &run_msg, sizeof(run_msg)); + zix_ring_write(proc->ui_to_plugin, &run_msg, sizeof(run_msg)); } } @@ -238,7 +242,7 @@ jalv_save_preset(Jalv* jalv, LilvState* const state = lilv_state_new_from_instance(jalv->plugin, - jalv->instance, + jalv->process.instance, map, jalv->temp_dir, dir, |