Phase 1 plus some refactoring.
This commit is contained in:
parent
71f7b4d96b
commit
8075e1b79d
17 changed files with 465 additions and 266 deletions
8
Makefile
8
Makefile
|
|
@ -49,8 +49,12 @@ $(TEST_OBJECT): tests/unit/test_core.c
|
|||
$(BUILD_DIR)/test_core: $(TEST_OBJECT) $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $(TEST_OBJECT) $(BUILD_DIR)/libxtc.a -lm
|
||||
|
||||
test: $(BUILD_DIR)/test_core $(BUILD_DIR)/xtc-check
|
||||
$(BUILD_DIR)/test_offline: $(BUILD_DIR)/tests/unit/test_offline.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm
|
||||
|
||||
test: $(BUILD_DIR)/test_core $(BUILD_DIR)/test_offline $(BUILD_DIR)/xtc-check
|
||||
./$(BUILD_DIR)/test_core
|
||||
./$(BUILD_DIR)/test_offline
|
||||
./$(BUILD_DIR)/xtc-check
|
||||
|
||||
debug:
|
||||
|
|
@ -75,5 +79,5 @@ install: all
|
|||
clean:
|
||||
rm -rf "$(BUILD_DIR)"
|
||||
|
||||
-include $(CORE_OBJECTS:.o=.d) $(TEST_OBJECT:.o=.d) $(BUILD_DIR)/src/main.d \
|
||||
-include $(CORE_OBJECTS:.o=.d) $(TEST_OBJECT:.o=.d) $(BUILD_DIR)/src/main.d $(BUILD_DIR)/tests/unit/test_offline.d \
|
||||
$(BUILD_DIR)/src/gui/main.d $(BUILD_DIR)/src/gui/main_window.d
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
This repository is a starter scaffold, not a production DAW. It currently provides:
|
||||
- a small C library/API;
|
||||
- a minimal graph model with validation and topological ordering;
|
||||
- a gain processor and offline audio-block processing;
|
||||
- a C unit test;
|
||||
- a mono graph model with port validation, cycle rejection and topological ordering;
|
||||
- compiled offline plans with preallocated buffers, sine oscillator, gain, meter and output nodes;
|
||||
- C tests for deterministic rendering, routing, validation and lifecycle;
|
||||
- an optional FLTK GUI shell;
|
||||
- architecture, format, GUI, roadmap, and decision documents.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,13 @@ thread: validate, topologically sort, determine buffer requirements, allocate
|
|||
resources, and publish a prepared immutable execution plan. The audio thread
|
||||
must not traverse a graph being edited.
|
||||
|
||||
The current scaffold is deliberately incomplete: `xt_graph_process_offline()`
|
||||
validates the graph and applies gain nodes in insertion order. It is a test
|
||||
placeholder, not the final graph execution algorithm.
|
||||
The offline engine now copies a validated graph into a prepared plan with
|
||||
stable node IDs, topological order, precomputed input adjacency, per-node mono
|
||||
buffers and oscillator/meter state. Each plan has one processing owner. Graph
|
||||
edits and destruction cannot affect an existing plan. All nodes are scheduled,
|
||||
including disconnected nodes; only explicit output nodes are rendered (or
|
||||
terminal nodes when no output node exists). See AUDIO_ENGINE.md for contracts.
|
||||
Plan publication across threads and retirement remain Phase 2 work.
|
||||
|
||||
## Ownership and threads
|
||||
|
||||
|
|
|
|||
|
|
@ -25,3 +25,57 @@ Audio sample position is the authoritative playback clock. Use integer or
|
|||
rational timeline representations to avoid cumulative floating-point drift.
|
||||
Define tempo-map conversion and behavior at tempo changes before implementing
|
||||
tempo automation.
|
||||
|
||||
## Phase 1 offline API
|
||||
|
||||
`xtc_graph_create` accepts 1..256 nodes; IDs are insertion indexes, stable for
|
||||
that graph's lifetime. Supported nodes are gain (any finite multiplier), sine
|
||||
oscillator (finite nonnegative Hz), pass-through peak meter and audio output.
|
||||
Ports carry mono float audio: input 0, output 1. Oscillators have no input and
|
||||
output nodes have no output. Port lookup reports direction, signal type and
|
||||
channel count. MIDI/control ports and channel conversion are not implemented;
|
||||
invalid ports/directions, duplicate edges and cycles are rejected before an
|
||||
edit becomes visible. Fan-out copies a source to consumers; fan-in sums inputs
|
||||
without normalization. Failed additions leave IDs and graph contents unchanged.
|
||||
|
||||
`xtc_graph_compile(graph, sample_rate, max_frames, &plan)` runs on the control
|
||||
thread. Empty graphs are rejected. Sample rate must be finite and positive,
|
||||
oscillators must be at or below Nyquist, and max_frames is 1..65536. Compilation
|
||||
allocates and copies topology/parameters, builds a topological schedule and
|
||||
input adjacency, and allocates one max_frames float buffer per node. The
|
||||
maximum buffer storage is 64 MiB per plan. Compilation may fail with an
|
||||
allocation error; output plan is then NULL. Destroy a plan on the control thread.
|
||||
|
||||
`xtc_plan_process` uses only prepared state: no allocation/free, sorting,
|
||||
logging, I/O or locks. Each call accepts up to max_frames; oversized calls
|
||||
return capacity errors without modifying output or state. Output storage must
|
||||
hold frame_count floats. NULL input means silence; input/output may alias.
|
||||
Zero frames leaves all state unchanged. No plan operations may run concurrently
|
||||
with processing, including reset, meter reads or destruction.
|
||||
|
||||
Unconnected non-oscillator nodes receive external input. Connected inputs
|
||||
receive only the sum of their upstream buffers. Explicit output nodes are
|
||||
summed to the caller's output. If none exist, terminal nodes are summed for
|
||||
compatibility with the original gain-chain API. Disconnected components cannot
|
||||
contribute when explicit outputs exist, but they are still processed. Floating
|
||||
point overflow and nonfinite input become silence per sample. No clipping or
|
||||
normalization is applied to finite samples.
|
||||
|
||||
Oscillators start at phase zero and carry phase continuously across blocks;
|
||||
reset clears phases and meter peaks. Meter reads return the last nonempty
|
||||
block's absolute peak. These are same-thread reads, not a thread-safe GUI
|
||||
meter bridge. Rendering is deterministic on the same build/platform regardless
|
||||
of block partitioning; libm results need not be bit-identical across platforms.
|
||||
|
||||
`xtc_graph_process_offline` remains a convenience helper, compiling at 48000 Hz
|
||||
and destroying the plan after one block. It accepts a finite external-input
|
||||
gain and requires non-NULL input/output. It now follows routing rather than
|
||||
multiplying every gain in insertion order. It allocates and is not suitable for
|
||||
an audio callback. Use a persistent plan for phase continuity and repeated blocks.
|
||||
|
||||
Tests in `tests/unit/test_offline.c` cover known oscillator samples, gain and
|
||||
meter output, reset and partition equivalence, fan-out/fan-in, reverse insertion
|
||||
order, disconnected nodes, in-place processing, nonfinite input, invalid ports,
|
||||
cycles, capacity limits, plan lifetime and repeated compilation/cleanup.
|
||||
Hardware playback, concurrent graph publication and backend stress tests remain
|
||||
Phase 2 work; this offline API does not claim hardware real-time qualification.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ make gui
|
|||
```
|
||||
|
||||
`all` builds the static C library and startup check. `test` runs the existing
|
||||
core test and startup check. `debug` builds and tests separately in
|
||||
core and offline graph tests plus the startup check. `debug` builds and tests separately in
|
||||
`build/debug`. `gui` builds the existing placeholder shell; it does not produce
|
||||
audio. Generated files remain under `build` and header changes trigger rebuilds.
|
||||
Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation
|
||||
|
|
@ -49,9 +49,8 @@ are changed by these build commands.
|
|||
|
||||
## Next milestone
|
||||
|
||||
Phase 1 must replace insertion-order gain processing with a validated,
|
||||
topologically scheduled offline plan, preallocated buffers, oscillator, gain,
|
||||
meter and output nodes, and deterministic routing tests. The current gain
|
||||
prototype is not a real-time engine. Public project persistence declarations
|
||||
also remain unimplemented. Hardware playback and interactive GUI behavior are
|
||||
outside this baseline.
|
||||
Phase 1 implements compiled offline plans, preallocated buffers, oscillator,
|
||||
gain, meter/output nodes and routing tests; see AUDIO_ENGINE.md. Phase 2 adds
|
||||
a hardware backend and safe cross-thread plan publication/retirement. Public
|
||||
project persistence declarations remain unimplemented. Hardware playback and
|
||||
interactive GUI behavior are not verified by the offline tests.
|
||||
|
|
|
|||
26
docs/IDEA.md
26
docs/IDEA.md
|
|
@ -229,23 +229,23 @@ Example API shape (illustrative only; refine after reviewing the actual codebase
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct xt_app xt_app;
|
||||
typedef struct xtc_app xtc_app;
|
||||
|
||||
typedef enum {
|
||||
XT_OK = 0,
|
||||
XT_ERR_INVALID_ARGUMENT,
|
||||
XT_ERR_OUT_OF_MEMORY,
|
||||
XT_ERR_NOT_FOUND,
|
||||
XT_ERR_INVALID_STATE,
|
||||
XT_ERR_IO,
|
||||
XT_ERR_UNSUPPORTED
|
||||
} xt_result;
|
||||
XTC_OK = 0,
|
||||
XTC_ERR_INVALID_ARGUMENT,
|
||||
XTC_ERR_OUT_OF_MEMORY,
|
||||
XTC_ERR_NOT_FOUND,
|
||||
XTC_ERR_INVALID_STATE,
|
||||
XTC_ERR_IO,
|
||||
XTC_ERR_UNSUPPORTED
|
||||
} xtc_result;
|
||||
|
||||
xt_result xt_app_create(xt_app **out_app);
|
||||
void xt_app_destroy(xt_app *app);
|
||||
xtc_result xtc_app_create(xtc_app **out_app);
|
||||
void xtc_app_destroy(xtc_app *app);
|
||||
|
||||
xt_result xt_transport_play(xt_app *app);
|
||||
xt_result xt_transport_stop(xt_app *app);
|
||||
xtc_result xtc_transport_play(xtc_app *app);
|
||||
xtc_result xtc_transport_stop(xtc_app *app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ See [BUILD.md](BUILD.md) for toolchain, commands, licensing and limitations.
|
|||
Graph data structures, validation, cycle detection, topological ordering,
|
||||
preallocated buffers, test oscillator, gain and offline render tests.
|
||||
|
||||
Implemented: mono ports, transactional cycle rejection, independent compiled
|
||||
plans, precomputed input routing, oscillator/gain/meter/output, deterministic
|
||||
variable-block tests and lifecycle/error coverage. See AUDIO_ENGINE.md for API
|
||||
limits and compatibility behavior. Cross-thread publication remains Phase 2.
|
||||
|
||||
## Phase 2 — Real-time backend
|
||||
Choose and implement a Linux backend. Document callback semantics, start/stop,
|
||||
device errors, shutdown, and safe graph publication.
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ extern "C" {
|
|||
#define XTC_VERSION_MINOR 1
|
||||
#define XTC_VERSION_PATCH 0
|
||||
|
||||
typedef struct xt_app xt_app;
|
||||
typedef struct xtc_app xtc_app;
|
||||
|
||||
/* Creates an application context. On success, *out_app is caller-owned. */
|
||||
xt_result xt_app_create(xt_app **out_app);
|
||||
xtc_result xtc_app_create(xtc_app **out_app);
|
||||
|
||||
/* Destroys an application context; NULL is accepted. */
|
||||
void xt_app_destroy(xt_app *app);
|
||||
void xtc_app_destroy(xtc_app *app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,33 +7,81 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct xt_graph xt_graph;
|
||||
typedef struct xtc_graph xtc_graph;
|
||||
|
||||
typedef enum {
|
||||
XT_SIGNAL_AUDIO = 1,
|
||||
XT_SIGNAL_EVENT = 2,
|
||||
XT_SIGNAL_CONTROL = 3
|
||||
} xt_signal_type;
|
||||
XTC_SIGNAL_AUDIO = 1,
|
||||
XTC_SIGNAL_EVENT = 2,
|
||||
XTC_SIGNAL_CONTROL = 3
|
||||
} xtc_signal_type;
|
||||
|
||||
typedef enum {
|
||||
XT_PORT_INPUT = 1,
|
||||
XT_PORT_OUTPUT = 2
|
||||
} xt_port_direction;
|
||||
XTC_PORT_INPUT = 1,
|
||||
XTC_PORT_OUTPUT = 2
|
||||
} xtc_port_direction;
|
||||
|
||||
typedef struct {
|
||||
float *samples;
|
||||
size_t frame_count;
|
||||
size_t channel_count;
|
||||
} xt_audio_buffer;
|
||||
} xtc_audio_buffer;
|
||||
|
||||
/* Graph API is intentionally small in this first scaffold. */
|
||||
xt_result xt_graph_create(size_t max_nodes, xt_graph **out_graph);
|
||||
void xt_graph_destroy(xt_graph *graph);
|
||||
xt_result xt_graph_add_gain(xt_graph *graph, float gain, size_t *out_node_id);
|
||||
xt_result xt_graph_connect(xt_graph *graph, size_t source_node,
|
||||
typedef struct xtc_plan xtc_plan;
|
||||
|
||||
typedef enum {
|
||||
XTC_NODE_GAIN = 1,
|
||||
XTC_NODE_OSCILLATOR,
|
||||
XTC_NODE_METER,
|
||||
XTC_NODE_OUTPUT
|
||||
} xtc_node_type;
|
||||
|
||||
typedef struct {
|
||||
xtc_port_direction direction;
|
||||
xtc_signal_type signal_type;
|
||||
size_t channel_count;
|
||||
} xtc_port_info;
|
||||
|
||||
/* Control-thread API. Limits: 256 nodes, mono audio only. IDs are stable
|
||||
* insertion indexes. Output parameters are written only on success, except
|
||||
* create/compile which set their output pointer to NULL on failure. */
|
||||
xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph);
|
||||
void xtc_graph_destroy(xtc_graph *graph);
|
||||
xtc_result xtc_graph_add_gain(xtc_graph *graph, float gain, size_t *out_node_id);
|
||||
xtc_result xtc_graph_connect(xtc_graph *graph, size_t source_node,
|
||||
size_t destination_node);
|
||||
xt_result xt_graph_validate(const xt_graph *graph);
|
||||
xt_result xt_graph_process_offline(xt_graph *graph, const float *input,
|
||||
/* Port 0 is input; port 1 is output. Oscillators have only output 1;
|
||||
* output nodes have only input 0. All ports carry mono audio. */
|
||||
xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id,
|
||||
size_t port_id, xtc_port_info *out_port);
|
||||
xtc_result xtc_graph_connect_ports(xtc_graph *graph, size_t source_node,
|
||||
size_t source_port, size_t destination_node,
|
||||
size_t destination_port);
|
||||
xtc_result xtc_graph_add_oscillator(xtc_graph *graph, float frequency,
|
||||
size_t *out_node_id);
|
||||
xtc_result xtc_graph_add_meter(xtc_graph *graph, size_t *out_node_id);
|
||||
xtc_result xtc_graph_add_output(xtc_graph *graph, size_t *out_node_id);
|
||||
/* Compile copies graph state; caller owns the plan. Sample rate must be
|
||||
* finite and positive, oscillator frequencies <= Nyquist. max_frames: 1..65536.
|
||||
* Editing/destroying the graph never changes an existing plan. */
|
||||
xtc_result xtc_graph_compile(const xtc_graph *graph, double sample_rate,
|
||||
size_t max_frames, xtc_plan **out_plan);
|
||||
void xtc_plan_destroy(xtc_plan *plan);
|
||||
/* Single processing owner, no concurrent calls (including meter reads/reset).
|
||||
* No allocation, I/O, locking or graph validation. NULL input means silence.
|
||||
* output must hold frame_count floats; input may equal output. On invalid
|
||||
* arguments output and DSP state are unchanged. Zero frames is a no-op.
|
||||
* Sums explicit output nodes; if absent, sums terminal nodes for compatibility.
|
||||
* Unconnected gain/meter/output inputs receive external input. */
|
||||
xtc_result xtc_plan_process(xtc_plan *plan, const float *input, float *output,
|
||||
size_t frame_count);
|
||||
/* Resets oscillator phases and meters, without allocation. */
|
||||
xtc_result xtc_plan_reset(xtc_plan *plan);
|
||||
/* Last block absolute peak, valid for meter nodes only. */
|
||||
xtc_result xtc_plan_meter_peak(const xtc_plan *plan, size_t node_id, float *out_peak);
|
||||
xtc_result xtc_graph_validate(const xtc_graph *graph);
|
||||
/* Allocating compatibility helper: compiles at 48000 Hz and processes once.
|
||||
* Requires non-NULL input/output and finite input_gain. Not for callbacks. */
|
||||
xtc_result xtc_graph_process_offline(xtc_graph *graph, const float *input,
|
||||
float *output, size_t frame_count,
|
||||
float input_gain);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct xt_project xt_project;
|
||||
typedef struct xtc_project xtc_project;
|
||||
|
||||
/* Project persistence is not implemented in this scaffold yet. */
|
||||
xt_result xt_project_create(xt_project **out_project);
|
||||
void xt_project_destroy(xt_project *project);
|
||||
xt_result xt_project_save(const xt_project *project, const char *path);
|
||||
xt_result xt_project_load(xt_project *project, const char *path);
|
||||
xtc_result xtc_project_create(xtc_project **out_project);
|
||||
void xtc_project_destroy(xtc_project *project);
|
||||
xtc_result xtc_project_save(const xtc_project *project, const char *path);
|
||||
xtc_result xtc_project_load(xtc_project *project, const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
typedef enum {
|
||||
XT_OK = 0,
|
||||
XT_ERR_INVALID_ARGUMENT,
|
||||
XT_ERR_OUT_OF_MEMORY,
|
||||
XT_ERR_NOT_FOUND,
|
||||
XT_ERR_INVALID_STATE,
|
||||
XT_ERR_IO,
|
||||
XT_ERR_UNSUPPORTED,
|
||||
XT_ERR_CAPACITY,
|
||||
XT_ERR_GRAPH_CYCLE
|
||||
} xt_result;
|
||||
XTC_OK = 0,
|
||||
XTC_ERR_INVALID_ARGUMENT,
|
||||
XTC_ERR_OUT_OF_MEMORY,
|
||||
XTC_ERR_NOT_FOUND,
|
||||
XTC_ERR_INVALID_STATE,
|
||||
XTC_ERR_IO,
|
||||
XTC_ERR_UNSUPPORTED,
|
||||
XTC_ERR_CAPACITY,
|
||||
XTC_ERR_GRAPH_CYCLE
|
||||
} xtc_result;
|
||||
|
||||
const char *xt_result_string(xt_result result);
|
||||
const char *xtc_result_string(xtc_result result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,45 +2,45 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
|
||||
struct xt_app {
|
||||
struct xtc_app {
|
||||
unsigned int reserved;
|
||||
};
|
||||
|
||||
const char *xt_result_string(xt_result result)
|
||||
const char *xtc_result_string(xtc_result result)
|
||||
{
|
||||
switch (result) {
|
||||
case XT_OK: return "success";
|
||||
case XT_ERR_INVALID_ARGUMENT: return "invalid argument";
|
||||
case XT_ERR_OUT_OF_MEMORY: return "out of memory";
|
||||
case XT_ERR_NOT_FOUND: return "not found";
|
||||
case XT_ERR_INVALID_STATE: return "invalid state";
|
||||
case XT_ERR_IO: return "I/O error";
|
||||
case XT_ERR_UNSUPPORTED: return "unsupported operation";
|
||||
case XT_ERR_CAPACITY: return "capacity exceeded";
|
||||
case XT_ERR_GRAPH_CYCLE: return "graph contains a cycle";
|
||||
case XTC_OK: return "success";
|
||||
case XTC_ERR_INVALID_ARGUMENT: return "invalid argument";
|
||||
case XTC_ERR_OUT_OF_MEMORY: return "out of memory";
|
||||
case XTC_ERR_NOT_FOUND: return "not found";
|
||||
case XTC_ERR_INVALID_STATE: return "invalid state";
|
||||
case XTC_ERR_IO: return "I/O error";
|
||||
case XTC_ERR_UNSUPPORTED: return "unsupported operation";
|
||||
case XTC_ERR_CAPACITY: return "capacity exceeded";
|
||||
case XTC_ERR_GRAPH_CYCLE: return "graph contains a cycle";
|
||||
default: return "unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
xt_result xt_app_create(xt_app **out_app)
|
||||
xtc_result xtc_app_create(xtc_app **out_app)
|
||||
{
|
||||
xt_app *app;
|
||||
xtc_app *app;
|
||||
|
||||
if (out_app == NULL) {
|
||||
return XT_ERR_INVALID_ARGUMENT;
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_app = NULL;
|
||||
|
||||
app = (xt_app *)calloc(1U, sizeof(*app));
|
||||
app = (xtc_app *)calloc(1U, sizeof(*app));
|
||||
if (app == NULL) {
|
||||
return XT_ERR_OUT_OF_MEMORY;
|
||||
return XTC_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
*out_app = app;
|
||||
return XT_OK;
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
void xt_app_destroy(xt_app *app)
|
||||
void xtc_app_destroy(xtc_app *app)
|
||||
{
|
||||
free(app);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include <stddef.h>
|
||||
|
||||
/* Kept as a separately testable DSP primitive. */
|
||||
void xt_dsp_gain_process(const float *input, float *output,
|
||||
void xtc_dsp_gain_process(const float *input, float *output,
|
||||
size_t frame_count, float gain)
|
||||
{
|
||||
size_t frame;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,165 @@
|
|||
/*
|
||||
* Offline engine integration point.
|
||||
*
|
||||
* The first scaffold exposes xt_graph_process_offline() from graph.c. This
|
||||
* file is reserved for the next milestone: a compiled immutable graph plan,
|
||||
* preallocated node buffers, and block-based processing independent of audio
|
||||
* hardware. Keep hardware callbacks and real-time constraints out of this
|
||||
* initial offline implementation.
|
||||
*/
|
||||
int xt_offline_engine_translation_unit_anchor(void)
|
||||
#include "../graph/graph_internal.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define XTC_TAU 6.28318530717958647692
|
||||
|
||||
struct xtc_plan {
|
||||
size_t node_count, max_frames;
|
||||
xtc_node *nodes;
|
||||
size_t *order, *offsets, *sources;
|
||||
unsigned char *render;
|
||||
float *buffers, *peaks;
|
||||
double *phases;
|
||||
double sample_rate;
|
||||
float input_gain;
|
||||
};
|
||||
|
||||
void xtc_plan_destroy(xtc_plan *plan)
|
||||
{
|
||||
return 0;
|
||||
if (plan == NULL) return;
|
||||
free(plan->nodes);
|
||||
free(plan->order);
|
||||
free(plan->offsets);
|
||||
free(plan->sources);
|
||||
free(plan->render);
|
||||
free(plan->buffers);
|
||||
free(plan->peaks);
|
||||
free(plan->phases);
|
||||
free(plan);
|
||||
}
|
||||
|
||||
xtc_result xtc_graph_compile(const xtc_graph *graph, double sample_rate,
|
||||
size_t max_frames, xtc_plan **out_plan)
|
||||
{
|
||||
xtc_plan *plan;
|
||||
xtc_result result;
|
||||
size_t node, edge, cursor = 0U;
|
||||
int has_output = 0;
|
||||
if (out_plan == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
*out_plan = NULL;
|
||||
if (graph == NULL || !isfinite(sample_rate) || sample_rate <= 0.0 ||
|
||||
max_frames == 0U || max_frames > XTC_MAX_FRAMES) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (graph->node_count == 0U) return XTC_ERR_INVALID_STATE;
|
||||
for (node = 0U; node < graph->node_count; ++node) {
|
||||
if (graph->nodes[node].type == XTC_NODE_OSCILLATOR &&
|
||||
(double)graph->nodes[node].value > sample_rate * 0.5)
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (graph->nodes[node].type == XTC_NODE_OUTPUT) has_output = 1;
|
||||
}
|
||||
/* Fixed graph/frame limits bound all allocation products below. */
|
||||
plan = calloc(1U, sizeof(*plan));
|
||||
if (plan == NULL) return XTC_ERR_OUT_OF_MEMORY;
|
||||
plan->node_count = graph->node_count;
|
||||
plan->max_frames = max_frames;
|
||||
plan->sample_rate = sample_rate;
|
||||
plan->input_gain = 1.0f;
|
||||
plan->nodes = calloc(plan->node_count, sizeof(*plan->nodes));
|
||||
plan->order = calloc(plan->node_count, sizeof(*plan->order));
|
||||
plan->offsets = calloc(plan->node_count + 1U, sizeof(*plan->offsets));
|
||||
plan->sources = calloc(graph->edge_count + 1U, sizeof(*plan->sources));
|
||||
plan->render = calloc(plan->node_count, sizeof(*plan->render));
|
||||
plan->buffers = calloc(plan->node_count * max_frames, sizeof(*plan->buffers));
|
||||
plan->peaks = calloc(plan->node_count, sizeof(*plan->peaks));
|
||||
plan->phases = calloc(plan->node_count, sizeof(*plan->phases));
|
||||
if (plan->nodes == NULL || plan->order == NULL || plan->offsets == NULL ||
|
||||
plan->sources == NULL || plan->render == NULL || plan->buffers == NULL ||
|
||||
plan->peaks == NULL || plan->phases == NULL) {
|
||||
xtc_plan_destroy(plan);
|
||||
return XTC_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
result = xtc_graph_order(graph, plan->order);
|
||||
if (result != XTC_OK) { xtc_plan_destroy(plan); return result; }
|
||||
memcpy(plan->nodes, graph->nodes, plan->node_count * sizeof(*plan->nodes));
|
||||
for (node = 0U; node < plan->node_count; ++node) {
|
||||
int terminal = 1;
|
||||
plan->offsets[node] = cursor;
|
||||
for (edge = 0U; edge < graph->edge_count; ++edge) {
|
||||
if (graph->edges[edge].destination == node)
|
||||
plan->sources[cursor++] = graph->edges[edge].source;
|
||||
if (graph->edges[edge].source == node) terminal = 0;
|
||||
}
|
||||
plan->render[node] = (unsigned char)(has_output ?
|
||||
plan->nodes[node].type == XTC_NODE_OUTPUT : terminal);
|
||||
}
|
||||
plan->offsets[plan->node_count] = cursor;
|
||||
*out_plan = plan;
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
xtc_result xtc_plan_reset(xtc_plan *plan)
|
||||
{
|
||||
if (plan == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
memset(plan->phases, 0, plan->node_count * sizeof(*plan->phases));
|
||||
memset(plan->peaks, 0, plan->node_count * sizeof(*plan->peaks));
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
xtc_result xtc_plan_meter_peak(const xtc_plan *plan, size_t node_id, float *out_peak)
|
||||
{
|
||||
if (plan == NULL || out_peak == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (node_id >= plan->node_count) return XTC_ERR_NOT_FOUND;
|
||||
if (plan->nodes[node_id].type != XTC_NODE_METER) return XTC_ERR_INVALID_ARGUMENT;
|
||||
*out_peak = plan->peaks[node_id];
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
xtc_result xtc_plan_process(xtc_plan *plan, const float *input, float *output,
|
||||
size_t frame_count)
|
||||
{
|
||||
size_t index, frame, edge;
|
||||
if (plan == NULL || output == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (frame_count > plan->max_frames) return XTC_ERR_CAPACITY;
|
||||
if (frame_count == 0U) return XTC_OK;
|
||||
/* All external input reads finish before output is written, allowing aliasing.
|
||||
* Nonfinite input/arithmetic becomes silence per sample, never persistent DSP state. */
|
||||
for (index = 0U; index < plan->node_count; ++index) {
|
||||
size_t node = plan->order[index];
|
||||
xtc_node spec = plan->nodes[node];
|
||||
float *buffer = plan->buffers + node * plan->max_frames;
|
||||
float peak = 0.0f;
|
||||
double phase = plan->phases[node];
|
||||
for (frame = 0U; frame < frame_count; ++frame) {
|
||||
float sample = 0.0f;
|
||||
if (spec.type == XTC_NODE_OSCILLATOR) {
|
||||
sample = (float)sin(XTC_TAU * phase);
|
||||
phase += (double)spec.value / plan->sample_rate;
|
||||
if (phase >= 1.0) phase -= 1.0;
|
||||
} else {
|
||||
if (plan->offsets[node] == plan->offsets[node + 1U])
|
||||
sample = input == NULL ? 0.0f : input[frame] * plan->input_gain;
|
||||
for (edge = plan->offsets[node]; edge < plan->offsets[node + 1U]; ++edge)
|
||||
sample += plan->buffers[plan->sources[edge] * plan->max_frames + frame];
|
||||
if (spec.type == XTC_NODE_GAIN) sample *= spec.value;
|
||||
}
|
||||
if (!isfinite(sample)) sample = 0.0f;
|
||||
buffer[frame] = sample;
|
||||
if (fabsf(sample) > peak) peak = fabsf(sample);
|
||||
}
|
||||
plan->phases[node] = phase;
|
||||
if (spec.type == XTC_NODE_METER) plan->peaks[node] = peak;
|
||||
}
|
||||
for (frame = 0U; frame < frame_count; ++frame) {
|
||||
float sample = 0.0f;
|
||||
for (index = 0U; index < plan->node_count; ++index)
|
||||
if (plan->render[index]) sample += plan->buffers[index * plan->max_frames + frame];
|
||||
output[frame] = isfinite(sample) ? sample : 0.0f;
|
||||
}
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
xtc_result xtc_graph_process_offline(xtc_graph *graph, const float *input,
|
||||
float *output, size_t frame_count, float input_gain)
|
||||
{
|
||||
xtc_plan *plan = NULL;
|
||||
xtc_result result;
|
||||
if (input == NULL || output == NULL || !isfinite(input_gain))
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
result = xtc_graph_compile(graph, 48000.0, frame_count == 0U ? 1U : frame_count, &plan);
|
||||
if (result != XTC_OK) return result;
|
||||
plan->input_gain = input_gain;
|
||||
result = xtc_plan_process(plan, input, output, frame_count);
|
||||
xtc_plan_destroy(plan);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,27 @@
|
|||
#include "xtc/xtc_engine.h"
|
||||
|
||||
#include "graph_internal.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
size_t source;
|
||||
size_t destination;
|
||||
} xt_edge;
|
||||
|
||||
typedef struct {
|
||||
float gain;
|
||||
} xt_gain_node;
|
||||
|
||||
struct xt_graph {
|
||||
size_t max_nodes;
|
||||
size_t node_count;
|
||||
size_t edge_count;
|
||||
xt_gain_node *nodes;
|
||||
xt_edge *edges;
|
||||
};
|
||||
|
||||
xt_result xt_graph_create(size_t max_nodes, xt_graph **out_graph)
|
||||
xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph)
|
||||
{
|
||||
xt_graph *graph;
|
||||
|
||||
if (out_graph == NULL || max_nodes == 0U) {
|
||||
return XT_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
xtc_graph *graph;
|
||||
if (out_graph == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
*out_graph = NULL;
|
||||
|
||||
graph = (xt_graph *)calloc(1U, sizeof(*graph));
|
||||
if (graph == NULL) {
|
||||
return XT_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
graph->nodes = (xt_gain_node *)calloc(max_nodes, sizeof(*graph->nodes));
|
||||
graph->edges = (xt_edge *)calloc(max_nodes * max_nodes, sizeof(*graph->edges));
|
||||
if (max_nodes == 0U || max_nodes > XTC_MAX_NODES) return XTC_ERR_INVALID_ARGUMENT;
|
||||
graph = calloc(1U, sizeof(*graph));
|
||||
if (graph == NULL) return XTC_ERR_OUT_OF_MEMORY;
|
||||
graph->nodes = calloc(max_nodes, sizeof(*graph->nodes));
|
||||
graph->edges = calloc(max_nodes * max_nodes, sizeof(*graph->edges));
|
||||
if (graph->nodes == NULL || graph->edges == NULL) {
|
||||
free(graph->nodes);
|
||||
free(graph->edges);
|
||||
free(graph);
|
||||
return XT_ERR_OUT_OF_MEMORY;
|
||||
xtc_graph_destroy(graph);
|
||||
return XTC_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
graph->max_nodes = max_nodes;
|
||||
*out_graph = graph;
|
||||
return XT_OK;
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
void xt_graph_destroy(xt_graph *graph)
|
||||
void xtc_graph_destroy(xtc_graph *graph)
|
||||
{
|
||||
if (graph != NULL) {
|
||||
free(graph->nodes);
|
||||
|
|
@ -57,133 +30,93 @@ void xt_graph_destroy(xt_graph *graph)
|
|||
}
|
||||
}
|
||||
|
||||
xt_result xt_graph_add_gain(xt_graph *graph, float gain, size_t *out_node_id)
|
||||
static xtc_result add_node(xtc_graph *graph, xtc_node_type type, float value,
|
||||
size_t *out_node_id)
|
||||
{
|
||||
if (graph == NULL || out_node_id == NULL) {
|
||||
return XT_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (graph->node_count >= graph->max_nodes) {
|
||||
return XT_ERR_CAPACITY;
|
||||
}
|
||||
if (graph == NULL || out_node_id == NULL || !isfinite(value))
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (graph->node_count == graph->max_nodes) return XTC_ERR_CAPACITY;
|
||||
graph->nodes[graph->node_count].type = type;
|
||||
graph->nodes[graph->node_count].value = value;
|
||||
*out_node_id = graph->node_count++;
|
||||
return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_graph_add_gain(xtc_graph *graph, float gain, size_t *out_node_id)
|
||||
{ return add_node(graph, XTC_NODE_GAIN, gain, out_node_id); }
|
||||
xtc_result xtc_graph_add_oscillator(xtc_graph *graph, float frequency, size_t *out_node_id)
|
||||
{
|
||||
if (frequency < 0.0f) return XTC_ERR_INVALID_ARGUMENT;
|
||||
return add_node(graph, XTC_NODE_OSCILLATOR, frequency, out_node_id);
|
||||
}
|
||||
xtc_result xtc_graph_add_meter(xtc_graph *graph, size_t *out_node_id)
|
||||
{ return add_node(graph, XTC_NODE_METER, 0.0f, out_node_id); }
|
||||
xtc_result xtc_graph_add_output(xtc_graph *graph, size_t *out_node_id)
|
||||
{ return add_node(graph, XTC_NODE_OUTPUT, 0.0f, out_node_id); }
|
||||
|
||||
graph->nodes[graph->node_count].gain = gain;
|
||||
*out_node_id = graph->node_count;
|
||||
graph->node_count++;
|
||||
return XT_OK;
|
||||
xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id,
|
||||
size_t port_id, xtc_port_info *out_port)
|
||||
{
|
||||
xtc_node_type type;
|
||||
if (graph == NULL || out_port == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (node_id >= graph->node_count) return XTC_ERR_NOT_FOUND;
|
||||
type = graph->nodes[node_id].type;
|
||||
if (port_id > 1U || (port_id == 0U && type == XTC_NODE_OSCILLATOR) ||
|
||||
(port_id == 1U && type == XTC_NODE_OUTPUT)) return XTC_ERR_NOT_FOUND;
|
||||
out_port->direction = port_id == 0U ? XTC_PORT_INPUT : XTC_PORT_OUTPUT;
|
||||
out_port->signal_type = XTC_SIGNAL_AUDIO;
|
||||
out_port->channel_count = 1U;
|
||||
return XTC_OK;
|
||||
}
|
||||
|
||||
xt_result xt_graph_connect(xt_graph *graph, size_t source_node,
|
||||
size_t destination_node)
|
||||
xtc_result xtc_graph_order(const xtc_graph *graph, size_t *order)
|
||||
{
|
||||
size_t i;
|
||||
size_t edge_capacity;
|
||||
|
||||
if (graph == NULL) {
|
||||
return XT_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (source_node >= graph->node_count || destination_node >= graph->node_count) {
|
||||
return XT_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
edge_capacity = graph->max_nodes * graph->max_nodes;
|
||||
if (graph->edge_count >= edge_capacity) {
|
||||
return XT_ERR_CAPACITY;
|
||||
}
|
||||
for (i = 0U; i < graph->edge_count; i++) {
|
||||
if (graph->edges[i].source == source_node &&
|
||||
graph->edges[i].destination == destination_node) {
|
||||
return XT_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
graph->edges[graph->edge_count].source = source_node;
|
||||
graph->edges[graph->edge_count].destination = destination_node;
|
||||
graph->edge_count++;
|
||||
return XT_OK;
|
||||
}
|
||||
|
||||
xt_result xt_graph_validate(const xt_graph *graph)
|
||||
{
|
||||
size_t *indegree;
|
||||
size_t *queue;
|
||||
size_t head = 0U;
|
||||
size_t tail = 0U;
|
||||
size_t visited = 0U;
|
||||
size_t i;
|
||||
|
||||
if (graph == NULL) {
|
||||
return XT_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (graph->node_count == 0U) {
|
||||
return XT_OK;
|
||||
}
|
||||
|
||||
indegree = (size_t *)calloc(graph->node_count, sizeof(*indegree));
|
||||
queue = (size_t *)calloc(graph->node_count, sizeof(*queue));
|
||||
if (indegree == NULL || queue == NULL) {
|
||||
free(indegree);
|
||||
free(queue);
|
||||
return XT_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
for (i = 0U; i < graph->edge_count; i++) {
|
||||
if (graph->edges[i].source >= graph->node_count ||
|
||||
graph->edges[i].destination >= graph->node_count) {
|
||||
free(indegree);
|
||||
free(queue);
|
||||
return XT_ERR_INVALID_STATE;
|
||||
}
|
||||
indegree[graph->edges[i].destination]++;
|
||||
}
|
||||
|
||||
for (i = 0U; i < graph->node_count; i++) {
|
||||
if (indegree[i] == 0U) {
|
||||
queue[tail++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
size_t indegree[XTC_MAX_NODES] = {0U};
|
||||
size_t head = 0U, tail = 0U, i;
|
||||
if (graph == NULL || order == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||
for (i = 0U; i < graph->edge_count; ++i)
|
||||
++indegree[graph->edges[i].destination];
|
||||
for (i = 0U; i < graph->node_count; ++i)
|
||||
if (indegree[i] == 0U) order[tail++] = i;
|
||||
while (head < tail) {
|
||||
size_t node = queue[head++];
|
||||
visited++;
|
||||
for (i = 0U; i < graph->edge_count; i++) {
|
||||
size_t node = order[head++];
|
||||
for (i = 0U; i < graph->edge_count; ++i) {
|
||||
if (graph->edges[i].source == node) {
|
||||
size_t dest = graph->edges[i].destination;
|
||||
if (indegree[dest] > 0U) {
|
||||
indegree[dest]--;
|
||||
if (indegree[dest] == 0U) {
|
||||
queue[tail++] = dest;
|
||||
}
|
||||
}
|
||||
if (--indegree[dest] == 0U) order[tail++] = dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(indegree);
|
||||
free(queue);
|
||||
return visited == graph->node_count ? XT_OK : XT_ERR_GRAPH_CYCLE;
|
||||
return tail == graph->node_count ? XTC_OK : XTC_ERR_GRAPH_CYCLE;
|
||||
}
|
||||
|
||||
xt_result xt_graph_process_offline(xt_graph *graph, const float *input,
|
||||
float *output, size_t frame_count,
|
||||
float input_gain)
|
||||
xtc_result xtc_graph_validate(const xtc_graph *graph)
|
||||
{
|
||||
size_t i;
|
||||
float cumulative_gain = input_gain;
|
||||
|
||||
if (graph == NULL || input == NULL || output == NULL) {
|
||||
return XT_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (xt_graph_validate(graph) != XT_OK) {
|
||||
return XT_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Temporary starter behavior: apply gains in node insertion order.
|
||||
This is not yet graph-plan execution and is only for offline tests. */
|
||||
for (i = 0U; i < graph->node_count; i++) {
|
||||
cumulative_gain *= graph->nodes[i].gain;
|
||||
}
|
||||
for (i = 0U; i < frame_count; i++) {
|
||||
output[i] = input[i] * cumulative_gain;
|
||||
}
|
||||
return XT_OK;
|
||||
size_t order[XTC_MAX_NODES];
|
||||
return xtc_graph_order(graph, order);
|
||||
}
|
||||
|
||||
xtc_result xtc_graph_connect_ports(xtc_graph *graph, size_t source_node,
|
||||
size_t source_port, size_t destination_node,
|
||||
size_t destination_port)
|
||||
{
|
||||
xtc_port_info source, dest;
|
||||
size_t i;
|
||||
xtc_result result = xtc_graph_get_port(graph, source_node, source_port, &source);
|
||||
if (result != XTC_OK) return result;
|
||||
result = xtc_graph_get_port(graph, destination_node, destination_port, &dest);
|
||||
if (result != XTC_OK) return result;
|
||||
if (source.direction != XTC_PORT_OUTPUT || dest.direction != XTC_PORT_INPUT ||
|
||||
source.signal_type != dest.signal_type || source.channel_count != dest.channel_count)
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
for (i = 0U; i < graph->edge_count; ++i)
|
||||
if (graph->edges[i].source == source_node && graph->edges[i].destination == destination_node)
|
||||
return XTC_ERR_INVALID_STATE;
|
||||
if (graph->edge_count == graph->max_nodes * graph->max_nodes) return XTC_ERR_CAPACITY;
|
||||
graph->edges[graph->edge_count].source = source_node;
|
||||
graph->edges[graph->edge_count++].destination = destination_node;
|
||||
result = xtc_graph_validate(graph);
|
||||
if (result != XTC_OK) --graph->edge_count;
|
||||
return result;
|
||||
}
|
||||
xtc_result xtc_graph_connect(xtc_graph *graph, size_t source_node, size_t destination_node)
|
||||
{ return xtc_graph_connect_ports(graph, source_node, 1U, destination_node, 0U); }
|
||||
|
|
|
|||
10
src/main.c
10
src/main.c
|
|
@ -6,16 +6,16 @@
|
|||
|
||||
int main(void)
|
||||
{
|
||||
xt_app *app = NULL;
|
||||
xt_result result = xt_app_create(&app);
|
||||
xtc_app *app = NULL;
|
||||
xtc_result result = xtc_app_create(&app);
|
||||
|
||||
if (result != XT_OK) {
|
||||
if (result != XTC_OK) {
|
||||
(void)fprintf(stderr, "Application initialization failed: %s\n",
|
||||
xt_result_string(result));
|
||||
xtc_result_string(result));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
xt_app_destroy(app);
|
||||
xtc_app_destroy(app);
|
||||
if (printf("%s %s: core startup/shutdown OK\n",
|
||||
XTC_PROJECT_NAME, XTC_VERSION_STRING) < 0) {
|
||||
return EXIT_FAILURE;
|
||||
|
|
|
|||
|
|
@ -13,31 +13,31 @@
|
|||
|
||||
int main(void)
|
||||
{
|
||||
xt_app *app = NULL;
|
||||
xt_graph *graph = NULL;
|
||||
xtc_app *app = NULL;
|
||||
xtc_graph *graph = NULL;
|
||||
size_t node_a = 0U;
|
||||
size_t node_b = 0U;
|
||||
float input[4] = {1.0f, -1.0f, 0.5f, 0.0f};
|
||||
float output[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
CHECK(xt_app_create(&app) == XT_OK);
|
||||
CHECK(xtc_app_create(&app) == XTC_OK);
|
||||
CHECK(app != NULL);
|
||||
xt_app_destroy(app);
|
||||
xtc_app_destroy(app);
|
||||
|
||||
CHECK(xt_graph_create(4U, &graph) == XT_OK);
|
||||
CHECK(xt_graph_add_gain(graph, 0.5f, &node_a) == XT_OK);
|
||||
CHECK(xt_graph_add_gain(graph, 2.0f, &node_b) == XT_OK);
|
||||
CHECK(xt_graph_connect(graph, node_a, node_b) == XT_OK);
|
||||
CHECK(xt_graph_validate(graph) == XT_OK);
|
||||
CHECK(xt_graph_process_offline(graph, input, output, 4U, 1.0f) == XT_OK);
|
||||
CHECK(xtc_graph_create(4U, &graph) == XTC_OK);
|
||||
CHECK(xtc_graph_add_gain(graph, 0.5f, &node_a) == XTC_OK);
|
||||
CHECK(xtc_graph_add_gain(graph, 2.0f, &node_b) == XTC_OK);
|
||||
CHECK(xtc_graph_connect(graph, node_a, node_b) == XTC_OK);
|
||||
CHECK(xtc_graph_validate(graph) == XTC_OK);
|
||||
CHECK(xtc_graph_process_offline(graph, input, output, 4U, 1.0f) == XTC_OK);
|
||||
CHECK(fabsf(output[0] - 1.0f) < 0.0001f);
|
||||
CHECK(fabsf(output[1] + 1.0f) < 0.0001f);
|
||||
CHECK(fabsf(output[2] - 0.5f) < 0.0001f);
|
||||
|
||||
CHECK(xt_graph_connect(graph, node_b, node_a) == XT_OK);
|
||||
CHECK(xt_graph_validate(graph) == XT_ERR_GRAPH_CYCLE);
|
||||
CHECK(xtc_graph_connect(graph, node_b, node_a) == XTC_ERR_GRAPH_CYCLE);
|
||||
CHECK(xtc_graph_validate(graph) == XTC_OK);
|
||||
|
||||
xt_graph_destroy(graph);
|
||||
xtc_graph_destroy(graph);
|
||||
(void)puts("All xtc core tests passed.");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue