Initial commit.
This commit is contained in:
commit
1d8f869b5b
39 changed files with 1671 additions and 0 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
35
AGENT.md
Normal 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
17
LICENSE
Normal 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
60
Makefile
Normal 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
60
README.md
Normal 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
38
docs/ARCHITECTURE.md
Normal 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
27
docs/AUDIO_ENGINE.md
Normal 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
18
docs/GUI.md
Normal 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
723
docs/IDEA.md
Normal 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
18
docs/PROJECT_FORMAT.md
Normal 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
42
docs/ROADMAP.md
Normal 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.
|
||||
23
docs/decisions/0001-modular-engine-first.md
Normal file
23
docs/decisions/0001-modular-engine-first.md
Normal 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.
|
||||
20
docs/decisions/0002-c-core-fltk-gui.md
Normal file
20
docs/decisions/0002-c-core-fltk-gui.md
Normal 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.
|
||||
19
docs/decisions/0003-realtime-thread-rules.md
Normal file
19
docs/decisions/0003-realtime-thread-rules.md
Normal 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
4
examples/README.md
Normal 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
26
include/xtlite/xtlite.h
Normal 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
|
||||
44
include/xtlite/xtlite_engine.h
Normal file
44
include/xtlite/xtlite_engine.h
Normal 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
|
||||
22
include/xtlite/xtlite_project.h
Normal file
22
include/xtlite/xtlite_project.h
Normal 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
|
||||
29
include/xtlite/xtlite_types.h
Normal file
29
include/xtlite/xtlite_types.h
Normal 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
|
||||
7
include/xtlite/xtlite_version.h
Normal file
7
include/xtlite/xtlite_version.h
Normal 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
8
src/backend/README.md
Normal 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
1
src/core/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Core lifecycle and public C API implementation.
|
||||
46
src/core/xtlite.c
Normal file
46
src/core/xtlite.c
Normal 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
1
src/dsp/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Small, independently testable DSP primitives implemented in C.
|
||||
15
src/dsp/gain.c
Normal file
15
src/dsp/gain.c
Normal 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
1
src/engine/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Offline and real-time processing coordination. Respect callback safety rules.
|
||||
13
src/engine/offline_engine.c
Normal file
13
src/engine/offline_engine.c
Normal 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
1
src/graph/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Graph nodes, ports, connections, validation, and compiled execution plans.
|
||||
189
src/graph/graph.c
Normal file
189
src/graph/graph.c
Normal 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
10
src/gui/main.cpp
Normal 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
52
src/gui/main_window.cpp
Normal 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
12
src/gui/main_window.hpp
Normal 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
5
src/midi/README.md
Normal 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.
|
||||
5
src/persistence/README.md
Normal file
5
src/persistence/README.md
Normal 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
1
src/project/README.md
Normal 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
5
src/util/README.md
Normal 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
4
tests/fixtures/README.md
vendored
Normal 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.
|
||||
11
tests/integration/README.md
Normal file
11
tests/integration/README.md
Normal 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
43
tests/unit/test_core.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue