From 86e57efa909ad4e475186aaf7ce8623eee9ee0aa Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 5 Oct 2016 00:32:44 -0400 Subject: Factor out Jack backend --- src/jack.c | 539 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/jalv.c | 536 +++------------------------------------------------ src/jalv_internal.h | 57 ++++-- src/state.c | 2 +- src/worker.c | 32 ++-- src/zix/ring.c | 222 ++++++++++++++++++++++ src/zix/ring.h | 130 +++++++++++++ wscript | 21 +- 8 files changed, 988 insertions(+), 551 deletions(-) create mode 100644 src/jack.c create mode 100644 src/zix/ring.c create mode 100644 src/zix/ring.h diff --git a/src/jack.c b/src/jack.c new file mode 100644 index 0000000..6c37fa4 --- /dev/null +++ b/src/jack.c @@ -0,0 +1,539 @@ +/* + Copyright 2007-2016 David Robillard + + 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. +*/ + +#include +#include +#ifdef JALV_JACK_SESSION +# include +#endif +#ifdef HAVE_JACK_METADATA +# include +#endif + +#include "jalv_internal.h" +#include "worker.h" + +struct JalvBackend { + jack_client_t* client; ///< Jack client +}; + +/** Jack buffer size callback. */ +static int +jack_buffer_size_cb(jack_nframes_t nframes, void* data) +{ + Jalv* const jalv = (Jalv*)data; + jalv->block_length = nframes; + jalv->buf_size_set = true; +#ifdef HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE + jalv->midi_buf_size = jack_port_type_get_buffer_size( + jalv->backend->client, JACK_DEFAULT_MIDI_TYPE); +#endif + jalv_allocate_port_buffers(jalv); + return 0; +} + +/** Jack shutdown callback. */ +static void +jack_shutdown_cb(void* data) +{ + Jalv* const jalv = (Jalv*)data; + jalv_close_ui(jalv); + zix_sem_post(jalv->done); +} + +/** Jack process callback. */ +static REALTIME int +jack_process_cb(jack_nframes_t nframes, void* data) +{ + Jalv* const jalv = (Jalv*)data; + jack_client_t* client = jalv->backend->client; + + /* Get Jack transport position */ + jack_position_t pos; + const bool rolling = (jack_transport_query(client, &pos) + == JackTransportRolling); + + /* If transport state is not as expected, then something has changed */ + const bool xport_changed = (rolling != jalv->rolling || + pos.frame != jalv->position || + pos.beats_per_minute != jalv->bpm); + + uint8_t pos_buf[256]; + LV2_Atom* lv2_pos = (LV2_Atom*)pos_buf; + if (xport_changed) { + /* Build an LV2 position object to report change to plugin */ + lv2_atom_forge_set_buffer(&jalv->forge, pos_buf, sizeof(pos_buf)); + LV2_Atom_Forge* forge = &jalv->forge; + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(forge, &frame, 0, jalv->urids.time_Position); + lv2_atom_forge_key(forge, jalv->urids.time_frame); + lv2_atom_forge_long(forge, pos.frame); + lv2_atom_forge_key(forge, jalv->urids.time_speed); + lv2_atom_forge_float(forge, rolling ? 1.0 : 0.0); + if (pos.valid & JackPositionBBT) { + lv2_atom_forge_key(forge, jalv->urids.time_barBeat); + lv2_atom_forge_float( + forge, pos.beat - 1 + (pos.tick / pos.ticks_per_beat)); + lv2_atom_forge_key(forge, jalv->urids.time_bar); + lv2_atom_forge_long(forge, pos.bar - 1); + lv2_atom_forge_key(forge, jalv->urids.time_beatUnit); + lv2_atom_forge_int(forge, pos.beat_type); + lv2_atom_forge_key(forge, jalv->urids.time_beatsPerBar); + lv2_atom_forge_float(forge, pos.beats_per_bar); + lv2_atom_forge_key(forge, jalv->urids.time_beatsPerMinute); + lv2_atom_forge_float(forge, pos.beats_per_minute); + } + + if (jalv->opts.dump) { + char* str = sratom_to_turtle( + jalv->sratom, &jalv->unmap, "time:", NULL, NULL, + lv2_pos->type, lv2_pos->size, LV2_ATOM_BODY(lv2_pos)); + jalv_ansi_start(stdout, 36); + printf("\n## Position ##\n%s\n", str); + jalv_ansi_reset(stdout); + free(str); + } + } + + /* Update transport state to expected values for next cycle */ + jalv->position = rolling ? pos.frame + nframes : pos.frame; + jalv->bpm = pos.beats_per_minute; + jalv->rolling = rolling; + + switch (jalv->play_state) { + case JALV_PAUSE_REQUESTED: + jalv->play_state = JALV_PAUSED; + zix_sem_post(&jalv->paused); + break; + case JALV_PAUSED: + for (uint32_t p = 0; p < jalv->num_ports; ++p) { + jack_port_t* jport = jalv->ports[p].sys_port; + if (jport && jalv->ports[p].flow == FLOW_OUTPUT) { + void* buf = jack_port_get_buffer(jport, nframes); + if (jalv->ports[p].type == TYPE_EVENT) { + jack_midi_clear_buffer(buf); + } else { + memset(buf, '\0', nframes * sizeof(float)); + } + } + } + return 0; + default: + break; + } + + /* Prepare port buffers */ + for (uint32_t p = 0; p < jalv->num_ports; ++p) { + struct Port* port = &jalv->ports[p]; + if (port->type == TYPE_AUDIO && port->sys_port) { + /* Connect plugin port directly to Jack port buffer */ + lilv_instance_connect_port( + jalv->instance, p, + jack_port_get_buffer(port->sys_port, nframes)); +#ifdef HAVE_JACK_METADATA + } else if (port->type == TYPE_CV && port->sys_port) { + /* Connect plugin port directly to Jack port buffer */ + lilv_instance_connect_port( + jalv->instance, p, + jack_port_get_buffer(port->sys_port, nframes)); +#endif + } else if (port->type == TYPE_EVENT && port->flow == FLOW_INPUT) { + lv2_evbuf_reset(port->evbuf, true); + + /* Write transport change event if applicable */ + LV2_Evbuf_Iterator iter = lv2_evbuf_begin(port->evbuf); + if (xport_changed) { + lv2_evbuf_write(&iter, 0, 0, + lv2_pos->type, lv2_pos->size, + (const uint8_t*)LV2_ATOM_BODY(lv2_pos)); + } + + if (jalv->request_update) { + /* Plugin state has changed, request an update */ + const LV2_Atom_Object get = { + { sizeof(LV2_Atom_Object_Body), jalv->urids.atom_Object }, + { 0, jalv->urids.patch_Get } }; + lv2_evbuf_write(&iter, 0, 0, + get.atom.type, get.atom.size, + (const uint8_t*)LV2_ATOM_BODY(&get)); + } + + if (port->sys_port) { + /* Write Jack MIDI input */ + void* buf = jack_port_get_buffer(port->sys_port, nframes); + for (uint32_t i = 0; i < jack_midi_get_event_count(buf); ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, buf, i); + lv2_evbuf_write(&iter, + ev.time, 0, + jalv->midi_event_id, + ev.size, ev.buffer); + } + } + } else if (port->type == TYPE_EVENT) { + /* Clear event output for plugin to write to */ + lv2_evbuf_reset(port->evbuf, false); + } + } + jalv->request_update = false; + + /* Read and apply control change events from UI */ + if (jalv->has_ui) { + 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 %d\n", + ev.protocol); + } + } + } + + /* 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; + jack_nframes_t 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; + } + + /* Deliver MIDI output and UI events */ + for (uint32_t p = 0; p < jalv->num_ports; ++p) { + struct Port* const port = &jalv->ports[p]; + if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && + lilv_port_has_property(jalv->plugin, port->lilv_port, + jalv->nodes.lv2_reportsLatency)) { + if (jalv->plugin_latency != port->control) { + jalv->plugin_latency = port->control; + jack_recompute_total_latencies(client); + } + } + + if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { + void* buf = NULL; + if (port->sys_port) { + buf = jack_port_get_buffer(port->sys_port, nframes); + jack_midi_clear_buffer(buf); + } + + for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(port->evbuf); + lv2_evbuf_is_valid(i); + i = lv2_evbuf_next(i)) { + uint32_t frames, subframes, type, size; + uint8_t* body; + lv2_evbuf_get(i, &frames, &subframes, &type, &size, &body); + if (buf && type == jalv->midi_event_id) { + jack_midi_event_write(buf, frames, body, size); + } + + /* TODO: Be more disciminate about what to send */ + if (jalv->has_ui && !port->old_api) { + char evbuf[sizeof(ControlChange) + sizeof(LV2_Atom)]; + ControlChange* ev = (ControlChange*)evbuf; + ev->index = p; + 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) { + fprintf(stderr, "Plugin => UI buffer overflow!\n"); + break; + } + zix_ring_write(jalv->plugin_events, evbuf, sizeof(evbuf)); + /* TODO: race, ensure reader handles this correctly */ + zix_ring_write(jalv->plugin_events, (const char*)body, size); + } + } + } else if (send_ui_updates + && port->flow != FLOW_INPUT + && port->type == TYPE_CONTROL) { + char buf[sizeof(ControlChange) + sizeof(float)]; + ControlChange* ev = (ControlChange*)buf; + ev->index = p; + ev->protocol = 0; + ev->size = sizeof(float); + *(float*)ev->body = port->control; + if (zix_ring_write(jalv->plugin_events, buf, sizeof(buf)) + < sizeof(buf)) { + fprintf(stderr, "Plugin => UI buffer overflow!\n"); + } + } + } + + return 0; +} + +/** Calculate latency assuming all ports depend on each other. */ +static void +jack_latency_cb(jack_latency_callback_mode_t mode, void* data) +{ + Jalv* const jalv = (Jalv*)data; + const enum PortFlow flow = ((mode == JackCaptureLatency) + ? FLOW_INPUT : FLOW_OUTPUT); + + /* First calculate the min/max latency of all feeding ports */ + uint32_t ports_found = 0; + jack_latency_range_t range = { UINT32_MAX, 0 }; + for (uint32_t p = 0; p < jalv->num_ports; ++p) { + struct Port* port = &jalv->ports[p]; + if (port->sys_port && port->flow == flow) { + jack_latency_range_t r; + jack_port_get_latency_range(port->sys_port, mode, &r); + if (r.min < range.min) { range.min = r.min; } + if (r.max > range.max) { range.max = r.max; } + ++ports_found; + } + } + + if (ports_found == 0) { + range.min = 0; + } + + /* Add the plugin's own latency */ + range.min += jalv->plugin_latency; + range.max += jalv->plugin_latency; + + /* Tell Jack about it */ + for (uint32_t p = 0; p < jalv->num_ports; ++p) { + struct Port* port = &jalv->ports[p]; + if (port->sys_port && port->flow == flow) { + jack_port_set_latency_range(port->sys_port, mode, &range); + } + } +} + +#ifdef JALV_JACK_SESSION +static void +jack_session_cb(jack_session_event_t* event, void* arg) +{ + Jalv* const jalv = (Jalv*)arg; + + #define MAX_CMD_LEN 256 + event->command_line = (char*)malloc(MAX_CMD_LEN); + snprintf(event->command_line, MAX_CMD_LEN, "%s -u %s -l \"${SESSION_DIR}\"", + jalv->prog_name, + event->client_uuid); + + switch (event->type) { + case JackSessionSave: + case JackSessionSaveTemplate: + jalv_save(jalv, event->session_dir); + break; + case JackSessionSaveAndQuit: + jalv_save(jalv, event->session_dir); + jalv_close_ui(jalv); + break; + } + + jack_session_reply(jalv->backend->client, event); + jack_session_event_free(event); +} +#endif /* JALV_JACK_SESSION */ + +JalvBackend* +jalv_backend_init(Jalv* jalv) +{ + jack_client_t* client = NULL; + + /* Determine the name of the JACK client */ + char* jack_name = NULL; + if (jalv->opts.name) { + /* Name given on command line */ + jack_name = jalv_strdup(jalv->opts.name); + } else { + /* Use plugin name */ + LilvNode* name = lilv_plugin_get_name(jalv->plugin); + jack_name = jalv_strdup(lilv_node_as_string(name)); + lilv_node_free(name); + } + + /* Truncate client name to suit JACK if necessary */ + if (strlen(jack_name) >= (unsigned)jack_client_name_size() - 1) { + jack_name[jack_client_name_size() - 1] = '\0'; + } + printf("JACK Name: %s\n", jack_name); + + /* Connect to JACK */ +#ifdef JALV_JACK_SESSION + if (jalv->opts.uuid) { + client = jack_client_open( + jack_name, + (jack_options_t)(JackSessionID | + (jalv->opts.name_exact ? JackUseExactName : 0)), + NULL, + jalv->opts.uuid); + } +#endif + + if (!client) { + client = jack_client_open( + jack_name, + (jalv->opts.name_exact ? JackUseExactName : JackNullOption), + NULL); + } + + free(jack_name); + if (!client) { + return NULL; + } + + /* Set audio engine properties */ + jalv->sample_rate = jack_get_sample_rate(client); + jalv->block_length = jack_get_buffer_size(client); + jalv->midi_buf_size = 4096; +#ifdef HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE + jalv->midi_buf_size = jack_port_type_get_buffer_size( + client, JACK_DEFAULT_MIDI_TYPE); +#endif + + /* Set JACK callbacks */ + void* const arg = (void*)jalv; + jack_set_process_callback(client, &jack_process_cb, arg); + jack_set_buffer_size_callback(client, &jack_buffer_size_cb, arg); + jack_on_shutdown(client, &jack_shutdown_cb, arg); + jack_set_latency_callback(client, &jack_latency_cb, arg); +#ifdef JALV_JACK_SESSION + jack_set_session_callback(client, &jack_session_cb, arg); +#endif + + /* Allocate and return opaque backend */ + JalvBackend* backend = (JalvBackend*)calloc(1, sizeof(JalvBackend)); + backend->client = client; + return backend; +} + +void +jalv_backend_close(Jalv* jalv) +{ + jack_client_close(jalv->backend->client); + free(jalv->backend); + jalv->backend = NULL; +} + +void +jalv_backend_activate(Jalv* jalv) +{ + jack_activate(jalv->backend->client); +} + +void +jalv_backend_deactivate(Jalv* jalv) +{ + jack_activate(jalv->backend->client); +} + +void +jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) +{ + jack_client_t* client = jalv->backend->client; + struct Port* const port = &jalv->ports[port_index]; + + const LilvNode* sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port); + + /* Connect unsupported ports to NULL (known to be optional by this point) */ + if (port->flow == FLOW_UNKNOWN || port->type == TYPE_UNKNOWN) { + lilv_instance_connect_port(jalv->instance, port_index, NULL); + return; + } + + /* Build Jack flags for port */ + enum JackPortFlags jack_flags = (port->flow == FLOW_INPUT) + ? JackPortIsInput + : JackPortIsOutput; + + /* Connect the port based on its type */ + switch (port->type) { + case TYPE_CONTROL: + lilv_instance_connect_port(jalv->instance, port_index, &port->control); + break; + case TYPE_AUDIO: + port->sys_port = jack_port_register( + client, lilv_node_as_string(sym), + JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); + break; +#ifdef HAVE_JACK_METADATA + case TYPE_CV: + port->sys_port = jack_port_register( + client, lilv_node_as_string(sym), + JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); + if (port->sys_port) { + jack_set_property(client, jack_port_uuid(port->sys_port), + "http://jackaudio.org/metadata/signal-type", "CV", + "text/plain"); + } + break; +#endif + case TYPE_EVENT: + if (lilv_port_supports_event( + jalv->plugin, port->lilv_port, jalv->nodes.midi_MidiEvent)) { + port->sys_port = jack_port_register( + client, lilv_node_as_string(sym), + JACK_DEFAULT_MIDI_TYPE, jack_flags, 0); + } + break; + default: + break; + } + +#ifdef HAVE_JACK_METADATA + if (port->sys_port) { + // Set port order to index + char index_str[16]; + snprintf(index_str, sizeof(index_str), "%d", port_index); + jack_set_property(client, jack_port_uuid(port->sys_port), + "http://jackaudio.org/metadata/order", index_str, + "http://www.w3.org/2001/XMLSchema#integer"); + + // Set port pretty name to label + LilvNode* name = lilv_port_get_name(jalv->plugin, port->lilv_port); + jack_set_property(client, jack_port_uuid(port->sys_port), + JACK_METADATA_PRETTY_NAME, lilv_node_as_string(name), + "text/plain"); + lilv_node_free(name); + } +#endif +} diff --git a/src/jalv.c b/src/jalv.c index 316af17..7d5ac5a 100644 --- a/src/jalv.c +++ b/src/jalv.c @@ -40,15 +40,6 @@ #include "jalv_config.h" #include "jalv_internal.h" -#include -#include -#ifdef JALV_JACK_SESSION -# include -#endif -#ifdef HAVE_JACK_METADATA -# include -#endif - #include "lv2/lv2plug.in/ns/ext/atom/atom.h" #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" #include "lv2/lv2plug.in/ns/ext/data-access/data-access.h" @@ -84,12 +75,6 @@ # define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif -#ifdef __clang__ -# define REALTIME __attribute__((annotate("realtime"))) -#else -# define REALTIME -#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 @@ -208,7 +193,7 @@ create_port(Jalv* jalv, struct Port* const port = &jalv->ports[port_index]; port->lilv_port = lilv_plugin_get_port_by_index(jalv->plugin, port_index); - port->jack_port = NULL; + port->sys_port = NULL; port->evbuf = NULL; port->buf_size = 0; port->index = port_index; @@ -301,7 +286,7 @@ jalv_create_ports(Jalv* jalv) /** Allocate port buffers (only necessary for MIDI). */ -static void +void jalv_allocate_port_buffers(Jalv* jalv) { for (uint32_t i = 0; i < jalv->num_ports; ++i) { @@ -368,81 +353,6 @@ print_control_value(Jalv* jalv, const struct Port* port, float value) printf("%-*s = %f\n", jalv->longest_sym, lilv_node_as_string(sym), value); } -/** - Expose a port to Jack (if applicable) and connect it to its buffer. -*/ -static void -activate_port(Jalv* jalv, - uint32_t port_index) -{ - struct Port* const port = &jalv->ports[port_index]; - - const LilvNode* sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port); - - /* Connect unsupported ports to NULL (known to be optional by this point) */ - if (port->flow == FLOW_UNKNOWN || port->type == TYPE_UNKNOWN) { - lilv_instance_connect_port(jalv->instance, port_index, NULL); - return; - } - - /* Build Jack flags for port */ - enum JackPortFlags jack_flags = (port->flow == FLOW_INPUT) - ? JackPortIsInput - : JackPortIsOutput; - - /* Connect the port based on its type */ - switch (port->type) { - case TYPE_CONTROL: - lilv_instance_connect_port(jalv->instance, port_index, &port->control); - break; - case TYPE_AUDIO: - port->jack_port = jack_port_register( - jalv->jack_client, lilv_node_as_string(sym), - JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); - break; -#ifdef HAVE_JACK_METADATA - case TYPE_CV: - port->jack_port = jack_port_register( - jalv->jack_client, lilv_node_as_string(sym), - JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); - if (port->jack_port) { - jack_set_property(jalv->jack_client, jack_port_uuid(port->jack_port), - "http://jackaudio.org/metadata/signal-type", "CV", - "text/plain"); - } - break; -#endif - case TYPE_EVENT: - if (lilv_port_supports_event( - jalv->plugin, port->lilv_port, jalv->nodes.midi_MidiEvent)) { - port->jack_port = jack_port_register( - jalv->jack_client, lilv_node_as_string(sym), - JACK_DEFAULT_MIDI_TYPE, jack_flags, 0); - } - break; - default: - break; - } - -#ifdef HAVE_JACK_METADATA - if (port->jack_port) { - // Set port order to index - char index_str[16]; - snprintf(index_str, sizeof(index_str), "%d", port_index); - jack_set_property(jalv->jack_client, jack_port_uuid(port->jack_port), - "http://jackaudio.org/metadata/order", index_str, - "http://www.w3.org/2001/XMLSchema#integer"); - - // Set port pretty name to label - LilvNode* name = lilv_port_get_name(jalv->plugin, port->lilv_port); - jack_set_property(jalv->jack_client, jack_port_uuid(port->jack_port), - JACK_METADATA_PRETTY_NAME, lilv_node_as_string(name), - "text/plain"); - lilv_node_free(name); - } -#endif -} - void jalv_create_controls(Jalv* jalv, bool writable) { @@ -532,350 +442,6 @@ jalv_set_control(const ControlID* control, } } - -/** Jack buffer size callback. */ -static int -jack_buffer_size_cb(jack_nframes_t nframes, void* data) -{ - Jalv* const jalv = (Jalv*)data; - jalv->block_length = nframes; - jalv->buf_size_set = true; -#ifdef HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE - jalv->midi_buf_size = jack_port_type_get_buffer_size( - jalv->jack_client, JACK_DEFAULT_MIDI_TYPE); -#endif - jalv_allocate_port_buffers(jalv); - return 0; -} - -/** Jack shutdown callback. */ -static void -jack_shutdown_cb(void* data) -{ - Jalv* const jalv = (Jalv*)data; - jalv_close_ui(jalv); - zix_sem_post(jalv->done); -} - -/** Jack process callback. */ -static REALTIME int -jack_process_cb(jack_nframes_t nframes, void* data) -{ - Jalv* const jalv = (Jalv*)data; - - /* Get Jack transport position */ - jack_position_t pos; - const bool rolling = (jack_transport_query(jalv->jack_client, &pos) - == JackTransportRolling); - - /* If transport state is not as expected, then something has changed */ - const bool xport_changed = (rolling != jalv->rolling || - pos.frame != jalv->position || - pos.beats_per_minute != jalv->bpm); - - uint8_t pos_buf[256]; - LV2_Atom* lv2_pos = (LV2_Atom*)pos_buf; - if (xport_changed) { - /* Build an LV2 position object to report change to plugin */ - lv2_atom_forge_set_buffer(&jalv->forge, pos_buf, sizeof(pos_buf)); - LV2_Atom_Forge* forge = &jalv->forge; - LV2_Atom_Forge_Frame frame; - lv2_atom_forge_object(forge, &frame, 0, jalv->urids.time_Position); - lv2_atom_forge_key(forge, jalv->urids.time_frame); - lv2_atom_forge_long(forge, pos.frame); - lv2_atom_forge_key(forge, jalv->urids.time_speed); - lv2_atom_forge_float(forge, rolling ? 1.0 : 0.0); - if (pos.valid & JackPositionBBT) { - lv2_atom_forge_key(forge, jalv->urids.time_barBeat); - lv2_atom_forge_float( - forge, pos.beat - 1 + (pos.tick / pos.ticks_per_beat)); - lv2_atom_forge_key(forge, jalv->urids.time_bar); - lv2_atom_forge_long(forge, pos.bar - 1); - lv2_atom_forge_key(forge, jalv->urids.time_beatUnit); - lv2_atom_forge_int(forge, pos.beat_type); - lv2_atom_forge_key(forge, jalv->urids.time_beatsPerBar); - lv2_atom_forge_float(forge, pos.beats_per_bar); - lv2_atom_forge_key(forge, jalv->urids.time_beatsPerMinute); - lv2_atom_forge_float(forge, pos.beats_per_minute); - } - - if (jalv->opts.dump) { - char* str = sratom_to_turtle( - jalv->sratom, &jalv->unmap, "time:", NULL, NULL, - lv2_pos->type, lv2_pos->size, LV2_ATOM_BODY(lv2_pos)); - jalv_ansi_start(stdout, 36); - printf("\n## Position ##\n%s\n", str); - jalv_ansi_reset(stdout); - free(str); - } - } - - /* Update transport state to expected values for next cycle */ - jalv->position = rolling ? pos.frame + nframes : pos.frame; - jalv->bpm = pos.beats_per_minute; - jalv->rolling = rolling; - - switch (jalv->play_state) { - case JALV_PAUSE_REQUESTED: - jalv->play_state = JALV_PAUSED; - zix_sem_post(&jalv->paused); - break; - case JALV_PAUSED: - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - jack_port_t* jport = jalv->ports[p].jack_port; - if (jport && jalv->ports[p].flow == FLOW_OUTPUT) { - void* buf = jack_port_get_buffer(jport, nframes); - if (jalv->ports[p].type == TYPE_EVENT) { - jack_midi_clear_buffer(buf); - } else { - memset(buf, '\0', nframes * sizeof(float)); - } - } - } - return 0; - default: - break; - } - - /* Prepare port buffers */ - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - struct Port* port = &jalv->ports[p]; - if (port->type == TYPE_AUDIO && port->jack_port) { - /* Connect plugin port directly to Jack port buffer */ - lilv_instance_connect_port( - jalv->instance, p, - jack_port_get_buffer(port->jack_port, nframes)); -#ifdef HAVE_JACK_METADATA - } else if (port->type == TYPE_CV && port->jack_port) { - /* Connect plugin port directly to Jack port buffer */ - lilv_instance_connect_port( - jalv->instance, p, - jack_port_get_buffer(port->jack_port, nframes)); -#endif - } else if (port->type == TYPE_EVENT && port->flow == FLOW_INPUT) { - lv2_evbuf_reset(port->evbuf, true); - - /* Write transport change event if applicable */ - LV2_Evbuf_Iterator iter = lv2_evbuf_begin(port->evbuf); - if (xport_changed) { - lv2_evbuf_write(&iter, 0, 0, - lv2_pos->type, lv2_pos->size, - (const uint8_t*)LV2_ATOM_BODY(lv2_pos)); - } - - if (jalv->request_update) { - /* Plugin state has changed, request an update */ - const LV2_Atom_Object get = { - { sizeof(LV2_Atom_Object_Body), jalv->urids.atom_Object }, - { 0, jalv->urids.patch_Get } }; - lv2_evbuf_write(&iter, 0, 0, - get.atom.type, get.atom.size, - (const uint8_t*)LV2_ATOM_BODY(&get)); - } - - if (port->jack_port) { - /* Write Jack MIDI input */ - void* buf = jack_port_get_buffer(port->jack_port, nframes); - for (uint32_t i = 0; i < jack_midi_get_event_count(buf); ++i) { - jack_midi_event_t ev; - jack_midi_event_get(&ev, buf, i); - lv2_evbuf_write(&iter, - ev.time, 0, - jalv->midi_event_id, - ev.size, ev.buffer); - } - } - } else if (port->type == TYPE_EVENT) { - /* Clear event output for plugin to write to */ - lv2_evbuf_reset(port->evbuf, false); - } - } - jalv->request_update = false; - - /* Read and apply control change events from UI */ - if (jalv->has_ui) { - ControlChange ev; - const size_t space = jack_ringbuffer_read_space(jalv->ui_events); - for (size_t i = 0; i < space; i += sizeof(ev) + ev.size) { - jack_ringbuffer_read(jalv->ui_events, (char*)&ev, sizeof(ev)); - char body[ev.size]; - if (jack_ringbuffer_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 %d\n", - ev.protocol); - } - } - } - - /* 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; - jack_nframes_t 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; - } - - /* Deliver MIDI output and UI events */ - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - struct Port* const port = &jalv->ports[p]; - if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && - lilv_port_has_property(jalv->plugin, port->lilv_port, - jalv->nodes.lv2_reportsLatency)) { - if (jalv->plugin_latency != port->control) { - jalv->plugin_latency = port->control; - jack_recompute_total_latencies(jalv->jack_client); - } - } - - if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { - void* buf = NULL; - if (port->jack_port) { - buf = jack_port_get_buffer(port->jack_port, nframes); - jack_midi_clear_buffer(buf); - } - - for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(port->evbuf); - lv2_evbuf_is_valid(i); - i = lv2_evbuf_next(i)) { - uint32_t frames, subframes, type, size; - uint8_t* body; - lv2_evbuf_get(i, &frames, &subframes, &type, &size, &body); - if (buf && type == jalv->midi_event_id) { - jack_midi_event_write(buf, frames, body, size); - } - - /* TODO: Be more disciminate about what to send */ - if (jalv->has_ui && !port->old_api) { - char evbuf[sizeof(ControlChange) + sizeof(LV2_Atom)]; - ControlChange* ev = (ControlChange*)evbuf; - ev->index = p; - 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 (jack_ringbuffer_write_space(jalv->plugin_events) - < sizeof(evbuf) + size) { - fprintf(stderr, "Plugin => UI buffer overflow!\n"); - break; - } - jack_ringbuffer_write(jalv->plugin_events, evbuf, sizeof(evbuf)); - /* TODO: race, ensure reader handles this correctly */ - jack_ringbuffer_write(jalv->plugin_events, (const char*)body, size); - } - } - } else if (send_ui_updates - && port->flow != FLOW_INPUT - && port->type == TYPE_CONTROL) { - char buf[sizeof(ControlChange) + sizeof(float)]; - ControlChange* ev = (ControlChange*)buf; - ev->index = p; - ev->protocol = 0; - ev->size = sizeof(float); - *(float*)ev->body = port->control; - if (jack_ringbuffer_write(jalv->plugin_events, buf, sizeof(buf)) - < sizeof(buf)) { - fprintf(stderr, "Plugin => UI buffer overflow!\n"); - } - } - } - - return 0; -} - -/** Calculate latency assuming all ports depend on each other. */ -static void -jack_latency_cb(jack_latency_callback_mode_t mode, void* data) -{ - Jalv* const jalv = (Jalv*)data; - const enum PortFlow flow = ((mode == JackCaptureLatency) - ? FLOW_INPUT : FLOW_OUTPUT); - - /* First calculate the min/max latency of all feeding ports */ - uint32_t ports_found = 0; - jack_latency_range_t range = { UINT32_MAX, 0 }; - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - struct Port* port = &jalv->ports[p]; - if (port->jack_port && port->flow == flow) { - jack_latency_range_t r; - jack_port_get_latency_range(port->jack_port, mode, &r); - if (r.min < range.min) { range.min = r.min; } - if (r.max > range.max) { range.max = r.max; } - ++ports_found; - } - } - - if (ports_found == 0) { - range.min = 0; - } - - /* Add the plugin's own latency */ - range.min += jalv->plugin_latency; - range.max += jalv->plugin_latency; - - /* Tell Jack about it */ - for (uint32_t p = 0; p < jalv->num_ports; ++p) { - struct Port* port = &jalv->ports[p]; - if (port->jack_port && port->flow == flow) { - jack_port_set_latency_range(port->jack_port, mode, &range); - } - } -} - -#ifdef JALV_JACK_SESSION -static void -jack_session_cb(jack_session_event_t* event, void* arg) -{ - Jalv* const jalv = (Jalv*)arg; - - #define MAX_CMD_LEN 256 - event->command_line = (char*)malloc(MAX_CMD_LEN); - snprintf(event->command_line, MAX_CMD_LEN, "%s -u %s -l \"${SESSION_DIR}\"", - jalv->prog_name, - event->client_uuid); - - switch (event->type) { - case JackSessionSave: - case JackSessionSaveTemplate: - jalv_save(jalv, event->session_dir); - break; - case JackSessionSaveAndQuit: - jalv_save(jalv, event->session_dir); - jalv_close_ui(jalv); - break; - } - - jack_session_reply(jalv->jack_client, event); - jack_session_event_free(event); -} -#endif /* JALV_JACK_SESSION */ - void jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent) { @@ -986,7 +552,7 @@ jalv_ui_write(SuilController controller, ev->protocol = protocol; ev->size = buffer_size; memcpy(ev->body, buffer, buffer_size); - jack_ringbuffer_write(jalv->ui_events, buf, sizeof(buf)); + zix_ring_write(jalv->ui_events, buf, sizeof(buf)); } uint32_t @@ -1039,19 +605,19 @@ jalv_update(Jalv* jalv) /* Emit UI events. */ ControlChange ev; - const size_t space = jack_ringbuffer_read_space(jalv->plugin_events); + const size_t space = zix_ring_read_space(jalv->plugin_events); for (size_t i = 0; i + sizeof(ev) + sizeof(float) <= space; i += sizeof(ev) + ev.size) { /* Read event header to get the size */ - jack_ringbuffer_read(jalv->plugin_events, (char*)&ev, sizeof(ev)); + 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 */ - jack_ringbuffer_read(jalv->plugin_events, (char*)buf, ev.size); + 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 */ @@ -1380,57 +946,11 @@ main(int argc, char** argv) jalv_create_controls(&jalv, true); jalv_create_controls(&jalv, false); - /* Determine the name of the JACK client */ - char* jack_name = NULL; - if (jalv.opts.name) { - /* Name given on command line */ - jack_name = jalv_strdup(jalv.opts.name); - } else { - /* Use plugin name */ - LilvNode* name = lilv_plugin_get_name(jalv.plugin); - jack_name = jalv_strdup(lilv_node_as_string(name)); - lilv_node_free(name); - } - - /* Truncate client name to suit JACK if necessary */ - if (strlen(jack_name) >= (unsigned)jack_client_name_size() - 1) { - jack_name[jack_client_name_size() - 1] = '\0'; - } - - /* Connect to JACK */ - printf("JACK Name: %s\n", jack_name); -#ifdef JALV_JACK_SESSION - if (jalv.opts.uuid) { - jalv.jack_client = jack_client_open( - jack_name, - (jack_options_t)(JackSessionID | - (jalv.opts.name_exact ? JackUseExactName : 0)), - NULL, - jalv.opts.uuid); - } -#endif - - if (!jalv.jack_client) { - jalv.jack_client = jack_client_open( - jack_name, - (jalv.opts.name_exact ? JackUseExactName : JackNullOption), - NULL); + if (!(jalv.backend = jalv_backend_init(&jalv))) { + die("Failed to connect to audio system"); } - free(jack_name); - - if (!jalv.jack_client) - die("Failed to connect to JACK.\n"); - - jalv.sample_rate = jack_get_sample_rate(jalv.jack_client); - jalv.block_length = jack_get_buffer_size(jalv.jack_client); -#ifdef HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE - jalv.midi_buf_size = jack_port_type_get_buffer_size( - jalv.jack_client, JACK_DEFAULT_MIDI_TYPE); -#else - jalv.midi_buf_size = 4096; - fprintf(stderr, "warning: No jack_port_type_get_buffer_size.\n"); -#endif + printf("Sample rate: %u Hz\n", jalv.sample_rate); printf("Block length: %u frames\n", jalv.block_length); printf("MIDI buffers: %zu bytes\n", jalv.midi_buf_size); @@ -1478,10 +998,10 @@ main(int argc, char** argv) options_feature.data = (void*)&options; /* Create Plugin <=> UI communication buffers */ - jalv.ui_events = jack_ringbuffer_create(jalv.opts.buffer_size); - jalv.plugin_events = jack_ringbuffer_create(jalv.opts.buffer_size); - jack_ringbuffer_mlock(jalv.ui_events); - jack_ringbuffer_mlock(jalv.plugin_events); + 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); /* Instantiate the plugin */ jalv.instance = lilv_plugin_instantiate( @@ -1521,22 +1041,11 @@ main(int argc, char** argv) } /* Set Jack callbacks */ - jack_set_process_callback(jalv.jack_client, - &jack_process_cb, (void*)(&jalv)); - jack_set_buffer_size_callback(jalv.jack_client, - &jack_buffer_size_cb, (void*)(&jalv)); - jack_on_shutdown(jalv.jack_client, - &jack_shutdown_cb, (void*)(&jalv)); - jack_set_latency_callback(jalv.jack_client, - &jack_latency_cb, (void*)(&jalv)); -#ifdef JALV_JACK_SESSION - jack_set_session_callback(jalv.jack_client, - &jack_session_cb, (void*)(&jalv)); -#endif + jalv_backend_init(&jalv); /* Create Jack ports and connect plugin ports to buffers */ for (uint32_t i = 0; i < jalv.num_ports; ++i) { - activate_port(&jalv, i); + jalv_backend_activate_port(&jalv, i); } /* Print initial control values */ @@ -1555,9 +1064,8 @@ main(int argc, char** argv) jalv.has_ui = jalv_discover_ui(&jalv); /* Activate Jack */ - jack_activate(jalv.jack_client); - jalv.sample_rate = jack_get_sample_rate(jalv.jack_client); - jalv.play_state = JALV_RUNNING; + jalv_backend_activate(&jalv); + jalv.play_state = JALV_RUNNING; /* Run UI (or prompt at console) */ jalv_open_ui(&jalv); @@ -1571,14 +1079,14 @@ main(int argc, char** argv) /* Terminate the worker */ jalv_worker_finish(&jalv.worker); - /* Deactivate JACK */ - jack_deactivate(jalv.jack_client); + /* 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); } } - jack_client_close(jalv.jack_client); + jalv_backend_close(&jalv); /* Deactivate plugin */ suil_instance_free(jalv.ui_instance); @@ -1587,8 +1095,8 @@ main(int argc, char** argv) /* Clean up */ free(jalv.ports); - jack_ringbuffer_free(jalv.ui_events); - jack_ringbuffer_free(jalv.plugin_events); + zix_ring_free(jalv.ui_events); + zix_ring_free(jalv.plugin_events); for (LilvNode** n = (LilvNode**)&jalv.nodes; *n; ++n) { lilv_node_free(*n); } diff --git a/src/jalv_internal.h b/src/jalv_internal.h index c46a2ac..9e9b1fa 100644 --- a/src/jalv_internal.h +++ b/src/jalv_internal.h @@ -24,9 +24,6 @@ # include #endif -#include -#include - #include "lilv/lilv.h" #include "serd/serd.h" #include "suil/suil.h" @@ -40,6 +37,7 @@ #include "lv2/lv2plug.in/ns/ext/urid/urid.h" #include "lv2/lv2plug.in/ns/ext/worker/worker.h" +#include "zix/ring.h" #include "zix/sem.h" #include "zix/thread.h" @@ -48,10 +46,18 @@ #include "lv2_evbuf.h" #include "symap.h" +#ifdef __clang__ +# define REALTIME __attribute__((annotate("realtime"))) +#else +# define REALTIME +#endif + #ifdef __cplusplus extern "C" { #endif +typedef struct JalvBackend JalvBackend; + typedef struct Jalv Jalv; enum PortFlow { @@ -69,10 +75,10 @@ enum PortType { }; struct Port { - const LilvPort* lilv_port; - enum PortType type; - enum PortFlow flow; - jack_port_t* jack_port; ///< For audio/MIDI ports, otherwise NULL + const LilvPort* lilv_port; ///< LV2 port + enum PortType type; ///< Data type + enum PortFlow flow; ///< Data flow direction + void* sys_port; ///< For audio/MIDI ports, otherwise NULL LV2_Evbuf* evbuf; ///< For MIDI ports, otherwise NULL void* widget; ///< Control widget, if applicable size_t buf_size; ///< Custom buffer size, or 0 @@ -248,8 +254,8 @@ typedef enum { typedef struct { Jalv* jalv; ///< Pointer back to Jalv - jack_ringbuffer_t* requests; ///< Requests to the worker - jack_ringbuffer_t* responses; ///< Responses from the worker + ZixRing* requests; ///< Requests to the worker + ZixRing* responses; ///< Responses from the worker void* response; ///< Worker response buffer ZixSem sem; ///< Worker semaphore ZixThread thread; ///< Worker thread @@ -271,9 +277,9 @@ struct Jalv { Sratom* ui_sratom; ///< Atom serialiser for UI thread Symap* symap; ///< URI map ZixSem symap_lock; ///< Lock for URI map - jack_client_t* jack_client; ///< Jack client - jack_ringbuffer_t* ui_events; ///< Port events from UI - jack_ringbuffer_t* plugin_events; ///< Port events from plugin + JalvBackend* backend; ///< Audio system backend + ZixRing* ui_events; ///< Port events from UI + ZixRing* plugin_events; ///< Port events from plugin void* ui_event_buf; ///< Buffer for reading UI port events JalvWorker worker; ///< Worker thread implementation JalvWorker state_worker; ///< Synchronous worker for state restore @@ -294,17 +300,17 @@ struct Jalv { void* window; ///< Window (if applicable) struct Port* ports; ///< Port array of size num_ports Controls controls; ///< Available plugin controls - uint32_t block_length; ///< Jack buffer size (block length) + uint32_t block_length; ///< Audio buffer size (block length) size_t midi_buf_size; ///< Size of MIDI port buffers uint32_t control_in; ///< Index of control input port uint32_t num_ports; ///< Size of the two following arrays: uint32_t longest_sym; ///< Longest port symbol uint32_t plugin_latency; ///< Latency reported by plugin (if any) float ui_update_hz; ///< Frequency of UI updates - jack_nframes_t sample_rate; ///< Sample rate - jack_nframes_t event_delta_t; ///< Frames since last update sent to UI + uint32_t sample_rate; ///< Sample rate + uint32_t event_delta_t; ///< Frames since last update sent to UI uint32_t midi_event_id; ///< MIDI event class ID in event context - jack_nframes_t position; ///< Transport position in frames + uint32_t position; ///< Transport position in frames float bpm; ///< Transport tempo in beats per minute bool rolling; ///< Transport speed (0=stop, 1=play) bool buf_size_set; ///< True iff buffer size callback fired @@ -317,9 +323,28 @@ struct Jalv { int jalv_init(int* argc, char*** argv, JalvOptions* opts); +JalvBackend* +jalv_backend_init(Jalv* jalv); + +void +jalv_backend_activate(Jalv* jalv); + +void +jalv_backend_deactivate(Jalv* jalv); + +void +jalv_backend_close(Jalv* jalv); + +/** Expose a port to the system (if applicable) and connect it to its buffer. */ +void +jalv_backend_activate_port(Jalv* jalv, uint32_t port_index); + void jalv_create_ports(Jalv* jalv); +void +jalv_allocate_port_buffers(Jalv* jalv); + struct Port* jalv_port_by_symbol(Jalv* jalv, const char* sym); diff --git a/src/state.c b/src/state.c index f59ea43..32d6c86 100644 --- a/src/state.c +++ b/src/state.c @@ -192,7 +192,7 @@ set_port_value(const char* port_symbol, ev->protocol = 0; ev->size = sizeof(fvalue); *(float*)ev->body = fvalue; - jack_ringbuffer_write(jalv->plugin_events, buf, sizeof(buf)); + zix_ring_write(jalv->plugin_events, buf, sizeof(buf)); } } diff --git a/src/worker.c b/src/worker.c index 9035733..6f94997 100644 --- a/src/worker.c +++ b/src/worker.c @@ -22,8 +22,8 @@ jalv_worker_respond(LV2_Worker_Respond_Handle handle, const void* data) { JalvWorker* worker = (JalvWorker*)handle; - jack_ringbuffer_write(worker->responses, (const char*)&size, sizeof(size)); - jack_ringbuffer_write(worker->responses, (const char*)data, size); + zix_ring_write(worker->responses, (const char*)&size, sizeof(size)); + zix_ring_write(worker->responses, (const char*)data, size); return LV2_WORKER_SUCCESS; } @@ -40,7 +40,7 @@ worker_func(void* data) } uint32_t size = 0; - jack_ringbuffer_read(worker->requests, (char*)&size, sizeof(size)); + zix_ring_read(worker->requests, (char*)&size, sizeof(size)); if (!(buf = realloc(buf, size))) { fprintf(stderr, "error: realloc() failed\n"); @@ -48,7 +48,7 @@ worker_func(void* data) return NULL; } - jack_ringbuffer_read(worker->requests, (char*)buf, size); + zix_ring_read(worker->requests, (char*)buf, size); zix_sem_wait(&jalv->work_lock); worker->iface->work( @@ -70,12 +70,12 @@ jalv_worker_init(Jalv* jalv, worker->threaded = threaded; if (threaded) { zix_thread_create(&worker->thread, 4096, worker_func, worker); - worker->requests = jack_ringbuffer_create(4096); - jack_ringbuffer_mlock(worker->requests); + worker->requests = zix_ring_new(4096); + zix_ring_mlock(worker->requests); } - worker->responses = jack_ringbuffer_create(4096); + worker->responses = zix_ring_new(4096); worker->response = malloc(4096); - jack_ringbuffer_mlock(worker->responses); + zix_ring_mlock(worker->responses); } void @@ -85,9 +85,9 @@ jalv_worker_finish(JalvWorker* worker) if (worker->threaded) { zix_sem_post(&worker->sem); zix_thread_join(worker->thread, NULL); - jack_ringbuffer_free(worker->requests); + zix_ring_free(worker->requests); } - jack_ringbuffer_free(worker->responses); + zix_ring_free(worker->responses); free(worker->response); } } @@ -101,9 +101,8 @@ jalv_worker_schedule(LV2_Worker_Schedule_Handle handle, Jalv* jalv = worker->jalv; if (worker->threaded) { // Schedule a request to be executed by the worker thread - jack_ringbuffer_write(worker->requests, - (const char*)&size, sizeof(size)); - jack_ringbuffer_write(worker->requests, (const char*)data, size); + zix_ring_write(worker->requests, (const char*)&size, sizeof(size)); + zix_ring_write(worker->requests, (const char*)data, size); zix_sem_post(&worker->sem); } else { // Execute work immediately in this thread @@ -119,13 +118,12 @@ void jalv_worker_emit_responses(JalvWorker* worker, LilvInstance* instance) { if (worker->responses) { - uint32_t read_space = jack_ringbuffer_read_space(worker->responses); + uint32_t read_space = zix_ring_read_space(worker->responses); while (read_space) { uint32_t size = 0; - jack_ringbuffer_read(worker->responses, (char*)&size, sizeof(size)); + zix_ring_read(worker->responses, (char*)&size, sizeof(size)); - jack_ringbuffer_read( - worker->responses, (char*)worker->response, size); + zix_ring_read(worker->responses, (char*)worker->response, size); worker->iface->work_response( instance->lv2_handle, size, worker->response); diff --git a/src/zix/ring.c b/src/zix/ring.c new file mode 100644 index 0000000..3da4f7f --- /dev/null +++ b/src/zix/ring.c @@ -0,0 +1,222 @@ +/* + Copyright 2011 David Robillard + + 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. +*/ + +#include +#include +#include + +#ifdef HAVE_MLOCK +# include +# define ZIX_MLOCK(ptr, size) mlock((ptr), (size)) +#elif defined(_WIN32) +# include +# define ZIX_MLOCK(ptr, size) VirtualLock((ptr), (size)) +#else +# pragma message("warning: No memory locking, possible RT violations") +# define ZIX_MLOCK(ptr, size) +#endif + +#if defined(__APPLE__) +# include +# define ZIX_FULL_BARRIER() OSMemoryBarrier() +#elif defined(_WIN32) +# include +# define ZIX_FULL_BARRIER() MemoryBarrier() +#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) +# define ZIX_FULL_BARRIER() __sync_synchronize() +#else +# pragma message("warning: No memory barriers, possible SMP bugs") +# define ZIX_FULL_BARRIER() +#endif + +/* No support for any systems with separate read and write barriers */ +#define ZIX_READ_BARRIER() ZIX_FULL_BARRIER() +#define ZIX_WRITE_BARRIER() ZIX_FULL_BARRIER() + +#include "zix/ring.h" + +struct ZixRingImpl { + uint32_t write_head; ///< Read index into buf + uint32_t read_head; ///< Write index into buf + uint32_t size; ///< Size (capacity) in bytes + uint32_t size_mask; ///< Mask for fast modulo + char* buf; ///< Contents +}; + +static inline uint32_t +next_power_of_two(uint32_t size) +{ + // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + size--; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + size++; + return size; +} + +ZixRing* +zix_ring_new(uint32_t size) +{ + ZixRing* ring = (ZixRing*)malloc(sizeof(ZixRing)); + ring->write_head = 0; + ring->read_head = 0; + ring->size = next_power_of_two(size); + ring->size_mask = ring->size - 1; + ring->buf = (char*)malloc(ring->size); + return ring; +} + +void +zix_ring_free(ZixRing* ring) +{ + free(ring->buf); + free(ring); +} + +void +zix_ring_mlock(ZixRing* ring) +{ + ZIX_MLOCK(ring, sizeof(ZixRing)); + ZIX_MLOCK(ring->buf, ring->size); +} + +void +zix_ring_reset(ZixRing* ring) +{ + ring->write_head = 0; + ring->read_head = 0; +} + +static inline uint32_t +read_space_internal(const ZixRing* ring, uint32_t r, uint32_t w) +{ + if (r < w) { + return w - r; + } else { + return (w - r + ring->size) & ring->size_mask; + } +} + +uint32_t +zix_ring_read_space(const ZixRing* ring) +{ + return read_space_internal(ring, ring->read_head, ring->write_head); +} + +static inline uint32_t +write_space_internal(const ZixRing* ring, uint32_t r, uint32_t w) +{ + if (r == w) { + return ring->size - 1; + } else if (r < w) { + return ((r - w + ring->size) & ring->size_mask) - 1; + } else { + return (r - w) - 1; + } +} + +uint32_t +zix_ring_write_space(const ZixRing* ring) +{ + return write_space_internal(ring, ring->read_head, ring->write_head); +} + +uint32_t +zix_ring_capacity(const ZixRing* ring) +{ + return ring->size - 1; +} + +static inline uint32_t +peek_internal(const ZixRing* ring, uint32_t r, uint32_t w, + uint32_t size, void* dst) +{ + if (read_space_internal(ring, r, w) < size) { + return 0; + } + + if (r + size < ring->size) { + memcpy(dst, &ring->buf[r], size); + } else { + const uint32_t first_size = ring->size - r; + memcpy(dst, &ring->buf[r], first_size); + memcpy((char*)dst + first_size, &ring->buf[0], size - first_size); + } + + return size; +} + +uint32_t +zix_ring_peek(ZixRing* ring, void* dst, uint32_t size) +{ + return peek_internal(ring, ring->read_head, ring->write_head, size, dst); +} + +uint32_t +zix_ring_read(ZixRing* ring, void* dst, uint32_t size) +{ + const uint32_t r = ring->read_head; + const uint32_t w = ring->write_head; + + if (peek_internal(ring, r, w, size, dst)) { + ZIX_READ_BARRIER(); + ring->read_head = (r + size) & ring->size_mask; + return size; + } else { + return 0; + } +} + +uint32_t +zix_ring_skip(ZixRing* ring, uint32_t size) +{ + const uint32_t r = ring->read_head; + const uint32_t w = ring->write_head; + if (read_space_internal(ring, r, w) < size) { + return 0; + } + + ZIX_READ_BARRIER(); + ring->read_head = (r + size) & ring->size_mask; + return size; +} + +uint32_t +zix_ring_write(ZixRing* ring, const void* src, uint32_t size) +{ + const uint32_t r = ring->read_head; + const uint32_t w = ring->write_head; + if (write_space_internal(ring, r, w) < size) { + return 0; + } + + if (w + size <= ring->size) { + memcpy(&ring->buf[w], src, size); + ZIX_WRITE_BARRIER(); + ring->write_head = (w + size) & ring->size_mask; + } else { + const uint32_t this_size = ring->size - w; + memcpy(&ring->buf[w], src, this_size); + memcpy(&ring->buf[0], (const char*)src + this_size, size - this_size); + ZIX_WRITE_BARRIER(); + ring->write_head = size - this_size; + } + + return size; +} diff --git a/src/zix/ring.h b/src/zix/ring.h new file mode 100644 index 0000000..f7f1893 --- /dev/null +++ b/src/zix/ring.h @@ -0,0 +1,130 @@ +/* + Copyright 2011-2014 David Robillard + + 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. +*/ + +#ifndef ZIX_RING_H +#define ZIX_RING_H + +#include + +#include "zix/common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + @addtogroup zix + @{ + @name Ring + @{ +*/ + +/** + A lock-free ring buffer. + + Thread-safe with a single reader and single writer, and realtime safe + on both ends. +*/ +typedef struct ZixRingImpl ZixRing; + +/** + Create a new ring. + @param size Size in bytes (note this may be rounded up). + + At most `size` - 1 bytes may be stored in the ring at once. +*/ +ZixRing* +zix_ring_new(uint32_t size); + +/** + Destroy a ring. +*/ +void +zix_ring_free(ZixRing* ring); + +/** + Lock the ring data into physical memory. + + This function is NOT thread safe or real-time safe, but it should be called + after zix_ring_new() to lock all ring memory to avoid page faults while + using the ring (i.e. this function MUST be called first in order for the + ring to be truly real-time safe). + +*/ +void +zix_ring_mlock(ZixRing* ring); + +/** + Reset (empty) a ring. + + This function is NOT thread-safe, it may only be called when there are no + readers or writers. +*/ +void +zix_ring_reset(ZixRing* ring); + +/** + Return the number of bytes of space available for reading. +*/ +uint32_t +zix_ring_read_space(const ZixRing* ring); + +/** + Return the number of bytes of space available for writing. +*/ +uint32_t +zix_ring_write_space(const ZixRing* ring); + +/** + Return the capacity (i.e. total write space when empty). +*/ +uint32_t +zix_ring_capacity(const ZixRing* ring); + +/** + Read from the ring without advancing the read head. +*/ +uint32_t +zix_ring_peek(ZixRing* ring, void* dst, uint32_t size); + +/** + Read from the ring and advance the read head. +*/ +uint32_t +zix_ring_read(ZixRing* ring, void* dst, uint32_t size); + +/** + Skip data in the ring (advance read head without reading). +*/ +uint32_t +zix_ring_skip(ZixRing* ring, uint32_t size); + +/** + Write data to the ring. +*/ +uint32_t +zix_ring_write(ZixRing* ring, const void* src, uint32_t size); + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ZIX_RING_H */ diff --git a/wscript b/wscript index 28cae4a..f987bdf 100644 --- a/wscript +++ b/wscript @@ -113,6 +113,12 @@ def configure(conf): define_name='HAVE_FILENO', mandatory=False) + conf.check(function_name='mlock', + header_name='sys/mman.h', + defines=defines, + define_name='HAVE_MLOCK', + mandatory=False) + if conf.is_defined('HAVE_ISATTY') and conf.is_defined('HAVE_FILENO'): autowaf.define(conf, 'JALV_WITH_COLOR', 1) conf.env.append_unique('CFLAGS', ['-D_POSIX_C_SOURCE=200809L']) @@ -135,9 +141,18 @@ def configure(conf): print('') def build(bld): - libs = 'LILV SUIL JACK SERD SORD SRATOM LV2' - - source = 'src/jalv.c src/symap.c src/state.c src/lv2_evbuf.c src/worker.c src/log.c src/control.c' + libs = 'LILV SUIL JACK SERD SORD SRATOM LV2' + source = ''' + src/control.c + src/jack.c + src/jalv.c + src/log.c + src/lv2_evbuf.c + src/state.c + src/symap.c + src/worker.c + src/zix/ring.c + ''' # Non-GUI version obj = bld(features = 'c cprogram', -- cgit v1.2.1