106 lines
4 KiB
C
106 lines
4 KiB
C
#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;
|
|
}
|