diff options
Diffstat (limited to 'src/jalv.c')
-rw-r--r-- | src/jalv.c | 1278 |
1 files changed, 406 insertions, 872 deletions
@@ -1,88 +1,60 @@ -// Copyright 2007-2022 David Robillard <d@drobilla.net> +// Copyright 2007-2024 David Robillard <d@drobilla.net> // SPDX-License-Identifier: ISC +#include "jalv.h" + #include "backend.h" +#include "comm.h" #include "control.h" +#include "dumper.h" +#include "features.h" #include "frontend.h" #include "jalv_config.h" -#include "jalv_internal.h" #include "log.h" -#include "lv2_evbuf.h" +#include "macros.h" +#include "mapper.h" #include "nodes.h" +#include "options.h" #include "port.h" +#include "process.h" +#include "process_setup.h" +#include "settings.h" #include "state.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 "lv2/atom/util.h" -#include "lv2/buf-size/buf-size.h" -#include "lv2/core/lv2.h" -#include "lv2/data-access/data-access.h" -#include "lv2/instance-access/instance-access.h" -#include "lv2/log/log.h" -#include "lv2/midi/midi.h" -#include "lv2/options/options.h" -#include "lv2/parameters/parameters.h" -#include "lv2/patch/patch.h" -#include "lv2/port-groups/port-groups.h" -#include "lv2/port-props/port-props.h" -#include "lv2/presets/presets.h" -#include "lv2/resize-port/resize-port.h" -#include "lv2/state/state.h" -#include "lv2/time/time.h" -#include "lv2/ui/ui.h" -#include "lv2/urid/urid.h" -#include "lv2/worker/worker.h" -#include "serd/serd.h" -#include "sratom/sratom.h" -#include "symap.h" -#include "zix/attributes.h" -#include "zix/ring.h" -#include "zix/sem.h" +#include <lilv/lilv.h> +#include <lv2/atom/atom.h> +#include <lv2/atom/forge.h> +#include <lv2/atom/util.h> +#include <lv2/buf-size/buf-size.h> +#include <lv2/core/lv2.h> +#include <lv2/data-access/data-access.h> +#include <lv2/instance-access/instance-access.h> +#include <lv2/log/log.h> +#include <lv2/patch/patch.h> +#include <lv2/state/state.h> +#include <lv2/ui/ui.h> +#include <lv2/urid/urid.h> +#include <lv2/worker/worker.h> +#include <zix/allocator.h> +#include <zix/filesystem.h> +#include <zix/path.h> +#include <zix/ring.h> +#include <zix/sem.h> +#include <zix/status.h> #if USE_SUIL -# include "suil/suil.h" -#endif - -#if defined(_WIN32) -# include <io.h> // for _mktemp -# define snprintf _snprintf -#elif defined(__APPLE__) -# include <unistd.h> // for mkdtemp on Darwin +# include <suil/suil.h> #endif -#include <assert.h> -#include <math.h> -#include <signal.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/stat.h> - -#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" -#define NS_XSD "http://www.w3.org/2001/XMLSchema#" - -#ifndef MIN -# define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif - -#ifndef MAX -# define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - -#ifndef ARRAY_SIZE -# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) -#endif - -#ifndef MSG_BUFFER_SIZE -# define MSG_BUFFER_SIZE 1024 -#endif /** Size factor for UI ring buffers. @@ -94,30 +66,6 @@ */ #define N_BUFFER_CYCLES 16 -static ZixSem* exit_sem = NULL; ///< Exit semaphore used by signal handler - -static LV2_URID -map_uri(LV2_URID_Map_Handle handle, const char* uri) -{ - Jalv* jalv = (Jalv*)handle; - zix_sem_wait(&jalv->symap_lock); - const LV2_URID id = symap_map(jalv->symap, uri); - zix_sem_post(&jalv->symap_lock); - return id; -} - -static const char* -unmap_uri(LV2_URID_Unmap_Handle handle, LV2_URID urid) -{ - Jalv* jalv = (Jalv*)handle; - zix_sem_wait(&jalv->symap_lock); - const char* uri = symap_unmap(jalv->symap, urid); - zix_sem_post(&jalv->symap_lock); - return uri; -} - -#define NS_EXT "http://lv2plug.in/ns/ext/" - /// These features have no data static const LV2_Feature static_features[] = { {LV2_STATE__loadDefaultState, NULL}, @@ -127,7 +75,7 @@ static const LV2_Feature static_features[] = { /// Return true iff Jalv supports the given feature static bool -feature_is_supported(Jalv* jalv, const char* uri) +feature_is_supported(const Jalv* jalv, const char* uri) { if (!strcmp(uri, "http://lv2plug.in/ns/lv2core#isLive") || !strcmp(uri, "http://lv2plug.in/ns/lv2core#inPlaceBroken")) { @@ -142,147 +90,91 @@ feature_is_supported(Jalv* jalv, const char* uri) return false; } -/// Abort and exit on error -static void -die(const char* msg) -{ - jalv_log(JALV_LOG_ERR, "%s\n", msg); - exit(EXIT_FAILURE); -} - /** Create a port structure from data description. This is called before plugin and Jack instantiation. The remaining instance-specific setup (e.g. buffers) is done later in activate_port(). */ -static void -create_port(Jalv* jalv, uint32_t port_index, float default_value) +static int +create_port(Jalv* jalv, uint32_t port_index) { - struct Port* const port = &jalv->ports[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->control = 0.0f; 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) { - die("Mandatory port has unknown type (neither input nor output)"); + 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; - port->control = isnan(default_value) ? 0.0f : default_value; + 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, jalv->plugin, port->lilv_port, port->index, - jalv->sample_rate, + jalv->settings.sample_rate, &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) { - die("Mandatory port has unknown data type"); - } - - 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.buffer_size = - MAX(jalv->opts.buffer_size, port->buf_size * N_BUFFER_CYCLES); } - lilv_node_free(min_size); -} - -/// Create port structures from data (via create_port()) for all ports -void -jalv_create_ports(Jalv* jalv) -{ - jalv->num_ports = lilv_plugin_get_num_ports(jalv->plugin); - jalv->ports = (struct Port*)calloc(jalv->num_ports, sizeof(struct Port)); - float* default_values = - (float*)calloc(lilv_plugin_get_num_ports(jalv->plugin), sizeof(float)); - lilv_plugin_get_port_ranges_float(jalv->plugin, NULL, NULL, default_values); - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - create_port(jalv, i, default_values[i]); + // 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; } - const LilvPort* control_input = lilv_plugin_get_port_by_designation( - jalv->plugin, jalv->nodes.lv2_InputPort, jalv->nodes.lv2_control); - if (control_input) { - const uint32_t index = lilv_port_get_index(jalv->plugin, control_input); - if (jalv->ports[index].type == TYPE_EVENT) { - jalv->control_in = index; - } else { - jalv_log(JALV_LOG_WARNING, - "Non-event port %u has lv2:control designation, ignored\n", - index); - } + // 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); } - free(default_values); + return 0; } -/// Allocate port buffers (only necessary for MIDI) -void -jalv_allocate_port_buffers(Jalv* jalv) +/// Create port structures from data (via create_port()) for all ports +static int +jalv_create_ports(Jalv* jalv) { - const LV2_URID atom_Chunk = jalv->map.map( - jalv->map.handle, lilv_node_as_string(jalv->nodes.atom_Chunk)); - - const LV2_URID atom_Sequence = jalv->map.map( - jalv->map.handle, lilv_node_as_string(jalv->nodes.atom_Sequence)); - - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - struct Port* const port = &jalv->ports[i]; - if (port->type == TYPE_EVENT) { - lv2_evbuf_free(port->evbuf); + const uint32_t n_ports = lilv_plugin_get_num_ports(jalv->plugin); - const size_t size = port->buf_size ? port->buf_size : jalv->midi_buf_size; + 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)); - port->evbuf = lv2_evbuf_new(size, atom_Chunk, atom_Sequence); + // Allocate control port buffers array and set to default values + jalv->process.controls_buf = (float*)calloc(n_ports, sizeof(float)); + lilv_plugin_get_port_ranges_float( + jalv->plugin, NULL, NULL, jalv->process.controls_buf); - lilv_instance_connect_port( - jalv->instance, i, lv2_evbuf_get_buffer(port->evbuf)); - - lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT); + for (uint32_t i = 0; i < jalv->num_ports; ++i) { + if (create_port(jalv, i)) { + return 1; } } + + return 0; } /** @@ -291,12 +183,12 @@ jalv_allocate_port_buffers(Jalv* jalv) TODO: Build an index to make this faster, currently O(n) which may be a problem when restoring the state of plugins with many ports. */ -struct Port* +JalvPort* jalv_port_by_symbol(Jalv* jalv, const char* sym) { for (uint32_t i = 0; i < jalv->num_ports; ++i) { - struct Port* const port = &jalv->ports[i]; - const LilvNode* port_sym = + JalvPort* const port = &jalv->ports[i]; + const LilvNode* port_sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port); if (!strcmp(lilv_node_as_string(port_sym), sym)) { @@ -307,7 +199,7 @@ jalv_port_by_symbol(Jalv* jalv, const char* sym) return NULL; } -ControlID* +static ControlID* jalv_control_by_symbol(Jalv* jalv, const char* sym) { for (size_t i = 0; i < jalv->controls.n_controls; ++i) { @@ -318,7 +210,7 @@ jalv_control_by_symbol(Jalv* jalv, const char* sym) return NULL; } -void +static void jalv_create_controls(Jalv* jalv, bool writable) { const LilvPlugin* plugin = jalv->plugin; @@ -352,8 +244,11 @@ jalv_create_controls(Jalv* jalv, bool writable) } } - record = new_property_control( - jalv->world, property, &jalv->nodes, &jalv->map, &jalv->forge); + record = new_property_control(jalv->world, + property, + &jalv->nodes, + jalv_mapper_urid_map(jalv->mapper), + &jalv->forge); if (writable) { record->is_writable = true; @@ -376,6 +271,53 @@ jalv_create_controls(Jalv* jalv, bool writable) lilv_node_free(patch_writable); } +static void +jalv_send_to_plugin(void* const jalv_handle, + const uint32_t port_index, + const uint32_t buffer_size, + const uint32_t protocol, + const void* const buffer) +{ + 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); + + } else if (protocol == 0U) { + if (buffer_size != sizeof(float)) { + st = ZIX_STATUS_BAD_ARG; + } else { + const float value = *(const float*)buffer; + st = jalv_write_control(proc->ui_to_plugin, port_index, value); + } + + } else if (protocol == jalv->urids.atom_eventTransfer) { + const LV2_Atom* const atom = (const LV2_Atom*)buffer; + if (buffer_size < sizeof(LV2_Atom) || + (sizeof(LV2_Atom) + atom->size != buffer_size)) { + st = ZIX_STATUS_BAD_ARG; + } else { + jalv_dump_atom(jalv->dumper, stdout, "UI => Plugin", atom, 36); + st = jalv_write_event( + proc->ui_to_plugin, port_index, atom->size, atom->type, atom + 1U); + } + + } else { + jalv_log(JALV_LOG_ERR, + "UI wrote with unsupported protocol %u (%s)\n", + protocol, + jalv_mapper_unmap_uri(jalv->mapper, protocol)); + } + + if (st) { + jalv_log(JALV_LOG_ERR, + "Failed to write to plugin from UI (%s)\n", + zix_strerror(st)); + } +} + void jalv_set_control(Jalv* jalv, const ControlID* control, @@ -384,25 +326,23 @@ jalv_set_control(Jalv* jalv, const void* body) { if (control->type == PORT && type == jalv->forge.Float) { - struct Port* port = &jalv->ports[control->index]; - port->control = *(const float*)body; - } else if (control->type == PROPERTY) { - // Copy forge since it is used by process thread - LV2_Atom_Forge forge = jalv->forge; + const float value = *(const float*)body; + jalv_write_control(jalv->process.ui_to_plugin, control->id.index, value); + } else if (control->type == PROPERTY && + jalv->process.control_in != UINT32_MAX) { LV2_Atom_Forge_Frame frame; - uint8_t buf[MSG_BUFFER_SIZE]; - lv2_atom_forge_set_buffer(&forge, buf, sizeof(buf)); + 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->id.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); @@ -413,8 +353,8 @@ jalv_set_control(Jalv* jalv, static uint32_t jalv_ui_port_index(void* const controller, const char* symbol) { - Jalv* const jalv = (Jalv*)controller; - struct Port* port = jalv_port_by_symbol(jalv, symbol); + Jalv* const jalv = (Jalv*)controller; + JalvPort* const port = jalv_port_by_symbol(jalv, symbol); return port ? port->index : LV2UI_INVALID_PORT_INDEX; } @@ -424,13 +364,15 @@ void jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) { #if USE_SUIL + const 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}; @@ -466,273 +408,46 @@ jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) lilv_free(binary_path); lilv_free(bundle_path); +#else + (void)jalv; + (void)native_ui_type; + (void)parent; #endif } -bool -jalv_ui_is_resizable(Jalv* jalv) -{ - if (!jalv->ui) { - return false; - } - - const LilvNode* s = lilv_ui_get_uri(jalv->ui); - LilvNode* p = lilv_new_uri(jalv->world, LV2_CORE__optionalFeature); - LilvNode* fs = lilv_new_uri(jalv->world, LV2_UI__fixedSize); - LilvNode* nrs = lilv_new_uri(jalv->world, LV2_UI__noUserResize); - - LilvNodes* fs_matches = lilv_world_find_nodes(jalv->world, s, p, fs); - LilvNodes* nrs_matches = lilv_world_find_nodes(jalv->world, s, p, nrs); - - lilv_nodes_free(nrs_matches); - lilv_nodes_free(fs_matches); - lilv_node_free(nrs); - lilv_node_free(fs); - lilv_node_free(p); - - return !fs_matches && !nrs_matches; -} - -static void -jalv_send_control_to_plugin(Jalv* const jalv, - uint32_t port_index, - uint32_t buffer_size, - const void* buffer) -{ - if (buffer_size != sizeof(float)) { - jalv_log(JALV_LOG_ERR, "UI wrote invalid control size %u\n", buffer_size); - - } else { - jalv_write_control( - jalv, jalv->ui_to_plugin, port_index, *(const float*)buffer); - } -} - -static void -jalv_send_event_to_plugin(Jalv* const jalv, - uint32_t port_index, - uint32_t buffer_size, - const void* buffer) -{ - const LV2_Atom* const atom = (const LV2_Atom*)buffer; - - if (buffer_size < sizeof(LV2_Atom)) { - jalv_log(JALV_LOG_ERR, "UI wrote impossible atom size\n"); - - } else if (sizeof(LV2_Atom) + atom->size != buffer_size) { - jalv_log(JALV_LOG_ERR, "UI wrote corrupt atom size\n"); - - } else { - jalv_dump_atom(jalv, stdout, "UI => Plugin", atom, 36); - jalv_write_event( - jalv, jalv->ui_to_plugin, port_index, atom->size, atom->type, atom + 1U); - } -} - -void -jalv_send_to_plugin(void* const jalv_handle, - uint32_t port_index, - uint32_t buffer_size, - uint32_t protocol, - const void* buffer) -{ - Jalv* const jalv = (Jalv*)jalv_handle; - - if (port_index >= jalv->num_ports) { - jalv_log(JALV_LOG_ERR, "UI wrote to invalid port index %u\n", port_index); - - } else if (protocol == 0U) { - jalv_send_control_to_plugin(jalv, port_index, buffer_size, buffer); - - } else if (protocol == jalv->urids.atom_eventTransfer) { - jalv_send_event_to_plugin(jalv, port_index, buffer_size, buffer); - - } else { - jalv_log(JALV_LOG_ERR, - "UI wrote with unsupported protocol %u (%s)\n", - protocol, - unmap_uri(jalv, protocol)); - } -} - -void -jalv_apply_ui_events(Jalv* jalv, uint32_t nframes) -{ - if (!jalv->has_ui) { - return; - } - - ControlChange ev = {0U, 0U, 0U}; - const size_t space = zix_ring_read_space(jalv->ui_to_plugin); - for (size_t i = 0; i < space; i += sizeof(ev) + ev.size) { - if (zix_ring_read(jalv->ui_to_plugin, &ev, sizeof(ev)) != sizeof(ev)) { - jalv_log(JALV_LOG_ERR, "Failed to read header from UI ring buffer\n"); - break; - } - - struct { - union { - LV2_Atom atom; - float control; - } head; - uint8_t body[MSG_BUFFER_SIZE]; - } buffer; - - if (zix_ring_read(jalv->ui_to_plugin, &buffer, ev.size) != ev.size) { - jalv_log(JALV_LOG_ERR, "Failed to read from UI ring buffer\n"); - break; - } - - assert(ev.index < jalv->num_ports); - struct Port* const port = &jalv->ports[ev.index]; - if (ev.protocol == 0) { - assert(ev.size == sizeof(float)); - port->control = buffer.head.control; - } else if (ev.protocol == jalv->urids.atom_eventTransfer) { - LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); - const LV2_Atom* const atom = &buffer.head.atom; - lv2_evbuf_write( - &e, nframes, 0, atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); - } else { - jalv_log( - JALV_LOG_ERR, "Unknown control change protocol %u\n", ev.protocol); - } - } -} - void jalv_init_ui(Jalv* jalv) { // Set initial control port values for (uint32_t i = 0; i < jalv->num_ports; ++i) { if (jalv->ports[i].type == TYPE_CONTROL) { - jalv_ui_port_event(jalv, i, sizeof(float), 0, &jalv->ports[i].control); + jalv_frontend_port_event( + jalv, i, sizeof(float), 0, &jalv->process.controls_buf[i]); } } - if (jalv->control_in != (uint32_t)-1) { + 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; - uint8_t buf[MSG_BUFFER_SIZE]; - lv2_atom_forge_set_buffer(&forge, buf, sizeof(buf)); - lv2_atom_forge_object(&forge, &frame, 0, jalv->urids.patch_Get); + uint64_t buf[4U] = {0U, 0U, 0U, 0U}; + 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); } } static int -jalv_write_control_change(Jalv* const jalv, - ZixRing* const target, - const void* const header, - const uint32_t header_size, - const void* const body, - const uint32_t body_size) +ring_error(const char* const message) { - ZixRingTransaction tx = zix_ring_begin_write(target); - if (zix_ring_amend_write(target, &tx, header, header_size) || - zix_ring_amend_write(target, &tx, body, body_size)) { - jalv_log(JALV_LOG_ERR, - target == jalv->plugin_to_ui ? "Plugin => UI buffer overflow" - : "UI => Plugin buffer overflow"); - return -1; - } - - zix_ring_commit_write(target, &tx); - return 0; -} - -int -jalv_write_event(Jalv* const jalv, - ZixRing* const target, - const uint32_t port_index, - const uint32_t size, - const LV2_URID type, - const void* const body) -{ - // TODO: Be more discriminate about what to send - - typedef struct { - ControlChange change; - LV2_Atom atom; - } Header; - - const Header header = { - {port_index, jalv->urids.atom_eventTransfer, sizeof(LV2_Atom) + size}, - {size, type}}; - - return jalv_write_control_change( - jalv, target, &header, sizeof(header), body, size); -} - -int -jalv_write_control(Jalv* const jalv, - ZixRing* const target, - const uint32_t port_index, - const float value) -{ - const ControlChange header = {port_index, 0, sizeof(value)}; - - return jalv_write_control_change( - jalv, target, &header, sizeof(header), &value, sizeof(value)); -} - -void -jalv_dump_atom(Jalv* const jalv, - FILE* const stream, - const char* const label, - const LV2_Atom* const atom, - const int color) -{ - if (jalv->opts.dump) { - char* const str = sratom_to_turtle(jalv->sratom, - &jalv->unmap, - "jalv:", - NULL, - NULL, - atom->type, - atom->size, - LV2_ATOM_BODY_CONST(atom)); - - jalv_ansi_start(stream, color); - fprintf(stream, "\n# %s (%u bytes):\n%s\n", label, atom->size, str); - jalv_ansi_reset(stream); - free(str); - } -} - -bool -jalv_run(Jalv* jalv, uint32_t nframes) -{ - // Read and apply control change events from UI - jalv_apply_ui_events(jalv, nframes); - - // Run plugin for this cycle - lilv_instance_run(jalv->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); - - // Check if it's time to send updates to the UI - jalv->event_delta_t += nframes; - bool send_ui_updates = false; - uint32_t update_frames = (uint32_t)(jalv->sample_rate / jalv->ui_update_hz); - if (jalv->has_ui && (jalv->event_delta_t > update_frames)) { - send_ui_updates = true; - jalv->event_delta_t = 0; - } - - return send_ui_updates; + jalv_log(JALV_LOG_ERR, "%s", message); + return 1; } int @@ -745,27 +460,36 @@ jalv_update(Jalv* jalv) } // Emit UI events - ControlChange ev; - const size_t space = zix_ring_read_space(jalv->plugin_to_ui); - for (size_t i = 0; i + sizeof(ev) < space; i += sizeof(ev) + ev.size) { - // Read event header to get the size - zix_ring_read(jalv->plugin_to_ui, &ev, sizeof(ev)); - - // Resize read buffer if necessary - jalv->ui_event_buf = realloc(jalv->ui_event_buf, ev.size); - void* const buf = jalv->ui_event_buf; - - // Read event body - zix_ring_read(jalv->plugin_to_ui, buf, ev.size); - - if (ev.protocol == jalv->urids.atom_eventTransfer) { - jalv_dump_atom(jalv, stdout, "Plugin => UI", (const LV2_Atom*)buf, 35); + 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) { + // Read message header (which includes the body size) + if (zix_ring_read(ring, &header, sizeof(header)) != sizeof(header)) { + return ring_error("Failed to read header from process ring\n"); } - jalv_ui_port_event(jalv, ev.index, ev.size, ev.protocol, buf); + // Read message body + void* const body = jalv->ui_msg; + if (zix_ring_read(ring, body, header.size) != header.size) { + return ring_error("Failed to read message from process ring\n"); + } - if (ev.protocol == 0 && jalv->opts.print_controls) { - jalv_print_control(jalv, &jalv->ports[ev.index], *(float*)buf); + if (header.type == CONTROL_PORT_CHANGE) { + const JalvControlChange* const msg = (const JalvControlChange*)body; + jalv_frontend_port_event(jalv, msg->port_index, sizeof(float), 0, body); + } else if (header.type == EVENT_TRANSFER) { + const JalvEventTransfer* const msg = (const JalvEventTransfer*)body; + jalv_dump_atom(jalv->dumper, stdout, "Plugin => UI", &msg->atom, 35); + jalv_frontend_port_event(jalv, + msg->port_index, + sizeof(LV2_Atom) + msg->atom.size, + jalv->urids.atom_eventTransfer, + &msg->atom); + } else if (header.type == LATENCY_CHANGE) { + jalv_backend_recompute_latencies(jalv->backend); + } else { + return ring_error("Unknown message type received from process ring\n"); } } @@ -775,14 +499,14 @@ jalv_update(Jalv* jalv) static bool jalv_apply_control_arg(Jalv* jalv, const char* s) { - char sym[256]; - float val = 0.0f; - if (sscanf(s, "%[^=]=%f", sym, &val) != 2) { + char sym[256] = {'\0'}; + float val = 0.0f; + if (sscanf(s, "%240[^=]=%f", sym, &val) != 2) { jalv_log(JALV_LOG_WARNING, "Ignoring invalid value `%s'\n", s); return false; } - ControlID* control = jalv_control_by_symbol(jalv, sym); + const ControlID* control = jalv_control_by_symbol(jalv, sym); if (!control) { jalv_log( JALV_LOG_WARNING, "Ignoring value for unknown control `%s'\n", sym); @@ -796,37 +520,12 @@ jalv_apply_control_arg(Jalv* jalv, const char* s) } static void -signal_handler(int ZIX_UNUSED(sig)) -{ - zix_sem_post(exit_sem); -} - -static void init_feature(LV2_Feature* const dest, const char* const URI, void* data) { dest->URI = URI; dest->data = data; } -static void -setup_signals(Jalv* const jalv) -{ - exit_sem = &jalv->done; - -#if !defined(_WIN32) && USE_SIGACTION - struct sigaction action; - sigemptyset(&action.sa_mask); - action.sa_flags = 0; - action.sa_handler = signal_handler; - sigaction(SIGINT, &action, NULL); - sigaction(SIGTERM, &action, NULL); -#else - // May not work in combination with fgets in the console interface - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); -#endif -} - static const LilvUI* jalv_select_custom_ui(const Jalv* const jalv) { @@ -887,114 +586,17 @@ jalv_select_custom_ui(const Jalv* const jalv) } static void -jalv_init_urids(Symap* const symap, JalvURIDs* const urids) -{ -#define MAP_URI(uri) symap_map(symap, (uri)) - - urids->atom_Float = MAP_URI(LV2_ATOM__Float); - urids->atom_Int = MAP_URI(LV2_ATOM__Int); - urids->atom_Object = MAP_URI(LV2_ATOM__Object); - urids->atom_Path = MAP_URI(LV2_ATOM__Path); - urids->atom_String = MAP_URI(LV2_ATOM__String); - urids->atom_eventTransfer = MAP_URI(LV2_ATOM__eventTransfer); - urids->bufsz_maxBlockLength = MAP_URI(LV2_BUF_SIZE__maxBlockLength); - urids->bufsz_minBlockLength = MAP_URI(LV2_BUF_SIZE__minBlockLength); - urids->bufsz_sequenceSize = MAP_URI(LV2_BUF_SIZE__sequenceSize); - urids->log_Error = MAP_URI(LV2_LOG__Error); - urids->log_Trace = MAP_URI(LV2_LOG__Trace); - urids->log_Warning = MAP_URI(LV2_LOG__Warning); - urids->midi_MidiEvent = MAP_URI(LV2_MIDI__MidiEvent); - urids->param_sampleRate = MAP_URI(LV2_PARAMETERS__sampleRate); - urids->patch_Get = MAP_URI(LV2_PATCH__Get); - urids->patch_Put = MAP_URI(LV2_PATCH__Put); - urids->patch_Set = MAP_URI(LV2_PATCH__Set); - urids->patch_body = MAP_URI(LV2_PATCH__body); - urids->patch_property = MAP_URI(LV2_PATCH__property); - urids->patch_value = MAP_URI(LV2_PATCH__value); - urids->time_Position = MAP_URI(LV2_TIME__Position); - urids->time_bar = MAP_URI(LV2_TIME__bar); - urids->time_barBeat = MAP_URI(LV2_TIME__barBeat); - urids->time_beatUnit = MAP_URI(LV2_TIME__beatUnit); - urids->time_beatsPerBar = MAP_URI(LV2_TIME__beatsPerBar); - urids->time_beatsPerMinute = MAP_URI(LV2_TIME__beatsPerMinute); - urids->time_frame = MAP_URI(LV2_TIME__frame); - urids->time_speed = MAP_URI(LV2_TIME__speed); - urids->ui_scaleFactor = MAP_URI(LV2_UI__scaleFactor); - urids->ui_updateRate = MAP_URI(LV2_UI__updateRate); - -#undef MAP_URI -} - -static void -jalv_init_nodes(LilvWorld* const world, JalvNodes* const nodes) -{ -#define MAP_NODE(uri) lilv_new_uri(world, (uri)) - - nodes->atom_AtomPort = MAP_NODE(LV2_ATOM__AtomPort); - nodes->atom_Chunk = MAP_NODE(LV2_ATOM__Chunk); - nodes->atom_Float = MAP_NODE(LV2_ATOM__Float); - nodes->atom_Path = MAP_NODE(LV2_ATOM__Path); - nodes->atom_Sequence = MAP_NODE(LV2_ATOM__Sequence); - nodes->lv2_AudioPort = MAP_NODE(LV2_CORE__AudioPort); - nodes->lv2_CVPort = MAP_NODE(LV2_CORE__CVPort); - nodes->lv2_ControlPort = MAP_NODE(LV2_CORE__ControlPort); - nodes->lv2_InputPort = MAP_NODE(LV2_CORE__InputPort); - nodes->lv2_OutputPort = MAP_NODE(LV2_CORE__OutputPort); - nodes->lv2_connectionOptional = MAP_NODE(LV2_CORE__connectionOptional); - nodes->lv2_control = MAP_NODE(LV2_CORE__control); - nodes->lv2_default = MAP_NODE(LV2_CORE__default); - nodes->lv2_enumeration = MAP_NODE(LV2_CORE__enumeration); - nodes->lv2_extensionData = MAP_NODE(LV2_CORE__extensionData); - nodes->lv2_integer = MAP_NODE(LV2_CORE__integer); - nodes->lv2_maximum = MAP_NODE(LV2_CORE__maximum); - nodes->lv2_minimum = MAP_NODE(LV2_CORE__minimum); - nodes->lv2_name = MAP_NODE(LV2_CORE__name); - nodes->lv2_reportsLatency = MAP_NODE(LV2_CORE__reportsLatency); - nodes->lv2_sampleRate = MAP_NODE(LV2_CORE__sampleRate); - nodes->lv2_symbol = MAP_NODE(LV2_CORE__symbol); - nodes->lv2_toggled = MAP_NODE(LV2_CORE__toggled); - nodes->midi_MidiEvent = MAP_NODE(LV2_MIDI__MidiEvent); - nodes->pg_group = MAP_NODE(LV2_PORT_GROUPS__group); - nodes->pprops_logarithmic = MAP_NODE(LV2_PORT_PROPS__logarithmic); - nodes->pprops_notOnGUI = MAP_NODE(LV2_PORT_PROPS__notOnGUI); - nodes->pprops_rangeSteps = MAP_NODE(LV2_PORT_PROPS__rangeSteps); - nodes->pset_Preset = MAP_NODE(LV2_PRESETS__Preset); - nodes->pset_bank = MAP_NODE(LV2_PRESETS__bank); - nodes->rdfs_comment = MAP_NODE(LILV_NS_RDFS "comment"); - nodes->rdfs_label = MAP_NODE(LILV_NS_RDFS "label"); - nodes->rdfs_range = MAP_NODE(LILV_NS_RDFS "range"); - nodes->rsz_minimumSize = MAP_NODE(LV2_RESIZE_PORT__minimumSize); - nodes->ui_showInterface = MAP_NODE(LV2_UI__showInterface); - nodes->work_interface = MAP_NODE(LV2_WORKER__interface); - nodes->work_schedule = MAP_NODE(LV2_WORKER__schedule); - nodes->end = NULL; - -#undef MAP_NODE -} - -static void -jalv_init_env(SerdEnv* const env) -{ - serd_env_set_prefix_from_strings( - env, (const uint8_t*)"patch", (const uint8_t*)LV2_PATCH_PREFIX); - serd_env_set_prefix_from_strings( - env, (const uint8_t*)"time", (const uint8_t*)LV2_TIME_PREFIX); - serd_env_set_prefix_from_strings( - env, (const uint8_t*)"xsd", (const uint8_t*)NS_XSD); -} - -static void jalv_init_features(Jalv* const jalv) { // urid:map - jalv->map.handle = jalv; - jalv->map.map = map_uri; - init_feature(&jalv->features.map_feature, LV2_URID__map, &jalv->map); + init_feature(&jalv->features.map_feature, + LV2_URID__map, + jalv_mapper_urid_map(jalv->mapper)); // urid:unmap - jalv->unmap.handle = jalv; - jalv->unmap.unmap = unmap_uri; - init_feature(&jalv->features.unmap_feature, LV2_URID__unmap, &jalv->unmap); + init_feature(&jalv->features.unmap_feature, + LV2_URID__unmap, + jalv_mapper_urid_unmap(jalv->mapper)); // state:makePath jalv->features.make_path.handle = jalv; @@ -1034,80 +636,52 @@ jalv_init_features(Jalv* const jalv) } static void -jalv_init_options(Jalv* const jalv) +jalv_init_ui_settings(Jalv* const jalv) { - const LV2_Options_Option options[ARRAY_SIZE(jalv->features.options)] = { - {LV2_OPTIONS_INSTANCE, - 0, - jalv->urids.param_sampleRate, - sizeof(float), - jalv->urids.atom_Float, - &jalv->sample_rate}, - {LV2_OPTIONS_INSTANCE, - 0, - jalv->urids.bufsz_minBlockLength, - sizeof(int32_t), - jalv->urids.atom_Int, - &jalv->block_length}, - {LV2_OPTIONS_INSTANCE, - 0, - jalv->urids.bufsz_maxBlockLength, - sizeof(int32_t), - jalv->urids.atom_Int, - &jalv->block_length}, - {LV2_OPTIONS_INSTANCE, - 0, - jalv->urids.bufsz_sequenceSize, - sizeof(int32_t), - jalv->urids.atom_Int, - &jalv->midi_buf_size}, - {LV2_OPTIONS_INSTANCE, - 0, - jalv->urids.ui_updateRate, - sizeof(float), - jalv->urids.atom_Float, - &jalv->ui_update_hz}, - {LV2_OPTIONS_INSTANCE, - 0, - jalv->urids.ui_scaleFactor, - sizeof(float), - jalv->urids.atom_Float, - &jalv->ui_scale_factor}, - {LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL}}; - - memcpy(jalv->features.options, options, sizeof(jalv->features.options)); - - init_feature(&jalv->features.options_feature, - LV2_OPTIONS__options, - (void*)jalv->features.options); -} + const JalvOptions* const opts = &jalv->opts; + JalvSettings* const settings = &jalv->settings; -static void -jalv_init_display(Jalv* const jalv) -{ - if (!jalv->opts.update_rate) { + if (!settings->ring_size) { + /* The UI ring is fed by plugin output ports (usually one), and the UI + updates roughly once per cycle. The ring size is a few times the size + of the MIDI output to give the UI a chance to keep up. */ + settings->ring_size = settings->midi_buf_size * N_BUFFER_CYCLES; + } + + if (opts->update_rate <= 0.0f) { // Calculate a reasonable UI update frequency - jalv->ui_update_hz = jalv_frontend_refresh_rate(jalv); - } else { - // Use user-specified UI update rate - jalv->ui_update_hz = jalv->opts.update_rate; - jalv->ui_update_hz = MAX(1.0f, jalv->ui_update_hz); + settings->ui_update_hz = jalv_frontend_refresh_rate(jalv); } - if (!jalv->opts.scale_factor) { + if (opts->scale_factor <= 0.0f) { // Calculate the monitor's scale factor - jalv->ui_scale_factor = jalv_frontend_scale_factor(jalv); - } else { - // Use user-specified UI scale factor - jalv->ui_scale_factor = jalv->opts.scale_factor; + settings->ui_scale_factor = jalv_frontend_scale_factor(jalv); } // The UI can only go so fast, clamp to reasonable limits - jalv->ui_update_hz = MIN(60, jalv->ui_update_hz); - jalv->opts.buffer_size = MAX(4096, jalv->opts.buffer_size); - jalv_log(JALV_LOG_INFO, "Comm buffers: %u bytes\n", jalv->opts.buffer_size); - jalv_log(JALV_LOG_INFO, "Update rate: %.01f Hz\n", jalv->ui_update_hz); - jalv_log(JALV_LOG_INFO, "Scale factor: %.01f\n", jalv->ui_scale_factor); + settings->ui_update_hz = MAX(1.0f, MIN(60.0f, settings->ui_update_hz)); + settings->ring_size = MAX(4096, settings->ring_size); + jalv_log(JALV_LOG_INFO, "Comm buffers: %u bytes\n", settings->ring_size); + jalv_log(JALV_LOG_INFO, "Update rate: %.01f Hz\n", settings->ui_update_hz); + jalv_log(JALV_LOG_INFO, "Scale factor: %.01f\n", settings->ui_scale_factor); +} + +static LilvState* +initial_state(LilvWorld* const world, + LV2_URID_Map* const urid_map, + const char* const state_path) +{ + LilvState* state = NULL; + if (state_path) { + if (zix_file_type(state_path) == ZIX_FILE_TYPE_DIRECTORY) { + char* const path = zix_path_join(NULL, state_path, "state.ttl"); + state = lilv_state_new_from_file(world, urid_map, NULL, path); + free(path); + } else { + state = lilv_state_new_from_file(world, urid_map, NULL, state_path); + } + } + return state; } int @@ -1118,86 +692,70 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) #endif // Parse command-line arguments - int ret = 0; - if ((ret = jalv_frontend_init(argc, argv, &jalv->opts))) { - jalv_close(jalv); + JalvFrontendArgs args = {argc, argv}; + const int ret = jalv_frontend_init(&args, &jalv->opts); + if (ret) { return ret; } + JalvSettings* const settings = &jalv->settings; + + settings->block_length = 4096U; + settings->midi_buf_size = 1024U; + settings->ring_size = jalv->opts.ring_size; + settings->ui_update_hz = jalv->opts.update_rate; + settings->ui_scale_factor = jalv->opts.scale_factor; + // Load the LV2 world LilvWorld* const world = lilv_world_new(); lilv_world_load_all(world); - jalv->world = world; - jalv->env = serd_env_new(NULL); - jalv->symap = symap_new(); - jalv->block_length = 4096U; - jalv->midi_buf_size = 1024U; - jalv->play_state = JALV_PAUSED; - jalv->bpm = 120.0f; - jalv->control_in = (uint32_t)-1; - jalv->log.urids = &jalv->urids; - jalv->log.tracing = jalv->opts.trace; - - zix_sem_init(&jalv->symap_lock, 1); + 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); + LV2_URID_Unmap* const urid_unmap = jalv_mapper_urid_unmap(jalv->mapper); + if (jalv->opts.dump) { + jalv->dumper = jalv_dumper_new(urid_map, urid_unmap); + } + zix_sem_init(&jalv->work_lock, 1); zix_sem_init(&jalv->done, 0); - zix_sem_init(&jalv->paused, 0); - - jalv_init_env(jalv->env); - jalv_init_urids(jalv->symap, &jalv->urids); + jalv_init_urids(jalv->mapper, &jalv->urids); jalv_init_nodes(world, &jalv->nodes); jalv_init_features(jalv); - lv2_atom_forge_init(&jalv->forge, &jalv->map); - - // Set up atom reading and writing environment - jalv->sratom = sratom_new(&jalv->map); - sratom_set_env(jalv->sratom, jalv->env); - jalv->ui_sratom = sratom_new(&jalv->map); - sratom_set_env(jalv->ui_sratom, jalv->env); + lv2_atom_forge_init(&jalv->forge, urid_map); // Create temporary directory for plugin state -#ifdef _WIN32 - jalv->temp_dir = jalv_strdup("jalvXXXXXX"); - _mktemp(jalv->temp_dir); -#else - char* templ = jalv_strdup("/tmp/jalv-XXXXXX"); - jalv->temp_dir = jalv_strjoin(mkdtemp(templ), "/"); - free(templ); -#endif - - // Get plugin URI from loaded state or command line - LilvState* state = NULL; - LilvNode* plugin_uri = NULL; - if (jalv->opts.load) { - struct stat info; - stat(jalv->opts.load, &info); - if ((info.st_mode & S_IFMT) == S_IFDIR) { - char* path = jalv_strjoin(jalv->opts.load, "/state.ttl"); - state = lilv_state_new_from_file(jalv->world, &jalv->map, NULL, path); - free(path); - } else { - state = lilv_state_new_from_file( - jalv->world, &jalv->map, NULL, jalv->opts.load); - } - if (!state) { - jalv_log(JALV_LOG_ERR, "Failed to load state from %s\n", jalv->opts.load); - jalv_close(jalv); - return -2; - } - plugin_uri = lilv_node_duplicate(lilv_state_get_plugin_uri(state)); - } else if (*argc > 1) { - plugin_uri = lilv_new_uri(world, (*argv)[*argc - 1]); + jalv->temp_dir = zix_create_temporary_directory(NULL, "jalvXXXXXX"); + if (!jalv->temp_dir) { + jalv_log(JALV_LOG_WARNING, "Failed to create temporary state directory\n"); } - if (!plugin_uri) { - plugin_uri = jalv_frontend_select_plugin(jalv); + // Load initial state given in options if any + LilvState* state = initial_state(world, urid_map, jalv->opts.load); + if (jalv->opts.load && !state) { + jalv_log(JALV_LOG_ERR, "Failed to load state from %s\n", jalv->opts.load); + return -2; } - if (!plugin_uri) { - jalv_log(JALV_LOG_ERR, "Missing plugin URI, try lv2ls to list plugins\n"); - jalv_close(jalv); - return -3; + // Get plugin URI from loaded state or command line + LilvNode* plugin_uri = NULL; + if (state) { + plugin_uri = lilv_node_duplicate(lilv_state_get_plugin_uri(state)); + } else if (*args.argc == 0) { + if (!(plugin_uri = jalv_frontend_select_plugin(jalv))) { + jalv_log(JALV_LOG_ERR, "Missing plugin URI, try lv2ls to list plugins\n"); + return -3; + } + } else if (*args.argc == 1) { + plugin_uri = lilv_new_uri(world, (*args.argv)[0]); + } else { + jalv_log(JALV_LOG_ERR, "Unexpected trailing arguments\n"); + return -1; } // Find plugin @@ -1208,18 +766,22 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) lilv_node_free(plugin_uri); if (!jalv->plugin) { jalv_log(JALV_LOG_ERR, "Failed to find plugin\n"); - jalv_close(jalv); return -4; } + if (!jalv->opts.name) { + jalv->opts.name = + jalv_strdup(lilv_node_as_string(lilv_plugin_get_name(jalv->plugin))); + } + // 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; } } @@ -1228,28 +790,23 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) LilvNode* preset = lilv_new_uri(jalv->world, jalv->opts.preset); jalv_load_presets(jalv, NULL, NULL); - state = lilv_state_new_from_world(jalv->world, &jalv->map, preset); + state = lilv_state_new_from_world(jalv->world, urid_map, preset); jalv->preset = state; lilv_node_free(preset); if (!state) { jalv_log(JALV_LOG_ERR, "Failed to find preset <%s>\n", jalv->opts.preset); - jalv_close(jalv); return -5; } } // Check for thread-safe state restore() method - LilvNode* state_threadSafeRestore = - lilv_new_uri(jalv->world, LV2_STATE__threadSafeRestore); - if (lilv_plugin_has_feature(jalv->plugin, state_threadSafeRestore)) { - jalv->safe_restore = true; - } - lilv_node_free(state_threadSafeRestore); + jalv->safe_restore = + lilv_plugin_has_feature(jalv->plugin, jalv->nodes.state_threadSafeRestore); if (!state) { // Not restoring state, load the plugin as a preset to get default state = lilv_state_new_from_world( - jalv->world, &jalv->map, lilv_plugin_get_uri(jalv->plugin)); + jalv->world, urid_map, lilv_plugin_get_uri(jalv->plugin)); } // Get a plugin UI @@ -1270,46 +827,47 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) } #endif - if (jalv->ui) { - jalv_log(JALV_LOG_INFO, - "UI: %s\n", - lilv_node_as_uri(lilv_ui_get_uri(jalv->ui))); - } + jalv_log(JALV_LOG_INFO, + "UI: %s\n", + lilv_node_as_uri(lilv_ui_get_uri(jalv->ui))); } } - // Create port and control structures - jalv_create_ports(jalv); + // 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; + } + + // Create input and output control structures jalv_create_controls(jalv, true); jalv_create_controls(jalv, false); - if (!(jalv->backend = jalv_backend_init(jalv))) { + if (jalv_backend_open(jalv->backend, + &jalv->urids, + &jalv->settings, + &jalv->process, + &jalv->done, + jalv->opts.name, + jalv->opts.name_exact)) { jalv_log(JALV_LOG_ERR, "Failed to connect to audio system\n"); - jalv_close(jalv); return -6; } - jalv_log(JALV_LOG_INFO, "Sample rate: %u Hz\n", (uint32_t)jalv->sample_rate); - jalv_log(JALV_LOG_INFO, "Block length: %u frames\n", jalv->block_length); - jalv_log(JALV_LOG_INFO, "MIDI buffers: %zu bytes\n", jalv->midi_buf_size); - - if (jalv->opts.buffer_size == 0) { - /* The UI ring is fed by plugin output ports (usually one), and the UI - updates roughly once per cycle. The ring size is a few times the size - of the MIDI output to give the UI a chance to keep up. The UI should be - able to keep up with 4 cycles, and tests show this works for me, but - this value might need increasing to avoid overflows. */ - jalv->opts.buffer_size = jalv->midi_buf_size * N_BUFFER_CYCLES; - } + jalv_log( + JALV_LOG_INFO, "Sample rate: %u Hz\n", (uint32_t)settings->sample_rate); + jalv_log(JALV_LOG_INFO, "Block length: %u frames\n", settings->block_length); + jalv_log(JALV_LOG_INFO, "MIDI buffers: %zu bytes\n", settings->midi_buf_size); - jalv_init_display(jalv); - jalv_init_options(jalv); + jalv_init_ui_settings(jalv); + jalv_init_lv2_options(&jalv->features, &jalv->urids, settings); - // Create Plugin <=> UI communication buffers - jalv->ui_to_plugin = zix_ring_new(NULL, jalv->opts.buffer_size); - jalv->plugin_to_ui = zix_ring_new(NULL, jalv->opts.buffer_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, @@ -1326,7 +884,6 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) jalv->feature_list = (const LV2_Feature**)calloc(1, sizeof(features)); if (!jalv->feature_list) { jalv_log(JALV_LOG_ERR, "Failed to allocate feature list\n"); - jalv_close(jalv); return -7; } memcpy(jalv->feature_list, features, sizeof(features)); @@ -1337,38 +894,37 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) const char* uri = lilv_node_as_uri(lilv_nodes_get(req_feats, f)); if (!feature_is_supported(jalv, uri)) { jalv_log(JALV_LOG_ERR, "Feature %s is not supported\n", uri); - jalv_close(jalv); return -8; } } lilv_nodes_free(req_feats); // Instantiate the plugin - jalv->instance = lilv_plugin_instantiate( - jalv->plugin, jalv->sample_rate, jalv->feature_list); - if (!jalv->instance) { + LilvInstance* const instance = lilv_plugin_instantiate( + jalv->plugin, settings->sample_rate, jalv->feature_list); + if (!instance) { jalv_log(JALV_LOG_ERR, "Failed to instantiate plugin\n"); - jalv_close(jalv); 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); - - jalv_worker_start(jalv->worker, worker_iface, jalv->instance->lv2_handle); - jalv_worker_start( - jalv->state_worker, worker_iface, jalv->instance->lv2_handle); + instance, LV2_WORKER__interface); + jalv_worker_attach(jalv->process.worker, worker_iface, instance->lv2_handle); + jalv_worker_attach( + jalv->process.state_worker, worker_iface, instance->lv2_handle); jalv_log(JALV_LOG_INFO, "\n"); - if (!jalv->buf_size_set) { - 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) { @@ -1385,100 +941,100 @@ jalv_open(Jalv* const jalv, int* argc, char*** argv) // Create Jack ports and connect plugin ports to buffers for (uint32_t i = 0; i < jalv->num_ports; ++i) { - jalv_backend_activate_port(jalv, i); + jalv_backend_activate_port(jalv->backend, &jalv->process, i); } - // Print initial control values - for (size_t i = 0; i < jalv->controls.n_controls; ++i) { - ControlID* control = jalv->controls.controls[i]; - if (control->type == PORT && control->is_writable) { - struct Port* port = &jalv->ports[control->index]; - jalv_print_control(jalv, port, port->control); + // Discover UI + jalv->process.has_ui = jalv_frontend_discover(jalv); + return 0; +} + +int +jalv_activate(Jalv* const jalv) +{ + jalv->process.run_state = JALV_RUNNING; + + if (jalv->backend) { + if (jalv->process.worker) { + jalv_worker_launch(jalv->process.worker); } + lilv_instance_activate(jalv->process.instance); + jalv_backend_activate(jalv->backend); } - // Activate plugin - lilv_instance_activate(jalv->instance); - - // Discover UI - jalv->has_ui = jalv_frontend_discover(jalv); + return 0; +} - // Activate audio backend - jalv_backend_activate(jalv); - jalv->play_state = JALV_RUNNING; +int +jalv_deactivate(Jalv* const jalv) +{ + if (jalv->backend) { + jalv_backend_deactivate(jalv->backend); + } + if (jalv->process.instance) { + lilv_instance_deactivate(jalv->process.instance); + } + if (jalv->process.worker) { + jalv_worker_exit(jalv->process.worker); + } + jalv->process.run_state = JALV_PAUSED; return 0; } int jalv_close(Jalv* const jalv) { - // Terminate the worker - jalv_worker_exit(jalv->worker); - - // Deactivate audio + // Stop audio processing, free event port buffers, and close backend + jalv_deactivate(jalv); + jalv_process_deactivate(&jalv->process); if (jalv->backend) { - jalv_backend_deactivate(jalv); - jalv_backend_close(jalv); + jalv_backend_close(jalv->backend); } - // Free event port buffers - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - if (jalv->ports[i].evbuf) { - lv2_evbuf_free(jalv->ports[i].evbuf); - } - } - - // Destroy the worker - jalv_worker_free(jalv->worker); - jalv_worker_free(jalv->state_worker); - - // Deactivate plugin + // Free UI and plugin instances #if USE_SUIL suil_instance_free(jalv->ui_instance); #endif - if (jalv->instance) { - lilv_instance_deactivate(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); - for (LilvNode** n = (LilvNode**)&jalv->nodes; *n; ++n) { - lilv_node_free(*n); - } - symap_free(jalv->symap); - zix_sem_destroy(&jalv->symap_lock); + jalv_process_cleanup(&jalv->process); + zix_aligned_free(NULL, jalv->ui_msg); + free(jalv->process.controls_buf); + jalv_free_nodes(&jalv->nodes); #if USE_SUIL suil_host_free(jalv->ui_host); #endif for (unsigned i = 0; i < jalv->controls.n_controls; ++i) { - ControlID* const control = jalv->controls.controls[i]; - lilv_node_free(control->node); - lilv_node_free(control->symbol); - lilv_node_free(control->label); - lilv_node_free(control->group); - lilv_node_free(control->min); - lilv_node_free(control->max); - lilv_node_free(control->def); - free(control); + free_control(jalv->controls.controls[i]); } free(jalv->controls.controls); - sratom_free(jalv->sratom); - sratom_free(jalv->ui_sratom); - serd_env_free(jalv->env); + jalv_dumper_free(jalv->dumper); lilv_uis_free(jalv->uis); + jalv_mapper_free(jalv->mapper); lilv_world_free(jalv->world); zix_sem_destroy(&jalv->done); - remove(jalv->temp_dir); - free(jalv->temp_dir); - free(jalv->ui_event_buf); + if (jalv->temp_dir) { + // Remove temporary state directory + const ZixStatus zst = zix_remove(jalv->temp_dir); + if (zst) { + jalv_log(JALV_LOG_WARNING, + "Failed to remove temporary directory %s (%s)\n", + jalv->temp_dir, + zix_strerror(zst)); + } + } + + zix_free(NULL, jalv->temp_dir); free(jalv->feature_list); free(jalv->opts.name); @@ -1487,25 +1043,3 @@ jalv_close(Jalv* const jalv) return 0; } - -int -main(int argc, char** argv) -{ - Jalv jalv; - memset(&jalv, '\0', sizeof(Jalv)); - - if (jalv_open(&jalv, &argc, &argv)) { - return EXIT_FAILURE; - } - - // Set up signal handlers - setup_signals(&jalv); - - // Run UI (or prompt at console) - jalv_frontend_open(&jalv); - - // Wait for finish signal from UI or signal handler - zix_sem_wait(&jalv.done); - - return jalv_close(&jalv); -} |