Skip to content

Commit

Permalink
Use additional IPC interface to read and write
Browse files Browse the repository at this point in the history
Lv2 control ports from external processes

Signed-off-by: Timo Wischer <[email protected]>
  • Loading branch information
Timo Wischer authored and drobilla committed Nov 10, 2018
1 parent 0f3e67a commit d399039
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 37 deletions.
27 changes: 23 additions & 4 deletions src/jack.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "jalv_internal.h"
#include "worker.h"
#include "zix/atomic.h"

struct JalvBackend {
jack_client_t* client; ///< Jack client
Expand Down Expand Up @@ -208,8 +209,12 @@ jack_process_cb(jack_nframes_t nframes, void* data)
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;
/* The shared memory will only be manipulated by this thread.
* Therefore reading is safe.
*/
const float latency = port->control.data[0];
if (jalv->plugin_latency != latency) {
jalv->plugin_latency = latency;
jack_recompute_total_latencies(client);
}
} else if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) {
Expand Down Expand Up @@ -244,7 +249,11 @@ jack_process_cb(jack_nframes_t nframes, void* data)
ev->index = p;
ev->protocol = 0;
ev->size = sizeof(float);
*(float*)ev->body = port->control;
/* The shared memory will only be manipulated by this thread.
* Therefore reading is safe.
*/
float* const dest = (float*)ev->body;
*dest = port->control.data[0];
if (zix_ring_write(jalv->plugin_events, buf, sizeof(buf))
< sizeof(buf)) {
fprintf(stderr, "Plugin => UI buffer overflow!\n");
Expand Down Expand Up @@ -461,7 +470,7 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index)
/* Connect the port based on its type */
switch (port->type) {
case TYPE_CONTROL:
lilv_instance_connect_port(jalv->instance, port_index, &port->control);
lilv_instance_connect_port(jalv->instance, port_index, port->control.data);
break;
case TYPE_AUDIO:
port->sys_port = jack_port_register(
Expand Down Expand Up @@ -511,6 +520,16 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index)
#endif
}

int
jalv_backend_instance_name(Jalv* jalv, char* const name, const size_t size)
{
const char* const client_name = jack_get_client_name(jalv->backend->client);
strncpy(name, client_name, size);
name[size-1] = 0;

return 0;
}

/**
* @brief jalv_backend_is_same_cycle
* @param jalv
Expand Down
50 changes: 37 additions & 13 deletions src/jalv.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

#include "jalv_config.h"
#include "jalv_internal.h"
#include "ipc_controls.h"
#include "zix/atomic.h"

#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
Expand Down Expand Up @@ -150,8 +152,7 @@ die(const char* msg)
*/
static void
create_port(Jalv* jalv,
uint32_t port_index,
float default_value)
uint32_t port_index)
{
struct Port* const port = &jalv->ports[port_index];

Expand All @@ -160,7 +161,9 @@ create_port(Jalv* jalv,
port->evbuf = NULL;
port->buf_size = 0;
port->index = port_index;
port->control = 0.0f;
port->control.shm_index = -1;
port->control.data = NULL;
port->control.new_data = NAN;
port->flow = FLOW_UNKNOWN;

const bool optional = lilv_port_has_property(
Expand All @@ -184,7 +187,6 @@ create_port(Jalv* jalv,
/* Set control values */
if (lilv_port_is_a(jalv->plugin, port->lilv_port, jalv->nodes.lv2_ControlPort)) {
port->type = TYPE_CONTROL;
port->control = isnan(default_value) ? 0.0f : default_value;
if (!hidden) {
add_control(&jalv->controls, new_port_control(jalv, port->index));
}
Expand Down Expand Up @@ -221,21 +223,16 @@ jalv_create_ports(Jalv* jalv)
{
jalv->num_ports = lilv_plugin_get_num_ports(jalv->plugin);
jalv->ports = (struct Port*)calloc(jalv->num_ports, sizeof(struct Port));
float* default_values = (float*)calloc(
lilv_plugin_get_num_ports(jalv->plugin), sizeof(float));
lilv_plugin_get_port_ranges_float(jalv->plugin, NULL, NULL, default_values);

for (uint32_t i = 0; i < jalv->num_ports; ++i) {
create_port(jalv, i, default_values[i]);
create_port(jalv, i);
}

const LilvPort* control_input = lilv_plugin_get_port_by_designation(
jalv->plugin, jalv->nodes.lv2_InputPort, jalv->nodes.lv2_control);
if (control_input) {
jalv->control_in = lilv_port_get_index(jalv->plugin, control_input);
}

free(default_values);
}

/**
Expand Down Expand Up @@ -365,7 +362,11 @@ jalv_set_control(const ControlID* control,
Jalv* jalv = control->jalv;
if (control->type == PORT && type == jalv->forge.Float) {
struct Port* port = &control->jalv->ports[control->index];
port->control = *(const float*)body;
/* This value will be written from different threads e.g. gtk and JACK processing CB
* and atomic copy is not guaranteed on all platforms (e.g. unaligned access on ARM).
* Therefore we cannot directly write to port->control
*/
ZIX_ATOMIC_WRITE(&port->control.new_data, *(const float*)body);
} else if (control->type == PROPERTY) {
// Copy forge since it is used by process thread
LV2_Atom_Forge forge = jalv->forge;
Expand Down Expand Up @@ -525,7 +526,8 @@ jalv_apply_ui_events(Jalv* jalv, uint32_t nframes)
struct Port* const port = &jalv->ports[ev.index];
if (ev.protocol == 0) {
assert(ev.size == sizeof(float));
port->control = *(float*)body;
/* only be called inside the lock. Therefore no atomic access required */
port->control.data[0] = *(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;
Expand Down Expand Up @@ -608,6 +610,11 @@ jalv_send_to_ui(Jalv* jalv,
bool
jalv_run(Jalv* jalv, uint32_t nframes)
{
/* lock control data shared memory for write access */
if (jalv_api_ctl_lock(jalv, true) != 0) {
die("api_ctl_lock() failed");
}

/* Read and apply control change events from UI */
jalv_apply_ui_events(jalv, nframes);

Expand All @@ -623,6 +630,11 @@ jalv_run(Jalv* jalv, uint32_t nframes)
jalv->worker.iface->end_run(jalv->instance->lv2_handle);
}

/* unlock control data shared memory for write access.
* Up to now only read access is allowed.
*/
jalv_api_ctl_unlock(jalv, true);

/* Check if it's time to send updates to the UI */
jalv->event_delta_t += nframes;
bool send_ui_updates = false;
Expand Down Expand Up @@ -1008,6 +1020,12 @@ jalv_open(Jalv* const jalv, int argc, char** argv)
return -6;
}

if (jalv_api_ctl_init(jalv) != 0) {
fprintf(stderr, "Init of external IPC API failed\n");
jalv_close(jalv);
return -7;
}

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);
Expand Down Expand Up @@ -1151,7 +1169,11 @@ jalv_open(Jalv* const jalv, int argc, char** argv)
ControlID* control = jalv->controls.controls[i];
if (control->type == PORT && control->is_writable) {
struct Port* port = &jalv->ports[control->index];
jalv_print_control(jalv, port, port->control);
/* Backend thread is not yet started.
* Therefore there is no other thread
* which writes to this variable
*/
jalv_print_control(jalv, port, port->control.data[0]);
}
}

Expand Down Expand Up @@ -1199,6 +1221,8 @@ jalv_close(Jalv* const jalv)
lilv_instance_free(jalv->instance);
}

jalv_api_ctl_destroy(jalv);

/* Clean up */
free(jalv->ports);
zix_ring_free(jalv->ui_events);
Expand Down
24 changes: 21 additions & 3 deletions src/jalv_console.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "jalv_config.h"
#include "jalv_internal.h"
#include "zix/atomic.h"

#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"

Expand All @@ -46,6 +47,7 @@ print_usage(const char* name, bool error)
fprintf(os, " -t Print trace messages from plugin\n");
fprintf(os, " -u UUID UUID for Jack session restoration\n");
fprintf(os, " -x Exact JACK client name (exit if taken)\n");
fprintf(os, " -g GROUP User group name or ID used for creating JALV API IPC resources\n");
return error ? 1 : 0;
}

Expand Down Expand Up @@ -120,6 +122,14 @@ jalv_init(int* argc, char*** argv, JalvOptions* opts)
opts->name = jalv_strdup((*argv)[a]);
} else if ((*argv)[a][1] == 'x') {
opts->name_exact = 1;
} else if ((*argv)[a][1] == 'g') {
if (++a == *argc) {
fprintf(stderr, "Missing argument for -g\n");
return 1;
}
free(opts->user_group);
opts->user_group = jalv_strdup((*argv)[a]);

} else {
fprintf(stderr, "Unknown option %s\n", (*argv)[a]);
return print_usage((*argv)[0], true);
Expand All @@ -145,7 +155,7 @@ jalv_print_controls(Jalv* jalv, bool writable, bool readable)
struct Port* const port = &jalv->ports[control->index];
printf("%s = %f\n",
lilv_node_as_string(control->symbol),
port->control);
port->control.data[0]);
}
}
}
Expand Down Expand Up @@ -191,7 +201,11 @@ jalv_process_command(Jalv* jalv, const char* cmd)
jalv_print_controls(jalv, false, true);
} else if (sscanf(cmd, "set %u %f", &index, &value) == 2) {
if (index < jalv->num_ports) {
jalv->ports[index].control = value;
/* This method will be called by main thread
* after starting the backend thread.
* Therefore we cannot directly write to port->control
*/
ZIX_ATOMIC_WRITE(&jalv->ports[index].control.new_data, value);
jalv_print_control(jalv, &jalv->ports[index], value);
} else {
fprintf(stderr, "error: port index out of range\n");
Expand All @@ -208,7 +222,11 @@ jalv_process_command(Jalv* jalv, const char* cmd)
}
}
if (port) {
port->control = value;
/* This method will be called by main thread
* after starting the backend thread.
* Therefore we cannot directly write to port->control
*/
ZIX_ATOMIC_WRITE(&port->control.new_data, value);
jalv_print_control(jalv, port, value);
} else {
fprintf(stderr, "error: no control named `%s'\n", sym);
Expand Down
45 changes: 44 additions & 1 deletion src/jalv_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "zix/ring.h"
#include "zix/sem.h"
#include "zix/thread.h"
#include "jalv.h"

#include "sratom/sratom.h"

Expand Down Expand Up @@ -87,7 +88,32 @@ struct Port {
void* widget; ///< Control widget, if applicable
size_t buf_size; ///< Custom buffer size, or 0
uint32_t index; ///< Port index
float control; ///< For control ports, otherwise 0.0f
struct {
int32_t shm_index; ///< Control port index in the shared memory
/**
* @brief data must only be accessed by the backend thread or
* when the backend thread was not yet started.
* The memory of this pointer will also be accessed by the loaded Lv2 plug-in.
* A Lv2 plug-in does not guarantee that it accesses this memory in an atomic manner.
* Therefore it could also result in invalid data
* when this memory will be accessed from a different thread
* with atomic read or writes.
* Instead of that
* jalv_api_ctl_read_port() should be used to read this memory and
* the first element of the memory can be changed by
* writing atomically to new_data.
*/
float* data; ///< For control ports, otherwise NULL
/**
* @brief new_data
* *data will be overwritten by this value
* before executing the run() function of the Lv2 plug-in.
* This variable will be accessed by different threads.
* Therefore atomic access has to be used.
*/
atomic_float new_data; ///< For control ports, otherwise NaN
} control;
bool old_api; ///< True for event, false for atom
};

/* Controls */
Expand Down Expand Up @@ -175,6 +201,7 @@ typedef struct {
int show_ui; ///< Show non-embedded UI
int print_controls; ///< Print control changes to stdout
int non_interactive; ///< Do not listen for commands on stdin
char* user_group; ///< The user group which is used for the JALV API
} JalvOptions;

typedef struct {
Expand Down Expand Up @@ -321,6 +348,19 @@ struct Jalv {
void* window; ///< Window (if applicable)
struct Port* ports; ///< Port array of size num_ports
Controls controls; ///< Available plugin controls
jalv_api_t jalv_api;
struct {
size_t ctl_input_data_size;
char* ctl_input_data; ///< changed values when in blocking mode
ZixShm shm;
} jalv_api_blocking;
size_t ctl_data_size;
/**
* @brief prev_ctl_data contians the control data before the last changes
* This data will be compared with the data in jalv_api_t::shm
* to find the changed control port values
*/
char* prev_ctl_data;
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
Expand Down Expand Up @@ -366,6 +406,9 @@ jalv_backend_close(Jalv* jalv);
void
jalv_backend_activate_port(Jalv* jalv, uint32_t port_index);

int
jalv_backend_instance_name(Jalv* jalv, char* const name, const size_t size);

uint32_t
jalv_backend_get_process_cycle_id(const Jalv* const jalv);

Expand Down
23 changes: 21 additions & 2 deletions src/jalv_qt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cstdio>

#include "jalv_internal.h"
#include "zix/atomic.h"

#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
Expand Down Expand Up @@ -459,7 +460,21 @@ Control::Control(PortContainer portContainer, QWidget* parent)
}

// Find and set min, max and default values for port
float defaultValue = ndef ? lilv_node_as_float(ndef) : port->control;
float defaultValue = 0.0f;
if (ndef) {
defaultValue = lilv_node_as_float(ndef);
} else {
/* the audio backend thread is already running
* when calling this function.
* Therefore the read could be interrupted
* by a write of the backend thread.
* Which could result in invalid data
* if the read or the write was not atomic
* (e.g. on ARM when accessing unaligned)
* As a result port->control cannot be used for reading
*/
jalv_api_ctl_read_port_by_index(&portContainer.jalv->jalv_api, port->control.shm_index, sizeof(defaultValue), &defaultValue);
}
setRange(lilv_node_as_float(nmin), lilv_node_as_float(nmax));
setValue(defaultValue);

Expand Down Expand Up @@ -577,7 +592,11 @@ Control::dialChanged(int dialValue)
float value = getValue();

label->setText(getValueLabel(value));
port->control = value;
/* This method will be called by main thread
* after starting the backend thread.
* Therefore we cannot directly write to port->control
*/
ZIX_ATOMIC_WRITE(&port->control.new_data, value);
}

static bool
Expand Down
Loading

0 comments on commit d399039

Please sign in to comment.