Initial commit.

This commit is contained in:
Johannes Findeisen 2026-10-26 22:51:25 +01:00
commit 1d8f869b5b
39 changed files with 1671 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
# Build products
/build/
/*.o
*.a
*.so
*.so.*
# IDE and editor files
.idea/
.vscode/
*.swp
*~
# Local test outputs
*.wav
*.mid

35
AGENT.md Normal file
View file

@ -0,0 +1,35 @@
# Instructions for the AI coding agent
This file is the working contract for AI-assisted development in this repository. Read it and inspect the repository before editing.
## Rules
1. Make small, buildable changes and run the relevant tests after each change.
2. Do not replace or remove existing behavior without explaining why.
3. Keep audio engine, DSP, project model, graph, persistence, and public core APIs in C (C99 baseline).
4. Keep FLTK and GUI code in C++ only. The GUI must call the core through C headers.
5. Use C `snake_case`; prefer explicit ownership and small functions.
6. Validate pointers, sizes, IDs, enum values, and allocation results.
7. Never allocate/free memory, perform file I/O, log, block on a mutex, or call GUI APIs from an audio callback.
8. Do not mutate a graph while the audio thread is traversing it. Prepare and publish a safe immutable execution plan.
9. Do not add dependencies without explaining the need, license, maintenance status, and build impact.
10. Do not claim incomplete features are implemented. Distinguish prototype, stub, and finished behavior.
11. Do not copy energyXT code, artwork, or proprietary resources.
12. Before a task, briefly state the plan and affected files. After it, report files changed, commands run, test results, and limitations.
## Definition of done
- Builds with warnings enabled.
- Relevant tests pass.
- Error paths and cleanup are handled.
- Documentation is updated when APIs or behavior change.
- No unrelated functionality is removed.
- Unverified behavior is reported honestly.
## First task
Run `make clean all test`. Inspect compiler and `pkg-config` availability. Do not install packages or alter system configuration without permission. Report current state and propose the next smallest step.
## Roadmap
Follow `docs/ROADMAP.md` in order. The initial implementation should improve the graph and offline engine before introducing hardware audio I/O, recording, MIDI, or external plugin hosting.

17
LICENSE Normal file
View file

@ -0,0 +1,17 @@
MIT License
Copyright (c) 2026 Johannes Findeisen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

60
Makefile Normal file
View file

@ -0,0 +1,60 @@
CC ?= cc
CXX ?= c++
PKG_CONFIG ?= pkg-config
BUILD_DIR ?= build
PREFIX ?= /usr/local
CFLAGS ?= -O2 -g
CFLAGS += -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude
CXXFLAGS ?= -O2 -g
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude
LDFLAGS ?=
CORE_SOURCES := src/core/xtlite.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c
CORE_OBJECTS := $(CORE_SOURCES:%.c=$(BUILD_DIR)/%.o)
TEST_OBJECT := $(BUILD_DIR)/tests/unit/test_core.o
GUI_AVAILABLE := $(shell $(PKG_CONFIG) --exists fltk 2>/dev/null && echo yes)
.PHONY: all clean test gui install
all: $(BUILD_DIR)/libxtlite.a
$(BUILD_DIR)/libxtlite.a: $(CORE_OBJECTS)
@mkdir -p $(@D)
$(AR) rcs $@ $^
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: %.cpp
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(shell $(PKG_CONFIG) --cflags fltk 2>/dev/null) -c $< -o $@
$(TEST_OBJECT): tests/unit/test_core.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/test_core: $(TEST_OBJECT) $(BUILD_DIR)/libxtlite.a
$(CC) $(LDFLAGS) -o $@ $(TEST_OBJECT) $(BUILD_DIR)/libxtlite.a -lm
test: $(BUILD_DIR)/test_core
./$(BUILD_DIR)/test_core
ifeq ($(GUI_AVAILABLE),yes)
gui: $(BUILD_DIR)/xtlite
$(BUILD_DIR)/xtlite: $(BUILD_DIR)/src/gui/main.o $(BUILD_DIR)/src/gui/main_window.o $(BUILD_DIR)/libxtlite.a
$(CXX) $(LDFLAGS) -o $@ $^ $(shell $(PKG_CONFIG) --libs fltk) -lm
else
gui:
@echo "FLTK was not found by pkg-config. Install FLTK development files to build the GUI."
@exit 1
endif
install: all
install -d "$(DESTDIR)$(PREFIX)/include/xtlite" "$(DESTDIR)$(PREFIX)/lib"
install -m 0644 include/xtlite/*.h "$(DESTDIR)$(PREFIX)/include/xtlite/"
install -m 0644 "$(BUILD_DIR)/libxtlite.a" "$(DESTDIR)$(PREFIX)/lib/"
clean:
rm -rf "$(BUILD_DIR)"

60
README.md Normal file
View file

@ -0,0 +1,60 @@
# xtlite — a lightweight, modular DAW
`xtlite` is a new, energyXT-inspired digital audio workstation project. It is not a clone and contains no energyXT source code or assets.
## Design decisions
- **Modular engine first:** sequencer, instruments, effects, mixers and I/O are nodes in one processing graph.
- **C core:** project model, graph, DSP and audio engine are implemented in C.
- **FLTK/C++ GUI:** C++ is confined to the graphical interface and calls the core through a C API.
- **Linux first:** keep backend-specific details behind an interface.
- **Small increments:** build and test each milestone before adding features.
## Current status
This repository is a starter scaffold, not a production DAW. It currently provides:
- a small C library/API;
- a minimal graph model with validation and topological ordering;
- a gain processor and offline audio-block processing;
- a C unit test;
- an optional FLTK GUI shell;
- architecture, format, GUI, roadmap, and decision documents.
Real audio-device integration, recording, MIDI, plugin hosting, project persistence, and the actual sequencer UI are not implemented yet.
## Build
Requirements: a C compiler, a C++ compiler, GNU Make, and `pkg-config`. FLTK is optional for the core and tests.
```sh
make
make test
```
Build the optional FLTK GUI if FLTK is installed:
```sh
make gui
```
Run the GUI:
```sh
./build/xtlite
```
The current GUI is only a shell and does not yet produce sound. The core's offline graph test is the first executable audio-processing milestone.
Clean generated files:
```sh
make clean
```
## Suggested first agent task
Read `AGENT.md` and `docs/ARCHITECTURE.md`. Inspect the existing implementation, run `make test`, and report gaps before adding the next feature. Do not jump directly to plugin hosting or a full sequencer.
## License
The project is scaffolded under the MIT License; see `LICENSE`. Review the licenses of any future dependencies before distribution.

38
docs/ARCHITECTURE.md Normal file
View file

@ -0,0 +1,38 @@
# Architecture
## Decision: modular engine first
The processing graph is the common model for audio sources, instruments,
effects, mixers, and outputs. The sequencer produces time-based content and
events consumed by graph nodes. The default workspace remains sequencer-first;
the graph editor is an advanced view of the same project.
## Layers
1. **FLTK/C++ GUI:** input, drawing, windows, view state.
2. **C core:** project model, commands, parameters, persistence, validation.
3. **C engine:** prepared processing graph, DSP, transport timing, event delivery.
4. **Backend:** hardware and system audio/MIDI integration.
The GUI must not directly modify engine internals. It calls a stable C API.
## Graph execution
The first implementation should reject cycles. Compile a graph off the audio
thread: validate, topologically sort, determine buffer requirements, allocate
resources, and publish a prepared immutable execution plan. The audio thread
must not traverse a graph being edited.
The current scaffold is deliberately incomplete: `xt_graph_process_offline()`
validates the graph and applies gain nodes in insertion order. It is a test
placeholder, not the final graph execution algorithm.
## Ownership and threads
- GUI thread owns FLTK widgets and view state.
- Project/control thread owns mutable project data and graph compilation.
- 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.

27
docs/AUDIO_ENGINE.md Normal file
View file

@ -0,0 +1,27 @@
# 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.

18
docs/GUI.md Normal file
View file

@ -0,0 +1,18 @@
# GUI guidelines
Use FLTK/C++ for windows, menus, dialogs, and the event loop. Keep all audio
engine and project-domain logic in C.
Main views:
- Sequencer: tracks, clips, ruler, playhead, selection, zoom and scrolling.
- Mixer: channels, faders, pan, mute/solo and meters.
- Modular graph: nodes, ports, connections, validation feedback.
- Transport: play/stop/record, tempo, meter, loop and position.
Use custom-drawn widgets where ordinary controls are not a good fit: timeline,
waveforms, piano roll, mixer strips and graph canvas. Keep drawing separate
from project mutation. Never call FLTK from the audio thread.
Visual direction: compact, high-contrast, restrained dark interface. Avoid
excessive animation and modal configuration. Recreate broad usability ideas,
not energyXT's exact artwork or proprietary resources.

723
docs/IDEA.md Normal file
View file

@ -0,0 +1,723 @@
# 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
xtlite/
├── README.md
├── LICENSE
├── Makefile
├── 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/
│ └── xtlite/
│ ├── xtlite.h
│ ├── xtlite_types.h
│ ├── xtlite_project.h
│ ├── xtlite_engine.h
│ └── xtlite_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
```
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 XTLITE_H
#define XTLITE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct xt_app xt_app;
typedef enum {
XT_OK = 0,
XT_ERR_INVALID_ARGUMENT,
XT_ERR_OUT_OF_MEMORY,
XT_ERR_NOT_FOUND,
XT_ERR_INVALID_STATE,
XT_ERR_IO,
XT_ERR_UNSUPPORTED
} xt_result;
xt_result xt_app_create(xt_app **out_app);
void xt_app_destroy(xt_app *app);
xt_result xt_transport_play(xt_app *app);
xt_result xt_transport_stop(xt_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 a straightforward Makefile unless the existing repository has a sound reason to use another build system.
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 phases in order. Do not jump ahead merely because a later feature is exciting.
### Phase 0 — Repository and design baseline
- Inspect the repository and available toolchain.
- Create or update architecture documentation and the Makefile.
- 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.
### Phase 1 — 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.
### Phase 2 — 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.
### Phase 3 — 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.
### Phase 4 — 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.
### Phase 5 — 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.
### Phase 6 — 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.
### Phase 7 — 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.
### Phase 8 — 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.
### Phase 9 — 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 **Phase 0 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 Makefile 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 Phase 0 is reviewed, continue with Phase 1. Keep each phase 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.

18
docs/PROJECT_FORMAT.md Normal file
View file

@ -0,0 +1,18 @@
# Project format
Project persistence is not implemented yet. Before implementation, define a
versioned format with stable IDs for tracks, nodes, clips, connections, and
parameters.
Requirements:
- reject malformed, truncated, unsupported, or unreasonable input safely;
- validate sizes before allocation;
- store external audio assets separately from project metadata;
- define missing-asset behavior;
- never execute code during loading;
- perform file I/O only off the audio thread;
- save atomically using a temporary file and a safe rename;
- add explicit migration code when the format changes.
Choose a simple format after evaluating dependency, robustness, and human
inspectability trade-offs. Document the schema before treating it as stable.

42
docs/ROADMAP.md Normal file
View file

@ -0,0 +1,42 @@
# Roadmap
## Phase 0 — Baseline
Inspect repository/toolchain, verify Makefile and minimal executable, document
dependencies. No system changes without permission.
## Phase 1 — C core and offline processing
Graph data structures, validation, cycle detection, topological ordering,
preallocated buffers, test oscillator, gain and offline render tests.
## Phase 2 — Real-time backend
Choose and implement a Linux backend. Document callback semantics, start/stop,
device errors, shutdown, and safe graph publication.
## Phase 3 — FLTK shell
Main window, transport controls, view switching, backend status, clean startup
and shutdown through the C API.
## Phase 4 — Modular editor
Draw nodes/ports/edges; create, move and connect nodes; reject invalid edges;
safely submit edits to the C core.
## Phase 5 — Project and sequencer
Versioned persistence, stable IDs, tracks, clips, timeline, selection, zoom,
scrolling, and transport synchronization.
## Phase 6 — Audio clips and recording
Audio import/playback, recording through bounded buffers, missing-asset
handling, no file I/O in callbacks.
## Phase 7 — MIDI and instrument
MIDI input/events/clips, simple internal instrument, note-off/panic handling,
then a basic piano roll.
## Phase 8 — Mixer and editing
Gain/pan, meters, mute/solo, automation, undo/redo, keyboard workflow.
## Phase 9 — Extensibility and release
Native node API, evaluate plugin hosting, licensing review, packaging,
installation tests, performance and recovery tests.
Do not skip phases without documenting why and preserving acceptance criteria.

View file

@ -0,0 +1,23 @@
# Decision 0001: Modular engine first
**Status:** Accepted
## Context
The project aims to preserve energyXT's combination of a simple workflow and a
flexible modular routing model.
## Decision
Use one unified processing graph for sources, instruments, effects, mixers,
and outputs. The sequencer is a timeline/event source integrated with that
graph. The default GUI is simple and sequencer-oriented, while a graph editor
exposes routing when needed.
## Consequences
- Graph validation, scheduling, buffer allocation, and safe graph replacement
are foundational requirements.
- Initial audio graph rejects cycles.
- Graph execution plans are prepared off the audio thread.
- The UI must not create a separate routing model disconnected from the engine.

View file

@ -0,0 +1,20 @@
# Decision 0002: C core with FLTK/C++ GUI
**Status:** Accepted
## Context
The project prioritizes a readable, small C implementation while using FLTK
for a lightweight desktop GUI.
## Decision
Keep the engine, DSP, project model, graph, and public core API in C (C99
baseline). Use C++ only for FLTK windows and widgets. Communicate through
documented C headers with `extern "C"` guards.
## Consequences
- No C++ classes, STL containers, exceptions, or FLTK types cross the C API.
- GUI code cannot mutate engine internals directly.
- Keep C++ simple and limited to the presentation layer.

View file

@ -0,0 +1,19 @@
# Decision 0003: Real-time thread rules
**Status:** Accepted
## Context
Audio glitches and unsafe graph changes can undermine the whole application.
## Decision
The audio callback performs bounded processing using preallocated resources.
It performs no allocation/free, blocking locks, file I/O, logging, GUI calls,
plugin loading, or waits. Graphs are prepared outside the callback and active
graph resources are retired only after safe reclamation.
## Consequences
Thread ownership and publication mechanisms must be documented and tested.
Any exception to these rules requires explicit evidence and review.

4
examples/README.md Normal file
View file

@ -0,0 +1,4 @@
# Examples
Add small runnable examples here as the C API stabilizes. Keep examples focused
on one behavior and make sure they build through the normal Makefile.

26
include/xtlite/xtlite.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef XTLITE_H
#define XTLITE_H
#include "xtlite_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define XTLITE_VERSION_MAJOR 0
#define XTLITE_VERSION_MINOR 1
#define XTLITE_VERSION_PATCH 0
typedef struct xt_app xt_app;
/* Creates an application context. On success, *out_app is caller-owned. */
xt_result xt_app_create(xt_app **out_app);
/* Destroys an application context; NULL is accepted. */
void xt_app_destroy(xt_app *app);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,44 @@
#ifndef XTLITE_ENGINE_H
#define XTLITE_ENGINE_H
#include "xtlite_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct xt_graph xt_graph;
typedef enum {
XT_SIGNAL_AUDIO = 1,
XT_SIGNAL_EVENT = 2,
XT_SIGNAL_CONTROL = 3
} xt_signal_type;
typedef enum {
XT_PORT_INPUT = 1,
XT_PORT_OUTPUT = 2
} xt_port_direction;
typedef struct {
float *samples;
size_t frame_count;
size_t channel_count;
} xt_audio_buffer;
/* Graph API is intentionally small in this first scaffold. */
xt_result xt_graph_create(size_t max_nodes, xt_graph **out_graph);
void xt_graph_destroy(xt_graph *graph);
xt_result xt_graph_add_gain(xt_graph *graph, float gain, size_t *out_node_id);
xt_result xt_graph_connect(xt_graph *graph, size_t source_node,
size_t destination_node);
xt_result xt_graph_validate(const xt_graph *graph);
xt_result xt_graph_process_offline(xt_graph *graph, const float *input,
float *output, size_t frame_count,
float input_gain);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,22 @@
#ifndef XTLITE_PROJECT_H
#define XTLITE_PROJECT_H
#include "xtlite_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct xt_project xt_project;
/* Project persistence is not implemented in this scaffold yet. */
xt_result xt_project_create(xt_project **out_project);
void xt_project_destroy(xt_project *project);
xt_result xt_project_save(const xt_project *project, const char *path);
xt_result xt_project_load(xt_project *project, const char *path);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,29 @@
#ifndef XTLITE_TYPES_H
#define XTLITE_TYPES_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
XT_OK = 0,
XT_ERR_INVALID_ARGUMENT,
XT_ERR_OUT_OF_MEMORY,
XT_ERR_NOT_FOUND,
XT_ERR_INVALID_STATE,
XT_ERR_IO,
XT_ERR_UNSUPPORTED,
XT_ERR_CAPACITY,
XT_ERR_GRAPH_CYCLE
} xt_result;
const char *xt_result_string(xt_result result);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,7 @@
#ifndef XTLITE_VERSION_H
#define XTLITE_VERSION_H
#define XTLITE_VERSION_STRING "0.1.0"
#define XTLITE_PROJECT_NAME "xtlite"
#endif

