diff options
Diffstat (limited to 'src/jalv.c')
-rw-r--r-- | src/jalv.c | 2378 |
1 files changed, 1273 insertions, 1105 deletions
@@ -1,25 +1,18 @@ -/* - Copyright 2007-2016 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#define _POSIX_C_SOURCE 200809L /* for mkdtemp */ -#define _DARWIN_C_SOURCE /* for mkdtemp on OSX */ +// Copyright 2007-2022 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC +#include "backend.h" +#include "control.h" +#include "frontend.h" #include "jalv_config.h" #include "jalv_internal.h" +#include "log.h" #include "lv2_evbuf.h" +#include "nodes.h" +#include "port.h" +#include "state.h" +#include "types.h" +#include "urids.h" #include "worker.h" #include "lilv/lilv.h" @@ -29,6 +22,7 @@ #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" @@ -46,17 +40,19 @@ #include "serd/serd.h" #include "sratom/sratom.h" #include "symap.h" -#include "zix/common.h" +#include "zix/attributes.h" #include "zix/ring.h" #include "zix/sem.h" -#ifdef HAVE_SUIL -#include "suil/suil.h" +#if USE_SUIL +# include "suil/suil.h" #endif -#ifdef _WIN32 -# include <io.h> /* for _mktemp */ -# define snprintf _snprintf +#if defined(_WIN32) +# include <io.h> // for _mktemp +# define snprintf _snprintf +#elif defined(__APPLE__) +# include <unistd.h> // for mkdtemp on Darwin #endif #include <assert.h> @@ -73,211 +69,220 @@ #define NS_XSD "http://www.w3.org/2001/XMLSchema#" #ifndef MIN -# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif #ifndef MAX -# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif #ifndef ARRAY_SIZE -# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #endif -/* Size factor for UI ring buffers. The ring size is a few times the size of - an event output to give the UI a chance to keep up. Experiments with Ingen, - which can highly saturate its event output, led me to this value. It - really ought to be enough for anybody(TM). +#ifndef MSG_BUFFER_SIZE +# define MSG_BUFFER_SIZE 1024 +#endif + +/** + Size factor for UI ring buffers. + + The ring size is a few times the size of an event output to give the UI a + chance to keep up. Experiments with Ingen, which can highly saturate its + event output, led me to this value. It really ought to be enough for + anybody(TM). */ #define N_BUFFER_CYCLES 16 -static ZixSem* exit_sem = NULL; /**< Exit semaphore used by signal handler*/ +static ZixSem* exit_sem = NULL; ///< Exit semaphore used by signal handler static LV2_URID -map_uri(LV2_URID_Map_Handle handle, - const char* uri) +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; + 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) +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; + 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 */ +/// These features have no data static const LV2_Feature static_features[] = { - { LV2_STATE__loadDefaultState, NULL }, - { LV2_BUF_SIZE__powerOf2BlockLength, NULL }, - { LV2_BUF_SIZE__fixedBlockLength, NULL }, - { LV2_BUF_SIZE__boundedBlockLength, NULL } }; + {LV2_STATE__loadDefaultState, NULL}, + {LV2_BUF_SIZE__powerOf2BlockLength, NULL}, + {LV2_BUF_SIZE__fixedBlockLength, NULL}, + {LV2_BUF_SIZE__boundedBlockLength, NULL}}; -/** Return true iff Jalv supports the given feature. */ +/// Return true iff Jalv supports the given feature static bool feature_is_supported(Jalv* jalv, const char* uri) { - if (!strcmp(uri, "http://lv2plug.in/ns/lv2core#isLive") || - !strcmp(uri, "http://lv2plug.in/ns/lv2core#inPlaceBroken")) { - return true; - } - - for (const LV2_Feature*const* f = jalv->feature_list; *f; ++f) { - if (!strcmp(uri, (*f)->URI)) { - return true; - } - } - return false; + if (!strcmp(uri, "http://lv2plug.in/ns/lv2core#isLive") || + !strcmp(uri, "http://lv2plug.in/ns/lv2core#inPlaceBroken")) { + return true; + } + + for (const LV2_Feature* const* f = jalv->feature_list; *f; ++f) { + if (!strcmp(uri, (*f)->URI)) { + return true; + } + } + return false; } -/** Abort and exit on error */ +/// Abort and exit on error static void die(const char* msg) { - fprintf(stderr, "%s\n", msg); - exit(EXIT_FAILURE); + 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(). + 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) +create_port(Jalv* jalv, uint32_t port_index, float default_value) { - struct Port* 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)"); - } - - const bool hidden = !jalv->opts.show_hidden && - lilv_port_has_property(jalv->plugin, - port->lilv_port, - jalv->nodes.pprops_notOnGUI); - - /* 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; - if (!hidden) { - add_control(&jalv->controls, new_port_control(jalv, port->index)); - } - } else if (lilv_port_is_a(jalv->plugin, port->lilv_port, - jalv->nodes.lv2_AudioPort)) { - port->type = TYPE_AUDIO; -#ifdef HAVE_JACK_METADATA - } else if (lilv_port_is_a(jalv->plugin, port->lilv_port, - jalv->nodes.lv2_CVPort)) { - port->type = TYPE_CV; + struct Port* 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)"); + } + + const bool hidden = !jalv->opts.show_hidden && + lilv_port_has_property(jalv->plugin, + port->lilv_port, + jalv->nodes.pprops_notOnGUI); + + // 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; + if (!hidden) { + add_control(&jalv->controls, + new_port_control(jalv->world, + jalv->plugin, + port->lilv_port, + port->index, + jalv->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); + } 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. -*/ +/// 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]); - } - - 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 { - fprintf(stderr, - "warning: Non-event port %u has lv2:control designation, " - "ignored\n", - index); - } - } - - free(default_values); + 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]); + } + + 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); + } + } + + free(default_values); } -/** - Allocate port buffers (only necessary for MIDI). -*/ +/// Allocate port buffers (only necessary for MIDI) void jalv_allocate_port_buffers(Jalv* jalv) { - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - struct Port* const port = &jalv->ports[i]; - switch (port->type) { - case TYPE_EVENT: { - lv2_evbuf_free(port->evbuf); - const size_t buf_size = (port->buf_size > 0) - ? port->buf_size - : jalv->midi_buf_size; - port->evbuf = lv2_evbuf_new( - buf_size, - jalv->map.map(jalv->map.handle, - lilv_node_as_string(jalv->nodes.atom_Chunk)), - jalv->map.map(jalv->map.handle, - lilv_node_as_string(jalv->nodes.atom_Sequence))); - lilv_instance_connect_port( - jalv->instance, i, lv2_evbuf_get_buffer(port->evbuf)); - } - default: break; - } - } + 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 size_t size = port->buf_size ? port->buf_size : jalv->midi_buf_size; + + port->evbuf = lv2_evbuf_new(size, atom_Chunk, atom_Sequence); + + lilv_instance_connect_port( + jalv->instance, i, lv2_evbuf_get_buffer(port->evbuf)); + + lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT); + } + } } /** @@ -289,1055 +294,1218 @@ jalv_allocate_port_buffers(Jalv* jalv) struct Port* 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 = lilv_port_get_symbol(jalv->plugin, - port->lilv_port); + for (uint32_t i = 0; i < jalv->num_ports; ++i) { + struct Port* 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)) { - return port; - } - } + if (!strcmp(lilv_node_as_string(port_sym), sym)) { + return port; + } + } - return NULL; + return NULL; } ControlID* jalv_control_by_symbol(Jalv* jalv, const char* sym) { - for (size_t i = 0; i < jalv->controls.n_controls; ++i) { - if (!strcmp(lilv_node_as_string(jalv->controls.controls[i]->symbol), - sym)) { - return jalv->controls.controls[i]; - } - } - return NULL; + for (size_t i = 0; i < jalv->controls.n_controls; ++i) { + if (!strcmp(lilv_node_as_string(jalv->controls.controls[i]->symbol), sym)) { + return jalv->controls.controls[i]; + } + } + return NULL; } void jalv_create_controls(Jalv* jalv, bool writable) { - const LilvPlugin* plugin = jalv->plugin; - LilvWorld* world = jalv->world; - LilvNode* patch_writable = lilv_new_uri(world, LV2_PATCH__writable); - LilvNode* patch_readable = lilv_new_uri(world, LV2_PATCH__readable); - - LilvNodes* properties = lilv_world_find_nodes( - world, - lilv_plugin_get_uri(plugin), - writable ? patch_writable : patch_readable, - NULL); - LILV_FOREACH(nodes, p, properties) { - const LilvNode* property = lilv_nodes_get(properties, p); - ControlID* record = NULL; - - if (!writable && lilv_world_ask(world, - lilv_plugin_get_uri(plugin), - patch_writable, - property)) { - // Find existing writable control - for (size_t i = 0; i < jalv->controls.n_controls; ++i) { - if (lilv_node_equals(jalv->controls.controls[i]->node, property)) { - record = jalv->controls.controls[i]; - record->is_readable = true; - break; - } - } - - if (record) { - continue; - } - } - - record = new_property_control(jalv, property); - if (writable) { - record->is_writable = true; - } else { - record->is_readable = true; - } - - if (record->value_type) { - add_control(&jalv->controls, record); - } else { - fprintf(stderr, "Parameter <%s> has unknown value type, ignored\n", - lilv_node_as_string(record->node)); - free(record); - } - } - lilv_nodes_free(properties); - - lilv_node_free(patch_readable); - lilv_node_free(patch_writable); + const LilvPlugin* plugin = jalv->plugin; + LilvWorld* world = jalv->world; + LilvNode* patch_writable = lilv_new_uri(world, LV2_PATCH__writable); + LilvNode* patch_readable = lilv_new_uri(world, LV2_PATCH__readable); + + LilvNodes* properties = + lilv_world_find_nodes(world, + lilv_plugin_get_uri(plugin), + writable ? patch_writable : patch_readable, + NULL); + LILV_FOREACH (nodes, p, properties) { + const LilvNode* property = lilv_nodes_get(properties, p); + ControlID* record = NULL; + + if (!writable && + lilv_world_ask( + world, lilv_plugin_get_uri(plugin), patch_writable, property)) { + // Find existing writable control + for (size_t i = 0; i < jalv->controls.n_controls; ++i) { + if (lilv_node_equals(jalv->controls.controls[i]->node, property)) { + record = jalv->controls.controls[i]; + record->is_readable = true; + break; + } + } + + if (record) { + continue; + } + } + + record = new_property_control( + jalv->world, property, &jalv->nodes, &jalv->map, &jalv->forge); + + if (writable) { + record->is_writable = true; + } else { + record->is_readable = true; + } + + if (record->value_type) { + add_control(&jalv->controls, record); + } else { + jalv_log(JALV_LOG_WARNING, + "Parameter <%s> has unknown value type, ignored\n", + lilv_node_as_string(record->node)); + free(record); + } + } + lilv_nodes_free(properties); + + lilv_node_free(patch_readable); + lilv_node_free(patch_writable); } void -jalv_set_control(const ControlID* control, +jalv_set_control(Jalv* jalv, + const ControlID* control, uint32_t size, LV2_URID type, const void* body) { - Jalv* jalv = control->jalv; - if (control->type == PORT && type == jalv->forge.Float) { - struct Port* port = &control->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; - LV2_Atom_Forge_Frame frame; - uint8_t buf[1024]; - lv2_atom_forge_set_buffer(&forge, buf, sizeof(buf)); - - 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); - - const LV2_Atom* atom = lv2_atom_forge_deref(&forge, frame.ref); - jalv_ui_write(jalv, - jalv->control_in, - lv2_atom_total_size(atom), - jalv->urids.atom_eventTransfer, - atom); - } + 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; + 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_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); + + const LV2_Atom* atom = lv2_atom_forge_deref(&forge, frame.ref); + jalv_send_to_plugin(jalv, + jalv->control_in, + lv2_atom_total_size(atom), + jalv->urids.atom_eventTransfer, + atom); + } } +#if USE_SUIL +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); + + return port ? port->index : LV2UI_INVALID_PORT_INDEX; +} +#endif + void jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) { -#ifdef HAVE_SUIL - jalv->ui_host = suil_host_new(jalv_ui_write, jalv_ui_port_index, NULL, NULL); - - const LV2_Feature parent_feature = { - LV2_UI__parent, parent - }; - const LV2_Feature instance_feature = { - NS_EXT "instance-access", lilv_instance_get_handle(jalv->instance) - }; - const LV2_Feature data_feature = { - LV2_DATA_ACCESS_URI, &jalv->features.ext_data - }; - const LV2_Feature idle_feature = { - LV2_UI__idleInterface, NULL - }; - const LV2_Feature* ui_features[] = { - &jalv->features.map_feature, - &jalv->features.unmap_feature, - &instance_feature, - &data_feature, - &jalv->features.log_feature, - &parent_feature, - &jalv->features.options_feature, - &idle_feature, - &jalv->features.request_value_feature, - NULL - }; - - const char* bundle_uri = lilv_node_as_uri(lilv_ui_get_bundle_uri(jalv->ui)); - const char* binary_uri = lilv_node_as_uri(lilv_ui_get_binary_uri(jalv->ui)); - char* bundle_path = lilv_file_uri_parse(bundle_uri, NULL); - char* binary_path = lilv_file_uri_parse(binary_uri, NULL); - - jalv->ui_instance = suil_instance_new( - jalv->ui_host, - jalv, - native_ui_type, - lilv_node_as_uri(lilv_plugin_get_uri(jalv->plugin)), - lilv_node_as_uri(lilv_ui_get_uri(jalv->ui)), - lilv_node_as_uri(jalv->ui_type), - bundle_path, - binary_path, - ui_features); - - lilv_free(binary_path); - lilv_free(bundle_path); +#if USE_SUIL + 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 data_feature = {LV2_DATA_ACCESS_URI, + &jalv->features.ext_data}; + + const LV2_Feature idle_feature = {LV2_UI__idleInterface, NULL}; + + const LV2_Feature* ui_features[] = {&jalv->features.map_feature, + &jalv->features.unmap_feature, + &instance_feature, + &data_feature, + &jalv->features.log_feature, + &parent_feature, + &jalv->features.options_feature, + &idle_feature, + &jalv->features.request_value_feature, + NULL}; + + const char* bundle_uri = lilv_node_as_uri(lilv_ui_get_bundle_uri(jalv->ui)); + const char* binary_uri = lilv_node_as_uri(lilv_ui_get_binary_uri(jalv->ui)); + char* bundle_path = lilv_file_uri_parse(bundle_uri, NULL); + char* binary_path = lilv_file_uri_parse(binary_uri, NULL); + + jalv->ui_instance = + suil_instance_new(jalv->ui_host, + jalv, + native_ui_type, + lilv_node_as_uri(lilv_plugin_get_uri(jalv->plugin)), + lilv_node_as_uri(lilv_ui_get_uri(jalv->ui)), + lilv_node_as_uri(jalv->ui_type), + bundle_path, + binary_path, + ui_features); + + lilv_free(binary_path); + lilv_free(bundle_path); #endif } bool jalv_ui_is_resizable(Jalv* jalv) { - if (!jalv->ui) { - return false; - } + 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); + } +} - 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); +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; - LilvNodes* fs_matches = lilv_world_find_nodes(jalv->world, s, p, fs); - LilvNodes* nrs_matches = lilv_world_find_nodes(jalv->world, s, p, nrs); + if (buffer_size < sizeof(LV2_Atom)) { + jalv_log(JALV_LOG_ERR, "UI wrote impossible atom size\n"); - lilv_nodes_free(nrs_matches); - lilv_nodes_free(fs_matches); - lilv_node_free(nrs); - lilv_node_free(fs); - lilv_node_free(p); + } else if (sizeof(LV2_Atom) + atom->size != buffer_size) { + jalv_log(JALV_LOG_ERR, "UI wrote corrupt atom size\n"); - return !fs_matches && !nrs_matches; + } 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_ui_write(void* const jalv_handle, - uint32_t port_index, - uint32_t buffer_size, - uint32_t protocol, - const void* buffer) +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 (protocol != 0 && protocol != jalv->urids.atom_eventTransfer) { - fprintf(stderr, "UI write with unsupported protocol %u (%s)\n", - protocol, unmap_uri(jalv, protocol)); - return; - } - - if (port_index >= jalv->num_ports) { - fprintf(stderr, "UI write to out of range port index %u\n", - port_index); - return; - } - - if (jalv->opts.dump && protocol == jalv->urids.atom_eventTransfer) { - const LV2_Atom* atom = (const LV2_Atom*)buffer; - char* str = sratom_to_turtle( - jalv->sratom, &jalv->unmap, "jalv:", NULL, NULL, - atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); - jalv_ansi_start(stdout, 36); - printf("\n## UI => Plugin (%u bytes) ##\n%s\n", atom->size, str); - jalv_ansi_reset(stdout); - free(str); - } - - char buf[sizeof(ControlChange) + buffer_size]; - ControlChange* ev = (ControlChange*)buf; - ev->index = port_index; - ev->protocol = protocol; - ev->size = buffer_size; - memcpy(ev->body, buffer, buffer_size); - zix_ring_write(jalv->ui_events, buf, sizeof(buf)); + 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; - const size_t space = zix_ring_read_space(jalv->ui_events); - for (size_t i = 0; i < space; i += sizeof(ev) + ev.size) { - zix_ring_read(jalv->ui_events, (char*)&ev, sizeof(ev)); - char body[ev.size]; - if (zix_ring_read(jalv->ui_events, body, ev.size) != ev.size) { - fprintf(stderr, "error: Error reading 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 = *(float*)body; - } else if (ev.protocol == jalv->urids.atom_eventTransfer) { - LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); - const LV2_Atom* const atom = (const LV2_Atom*)body; - lv2_evbuf_write(&e, nframes, 0, atom->type, atom->size, - (const uint8_t*)LV2_ATOM_BODY_CONST(atom)); - } else { - fprintf(stderr, "error: Unknown control change protocol %u\n", - ev.protocol); - } - } + 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); + } + } } -uint32_t -jalv_ui_port_index(void* const controller, const char* symbol) +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); + } + } + + if (jalv->control_in != (uint32_t)-1) { + // 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); + + const LV2_Atom* atom = lv2_atom_forge_deref(&forge, frame.ref); + jalv_send_to_plugin(jalv, + jalv->control_in, + lv2_atom_total_size(atom), + jalv->urids.atom_eventTransfer, + atom); + lv2_atom_forge_pop(&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) +{ + 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) { - Jalv* const jalv = (Jalv*)controller; - struct Port* port = jalv_port_by_symbol(jalv, symbol); + // TODO: Be more discriminate about what to send + + typedef struct { + ControlChange change; + LV2_Atom atom; + } Header; - return port ? port->index : LV2UI_INVALID_PORT_INDEX; + 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); } -void -jalv_init_ui(Jalv* jalv) +int +jalv_write_control(Jalv* const jalv, + ZixRing* const target, + const uint32_t port_index, + const float value) { - // 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); - } - } - - if (jalv->control_in != (uint32_t)-1) { - // Send patch:Get message for initial parameters/etc - LV2_Atom_Forge forge = jalv->forge; - LV2_Atom_Forge_Frame frame; - uint8_t buf[1024]; - lv2_atom_forge_set_buffer(&forge, buf, sizeof(buf)); - lv2_atom_forge_object(&forge, &frame, 0, jalv->urids.patch_Get); - - const LV2_Atom* atom = lv2_atom_forge_deref(&forge, frame.ref); - jalv_ui_write(jalv, - jalv->control_in, - lv2_atom_total_size(atom), - jalv->urids.atom_eventTransfer, - atom); - lv2_atom_forge_pop(&forge, &frame); - } + const ControlChange header = {port_index, 0, sizeof(value)}; + + return jalv_write_control_change( + jalv, target, &header, sizeof(header), &value, sizeof(value)); } -bool -jalv_send_to_ui(Jalv* jalv, - uint32_t port_index, - uint32_t type, - uint32_t size, - const void* body) +void +jalv_dump_atom(Jalv* const jalv, + FILE* const stream, + const char* const label, + const LV2_Atom* const atom, + const int color) { - /* TODO: Be more disciminate about what to send */ - char evbuf[sizeof(ControlChange) + sizeof(LV2_Atom)]; - ControlChange* ev = (ControlChange*)evbuf; - ev->index = port_index; - ev->protocol = jalv->urids.atom_eventTransfer; - ev->size = sizeof(LV2_Atom) + size; - - LV2_Atom* atom = (LV2_Atom*)ev->body; - atom->type = type; - atom->size = size; - - if (zix_ring_write_space(jalv->plugin_events) >= sizeof(evbuf) + size) { - zix_ring_write(jalv->plugin_events, evbuf, sizeof(evbuf)); - zix_ring_write(jalv->plugin_events, (const char*)body, size); - return true; - } else { - fprintf(stderr, "Plugin => UI buffer overflow!\n"); - return false; - } + 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. */ - jalv_worker_emit_responses(&jalv->state_worker, jalv->instance); - jalv_worker_emit_responses(&jalv->worker, jalv->instance); - - /* Notify the plugin the run() cycle is finished */ - if (jalv->worker.iface && jalv->worker.iface->end_run) { - jalv->worker.iface->end_run(jalv->instance->lv2_handle); - } - - /* Check if it's time to send updates to the UI */ - jalv->event_delta_t += nframes; - bool send_ui_updates = false; - float update_frames = 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; + // 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; } int jalv_update(Jalv* jalv) { - /* Check quit flag and close if set. */ - if (zix_sem_try_wait(&jalv->done)) { - jalv_close_ui(jalv); - return 0; - } - - /* Emit UI events. */ - ControlChange ev; - const size_t space = zix_ring_read_space(jalv->plugin_events); - 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_events, (char*)&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_events, (char*)buf, ev.size); - - if (jalv->opts.dump && ev.protocol == jalv->urids.atom_eventTransfer) { - /* Dump event in Turtle to the console */ - LV2_Atom* atom = (LV2_Atom*)buf; - char* str = sratom_to_turtle( - jalv->ui_sratom, &jalv->unmap, "jalv:", NULL, NULL, - atom->type, atom->size, LV2_ATOM_BODY(atom)); - jalv_ansi_start(stdout, 35); - printf("\n## Plugin => UI (%u bytes) ##\n%s\n", atom->size, str); - jalv_ansi_reset(stdout); - free(str); - } - - jalv_ui_port_event(jalv, ev.index, ev.size, ev.protocol, buf); - - if (ev.protocol == 0 && jalv->opts.print_controls) { - jalv_print_control(jalv, &jalv->ports[ev.index], *(float*)buf); - } - } - - return 1; + // Check quit flag and close if set + if (!zix_sem_try_wait(&jalv->done)) { + jalv_frontend_close(jalv); + return 0; + } + + // 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); + } + + jalv_ui_port_event(jalv, ev.index, ev.size, ev.protocol, buf); + + if (ev.protocol == 0 && jalv->opts.print_controls) { + jalv_print_control(jalv, &jalv->ports[ev.index], *(float*)buf); + } + } + + return 1; } 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) { - fprintf(stderr, "warning: Ignoring invalid value `%s'\n", s); - return false; - } - - ControlID* control = jalv_control_by_symbol(jalv, sym); - if (!control) { - fprintf(stderr, "warning: Ignoring value for unknown control `%s'\n", sym); - return false; - } - - jalv_set_control(control, sizeof(float), jalv->urids.atom_Float, &val); - printf("%s = %f\n", sym, val); - - return true; + char sym[256]; + float val = 0.0f; + if (sscanf(s, "%[^=]=%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); + if (!control) { + jalv_log( + JALV_LOG_WARNING, "Ignoring value for unknown control `%s'\n", sym); + return false; + } + + jalv_set_control(jalv, control, sizeof(float), jalv->urids.atom_Float, &val); + jalv_log(JALV_LOG_INFO, "%s = %f\n", sym, val); + + return true; } static void signal_handler(int ZIX_UNUSED(sig)) { - zix_sem_post(exit_sem); + 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; + dest->URI = URI; + dest->data = data; } static void setup_signals(Jalv* const jalv) { - exit_sem = &jalv->done; - -#ifdef HAVE_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); + 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); + // 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) { - const char* const native_ui_type_uri = jalv_native_ui_type(); - - if (jalv->opts.ui_uri) { - // Specific UI explicitly requested by user - LilvNode* uri = lilv_new_uri(jalv->world, jalv->opts.ui_uri); - const LilvUI* ui = lilv_uis_get_by_uri(jalv->uis, uri); - - lilv_node_free(uri); - return ui; - } - -#ifdef HAVE_SUIL - if (native_ui_type_uri) { - // Try to find an embeddable UI - LilvNode* native_type = lilv_new_uri(jalv->world, native_ui_type_uri); - - LILV_FOREACH (uis, u, jalv->uis) { - const LilvUI* ui = lilv_uis_get(jalv->uis, u); - const LilvNode* type = NULL; - const bool supported = lilv_ui_is_supported( - ui, suil_ui_supported, native_type, &type); - - if (supported) { - lilv_node_free(native_type); - return ui; - } - } - - lilv_node_free(native_type); - } + const char* const native_ui_type_uri = jalv_frontend_ui_type(); + + if (jalv->opts.ui_uri) { + // Specific UI explicitly requested by user + LilvNode* uri = lilv_new_uri(jalv->world, jalv->opts.ui_uri); + const LilvUI* ui = lilv_uis_get_by_uri(jalv->uis, uri); + + lilv_node_free(uri); + return ui; + } + +#if USE_SUIL + if (native_ui_type_uri) { + // Try to find an embeddable UI + LilvNode* native_type = lilv_new_uri(jalv->world, native_ui_type_uri); + + LILV_FOREACH (uis, u, jalv->uis) { + const LilvUI* ui = lilv_uis_get(jalv->uis, u); + const LilvNode* type = NULL; + const bool supported = + lilv_ui_is_supported(ui, suil_ui_supported, native_type, &type); + + if (supported) { + lilv_node_free(native_type); + return ui; + } + } + + lilv_node_free(native_type); + } #endif - if (!native_ui_type_uri && jalv->opts.show_ui) { - // Try to find a UI with ui:showInterface - LILV_FOREACH (uis, u, jalv->uis) { - const LilvUI* ui = lilv_uis_get(jalv->uis, u); - const LilvNode* ui_node = lilv_ui_get_uri(ui); + if (!native_ui_type_uri && jalv->opts.show_ui) { + // Try to find a UI with ui:showInterface + LILV_FOREACH (uis, u, jalv->uis) { + const LilvUI* ui = lilv_uis_get(jalv->uis, u); + const LilvNode* ui_node = lilv_ui_get_uri(ui); + + lilv_world_load_resource(jalv->world, ui_node); - lilv_world_load_resource(jalv->world, ui_node); + const bool supported = lilv_world_ask(jalv->world, + ui_node, + jalv->nodes.lv2_extensionData, + jalv->nodes.ui_showInterface); - const bool supported = lilv_world_ask(jalv->world, - ui_node, - jalv->nodes.lv2_extensionData, - jalv->nodes.ui_showInterface); + lilv_world_unload_resource(jalv->world, ui_node); - lilv_world_unload_resource(jalv->world, ui_node); + if (supported) { + return ui; + } + } + } - if (supported) { - return ui; - } - } - } + return NULL; +} - return NULL; +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); + + // urid:unmap + jalv->unmap.handle = jalv; + jalv->unmap.unmap = unmap_uri; + init_feature(&jalv->features.unmap_feature, LV2_URID__unmap, &jalv->unmap); + + // state:makePath + jalv->features.make_path.handle = jalv; + jalv->features.make_path.path = jalv_make_path; + init_feature(&jalv->features.make_path_feature, + LV2_STATE__makePath, + &jalv->features.make_path); + + // worker:schedule (normal) + jalv->features.sched.schedule_work = jalv_worker_schedule; + init_feature( + &jalv->features.sched_feature, LV2_WORKER__schedule, &jalv->features.sched); + + // worker:schedule (state) + jalv->features.ssched.schedule_work = jalv_worker_schedule; + init_feature(&jalv->features.state_sched_feature, + LV2_WORKER__schedule, + &jalv->features.ssched); + + // log:log + jalv->features.llog.handle = &jalv->log; + jalv->features.llog.printf = jalv_printf; + jalv->features.llog.vprintf = jalv_vprintf; + init_feature(&jalv->features.log_feature, LV2_LOG__log, &jalv->features.llog); + + // (options:options is initialized later by jalv_init_options()) + + // state:threadSafeRestore + init_feature( + &jalv->features.safe_restore_feature, LV2_STATE__threadSafeRestore, NULL); + + // ui:requestValue + jalv->features.request_value.handle = jalv; + init_feature(&jalv->features.request_value_feature, + LV2_UI__requestValue, + &jalv->features.request_value); +} + +static void +jalv_init_options(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); +} + +static void +jalv_init_display(Jalv* const jalv) +{ + if (!jalv->opts.update_rate) { + // 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); + } + + if (!jalv->opts.scale_factor) { + // 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; + } + + // 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); } int jalv_open(Jalv* const jalv, int* argc, char*** argv) { - jalv->prog_name = (*argv)[0]; - jalv->block_length = 4096; /* Should be set by backend */ - jalv->midi_buf_size = 1024; /* Should be set by backend */ - jalv->play_state = JALV_PAUSED; - jalv->bpm = 120.0f; - jalv->control_in = (uint32_t)-1; - -#ifdef HAVE_SUIL - suil_init(argc, argv, SUIL_ARG_NONE); +#if USE_SUIL + suil_init(argc, argv, SUIL_ARG_NONE); #endif - if (jalv_init(argc, argv, &jalv->opts)) { - jalv_close(jalv); - return -1; - } - - jalv->symap = symap_new(); - zix_sem_init(&jalv->symap_lock, 1); - zix_sem_init(&jalv->work_lock, 1); - - jalv->map.handle = jalv; - jalv->map.map = map_uri; - init_feature(&jalv->features.map_feature, LV2_URID__map, &jalv->map); - - jalv->worker.jalv = jalv; - jalv->state_worker.jalv = jalv; - - jalv->unmap.handle = jalv; - jalv->unmap.unmap = unmap_uri; - init_feature(&jalv->features.unmap_feature, LV2_URID__unmap, &jalv->unmap); - - lv2_atom_forge_init(&jalv->forge, &jalv->map); - - jalv->env = serd_env_new(NULL); - serd_env_set_prefix_from_strings( - jalv->env, (const uint8_t*)"patch", (const uint8_t*)LV2_PATCH_PREFIX); - serd_env_set_prefix_from_strings( - jalv->env, (const uint8_t*)"time", (const uint8_t*)LV2_TIME_PREFIX); - serd_env_set_prefix_from_strings( - jalv->env, (const uint8_t*)"xsd", (const uint8_t*)NS_XSD); - - jalv->sratom = sratom_new(&jalv->map); - jalv->ui_sratom = sratom_new(&jalv->map); - sratom_set_env(jalv->sratom, jalv->env); - sratom_set_env(jalv->ui_sratom, jalv->env); - - jalv->urids.atom_Float = symap_map(jalv->symap, LV2_ATOM__Float); - jalv->urids.atom_Int = symap_map(jalv->symap, LV2_ATOM__Int); - jalv->urids.atom_Object = symap_map(jalv->symap, LV2_ATOM__Object); - jalv->urids.atom_Path = symap_map(jalv->symap, LV2_ATOM__Path); - jalv->urids.atom_String = symap_map(jalv->symap, LV2_ATOM__String); - jalv->urids.atom_eventTransfer = symap_map(jalv->symap, LV2_ATOM__eventTransfer); - jalv->urids.bufsz_maxBlockLength = symap_map(jalv->symap, LV2_BUF_SIZE__maxBlockLength); - jalv->urids.bufsz_minBlockLength = symap_map(jalv->symap, LV2_BUF_SIZE__minBlockLength); - jalv->urids.bufsz_sequenceSize = symap_map(jalv->symap, LV2_BUF_SIZE__sequenceSize); - jalv->urids.log_Error = symap_map(jalv->symap, LV2_LOG__Error); - jalv->urids.log_Trace = symap_map(jalv->symap, LV2_LOG__Trace); - jalv->urids.log_Warning = symap_map(jalv->symap, LV2_LOG__Warning); - jalv->urids.midi_MidiEvent = symap_map(jalv->symap, LV2_MIDI__MidiEvent); - jalv->urids.param_sampleRate = symap_map(jalv->symap, LV2_PARAMETERS__sampleRate); - jalv->urids.patch_Get = symap_map(jalv->symap, LV2_PATCH__Get); - jalv->urids.patch_Put = symap_map(jalv->symap, LV2_PATCH__Put); - jalv->urids.patch_Set = symap_map(jalv->symap, LV2_PATCH__Set); - jalv->urids.patch_body = symap_map(jalv->symap, LV2_PATCH__body); - jalv->urids.patch_property = symap_map(jalv->symap, LV2_PATCH__property); - jalv->urids.patch_value = symap_map(jalv->symap, LV2_PATCH__value); - jalv->urids.time_Position = symap_map(jalv->symap, LV2_TIME__Position); - jalv->urids.time_bar = symap_map(jalv->symap, LV2_TIME__bar); - jalv->urids.time_barBeat = symap_map(jalv->symap, LV2_TIME__barBeat); - jalv->urids.time_beatUnit = symap_map(jalv->symap, LV2_TIME__beatUnit); - jalv->urids.time_beatsPerBar = symap_map(jalv->symap, LV2_TIME__beatsPerBar); - jalv->urids.time_beatsPerMinute = symap_map(jalv->symap, LV2_TIME__beatsPerMinute); - jalv->urids.time_frame = symap_map(jalv->symap, LV2_TIME__frame); - jalv->urids.time_speed = symap_map(jalv->symap, LV2_TIME__speed); - jalv->urids.ui_updateRate = symap_map(jalv->symap, LV2_UI__updateRate); - + // Parse command-line arguments + int ret = 0; + if ((ret = jalv_frontend_init(argc, argv, &jalv->opts))) { + jalv_close(jalv); + return ret; + } + + // 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); + 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_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); + + // Create temporary directory for plugin state #ifdef _WIN32 - jalv->temp_dir = jalv_strdup("jalvXXXXXX"); - _mktemp(jalv->temp_dir); + 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); + 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]); + } + + if (!plugin_uri) { + plugin_uri = jalv_frontend_select_plugin(jalv); + } + + if (!plugin_uri) { + jalv_log(JALV_LOG_ERR, "Missing plugin URI, try lv2ls to list plugins\n"); + jalv_close(jalv); + return -3; + } + + // Find plugin + const char* const plugin_uri_str = lilv_node_as_string(plugin_uri); + const LilvPlugins* const plugins = lilv_world_get_all_plugins(world); + jalv_log(JALV_LOG_INFO, "Plugin: %s\n", plugin_uri_str); + jalv->plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + lilv_node_free(plugin_uri); + if (!jalv->plugin) { + jalv_log(JALV_LOG_ERR, "Failed to find plugin\n"); + jalv_close(jalv); + return -4; + } + + // 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; + if (jalv->safe_restore) { + jalv->state_worker = jalv_worker_new(&jalv->work_lock, false); + jalv->features.ssched.handle = jalv->state_worker; + } + } + + // Load preset, if specified + if (jalv->opts.preset) { + 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); + 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); + + 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)); + } + + // Get a plugin UI + jalv->uis = lilv_plugin_get_uis(jalv->plugin); + if (!jalv->opts.generic_ui) { + if ((jalv->ui = jalv_select_custom_ui(jalv))) { +#if USE_SUIL + const char* host_type_uri = jalv_frontend_ui_type(); + if (host_type_uri) { + LilvNode* host_type = lilv_new_uri(jalv->world, host_type_uri); + + if (!lilv_ui_is_supported( + jalv->ui, suil_ui_supported, host_type, &jalv->ui_type)) { + jalv->ui = NULL; + } + + lilv_node_free(host_type); + } #endif - jalv->features.make_path.handle = jalv; - jalv->features.make_path.path = jalv_make_path; - init_feature(&jalv->features.make_path_feature, - LV2_STATE__makePath, &jalv->features.make_path); - - jalv->features.sched.handle = &jalv->worker; - jalv->features.sched.schedule_work = jalv_worker_schedule; - init_feature(&jalv->features.sched_feature, - LV2_WORKER__schedule, &jalv->features.sched); - - jalv->features.ssched.handle = &jalv->state_worker; - jalv->features.ssched.schedule_work = jalv_worker_schedule; - init_feature(&jalv->features.state_sched_feature, - LV2_WORKER__schedule, &jalv->features.ssched); - - jalv->features.llog.handle = jalv; - jalv->features.llog.printf = jalv_printf; - jalv->features.llog.vprintf = jalv_vprintf; - init_feature(&jalv->features.log_feature, - LV2_LOG__log, &jalv->features.llog); - - jalv->features.request_value.handle = jalv; - init_feature(&jalv->features.request_value_feature, - LV2_UI__requestValue, &jalv->features.request_value); - - zix_sem_init(&jalv->done, 0); - - zix_sem_init(&jalv->paused, 0); - zix_sem_init(&jalv->worker.sem, 0); - - /* Find all installed plugins */ - LilvWorld* world = lilv_world_new(); - lilv_world_load_all(world); - jalv->world = world; - const LilvPlugins* plugins = lilv_world_get_all_plugins(world); - - /* Cache URIs for concepts we'll use */ - jalv->nodes.atom_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort); - jalv->nodes.atom_Chunk = lilv_new_uri(world, LV2_ATOM__Chunk); - jalv->nodes.atom_Float = lilv_new_uri(world, LV2_ATOM__Float); - jalv->nodes.atom_Path = lilv_new_uri(world, LV2_ATOM__Path); - jalv->nodes.atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence); - jalv->nodes.lv2_AudioPort = lilv_new_uri(world, LV2_CORE__AudioPort); - jalv->nodes.lv2_CVPort = lilv_new_uri(world, LV2_CORE__CVPort); - jalv->nodes.lv2_ControlPort = lilv_new_uri(world, LV2_CORE__ControlPort); - jalv->nodes.lv2_InputPort = lilv_new_uri(world, LV2_CORE__InputPort); - jalv->nodes.lv2_OutputPort = lilv_new_uri(world, LV2_CORE__OutputPort); - jalv->nodes.lv2_connectionOptional = lilv_new_uri(world, LV2_CORE__connectionOptional); - jalv->nodes.lv2_control = lilv_new_uri(world, LV2_CORE__control); - jalv->nodes.lv2_default = lilv_new_uri(world, LV2_CORE__default); - jalv->nodes.lv2_enumeration = lilv_new_uri(world, LV2_CORE__enumeration); - jalv->nodes.lv2_extensionData = lilv_new_uri(world, LV2_CORE__extensionData); - jalv->nodes.lv2_integer = lilv_new_uri(world, LV2_CORE__integer); - jalv->nodes.lv2_maximum = lilv_new_uri(world, LV2_CORE__maximum); - jalv->nodes.lv2_minimum = lilv_new_uri(world, LV2_CORE__minimum); - jalv->nodes.lv2_name = lilv_new_uri(world, LV2_CORE__name); - jalv->nodes.lv2_reportsLatency = lilv_new_uri(world, LV2_CORE__reportsLatency); - jalv->nodes.lv2_sampleRate = lilv_new_uri(world, LV2_CORE__sampleRate); - jalv->nodes.lv2_symbol = lilv_new_uri(world, LV2_CORE__symbol); - jalv->nodes.lv2_toggled = lilv_new_uri(world, LV2_CORE__toggled); - jalv->nodes.midi_MidiEvent = lilv_new_uri(world, LV2_MIDI__MidiEvent); - jalv->nodes.pg_group = lilv_new_uri(world, LV2_PORT_GROUPS__group); - jalv->nodes.pprops_logarithmic = lilv_new_uri(world, LV2_PORT_PROPS__logarithmic); - jalv->nodes.pprops_notOnGUI = lilv_new_uri(world, LV2_PORT_PROPS__notOnGUI); - jalv->nodes.pprops_rangeSteps = lilv_new_uri(world, LV2_PORT_PROPS__rangeSteps); - jalv->nodes.pset_Preset = lilv_new_uri(world, LV2_PRESETS__Preset); - jalv->nodes.pset_bank = lilv_new_uri(world, LV2_PRESETS__bank); - jalv->nodes.rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); - jalv->nodes.rdfs_label = lilv_new_uri(world, LILV_NS_RDFS "label"); - jalv->nodes.rdfs_range = lilv_new_uri(world, LILV_NS_RDFS "range"); - jalv->nodes.rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize); - jalv->nodes.ui_showInterface = lilv_new_uri(world, LV2_UI__showInterface); - jalv->nodes.work_interface = lilv_new_uri(world, LV2_WORKER__interface); - jalv->nodes.work_schedule = lilv_new_uri(world, LV2_WORKER__schedule); - jalv->nodes.end = NULL; - - /* 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 (S_ISDIR(info.st_mode)) { - 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) { - fprintf(stderr, "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]); - } - - if (!plugin_uri) { - fprintf(stderr, "Missing plugin URI, try lv2ls to list plugins\n"); - jalv_close(jalv); - return -3; - } - - /* Find plugin */ - printf("Plugin: %s\n", lilv_node_as_string(plugin_uri)); - jalv->plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); - lilv_node_free(plugin_uri); - if (!jalv->plugin) { - fprintf(stderr, "Failed to find plugin\n"); - jalv_close(jalv); - return -4; - } - - /* Load preset, if specified */ - if (jalv->opts.preset) { - 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); - jalv->preset = state; - lilv_node_free(preset); - if (!state) { - fprintf(stderr, "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); - - 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)); - } - - /* Get a plugin UI */ - jalv->uis = lilv_plugin_get_uis(jalv->plugin); - if (!jalv->opts.generic_ui) { - if ((jalv->ui = jalv_select_custom_ui(jalv))) { - const char* host_type_uri = jalv_native_ui_type(); - if (host_type_uri) { - LilvNode* host_type = lilv_new_uri(jalv->world, host_type_uri); - - if (!lilv_ui_is_supported(jalv->ui, - suil_ui_supported, - host_type, - &jalv->ui_type)) { - jalv->ui = NULL; - } - - lilv_node_free(host_type); - } - } - } - - /* Create ringbuffers for UI if necessary */ - if (jalv->ui) { - fprintf(stderr, "UI: %s\n", - lilv_node_as_uri(lilv_ui_get_uri(jalv->ui))); - } else { - fprintf(stderr, "UI: None\n"); - } - - /* Create port and control structures */ - jalv_create_ports(jalv); - jalv_create_controls(jalv, true); - jalv_create_controls(jalv, false); - - if (!(jalv->backend = jalv_backend_init(jalv))) { - fprintf(stderr, "Failed to connect to audio system\n"); - jalv_close(jalv); - return -6; - } - - printf("Sample rate: %u Hz\n", (uint32_t)jalv->sample_rate); - printf("Block length: %u frames\n", jalv->block_length); - printf("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; - } - - if (jalv->opts.update_rate == 0.0) { - /* Calculate a reasonable UI update frequency. */ - jalv->ui_update_hz = jalv_ui_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); - } - - /* 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); - fprintf(stderr, "Comm buffers: %u bytes\n", jalv->opts.buffer_size); - fprintf(stderr, "Update rate: %.01f Hz\n", jalv->ui_update_hz); - - /* Build options array to pass to plugin */ - 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, 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); - - init_feature(&jalv->features.safe_restore_feature, - LV2_STATE__threadSafeRestore, - NULL); - - /* Create Plugin <=> UI communication buffers */ - jalv->ui_events = zix_ring_new(jalv->opts.buffer_size); - jalv->plugin_events = zix_ring_new(jalv->opts.buffer_size); - zix_ring_mlock(jalv->ui_events); - zix_ring_mlock(jalv->plugin_events); - - /* Build feature list for passing to plugins */ - const LV2_Feature* const features[] = { - &jalv->features.map_feature, - &jalv->features.unmap_feature, - &jalv->features.sched_feature, - &jalv->features.log_feature, - &jalv->features.options_feature, - &static_features[0], - &static_features[1], - &static_features[2], - &static_features[3], - NULL - }; - jalv->feature_list = calloc(1, sizeof(features)); - if (!jalv->feature_list) { - fprintf(stderr, "Failed to allocate feature list\n"); - jalv_close(jalv); - return -7; - } - memcpy(jalv->feature_list, features, sizeof(features)); - - /* Check that any required features are supported */ - LilvNodes* req_feats = lilv_plugin_get_required_features(jalv->plugin); - LILV_FOREACH(nodes, f, req_feats) { - const char* uri = lilv_node_as_uri(lilv_nodes_get(req_feats, f)); - if (!feature_is_supported(jalv, uri)) { - fprintf(stderr, "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) { - fprintf(stderr, "Failed to instantiate plugin.\n"); - jalv_close(jalv); - return -9; - } - - jalv->features.ext_data.data_access = - lilv_instance_get_descriptor(jalv->instance)->extension_data; - - fprintf(stderr, "\n"); - if (!jalv->buf_size_set) { - jalv_allocate_port_buffers(jalv); - } - - /* Create workers if necessary */ - if (lilv_plugin_has_extension_data(jalv->plugin, jalv->nodes.work_interface)) { - const LV2_Worker_Interface* iface = (const LV2_Worker_Interface*) - lilv_instance_get_extension_data(jalv->instance, LV2_WORKER__interface); - - jalv_worker_init(jalv, &jalv->worker, iface, true); - if (jalv->safe_restore) { - jalv_worker_init(jalv, &jalv->state_worker, iface, false); - } - } - - /* Apply loaded state to plugin instance if necessary */ - if (state) { - jalv_apply_state(jalv, state); - } - - if (jalv->opts.controls) { - for (char** c = jalv->opts.controls; *c; ++c) { - jalv_apply_control_arg(jalv, *c); - } - } - - /* 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); - } - - /* 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); - } - } - - /* Activate plugin */ - lilv_instance_activate(jalv->instance); - - /* Discover UI */ - jalv->has_ui = jalv_discover_ui(jalv); - - /* Activate Jack */ - jalv_backend_activate(jalv); - jalv->play_state = JALV_RUNNING; - - return 0; + if (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); + jalv_create_controls(jalv, true); + jalv_create_controls(jalv, false); + + if (!(jalv->backend = jalv_backend_init(jalv))) { + 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_init_display(jalv); + jalv_init_options(jalv); + + // 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); + + // Build feature list for passing to plugins + const LV2_Feature* const features[] = {&jalv->features.map_feature, + &jalv->features.unmap_feature, + &jalv->features.sched_feature, + &jalv->features.log_feature, + &jalv->features.options_feature, + &static_features[0], + &static_features[1], + &static_features[2], + &static_features[3], + NULL}; + + 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)); + + // Check that any required features are supported + LilvNodes* req_feats = lilv_plugin_get_required_features(jalv->plugin); + LILV_FOREACH (nodes, f, req_feats) { + 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) { + jalv_log(JALV_LOG_ERR, "Failed to instantiate plugin\n"); + jalv_close(jalv); + return -9; + } + + // Point things to the instance that require it + + jalv->features.ext_data.data_access = + lilv_instance_get_descriptor(jalv->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); + + jalv_log(JALV_LOG_INFO, "\n"); + if (!jalv->buf_size_set) { + jalv_allocate_port_buffers(jalv); + } + + // Apply loaded state to plugin instance if necessary + if (state) { + jalv_apply_state(jalv, state); + lilv_state_free(state); + } + + // Apply initial controls from command-line arguments + if (jalv->opts.controls) { + for (char** c = jalv->opts.controls; *c; ++c) { + jalv_apply_control_arg(jalv, *c); + } + } + + // 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); + } + + // 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); + } + } + + // Activate plugin + lilv_instance_activate(jalv->instance); + + // Discover UI + jalv->has_ui = jalv_frontend_discover(jalv); + + // Activate audio backend + jalv_backend_activate(jalv); + jalv->play_state = JALV_RUNNING; + + return 0; } int jalv_close(Jalv* const jalv) { - jalv->exit = true; - - fprintf(stderr, "Exiting...\n"); - - /* Terminate the worker */ - jalv_worker_finish(&jalv->worker); - - /* Deactivate audio */ - jalv_backend_deactivate(jalv); - for (uint32_t i = 0; i < jalv->num_ports; ++i) { - if (jalv->ports[i].evbuf) { - lv2_evbuf_free(jalv->ports[i].evbuf); - } - } - jalv_backend_close(jalv); - - /* Destroy the worker */ - jalv_worker_destroy(&jalv->worker); - - /* Deactivate plugin */ -#ifdef HAVE_SUIL - suil_instance_free(jalv->ui_instance); + // Terminate the worker + jalv_worker_exit(jalv->worker); + + // Deactivate audio + if (jalv->backend) { + jalv_backend_deactivate(jalv); + jalv_backend_close(jalv); + } + + // 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 +#if USE_SUIL + suil_instance_free(jalv->ui_instance); #endif - if (jalv->instance) { - lilv_instance_deactivate(jalv->instance); - lilv_instance_free(jalv->instance); - } - - /* Clean up */ - free(jalv->ports); - zix_ring_free(jalv->ui_events); - zix_ring_free(jalv->plugin_events); - for (LilvNode** n = (LilvNode**)&jalv->nodes; *n; ++n) { - lilv_node_free(*n); - } - symap_free(jalv->symap); - zix_sem_destroy(&jalv->symap_lock); -#ifdef HAVE_SUIL - suil_host_free(jalv->ui_host); + if (jalv->instance) { + lilv_instance_deactivate(jalv->instance); + lilv_instance_free(jalv->instance); + } + + // Clean up + 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); +#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(jalv->controls.controls); - - if (jalv->sratom) { - sratom_free(jalv->sratom); - } - if (jalv->ui_sratom) { - sratom_free(jalv->ui_sratom); - } - lilv_uis_free(jalv->uis); - lilv_world_free(jalv->world); - - zix_sem_destroy(&jalv->done); - - remove(jalv->temp_dir); - free(jalv->temp_dir); - free(jalv->ui_event_buf); - free(jalv->feature_list); - - free(jalv->opts.name); - free(jalv->opts.load); - free(jalv->opts.controls); - - return 0; + 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(jalv->controls.controls); + + sratom_free(jalv->sratom); + sratom_free(jalv->ui_sratom); + serd_env_free(jalv->env); + lilv_uis_free(jalv->uis); + lilv_world_free(jalv->world); + + zix_sem_destroy(&jalv->done); + + remove(jalv->temp_dir); + free(jalv->temp_dir); + free(jalv->ui_event_buf); + free(jalv->feature_list); + + free(jalv->opts.name); + free(jalv->opts.load); + free(jalv->opts.controls); + + return 0; } int main(int argc, char** argv) { - Jalv jalv; - memset(&jalv, '\0', sizeof(Jalv)); + Jalv jalv; + memset(&jalv, '\0', sizeof(Jalv)); - if (jalv_open(&jalv, &argc, &argv)) { - return EXIT_FAILURE; - } + if (jalv_open(&jalv, &argc, &argv)) { + return EXIT_FAILURE; + } - /* Set up signal handlers */ - setup_signals(&jalv); + // Set up signal handlers + setup_signals(&jalv); - /* Run UI (or prompt at console) */ - jalv_open_ui(&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); + // Wait for finish signal from UI or signal handler + zix_sem_wait(&jalv.done); - return jalv_close(&jalv); + return jalv_close(&jalv); } |