#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; }; 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; __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->last_result = (xtc_result)__atomic_load_n(&engine->result, __ATOMIC_RELAXED); return XTC_OK; } xtc_result xtc_realtime_process(xtc_realtime *engine, const float *input, float *output, size_t frame_count) { xtc_result result = XTC_OK; if (engine == NULL || output == NULL || 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_exchange_n(&engine->pending, NULL, __ATOMIC_ACQUIRE); if (next != NULL) { xtc_plan *old = engine->active; engine->active = next; /* 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(engine->active, input, output, frame_count); else memset(output, 0, frame_count * sizeof(*output)); } if (result != XTC_OK) memset(output, 0, frame_count * sizeof(*output)); __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; }