8
src/backend/README.md Normal file
View file

@ -0,0 +1,8 @@
# Audio backend
No hardware backend is implemented yet. Keep backend-specific headers out of
public headers. The first backend should be selected after checking the target
Linux environment and its development packages.
A backend implementation must document callback threading, buffer sizes,
sample-rate negotiation, device loss, and shutdown semantics.

1
src/core/README.md Normal file
View file

@ -0,0 +1 @@
Core lifecycle and public C API implementation.

46
src/core/xtlite.c Normal file
View file

@ -0,0 +1,46 @@
#include "xtlite/xtlite.h"
#include <stdlib.h>
struct xt_app {
unsigned int reserved;
};
const char *xt_result_string(xt_result result)
{
switch (result) {
case XT_OK: return "success";
case XT_ERR_INVALID_ARGUMENT: return "invalid argument";
case XT_ERR_OUT_OF_MEMORY: return "out of memory";
case XT_ERR_NOT_FOUND: return "not found";
case XT_ERR_INVALID_STATE: return "invalid state";
case XT_ERR_IO: return "I/O error";
case XT_ERR_UNSUPPORTED: return "unsupported operation";
case XT_ERR_CAPACITY: return "capacity exceeded";
case XT_ERR_GRAPH_CYCLE: return "graph contains a cycle";
default: return "unknown error";
}
}
xt_result xt_app_create(xt_app **out_app)
{
xt_app *app;
if (out_app == NULL) {
return XT_ERR_INVALID_ARGUMENT;
}
*out_app = NULL;
app = (xt_app *)calloc(1U, sizeof(*app));
if (app == NULL) {
return XT_ERR_OUT_OF_MEMORY;
}
*out_app = app;
return XT_OK;
}
void xt_app_destroy(xt_app *app)
{
free(app);
}

