The consolidated inventory of what the app can do today, what is only partly there, and what is agreed but not built — every row naming the file that proves it.
Note. This page's header line records when the inventory was first written. The tables name
the release each row shipped in, and several rows are newer than that header (0.3.0-alpha.275 … .278).
Written against 0.3.0-alpha.272 (2026-11-01). This page is the consolidated inventory: what the
app can do today, what is only partly there, and what is agreed but not built. PROGRESS.md records
what each release changed; this page answers "what is in it, right now".
How to read the state column
| word |
meaning |
| shipped |
in the packaged build, covered by the test suites |
| partial |
it works, with a limit named in the row |
| planned |
agreed and written down, not built |
| not built |
deliberately dropped (with the reason recorded) |
| unverified |
written, but never run on the target (say so, never claim it works) |
Evidence names the module, file or document that proves the claim — every row can be checked.
| Feature |
What it does |
State |
Evidence |
| Windows build |
MSVC, Qt 6.8, packaged app + setup.exe |
shipped |
tools/package.ps1, tools/installer.iss |
| Windows installer |
per-user install (no admin prompt), Start Menu entry, uninstaller; verified by silent install → run → uninstall |
shipped |
tools/make_installer.ps1, docs/implementation/building.md |
| Portable Windows zip |
dist/Nebula-<version>/ + .zip, latest version only |
shipped |
tools/package.ps1 (keep-1 prune) |
| Linux build |
GCC, Qt 6.8 (aqtinstall), tests + app |
shipped |
.gitea/workflows/ci.yml, references/wsl-linux-verification.md |
Linux .deb |
installs to /opt/nebula, menu entry, nebula command, bundles its own Qt; verified by install → run → remove |
shipped |
tools/make_installer_linux.sh |
Linux portable .tar.gz |
the same tree, unpack anywhere |
shipped |
same script |
macOS .dmg |
macdeployqt bundle + disk image script |
unverified — no Mac in the toolchain, no CI job |
tools/make_installer_macos.sh (its own header says so) |
| macOS MIDI input |
— |
not built (Windows uses WinMM, Linux ALSA; no CoreMIDI) |
docs/plan/keyboard-shortcuts.md, HANDOFF |
| Headless project render |
render a saved .nebproj to WAV with no window |
shipped (example tool) |
examples/render_project_file.cpp |
2. The app's surfaces
| Feature |
What it does |
State |
Evidence |
| Timeline (arrangement) |
tracks, clips, drag/move, split, quantise, zoom, loop range, playhead |
shipped |
app/TimelineView.qml, PianoRoll.qml |
| Clip Launcher (Session) |
scene grid, clip triggers, launch quantise, per-clip loop |
shipped |
app/SessionView.qml |
| Mixer |
channel strips, faders, pan, meters, groups, returns, master |
shipped |
app/MixerView.qml |
| Node view |
build your own instrument by wiring modules (NOSI graph) |
shipped |
app/NodeView.qml, docs/plan/node-view-and-nosi.md |
| Browser panel |
instruments, effects, samples, content library, search |
shipped |
app/BrowserPanel.qml |
| Detail panel + cards |
per-track cards: Instrument, Devices/FX, Note Effects, Analyser, Mastering, Automation, Log |
shipped |
app/DetailPanel.qml + *Card.qml |
| Command palette |
Ctrl+K action list with shortcuts |
shipped |
app/CommandPalette.qml |
| Settings sheet |
11 pages: appearance, audio, MIDI, instrument, local API, export, logging, project, bug report, keys & shortcuts, about |
shipped |
app/SettingsPanel.qml + sections |
| Themes |
built-in themes + user theme folder + import/export; contrast-checked |
shipped |
app/theme/*, tools/check_docs.py theme rules |
| Help strip |
hover help for every control ("point at anything") |
shipped |
app/Main.qml |
| Translations |
i18n skeleton + string externalisation |
partial (skeleton; strings extractable, no shipped translations yet) |
tools/translations.ps1 |
| Accessibility |
accessible names/roles on controls, checked by a guard |
shipped |
check_qml_accessible guard |
| Crash report dialog |
in-app report to the project's endpoint, with a key |
shipped |
bug-report settings page, docs/plan/distribution-and-support.md |
3. Instruments
| Feature |
What it does |
State |
Evidence |
| Modular synth voices |
93 lead/key patches (Modular Lead, Square Lead, Acid Saw, Tine E-Piano, …) |
shipped |
plugins/src/patch.cpp (voice_patch) |
| Bass patches |
64 bass patches (Sub Bass, 808 Sub, Minimoog Bass, Motown Bass, Bass Guitar, …) |
shipped |
same (categorised_voice("bass", …)) |
| Pad patches |
61 pads (Warm Strings, Glass Pad, Aurora Pad, Choir Pad, …) |
shipped |
same (pad_patch) |
| FM instruments |
45 FM patches (FM E-Piano, FM Bell, FM Bass, FM Brass, …) |
shipped |
same (fm) |
| 263 factory patches total, four families |
— |
shipped |
factory_patches() |
| Sampler |
one sample per track, root note, tune, attack/release, loop, start/end |
shipped |
app/SamplerCard.qml, nebula.services.kSamplerInstrument |
| Drum Rack |
16 pads, per-pad sample + gain/tune/release/start/end, choke groups, kit load/save (.nebkit), slice-a-sample to pads |
shipped |
app/DrumRackCard.qml, services/src/track_instrument.cpp |
| Instrument Rack |
layered instruments with per-layer gain, level control, add/remove/reorder layers |
shipped |
app/RackCard.qml |
| 24 drum-machine kits |
606, 707, 808, Acid, Breaks, Cinematic, Deep House, Drum and Bass, Electro, Funk, Garage, Hip-Hop, Jazz, Jungle, Kitchen Keys, Latin, Minimal, Orchestral, Percussion, Reggae, Rock, Techno, Trance, Trap |
shipped |
dist/…/patches/*Kit.nebpatch |
| 292 patch files ship with the build |
factory patches + ready-made presets |
shipped |
dist/…/patches/ |
| Per-instrument presets |
save/load a patch's knob settings (.nebpatch) |
shipped |
app/PatchFilesSection.qml |
| Rhytmix |
voice-to-MIDI (sing → notes), chord naming with plain-English explanation, next-chord suggestions, Play chords / inversions / voicings (block, strum, arpeggio, humanized), Scale lock, Hold, Strum |
shipped |
app/RhytmixCard.qml |
| Groover |
take the groove of one clip (Copy / Humanize / Shuffle / Auto / Templates) and write a new clip with that feel |
shipped |
app/GrooverCard.qml, app/src/groover_controller.cpp |
| Step sequencer + grids |
melodic step grid, drum grid, synth grid, piano roll |
shipped |
app/StepSequencer.qml, PianoRoll.qml |
4. Effects
33 built-in effects (plugins/src/device_preset.cpp → track_effect_ids()), all with factory presets
and per-effect preset save/load:
| Group |
Effects |
| Time |
nebula.delay, nebula.pingpong, nebula.comb, nebula.stutter, nebula.haas |
| Space |
nebula.reverb, nebula.convolutionreverb |
| Tone |
nebula.eq, nebula.autofilter, nebula.tremolo, nebula.autopan, nebula.vibrato |
| Dynamics |
nebula.compressor, nebula.multibandcompressor, nebula.gate, nebula.expander, nebula.transientshaper, nebula.deesser, nebula.limiter, nebula.lookaheadlimiter, nebula.utility |
| Drive / colour |
nebula.distortion, nebula.saturator, nebula.wavefolder, nebula.bitcrusher |
| Modulation / pitch |
nebula.chorus, nebula.flanger, nebula.phaser, nebula.ringmod, nebula.octave, nebula.pitchshifter, nebula.vocoder, nebula.granular |
| Feature |
What it does |
State |
Evidence |
| Per-track effect chains |
add / remove / reorder / bypass, per-device preset |
shipped |
app/DevicesPanel.qml, devices.* API |
| Master chain |
the same chain on the master, with a mastering card and a Key row |
shipped |
app/MasteringCard.qml |
| Sidechain / key |
any keyable device can be keyed from another track (compressor ducking, gates); works offline and live, on tracks, buses and the master |
shipped (0.3.0-alpha.270–272) |
services/src/sidechain_plan.cpp, tests/services/test_sidechain*.cpp |
| Latency compensation |
devices that report latency (look-ahead limiter) are aligned; PDC across tracks |
shipped |
nebula.lookaheadlimiter (latency_samples()), engine PDC tests |
| Note effects |
per-track MIDI note-effect chains (arp/scale/etc.) with a picker and settings |
shipped |
app/NoteEffectsCard.qml |
| Analysers |
spectrum + loudness readings from the real render |
shipped |
app/AnalyserCard.qml |
5. The audio engine
| Feature |
What it does |
State |
Evidence |
| Audio backends |
WASAPI (Windows), ALSA (Linux), device pick, sample rate, buffer size, exclusive mode |
shipped |
docs/implementation/asio.md, settings audio.* |
| Audio input |
record and monitor an input, audio tracks and audio clips |
shipped |
addAudioClip, app/AudioSection.qml |
| Transport |
play/stop, metronome, loop range, position, tempo |
shipped |
TransportBar.qml, transport.* API |
| Tempo map |
tempo events, beat ↔ sample conversion |
shipped |
TempoMap, project_file tempo_events |
| Offline render |
render the project (whole or a range) to audio, run-to-run bit-identical |
shipped |
services/src/project_renderer.cpp, docs/adr/0022-project-to-audio-bridge.md |
| Live backing render |
the app renders what you hear in the background and swaps it in |
shipped |
app/src/render_worker.cpp |
| Live device state across rebuilds |
editing a knob no longer restarts a device: delay tails, envelope levels, oscillator phase and held notes survive a palette rebuild |
shipped on Windows (0.3.0-alpha.272); open Linux defect — see below |
engine/include/nebula/engine/node_cache.h, tests/engine/test_node_cache.cpp |
| Real-time safety |
no locks on the audio callback (audited, mutex by mutex), NEBULA_NONBLOCKING annotations, allocation-tripwire tests on Windows, RealtimeSanitizer + ThreadSanitizer jobs on Linux |
shipped |
docs/architecture/rt-lock-audit.md, tests/core/test_rt_safety.cpp |
| Multi-core render scheduler |
the offline renderer renders independent tracks at once, in waves that respect sidechain keys, and sums in one fixed order; the thread count cannot change the samples (measured 3.58x on four cores, 691 ms -> 193 ms) |
shipped (0.3.0-alpha.275) |
engine/include/nebula/engine/render_pool.h, services/src/project_renderer.cpp, ADR-0028 |
| Export |
WAV (16/24/32-bit) and MP3 (LAME loaded at run time, never linked) |
shipped |
engine/src/audio_file.cpp, libmp3lame.dll in the package |
6. The project model and files
| Feature |
What it does |
State |
Evidence |
| Command bus + undo/redo |
every edit is a command, undoable, with history info |
shipped |
model/src/command_bus.cpp, history.* API |
Project file .nebproj |
versioned JSON, unknown-field preservation, migrations |
shipped |
model/src/project_file.cpp |
| Autosave + crash recovery |
auto-saved copy restored after a kill, with a recovery dialog |
shipped |
projects/recovery.nebproj, app/Main.qml recover dialog |
| Atomic writes + rolling backups |
a kill during a save cannot corrupt the file; .bak1, .bak2, … |
shipped |
core::write_file_atomically, rotate_project_backups |
| Command-replay journal |
— |
not built (deliberately: atomic writes + recovery already meet the criterion) |
docs/plan/phases.md P3 note (AC-0281/AC-0283) |
Kit file .nebkit |
save/load a Drum Rack's pads, relocatable |
shipped |
saveKitToFile / loadKitFromFile |
| Settings file |
only non-default values are stored; portable with --data-dir |
shipped |
core/src/settings.cpp |
| Sample import |
drag a WAV into the Sampler / a Drum Rack pad; slices |
shipped |
tracks.setSamplerSample, sliceToDrumRack |
| Feature |
What it does |
State |
Evidence |
| Automation curves |
draw a knob's curve on a lane, per track |
shipped |
app/AutomationLane.qml |
| Automation recording |
arm knobs, record a pass while the transport runs, written back into curves |
shipped |
app/AutomationPanel.qml, sampleArmedKnobs |
| Clip swing |
per-clip swing amount |
shipped |
clips.setSwing |
| Humanize / feel |
via Groover templates |
shipped |
groover_controller.cpp |
| Scale lock / chord modes |
keep notes in key, one key = a chord, inversions and voicings |
shipped |
RhytmixCard.qml |
8. MIDI
| Feature |
What it does |
State |
Evidence |
| MIDI input |
several keyboards at once, hot-plug, per-device enable |
shipped |
app/MidiSection.qml |
| Hardware mapping |
a unified CC map shared by all devices, learn-by-moving |
shipped |
settings midi.cc_map |
| Computer keyboard |
play notes from the typing keyboard (A–K, Z/X octave) |
shipped |
PianoKeys.qml, help strip |
| MIDI out |
the notes the song plays and the 24-ticks-a-beat clock go out of a chosen MIDI port, so external gear plays along and follows the transport; Start/Continue/Stop with the transport |
shipped (0.3.0-alpha.274) |
engine/include/nebula/engine/midi_out_queue.h, engine/src/midi_winmm.cpp / midi_alsa.cpp, app/src/midi_controller.cpp, ADR-0027 |
| MIDI out — not built |
per-track routing to different ports, CC automation on the way out, clock input, timing finer than the 4 ms drain |
planned (Phase 11) |
ADR-0027 |
| MIDI clock input | Nebula follows external gear: pick an input as the clock source and the transport follows start/continue/stop, song position and the smoothed tempo; free-running when the clock stops | shipped (0.3.0-alpha.278) | ADR-0030 |
| MTC | MIDI Timecode in and out, including exact 29.97 drop-frame, with a frame-rate setting | shipped (0.3.0-alpha.278) | ADR-0030 |
| Program change + per-track MIDI channel | program change in selects a track's preset and out is emitted when it changes; each track has its own MIDI channel | shipped (0.3.0-alpha.278) | ADR-0030 |
| MPE | per-note member channels: each note keeps its own bend/pressure/CC74 with its own voice, +/-48 semitones, in and out | shipped (0.3.0-alpha.278) | ADR-0031 |
| Lua controller scripts | a sandboxed Lua 5.4.8 host that turns MIDI input into actions through MIDI learn's own path; scripts folder + enable switch + Reload | shipped (0.3.0-alpha.278) | ADR-0031 |
| Multi-core LIVE mixer | playback renders independent tracks in parallel waves on a real-time-safe fork/join; the mix is bit-identical at any thread count (3.13x measured) | shipped (0.3.0-alpha.278) | ADR-0028 |
9. The Control API, OSC and the AI/MCP path
| Feature |
What it does |
State |
Evidence |
| Local Control API |
JSON-RPC 2.0 over a localhost socket, token-guarded, 64 methods (app.ping, project.*, tracks.*, clips.*, devices.*, transport.*, history.*, instruments.*), events pushed to the client (nebula/render.finished, nebula/project.changed) |
shipped |
docs/adr/0024-control-api-v1.md, app/src/api_controller.cpp |
| Off by default |
the API is opt-in (settings → Local API), token generated per machine, localhost only |
shipped |
core/src/settings.cpp api.* |
| OSC door |
the same actions over UDP for hardware controllers |
shipped |
api_server/src/osc.cpp |
| Every API action is a command |
scripts get exactly the undoable edits the UI makes — an AI cannot do more than a script could |
shipped |
ADR-0023 rule 4 |
| Headless render from a script |
project.renderToWav renders in the background and reports the written path |
shipped |
api_controller.cpp |
| AI assistant plugin (chat panel that drives the app: compose from a prompt, arrange, mix, export, build instruments) |
— |
planned, closed-source add-on — deliberately outside the open repo |
docs/plan/ai-plugin.md |
| MCP server |
an MCP server as a client of the same API door |
planned |
ADR-0024 ("the MCP server as a client of this same door") |
Fixed in 0.3.0-alpha.273 (was a measured limitation on 2026-11-01): instruments.list and
devices.catalogue never answered — their replies are bigger than the 8 KiB wire line and, unlike
other oversized replies, the error came back with "id": null, so a client matching replies by id
waited for an answer that had already arrived. Two changes: the server now carries the request's id on
every reply (including "the reply is too long" and an internal error), and both methods answer a
page — {"instruments": [...], "total": 291, "offset": 0} — so offset += items.size() walks the
whole catalogue. Measured on the fixed build: a 156-entry page in 0.02 s, 291/291 instruments walked,
33 devices in two pages; instruments.list also takes limit and withPresets (46 entries per page
with presets). (api_server/src/api_server.cpp, app/src/api_controller.cpp,
tests/api_server/test_api_server.cpp, tests/app/test_api_controller.cpp.)
10. Content library
| Feature |
What it does |
State |
Evidence |
| Content Library browser |
presets, kits, one-shots, loops, demo projects from a library folder with an index.json |
shipped |
services/src/content_catalog.cpp, app/BundleCard.qml |
| One-shot bundle |
the CC0 one-shot kit (33 sounds) generated into the data folder, exportable for publishing |
shipped |
--export-one-shots, examples/gen_one_shots.cpp |
| Kit loading |
load a library kit onto a Drum Rack (copies its samples into the project) |
shipped |
loadContentKit |
| Starter library |
2 kits, 4 one-shots, 2 presets, 1 demo project |
shipped (sample library; the full library is a separate repo) |
Nebula-Sound-System-Content-Library/index.json |
11. Quality gates, CI and release discipline
| Feature |
What it does |
State |
Evidence |
| Test suites |
Windows 1225 cases, Linux 907 (six Qt-free suites + the app suite) |
shipped |
tests/, PROGRESS.md |
| Performance gates |
startup to first frame < 3000 ms, idle CPU < 1 %, RAM < 300 MB — measured every packaging run |
shipped |
tools/package.ps1 (measured 2174 ms / 0.55 % / 261 MB at alpha.272) |
| QML sweep |
36 documented screens opened headlessly; any QML runtime error fails the package |
shipped |
tools/package.ps1 sweep list |
| Documentation guards |
67 probes over the docs/layers/themes/shortcuts/API/… |
shipped |
tools/check_guards_selftest.py |
| Licence compliance |
REUSE lint over every file (539/539 at alpha.272) |
shipped |
reuse lint |
| Fault plants |
break a guard on purpose, prove the test catches it, restore |
practice |
recorded in PROGRESS.md per release |
| CI |
Gitea Actions (Windows + Linux), local runs when the runner is busy |
shipped |
.gitea/workflows/ci.yml |
| Release discipline |
one feature per release, an AC id per unit, CHANGELOG/PROGRESS/CHANGELOG-ARCH/HANDOFF updated, both remotes pushed with tag parity |
practice |
PROGRESS.md, docs/standards/versioning.md |
12. Not built / known gaps (say these out loud)
Not a defect — an inconsistent incremental build (2026-11-02, explained the same day). One Linux
build failed the two audible node-cache tail cases in tests/services/test_live_state.cpp
(tail rms = 0 after a palette rebuild). A CLEAN build of the same sources passes (the whole services
suite: 268 cases), as does a rebuild, and as does the code under ASan/UBSan at -O1 and -O3;
Windows is green (1226/1226). The "adding a print fixes it" observation is explained: the print was
in a HEADER (node.h), which forces every dependent translation unit to recompile and thereby
repaired the inconsistent build. The mechanism: rsync -a preserves the Windows source mtimes, which can
be OLDER than the WSL object files, so ninja silently skips rebuilding them and the mirror runs a binary
that does not match its sources. Rule: rebuild cleanly before reporting a cross-platform
failure; only a failure that survives a clean build is a code defect. The probe recipe stays in the
repo skill (references/control-api-rig-and-capture.md) in case it ever returns.
| Gap |
Note |
|
|
| macOS |
build script only; no Mac in the toolchain, no CI job, no CoreMIDI input, no signing/notarisation. Unverified, not claimed. |
|
|
| AI assistant plugin |
planned, closed source, outside the open repo (docs/plan/ai-plugin.md). |
|
|
| MCP server |
mcp/ — a stdio MCP server (Python, the official SDK) exposing 11 tools over the existing Control API; a render tool waits for nebula/render.finished and answers with the file. Verified against the running app (AC-0334) |
shipped (0.3.0-alpha.276) |
mcp/, ADR-0029 |
| MIDI output — per-track ports, CC out, timing |
SHIPPED (0.3.0-alpha.278): per-track output ports, CC 7/CC 10 automation out, clock input (see the rows below), and a configurable drain with measured jitter |
shipped |
ADR-0027, ADR-0034 |
| Command-replay journal |
deliberately not built (atomic writes + crash recovery cover the need). |
|
|
| Plug-in hosting |
CLAP hosting SHIPPED (0.3.0-alpha.278): discovery, a host node with knobs/PDC latency/state, SEH containment. VST3 is NOT supported and cannot be (GPLv3-or-proprietary SDK vs FSL); LV2/AU and a plug-in GUI are not built. Out-of-process hosting (32-bit / foreign licences) ships as the bridge. |
ADR-0032, ADR-0033 |
|
| Translations |
skeleton only; no translated UI ships yet. |
|
|