#include "xtc/xtc_realtime.h" #include #include #include struct xtc_realtime { double sample_rate; size_t max_frames; xtc_plan *pending; xtc_plan *retired; xtc_plan *active; /* Audio owner only, until consumer is stopped/joined. */ unsigned int blocks; unsigned int result; unsigned int submitted_revision, pending_revision, active_revision; }; xtc_result xtc_realtime_create(double sample_rate, size_t max_frames, xtc_realtime **out_engine) { xtc_realtime *engine; if (out_engine == NULL) return XTC_ERR_INVALID_ARGUMENT; *out_engine = NULL; if (!isfinite(sample_rate) || sample_rate <= 0.0 || max_frames == 0U || max_frames > 65536U) return XTC_ERR_INVALID_ARGUMENT; if (!__atomic_always_lock_free(sizeof(xtc_plan *), 0) || !__atomic_always_lock_free(sizeof(unsigned int), 0)) return XTC_ERR_UNSUPPORTED; engine = calloc(1U, sizeof(*engine)); if (engine == NULL) return XTC_ERR_OUT_OF_MEMORY; engine->sample_rate = sample_rate; engine->max_frames = max_frames; *out_engine = engine; return XTC_OK; } void xtc_realtime_collect(xtc_realtime *engine) { xtc_plan *retired; if (engine == NULL) return; retired = __atomic_exchange_n(&engine->retired, NULL, __ATOMIC_ACQ_REL); xtc_plan_destroy(retired); } void xtc_realtime_destroy(xtc_realtime *engine) { if (engine == NULL) return; /* Lifecycle contract: no callback can still execute. */ xtc_plan_destroy(engine->active); xtc_plan_destroy(engine->pending); xtc_plan_destroy(engine->retired); free(engine); } xtc_result xtc_realtime_submit(xtc_realtime *engine, const xtc_graph *graph) { xtc_plan *plan = NULL; xtc_result result; if (engine == NULL || graph == NULL) return XTC_ERR_INVALID_ARGUMENT; xtc_realtime_collect(engine); if (__atomic_load_n(&engine->pending, __ATOMIC_ACQUIRE) != NULL) return XTC_ERR_CAPACITY; result = xtc_graph_compile(graph, engine->sample_rate, engine->max_frames, &plan); if (result != XTC_OK) return result; { unsigned int revision = __atomic_load_n(&engine->submitted_revision, __ATOMIC_RELAXED) + 1U; __atomic_store_n(&engine->pending_revision, revision, __ATOMIC_RELAXED); __atomic_store_n(&engine->submitted_revision, revision, __ATOMIC_RELAXED); } __atomic_store_n(&engine->pending, plan, __ATOMIC_RELEASE); return XTC_OK; } xtc_result xtc_realtime_get_status(const xtc_realtime *engine, xtc_realtime_status *out_status) { if (engine == NULL || out_status == NULL) return XTC_ERR_INVALID_ARGUMENT; out_status->processed_blocks = __atomic_load_n(&engine->blocks, __ATOMIC_RELAXED); out_status->submitted_revision = __atomic_load_n(&engine->submitted_revision, __ATOMIC_RELAXED); out_status->active_revision = __atomic_load_n(&engine->active_revision, __ATOMIC_RELAXED); out_status->last_result = (xtc_result)__atomic_load_n(&engine->result, __ATOMIC_RELAXED); return XTC_OK; } xtc_result xtc_realtime_process_stereo(xtc_realtime *engine, const float *input, const float *timeline, const float *timeline_right, const float *instrument, const float *instrument_right, float *output, float *right, size_t frame_count) { xtc_result result = XTC_OK; if (engine == NULL || output == NULL || (right && right == output) || frame_count > SIZE_MAX / sizeof(*output)) return XTC_ERR_INVALID_ARGUMENT; if (frame_count == 0U) return XTC_OK; if (frame_count > engine->max_frames) { result = XTC_ERR_CAPACITY; } else { /* A full retirement slot delays adoption without delaying processing. * acquire pairs with control's release exchange after its last read. */ if (__atomic_load_n(&engine->retired, __ATOMIC_ACQUIRE) == NULL) { xtc_plan *next = __atomic_load_n(&engine->pending, __ATOMIC_ACQUIRE); if (next != NULL) { xtc_plan *old = engine->active; /* Read revision BEFORE releasing the pending slot. The producer * cannot reuse pending_revision until its acquire observes NULL. */ unsigned int revision = __atomic_load_n(&engine->pending_revision, __ATOMIC_RELAXED); (void)__atomic_exchange_n(&engine->pending, NULL, __ATOMIC_ACQ_REL); engine->active = next; __atomic_store_n(&engine->active_revision, revision, __ATOMIC_RELAXED); /* No reference to old is used after this release. */ __atomic_store_n(&engine->retired, old, __ATOMIC_RELEASE); } } if (engine->active != NULL) result = xtc_plan_process_stereo(engine->active, input, timeline, timeline_right, instrument, instrument_right, output, right, frame_count); else { memset(output, 0, frame_count * sizeof(*output)); if (right) memset(right, 0, frame_count * sizeof(*right)); } } if (result != XTC_OK) { memset(output, 0, frame_count * sizeof(*output)); if (right) memset(right, 0, frame_count * sizeof(*right)); } __atomic_store_n(&engine->result, (unsigned int)result, __ATOMIC_RELAXED); __atomic_store_n(&engine->blocks, __atomic_load_n(&engine->blocks, __ATOMIC_RELAXED) + 1U, __ATOMIC_RELAXED); return result; } xtc_result xtc_realtime_process(xtc_realtime *engine, const float *input, float *output, size_t frames) { return xtc_realtime_process_audio(engine, input, NULL, output, frames); } xtc_result xtc_realtime_process_audio(xtc_realtime *engine, const float *input, const float *timeline, float *output, size_t frames) { return xtc_realtime_process_sources(engine, input, timeline, NULL, output, frames); } xtc_result xtc_realtime_process_sources(xtc_realtime *p, const float *in, const float *audio, const float *midi, float *out, size_t frames) { return xtc_realtime_process_stereo(p, in, audio, NULL, midi, NULL, out, NULL, frames); }