1
src/dsp/README.md Normal file
View file

@ -0,0 +1 @@
Small, independently testable DSP primitives implemented in C.

15
src/dsp/gain.c Normal file
View file

@ -0,0 +1,15 @@
#include <stddef.h>
/* Kept as a separately testable DSP primitive. */
void xt_dsp_gain_process(const float *input, float *output,
size_t frame_count, float gain)
{
size_t frame;
if (input == NULL || output == NULL) {
return;
}
for (frame = 0U; frame < frame_count; frame++) {
output[frame] = input[frame] * gain;
}
}

1
src/engine/README.md Normal file
View file

@ -0,0 +1 @@
Offline and real-time processing coordination. Respect callback safety rules.

View file

@ -0,0 +1,13 @@
/*
* Offline engine integration point.
*
* The first scaffold exposes xt_graph_process_offline() from graph.c. This
* file is reserved for the next milestone: a compiled immutable graph plan,
* preallocated node buffers, and block-based processing independent of audio
* hardware. Keep hardware callbacks and real-time constraints out of this
* initial offline implementation.
*/
int xt_offline_engine_translation_unit_anchor(void)
{
return 0;
}

1
src/graph/README.md Normal file
View file

@ -0,0 +1 @@
Graph nodes, ports, connections, validation, and compiled execution plans.

