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
|
$(BUILD_DIR)/test_core: $(TEST_OBJECT) $(BUILD_DIR)/libxtc.a
|
||||||
$(CC) $(LDFLAGS) -o $@ $(TEST_OBJECT) $(BUILD_DIR)/libxtc.a -lm
|
$(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_core
|
||||||
|
./$(BUILD_DIR)/test_offline
|
||||||
./$(BUILD_DIR)/xtc-check
|
./$(BUILD_DIR)/xtc-check
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
|
|
@ -75,5 +79,5 @@ install: all
|
||||||
clean:
|
clean:
|
||||||
rm -rf "$(BUILD_DIR)"
|
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
|
$(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:
|
This repository is a starter scaffold, not a production DAW. It currently provides:
|
||||||
- a small C library/API;
|
- a small C library/API;
|
||||||
- a minimal graph model with validation and topological ordering;
|
- a mono graph model with port validation, cycle rejection and topological ordering;
|
||||||
- a gain processor and offline audio-block processing;
|
- compiled offline plans with preallocated buffers, sine oscillator, gain, meter and output nodes;
|
||||||
- a C unit test;
|
- C tests for deterministic rendering, routing, validation and lifecycle;
|
||||||
- an optional FLTK GUI shell;
|
- an optional FLTK GUI shell;
|
||||||
- architecture, format, GUI, roadmap, and decision documents.
|
- 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
|
resources, and publish a prepared immutable execution plan. The audio thread
|
||||||
must not traverse a graph being edited.
|
must not traverse a graph being edited.
|
||||||
|
|
||||||
The current scaffold is deliberately incomplete: `xt_graph_process_offline()`
|
The offline engine now copies a validated graph into a prepared plan with
|
||||||
validates the graph and applies gain nodes in insertion order. It is a test
|
stable node IDs, topological order, precomputed input adjacency, per-node mono
|
||||||
placeholder, not the final graph execution algorithm.
|
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
|
## 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.
|
rational timeline representations to avoid cumulative floating-point drift.
|
||||||
Define tempo-map conversion and behavior at tempo changes before implementing
|
Define tempo-map conversion and behavior at tempo changes before implementing
|
||||||
tempo automation.
|
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
|
`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
|
`build/debug`. `gui` builds the existing placeholder shell; it does not produce
|
||||||
audio. Generated files remain under `build` and header changes trigger rebuilds.
|
audio. Generated files remain under `build` and header changes trigger rebuilds.
|
||||||
Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation
|
Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation
|
||||||
|
|
@ -49,9 +49,8 @@ are changed by these build commands.
|
||||||
|
|
||||||
## Next milestone
|
## Next milestone
|
||||||
|
|
||||||
Phase 1 must replace insertion-order gain processing with a validated,
|
Phase 1 implements compiled offline plans, preallocated buffers, oscillator,
|
||||||
topologically scheduled offline plan, preallocated buffers, oscillator, gain,
|
gain, meter/output nodes and routing tests; see AUDIO_ENGINE.md. Phase 2 adds
|
||||||
meter and output nodes, and deterministic routing tests. The current gain
|
a hardware backend and safe cross-thread plan publication/retirement. Public
|
||||||
prototype is not a real-time engine. Public project persistence declarations
|
project persistence declarations remain unimplemented. Hardware playback and
|
||||||
also remain unimplemented. Hardware playback and interactive GUI behavior are
|
interactive GUI behavior are not verified by the offline tests.
|
||||||
outside this baseline.
|
|
||||||
|
|
|
||||||
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" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct xt_app xt_app;
|
typedef struct xtc_app xtc_app;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
XT_OK = 0,
|
XTC_OK = 0,
|
||||||
XT_ERR_INVALID_ARGUMENT,
|
XTC_ERR_INVALID_ARGUMENT,
|
||||||
XT_ERR_OUT_OF_MEMORY,
|
XTC_ERR_OUT_OF_MEMORY,
|
||||||
XT_ERR_NOT_FOUND,
|
XTC_ERR_NOT_FOUND,
|
||||||
XT_ERR_INVALID_STATE,
|
XTC_ERR_INVALID_STATE,
|
||||||
XT_ERR_IO,
|
XTC_ERR_IO,
|
||||||
XT_ERR_UNSUPPORTED
|
XTC_ERR_UNSUPPORTED
|
||||||
} xt_result;
|
} xtc_result;
|
||||||
|
|
||||||
xt_result xt_app_create(xt_app **out_app);
|
xtc_result xtc_app_create(xtc_app **out_app);
|
||||||
void xt_app_destroy(xt_app *app);
|
void xtc_app_destroy(xtc_app *app);
|
||||||
|
|
||||||
xt_result xt_transport_play(xt_app *app);
|
xtc_result xtc_transport_play(xtc_app *app);
|
||||||
xt_result xt_transport_stop(xt_app *app);
|
xtc_result xtc_transport_stop(xtc_app *app);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#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,
|
Graph data structures, validation, cycle detection, topological ordering,
|
||||||
preallocated buffers, test oscillator, gain and offline render tests.
|
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
|
## Phase 2 — Real-time backend
|
||||||
Choose and implement a Linux backend. Document callback semantics, start/stop,
|
Choose and implement a Linux backend. Document callback semantics, start/stop,
|
||||||
device errors, shutdown, and safe graph publication.
|
device errors, shutdown, and safe graph publication.
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ extern "C" {
|
||||||
#define XTC_VERSION_MINOR 1
|
#define XTC_VERSION_MINOR 1
|
||||||
#define XTC_VERSION_PATCH 0
|
#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. */
|
/* 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. */
|
/* Destroys an application context; NULL is accepted. */
|
||||||
void xt_app_destroy(xt_app *app);
|
void xtc_app_destroy(xtc_app *app);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,81 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct xt_graph xt_graph;
|
typedef struct xtc_graph xtc_graph;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
XT_SIGNAL_AUDIO = 1,
|
XTC_SIGNAL_AUDIO = 1,
|
||||||
XT_SIGNAL_EVENT = 2,
|
XTC_SIGNAL_EVENT = 2,
|
||||||
XT_SIGNAL_CONTROL = 3
|
XTC_SIGNAL_CONTROL = 3
|
||||||
} xt_signal_type;
|
} xtc_signal_type;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
XT_PORT_INPUT = 1,
|
XTC_PORT_INPUT = 1,
|
||||||
XT_PORT_OUTPUT = 2
|
XTC_PORT_OUTPUT = 2
|
||||||
} xt_port_direction;
|
} xtc_port_direction;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
float *samples;
|
float *samples;
|
||||||
size_t frame_count;
|
size_t frame_count;
|
||||||
size_t channel_count;
|
size_t channel_count;
|
||||||
} xt_audio_buffer;
|
} xtc_audio_buffer;
|
||||||
|
|
||||||
/* Graph API is intentionally small in this first scaffold. */
|
typedef struct xtc_plan xtc_plan;
|
||||||
xt_result xt_graph_create(size_t max_nodes, xt_graph **out_graph);
|
|
||||||
void xt_graph_destroy(xt_graph *graph);
|
typedef enum {
|
||||||
xt_result xt_graph_add_gain(xt_graph *graph, float gain, size_t *out_node_id);
|
XTC_NODE_GAIN = 1,
|
||||||
xt_result xt_graph_connect(xt_graph *graph, size_t source_node,
|
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);
|
size_t destination_node);
|
||||||
xt_result xt_graph_validate(const xt_graph *graph);
|
/* Port 0 is input; port 1 is output. Oscillators have only output 1;
|
||||||
xt_result xt_graph_process_offline(xt_graph *graph, const float *input,
|
* 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 *output, size_t frame_count,
|
||||||
float input_gain);
|
float input_gain);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct xt_project xt_project;
|
typedef struct xtc_project xtc_project;
|
||||||
|
|
||||||
/* Project persistence is not implemented in this scaffold yet. */
|
/* Project persistence is not implemented in this scaffold yet. */
|
||||||
xt_result xt_project_create(xt_project **out_project);
|
xtc_result xtc_project_create(xtc_project **out_project);
|
||||||
void xt_project_destroy(xt_project *project);
|
void xtc_project_destroy(xtc_project *project);
|
||||||
xt_result xt_project_save(const xt_project *project, const char *path);
|
xtc_result xtc_project_save(const xtc_project *project, const char *path);
|
||||||
xt_result xt_project_load(xt_project *project, const char *path);
|
xtc_result xtc_project_load(xtc_project *project, const char *path);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,18 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
XT_OK = 0,
|
XTC_OK = 0,
|
||||||
XT_ERR_INVALID_ARGUMENT,
|
XTC_ERR_INVALID_ARGUMENT,
|
||||||
XT_ERR_OUT_OF_MEMORY,
|
XTC_ERR_OUT_OF_MEMORY,
|
||||||
XT_ERR_NOT_FOUND,
|
XTC_ERR_NOT_FOUND,
|
||||||
XT_ERR_INVALID_STATE,
|
XTC_ERR_INVALID_STATE,
|
||||||
XT_ERR_IO,
|
XTC_ERR_IO,
|
||||||
XT_ERR_UNSUPPORTED,
|
XTC_ERR_UNSUPPORTED,
|
||||||
XT_ERR_CAPACITY,
|
XTC_ERR_CAPACITY,
|
||||||
XT_ERR_GRAPH_CYCLE
|
XTC_ERR_GRAPH_CYCLE
|
||||||
} xt_result;
|
} xtc_result;
|
||||||
|
|
||||||
const char *xt_result_string(xt_result result);
|
const char *xtc_result_string(xtc_result result);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,45 @@
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
struct xt_app {
|
struct xtc_app {
|
||||||
unsigned int reserved;
|
unsigned int reserved;
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *xt_result_string(xt_result result)
|
const char *xtc_result_string(xtc_result result)
|
||||||
{
|
{
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case XT_OK: return "success";
|
case XTC_OK: return "success";
|
||||||
case XT_ERR_INVALID_ARGUMENT: return "invalid argument";
|
case XTC_ERR_INVALID_ARGUMENT: return "invalid argument";
|
||||||
case XT_ERR_OUT_OF_MEMORY: return "out of memory";
|
case XTC_ERR_OUT_OF_MEMORY: return "out of memory";
|
||||||
case XT_ERR_NOT_FOUND: return "not found";
|
case XTC_ERR_NOT_FOUND: return "not found";
|
||||||
case XT_ERR_INVALID_STATE: return "invalid state";
|
case XTC_ERR_INVALID_STATE: return "invalid state";
|
||||||
case XT_ERR_IO: return "I/O error";
|
case XTC_ERR_IO: return "I/O error";
|
||||||
case XT_ERR_UNSUPPORTED: return "unsupported operation";
|
case XTC_ERR_UNSUPPORTED: return "unsupported operation";
|
||||||
case XT_ERR_CAPACITY: return "capacity exceeded";
|
case XTC_ERR_CAPACITY: return "capacity exceeded";
|
||||||
case XT_ERR_GRAPH_CYCLE: return "graph contains a cycle";
|
case XTC_ERR_GRAPH_CYCLE: return "graph contains a cycle";
|
||||||
default: return "unknown error";
|
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) {
|
if (out_app == NULL) {
|
||||||
return XT_ERR_INVALID_ARGUMENT;
|
return XTC_ERR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
*out_app = NULL;
|
*out_app = NULL;
|
||||||
|
|
||||||
app = (xt_app *)calloc(1U, sizeof(*app));
|
app = (xtc_app *)calloc(1U, sizeof(*app));
|
||||||
if (app == NULL) {
|
if (app == NULL) {
|
||||||
return XT_ERR_OUT_OF_MEMORY;
|
return XTC_ERR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_app = app;
|
*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);
|
free(app);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
/* Kept as a separately testable DSP primitive. */
|
/* 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_count, float gain)
|
||||||
{
|
{
|
||||||
size_t frame;
|
size_t frame;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,165 @@
|
||||||
/*
|
#include "../graph/graph_internal.h"
|
||||||
* Offline engine integration point.
|
#include <math.h>
|
||||||
*
|
#include <stdlib.h>
|
||||||
* The first scaffold exposes xt_graph_process_offline() from graph.c. This
|
#include <string.h>
|
||||||
* file is reserved for the next milestone: a compiled immutable graph plan,
|
|
||||||
* preallocated node buffers, and block-based processing independent of audio
|
#define XTC_TAU 6.28318530717958647692
|
||||||
* hardware. Keep hardware callbacks and real-time constraints out of this
|
|
||||||
* initial offline implementation.
|
struct xtc_plan {
|
||||||
*/
|
size_t node_count, max_frames;
|
||||||
int xt_offline_engine_translation_unit_anchor(void)
|
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 <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
typedef struct {
|
xtc_result xtc_graph_create(size_t max_nodes, xtc_graph **out_graph)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
xt_graph *graph;
|
xtc_graph *graph;
|
||||||
|
if (out_graph == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||||
if (out_graph == NULL || max_nodes == 0U) {
|
|
||||||
return XT_ERR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
*out_graph = NULL;
|
*out_graph = NULL;
|
||||||
|
if (max_nodes == 0U || max_nodes > XTC_MAX_NODES) return XTC_ERR_INVALID_ARGUMENT;
|
||||||
graph = (xt_graph *)calloc(1U, sizeof(*graph));
|
graph = calloc(1U, sizeof(*graph));
|
||||||
if (graph == NULL) {
|
if (graph == NULL) return XTC_ERR_OUT_OF_MEMORY;
|
||||||
return XT_ERR_OUT_OF_MEMORY;
|
graph->nodes = calloc(max_nodes, sizeof(*graph->nodes));
|
||||||
}
|
graph->edges = calloc(max_nodes * max_nodes, sizeof(*graph->edges));
|
||||||
|
|
||||||
graph->nodes = (xt_gain_node *)calloc(max_nodes, sizeof(*graph->nodes));
|
|
||||||
graph->edges = (xt_edge *)calloc(max_nodes * max_nodes, sizeof(*graph->edges));
|
|
||||||
if (graph->nodes == NULL || graph->edges == NULL) {
|
if (graph->nodes == NULL || graph->edges == NULL) {
|
||||||
free(graph->nodes);
|
xtc_graph_destroy(graph);
|
||||||
free(graph->edges);
|
return XTC_ERR_OUT_OF_MEMORY;
|
||||||
free(graph);
|
|
||||||
return XT_ERR_OUT_OF_MEMORY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
graph->max_nodes = max_nodes;
|
graph->max_nodes = max_nodes;
|
||||||
*out_graph = graph;
|
*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) {
|
if (graph != NULL) {
|
||||||
free(graph->nodes);
|
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) {
|
if (graph == NULL || out_node_id == NULL || !isfinite(value))
|
||||||
return XT_ERR_INVALID_ARGUMENT;
|
return XTC_ERR_INVALID_ARGUMENT;
|
||||||
}
|
if (graph->node_count == graph->max_nodes) return XTC_ERR_CAPACITY;
|
||||||
if (graph->node_count >= graph->max_nodes) {
|
graph->nodes[graph->node_count].type = type;
|
||||||
return XT_ERR_CAPACITY;
|
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;
|
xtc_result xtc_graph_get_port(const xtc_graph *graph, size_t node_id,
|
||||||
*out_node_id = graph->node_count;
|
size_t port_id, xtc_port_info *out_port)
|
||||||
graph->node_count++;
|
{
|
||||||
return XT_OK;
|
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,
|
xtc_result xtc_graph_order(const xtc_graph *graph, size_t *order)
|
||||||
size_t destination_node)
|
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t indegree[XTC_MAX_NODES] = {0U};
|
||||||
size_t edge_capacity;
|
size_t head = 0U, tail = 0U, i;
|
||||||
|
if (graph == NULL || order == NULL) return XTC_ERR_INVALID_ARGUMENT;
|
||||||
if (graph == NULL) {
|
for (i = 0U; i < graph->edge_count; ++i)
|
||||||
return XT_ERR_INVALID_ARGUMENT;
|
++indegree[graph->edges[i].destination];
|
||||||
}
|
for (i = 0U; i < graph->node_count; ++i)
|
||||||
if (source_node >= graph->node_count || destination_node >= graph->node_count) {
|
if (indegree[i] == 0U) order[tail++] = i;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (head < tail) {
|
while (head < tail) {
|
||||||
size_t node = queue[head++];
|
size_t node = order[head++];
|
||||||
visited++;
|
for (i = 0U; i < graph->edge_count; ++i) {
|
||||||
for (i = 0U; i < graph->edge_count; i++) {
|
|
||||||
if (graph->edges[i].source == node) {
|
if (graph->edges[i].source == node) {
|
||||||
size_t dest = graph->edges[i].destination;
|
size_t dest = graph->edges[i].destination;
|
||||||
if (indegree[dest] > 0U) {
|
if (--indegree[dest] == 0U) order[tail++] = dest;
|
||||||
indegree[dest]--;
|
|
||||||
if (indegree[dest] == 0U) {
|
|
||||||
queue[tail++] = dest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return tail == graph->node_count ? XTC_OK : XTC_ERR_GRAPH_CYCLE;
|
||||||
free(indegree);
|
|
||||||
free(queue);
|
|
||||||
return visited == graph->node_count ? XT_OK : XT_ERR_GRAPH_CYCLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xt_result xt_graph_process_offline(xt_graph *graph, const float *input,
|
xtc_result xtc_graph_validate(const xtc_graph *graph)
|
||||||
float *output, size_t frame_count,
|
|
||||||
float input_gain)
|
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t order[XTC_MAX_NODES];
|
||||||
float cumulative_gain = input_gain;
|
return xtc_graph_order(graph, order);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
int main(void)
|
||||||
{
|
{
|
||||||
xt_app *app = NULL;
|
xtc_app *app = NULL;
|
||||||
xt_result result = xt_app_create(&app);
|
xtc_result result = xtc_app_create(&app);
|
||||||
|
|
||||||
if (result != XT_OK) {
|
if (result != XTC_OK) {
|
||||||
(void)fprintf(stderr, "Application initialization failed: %s\n",
|
(void)fprintf(stderr, "Application initialization failed: %s\n",
|
||||||
xt_result_string(result));
|
xtc_result_string(result));
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
xt_app_destroy(app);
|
xtc_app_destroy(app);
|
||||||
if (printf("%s %s: core startup/shutdown OK\n",
|
if (printf("%s %s: core startup/shutdown OK\n",
|
||||||
XTC_PROJECT_NAME, XTC_VERSION_STRING) < 0) {
|
XTC_PROJECT_NAME, XTC_VERSION_STRING) < 0) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
|
||||||
|
|
@ -13,31 +13,31 @@
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
xt_app *app = NULL;
|
xtc_app *app = NULL;
|
||||||
xt_graph *graph = NULL;
|
xtc_graph *graph = NULL;
|
||||||
size_t node_a = 0U;
|
size_t node_a = 0U;
|
||||||
size_t node_b = 0U;
|
size_t node_b = 0U;
|
||||||
float input[4] = {1.0f, -1.0f, 0.5f, 0.0f};
|
float input[4] = {1.0f, -1.0f, 0.5f, 0.0f};
|
||||||
float output[4] = {0.0f, 0.0f, 0.0f, 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);
|
CHECK(app != NULL);
|
||||||
xt_app_destroy(app);
|
xtc_app_destroy(app);
|
||||||
|
|
||||||
CHECK(xt_graph_create(4U, &graph) == XT_OK);
|
CHECK(xtc_graph_create(4U, &graph) == XTC_OK);
|
||||||
CHECK(xt_graph_add_gain(graph, 0.5f, &node_a) == XT_OK);
|
CHECK(xtc_graph_add_gain(graph, 0.5f, &node_a) == XTC_OK);
|
||||||
CHECK(xt_graph_add_gain(graph, 2.0f, &node_b) == XT_OK);
|
CHECK(xtc_graph_add_gain(graph, 2.0f, &node_b) == XTC_OK);
|
||||||
CHECK(xt_graph_connect(graph, node_a, node_b) == XT_OK);
|
CHECK(xtc_graph_connect(graph, node_a, node_b) == XTC_OK);
|
||||||
CHECK(xt_graph_validate(graph) == XT_OK);
|
CHECK(xtc_graph_validate(graph) == XTC_OK);
|
||||||
CHECK(xt_graph_process_offline(graph, input, output, 4U, 1.0f) == XT_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[0] - 1.0f) < 0.0001f);
|
||||||
CHECK(fabsf(output[1] + 1.0f) < 0.0001f);
|
CHECK(fabsf(output[1] + 1.0f) < 0.0001f);
|
||||||
CHECK(fabsf(output[2] - 0.5f) < 0.0001f);
|
CHECK(fabsf(output[2] - 0.5f) < 0.0001f);
|
||||||
|
|
||||||
CHECK(xt_graph_connect(graph, node_b, node_a) == XT_OK);
|
CHECK(xtc_graph_connect(graph, node_b, node_a) == XTC_ERR_GRAPH_CYCLE);
|
||||||
CHECK(xt_graph_validate(graph) == XT_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.");
|
(void)puts("All xtc core tests passed.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue