aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2016-10-05 00:32:44 -0400
committerDavid Robillard <d@drobilla.net>2016-10-05 01:31:35 -0400
commit86e57efa909ad4e475186aaf7ce8623eee9ee0aa (patch)
tree8b1cdb398043df5aa8faf4932f641cc4f7a82ae8
parent538bc9fbd498088dc1812f16dcfe83e3a3c20781 (diff)
downloadjalv-86e57efa909ad4e475186aaf7ce8623eee9ee0aa.tar.gz
jalv-86e57efa909ad4e475186aaf7ce8623eee9ee0aa.tar.bz2
jalv-86e57efa909ad4e475186aaf7ce8623eee9ee0aa.zip
Factor out Jack backend
-rw-r--r--src/jack.c539
-rw-r--r--src/jalv.c536
-rw-r--r--src/jalv_internal.h57
-rw-r--r--src/state.c2
-rw-r--r--src/worker.c32
-rw-r--r--src/zix/ring.c222
-rw-r--r--src/zix/ring.h130
-rw-r--r--wscript21
8 files changed, 988 insertions, 551 deletions
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 <http://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.
+*/
+
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#ifdef JALV_JACK_SESSION
+# include <jack/session.h>
+#endif
+#ifdef HAVE_JACK_METADATA
+# include <jack/metadata.h>
+#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 <jack/jack.h>
-#include <jack/midiport.h>
-#ifdef JALV_JACK_SESSION
-# include <jack/session.h>
-#endif
-#ifdef HAVE_JACK_METADATA
-# include <jack/metadata.h>
-#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 <unistd.h>
#endif
-#include <jack/jack.h>
-#include <jack/ringbuffer.h>
-
#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 <http://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.
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_MLOCK
+# include <sys/mman.h>
+# define ZIX_MLOCK(ptr, size) mlock((ptr), (size))
+#elif defined(_WIN32)
+# include <windows.h>
+# 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 <libkern/OSAtomic.h>
+# define ZIX_FULL_BARRIER() OSMemoryBarrier()
+#elif defined(_WIN32)
+# include <windows.h>
+# 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 <http://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.
+*/
+
+#ifndef ZIX_RING_H
+#define ZIX_RING_H
+
+#include <stdint.h>
+
+#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',