189
src/graph/graph.c Normal file
View file

@ -0,0 +1,189 @@
#include "xtlite/xtlite_engine.h"
#include <stdlib.h>
#include <string.h>
typedef struct {
size_t source;
size_t destination;
} xt_edge;
typedef struct {
float gain;
} xt_gain_node;
struct xt_graph {
size_t max_nodes;
size_t node_count;
size_t edge_count;
xt_gain_node *nodes;
xt_edge *edges;
};
xt_result xt_graph_create(size_t max_nodes, xt_graph **out_graph)
{
xt_graph *graph;
if (out_graph == NULL || max_nodes == 0U) {
return XT_ERR_INVALID_ARGUMENT;
}
*out_graph = NULL;
graph = (xt_graph *)calloc(1U, sizeof(*graph));
if (graph == NULL) {
return XT_ERR_OUT_OF_MEMORY;
}
graph->nodes = (xt_gain_node *)calloc(max_nodes, sizeof(*graph->nodes));
graph->edges = (xt_edge *)calloc(max_nodes * max_nodes, sizeof(*graph->edges));
if (graph->nodes == NULL || graph->edges == NULL) {
free(graph->nodes);
free(graph->edges);
free(graph);
return XT_ERR_OUT_OF_MEMORY;
}
graph->max_nodes = max_nodes;
*out_graph = graph;
return XT_OK;
}
void xt_graph_destroy(xt_graph *graph)
{
if (graph != NULL) {
free(graph->nodes);
free(graph->edges);
free(graph);
}
}
xt_result xt_graph_add_gain(xt_graph *graph, float gain, size_t *out_node_id)
{
if (graph == NULL || out_node_id == NULL) {
return XT_ERR_INVALID_ARGUMENT;
}
if (graph->node_count >= graph->max_nodes) {
return XT_ERR_CAPACITY;
}
graph->nodes[graph->node_count].gain = gain;
*out_node_id = graph->node_count;
graph->node_count++;
return XT_OK;
}
xt_result xt_graph_connect(xt_graph *graph, size_t source_node,
size_t destination_node)
{
size_t i;
size_t edge_capacity;
if (graph == NULL) {
return XT_ERR_INVALID_ARGUMENT;
}
if (source_node >= graph->node_count || destination_node >= graph->node_count) {
return XT_ERR_NOT_FOUND;
}
edge_capacity = graph->max_nodes * graph->max_nodes;
if (graph->edge_count >= edge_capacity) {
return XT_ERR_CAPACITY;
}
for (i = 0U; i < graph->edge_count; i++) {
if (graph->edges[i].source == source_node &&
graph->edges[i].destination == destination_node) {
return XT_ERR_INVALID_STATE;
}
}
graph->edges[graph->edge_count].source = source_node;
graph->edges[graph->edge_count].destination = destination_node;
graph->edge_count++;
return XT_OK;
}
xt_result xt_graph_validate(const xt_graph *graph)
{
size_t *indegree;
size_t *queue;
size_t head = 0U;
size_t tail = 0U;
size_t visited = 0U;
size_t i;
if (graph == NULL) {
return XT_ERR_INVALID_ARGUMENT;
}
if (graph->node_count == 0U) {
return XT_OK;
}
indegree = (size_t *)calloc(graph->node_count, sizeof(*indegree));
queue = (size_t *)calloc(graph->node_count, sizeof(*queue));
if (indegree == NULL || queue == NULL) {
free(indegree);
free(queue);
return XT_ERR_OUT_OF_MEMORY;
}
for (i = 0U; i < graph->edge_count; i++) {
if (graph->edges[i].source >= graph->node_count ||
graph->edges[i].destination >= graph->node_count) {
free(indegree);
free(queue);
return XT_ERR_INVALID_STATE;
}
indegree[graph->edges[i].destination]++;
}
for (i = 0U; i < graph->node_count; i++) {
if (indegree[i] == 0U) {
queue[tail++] = i;
}
}
while (head < tail) {
size_t node = queue[head++];
visited++;
for (i = 0U; i < graph->edge_count; i++) {
if (graph->edges[i].source == node) {
size_t dest = graph->edges[i].destination;
if (indegree[dest] > 0U) {
indegree[dest]--;
if (indegree[dest] == 0U) {
queue[tail++] = dest;
}
}
}
}
}
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,
float *output, size_t frame_count,
float input_gain)
{
size_t i;
float cumulative_gain = input_gain;
if (graph == NULL || input == NULL || output == NULL) {
return XT_ERR_INVALID_ARGUMENT;
}
if (xt_graph_validate(graph) != XT_OK) {
return XT_ERR_INVALID_STATE;
}
/* Temporary starter behavior: apply gains in node insertion order.
This is not yet graph-plan execution and is only for offline tests. */
for (i = 0U; i < graph->node_count; i++) {
cumulative_gain *= graph->nodes[i].gain;
}
for (i = 0U; i < frame_count; i++) {
output[i] = input[i] * cumulative_gain;
}
return XT_OK;
}

