Standing goal: fill all gaps from the 10-agent SOTA gap analysis so Euterpe is the most SOTA, industry-leading DAW in the world — across AI integration/utilization and UI/UX. Work autonomously, one slice at a time, commit+push both branches per slice.
Core insight from the audit: the engine + AI/DSP libraries are mostly REAL
and tested; the dominant gap is that the React DAW (apps/euterpe-studio-web)
wires almost none of it. So most slices are "surface existing, tested
capability in the DAW UI", a few are genuinely-new builds (arrangement timeline,
on-device inference forward pass).
Constraints: 16 GB Mac — unit-test logic (pure helpers) + tsc; defer the live
in-browser pixel pass (canvas/React code is written + type-checked, not
pixel-run). Fail-closed seams for any model/cred-bound path. --no-verify
commits (pre-commit nx hangs). Push branch + :main every slice.
Sequencing (value-per-effort, dependency-aware)#
Wave 1 — Pure-UI / wiring wins over finished backends (fast, self-contained)#
- Automation curve editor (UI/UX P0, M) —
AutomationLaneSVG editor over the existing breakpoint reducer/engine (volume/pan/cutoff/sends/master). Replace the 3 macro buttons with draw/move/delete breakpoints + per-lane param select. Pure helpers tested. - Waveform + spectrum visualization (UI/UX P1, M) — canvas
WaveformViewfrom the retainedsampleBuffersRef(peak min/max bins) with a draggable start marker; masterSpectrumViewfrom an FFT band array added to the meter snapshot. - Generator-track live steering in the DAW (UI/UX P1, M) — lift the proven
/realtimesteering (prompt/drums/style-ref + keyboard→generator notes) into the DAW generator panel, reusing realtime-mrt2 controller + stream bridge. - Touch/pointer migration (UI/UX P1, M) — piano-roll + step-grid
onMouse*→ Pointer Events +touch-action:none+ ≥24px hit targets (WCAG 2.2). - Light / high-contrast theme (UI/UX P3, S) — parameterize
theme.tsinto selectable token sets (dark/light/high-contrast) +prefers-color-scheme+ toggle. - Polyphonic transcription wiring (AI P3, M) — call
@euterpe/transcribe's polyphonic multi-F0 path into a multi-note piano-roll clip + per-note confidence overlay. - Mastering/mix-assistant "apply fixes" + reference match + LUFS target (AI
P1, M) — wire
@euterpe/mastermix-assistant recommendations (dispatch inserts/EQ/comp) + reference-track upload + streaming-LUFS target into the mix-report/master UI.
Wave 1 build log addendum#
- W1.7a STREAMING-LUFS TARGET ON MASTER EXPORT (AI P1, part 1) — DONE. New
loudness-target.tswires@euterpe/master's real loudness-preset library (Spotify −14, Apple −16, YouTube, club, …) +normalizeToLufs(gain to the platform target, capped by its true-peak ceiling; silent input untouched) — 4 vitest. Transport bar gains a "LUFS: off / platform" selector; masterExport measures the bounce (analyzeBounce) + normalizes to the chosen target. Closes the "explicit LUFS target = baseline" part of the gap. 389 app tests; tsc clean. REMAINING (W1.7b, deferred to Wave 2 — genuinely L-effort): reference-track match (needs per- band master+reference spectra → matchEq → N-band EQ application on the master) and the 15-subtask mix-assistant "apply fixes" (needs per-track render+analysis → TrackDescriptors → map recommendations → DawActions). Both are real libraries (@euterpe/masterfrontierMasteringmatchEq+ mixAssistant) already present; the work is the analysis-build + action-mapping. - W1.7b REFERENCE-TRACK TONE MATCHING ON MASTER EXPORT (AI P1, part 2) — DONE.
Extracted a shared
fft.ts(radix-2 FFT + Hann magnitude spectrum; transcribe now reuses it). Newreference-match.ts:bandLevelsDb(9 octave-band levels from averaged Hann frames),applyMatchEq(real RBJ peaking-EQ biquads per band), andreferenceMatchMasterwhich analyzes master + reference, calls@euterpe/masterfrontierMastering.matchEqfor the smoothed/clamped per-band moves, and EQs the master toward the reference — 3 vitest (band detection, EQ boost, tonal-balance shift toward a bright vs bass-heavy reference). Transport bar gains a "⤒ Ref" upload; masterExport decodes the reference at the engine's rate (auto-resampled) + tone-matches before the LUFS target. The mastering gap's reference-match part is now closed; only the 15-subtask mix-assistant "apply fixes" remains (Wave 2). 392 app tests; tsc clean.
Wave 2 — AI table-stakes (model/provider-bound, fail-closed)#
- Generation→DAW bridge (AI P0, L) — a DAW "Generate" surface calling the existing BFF generation job pipeline (Suno/Udio) → import render as audio track (+ stems + MIDI when available). Fail-closed without creds.
- Stem separation in the DAW (AI P0 / UI/UX P1, XL) — real separation
backend behind the
stem-separation-sotacontract (WASM/ONNX Demucs or BFF provider) → land stems as tracks; "Split to stems" in SamplerPanel + command verb. - Audio/symbolic inpainting "regenerate region" (AI P2) — wire symbolic genesis inpainting into the piano-roll ("regenerate these bars"); audio inpainting via BFF provider, fail-closed.
Wave 2 build log#
- W2.10a SYMBOLIC "REGENERATE BARS" INPAINTING (AI P2, the cheap half) — DONE.
Wired
@euterpe/genesis's real symbolic inpainting into the piano roll. Newclip-inpaint.ts:regenerateClipRegionbridges ClipNote↔genesis MusicNote, partitions notes into before/in-region/after, callsinpaintSection(scale-aware, context-conditioned), and splices the regenerated region back;deriveKeymaps a track's scale-lock root+scale → a genesis key. 3 vitest (region regenerated, before/after preserved, empty-region no-op). Piano roll gains a "✨ Regen ½" button (AI-regenerates the 2nd half conditioned on the 1st). Cross-package wiring: added a narrow@euterpe/genesis/inpaintingsubpath export (avoids the heavy genesis barrel), app dep + vitest alias + transpilePackage + tsconfig path. 395 app tests; tsc clean. (The audio half of inpainting — region regenerate on a waveform — is provider/model-bound, Wave 2 cont.)
Wave 3 build log#
-
W3.11a SONG-LENGTH-AWARE BOUNCE/MASTER/ANALYZE/STEMS (UI/UX P0, arrangement prerequisite) — DONE. Removed the hard-coded 4-bar window (
(4*4*60)/bpm) at all 4 render sites. Newsong-length.ts:songLengthBeats(longest content across tracks — pattern bar length or note- clip length — plus any enabled loop, rounded up to a whole bar, 4-bar floor) +songLengthSeconds. 4 vitest. Now a 16-bar piano-roll clip bounces/masters/analyzes in full instead of being cut to 4 bars. 399 app tests; tsc clean. (The full multi-track clip-placement ArrangementView + engine per-clip scheduling is the remaining XL arrangement build.) -
W1.7c MIX-ASSISTANT "APPLY FIXES" — AUTO-BALANCE (AI P1) — DONE. The mix report was read-only; now an "⚖︎ Auto-Balance" button renders every (unmuted) track offline post-fader, measures its true RMS/peak/spectrum, builds a real
@euterpe/masterTrackDescriptorper track (instrument inferred from name → spectral fallback; 8-band frequency profile via the shared FFT; dominant freq; crest factor), runsmixAssistant.suggestLevelBalancing, and dispatches the resultingsetTrackGainmoves (clamped −24…+12 dB, sub-0.5 dB nudges ignored). Because the render is post-fader, the library'sadjustmentDbis exactly the fader delta — nothing fabricated. Newmix-assistant-bridge.ts(buildTrackDescriptor/inferInstrument/autoBalanceActions) + 6 vitest; abalance-notesummarizes the assessment. 405 app tests; tsc clean. -
W2.8a SYMBOLIC GENERATION INTO THE DAW — "✨ Generate" CLIP (AI P0, the local/cred-free half of the generation→DAW bridge) — DONE. The piano roll could only inpaint half a clip; now a "✨ Generate" button fills the whole clip with a fresh melody from
@euterpe/genesis's real note model. Newgenerate-clip.tsderives a diatonic progression from the track's key — real functional harmony (I–V–vi–IV major / i–VI–III–VII minor, voiced from the scale's own triads) — feeds it togenerateMelodyFromChords, and converts theMelodyNotes toClipNotes (velocity re-scaled, trimmed to length, sorted). No creds (genesis is on-device, real inference). Added a narrow@euterpe/genesis/melody-gensubpath export (pkg + vitest alias + tsconfig path) to avoid the heavy barrel. 8 vitest (progression theory verified note-for-note + generated-clip invariants). 413 app tests; tsc clean. (The cloud generation half — Suno/Udio audio onto a track via the BFF — remains, cred-bound.) -
W1.7d AUTO-MIX = LEVEL + PAN (AI P1, extends W1.7c) — DONE. The auto-balance button is now "⚖︎ Auto-Mix": one render pass builds the per-track descriptors once and feeds BOTH
mixAssistant.suggestLevelBalancingandmixAssistant.suggestPanning, dispatching the combinedsetTrackGain+setTrackPanmoves (pan clamped −1…1, sub-0.05 moves ignored). NewautoPanActions/autoMixActionsin the bridge (+2 vitest); the note reports the level + stereo- field assessments. 415 app tests; tsc clean. -
W2.8b MELODIC VARIATION — "✨ Vary" (AI P1, completes the local composition suite generate/ vary/regen) — DONE. A variation-type selector + "✨ Vary" button transform the current clip with genesis's real
generateMelodicVariation(invert / retrograde / ornament / simplify / augment / diminish / octave ±). NewvaryClipNotesingenerate-clip.tsbuilds aMelodyfrom the user's own clip, runs the transform, converts back, and resizes the clip to fit (whole bars). +4 vitest (transpose shifts every pitch by the interval; retrograde preserves the pitch multiset; inversion changes contour; empty-clip + whole-bar length). 419 app tests; tsc clean. -
W3.13 LLM COPILOT FOR THE COMMAND BAR (AI P0 differentiator) — DONE (real backend, fail-closed). The command bar now asks an LLM to translate free-text into
DawActions before falling back to the deterministic parser. Built end-to-end inside studio-web: a real Next.js API routeapp/api/copilot/route.tscalls the Anthropic Messages API (tool use) and is fail-closed — noOSHUN_ANTHROPIC_API_KEY/ANTHROPIC_API_KEY→ 503copilot_not_configuredand the bar uses the parser.copilot.tsis the security boundary:validateActionsre-checks every model action against an explicit 23-type whitelist with per-field type + range clamps and 0-based track-bounds, dropping anything unknown/malformed (server-side AND client-side, defense in depth);requestCopilotlatches off on a 503 so it never re-hammers an unconfigured deploy. 8 vitest (clamps, drops unknown/out-of-range, every whitelisted type validates, 503-latch + success + network-error fallback). 427 app tests; tsc clean (tsc resolves the route→src import). Deploy: setOSHUN_ANTHROPIC_API_KEY(+ optionalOSHUN_EUTERPE_COPILOT_MODEL, defaultclaude-haiku-4-5-20251001). -
W2.9 IN-DAW STEM SEPARATION via real DSP (AI P0 headline) — DONE (classic algorithms, honestly labelled — not neural Demucs). New
stem-separation.ts: (1) HPSS (Fitzgerald median-filter harmonic/percussive) — STFT (FRAME 2048 / HOP 512 / periodic Hann), per-bin time-median → harmonic + per-frame freq-median → percussive, complementary soft (Wiener) masks summing to 1, inverse-STFT overlap-add with COLA normalization (addediffttofft.ts); (2) mid/side center extraction (exact M/S). The SamplerPanel gains "✂ Split stems" on a loaded sample → HPSS → two WAV downloads (harmonic + percussive). 6 vitest proving real behavior: sustained tone → harmonic (RMS > 4× percussive), broadband clicks → percussive, harmonic+percussive reconstructs the input (<0.02 interior error), exact M/S reconstruction. 433 app tests; tsc clean. (A neural Demucs/ONNX backend for vocal/bass/drums/other remains a deploy-bound upgrade; HPSS + M/S are the real on-device baseline.) -
W2.11 KEY + TEMPO DETECTION from audio (AI P1 — a SOTA DAW analyzes imported audio) — DONE. New
audio-analysis.ts(real MIR):detectKey= Krumhansl–Schmuckler key-profile correlation over a 12-bin chromagram (FFT magnitude → pitch classes, A1–D8 band);detectTempo= autocorrelation of a spectral-flux onset envelope, peak-picked in the 60–200 BPM range (honest 0-BPM on a pulseless/ short signal). SamplerPanel "🔍 Detect key / tempo" on a loaded sample → scale-locks the track to the detected key + sets project tempo, shows the result. 6 vitest (chroma peaks at the played PCs; C-major triad → C major; A-minor triad → A minor; 120/100-BPM click tracks recovered to frame resolution; 0 BPM for a short signal). 439 app tests; tsc clean. -
W1.8 NOTE-CLIP GROOVE — quantize + humanize (UI/UX P1, standard DAW edit tools) — DONE. The piano roll gains "⊞ Quantize" (snap to 1/16, reusing the recorder's
quantizeClip) and "✎ Humanize" (newgroove-clip.ts: bounded ± timing/velocity offsets via a seeded mulberry32 PRNG — real controlled randomization, deterministic + testable, pitch/length untouched, start≥0, vel∈[0,1]). 5 vitest (grid snap; seed-determinism; in-bounds; non-no-op; velocity rail clamp). 444 app tests; tsc clean. -
W2.8c HARMONIZE — counter-melody generation (AI P1, rounds out the composition suite: generate / vary / regen / harmonize) — DONE. Piano-roll "✨ Harmonize" runs genesis's real
generateCounterMelodyagainst the clip (contrary motion) and merges the harmony voice in, making the clip polyphonic.harmonizeClipNotesingenerate-clip.ts; +1 vitest (real counter-melody, deterministic via genesis's fixed seed, differs from the source line, empty-in→empty-out). 445 app tests; tsc clean.
Wave 3 — Big new builds#
- Arrangement / song timeline (UI/UX P0, XL) —
ArrangementView(SVG) over a new clip-placement reducer adopting@euterpe/studio/timelinetypes; bounce/master/analyze respect real song length (not hardcoded 4 bars). Session/launcher grid as a follow-on. - Take-comping / take lanes (UI/UX P2, L) — loop-record appends takes to a lane array + swipe-comp selection; built on the arrangement substrate.
- LLM copilot (AI P1, M) — front the deterministic command parser with a
BFF LLM intent-resolver (Anthropic SDK, BYO-key, fail-closed) emitting exact
DawAction[]+ conversation memory + transcript UI; parser stays the safe validator/fallback. - On-device neural realtime inference (AI P0, XL) — bridge
candle/ONNX into the mrt2-native Rust crate for a real forward pass (open-weights Magenta RT / distilled), or package the MLX sidecar as a shipping deploy artifact. The highest-leverage real-model work. - Collaboration (UI/UX P2 / AI P2, XL) — share-by-link project rehydrate
(BFF) first; CRDT co-edit (Yjs over the reducer) later; reuse
@euterpe/collab.
Per-slice protocol#
Pure helpers (DOM-free vitest) + a type-checked SVG/React component; wire
reducer/engine; run tsc + the touched specs; adversarial stub scan; build-plan
entry; commit + push both branches.
Build log (append per slice)#
- W1.1 AUTOMATION CURVE EDITOR (UI/UX P0) — DONE. Pure
automation-lane-helpers.ts(one generic{beat,value}model over all 6 lanes; lin/log value↔norm; pointer↔point with ¼-beat snap+clamp; add/move/remove keep-sorted; pixel-space hitTest; typed↔generic bridge for db/pan/hz/level) — 6 vitest.AutomationLaneSVG editor (Pointer Events → also touch-ready): click-add, drag-move, dbl/right-click-delete, per-lane param select (volume/pan/cutoff/reverb-send/delay-send/master-gain), polyline + handles, clear. Wired into daw-app's left column for the selected track; dispatches the EXISTING setXAutomation actions (zero engine work — reducer+engine already interpolate). Channel-strip macro buttons kept as quick-preset starts. 377 app tests; tsc clean; stub scan clean. - W1.2 WAVEFORM + SPECTRUM VISUALIZATION (UI/UX P1) — DONE. Pure
waveform-helpers.ts(computePeaks→ per-bin min/max for waveform thumbnails;frequencyBars→ log-spaced [0,1] bars from an FFT byte array) — 4 vitest.audio-visualizers.tsx:WaveformView(canvas peaks of the retained decoded buffer + draggable start marker via Pointer Events → drives the sampler start frac) +SpectrumView(rAF loop over an AnalyserNode, log bars, green/amber/red). AudioEngine gainscreateOutputAnalyser()— a parallel analyser tap off the worklet node (no wasm change). Wired: WaveformView in SamplerPanel (sampleVersion-bumped re-render on load/record), SpectrumView in the master column. 381 app tests; app + engine-web tsc clean; stub scan clean. - W1.3 GENERATOR-TRACK LIVE STEERING (UI/UX P1) — DONE. Lifted the proven
/realtime MRT2 steering into the DAW. New
GeneratorPanelcomponent: when connected, a text-prompt input (Enter/Steer → sendTextUpdate), a Drums toggle (sendDrumToggle), and a style-ref URL (sendAudioRef) — right where the generated audio is mixed. daw-app keeps the raw WebSocket + acreateMrt2Session()controller state, sends control envelopes over the live socket, and binds the on-screen keyboard so played notes condition the generator (sendNoteOn/Off in recordDispatch). All reuse the unit-tested realtime-mrt2 controller builders + the existing stream bridge. Fail-closed (no socket → no-op). 381 app tests; tsc clean. - W1.4 TOUCH / POINTER MIGRATION (UI/UX P1) — DONE. piano-roll.tsx (the only
mouse-drag surface) migrated
onMouse{Down,Move,Up,Leave}→ Pointer Events withsetPointerCapture+touchAction:'none'on the SVG, so note add/move/resize works on tablet/stylus. Verified step-grid is fullyonClick-based (touch-native) and NOonMouse*remains anywhere in the DAW; the editors built this wave (automation-lane, waveform start-marker, generator) + the keyboard already use Pointer Events. The whole editing surface is now touch-operable. 381 app tests; tsc clean. - W1.5 LIGHT / HIGH-CONTRAST THEME (UI/UX P3) — DONE. theme.ts parameterized
into 3 token sets (dark/light/highContrast) with
applyThemeMode(mutates the shared tokens + panel/ heading in place;button()already recomputes live),preferredThemeMode()fromprefers-color-scheme, andTHEME_SETS. daw-app holds athemeModestate (initial = preferred), retones on change, and a header toggle cycles dark→light→high-contrast; the whole tree re-renders + reads the live tokens (no per-component churn). High-contrast = pure white-on-black for low-vision/light-sensitive users (complements the existing ARIA layer). 3 theme tests; 384 app tests; tsc clean. - W1.6 POLYPHONIC TRANSCRIPTION (AI P3) — DONE, with a real fix. The DAW
transcribed monophonically (estimateF0, one note/step). Probed the library's
detectPolyphonicand found it BROKEN — iterative F0+sinusoid-subtraction loops on one pitch (its own comment admits "simplified"; estimateF0 was ~10 Hz off + the subtraction is phase/freq-imprecise, so a 150+800 Hz mix returned the SAME note 4×). Rather than wire a broken path, implemented a real spectral peak-picking detector in transcribe.ts: Hann+radix-2 FFT → parabolic-interpolated spectral peaks in-band → skip near-integer harmonics of chosen fundamentals → top-N MIDI notes. transcribeToPattern gainsmaxVoices(1=mono, >1=poly); daw-app's transcribeFile now uses maxVoices 4 so chords transcribe (multi-note steps play as the engine already supports chords). Test: 150+800 Hz → mono=1 note, poly recovers BOTH (MIDI 50 + 80, verified). 385 app tests; tsc clean.