Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC interface for control ports #21

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,427 changes: 2,427 additions & 0 deletions Doxyfile

Large diffs are not rendered by default.

696 changes: 696 additions & 0 deletions src/ipc_controls.c

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions src/ipc_controls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2017 Timo Wischer <[email protected]>

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 CONTROL_API_H
#define CONTROL_API_H

/**
* @addtogroup Internal
* @{
* @name Control API
* @{
*
* @brief The following functions are used to handle the API.
* These functions are only used internally
*/

#include "jalv.h"
#include "jalv_internal.h"

/**
* @brief api_ctl_init creates and initilizes all required IPC mechanismen
* @param jalv
* @return <0 on error
*/
int jalv_api_ctl_init(Jalv* const jalv);

/**
* @brief api_ctl_lock has to be called before the control port data will be changed
* @param jalv
* @return <0 on error
* This function will be called from real time context
*/
int jalv_api_ctl_lock(Jalv* const jalv, const bool process_requests);

/**
* @brief api_ctl_unlock has to be called to provide the changed control data to the IPC clients
* @param jalv
* @return <0 on error
*/
int jalv_api_ctl_unlock(Jalv* const jalv, const bool trigger_events);

/**
* @brief jalv_api_ctl_destroy frees all internal ressources of the JALV API
* @param jalv
*/
void jalv_api_ctl_destroy(Jalv* const jalv);

/**
@endcond
@}
@}
*/

#endif // CONTROL_API_H
38 changes: 34 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,27 @@ 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
* @return true if this is the same period which will be processed
*/
uint32_t
jalv_backend_get_process_cycle_id(const Jalv* const jalv)
{
return jack_last_frame_time(jalv->backend->client);
}

int
jack_initialize(jack_client_t* const client, const char* const load_init)
{
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
Loading