10
src/gui/main.cpp Normal file
View file

@ -0,0 +1,10 @@
#include <FL/Fl.H>
#include "main_window.hpp"
int main(int argc, char **argv)
{
MainWindow window;
window.show(argc, argv);
return Fl::run();
}

52
src/gui/main_window.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "main_window.hpp"
#include <FL/Fl_Box.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Group.H>
extern "C" {
#include "xtlite/xtlite.h"
}
MainWindow::MainWindow()
: Fl_Double_Window(1000, 680, "xtlite — modular audio workstation")
{
begin();
Fl_Menu_Bar *menu = new Fl_Menu_Bar(0, 0, 1000, 28);
menu->add("File/New", 0, nullptr);
menu->add("File/Open...", 0, nullptr);
menu->add("File/Save", 0, nullptr);
menu->add("Help/About", 0, nullptr);
Fl_Tabs *tabs = new Fl_Tabs(8, 36, 984, 600);
tabs->begin();
Fl_Group *sequencer = new Fl_Group(8, 60, 984, 576, "Sequencer");
sequencer->begin();
new Fl_Box(24, 84, 940, 40,
"Sequencer placeholder — timeline and clips are not implemented yet.");
sequencer->end();
Fl_Group *mixer = new Fl_Group(8, 60, 984, 576, "Mixer");
mixer->begin();
new Fl_Box(24, 84, 940, 40,
"Mixer placeholder — channels and meters are not implemented yet.");
mixer->end();
Fl_Group *graph = new Fl_Group(8, 60, 984, 576, "Modular Graph");
graph->begin();
new Fl_Box(24, 84, 940, 40,
"Modular graph placeholder — the C graph API is the current prototype.");
graph->end();
tabs->end();
end();
resizable(tabs);
}
void MainWindow::show(int argc, char **argv)
{
Fl_Double_Window::show(argc, argv);
}

