@wenathlan/debonair 2.0.94 → 2.0.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +13 -1
  2. package/docs/.gitkeep +0 -0
  3. package/docs/competitor-dissection.md +572 -0
  4. package/docs/enginemap.md +94 -0
  5. package/docs/planning.ladder.md +1107 -0
  6. package/docs/planning.v2.28-completo.md +69 -0
  7. package/docs/planning.versions.2.0.97-3.0.md +48 -0
  8. package/package.json +25 -1
  9. package/tests/.gitkeep +0 -0
  10. package/tests/analyze.test.ts +54 -0
  11. package/tests/arrangement.test.ts +109 -0
  12. package/tests/arranger.test.ts +116 -0
  13. package/tests/beatgrid.test.ts +0 -0
  14. package/tests/dawview.test.ts +104 -0
  15. package/tests/ducking.test.ts +0 -0
  16. package/tests/family.test.ts +80 -0
  17. package/tests/familyurl.test.ts +32 -0
  18. package/tests/fingerprint.test.ts +61 -0
  19. package/tests/genrematrix.test.ts +116 -0
  20. package/tests/harmonyengine.test.ts +204 -0
  21. package/tests/katexis.test.ts +97 -0
  22. package/tests/loudness.test.ts +0 -0
  23. package/tests/midiwriter.test.ts +183 -0
  24. package/tests/mixergraph.test.ts +0 -0
  25. package/tests/motionpref.test.ts +82 -0
  26. package/tests/musictheory.test.ts +231 -0
  27. package/tests/onboardingstate.test.ts +68 -0
  28. package/tests/pianoroll.test.ts +336 -0
  29. package/tests/remixplan.test.ts +0 -0
  30. package/tests/rhythmgrammar.test.ts +205 -0
  31. package/tests/solcss.test.ts +148 -0
  32. package/tests/spectrum.test.ts +59 -0
  33. package/tests/speechalign.test.ts +0 -0
  34. package/tests/stems.test.ts +127 -0
  35. package/tests/takeevents.test.ts +129 -0
  36. package/tests/takestore.test.ts +93 -0
  37. package/tests/timelinemodel.test.ts +0 -0
  38. package/tests/tuning.test.ts +27 -0
  39. package/tests/vocalsynth.test.ts +91 -0
  40. package/tests/wavwriter.test.ts +137 -0
package/README.md CHANGED
@@ -8,9 +8,21 @@ Audio and DAW. Isolated marketing app for the operating system. Houses the katex
8
8
 
9
9
  # Debonair Sol
10
10
 
11
- Debonair is the audio workstation (DAW) of the DevThink family, running on the katexis engine. This Sol theme is the workstation's web surface — a Vite + React SPA whose library, generate, studio and settings routes drive tracks, clips, mixer strips and text-to-music render jobs stored in the same sqlite database that `schema.prisma` and `drizzle.config.ts` describe.
11
+ Debonair is the audio workstation (DAW) of the DevThink family, running on the katexis engine. This Sol theme is the workstation's web surface — a Vite + React SPA whose library, generate, studio and settings routes drive real engine plans (no mock anywhere: the studio plays the WebAudio transport of `Sol/studio/audioengine.ts` over the plans `arranger.ts` composes).
12
12
 
13
13
  ```bash
14
14
  pnpm install
15
15
  pnpm dev
16
16
  ```
