xtc/docs/IDEA.md

879 lines
31 KiB
Markdown

# Project Brief: A Modern, Lightweight, energyXT-Inspired DAW
**Document purpose:** This file is the authoritative project brief and
implementation guide for an AI coding agent working inside an IDE.
**Working project name:** `xtc` (temporary; keep the project name configurable
and do not hard-code it into public APIs).
**Primary goal:** Build a modern, reliable, lightweight digital audio
workstation inspired by the workflow and modular philosophy of energyXT. This is
a new implementation, not a clone of energyXT's proprietary code or assets.
**Architecture decision:** **B — Modular engine first.** The sequencer,
instruments, effects, mixers, audio inputs, and outputs are components in one
unified processing graph. The default user experience should still be a simple
sequencer-first workspace; users can open the modular view when they need to
inspect or edit signal flow.
**Language decision:**
- **100% C for the audio engine and core domain logic.**
- **FLTK with C++ for the graphical user interface only.**
- The GUI talks to the core through a small, documented C API.
- Do not introduce C++ into the audio engine or core library.
- Keep dependencies small, prefer open-source components, and avoid unnecessary
frameworks.
---
## 1. Instructions to the AI Coding Agent
Treat this document as the project contract. Follow it in every implementation
task.
1. Read this file and the existing project before making changes.
2. Inspect the current build system, source tree, and tests. Do not assume the
repository is empty.
3. Implement work in small, buildable increments. Each increment must compile
and have relevant tests before proceeding.
4. Prefer complete, coherent file updates over partial snippets. Do not silently
remove existing functionality.
5. Before editing, state briefly what you intend to change and which files are
affected. Then make the changes.
6. Do not invent APIs, FLTK features, audio backend capabilities, or dependency
behavior. Check installed headers and official documentation when uncertain.
7. Keep C source compatible with **C99** initially. Use C11 features only when
explicitly justified and supported by the selected compiler/toolchain.
8. Use C snake_case identifiers. Use clear names; avoid cryptic one-letter names
except conventional loop counters.
9. Validate external input, lengths, indexes, counts, enum values, and
allocation sizes. Handle errors explicitly.
10. Do not hide failures, silently clamp invalid data, or continue with
partially initialized objects.
11. Avoid unnecessary dependencies, generated-code systems, global mutable
state, and elaborate abstractions.
12. Never perform audio-thread-unsafe operations from the real-time callback.
See the real-time rules below.
13. Keep the project usable on Linux first. Do not make Linux-specific details
leak into the public core API.
14. Add or update tests and documentation with each feature.
15. At the end of a task, report: files changed, behavior added, build/test
commands run, results, known limitations, and the next sensible step.
16. If a requirement is ambiguous, choose the smallest safe implementation
consistent with this document and explain the assumption. Ask a question
only if the ambiguity blocks safe progress.
17. Do not claim that a feature works unless it has been implemented and tested.
Clearly distinguish a prototype, stub, and finished feature.
18. Never copy energyXT source code, proprietary resources, UI artwork, or
undocumented internal implementation. Use energyXT only as inspiration for
high-level workflow ideas.
### Definition of done for every task
A task is complete only when:
- the code builds with warnings enabled;
- relevant tests pass;
- error paths are considered;
- resource ownership and cleanup are clear;
- documentation is updated where behavior or APIs changed;
- no unrelated functionality was removed;
- the agent reports any untested behavior honestly.
---
## 2. Product Principles
The application should feel like a small, responsive music instrument rather
than a general-purpose content-creation suite.
### Core principles
- **Simple by default, powerful when opened up.**
- A single unified component graph underlies the application.
- The sequencer, mixer, instruments, effects, audio I/O, and utility processors
use a common node model.
- Common operations should require few clicks and avoid modal dialogs when
practical.
- The modular graph should expose real signal flow, not be a decorative diagram.
- The project must remain understandable to a human developer.
- Audio reliability is more important than visual effects or feature count.
- Favor predictable behavior over clever implicit behavior.
- Use non-destructive editing where practical.
- Avoid feature bloat. Every feature must justify its complexity.
- Save files should be versioned and recoverable.
- The application should remain useful at every development milestone.
### Explicit non-goals for the first release
Do not initially implement:
- video, notation, collaboration, cloud accounts, or online services;
- an integrated marketplace;
- scripting as a requirement for basic operation;
- dozens of bundled instruments or effects;
- advanced surround/spatial audio;
- distributed processing;
- a complicated theme system;
- an elaborate plugin sandbox;
- automatic online update services;
- a proprietary project format that cannot be inspected or migrated.
These may be considered later, but they must not delay the first reliable
recording and playback workflow.
---
## 3. High-Level Architecture
Use distinct layers with narrow interfaces.
```text
+------------------------------------------------------------+
| FLTK / C++ GUI |
| Main window, transport, sequencer, mixer, modular editor, |
| dialogs, custom drawing, keyboard and mouse interaction |
+----------------------------+-------------------------------+
|
documented C API
|
+----------------------------v-------------------------------+
| C application core |
| Project model, commands/undo, transport state, persistence, |
| parameter model, asset references, validation |
+----------------------------+-------------------------------+
|
real-time-safe bridge
|
+----------------------------v-------------------------------+
| C audio engine |
| Graph compilation, node processing, scheduling, buffers, |
| transport sample position, MIDI/event delivery, metering |
+----------------------------+-------------------------------+
|
backend abstraction
|
+----------------------------v-------------------------------+
| Audio/MIDI backend(s): start with JACK-compatible audio |
| and ALSA MIDI on Linux; add backends only when justified |
+------------------------------------------------------------+
```
The C core and audio engine may be separate libraries or clearly separated
modules within one C library. Keep their interfaces explicit even if they
initially compile into one library.
### Suggested repository layout
Adapt this to an existing repository instead of replacing it blindly.
```text
xtc/
├── README.md
├── LICENSE
├── CMakeLists.txt
├── docs/
│ ├── ARCHITECTURE.md
│ ├── AUDIO_ENGINE.md
│ ├── PROJECT_FORMAT.md
│ ├── GUI.md
│ ├── ROADMAP.md
│ └── decisions/
│ ├── 0001-modular-engine-first.md
│ ├── 0002-c-core-fltk-gui.md
│ └── 0003-realtime-thread-rules.md
├── include/
│ └── xtc/
│ ├── xtc.h
│ ├── xtc_types.h
│ ├── xtc_project.h
│ ├── xtc_engine.h
│ └── xtc_version.h
├── src/
│ ├── core/
│ ├── project/
│ ├── graph/
│ ├── engine/
│ ├── backend/
│ ├── midi/
│ ├── dsp/
│ ├── persistence/
│ ├── util/
│ └── gui/
│ ├── main.cpp
│ ├── main_window.cpp
│ ├── main_window.hpp
│ ├── transport_widget.cpp
│ ├── sequencer_widget.cpp
│ ├── mixer_widget.cpp
│ └── graph_widget.cpp
├── tests/
│ ├── unit/
│ ├── integration/
│ └── fixtures/
├── examples/
└── build/ # generated; do not commit
```
The implemented sequencer now uses nested track, header, clip, velocity and
ruler widgets; see the [current source and ownership map](ARCHITECTURE.md#sequencer-widget-ownership).
Keep C and C++ files clearly separated. C headers exposed to C++ must use
`extern "C"` guards.
---
## 4. Language and API Boundary
### C Code owns
- project and track data;
- graph nodes, ports, and connections;
- audio processing and DSP;
- transport and sample-position calculations;
- MIDI event representation and scheduling;
- parameter values and automation evaluation;
- project serialization and validation;
- undoable core commands where they affect domain state;
- backend-independent engine state;
- error codes and diagnostic messages.
### C++/FLTK Code owns
- windows and widgets;
- layout and drawing;
- mouse and keyboard interaction;
- selection and view state;
- GUI-only drag previews;
- user-facing dialogs and menus;
- conversion between user gestures and C API commands.
### Boundary rules
- Public C headers must compile from both C and C++.
- Do not expose C++ classes, STL containers, exceptions, or FLTK types through
the C API.
- Prefer opaque handles for objects whose internal layout must remain private.
- Use explicit integer error codes and documented output parameters.
- Define ownership for every pointer: borrowed, caller-owned, or transferred.
- Do not return pointers into mutable engine memory unless their lifetime is
guaranteed.
- Do not let GUI widgets mutate internal engine structures directly.
- Avoid a public API that mirrors every internal struct field. Provide stable
operations instead.
Example API shape (illustrative only; refine after reviewing the actual
codebase):
```c
#ifndef XTC_H
#define XTC_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct xtc_app xtc_app;
typedef enum {
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;
xtc_result xtc_app_create(xtc_app **out_app);
void xtc_app_destroy(xtc_app *app);
xtc_result xtc_transport_play(xtc_app *app);
xtc_result xtc_transport_stop(xtc_app *app);
#ifdef __cplusplus
}
#endif
#endif
```
Do not implement this exact API blindly if the existing design calls for a
better interface. Keep it small and consistent.
---
## 5. Modular Processing Graph — Primary Design Decision
**Decision B: Modular engine first.** The graph is the common representation of
signal processing and routing. The sequencer is a source of time-based events
and clips, not a separate audio system disconnected from the graph.
### Node categories
Start with a small set:
- audio input;
- audio output;
- audio clip player;
- MIDI input;
- MIDI clip/event player;
- instrument;
- audio effect;
- gain/pan;
- mixer;
- metering/analyzer;
- utility nodes such as channel mapping.
Do not build every node at once. First prove the graph with a test tone, gain
node, and output node.
### Ports and connections
Each node has a stable identifier, a node type, and declared ports. A port has:
- a unique identifier within its node;
- a direction (input or output);
- a signal/data type (audio, MIDI/event, or control);
- a channel layout or event format where applicable;
- a documented sample/block processing contract.
Validate connections before accepting them:
- source and destination exist;
- source port is an output and destination is an input;
- signal types are compatible;
- channel layouts can be connected or explicitly converted;
- the connection does not violate graph rules;
- identifiers and connection counts are valid.
### Cycles and feedback
For the first working engine, reject cycles in the audio-processing graph. A
directed acyclic graph is easier to schedule safely and predictably. Report a
useful error to the user. Do not silently break a cycle.
Feedback support can be considered later using explicit delay/feedback nodes and
a defined block-boundary model. Never introduce arbitrary graph cycles without a
correct scheduling design.
### Graph compilation
Compile the editable graph into an execution plan outside the real-time thread:
1. validate nodes, ports, and edges;
2. identify source nodes and reachable output nodes;
3. detect cycles;
4. topologically sort processing nodes;
5. determine channel layouts and required buffers;
6. allocate buffers and prepare node state;
7. publish an immutable, ready-to-run plan.
The audio callback processes the prepared plan. It must not perform graph
discovery, sorting, dynamic allocation, file loading, or plugin creation.
### Graph replacement during playback
The editor must not modify the graph currently being traversed by the audio
thread. Prepare a new plan off-thread and publish it through a bounded
real-time-safe mechanism. Define how the old plan is retired only after the
audio thread can no longer access it. Do not use a naive pointer swap followed
by immediate free.
Possible approaches include a carefully designed single-producer/single-consumer
handoff, epochs, or another proven reclamation scheme. Choose one, document it,
and test it under stress. Keep the initial design as simple as safety allows.
### Sequencer integration
The sequencer stores timeline content and emits events to the graph at the
correct sample positions. A clip player or instrument node consumes those
events. The GUI timeline is a view/editor for project data; it is not the audio
scheduler.
---
## 6. Real-Time Audio Requirements
These rules are mandatory.
### Audio callback must not
- allocate or free memory;
- acquire blocking mutexes or wait on condition variables;
- perform file, network, or console I/O;
- log using potentially blocking facilities;
- create or destroy GUI objects;
- load plugins, sample files, or project files;
- resize containers or buffers;
- perform unbounded work based on arbitrary user input;
- wait for the GUI or another thread.
### Audio callback should
- process a bounded number of frames and events;
- use preallocated buffers;
- use predictable algorithms;
- avoid unnecessary copying;
- detect invalid runtime states and recover safely;
- update meters through a bounded, non-blocking communication path;
- process silence safely when a source is unavailable;
- handle backend buffer sizes and channel counts within documented limits.
Do not assume all backends use a fixed block size unless the backend contract
guarantees it. Establish maximum supported frame/event counts and define what
happens when limits are exceeded.
### Thread ownership
Document which thread owns each mutable resource:
- GUI thread: widgets and GUI state;
- control/project thread: editable project state, file operations, graph
compilation;
- audio thread: active processing plan and real-time DSP state;
- backend callbacks: follow the backend's documented threading model.
Avoid sharing mutable structs across threads without an explicit synchronization
strategy. Atomics are not automatically safe for every use: verify that the
selected atomic operations are lock-free where required and use correct memory
ordering.
### Timing
Use audio sample position as the authoritative playback clock. Represent
timeline positions with an integer or rational scheme that avoids accumulated
floating-point drift. Convert musical time (bars/beats/ticks) to sample
positions using tempo and meter maps. Define behavior at tempo changes before
implementing advanced tempo automation.
---
## 7. Audio Backend Strategy
Start on Linux and keep the backend replaceable.
1. Define a small backend interface in C.
2. Implement one backend first, selected after checking the target system and
installed development packages. JACK-compatible audio is a reasonable first
target on Linux.
3. Test device enumeration, start/stop, sample rate, buffer size, channel count,
disconnects, and shutdown.
4. Keep backend-specific headers out of the public API.
5. Add another backend only after the engine works and tests pass.
Do not assume PipeWire-native, JACK compatibility, ALSA PCM, and desktop audio
routing are interchangeable. Document the chosen backend's setup and
limitations.
The engine should also have an offline/test mode that processes buffers without
physical audio hardware. This makes unit and integration testing repeatable.
---
## 8. MIDI, Parameters, Automation, and DSP
### MIDI/Events
- Use a compact, well-defined event representation.
- Store event time relative to a project timeline and convert it to sample
offsets for a processing block.
- Validate event sizes and event counts.
- Handle note-on, note-off, velocity, channel, controller, pitch bend, and
tempo/transport events as needed.
- Ensure all held notes can be released on stop, panic, seek, device loss, or
project teardown.
- Avoid unbounded event queues.
### Parameters
Every automatable control should have:
- a stable parameter ID;
- a type and valid range;
- a default value;
- a documented unit and mapping;
- a safe update mechanism;
- optional automation and MIDI mapping.
Do not silently clamp invalid serialized values. Validate project data and
return an error or apply a clearly documented migration rule. UI controls may
constrain user input, but core validation must remain authoritative.
### Automation
Implement automation only after transport, graph processing, and basic parameter
updates work. Define interpolation types explicitly (initially step and linear
are enough). Automation evaluation must be deterministic and bounded.
### DSP
Start with a few well-tested processors:
- gain;
- pan;
- simple meter/peak detector;
- test oscillator;
- basic delay only after buffer/lifetime handling is sound.
Keep DSP algorithms in C and independently testable. Use appropriate numerical
safeguards, including finite-value checks where needed. Avoid premature SIMD
optimization until profiling shows a real bottleneck.
---
## 9. FLTK/C++ GUI Guidelines
The GUI is a thin client of the C API, not the owner of the engine.
### Main views
1. **Sequencer:** tracks, audio/MIDI clips, timeline ruler, playhead, selection,
zoom and horizontal scrolling.
2. **Mixer:** channels, faders, pan, mute/solo, inserts/sends when supported,
and meters.
3. **Modular graph:** nodes, ports, connections, zoom/pan, selection, and
connection validation feedback.
4. **Transport:** play, stop, record, tempo, meter, loop range, and current
position.
5. **Project/file dialogs:** create, open, save, import audio, and manage
missing assets.
The default workspace should emphasize the sequencer and transport. The graph
view is an advanced but first-class view of the same underlying project.
### GUI Implementation
- Use FLTK for conventional widgets and application windows.
- Use custom-drawn widgets for the timeline, piano roll, mixer strips, waveform
display, and graph canvas where needed.
- Keep drawing code separate from project mutation.
- Use GUI timers or the toolkit's appropriate event mechanism for refreshing
meters; never poll from the audio callback or block the GUI waiting for audio.
- Avoid redrawing the entire application when only a small region changes.
- Support keyboard shortcuts and predictable focus behavior.
- Make selection, dragging, snapping, zooming, and scrolling consistent across
editors.
- Never call FLTK APIs from the audio thread.
- Do not hold GUI locks while calling potentially blocking engine operations.
- Keep C++ simple: use RAII for GUI resources, clear ownership, and small
classes. Do not force the C core into C++ abstractions.
### Visual direction
Aim for a compact, dark, high-contrast interface with restrained decoration.
Prioritize legible waveforms, clip boundaries, transport state, and meter
levels. Avoid excessive gradients, animations, nested panels, and modal
configuration windows.
Do not copy energyXT's artwork, exact visual assets, or proprietary UI
resources. Recreate the general principles of compactness, direct manipulation,
and modular routing in an original design.
---
## 10. Project Format and Persistence
The project format must be versioned from its first release.
### Requirements
- Include a format version and application version.
- Store stable IDs for tracks, nodes, ports where necessary, clips, parameters,
and connections.
- Save relative asset paths when possible.
- Define behavior for missing audio files.
- Validate every field before allocating large amounts of memory.
- Handle truncated, malformed, unsupported, or corrupted files gracefully.
- Never execute code while loading a project.
- Save atomically: write a temporary file, flush as appropriate, and rename it
into place when safe.
- Preserve unknown optional fields when practical, or explicitly document when
they are discarded.
- Include migration functions when the format changes.
- Keep project serialization outside the real-time thread.
Choose a simple, well-supported format after considering the dependency and
robustness trade-offs. A small, documented text format can help inspect
projects, but large binary audio data should generally live in separate assets
rather than be embedded in a text document. Do not add a serialization
dependency without a clear reason.
---
## 11. Undo/Redo and Editing Commands
Use explicit commands for user-visible edits, such as:
- create/delete/move/resize clip;
- create/delete/rename track;
- change parameter;
- add/remove node;
- connect/disconnect ports;
- change tempo or meter.
A command should validate its inputs and have a well-defined inverse or stored
previous state. Avoid designing a generic event-sourcing framework before it is
needed.
The initial implementation may begin without a complete undo stack, but design
edit operations so undo/redo can be added without rewriting all editors. Never
make an edit partially visible if it fails validation.
---
## 12. Build, Coding Standards, and Dependencies
### Build system
Use the root CMake project and CTest. Keep SDK integrations optional and
all generated output in separate build directories.
The build must:
- compile C with the selected C standard and warnings enabled;
- compile C++ GUI sources with the standard supported by the chosen FLTK
version;
- link the GUI with FLTK and the C core;
- link the required audio backend;
- provide `all`, `clean`, `test`, and a debug build target;
- allow compiler and installation-prefix overrides;
- avoid embedding machine-specific paths;
- separate generated objects and binaries into a build directory.
Use `pkg-config` where supported by the installed dependencies. Check actual
package names on the target distribution rather than hard-coding unverified
names.
### C Style
- Use C99 initially.
- Use `snake_case`.
- Prefer explicit structs and functions over clever macros.
- Keep functions focused.
- Validate pointers and sizes at API boundaries.
- Check allocation and I/O results.
- Avoid variable-length arrays for untrusted sizes.
- Make ownership and cleanup obvious.
- Use `const` where appropriate.
- Do not suppress warnings merely to get a clean build.
### C++ GUI Style
- Keep C++ confined to the GUI and any unavoidable C++-only third-party wrapper.
- Prefer small classes with explicit ownership.
- Use RAII for GUI-side resources.
- Avoid exceptions crossing the C API boundary.
- Catch exceptions at the GUI's top-level boundary if any dependency can throw.
- Never pass STL types across the C API.
### Dependencies
Before adding a dependency:
1. identify the exact feature it provides;
2. check license and maintenance status;
3. determine whether the existing code can provide the functionality simply and
safely;
4. document the dependency and its version constraints;
5. keep it optional if the feature is optional.
Prefer open-source dependencies with clear licensing. Do not claim the whole
application is license-compatible until all direct and bundled dependencies have
been reviewed.
---
## 13. Testing and Diagnostics
### Unit tests
Test at least:
- graph validation and topological sorting;
- invalid ports and incompatible connections;
- cycle detection;
- parameter validation;
- sample-position and tempo conversion;
- project serialization/deserialization;
- malformed project files and missing assets;
- node lifecycle and buffer bounds;
- DSP output for known inputs;
- command execution and undo/redo when implemented.
### Integration tests
Test:
- test oscillator → gain → output processing;
- offline rendering with deterministic output;
- graph replacement during active processing;
- start/stop and repeated initialization/cleanup;
- project save/load round trips;
- missing audio assets;
- MIDI note start/stop and panic;
- backend disconnect/reconnect where supported.
### Tools
Where available, use:
- compiler warnings;
- AddressSanitizer and UndefinedBehaviorSanitizer in non-real-time test builds;
- static analysis;
- a leak checker;
- stress tests for graph edits and shutdown;
- audio loopback or offline render comparisons.
Sanitizers and diagnostic logging should not be enabled in production real-time
callbacks unless their real-time implications have been specifically evaluated.
---
## 14. Development Roadmap and Acceptance Criteria
Implement these milestones in order. Do not jump ahead merely because a later
feature is exciting.
### Repository and design baseline
- Inspect the repository and available toolchain.
- Create or update architecture documentation and the CMake project.
- Verify C compiler, C++ compiler, FLTK, audio development headers, and test
tools.
- Record dependency and licensing decisions.
- Add a minimal application that starts and exits cleanly.
**Acceptance:** clean build on the target Linux environment; no audio engine
assumptions hidden in GUI code.
### C Core and offline processing
- Create the C library/API.
- Define node, port, connection, and graph data structures.
- Implement graph validation and topological sorting.
- Implement preallocated audio buffers.
- Add oscillator, gain, meter, and output test nodes.
- Implement deterministic offline block processing.
**Acceptance:** automated test proves oscillator → gain → meter/output produces
expected samples without hardware.
### Real-time backend
- Add the first Linux audio backend.
- Implement start/stop, device configuration, and error handling.
- Ensure callback rules are followed.
- Add safe control-to-audio communication.
- Stress-test shutdown and graph replacement.
**Acceptance:** stable playback for a sustained test; no known callback
allocations or blocking calls; failures are reported clearly.
### FLTK Shell
- Create main window, menus, transport, status area, and view switching.
- Call the C API through the public header.
- Add an audio engine status indicator.
- Implement clean startup and shutdown.
**Acceptance:** GUI can start/stop the engine, report backend errors, and close
without leaks or hangs.
### Modular graph editor
- Draw nodes and ports.
- Add/select/move nodes.
- Create/delete connections.
- Display invalid connection feedback.
- Submit graph edits through validated C API commands.
**Acceptance:** graph shown by the GUI matches the graph used by the engine;
invalid connections are rejected without corrupting active playback.
### Project and sequencer foundation
- Add project persistence and stable IDs.
- Add tracks and clip objects.
- Implement timeline rendering, selection, moving, trimming, zoom, and
scrolling.
- Add transport/sample-position synchronization.
**Acceptance:** save/load a project with clips and reproduce the same
arrangement.
### Audio clips and recording
- Add audio file import and clip playback.
- Add recording through a safe producer/consumer buffer path.
- Keep file I/O outside the audio callback.
- Add missing-asset reporting.
**Acceptance:** record, save, reopen, and play a basic audio project without
callback file I/O.
### MIDI And a simple instrument
- Add MIDI input and clip events.
- Add a basic internal synthesizer or test instrument.
- Handle note-off, panic, seeking, and stop correctly.
- Add a minimal piano-roll editor only after event scheduling works.
**Acceptance:** record or draw MIDI notes, play them in time, save the project,
and reopen it.
### Mixer, automation, and editing polish
- Add mixer channels, gain/pan, meters, mute/solo.
- Add automation lanes and interpolation.
- Add a bounded undo/redo system for common edits.
- Improve keyboard interaction and workflow.
**Acceptance:** basic composition and mixing can be completed without editing
project files manually.
### Extensibility and release preparation
- Define and document the native node/instrument/effect API.
- Evaluate external plugin hosting only after core lifecycle handling is robust.
- Review project format migration, crash behavior, performance, and licensing.
- Package the application and document Linux setup.
**Acceptance:** reproducible release build, tested install/uninstall, clear
limitations, and complete user/developer documentation.
---
## 15. Initial Implementation Task for the Agent
Start with the **repository and design baseline** only.
1. Inspect the current repository and report its state.
2. Identify the available C/C++ compilers, FLTK version, audio backend
development packages, `pkg-config`, and test tools.
3. Do not install packages or modify system configuration without permission.
4. Propose the smallest initial source tree compatible with this brief.
5. Add or update the CMake project and documentation only as needed.
6. Create a minimal program that builds and exits cleanly; do not yet implement
a full audio engine or GUI.
7. Build it and run its tests.
8. Report exact commands, results, assumptions, and any missing dependencies.
After the baseline is reviewed, continue with the C core and offline processing.
Keep each milestone independently buildable and testable.
---
## 16. Long-Term Quality Bar
The project succeeds if a musician can open it quickly, connect components
without fighting the interface, record and arrange audio/MIDI, and make music
without needing to understand every detail of the engine.
The implementation succeeds if a developer can read the C code, understand
ownership and thread boundaries, test components independently, and add
functionality without turning the application into an unmaintainable framework.
**Guiding rule:** build a small, coherent system whose components compose well.
Do not build a large DAW and then attempt to simplify it afterward.