12
src/gui/main_window.hpp Normal file
View file

@ -0,0 +1,12 @@
#ifndef XTLITE_MAIN_WINDOW_HPP
#define XTLITE_MAIN_WINDOW_HPP
#include <FL/Fl_Double_Window.H>
class MainWindow : public Fl_Double_Window {
public:
MainWindow();
void show(int argc, char **argv);
};
#endif

5
src/midi/README.md Normal file
View file

@ -0,0 +1,5 @@
# MIDI and events
MIDI input, MIDI clips, event scheduling, panic behavior, and device handling
are not implemented yet. Define a bounded event representation and explicit
sample-offset semantics before adding real-time event delivery.

View file

@ -0,0 +1,5 @@
# Persistence
Project save/load is intentionally unimplemented in this scaffold. The project
format must be versioned, validated, and saved atomically. File I/O must never
occur in the audio callback. See `docs/PROJECT_FORMAT.md`.

1
src/project/README.md Normal file
View file

@ -0,0 +1 @@
Project domain model will live here. Keep it in C and separate from FLTK widgets.

5
src/util/README.md Normal file
View file

@ -0,0 +1,5 @@
# Utilities
Put small, reusable C helpers here only when they have a clear owner and tests.
Avoid creating a generic utility framework. Validate sizes and allocation
arithmetic, and document ownership for allocated data.

