# Audio engine requirements ## Real-time callback rules The callback must not allocate/free memory, lock blocking mutexes, perform filesystem/network/console I/O, log, load plugins or samples, call GUI APIs, or wait for another thread. Use preallocated buffers and bounded work. Publish parameter and graph changes through a documented real-time-safe mechanism. Verify atomics are lock-free where required on the target platform. ## Processing model Process a bounded number of frames per block. Do not assume a fixed block size unless the backend guarantees it. Define maximum frame and event counts and safe behavior when those limits are reached. The initial milestone is offline processing, then a test oscillator -> gain -> meter/output graph, then a hardware backend. ## Clock and transport 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. The offline API remains single-owner. The realtime/backend APIs below add publication and JACK processing; offline tests alone do not qualify hardware. ## Phase 2 backend and realtime bridge Use `xtc_backend.h` on one serialized control thread: 1. Open a config with `XTC_BACKEND_JACK`, a client name, channel_count 1, and max_frames 1..65536. It must accommodate the server's current buffer size. The actual rate and buffer size are returned through get_status. Open does not start the server or acquire devices directly. Invalid config returns invalid argument, missing build support returns unsupported, and JACK setup failures return I/O error. If cleanup itself fails, a non-NULL output handle is preserved for close retry; no other operation is allowed on that handle. 2. Submit a graph (copied/compiled at the server rate), then start. Starting an already running client is an invalid-state error; stopping a stopped client succeeds. With no submitted graph, callbacks produce silence. 3. Enumerate available audio ports with visit_ports. Names are borrowed only during the visitor; do not reenter backend operations from the visitor. Connect the client's input to a remote output or its output to a remote input explicitly. JACK maps device selection to server/device port routes; ALSA device identifiers are not accepted here. The server controls rate, period and hardware configuration outside this API. Names/ports can disappear between enumeration and connection; route failures are returned as I/O errors. 4. During playback, submit edited graphs and periodically collect retired resources. Capacity means one pending graph is still awaiting adoption; keep the current graph and retry later. get_status is a nonblocking snapshot of rate, frames, running/server-loss/rate-change flags, xruns, processed blocks and last process result. No active DSP memory is exposed. 5. Stop pauses the retained DSP state after deactivation. Restart resumes it. Close removes the JACK client, synchronizes all callbacks, then destroys plans. Stop/close failures preserve the handle for recovery/retry. No plan is freed on the audio thread. Sample-rate change or server loss requires close/reopen and a fresh submission; there is no automatic reconnect. Buffers can change size. In-limit changes process normally; oversized blocks are silent and report capacity errors until the size is supported again. Rate changes latch silence and invalid-state processing results. A missing input buffer is treated as unavailable input (silence). A missing output buffer is reported as an I/O process error. Shutdown callbacks only set the loss flag; the control thread closes the client. No JACK or GUI types cross the C API. The standalone `xtc_realtime.h` API allows hardware-independent consumers and stress tests. Submit compiles on the producer; process adopts at a block boundary without allocation/free, I/O, locks or waits. All process calls must come from one consumer thread, and destruction requires that consumer stopped and joined. Release/acquire publication, retirement backpressure and atomic requirements are specified in decision 0004. Ordinary project processing and JACK notification callbacks were audited for prohibited operations; JACK's own internal realtime guarantees depend on the installed implementation/server. Validation commands and results are recorded in BUILD.md and the integration test README. Current limitations: mono input/output, no parameter ramping or crossfaded graph changes, no realtime meter bridge, no automatic reconnect, no physical-device or long-duration endurance qualification. GUI controls and status display are Phase 3; the GUI remains a placeholder shell.