1
0
Fork 0
forked from hanez/xtc
xtc/src/backend/jack_backend.c

455 lines
23 KiB
C
Raw Normal View History

2026-10-26 23:32:04 +01:00
#include "xtc/xtc_backend.h"
#include "xtc/xtc_realtime.h"
#include <stdlib.h>
#include <string.h>
#ifdef XTC_HAVE_JACK
#include <jack/jack.h>
#include <jack/midiport.h>
2026-10-26 23:32:04 +01:00
#include <errno.h>
#include <math.h>
2026-10-26 23:32:04 +01:00
struct xtc_backend {
jack_client_t *client;
jack_port_t *input, *output, *midi_input, *output_right;
2026-10-26 23:32:04 +01:00
xtc_realtime *engine;
xtc_audio_plan *audio;
xtc_midi_plan *midi;
unsigned int panic_requested, panic_seen, midi_dropped, peak_left, peak_right;
xtc_recorder *recorder; /* Borrowed, installed while callbacks quiescent. */
float *timeline, *instrument;
2026-10-26 23:32:04 +01:00
size_t max_frames;
uint64_t sample_position;
2026-10-26 23:32:04 +01:00
unsigned int expected_rate;
unsigned int rate, frames, lost, rate_changed, xruns;
unsigned int process_result;
int ready, running; /* Control thread only. */
};
static xtc_result render_midi(xtc_backend *backend, uint64_t position, jack_nframes_t frames)
{
xtc_midi_event events[XTC_MIDI_MAX_EVENTS];
size_t count = 0;
uint32_t i, total;
unsigned int requested = __atomic_load_n(&backend->panic_requested, __ATOMIC_ACQUIRE);
void *buffer;
if (!backend->midi) return XTC_OK;
if (requested != backend->panic_seen) {
xtc_midi_plan_panic(backend->midi, position); backend->panic_seen = requested;
}
buffer = jack_port_get_buffer(backend->midi_input, frames);
total = buffer ? jack_midi_get_event_count(buffer) : 0;
if (!buffer || total > XTC_MIDI_MAX_EVENTS) {
(void)__atomic_fetch_add(&backend->midi_dropped, total ? total : 1U, __ATOMIC_RELAXED);
xtc_midi_plan_panic(backend->midi, position);
} else for (i = 0; i < total; ++i) {
jack_midi_event_t raw;
xtc_result result;
if (jack_midi_event_get(&raw, buffer, i) != 0 || raw.time >= frames) {
(void)__atomic_fetch_add(&backend->midi_dropped, 1U, __ATOMIC_RELAXED);
xtc_midi_plan_panic(backend->midi, position); count = 0; break;
}
result = xtc_midi_decode(raw.buffer, raw.size, (unsigned int)raw.time, events + count);
if (result == XTC_ERR_UNSUPPORTED) continue;
if (result != XTC_OK || (count && events[count].offset < events[count - 1].offset)) {
(void)__atomic_fetch_add(&backend->midi_dropped, 1U, __ATOMIC_RELAXED);
xtc_midi_plan_panic(backend->midi, position); count = 0; break;
}
++count;
}
return xtc_midi_plan_render_stereo(backend->midi, position, events, count, backend->instrument,
backend->output_right ? backend->instrument + backend->max_frames : NULL, (size_t)frames);
}
2026-10-26 23:32:04 +01:00
static int process(jack_nframes_t frames, void *context)
{
xtc_backend *backend = context;
float *output = jack_port_get_buffer(backend->output, frames);
float *right = backend->output_right ? jack_port_get_buffer(backend->output_right, frames) : NULL;
2026-10-26 23:32:04 +01:00
xtc_result result = XTC_OK;
if (output == NULL || (backend->output_right && !right)) {
if (output) memset(output, 0, (size_t)frames * sizeof(*output));
if (right) memset(right, 0, (size_t)frames * sizeof(*right));
__atomic_store_n(&backend->peak_left, 0U, __ATOMIC_RELAXED);
__atomic_store_n(&backend->peak_right, 0U, __ATOMIC_RELAXED);
(void)__atomic_fetch_add(&backend->panic_requested, 1U, __ATOMIC_RELEASE);
if (backend->recorder) xtc_recorder_push(backend->recorder, NULL, 1U);
2026-10-26 23:32:04 +01:00
__atomic_store_n(&backend->process_result, (unsigned int)XTC_ERR_IO, __ATOMIC_RELAXED);
return 0;
}
if (__atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE) ||
__atomic_load_n(&backend->rate_changed, __ATOMIC_ACQUIRE)) {
result = XTC_ERR_INVALID_STATE;
} else if ((size_t)frames > backend->max_frames) {
result = XTC_ERR_CAPACITY;
} else {
const float *input = jack_port_get_buffer(backend->input, frames);
uint64_t position = __atomic_load_n(&backend->sample_position, __ATOMIC_RELAXED);
if (backend->recorder) xtc_recorder_push(backend->recorder, input, (size_t)frames);
if (backend->audio)
result = xtc_audio_plan_render_stereo(backend->audio, position, backend->timeline,
right ? backend->timeline + backend->max_frames : NULL, (size_t)frames);
if (result == XTC_OK) result = render_midi(backend, position, frames);
if (result == XTC_OK)
result = xtc_realtime_process_stereo(backend->engine, input,
backend->audio ? backend->timeline : NULL,
backend->audio && right ? backend->timeline + backend->max_frames : NULL,
backend->midi ? backend->instrument : NULL,
backend->midi && right ? backend->instrument + backend->max_frames : NULL,
output, right, (size_t)frames);
2026-10-26 23:32:04 +01:00
}
if (result == XTC_OK) {
uint64_t position = __atomic_load_n(&backend->sample_position, __ATOMIC_RELAXED);
if (position > UINT64_MAX - frames) result = XTC_ERR_CAPACITY;
else __atomic_store_n(&backend->sample_position, position + frames, __ATOMIC_RELAXED);
}
if (result != XTC_OK) {
(void)__atomic_fetch_add(&backend->panic_requested, 1U, __ATOMIC_RELEASE);
if (backend->recorder) xtc_recorder_push(backend->recorder, NULL, 1U);
memset(output, 0, (size_t)frames * sizeof(*output));
if (right) memset(right, 0, (size_t)frames * sizeof(*right));
}
{
size_t i; float left_peak = 0, right_peak = 0;
for (i = 0; i < frames; ++i) {
float l = fabsf(output[i]), r = right ? fabsf(right[i]) : l;
if (l > left_peak) left_peak = l;
if (r > right_peak) right_peak = r;
}
__atomic_store_n(&backend->peak_left, (unsigned int)(fminf(left_peak, 1000) * 1000000), __ATOMIC_RELAXED);
__atomic_store_n(&backend->peak_right, (unsigned int)(fminf(right_peak, 1000) * 1000000), __ATOMIC_RELAXED);
}
2026-10-26 23:32:04 +01:00
__atomic_store_n(&backend->process_result, (unsigned int)result, __ATOMIC_RELAXED);
return 0; /* Keep connected and silent on recoverable processing errors. */
}
static void shutdown_callback(void *context)
{
xtc_backend *backend = context;
__atomic_store_n(&backend->lost, 1U, __ATOMIC_RELEASE);
}
static void port_connection(jack_port_id_t source, jack_port_id_t destination, int connected, void *context)
{
xtc_backend *backend = context;
if (!connected && (jack_port_by_id(backend->client, source) == backend->midi_input ||
jack_port_by_id(backend->client, destination) == backend->midi_input))
(void)__atomic_fetch_add(&backend->panic_requested, 1U, __ATOMIC_RELEASE);
}
2026-10-26 23:32:04 +01:00
static int rate_callback(jack_nframes_t rate, void *context)
{
xtc_backend *backend = context;
__atomic_store_n(&backend->rate, (unsigned int)rate, __ATOMIC_RELAXED);
if ((unsigned int)rate != backend->expected_rate)
__atomic_store_n(&backend->rate_changed, 1U, __ATOMIC_RELEASE);
return 0;
}
static int buffer_callback(jack_nframes_t frames, void *context)
{
xtc_backend *backend = context;
__atomic_store_n(&backend->frames, (unsigned int)frames, __ATOMIC_RELAXED);
return 0;
}
static int xrun_callback(void *context)
{
xtc_backend *backend = context;
(void)__atomic_fetch_add(&backend->xruns, 1U, __ATOMIC_RELAXED);
(void)__atomic_fetch_add(&backend->panic_requested, 1U, __ATOMIC_RELEASE);
2026-10-26 23:32:04 +01:00
return 0;
}
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out_backend)
{
xtc_backend *backend;
xtc_result result;
jack_status_t status = 0;
if (out_backend == NULL) return XTC_ERR_INVALID_ARGUMENT;
*out_backend = NULL;
if (config == NULL || config->client_name == NULL || config->client_name[0] == '\0' ||
config->max_frames == 0U || config->max_frames > 65536U || (config->channel_count != 1U && config->channel_count != 2U))
2026-10-26 23:32:04 +01:00
return XTC_ERR_INVALID_ARGUMENT;
if (!__atomic_always_lock_free(sizeof(uint64_t), 0)) return XTC_ERR_UNSUPPORTED;
2026-10-26 23:32:04 +01:00
if (config->kind != XTC_BACKEND_JACK) return XTC_ERR_UNSUPPORTED;
if (strlen(config->client_name) >= (size_t)jack_client_name_size()) return XTC_ERR_INVALID_ARGUMENT;
backend = calloc(1U, sizeof(*backend));
if (backend == NULL) return XTC_ERR_OUT_OF_MEMORY;
backend->timeline = calloc(config->max_frames * config->channel_count, sizeof(float));
backend->instrument = calloc(config->max_frames * config->channel_count, sizeof(float));
if (!backend->timeline || !backend->instrument) {
free(backend->timeline); free(backend->instrument); free(backend); return XTC_ERR_OUT_OF_MEMORY;
}
2026-10-26 23:32:04 +01:00
backend->max_frames = config->max_frames;
backend->client = jack_client_open(config->client_name, JackNoStartServer, &status);
if (backend->client == NULL) { free(backend->instrument); free(backend->timeline); free(backend); return XTC_ERR_IO; }
2026-10-26 23:32:04 +01:00
backend->expected_rate = (unsigned int)jack_get_sample_rate(backend->client);
backend->rate = backend->expected_rate;
backend->frames = (unsigned int)jack_get_buffer_size(backend->client);
result = backend->frames == 0U || backend->frames > config->max_frames ? XTC_ERR_CAPACITY :
xtc_realtime_create((double)backend->rate, config->max_frames, &backend->engine);
if (result != XTC_OK) goto fail;
backend->input = jack_port_register(backend->client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0U);
backend->output = jack_port_register(backend->client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0U);
backend->midi_input = jack_port_register(backend->client, "midi_in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0U);
if (config->channel_count == 2U)
backend->output_right = jack_port_register(backend->client, "output_right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0U);
if ((config->channel_count == 2U && !backend->output_right) || backend->input == NULL || backend->output == NULL || backend->midi_input == NULL) { result = XTC_ERR_IO; goto fail; }
if (jack_set_port_connect_callback(backend->client, port_connection, backend) != 0 ||
jack_set_process_callback(backend->client, process, backend) != 0 ||
2026-10-26 23:32:04 +01:00
jack_set_sample_rate_callback(backend->client, rate_callback, backend) != 0 ||
jack_set_buffer_size_callback(backend->client, buffer_callback, backend) != 0 ||
jack_set_xrun_callback(backend->client, xrun_callback, backend) != 0) {
result = XTC_ERR_IO;
goto fail;
}
jack_on_shutdown(backend->client, shutdown_callback, backend);
backend->ready = 1;
*out_backend = backend;
return XTC_OK;
fail:
/* Client never activated: no processing callback can reference the engine.
* close synchronizes any registered notification callbacks. */
if (jack_client_close(backend->client) != 0) {
*out_backend = backend; /* Preserve callback context for close retry. */
return XTC_ERR_IO;
}
xtc_realtime_destroy(backend->engine);
xtc_audio_plan_destroy(backend->audio);
xtc_midi_plan_destroy(backend->midi);
free(backend->instrument);
free(backend->timeline);
2026-10-26 23:32:04 +01:00
free(backend);
return result;
}
xtc_result xtc_backend_close(xtc_backend *backend)
{
if (backend == NULL) return XTC_OK;
/* JACK removes this client and joins callbacks before returning success. */
if (jack_client_close(backend->client) != 0) return XTC_ERR_IO;
xtc_realtime_destroy(backend->engine);
xtc_audio_plan_destroy(backend->audio);
xtc_midi_plan_destroy(backend->midi);
free(backend->instrument);
free(backend->timeline);
2026-10-26 23:32:04 +01:00
free(backend);
return XTC_OK;
}
xtc_result xtc_backend_start(xtc_backend *backend)
{
if (backend == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready) return XTC_ERR_INVALID_STATE;
if (__atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE) ||
__atomic_load_n(&backend->rate_changed, __ATOMIC_ACQUIRE)) return XTC_ERR_INVALID_STATE;
if (backend->running) return XTC_ERR_INVALID_STATE;
xtc_midi_plan_reset(backend->midi);
backend->panic_seen = __atomic_load_n(&backend->panic_requested, __ATOMIC_ACQUIRE);
2026-10-26 23:32:04 +01:00
if (jack_activate(backend->client) != 0) return XTC_ERR_IO;
backend->running = 1;
return XTC_OK;
}
xtc_result xtc_backend_stop(xtc_backend *backend)
{
if (backend == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready) return XTC_ERR_INVALID_STATE;
if (!backend->running) { xtc_midi_plan_reset(backend->midi); return XTC_OK; }
2026-10-26 23:32:04 +01:00
if (jack_deactivate(backend->client) != 0) return XTC_ERR_IO;
backend->running = 0;
xtc_midi_plan_reset(backend->midi);
2026-10-26 23:32:04 +01:00
xtc_realtime_collect(backend->engine);
return XTC_OK;
}
xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample)
{
if (backend == NULL || sample > UINT64_MAX - 65536U) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready || backend->running ||
__atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE) ||
__atomic_load_n(&backend->rate_changed, __ATOMIC_ACQUIRE)) return XTC_ERR_INVALID_STATE;
xtc_midi_plan_reset(backend->midi);
__atomic_store_n(&backend->sample_position, sample, __ATOMIC_RELAXED);
return XTC_OK;
}
2026-10-26 23:32:04 +01:00
xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
{
if (backend == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready) return XTC_ERR_INVALID_STATE;
if (__atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE) ||
__atomic_load_n(&backend->rate_changed, __ATOMIC_ACQUIRE)) return XTC_ERR_INVALID_STATE;
if (!backend->running) {
xtc_realtime *replacement = NULL;
xtc_result result = xtc_realtime_create((double)backend->expected_rate, backend->max_frames, &replacement);
if (result != XTC_OK) return result;
result = xtc_realtime_submit(replacement, graph);
if (result != XTC_OK) { xtc_realtime_destroy(replacement); return result; }
/* Inactive client, or successful deactivate: process callback quiescent.
* Notifications never access engine. Preserve old engine until ready. */
xtc_realtime_destroy(backend->engine);
backend->engine = replacement;
__atomic_store_n(&backend->process_result, (unsigned int)XTC_OK, __ATOMIC_RELAXED);
return XTC_OK;
}
2026-10-26 23:32:04 +01:00
return xtc_realtime_submit(backend->engine, graph);
}
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project)
{
xtc_audio_plan *candidate = NULL;
xtc_midi_plan *midi = NULL;
xtc_result result;
if (!backend || !project) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready || backend->running) return XTC_ERR_INVALID_STATE;
result = xtc_audio_plan_create(project, backend->expected_rate, &candidate);
if (result == XTC_OK) result = xtc_midi_plan_create(project, backend->expected_rate, &midi);
if (result == XTC_OK) result = xtc_backend_submit(backend, xtc_project_graph(project));
if (result != XTC_OK) { xtc_audio_plan_destroy(candidate); xtc_midi_plan_destroy(midi); return result; }
xtc_audio_plan_destroy(backend->audio); backend->audio = candidate;
xtc_midi_plan_destroy(backend->midi); backend->midi = midi;
return XTC_OK;
}
const char *xtc_backend_asset_issue(const xtc_backend *backend)
{
const char *path = "";
if (backend) xtc_audio_plan_issues(backend->audio, &path);
return path;
}
xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder)
{
if (!backend) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready || backend->running) return XTC_ERR_INVALID_STATE;
if (recorder && (__atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE) ||
__atomic_load_n(&backend->rate_changed, __ATOMIC_ACQUIRE))) return XTC_ERR_INVALID_STATE;
backend->recorder = recorder;
return XTC_OK;
}
xtc_result xtc_backend_panic(xtc_backend *backend)
{
if (!backend) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready) return XTC_ERR_INVALID_STATE;
(void)__atomic_fetch_add(&backend->panic_requested, 1U, __ATOMIC_RELEASE);
if (!backend->running) xtc_midi_plan_reset(backend->midi);
return XTC_OK;
}
xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **out)
{
if (!backend || !out) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready) return XTC_ERR_INVALID_STATE;
*out = jack_port_name(backend->midi_input); return XTC_OK;
}
2026-10-26 23:32:04 +01:00
void xtc_backend_collect(xtc_backend *backend)
{ if (backend != NULL) xtc_realtime_collect(backend->engine); }
xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status *out_status)
{
xtc_realtime_status engine_status;
if (backend == NULL || out_status == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready) return XTC_ERR_INVALID_STATE;
(void)xtc_realtime_get_status(backend->engine, &engine_status);
out_status->peak_left = backend->running ? (float)__atomic_load_n(&backend->peak_left, __ATOMIC_RELAXED) / 1000000.0f : 0;
out_status->peak_right = backend->running ? (float)__atomic_load_n(&backend->peak_right, __ATOMIC_RELAXED) / 1000000.0f : 0;
out_status->midi_dropped = __atomic_load_n(&backend->midi_dropped, __ATOMIC_RELAXED);
out_status->asset_issues = (unsigned int)xtc_audio_plan_issues(backend->audio, NULL);
2026-10-26 23:32:04 +01:00
out_status->sample_rate = __atomic_load_n(&backend->rate, __ATOMIC_RELAXED);
out_status->buffer_frames = __atomic_load_n(&backend->frames, __ATOMIC_RELAXED);
out_status->xruns = __atomic_load_n(&backend->xruns, __ATOMIC_RELAXED);
out_status->server_lost = (int)__atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE);
out_status->sample_rate_changed = (int)__atomic_load_n(&backend->rate_changed, __ATOMIC_ACQUIRE);
out_status->running = backend->running && !out_status->server_lost;
out_status->sample_position = __atomic_load_n(&backend->sample_position, __ATOMIC_RELAXED);
2026-10-26 23:32:04 +01:00
out_status->processed_blocks = engine_status.processed_blocks;
out_status->submitted_revision = engine_status.submitted_revision;
out_status->active_revision = engine_status.active_revision;
2026-10-26 23:32:04 +01:00
out_status->process_result = (xtc_result)__atomic_load_n(&backend->process_result, __ATOMIC_RELAXED);
return XTC_OK;
}
xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction direction,
const char **out_name)
{
if (backend == NULL || out_name == NULL ||
(direction != XTC_PORT_INPUT && direction != XTC_PORT_OUTPUT)) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready || __atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE)) return XTC_ERR_INVALID_STATE;
*out_name = jack_port_name(direction == XTC_PORT_INPUT ? backend->input : backend->output);
return *out_name == NULL ? XTC_ERR_IO : XTC_OK;
}
xtc_result xtc_backend_output_name(const xtc_backend *backend, size_t channel, const char **out)
{
if (!backend || !out || channel > 1) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready || __atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE)) return XTC_ERR_INVALID_STATE;
if (channel && !backend->output_right) return XTC_ERR_NOT_FOUND;
*out = jack_port_name(channel ? backend->output_right : backend->output);
return *out ? XTC_OK : XTC_ERR_IO;
}
2026-10-26 23:32:04 +01:00
xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction direction,
xtc_backend_port_visitor visitor, void *context)
{
const char **ports;
size_t i;
if (backend == NULL || visitor == NULL ||
(direction != XTC_PORT_INPUT && direction != XTC_PORT_OUTPUT)) return XTC_ERR_INVALID_ARGUMENT;
if (!backend->ready || __atomic_load_n(&backend->lost, __ATOMIC_ACQUIRE)) return XTC_ERR_INVALID_STATE;
ports = jack_get_ports(backend->client, NULL, JACK_DEFAULT_AUDIO_TYPE,
direction == XTC_PORT_INPUT ? JackPortIsInput : JackPortIsOutput);
if (ports == NULL) return XTC_OK;
for (i = 0U; ports[i] != NULL; ++i) if (visitor(ports[i], context) != 0) break;
jack_free(ports);
return XTC_OK;
}
static xtc_result route(xtc_backend *backend, xtc_port_direction direction,
const char *remote, int disconnect)
{
const char *own = NULL;
const char *source, *dest;
xtc_result result;
int code;
if (remote == NULL || remote[0] == '\0') return XTC_ERR_INVALID_ARGUMENT;
result = xtc_backend_port_name(backend, direction, &own);
if (result != XTC_OK) return result;
if (!backend->running) return XTC_ERR_INVALID_STATE;
source = direction == XTC_PORT_OUTPUT ? own : remote;
dest = direction == XTC_PORT_OUTPUT ? remote : own;
code = disconnect ? jack_disconnect(backend->client, source, dest) :
jack_connect(backend->client, source, dest);
return code == 0 || (!disconnect && code == EEXIST) ? XTC_OK : XTC_ERR_IO;
}
xtc_result xtc_backend_connect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{ return route(backend, direction, remote, 0); }
xtc_result xtc_backend_disconnect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{ return route(backend, direction, remote, 1); }
#else
xtc_result xtc_backend_output_name(const xtc_backend *b, size_t c, const char **out)
{ (void)b; (void)c; (void)out; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_panic(xtc_backend *backend)
{ (void)backend; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_midi_port_name(const xtc_backend *backend, const char **out)
{ (void)backend; (void)out; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project)
{ (void)backend; (void)project; return XTC_ERR_UNSUPPORTED; }
const char *xtc_backend_asset_issue(const xtc_backend *backend)
{ (void)backend; return ""; }
xtc_result xtc_backend_set_recorder(xtc_backend *backend, xtc_recorder *recorder)
{ (void)backend; (void)recorder; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_seek(xtc_backend *backend, uint64_t sample)
{ (void)backend; (void)sample; return XTC_ERR_UNSUPPORTED; }
2026-10-26 23:32:04 +01:00
/* Keep core/offline builds available on machines without JACK headers. */
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out_backend)
{
if (out_backend == NULL) return XTC_ERR_INVALID_ARGUMENT;
*out_backend = NULL;
if (config == NULL) return XTC_ERR_INVALID_ARGUMENT;
return XTC_ERR_UNSUPPORTED;
}
xtc_result xtc_backend_close(xtc_backend *backend)
{ return backend == NULL ? XTC_OK : XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_start(xtc_backend *backend)
{ (void)backend; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_stop(xtc_backend *backend)
{ (void)backend; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
{ (void)backend; (void)graph; return XTC_ERR_UNSUPPORTED; }
void xtc_backend_collect(xtc_backend *backend) { (void)backend; }
xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status *out_status)
{ (void)backend; (void)out_status; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_port_name(const xtc_backend *backend, xtc_port_direction direction, const char **out_name)
{ (void)backend; (void)direction; (void)out_name; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_visit_ports(xtc_backend *backend, xtc_port_direction direction,
xtc_backend_port_visitor visitor, void *context)
{ (void)backend; (void)direction; (void)visitor; (void)context; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_connect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{ (void)backend; (void)direction; (void)remote; return XTC_ERR_UNSUPPORTED; }
xtc_result xtc_backend_disconnect(xtc_backend *backend, xtc_port_direction direction, const char *remote)
{ (void)backend; (void)direction; (void)remote; return XTC_ERR_UNSUPPORTED; }
#endif