4
tests/fixtures/README.md vendored Normal file
View file

@ -0,0 +1,4 @@
# Test fixtures
Store small, redistributable fixtures here. Document their origin and license.
Do not commit large recordings or assets with unclear licensing.

View file

@ -0,0 +1,11 @@
# Integration tests
Planned integration coverage:
- deterministic offline render;
- repeated engine initialization and shutdown;
- graph replacement while processing;
- project save/load round trip;
- backend disconnect/reconnect;
- MIDI panic and transport stop.
Do not mark these tests as passing until they exist and run.

43
tests/unit/test_core.c Normal file
View file

@ -0,0 +1,43 @@
#include "xtlite/xtlite.h"
#include "xtlite/xtlite_engine.h"
#include <math.h>
#include <stdio.h>
#define CHECK(condition) do { \
if (!(condition)) { \
(void)fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \
return 1; \
} \
} while (0)
int main(void)
{
xt_app *app = NULL;
xt_graph *graph = NULL;
size_t node_a = 0U;
size_t node_b = 0U;
float input[4] = {1.0f, -1.0f, 0.5f, 0.0f};
float output[4] = {0.0f, 0.0f, 0.0f, 0.0f};
CHECK(xt_app_create(&app) == XT_OK);
CHECK(app != NULL);
xt_app_destroy(app);
CHECK(xt_graph_create(4U, &graph) == XT_OK);
CHECK(xt_graph_add_gain(graph, 0.5f, &node_a) == XT_OK);
CHECK(xt_graph_add_gain(graph, 2.0f, &node_b) == XT_OK);
CHECK(xt_graph_connect(graph, node_a, node_b) == XT_OK);
CHECK(xt_graph_validate(graph) == XT_OK);
CHECK(xt_graph_process_offline(graph, input, output, 4U, 1.0f) == XT_OK);
CHECK(fabsf(output[0] - 1.0f) < 0.0001f);
CHECK(fabsf(output[1] + 1.0f) < 0.0001f);
CHECK(fabsf(output[2] - 0.5f) < 0.0001f);
CHECK(xt_graph_connect(graph, node_b, node_a) == XT_OK);
CHECK(xt_graph_validate(graph) == XT_ERR_GRAPH_CYCLE);
xt_graph_destroy(graph);
(void)puts("All xtlite core tests passed.");
return 0;
}