1
0
Fork 0
forked from hanez/xtc
xtc/src/engine/realtime.c

106 lines
4 KiB
C
Raw Normal View History

2026-10-26 23:32:04 +01:00
#include "xtc/xtc_realtime.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
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;
}