Phase 2.
This commit is contained in:
parent
c0ca1ff396
commit
577d80f018
16 changed files with 1100 additions and 41 deletions
|
|
@ -29,7 +29,9 @@ 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.
|
||||
`xtc_realtime` now provides a one-pending/one-retired handoff around an
|
||||
exclusively audio-owned active plan. `xtc_backend` is the C backend boundary;
|
||||
the optional JACK implementation drives that same realtime engine.
|
||||
|
||||
## Ownership and threads
|
||||
|
||||
|
|
@ -38,5 +40,6 @@ Plan publication across threads and retirement remain Phase 2 work.
|
|||
- Audio thread owns the active processing plan and DSP state.
|
||||
- Backend callbacks follow the backend's documented threading contract.
|
||||
|
||||
Do not free an active plan until the audio thread has released it. Pick and
|
||||
document a bounded handoff/reclamation strategy before real-time graph swaps.
|
||||
Only acknowledged retired plans are reclaimed during processing. Backend close
|
||||
synchronizes callbacks before reclaiming remaining plans. See decision 0004
|
||||
for the release/acquire protocol, bounded resource retention and recovery.
|
||||
|
|
|
|||
|
|
@ -77,5 +77,59 @@ 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.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ make gui
|
|||
```
|
||||
|
||||
`all` builds the static C library and startup check. `test` runs the existing
|
||||
core and offline graph tests plus the startup check. `debug` builds and tests separately in
|
||||
core/offline tests, concurrent plan-handoff stress, JACK fault injection
|
||||
(when JACK headers are available), and 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
|
||||
|
|
@ -29,7 +30,7 @@ Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation
|
|||
| GNU Make | 4.4.1 | Build and test driver |
|
||||
| pkg-config | 3.0.10 | Development package discovery |
|
||||
| FLTK | 1.4.5 | Optional GUI; discovered through `fltk-config` |
|
||||
| JACK | 3.1609.0 (pkg-config) | Candidate backend; not linked yet |
|
||||
| JACK | 3.1609.0 (pkg-config) | Optional JACK backend (PipeWire compatibility library) |
|
||||
| ALSA | 1.2.16.1 (pkg-config) | Future MIDI candidate; not linked yet |
|
||||
| Clang | 23.1.3 | Alternative compiler / diagnostics |
|
||||
|
||||
|
|
@ -39,18 +40,44 @@ the Makefile tries pkg-config first, then `fltk-config`.
|
|||
|
||||
## Dependencies and licensing
|
||||
|
||||
No new dependencies are introduced. The core needs the C runtime and math
|
||||
library. The repository is MIT licensed. The optional GUI uses maintained
|
||||
The offline core needs the C runtime and math library. Phase 2 adds optional
|
||||
JACK support, selected with pkg-config; missing JACK does not prevent core/tests
|
||||
from building. Realtime publication uses GCC/Clang lock-free atomic builtins in
|
||||
C99. Tests also use POSIX threads. Backend consumers must link JACK explicitly;
|
||||
backend objects are pulled from the static library only when used. The repository is MIT licensed. The optional GUI uses maintained
|
||||
FLTK 1.4 (LGPL with FLTK's static-linking exception); its platform link flags
|
||||
come from the installed package. Distribution still requires review of all
|
||||
linked dependencies and notices. JACK and ALSA remain candidates only; backend
|
||||
selection and license review belong to Phase 2. No packages or system settings
|
||||
linked dependencies and notices. The installed PipeWire JACK library is MIT;
|
||||
traditional JACK client libraries use LGPL licensing. See decision 0004 for
|
||||
the dependency choice and caveats. ALSA remains a future MIDI candidate. No packages or system settings
|
||||
are changed by these build commands.
|
||||
|
||||
## Phase 2 verification
|
||||
|
||||
```sh
|
||||
make all test gui debug
|
||||
make test-jack
|
||||
# Extended live run after building the integration binary:
|
||||
./build/test_jack 30
|
||||
# Verify optional build without JACK metadata, in a separate directory:
|
||||
make BUILD_DIR=build/nojack PKG_CONFIG=false all test
|
||||
# Address/undefined sanitizers (non-realtime tests only):
|
||||
make BUILD_DIR=build/sanitize CC=clang CFLAGS='-O1 -g -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -fsanitize=address,undefined -fno-omit-frame-pointer' LDFLAGS='-fsanitize=address,undefined' all test
|
||||
# Thread sanitizer for publication/reclamation:
|
||||
make BUILD_DIR=build/tsan CC=cc CFLAGS='-O1 -g -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -fsanitize=thread' LDFLAGS='-fsanitize=thread' build/tsan/test_realtime
|
||||
./build/tsan/test_realtime
|
||||
```
|
||||
|
||||
Normal, sanitizer and no-JACK tests pass. ThreadSanitizer reports no race in
|
||||
2,000 concurrent submissions. The 30-second live loopback test passed at 48 kHz
|
||||
with zero xruns and ten restart cycles; see tests/integration/README.md. Live
|
||||
server termination/format changes were not performed on the shared server;
|
||||
fault-injection tests cover those notification paths. No packages installed
|
||||
or system/device configuration changed. Sanitizers are not enabled in the live
|
||||
callback qualification run.
|
||||
|
||||
## Next milestone
|
||||
|
||||
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.
|
||||
Phase 3 connects the FLTK shell to the backend C API for engine controls,
|
||||
status/error reporting and clean shutdown. Physical playback and endurance,
|
||||
project persistence and sequencing remain unverified/unimplemented as applicable.
|
||||
|
|
|
|||
|
|
@ -15,12 +15,19 @@ 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.
|
||||
limits and compatibility behavior.
|
||||
|
||||
## Phase 2 — Real-time backend
|
||||
Choose and implement a Linux backend. Document callback semantics, start/stop,
|
||||
device errors, shutdown, and safe graph publication.
|
||||
|
||||
Implemented: optional JACK-compatible mono backend, explicit audio port routing,
|
||||
format/xrun/loss status, start/stop/reopen, and bounded pending/retired plan
|
||||
handoff using lock-free atomics. Hardware-independent failure/concurrency tests
|
||||
and a 30-second software-loopback run pass. See decision 0004 and AUDIO_ENGINE.md.
|
||||
Physical hardware playback/endurance and live server-loss recovery remain
|
||||
unverified; no GUI engine integration until Phase 3.
|
||||
|
||||
## Phase 3 — FLTK shell
|
||||
Main window, transport controls, view switching, backend status, clean startup
|
||||
and shutdown through the C API.
|
||||
|
|
|
|||
84
docs/decisions/0004-jack-and-plan-handoff.md
Normal file
84
docs/decisions/0004-jack-and-plan-handoff.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Decision 0004: JACK backend and bounded plan handoff
|
||||
|
||||
**Status:** Accepted for Phase 2
|
||||
|
||||
## Backend
|
||||
|
||||
Use the JACK C client API. The inspected system provides PipeWire's maintained
|
||||
JACK compatibility library (`pkg-config jack` reports 3.1609.0), headers, a live
|
||||
server, and mono audio ports. This is JACK client integration, not native
|
||||
PipeWire or ALSA PCM. JACK owns device access, timing, sample rate and block
|
||||
size. The application registers one mono input/output pair and exposes port
|
||||
inspection and explicit routing. It neither starts the server nor changes its
|
||||
configuration or automatically connects hardware.
|
||||
|
||||
JACK support is optional at build time. Without JACK development metadata,
|
||||
backend open returns unsupported; offline and handoff tests still work.
|
||||
Consumers that call backend APIs link `pkg-config --libs jack` as well as `-lm`.
|
||||
No backend headers appear in public headers. The kind/configuration/status API
|
||||
is the boundary for adding another backend; there is no speculative plugin
|
||||
framework or second driver implementation.
|
||||
|
||||
The optional dependency provides actual audio callbacks and port routing.
|
||||
PipeWire's installed JACK library is MIT licensed (`pipewire-jack/COPYING`).
|
||||
Traditional JACK client libraries use LGPL licensing; review the actual linked
|
||||
implementation, transitive libraries and notices when distributing. Nothing
|
||||
is bundled or installed here. ALSA is still a future MIDI candidate.
|
||||
|
||||
## C99 and atomic operations
|
||||
|
||||
Retain C99 and use GCC/Clang `__atomic` builtins. This is a documented compiler
|
||||
extension, avoiding a language-standard change and keeping the existing core
|
||||
C99-compatible on the selected compilers. Initialization requires pointer and
|
||||
unsigned-int atomics to be always lock-free, or returns unsupported before
|
||||
callbacks are installed. Naturally aligned fields reside in calloc-allocated
|
||||
objects. There is no mutex or libatomic fallback in the callback path.
|
||||
|
||||
## Ownership and publication
|
||||
|
||||
One serialized control producer and one audio consumer share two pointer slots:
|
||||
|
||||
- `pending`: producer publishes a fully compiled plan with release; consumer
|
||||
exchanges it for NULL with acquire at the next eligible block boundary.
|
||||
- `retired`: consumer releases its previous active plan into this slot only
|
||||
after its final access. Producer exchanges with NULL using acquire/release,
|
||||
then destroys that acknowledged plan on the control thread.
|
||||
|
||||
The active pointer and all active DSP state belong exclusively to the consumer.
|
||||
Producer never reads them. Caller graph memory is copied before publication;
|
||||
callers never receive plan pointers through the realtime/backend API.
|
||||
|
||||
A full pending slot returns capacity without replacing anything. A full retired
|
||||
slot defers adoption while the current plan continues processing. The consumer
|
||||
performs at most one adoption per block and never loops waiting for the producer.
|
||||
Producer collection can overlap processing because the retired plan is no
|
||||
longer reachable from the consumer. Collection clears the slot before freeing;
|
||||
consumer may then retire another, distinct plan. Publication never immediately
|
||||
frees the previously active plan.
|
||||
|
||||
Submitting collects any already retired plan, so a producer repeatedly submitting
|
||||
also reclaims memory. If it becomes idle, one pending, one active, and one retired
|
||||
plan bound retained resources (at most three plans, each with up to 64 MiB of
|
||||
sample buffers). Periodic collect is recommended. Shutdown closes the JACK client
|
||||
and synchronizes its callbacks before destroying any active/pending/retired plan.
|
||||
Standalone realtime-engine callers must stop and join their consumer themselves.
|
||||
|
||||
## Status, errors and recovery
|
||||
|
||||
Unsigned-int status fields use lock-free atomics. Values are individual snapshots,
|
||||
not one transactional state. Counters wrap naturally; no string formatting,
|
||||
logging, allocation, GUI operation or blocking synchronization occurs in project
|
||||
callbacks. JACK's shutdown notification only stores a flag and leaves cleanup to
|
||||
the control thread. Other notifications store the current format and xrun count.
|
||||
|
||||
Buffer changes within the configured maximum require no allocation. Larger
|
||||
blocks are fully silenced and reported as capacity errors; smaller blocks can
|
||||
resume processing. Sample-rate changes latch a flag and silence processing
|
||||
rather than replaying an oscillator compiled for a different rate. Close/reopen
|
||||
and resubmit after a rate change or server loss. There is no automatic reconnect.
|
||||
JACK-deactivate pauses the retained plan; restart continues its oscillator phase.
|
||||
Plan replacement starts the newly compiled oscillator at phase zero, without
|
||||
crossfade. Smooth parameter ramps and click-free graph transitions are future work.
|
||||
|
||||
No stereo, recording, MIDI, native PipeWire API, GUI engine controls or hardware
|
||||
configuration dialogs are part of this phase.
|
||||
Loading…
Add table
Add a link
Reference in a new issue