17
+
18
+ ## The contract (campaign 5-b)
19
+
20
+ - **test** — `pnpm test` runs `vitest run` over `tests/` (223+ cases across 20 files, node environment). `bun test` answers green on the same suite (bun aliases the vitest API).
21
+ - **typecheck** — `pnpm typecheck` (`tsc --noEmit`) must exit 0.
22
+ - **lint** — `npx biome check .` must answer 0 errors and 0 warnings.
23
+ - **restore:entry** — the vite build emits hashed bundles beside the sources and rewrites `index.html` to the hashed face. `pnpm restore:entry` restores the source face from HEAD, with two guards: it REFUSES (exit 1, nothing touched) while HEAD itself is not the source face (`script … App.tsx`), and it REFUSES while the HEAD face still carries the Google Fonts CDN link (the type triad self-hosts via `@fontsource`, imported by `App.tsx` — the face must never ride a font CDN again).
24
+ - **build hygiene** — `.gitignore` bars every hashed emit (`/index-*.js|css`, `/favicon-*.svg|png|ico`, `/manifest-*.webmanifest`, `/icon-*.png`, `/apple-touch-icon*.png`, `/registerSW-*.js`, `/sw.js`, `/*.woff2`); the pages workflow rebuilds per deploy, so only the canonical unhashed faces stay tracked. After a local build, run `pnpm restore:entry` to put `index.html` back on the source face. NEVER build twice without the source face back on disk: the build entry is the root `index.html`, so a second build over the hashed face compiles the previous emit (the double-hash signature — `favicon-XXXX-XXXX.svg` — the legacy tree carried).
25
+
26
+ ## The family contract
27
+
28
+ The chrome links the sibling deploy units through `familyurl.ts` (the verbatim family body) over the one accent table of `family.ts` (spec §12) — the rail foot, the landing footer and `tests/family.test.ts` read the same table, so the row cannot drift.
package/docs/.gitkeep CHANGED
File without changes
@@ -0,0 +1,572 @@
1
+ # Competitor Dissection — inputs for the kathesis engine
2
+
3
+ > Task K1. Read-only study of five competitor clones already downloaded to
4
+ > `allan/temp/`. Goal: extract concrete, importable logic (algorithms, data
5
+ > structures, render/export strategies, DAW-UI patterns) for the kathesis
6
+ > engines at the debonair root. Every claim carries a `file:line` reference
7
+ > against the clone as of this dissection. Nothing here was copied verbatim
8
+ > into the codebase — this document is the queue, not the implementation.
9
+
10
+ Sources dissected (with license, which gates what may be reused):
11
+
12
+ | Repo | What it is | License |
13
+ | --- | --- | --- |
14
+ | `libsonare` | C++17 audio engine (analysis → mastering → mixing → MIDI → DAW runtime) shipped as WASM/Python/Node/C++ | Apache-2.0 |
15
+ | `pulsegridstudio` | Dependency-free browser DAW: one JS DSP module shared by AudioWorklet, export Worker and Node tests | MIT |
16
+ | `magenta-js` | TensorFlow.js music library (NoteSequence, quantization, melody/chord encodings, RNN/VAE models) | Apache-2.0 |
17
+ | `acestep.cpp` | GGML port of ACE-Step 1.5: prompt+lyrics → 48 kHz stereo music (LM + DiT + VAE) | MIT |
18
+ | `heartlib` | HeartMuLa family: 12.5 Hz music LM conditioned on tags+lyrics, HeartCodec tokenizer | Apache-2.0 |
19
+
20
+ kathesis baseline for comparison: pure-TS engines at the debonair root
21
+ (`musictheory`, `harmonyengine`, `rhythmgrammar`, `genrematrix`, `arranger`,
22
+ `takeevents`, `loudness` EBU R128, `ducking`, `mixergraph`, `speechalign`,
23
+ `beatgrid`, `remixplan`, `timelinemodel`, `playerbus`, `spectrum`
24
+ fft/hann/chroma, `fingerprint` landmarks, `analyze`, `vocalsynth` formants)
25
+ driven by the pipeline prompt+seed → planTake → scheduleTake → TakeEngine
26
+ (WebAudio 48 kHz).
27
+
28
+ ---
29
+
30
+ ## 1. libsonare — the analysis/mastering/mixing library
31
+
32
+ ### What it is
33
+
34
+ A C++17 engine ("turns audio into data and data back into audio") with a C
35
+ API, WASM build for the browser, Python and Node bindings, plus a CLI
36
+ (`README.md:5-11`). ~95 named mastering processors, mixing scenes with buses
37
+ and sends, offline bounce of compiled timelines, realtime allocation-free
38
+ playback, analysis (BPM/key/chords/sections matching librosa defaults).
39
+ Zero GPL code, no model weights (`README.md:9-11`). The submodules that
40
+ matter to kathesis: `src/analysis/*`, `src/mastering/*`, `src/engine/*`,
41
+ `src/c_api/project_bounce.cpp`, `src/graph/*`.
42
+
43
+ ### Pipeline
44
+
45
+ Analysis is a constellation of focused analyzers over one shared frontend:
46
+ `bpm_analyzer` (onset envelope → autocorrelation + Fourier tempogram),
47
+ `key_analyzer` (chroma × rotated key profiles), `chord_analyzer` (chroma
48
+ templates → **HMM Viterbi** decode), `section_analyzer` (section descriptors
49
+ → self-similarity → repeats), plus meter/downbeat/timbre/melody analyzers
50
+ (`src/analysis/`, 24 files, ~9.5k lines). The master side is a named-processor
51
+ registry (`src/mastering/api/named_processor_registry.cpp`,
52
+ `insert_factory.cpp` 2.6k lines) whose parameters self-describe, surfaced
53
+ through a generated capability catalog (`tools/capability-catalog.json`,
54
+ top-level keys: `abi`, `chain`, `masteringPresets`, `presets`,
55
+ `processors`, `version`). The DAW side compiles a project into an
56
+ `arr::CompiledTimeline` and bounces it offline with instruments + mixer
57
+ (`src/c_api/project_bounce.cpp`).
58
+
59
+ ### Concrete logics to import
60
+
61
+ 1. **Cadence-graded chord HMM (Viterbi with beam).** Chord decoding as a
62
+ HMM where transition log-probabilities are graded by cadence strength —
63
+ `None/Weak/Full/Dominant` (V7→I with the tritone present outranks
64
+ plain V→I, which outranks weak-quality cadences, which are floored so a
65
+ weak cadence never scores below a related transition) — then a 2-row
66
+ Viterbi over beam-limited candidates with backpointers
67
+ (`src/analysis/chord_hmm.cpp:34-118`, floor at `:154-161`, Viterbi at
68
+ `:181-263`). This is the missing piece in `harmonyengine`'s chord
69
+ selection: kathesis currently picks chords by template/genre tables; the
70
+ cadence grading turns any such table into a progression-aware decoder.
71
+ 2. **Swappable key profiles behind one rotation function.** Key detection
72
+ parameterized over Krumhansl-Schmuckler / Temperley / Shaath /
73
+ Bellman-Budge / Faraldo EDM profiles with a single
74
+ `rotate_profile(profile, semitones)` primitive
75
+ (`src/analysis/key_profiles.cpp:9-56`). `analyze.ts` currently hardcodes
76
+ one profile; making it a parameter costs one array-of-arrays and lets the
77
+ report state agreement across profiles as confidence.
78
+ 3. **BPM: autocorrelation + parabolic peak refinement + Fourier tempogram
79
+ fallback.** FFT-based autocorrelation of the onset envelope, parabolic
80
+ interpolation of lag peaks, a librosa-compatible Fourier tempogram, and a
81
+ merge when either fails (`src/analysis/bpm_analyzer.cpp:244-283`,
82
+ `:319-353`, `:447-457`). Complements kathesis `beatgrid` onset-flux with
83
+ the tempogram path for fractional-BPM material.
84
+ 4. **Section self-similarity with a confidence floor.** Build 12-chroma +
85
+ energy + vocal-likelihood descriptors per detected section, cosine
86
+ self-similarity matrix, count repeats per section, and *refuse to assert
87
+ a semantic label below a confidence floor* — emit the segment without the
88
+ label instead (`src/analysis/section_analyzer.cpp:513-529`,
89
+ floor at `:640-660`). Direct upgrade for `analyze.ts` structure and
90
+ `remixplan` arrangement proposals.
91
+ 5. **Streaming loudness preview per platform.** For each platform table
92
+ entry (Spotify −14 LUFS/−1 dBTP, Apple Music −16/−1, YouTube −14/−1),
93
+ compute the normalization gain and a boolean ceiling-risk flag; the header
94
+ documents the BS.1770 channel-sum pitfall (a 0.5·(L+R) downmix reads ~6 dB
95
+ low on decorrelated stereo)
96
+ (`src/mastering/maximizer/streaming_preview.h:13-51`,
97
+ computation at `streaming_preview.cpp:14-29`). Trivially portable to
98
+ `loudness.ts` as a pure function — a natural "mastering targets" panel
99
+ for the debonair studio.
100
+ 6. **Auto-derived bounce length + instrument tail probe.** Bounce end = max
101
+ over clips of `start+length` and last event frame; when the caller omits
102
+ the length, replay scheduled events through the instrument in render order
103
+ and ask it for its release tail (`materialize_tail_probe`), so MIDI-only
104
+ arrangements still include tails
105
+ (`src/c_api/project_bounce.cpp:46-68`, `:74-110`, contract note at
106
+ `:146-155`). Mirrors the kathesis need to compute take length + tail at
107
+ `scheduleTake`/export time instead of hardcoding a bar count.
108
+ 7. **Coherent multichannel phase-vocoder stretch.** Bake time-stretch per
109
+ segment where all channels share a reference polarity aligned per bin by
110
+ correlation with channel 0 (anti-phase channels reinforce instead of
111
+ cancelling, `src/engine/tempo_sync.cpp:132-145`) and apply the reference
112
+ rotation `synthesis_phase − analysis_phase` to each channel's own analysis
113
+ phase, preserving inter-channel phase exactly
114
+ (`:174-186`); segment joins are smoothed by a crossfade anchored on the
115
+ first sample after the boundary (`:215-232`). This is the correct recipe
116
+ when `remixplan`/`beatgrid` need to warp imported audio to a tempo map.
117
+ 8. **Self-describing parameter descriptors → generated UI.** Every processor
118
+ and synth engine declares its own parameters (range, default, unit,
119
+ display scale, cross-parameter bounds) and the host builds controls from
120
+ `capabilityCatalog()` instead of a hand-written table
121
+ (`README.md:39-41`; schema in `tools/capability-catalog.json`; enum shape
122
+ in `include/sonare/sonare_c_types_functions.h:190-333`). Pattern for
123
+ kathesis: one `paramDescriptors()` per engine feeding both the studio UI
124
+ and validation.
125
+ 9. **Scene schema with role-resolved master.** Buses carry a `role` string
126
+ ("master" wins over id "master" when resolving the output bus; scene
127
+ default role is "aux")
128
+ (`src/mixing/api/scene.h:132-141`, resolution in
129
+ `project_bounce.cpp:115-132`). A cleaner extension point than a hardcoded
130
+ master id in `mixergraph`.
131
+ 10. **Serialization guardrails.** Bounce results are cleared before any
132
+ rejected call so a later query can never report diagnostics from a
133
+ bounce that never happened (`project_bounce.cpp:134-167`), and every
134
+ allocation is bounded by `checked_size_product` before it happens
135
+ (`tempo_sync.cpp:58-78`). Worth adopting as conventions in the
136
+ take/render path.
137
+
138
+ ### What not to copy
139
+
140
+ - **`tools/gs/` (Roland GS tables, classic overlays, address censuses).**
141
+ The GS insertion-effect tables are reverse-engineered from Roland's
142
+ SC-88 material; GS is a Roland trademark. Study the *architecture* (SysEx
143
+ reception, two realisations of an insert), never ship the derived tables.
144
+ - **Brand and product chrome:** "sonare studio", libsonare docs/demos site,
145
+ audition harness, voice-match corpora (Bach excerpts).
146
+ - **Internal QA machinery** (`tools/parity/`, `tools/conformance/`,
147
+ `benchmarks/`, ABI tooling ~100 files): process scaffolding, not engine
148
+ logic; kathesis has its own gates.
149
+ - **Code as code:** Apache-2.0 permits use, but kathesis doctrine wants
150
+ pure-TS re-implementations; take the algorithms, write the TS, keep the
151
+ attribution note in the doc.
152
+
153
+ ---
154
+
155
+ ## 2. pulsegridstudio — the dependency-free DAW
156
+
157
+ ### What it is
158
+
159
+ A single-page DAW in plain JS (725 lines total): polyphonic synth, bass,
160
+ pluck, procedural drums, PCM sampler, TPT filter, saturation, tempo-synced
161
+ stereo delay, lookahead compressor, session/arrangement clips, piano roll,
162
+ volume automation, Web MIDI/keyboard overdub, IndexedDB autosave, and WAV
163
+ export — all sharing **one** DSP implementation used unchanged by the
164
+ AudioWorklet, the export Worker and Node tests (`README.md:5-9`,
165
+ `engine/dsp.js:1-4`). The layout is Ableton-inspired, explicitly declared as
166
+ independent (`README.md:11-12`).
167
+
168
+ ### Pipeline
169
+
170
+ `AudioWorkletProcessor` owns a `PulseEngine` (worklet thread); the main
171
+ thread talks to it through a command port (`project`, `param`, `note`,
172
+ `launch`, `capture-*`) and receives one packed telemetry object at ~24 Hz
173
+ (`engine/worklet.js:37`). Export does **not** use `OfflineAudioContext`:
174
+ `renderProject` instantiates the same engine on a Worker and renders in
175
+ 128-sample blocks, inserting a pause exactly at the musical endpoint so
176
+ releases/delay tails continue through the requested tail
177
+ (`engine/offline.js:14-21`), then `encodeWav` writes RIFF PCM 16/24-bit
178
+ (`engine/wav.js`).
179
+
180
+ ### Concrete logics to import
181
+
182
+ 1. **Offline render without OfflineAudioContext, with latency-prefix
183
+ discard.** Fixed 128-sample blocks; render total = musical frames +
184
+ tail + known internal latency; write each block at `dest = at +
185
+ i − maxLatency` so only the *known* latency prefix is discarded, never
186
+ musical signal; `engine.pause()` exactly at the musical endpoint to
187
+ carry release tails into the tail region
188
+ (`engine/offline.js:14-21`). This is the pattern for a kathesis
189
+ deterministic WAV export (Node/CI-safe, bit-comparable across block
190
+ sizes — they test exactly that).
191
+ 2. **TakeEngine-grade scheduler details:**
192
+ - Clip launch quantization: a pending launch stores
193
+ `pendingBeat = ceil((beat + step/2) / quantum) * quantum` and flips at
194
+ a half-sample-resolution guard (`engine/dsp.js:57`, flip at `:62`).
195
+ - **Note chase on seek**: when a cursor jumps into the middle of a clip,
196
+ retrigger only notes still sounding at the seek position, with exact
197
+ remaining duration, restarting oscillator phase rather than faking
198
+ past DSP state (`engine/dsp.js:73-79`).
199
+ - Sorted-note cursor + half-step windows for event scheduling
200
+ (`engine/dsp.js:86`), loop detection by `cycle` + position regression
201
+ (`:70-73`).
202
+ These map 1:1 onto `takeevents.ts` schedule/seek fixes.
203
+ 3. **Cross-track latency alignment.** Compressor look-aheads create
204
+ per-track latency; the engine computes `maxLatency` over all tracks and
205
+ gives every track `compensate = maxLatency − lookahead`, aligning all
206
+ tracks to the slowest (`engine/dsp.js:44`, delay lines at `:129-131`).
207
+ kathesis `playerbus` should apply the same rule when any node introduces
208
+ latency.
209
+ 4. **Voice pool + steal-by-lowest-envelope.** Fixed 24 voices, steal the
210
+ quietest (`env` minimum) when full — no allocation in the render loop
211
+ (`engine/dsp.js:31`, `newVoice` at `:9`). Plus the **signature-diff
212
+ pattern**: musical data (clips/arrangement) is only re-sorted and
213
+ re-indexed when a JSON signature changes, while compact parameter
214
+ messages mutate coefficients in place (`:14-21`, `:45-52`).
215
+ 5. **polyBLEP antialiasing in three lines.** `blep(t,dt)` correction applied
216
+ to square/saw only (`engine/dsp.js:7-8`) — good enough for a lightweight
217
+ oscillator upgrade in `vocalsynth`/`takeevents` synth voices.
218
+ 6. **TPT SVF coefficients from a single `tan` call.** Topology-preserving
219
+ state-variable filter with `g = tan(π·fc/sr)`, `k = 1/Q`, stable at
220
+ audio rate, four state vars per channel (`engine/dsp.js:23-26`, tick at
221
+ `:125-126`) — a pure-TS filter kathesis can adopt for per-track cutoff.
222
+ 7. **Master safety as bounded soft clip, plus smoothed faders + peak/RMS
223
+ telemetry.** `|x|>0.9 → sign·(0.9+0.1·tanh((|x|−0.9)·10))` — explicitly
224
+ "not a brickwall" (`engine/dsp.js:139-141`); per-sample 0.002-lerp toward
225
+ target volume/pan avoids zipper noise (`:132`); decaying peak/RMS meters
226
+ (`:135`) feed the UI. TakeEngine polish items.
227
+ 8. **Live event ring buffer with drop accounting.** 512-slot pre-allocated
228
+ event ring; writer drops (and counts) when full; notes carry a
229
+ `contextFrame` translated into worklet time `engine.frame +
230
+ (contextFrame − currentFrame)` for latency-corrected live input
231
+ (`engine/dsp.js:42,59-60`, translation in `engine/worklet.js:27`).
232
+ 9. **Capture block pool with transferable recycle.** 8 pre-allocated 8192-
233
+ frame stereo blocks; the worklet posts them zero-copy and the main thread
234
+ copies then returns the buffers via transfer list (`capture-return`)
235
+ (`engine/audio.js:21-23`, pool in `engine/worklet.js:5-7,39`).
236
+ Memory-stable recording without GC churn.
237
+ 10. **Project validate-and-clamp loader + labeled undo history.**
238
+ `validateProject` throws on structural violations (version, ids unique,
239
+ counts, ranges) and *clamps* every numeric to an explicit ranges table
240
+ (`engine/model.js`, ranges at the `ranges={...}` literal), returning a
241
+ normalized object; `History` keeps ≤80 labeled whole-state snapshots for
242
+ undo/redo (`engine/model.js` `class History`). The `.pulse` container
243
+ serializes PCM as base64 with a 256 MB budget and per-sample finiteness
244
+ checks (`engine/storage.js` `serialize`/`deserialize`).
245
+
246
+ ### What not to copy
247
+
248
+ - **Any Ableton Live reference in copy/UI claims** (`README.md:11-12` shows
249
+ their own disclaimer — kathesis ships its own FL-informed design and must
250
+ phrase inspiration the same way).
251
+ - **The demo song content** ("After Hours", clip names, note patterns in
252
+ `engine/model.js` `demoProject`) — someone's authored musical data.
253
+ - **WebGPU visual pipeline** (`engine/visual.js`) — outside kathesis scope
254
+ and duplicate of the Sol design system.
255
+ - **Brand bits**: the "Pulsegrid" name, `COLORS` palette, `.pulse` extension
256
+ (take the *validate-then-clamp* idea, not the format identity).
257
+
258
+ ---
259
+
260
+ ## 3. magenta-js — the interchange & encoding library
261
+
262
+ ### What it is
263
+
264
+ Google's TensorFlow.js music library (`music/src`): the `NoteSequence`
265
+ proto as the central interchange schema, quantization utilities, melody and
266
+ chord encoders, conditioning controls, MIDI I/O, player/visualizer, and
267
+ model wrappers (MusicRNN, MusicVAE, CoConet, GANSynth, DDSP, Onsets &
268
+ Frames, Piano Genie, SPICE). License Apache-2.0. The parts relevant to
269
+ kathesis are the **pure data-structure** modules (`core/sequences.ts`,
270
+ `core/melodies.ts`, `core/chords.ts`, `core/constants.ts`,
271
+ `core/aux_inputs.ts`) — not the tfjs model stack.
272
+
273
+ ### Pipeline
274
+
275
+ MIDI/audio in → `NoteSequence` (`music/src/protobuf`) →
276
+ `quantizeNoteSequence` (snap seconds→steps, validate single tempo /
277
+ power-of-2 time signature) → model-specific encodings (melody grid, chord
278
+ one-hots, drum pitch classes) → model → decode → player/visualizer. The
279
+ encoding layer is the reusable part: every model just consumes tensors
280
+ produced from the same quantized sequence.
281
+
282
+ ### Concrete logics to import
283
+
284
+ 1. **Quantize-to-step with an adjustable cutoff.**
285
+ `quantizeToStep(s, sps, cutoff=0.5) = floor(s·sps + (1−cutoff))` — the
286
+ cutoff is a *fraction of a step* that decides whether early notes round
287
+ up or down (`music/src/core/sequences.ts:27-36,101-106`). A quantize
288
+ knob for the debonair piano roll ("tight ↔ humanized") for free.
289
+ 2. **Minimum-1-step note repair + negative-time rejection.** Quantized end
290
+ == quantized start → end+=1; negative times throw
291
+ (`sequences.ts:130-146`). Prevents zero-length note degeneracies kathesis
292
+ currently tolerates.
293
+ 3. **Melody event-grid encoding.** `Int32Array` per step: `NO_EVENT(−1)`,
294
+ `NOTE_OFF(−2)`, else `pitch − minPitch + FIRST_PITCH`; built from a
295
+ sequence sorted by start then **pitch descending** so polyphony collapses
296
+ to the top voice when `ignorePolyphony`
297
+ (`music/src/core/melodies.ts:69-104`; constants `:1-55`). The canonical
298
+ compact form for `rhythmgrammar`/`arranger` leads.
299
+ 4. **Conditioning controls: onset rhythm + Parsons contour.** `MelodyRhythm`
300
+ emits a depth-1 onset indicator; `MelodyShape` emits a depth-3
301
+ up/same/down one-hot Parsons contour that propagates the *first* direction
302
+ backwards through rests and continues the last direction to the end
303
+ (`melodies.ts:156-254`). Exactly the shape of control signal kathesis
304
+ needs to steer `arranger`/`rhythmgrammar` from a reference motif.
305
+ 5. **Chord encoders with a NO_CHORD slot.** `MajorMinor` (1+2·12 one-hot),
306
+ `Triad` (1+4·12), `PitchChord` (root one-hot ⊕ pitch-class bitmap ⊕ bass
307
+ one-hot), all indexed by `quality·12 + root` with `N.C.` at 0
308
+ (`music/src/core/chords.ts:167-258`, quality from interval sets at
309
+ `:28-34`). Drop-in conditioning vocabulary for `harmonyengine` ↔
310
+ `genrematrix` handoffs.
311
+ 6. **GM drum taxonomy as 9 pitch-class buckets.**
312
+ `DEFAULT_DRUM_PITCH_CLASSES` groups all GM drum notes into bass/snare/
313
+ closed-hat/open-hat/toms×3/crash/ride (`music/src/core/constants.ts:28-52`).
314
+ `rhythmgrammar` currently models kick/snare/hat only; this bucket table
315
+ covers arbitrary GM drums and maps neatly onto pulsegrid's procedural
316
+ drum ranges.
317
+ 7. **Sustain-pedal CC64 → note extension, and consecutive-note merging.**
318
+ `applySustainControlChanges` folds pedal events into note lengths;
319
+ `mergeConsecutiveNotes` joins same-pitch neighbors
320
+ (`sequences.ts:578,504`). Both are needed before any quantized editing in
321
+ kathesis is honest about MIDI input.
322
+ 8. **Sequence algebra: concatenate / trim / split-chunks.** Chunk a long
323
+ sequence into model-sized windows and reassemble, with relative-time
324
+ rewrites (`sequences.ts:743,788,873`). The pattern for chunked
325
+ generation of long takes.
326
+ 9. **BinaryCounter auxiliary input.** A step counter encoded as −1/1 bits,
327
+ LSB first, as an auxiliary model input (`core/aux_inputs.ts:53-71`) — a
328
+ cheap positional feature for any step-conditioned generator in kathesis.
329
+ 10. **Exception taxonomy per contract.** Dedicated error classes
330
+ (`MultipleTempoException`, `BadTimeSignatureException`,
331
+ `NegativeTimeException`, `QuantizationStatusException` —
332
+ `sequences.ts:38-74`) make failure modes part of the API surface;
333
+ mirrors how kathesis engines should throw typed errors.
334
+
335
+ ### What not to copy
336
+
337
+ - **The TensorFlow.js dependency and all model wrappers**
338
+ (`music/src/music_vae`, `music_rnn`, `gansynth`, `ddsp`, `coconet`,
339
+ `transcription`, `piano_genie`, `spice`) + checkpoints: conflicts with
340
+ kathesis pure-TS doctrine, bundle budget, and the "no ML runtime" line
341
+ drawn in the engine docs; revisit only behind a worker/ONNX seam.
342
+ - **The protobuf stack** (`music/src/protobuf/proto.js` generated code):
343
+ replace with plain TS interfaces + the existing `takeevents` model.
344
+ - **tonal.js as a dependency** (used in `chords.ts:26`): kathesis'
345
+ `musictheory` already owns this; port the *encodings* only.
346
+ - **Demos, visualizer styling, brand** ("Magenta" name, demo HTMLs).
347
+
348
+ ---
349
+
350
+ ## 4. acestep.cpp — prompt+lyrics → 48 kHz via GGML
351
+
352
+ ### What it is
353
+
354
+ Portable C++17 GGML implementation of ACE-Step 1.5: Qwen3 text encoder +
355
+ Qwen3 LM (0.6B/1.7B/4B) that plans metadata + FSQ audio codes at 5 Hz, a
356
+ DiT (2B/4B) with flow-matching denoising at 25 Hz latents, and an
357
+ AutoencoderOobleck VAE decoding to stereo 48 kHz; HTTP server + browser UI
358
+ (`docs/ARCHITECTURE.md:1-9,38-58`). MIT licensed. Notable engineering: a
359
+ VRAM policy where exactly one GPU module is resident at a time
360
+ (`ARCHITECTURE.md:96-106`), GGUF bundling tokenizer+config (`:73-88`),
361
+ and cancel-aware generation.
362
+
363
+ ### Pipeline
364
+
365
+ `AceRequest{caption, lyrics, bpm, duration, keyscale, timesignature,
366
+ vocal_language, seed, lm_*, inference_steps, guidance_scale, shift,
367
+ audio_cover_strength…}` (`src/request.h:11-64`) → prompt building + CoT
368
+ parsing (`src/prompt.h:14-55`) → **constrained decoding** of metadata
369
+ (PrefixTree + MetadataFSM) → LM emits audio codes (V=65536 compacted
370
+ sampling: temperature → top-k via `nth_element` → top-p nucleus →
371
+ multinomial; `src/sampling.h:14-60`) → FSQ codes → DiT context; DiT
372
+ denoises 25 Hz latents with APG guidance; VAE decodes tiled to 48 kHz
373
+ (`src/vae.h:456,561`).
374
+
375
+ ### Concrete logics to import
376
+
377
+ 1. **APG guidance for any cond/uncond generator.** Momentum-smoothed
378
+ difference `cond − uncond` (running average with momentum −0.75),
379
+ per-channel L2 norm clip to 2.5, decomposition into the component
380
+ parallel/orthogonal to the conditional prediction, and
381
+ `result = pred_cond + (scale−1)·orthogonal`
382
+ (`src/dit-sampler.h:21-123`; projection `:45-65`, forward `:74-123`).
383
+ This is a *portable* guidance improvement over plain CFG for the future
384
+ kathesis generation backend (only meaningful when a neural generator
385
+ lands, but the math is 80 lines).
386
+ 2. **Batched CFG in one forward.** cond+uncond denoise in a single batched
387
+ forward pass, halving model compute per step; when memory-bound, two
388
+ forwards instead (`src/dit-sampler.h:170-171`). Same trick heartlib uses.
389
+ 3. **Cover mode = timed context switching.** Generation can start from a
390
+ source context and switch to silence/text2music context after
391
+ `cover_steps`, with `audio_cover_strength` as the fraction of DiT steps
392
+ that keep source context (`src/dit-sampler.h:145-146,422-424`;
393
+ `src/request.h:64`). The conceptual skeleton for kathesis "remix/cover
394
+ from reference" (`remixplan`), even in a non-neural form: run N steps of
395
+ source-faithful planning then fade the planner to the target brief.
396
+ 4. **Constrained decoding of structured metadata (PrefixTree FSM).** A
397
+ prefix tree of valid token sequences plus a state machine over
398
+ `bpm/duration/key:scale/time-signature/language` fields so an LM *cannot*
399
+ emit invalid YAML metadata (`src/metadata-fsm.h:10-58`). kathesis
400
+ `katexis.planTake` should validate/repair prompt→plan fields with the
401
+ same FSM mindset (enumerate legal field values, reject or repair).
402
+ 5. **Compacted-logit sampling.** For code tokens, compact the vocab to the
403
+ legal subset (EOS + audio codes ≈65k) before top-k/top-p;
404
+ `nth_element` O(V) for top-k; sort only survivors for top-p
405
+ (`src/sampling.h:14-60`). Generalizes to "sample only from genre-legal
406
+ chords/scales" in any kathesis sampler.
407
+ 6. **Pluggable solver registry with NFE accounting.** Solver interface with
408
+ `VelocityRecord` state, registry of euler/dpm/sde/stork with
409
+ `nfe`/order metadata and graceful fallback to euler on unknown names
410
+ (`src/solvers/solver-*.h`, dispatch+warning at
411
+ `src/dit-sampler.h:396-411`). The naming/structure to copy for any
412
+ step-wise kathesis generator ("solver" as strategy object, steps =
413
+ NFE).
414
+ 7. **Philox seeded noise per sample.** Deterministic Philox counter-based
415
+ RNG for initial noise, seeds stored as int64 but consumed as low-32-bits
416
+ to match the reference (`src/philox.h`, `src/request.h:26-29`). Matches
417
+ kathesis mulberry32 determinism doctrine — Philox is the upgrade path if
418
+ array-level parallel noise is needed.
419
+ 8. **Cancel callback threaded through generation.** `bool (*cancel)(void*)`
420
+ checked every step, with logged "Cancelled at step k/N"
421
+ (`src/dit-sampler.h:147-148,415-418`). kathesis take generation (which
422
+ can be slow) deserves the same first-class cancellation seam.
423
+ 9. **Auto inference config per model tier.** `inference_steps 0 → auto
424
+ (turbo: 8, sft/base: 50)`, `guidance_scale 0 → auto`, `shift 0 → auto`
425
+ (`src/request.h:48-50`) — defaults resolved by model capability, so the
426
+ API surface stays minimal. Same idea for kathesis genre→quality
427
+ presets.
428
+ 10. **Beat-frame hygiene at the request schema level.** `AceRequest` keeps
429
+ `duration`/`bpm` as *unset-able* (0/"" = unset) and lets the pipeline
430
+ derive them — the same unset-vs-explicit discipline kathesis plans
431
+ should adopt (`src/request.h:13-21`).
432
+
433
+ ### What not to copy
434
+
435
+ - **Model weights/GGUFs** (HuggingFace `Serveurperso/ACE-Step-1.5-GGUF`),
436
+ the ACE-Step brand, and the browser UI chrome (screenshots in README).
437
+ - **The GGML/vendored tree** (`ggml/`, `vendor/`) — kathesis never ships a C
438
+ runtime.
439
+ - **Server plumbing** (`server.sh/cmd`, `model-store`, HTTP layer): the
440
+ debonair pipeline is in-app, not a local server product.
441
+ - License permits MIT reuse, but again: re-implement in TS where it lands.
442
+
443
+ ---
444
+
445
+ ## 5. heartlib — tags+lyrics music LM (HeartMuLa)
446
+
447
+ ### What it is
448
+
449
+ Python/PyTorch package for the HeartMuLa family (Apache-2.0): HeartMuLa
450
+ music LM (llama3.2-torchtune backbones 300M→7B) over HeartCodec (12.5 Hz
451
+ causal codec, 48 kHz), conditioned on multilingual tags and lyrics; plus
452
+ HeartTranscriptor (whisper-tuned lyric transcription) and HeartCLAP
453
+ alignment (`README.md:13-21`). Their internal 7B is claimed Suno-adjacent
454
+ (`README.md:29-30`) — the direct architectural reference for what the
455
+ debonair "Suno-like" pipeline is really like under the hood.
456
+
457
+ ### Pipeline
458
+
459
+ `HeartMuLaGenPipeline` (`src/heartlib/pipelines/music_generation.py`):
460
+ preprocess wraps tags in literal `<tag>…</tag>` tokens and adds BOS/EOS
461
+ (`:222-233`), builds a `[prompt_len, 8+1]` token matrix — one channel per
462
+ RVQ codebook plus a text channel — with the tags in the last channel and
463
+ the MUQ reference-embedding slot between tags and lyrics
464
+ (`:258-263`); CFG duplicates the batch with an unconditional mask
465
+ (`:268-282`). Forward: `generate_frame` per 80 ms frame with a KV-cached
466
+ backbone, top-k sampling, and per-frame EOS check against
467
+ `audio_eos_id` (`:297-353`); `max_audio_frames = ms // 80` caps length.
468
+ Postprocess: codec detokenize → save 48 kHz (`:355-359`). Lazy
469
+ load/unload purges CUDA cache between stages (`:171-198`).
470
+
471
+ The model core (`src/heartlib/heartmula/modeling_heartmula.py`): backbone
472
+ predicts codebook-0 token from the merged embedding sum; a small
473
+ **decoder LM** then predicts codebooks 1..8 conditioned on the backbone's
474
+ last hidden + c0 embedding (`generate_frame`, `:181-270`) — the MusicGen
475
+ two-stage RVQ pattern; CFG applies `uncond + (cond−uncond)·scale` on both
476
+ stages (`:226-231,255-262`). HeartCodec itself: SQ (single-quantizer,
477
+ causal) codec with Snake activations, downsample factors
478
+ `[3,4,4,4,5]` → 12.5 Hz, 8 codebooks @ 8192, plus a DiT/flow-matching
479
+ latent estimator in the same config
480
+ (`src/heartlib/heartcodec/configuration_heartcodec.py:6-55`,
481
+ `models/sq_codec.py`, `models/flow_matching.py:16-60`).
482
+
483
+ ### Concrete logics to import
484
+
485
+ 1. **The tags/lyrics prompt contract.** `<tag>…</tag>` + BOS/EOS framing
486
+ with a reserved embedding slot between tags and lyrics for a reference
487
+ audio embedding (`music_generation.py:222-263`) — this is, concretely,
488
+ the prompt grammar a Suno-like competitor uses; kathesis `planTake`
489
+ prompt schema should reserve the same three regions (style tags |
490
+ reference | lyrics) even before any neural consumer exists.
491
+ 2. **80 ms frame quantum end-to-end.** 12.5 Hz codec ⇒ 80 ms per token
492
+ frame; generation length is `ms // 80` and every loop iterates a frame
493
+ (`music_generation.py:331-350`). kathesis `timelinemodel` benefits from
494
+ choosing one irreducible audio quantum for plans/schedules instead of
495
+ mixing seconds/frames/steps.
496
+ 3. **Frame-level EOS stop check.** Stop the whole track when any codebook
497
+ emits `≥ audio_eos_id` (`:348-349`) — the stopping rule kathesis
498
+ generators lack today (takes run to a bar count instead of a musical
499
+ stop signal).
500
+ 4. **Two-stage RVQ decoding split.** Big LM predicts coarse codebook 0;
501
+ small decoder fills remaining codebooks from the coarse hidden
502
+ (`modeling_heartmula.py:232-262`). As a *pure-TS analogy*: plan coarse
503
+ structure with the full engine, fill detail voices with a cheap
504
+ secondary pass reading only the coarse state — the layering kathesis
505
+ `arranger` → per-track voices should formalize.
506
+ 5. **CFG via duplicated batch + learned unconditional embedding.** Where no
507
+ uncond text exists, a learned `unconditional_text_embedding` replaces
508
+ the condition under a boolean mask (`:196-229`) — portable to any
509
+ conditional kathesis generator (condition = plan; uncond = learned
510
+ null-plan vector).
511
+ 6. **Preprocess/forward/postprocess pipeline shape with parameter
512
+ sanitization.** `_sanitize_parameters` splits kwargs into
513
+ preprocess/forward/postprocess groups with explicit defaults (cfg 1.5,
514
+ 120 s cap, top-k 50) (`:200-211`) — the same three-phase contract the
515
+ kathesis take pipeline already wants; adopt the *defaults table*.
516
+ 7. **Lazy load + stage-scoped unload.** Models load on first use and are
517
+ dropped (with cache purge) after their stage (`:114-198`) — the
518
+ memory-discipline analog for kathesis workers: load analysis or
519
+ synthesis resources per stage, never hold all engines live at once.
520
+ 8. **Codec config as one file of numbers.** `HeartCodecConfig` enumerates
521
+ every rate that matters (48 kHz, 12.5 Hz latent, codebook size, down/
522
+ upsample factors) in one typed config (`configuration_heartcodec.py:6-55`)
523
+ — keep kathesis audio constants in one engine-level config object.
524
+
525
+ ### What not to copy
526
+
527
+ - **Weights and HF checkpoints** (HeartMuLa-oss-3B, HeartCodec encoder) —
528
+ both out of bundle budget and license-adjacent concerns are nil (Apache)
529
+ but kathesis ships no model binaries.
530
+ - **`models/feature_extractors.py` frontend**: adapted from MusicFM
531
+ (ByteDance MIT notice at `:1-21`) and depends on Whisper/WavLM/torch
532
+ transformers — study, don't embed; keep attribution if ideas cross over.
533
+ - **Brand assets** (logo, Discord, demo page, "Suno-adjacent" marketing),
534
+ `assets/*.mp3`, MuLaCover weights.
535
+ - PyTorch/torchaudio dependency style: kathesis stays pure TS; the pipeline
536
+ *contract* is what gets imported.
537
+
538
+ ---
539
+
540
+ ## kathesis import queue (prioritized top 10)
541
+
542
+ Each item names the kathesis engine file at the debonair root that would
543
+ receive it. Order = value/effort for the current P1-P7 lineage (pure-TS,
544
+ 48 kHz, deterministic).
545
+
546
+ | # | Logic (source) | Lands in |
547
+ | --- | --- | --- |
548
+ | 1 | Note chase on seek + launch quantization `pendingBeat = ceil((beat+step/2)/quantum)·quantum` (pulsegrid `dsp.js:57,62,73-86`) | `takeevents.ts` |
549
+ | 2 | Offline render block-loop without OfflineAudioContext: pause-at-musical-end, tail through releases, discard only known latency prefix; RIFF WAV 16/24-bit encoder (pulsegrid `offline.js:14-21`, `wav.js`) | `playerbus.ts` (+ export path in `takeevents.ts`) |
550
+ | 3 | Cadence-graded chord HMM (Viterbi + beam, Weak-floor rule) (libsonare `chord_hmm.cpp:34-118,143-166,181-263`) | `harmonyengine.ts` |
551
+ | 4 | Streaming loudness preview: platform table {Spotify −14/−1, Apple −16/−1, YouTube −14/−1} → gain + ceiling-risk flag (libsonare `streaming_preview.h:13-51`, `streaming_preview.cpp:14-29`) | `loudness.ts` |
552
+ | 5 | Quantize-to-step with cutoff + min-1-step repair + sustain-CC64 fold + consecutive-note merge (magenta `sequences.ts:27-36,101-157,504,578`) | `timelinemodel.ts` (quantize) + `takeevents.ts` |
553
+ | 6 | Melody event-grid (NO_EVENT/NOTE_OFF) + Parsons contour & onset-rhythm conditioning controls + GM drum pitch-class buckets (magenta `melodies.ts:69-104,156-254`, `constants.ts:28-52`) | `rhythmgrammar.ts` + `arranger.ts` |
554
+ | 7 | Section self-similarity labeling with confidence floor (libsonare `section_analyzer.cpp:513-529,640-660`) | `analyze.ts` |
555
+ | 8 | Cross-track latency alignment `compensate = maxLatency − lookahead` + fixed voice pool steal-by-lowest-env + signature-diff table rebuild (pulsegrid `dsp.js:14-21,31,44,45-52`) | `playerbus.ts` + `takeevents.ts` |
556
+ | 9 | Constrained metadata FSM / prefix tree for plan fields (unset-vs-explicit request schema) (acestep.cpp `metadata-fsm.h:10-58`, `request.h:13-64`) | `katexis.ts` (planTake) |
557
+ | 10 | TPT SVF coefficients from `tan` + polyBLEP oscillators + smoothed faders/pan + bounded tanh master guard (pulsegrid `dsp.js:7-8,23-26,125-141`) | `takeevents.ts` (TakeEngine voices) |
558
+
559
+ Runners-up (kept visible, not queued now): multichannel coherent phase
560
+ vococoder stretch + segment-join fades (libsonare `tempo_sync.cpp:132-232`)
561
+ → future `spectrum.ts` stretch util for `remixplan`; APG guidance +
562
+ batched CFG (acestep.cpp `dit-sampler.h:21-123,170-171`) → future neural
563
+ generation seam; key-profile menu (libsonare `key_profiles.cpp:9-56`) →
564
+ `analyze.ts`; frame-level EOS stop + tags|reference|lyrics prompt grammar
565
+ (heartlib `music_generation.py:222-263,348-349`) → `katexis.ts`/future
566
+ generation; self-describing parameter descriptors (libsonare
567
+ `capability-catalog.json`) → all engines' UI seam.
568
+
569
+ ---
570
+
571
+ *End of dissection. Written by the K1 agent; read-only study, no app code
572
+ touched.*