@hraness/dawg 0.6.1 → 0.7.0
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.
- package/CHANGELOG.md +92 -0
- package/DAWG.md +343 -116
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +832 -13
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +276 -3
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +1849 -42
- package/core/sections.ts +292 -26
- package/core/sing.ts +815 -0
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +17 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -13
- package/guides/styles.md +31 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +5 -2
- package/src/agent/agent.ts +126 -14
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +12 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +108 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +11 -7
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +37 -3
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +160 -28
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +44 -29
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +558 -0
- package/src/commands/help.ts +610 -380
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +8 -8
- package/src/commands/modal.ts +1 -1
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sing.ts +478 -0
- package/src/commands/strum.ts +13 -1
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1397 -253
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +20 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +759 -177
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +47 -7
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
package/DAWG.md
CHANGED
|
@@ -11,7 +11,11 @@ bun install
|
|
|
11
11
|
bun run dawg
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Run `dawg` from any directory. It creates `.dawg/session` on first use and reuses that session in later terminal windows. Use `dawg --new` for a separate composition, `dawg --session <name|id>` to attach explicitly (an unknown value is `no session named "…" · dawg sessions`, never a new session), and `dawg --track bass` to focus a named track. `dawg --version` prints the version; unknown subcommands and options are rejected with usage before `.dawg/` exists, and the launch that creates `.dawg/` says `created .dawg/ · add it to .gitignore` in the strip. Every window connects to `dawgd`, a per-session daemon the first window starts in the background. It is the single writer: windows send operations with a base revision and an idempotency key, duplicate keys are no-ops, a stale full composition receives a typed rebase diagnostic, and a stale `operations` intent (what the agent sends) is replayed on the current score when nothing it touches changed since its base (the notes it updates or removes, the tracks it rewrites or clears, tempo and length, and ids it creates; at most 64 revisions back, with the base recovered by rewinding the event log); anything else still gets the rebase diagnostic. Every event stores a compact reverse delta (`rewind`) rather than a copy of the previous score, so a session record grows by about the size of each edit; when it nears its 4 MiB cap the oldest rewinds are compacted away and only that older history becomes unreachable. Rebased events record `rebasedFrom`, and their rewind leads back to the score they replayed on, so undo drops only that change, and on the file fallback the agent commits the full composition with the strict base check. Accepted commits are persisted through the same atomic snapshot store before being broadcast to every window. The daemon also owns the only transport and audio player, broadcasting play, pause, seek, and tempo with a timestamp so every window draws the same hit line. It keeps a presence table (`clientId`, `pid`, focused track) and can atomically claim the first unfocused track for a new window. The daemon exits 30 seconds after its last window closes, removes its socket on SIGTERM, and a crashed daemon's socket and lock are reclaimed by the next window. If `dawgd` cannot be started (or `DAWG_DAEMON=0`), windows fall back to the snapshot under the file lock, watching the session directory with `fs.watch` (so renames and edits from other windows arrive immediately) with a 1 s backstop poll, or a 200 ms poll where watching is unavailable, with presence kept in per-window heartbeat files. `dawg sessions` lists sessions in the current workspace. `dawg render <out.wav|out.mid> [--session <name|id>] [--import <file>]` reads the session record from disk (no daemon, no audio) and writes a stereo 16-bit WAV through the playback renderer (or a Standard MIDI File with tempo and time-signature meta events for `.mid`, see **Tempo and meter**); the same score always yields the same bytes, and the command prints the size and sha256. `/status` reports `status · <name> · rev <n> · <digest> · shared via dawgd` (or `saved locally · no daemon`), where the digest is the 16-hex composition digest dawgd broadcasts. In demo mode a drum track is seeded with a one-bar kick, snare and hat groove instead of melodic notes.
|
|
14
|
+
Run `dawg` from any directory. It creates `.dawg/session` on first use and reuses that session in later terminal windows. Use `dawg --new` for a separate composition, `dawg --session <name|id>` to attach explicitly (an unknown value is `no session named "…" · dawg sessions`, never a new session), and `dawg --track bass` to focus a named track (normalized like `/track`: lowercase, spaces become `-`, `[a-z0-9._-]{1,64}`, and an existing track matches by id or name, so `--track Bass` focuses `bass`). `dawg --version` prints the version; unknown subcommands and options, a value flag with no value (or one starting with `-`), an invalid `--track` and an unknown `--theme` are rejected with usage and exit 2 before `.dawg/` exists, and `dawg <command> --help` prints usage and writes nothing for every subcommand (`init`, `check` and `sessions` also reject extra arguments). `dawg --import a --export b` converts one loop file to another without opening a session, and a demo frame (`--demo`, or stdin not a terminal) in a directory with no `.dawg/` renders from a throwaway session and leaves nothing behind; and the launch that creates `.dawg/` says `created .dawg/ · add it to .gitignore` in the strip. Every window connects to `dawgd`, a per-session daemon the first window starts in the background. It is the single writer: windows send operations with a base revision and an idempotency key, duplicate keys are no-ops, a stale full composition receives a typed rebase diagnostic, and a stale `operations` intent (what the agent sends) is replayed on the current score when nothing it touches changed since its base (the notes it updates or removes, the tracks it rewrites or clears, tempo and length, and ids it creates; at most 64 revisions back, with the base recovered by rewinding the event log); anything else still gets the rebase diagnostic. Every event stores a compact reverse delta (`rewind`) rather than a copy of the previous score, so a session record grows by about the size of each edit; when it nears its 4 MiB cap the oldest rewinds are compacted away and only that older history becomes unreachable. Rebased events record `rebasedFrom`, and their rewind leads back to the score they replayed on, so undo drops only that change, and on the file fallback the agent commits the full composition with the strict base check. Accepted commits are persisted through the same atomic snapshot store before being broadcast to every window. The daemon also owns the only transport and audio player, broadcasting play, pause, seek, and tempo with a timestamp so every window draws the same hit line. It keeps a presence table (`clientId`, `pid`, focused track) and can atomically claim the first unfocused track for a new window. The daemon exits 30 seconds after its last window closes, removes its socket on SIGTERM, and a crashed daemon's socket and lock are reclaimed by the next window. If `dawgd` cannot be started (or `DAWG_DAEMON=0`), windows fall back to the snapshot under the file lock, watching the session directory with `fs.watch` (so renames and edits from other windows arrive immediately) with a 1 s backstop poll, or a 200 ms poll where watching is unavailable, with presence kept in per-window heartbeat files. `dawg sessions` lists sessions in the current workspace. `dawg render <out.wav|out.mid> [--session <name|id>] [--import <file>]` reads the session record from disk (no daemon, no audio) and writes a stereo 16-bit WAV through the playback renderer (or a Standard MIDI File with tempo and time-signature meta events for `.mid`, see **Tempo and meter**); the same score always yields the same bytes, and the command prints the size and sha256. `/status` reports `status · <name> · rev <n> · <digest> · shared via dawgd` (or `saved locally · no daemon`), where the digest is the 16-hex composition digest dawgd broadcasts. In demo mode a drum track is seeded with a one-bar kick, snare and hat groove instead of melodic notes.
|
|
15
|
+
|
|
16
|
+
### One command grammar
|
|
17
|
+
|
|
18
|
+
Every prompt-bar command reads the same with or without its slash (`/fx reverb on` is `fx reverb on`). `remove`, `rm` and `delete` are one verb on every object, and `list`, `presets`, `ls` and a bare noun are one listing. Aliases run their canonical form: `rig <preset>` (`track <preset>` still works), `model key` (`login`), `chords idiom` (`chords style`), `synth filter` (`synth cutoff`), `loop a-b|off` (`loop 2-3` marks or moves a `loop` section when none spans those bars), `export <file>.wav [stems]` (the `dawg render` path; agent paths are workspace-relative), and a bare `pattern <groove>` or `kit <name>`. A known verb that fails answers with one usage card, `✗ tempo 900 · tempo takes 20…300 BPM · tempo 128`, with a nearest suggestion and never a raw core key; it never reaches the agent, and without a provider nothing does. Instruments are checked exactly on write (`✗ instrument sawtoth · did you mean sawtooth? · instrument list`), while stored projects still load. `remove n1` checks the note exists (`✗ no note n1 · notes lists them`). Status reads such as bare `fx` and `tracks` print `•`, and a bare scalar (`tempo`, `volume`) opens its fader. The agent tools `set_effects`, `vocode` and `add_drums` still run but are not advertised; use `set_fx`, `set_vocoder` and `set_rhythm`.
|
|
15
19
|
|
|
16
20
|
### Sessions, names and forks
|
|
17
21
|
|
|
@@ -27,17 +31,17 @@ Auto-naming (`src/session/naming.ts`) is gated on a local musical fingerprint: t
|
|
|
27
31
|
|
|
28
32
|
The `dawgd` protocol is newline-delimited JSON over a Unix socket in the session directory, or a hashed path under `$TMPDIR` when that path would exceed the platform socket-path limit. Every frame carries `v: 1`, frames are size-bounded, and every inbound frame is parsed from `unknown`. Clients send `hello`, `apply`, `transport`, `sync`, `focus`, `claim`, `meta`, and `ping`; the daemon replies with `welcome`, `result`, `snapshot`, `claimed`, `pong`, and typed `error` frames, and pushes `commit`, `transport`, `presence`, and `meta`.
|
|
29
33
|
|
|
30
|
-
The header (`dawg` · track · ▶/⏸ BPM · key · session · N windows, then model · rev · sync on the right; `test/tui.test.ts` freezes the order) sits above the highway. The highway sits above the activity strip and the prompt. Notes stream toward the hit line, and velocity sets glyph density (`░▒▓█`) and saturation. Beat and bar rules get stronger at each level. Sustains draw as beams with a decaying tail and a short ghost after release. Each hit runs approach glow → flash/burst at the line → fade. The hit line pulses on the beat, and a sweep marks the loop wrap. A track with no hits draws `<track> · empty ·
|
|
34
|
+
The header (`dawg` · track · ▶/⏸ BPM · key · session · N windows, then model · rev · sync on the right; `test/tui.test.ts` freezes the order) sits above the highway. The highway sits above the activity strip and the prompt. Notes stream toward the hit line, and velocity sets glyph density (`░▒▓█`) and saturation. Beat and bar rules get stronger at each level. Sustains draw as beams with a decaying tail and a short ghost after release. Each hit runs approach glow → flash/burst at the line → fade. The hit line pulses on the beat, and a sweep marks the loop wrap. A track with no hits draws `<track> · empty · space play · ctrl-p play mode · ctrl-k menu` on a reserved row that never covers the ruler, led by `hit kick at 0` on a kit, `/lyrics la la · sing ooh` on a vocal track and, from 100 columns, a seeded `try style <id>` (the prompt placeholder suggests the same style); while playing it reads `space stop · type a request`, or `space stop · ctrl-p play mode` with no agent. The hit line stays. Drum tracks use one lane per voice with a legend; lane projection is pluggable (`LaneProjection` in `tui/highway.ts`). By default every unmuted track is overlaid through its own projection and accent, the focused track drawn on top at full strength and the others dimmed; `/view focus` restores the single-track view. Animation is derived from transport time, so frame rate never changes timing. Frames render into a retained cell buffer, only changed cells are written with minimal color codes, and output is capped at about 30 fps (bursts of forced frames coalesce), under 60 KB/s for a style-sized song at 80x24.
|
|
31
35
|
|
|
32
|
-
Keys: `Space` (empty prompt) toggles playback. `Enter`
|
|
36
|
+
Keys: `Space` (empty prompt) toggles playback. `Enter` sends, or waits for the agent turn when the pill reads NEXT. `Shift-Enter`/`Ctrl-J` inserts a newline, `Alt-Enter` queues and `Ctrl-Q` toggles the NOW / NEXT pill. During an agent turn a typed command that parses locally (`tempo 120`, `play`, a note edit) runs at once beside the turn instead of steering it, so commands and keyboard play never wait on the model; prose and slash commands still steer. `Ctrl-Z`/`Ctrl-Y` undo and redo, and `Ctrl-O` or `/transcript` opens the transcript, which scrolls with ↑/↓, PgUp/PgDn and Home/End, and `/` cycles its filter (all, requests, ops, errors). `Esc` cancels an agent turn, closes the overlay or clears the draft. `Ctrl-L` redraws and `Ctrl-C` exits. Bracketed paste keeps multiline text intact.
|
|
33
37
|
|
|
34
38
|
Themes: `/theme default|high-contrast|mono`, `--theme`, `DAWG_THEME`. Color falls back through truecolor, 256-color, 16-color and monochrome. `NO_COLOR` and `TERM=dumb` are supported. `/motion off`, `--reduce-motion` and `DAWG_REDUCE_MOTION=1` switch to static states in the same positions. `--no-mouse` or `DAWG_MOUSE=0` keeps the terminal's own mouse selection (see **Mouse**).
|
|
35
39
|
|
|
36
|
-
Other code reports into the activity strip through `ActivityFeed` (`tui/activity.ts`): `pushCard(text, {tone, baseRevision, resultRevision, hint})`, `pushError(text)`, `setSpinner(label | undefined)`, `setQueueDepth(n)` and `applyAgentEvent(event)`, which accepts the agent's streaming `AgentEvent`s unchanged. Command handlers return a `Receipt` (`{ok: true | false | "warn", text}`), so a failure such as `main is not a drum track` is red because it says so, not because of its wording; `receiptTone` only classifies the legacy strings that remain. The
|
|
40
|
+
Other code reports into the activity strip through `ActivityFeed` (`tui/activity.ts`): `pushCard(text, {tone, baseRevision, resultRevision, hint})`, `pushError(text)`, `setSpinner(label | undefined)`, `setQueueDepth(n)` and `applyAgentEvent(event)`, which accepts the agent's streaming `AgentEvent`s unchanged. Command handlers return a `Receipt` (`{ok: true | false | "warn", text}`), so a failure such as `main is not a drum track` is red because it says so, not because of its wording; `receiptTone` only classifies the legacy strings that remain. The `ctrl-z undo` hint rides only on receipts that changed the score, and only the first three in a session. `/help`, `/sessions` and `/tracks` open a scrollable text overlay (`TuiApp.openText`) and leave one summary card in the strip; `/help` is generated from `src/commands/help.ts`, which also feeds `dawg --help` and the usage hints that answer an unknown `/word` or a near-miss such as `pan 3` without a model call. A known verb followed by a sentence (three or more words, no numbers: `add a walking bass in A minor`) goes to the agent instead. An unknown `/word` names the nearest command (`unknown command /clik · did you mean /click? · /help`); a bare word one edit from a command whose remaining words parse as its arguments is suggested locally (`tempoo 90 · did you mean tempo 90?`); other bare words are requests for the agent.
|
|
37
41
|
|
|
38
|
-
`/help` opens a short
|
|
42
|
+
`/help` opens a short start page (type a request or a style, Ctrl-P, Ctrl-K, `?`, undo) and the ten topics: **sound**, **voice**, **effects**, **rhythm**, **chords**, **mix**, **arrange**, **project**, **keys** and **agent**. The same ids open the same subject in all three doors: `/help voice` lists its commands, `/guide voice` explains it, `/menu voice` opens its Ctrl-K section. `/help all` is the full reference and `/help <command>` (`/help autotune`) shows one command's usage. Entries are written bare (`tempo 96`, `track drums`); only the free-text window verbs keep the slash (`/rename`, `/fork`, `/resume`, `/auth`), and `/play` (play mode, Ctrl-P) keeps it because bare `play` is the transport. Groups follow the Ctrl-K root order and stay within about fifteen rows, with sub-groups such as `sound · samples`; guitar amp presets are `rig <preset>` under effects, and the agent key is `model key` (`login` is an alias, not listed). Placeholders read `<drum>`, `<sample>`, `<register>` and `<lane>`. `/help keys` is drawn from the key table in `tui/grammar.ts`, so it always matches the keys. Old topic names still open their topic as aliases (music → arrange, session → project, window → keys, tuning → chords, performance → sound, and more in `src/lang/glossary.ts`). An unknown topic answers `no topic X · did you mean Y · /help`.
|
|
39
43
|
|
|
40
|
-
`/guide` (or F1) opens the user guides
|
|
44
|
+
`/guide` (or F1) opens the user guides (`guides/*.md`, shipped in the package and rendered unchanged on dawg.sh/docs). The tree follows the topics: Getting started, then Using dawg (`keys`: the menu, faders, play mode), Sound, Voice, Effects, Rhythm, Chords (with Tuning), Mix (with Automation), Arrange (notes, tracks, tempo, styles, sections), Project (files, sessions) and Agent (show-me, providers, web search). Every guide uses one template, **Ask**, **Type it yourself**, **Menu**, **Keys** and **Next**, so the same answer is always in the same place. The pane: ↑↓ (j k) move, → l or Enter expand a section then open a guide, ← h collapse or go to the parent and back from a page, `/` filters by title and text, Esc clears the filter, then steps back, then closes. `/guide <topic>` opens one guide directly and accepts every topic id and alias (`/guide scale` opens Chords). `guides/guides.test.ts` keeps every guide within 20 rows at 72 columns, checks the template, every `/command` and every `Ctrl-K ›` path, and lints the words against the glossary.
|
|
41
45
|
|
|
42
46
|
The local command path understands requests such as:
|
|
43
47
|
|
|
@@ -92,12 +96,15 @@ duration note <id> 0.25
|
|
|
92
96
|
/export loop.track.json
|
|
93
97
|
/import loop.track.json
|
|
94
98
|
/model opus-5.5
|
|
99
|
+
/model fast
|
|
95
100
|
/help
|
|
96
101
|
/help all
|
|
97
102
|
```
|
|
98
103
|
|
|
99
104
|
`/track <name>` focuses a track in this window and creates it when it is new; when another live window has it focused the reply is `<name> is open in another window` and focus stays put. Music words are bare and app commands take a slash; `tracks`, `export`, `import` and `track` still work bare as aliases but are listed once.
|
|
100
105
|
|
|
106
|
+
`/track rm <name>` (aliases `remove` and `delete`) removes a track and its notes, and drops any `vocoder.src` or `autotune.from` on another track that named it; the last track stays (`/clear` empties it). `/track move <name> <position>` puts a track at a 1-based place in the list. Names match the id or the display name (`/track rm piano b`). Both undo with ^Z, and both sit in Ctrl-K › Arrange › tracks as **move** and **remove** on the focused track.
|
|
107
|
+
|
|
101
108
|
Drum tracks use the `kit` instrument (a track named `drums` gets it automatically). Notes on a kit track keep the score's MIDI pitch field, using General MIDI percussion numbers (kick 36, rim 37, snare 38, clap 39, closed hat 42, tom 45, open hat 46), so drum hits round-trip through `track.loop/v1` unchanged and the highway draws them in one lane per voice. Grammar, one command per prompt, beats in score beats:
|
|
102
109
|
|
|
103
110
|
```text
|
|
@@ -124,16 +131,24 @@ undo | redo
|
|
|
124
131
|
|
|
125
132
|
Effects live on the track as optional `filter {cutoff, resonance}`, `delay {beats, feedback, mix}`, `reverb {mix, size}`, `filterAutomation`, `resonanceAutomation`, `delayFeedbackAutomation`, `delayMixAutomation`, and `solo` fields. Effect lanes modulate an existing effect: a resonance lane needs a filter and the delay lanes need a delay. Documents written before these fields existed still parse; out-of-range or non-finite values are rejected. Undo and redo append ordinary session events, so history is shared by every window and a new edit clears the redo stack.
|
|
126
133
|
|
|
127
|
-
Unrecognized prompts go to the agent whenever a provider is configured (`DAWG_AI=0` disables it); see **Providers and auth** below. `src/agent/models.ts` holds the model catalog: frontier (`opus-5.5`, `fable-5.1`, `sol-6.1`, `gemini-3.1-pro`), fast (`sonnet-5.5`, `haiku-4.5`, `gpt-5.4-mini`, `gemini-3.8-flash`) and open weights (`deepseek-v4-pro`, `kimi-k3`, `qwen3.8-27b`, `glm-5.3`, `llama-4-maverick`), each with its gateway and OpenRouter ID, checked against the live gateway and OpenRouter model lists and tagged for tool use. The `/model` picker joins the catalog with the live list and shows only tool-calling models; any other `vendor/model` ID the provider lists is accepted too. `DAWG_MODEL` picks the model for one run, and an unknown value fails at startup with the valid list. The default is `opus-5.5`. `DAWG_OPUS_MODEL` / `DAWG_SOL_MODEL` still remap those two aliases.
|
|
134
|
+
Unrecognized prompts go to the agent whenever a provider is configured (`DAWG_AI=0` disables it); see **Providers and auth** below. `src/agent/models.ts` holds the model catalog: frontier (`opus-5.5`, `fable-5.1`, `sol-6.1`, `gemini-3.1-pro`), fast (`sonnet-5.5`, `haiku-5.5`, `haiku-4.5`, `gpt-5.4-mini`, `gemini-3.8-flash`, `glm-5.3-flash`) and open weights (`deepseek-v4-pro`, `kimi-k3`, `qwen3.8-27b`, `glm-5.3`, `llama-4-maverick`), each with its gateway and OpenRouter ID, checked against the live gateway and OpenRouter model lists and tagged for tool use. The `/model` picker joins the catalog with the live list and shows only tool-calling models; any other `vendor/model` ID the provider lists is accepted too, and on OpenRouter a routing variant suffix such as `:nitro`, `:free` or `:floor` is kept (`DAWG_MODEL=google/gemini-2.5-flash:nitro`). Anthropic models get a prompt-cache breakpoint on the static system prompt, which stays byte-identical across steps. `DAWG_MODEL` picks the model for one run, and an unknown value fails at startup with the valid list. The default is `opus-5.5`. `/model fast` (also `dawg model fast`, `DAWG_MODEL=fast`, and `gatewayModel`/`openrouterModel: "fast"` resolved at save) picks `haiku-5.5`, the fastest model in `bench/agent-eval` that passes the non-style tasks about as well as the default (`FAST_MODEL_ALIAS` in `src/agent/models.ts`; numbers in `docs/model-eval.md`). A catalog row may name a `fallback` on another vendor: `haiku-5.5` falls back to `glm-5.3-flash` for one try when every attempt failed before the first byte (repeated 5xx, header timeout, or a stream that sends nothing for `GATEWAY_FIRST_BYTE_TIMEOUT_MS`, 20 s; any byte, a keep-alive comment included, counts). A reply that starts with `Done: <summary>` beside its tool calls ends the turn once they apply, saving the summary round trip, unless the step wrote notes, rhythms, chords or structure (`CONTENT_TOOLS`), which always get a review step. `DAWG_OPUS_MODEL` / `DAWG_SOL_MODEL` still remap those two aliases.
|
|
128
135
|
|
|
129
136
|
### Agent turns
|
|
130
137
|
|
|
131
138
|
A turn calls `POST /v1/chat/completions` with `stream: true` and one JSON-schema tool for each operation family. The OpenAI-compatible SSE stream is parsed locally with `fetch`, so the agent adds no runtime dependency. Each request sends a compact, deterministic **composition brief** instead of raw logs. It contains revision, tempo, meter, bars, key, tracks with instrument, mix, note count and pitch range, the focused track's notes, recent accepted operations and the instrument list. It is capped at 12 KiB and never includes environment values.
|
|
132
139
|
|
|
133
|
-
When a tool call finishes streaming, it passes three checks: the tool's own argument checks, the planner's bounded operation validator, and a dry run of the score reducer. Only then is it committed through the session as a separate revision pinned to the revision it was planned against. If the call fails a check, or another window committed first (stale revision), dawg rejects it without changing the score. The model receives the diagnostic as the tool result and can correct itself. A turn is bounded to 8 steps, 32 tool calls,
|
|
140
|
+
When a tool call finishes streaming, it passes three checks: the tool's own argument checks, the planner's bounded operation validator, and a dry run of the score reducer. Only then is it committed through the session as a separate revision pinned to the revision it was planned against. If the call fails a check, or another window committed first (stale revision), dawg rejects it without changing the score. The model receives the diagnostic as the tool result and can correct itself. A turn is bounded to 8 steps, 32 tool calls, 1 MiB of streamed response and a 90 s timeout.
|
|
134
141
|
|
|
135
142
|
`runAgentTurn` (`src/agent/agent.ts`) emits structured progress events for the TUI: `step`, `text-delta`, `tool-start`, `tool-applied` (with `summary`, `baseRevision`, `resultRevision` and `trackId`), `tool-rejected` (with `diagnostic`), and a final `done` or `error` (`aborted`, `timeout`, `budget`, `provider`). To add an operation family, append a tool to `AGENT_TOOLS` in `src/agent/tools.ts`. The schema, dispatch and validation all come from that one entry.
|
|
136
143
|
|
|
144
|
+
Times in tools and the brief are beats from 0; the system prompt maps musical counts (beats 2 and 4 of a 4/4 bar are beats 1 and 3) so models place backbeats correctly. `update_notes` takes either an absolute `pitch` or a relative `transpose` in semitones, and its summary lists each moved note as `id before→after`. Tool schemas are sent with unions rewritten as `anyOf` branches (`src/agent/portable-schema.ts`) so Gemini accepts them. `bench/agent-eval/` measures how well a model drives these tools; see `docs/model-eval.md`.
|
|
145
|
+
|
|
146
|
+
### Show-me (command mode)
|
|
147
|
+
|
|
148
|
+
In a terminal, with `/showme on` (the default) or `quiet` and an API provider (the gateway or a direct key), a turn runs in **command mode** (`src/agent/command-agent.ts`). The model writes dawg prompt commands, one per line, as a person types them. The prompt bar shows the line being written as ghost text at the model's own speed. Each complete line that parses (`isAgentCommand`, the same `commandParses` the prompt's typo check uses) runs through the TUI's `submit()`, the path Enter runs: same parser, commit, undo step and project sync. The score changes during the turn. Lines that are not commands are prose for the transcript. Window-only commands (`/model`, `model key`, `/showme`, `/theme`, `/quit`) never run. A failed line goes back to the model with its error for a correction round, up to 4 steps. A small JSON tool set stays for what commands cannot express: workspace files, web, media, `explain`, `preview_sound` and `measure_mix`.
|
|
149
|
+
|
|
150
|
+
A caption under the prompt names the gesture (`src/agent/show-me.ts`). For a parameter value, the fader glides about 150 ms in 30 ms steps through staged audition while the loop plays, then the command commits. For a note or drum hit, the caption names the play-mode key and the octave keys, and the note sounds in time behind the stream (`NoteScheduler`): grid-spaced when the model is faster than the tempo, step entry when slower. Media tools name the `dawg media` verb. At the end of the turn the caption gives a do-it-yourself hint with the Ctrl-K path. Nothing is replayed after the turn, and typing, play mode and the next turn are never blocked. `/showme off`, non-TTY runs and subscription providers use the JSON tool loop above. Command mode sends about a ninth of the input tokens and finishes in about half the time; see `docs/show-me.md` for the measurements and the exceptions.
|
|
151
|
+
|
|
137
152
|
### Workspace and web tools
|
|
138
153
|
|
|
139
154
|
The project directory (the directory `dawg` runs in) is the agent's workspace. Six more entries in `AGENT_TOOLS` give the model bounded file and web access on both the gateway and xcb paths; `src/agent/workspace.ts` holds the path policy and `src/web/` the network side.
|
|
@@ -142,13 +157,13 @@ The project directory (the directory `dawg` runs in) is the agent's workspace. S
|
|
|
142
157
|
- `write_file`, `edit_file`: only `song.ts` and the focused track's `tracks/<slug>/` directory (`core/slug.ts` derives the slug from the track name). Writes are atomic (temp file and rename), capped at 1 MiB, and create parent directories. `edit_file` replaces exactly one occurrence of `old`; zero or several matches return a count and nothing changes. `tracks/<slug>/notes.md` is the model's scratchpad and is never parsed. After a write, the optional host hook `onWorkspaceWrite(path)` can append text to the tool result (the project lane uses it to report how `track.ts` applied).
|
|
143
158
|
- Every path is resolved lexically and then through `realpath`; `..`, absolute paths outside the root, symlinks that leave the project and anything under `.dawg/` are rejected with a diagnostic naming the writable roots. The brief gains a `project` entry with a tree of at most 30 lines and the first 1 KiB of the focused `notes.md`; both are shed before track summaries when the 12 KiB budget is tight.
|
|
144
159
|
- `web_search` returns up to 8 `{title, url, snippet}` results. Providers, first match wins: `BRAVE_SEARCH_API_KEY` (explicit override); an AI Gateway key, which makes one non-streaming `anthropic/claude-haiku-4.5` call with the gateway's server-side search tool (`DAWG_WEB_SEARCH=exa|perplexity|parallel|browserbase`, default `exa`; the gateway bills the search, about $0.007 for Exa; its reported cost already includes that fee, so dawg records it as-is and uses the fee as an estimate only when no cost is reported; a live Exa search with three results cost $0.013); an OpenRouter key (`OPENROUTER_API_KEY`), which uses the `web` plugin and its `url_citation` annotations; else DuckDuckGo's HTML endpoint. `DAWG_WEB_SEARCH` can also pin a backend (`duckduckgo`, `openrouter`, `gateway`, `brave`) when its credentials exist. A failing paid provider falls through to DuckDuckGo and the result says so. The activity card names the answering provider (`searched via gateway · exa`), and `WebHost.onSpend` reports each billed search for the spend ledger.
|
|
145
|
-
- `fetch_url` fetches one public http(s) URL locally: hostnames are resolved first and loopback, private, link-local, CGNAT and multicast addresses (IPv4, IPv6 and mapped) are refused, redirects (at most 3) are re-checked per hop, bodies stop at 2 MiB and the text handed to the model at 32 KiB. HTML is reduced to headings, lists, links and paragraphs; scripts, styles and navigation are dropped. Fetched text is untrusted and the system prompt says so.
|
|
160
|
+
- `fetch_url` fetches one public http(s) URL locally: hostnames are resolved first and loopback, private, link-local, CGNAT and multicast addresses (IPv4, IPv6 and mapped) are refused, the connection is pinned to the address that was checked (the Host header and TLS server name keep the original name, so DNS rebinding cannot swap in a private address), IPv4-compatible, 6to4, site-local and discard-prefix IPv6 forms are classified by what they embed, redirects (at most 3) are re-checked per hop, bodies stop at 2 MiB and the text handed to the model at 32 KiB. HTML is reduced to headings, lists, links and paragraphs; scripts, styles and navigation are dropped. Fetched text is untrusted and the system prompt says so.
|
|
146
161
|
|
|
147
162
|
All limits live in `WORKSPACE_LIMITS`, `SEARCH_LIMITS` and `FETCH_LIMITS`. Search and fetch take an injectable `fetch` (and `lookup`), so tests run on fixtures in `src/web/fixtures/` without network. The gateway search fixture is derived from the documented response shape; capture a live response once to confirm it.
|
|
148
163
|
|
|
149
164
|
### Media tools
|
|
150
165
|
|
|
151
|
-
Seven more `AGENT_TOOLS` entries (`src/media/`) turn reference audio into material for a track. They work on files under the focused track's `tracks/<slug>/downloads/` (the same slug and write scope as `write_file`), return project-relative output paths so the model can chain them, and never install anything: a missing binary is reported with its install command. `dawg media <verb>` runs the same code from the shell, and `dawg media doctor` lists the backend, each binary, how it runs and how to install it.
|
|
166
|
+
Seven more `AGENT_TOOLS` entries (`src/media/`) turn reference audio into material for a track. They work on files under the focused track's `tracks/<slug>/downloads/` (the same slug and write scope as `write_file`), return project-relative output paths so the model can chain them, and never install anything: a missing binary is reported with its install command. `dawg media <verb>` runs the same code from the shell, and `dawg media doctor` lists the backend, each binary, how it runs and how to install it, plus one line for the project's pitch analysis cache (`.dawg/analysis`: files, size against its 64 MB cap, least recently used pruned, tracker version).
|
|
152
167
|
|
|
153
168
|
- `download_audio {url, name?}`: YouTube only (`youtube.com`, `youtu.be`, `music.youtube.com`). Writes `<name>.wav` plus a `<name>.json` sidecar (title, duration, source URL, backend, sha256, time). The same source URL is reused instead of downloaded again. yt-dlp runs with `--no-playlist`, `--max-filesize 500m` and a 15 min budget.
|
|
154
169
|
- `split_stems {file}`: six stems (vocals, drums, bass, guitar, piano, other) into `<base>.stems/`, cached once present. 20 min budget.
|
|
@@ -164,7 +179,7 @@ Seven more `AGENT_TOOLS` entries (`src/media/`) turn reference audio into materi
|
|
|
164
179
|
|
|
165
180
|
### Providers and auth
|
|
166
181
|
|
|
167
|
-
`src/agent/provider.ts` picks a backend per turn: `DAWG_PROVIDER`, then the choice saved in `~/.config/dawg/config.json`, then `auto` (AI Gateway, then OpenRouter, then a ready Codex or Claude subscription, else offline with a `dawg
|
|
182
|
+
`src/agent/provider.ts` picks a backend per turn: `DAWG_PROVIDER`, then the choice saved in `~/.config/dawg/config.json`, then `auto` (AI Gateway, then OpenRouter, then a ready Codex or Claude subscription, else offline with a `dawg model key` hint). The config holds `provider`, the model and, for subscriptions, the xcb account. It is written atomically with 0600 permissions and never holds a key. A saved choice that stops working (revoked key, account gone) comes back as `offline` with `invalidSaved` and the reason; startup and `dawg model key` (alias `dawg login`) say so once and open the picker rather than switching providers. Only `dawg logout`, `/logout`, `dawg model key <provider>` and `/model` change it.
|
|
168
183
|
|
|
169
184
|
Keys (`ai-gateway`, `openrouter`) resolve from the environment (`AI_GATEWAY_API_KEY`, `OPENROUTER_API_KEY`), then the macOS Keychain (service `dawg`), then `~/.config/dawg/credentials.json` (0600 under a 0700 directory, written atomically through a temp file and rename). Storing uses `security -i` with the command on stdin, so a key never appears on an argv. Keys must match `[A-Za-z0-9._-]{16,256}` and are shown only masked (`vck_…abcd`). Nothing auth-related goes to `.dawg/`.
|
|
170
185
|
|
|
@@ -178,9 +193,9 @@ Keys (`ai-gateway`, `openrouter`) resolve from the environment (`AI_GATEWAY_API_
|
|
|
178
193
|
|
|
179
194
|
Every subprocess goes through the injectable `CommandRunner` in `src/auth/runner.ts`, which bounds output, writes stdin and on abort sends SIGTERM (SIGKILL after 15 s) and waits for exit. Tests script `vercel`, `security` and `xcb` through it and run OpenRouter against a local fake server.
|
|
180
195
|
|
|
181
|
-
`/login` in the TUI calls `handoff()`: it stops the frame timer, detaches stdin, leaves raw mode, bracketed paste and the alternate screen, runs the same flow on the real terminal, then re-enters, clears and forces a full redraw.
|
|
196
|
+
`model key` (alias `/login`) in the TUI calls `handoff()`: it stops the frame timer, detaches stdin, leaves raw mode, bracketed paste and the alternate screen, runs the same flow on the real terminal, then re-enters, clears and forces a full redraw.
|
|
182
197
|
|
|
183
|
-
The gateway and OpenRouter share `src/agent/gateway.ts`, an OpenAI-compatible streaming client with tool calls that requests `stream_options.include_usage`. `src/agent/usage.ts` prices each usage chunk, using the provider's own `cost` when present and otherwise tokens × the models.dev price. It keeps the session total and a daily ledger in `~/.config/dawg/usage.json` (31 days, lock file plus atomic rename) that windows share, and draws the spend line under the prompt (`$0.12 session · $0.48 today · opus-5.5 · gateway`; `subscription` for xcb; `no model
|
|
198
|
+
The gateway and OpenRouter share `src/agent/gateway.ts`, an OpenAI-compatible streaming client with tool calls that requests `stream_options.include_usage`. `src/agent/usage.ts` prices each usage chunk, using the provider's own `cost` when present and otherwise tokens × the models.dev price. It keeps the session total and a daily ledger in `~/.config/dawg/usage.json` (31 days, lock file plus atomic rename) that windows share, and draws the spend line under the prompt (`$0.12 session · $0.48 today · opus-5.5 · gateway`; `subscription` for xcb; `commands only` offline, with no model key nag; it narrows by dropping today, then session). Billed web searches add to the same meter. Prices come from `https://models.dev/api.json`, cached in `~/.config/dawg/cache/` for 24 h, fetched with a timeout and size cap, with a stale cache preferred to nothing offline; on OpenRouter its own `/models` prices win. The picker's `~$0.005/prompt` is `TYPICAL_PROMPT` (≈ 53,000 input + 600 output tokens, measured from the system prompt, tool schemas and a fixture brief over about 2 requests at about 3 bytes per token, as the agent eval in `bench/agent-eval` observed) × price.
|
|
184
199
|
|
|
185
200
|
The xcb provider (`src/agent/xcb.ts`, `src/agent/xcb-agent.ts`) calls `xcb --json generate` with one `{version:1, account, model, prompt, timeoutMs, maxOutputBytes}` request on stdin. xcb exposes zero tools and does not stream, so the prompt carries the system rules, the composition brief and the tool catalog as JSON schemas, and asks for exactly one `{ops:[{tool,args}], say?, done}` object. The reply is untrusted. dawg takes the first balanced JSON object in at most 64 KiB, allows at most 16 ops and caps `say` at 400 characters. Each op then goes through `executeCall`, the same argument checks, operation validator, reducer dry run and per-op revision commit used by the gateway loop, and emits the same `tool-applied`/`tool-rejected`/`text-delta` events. If a reply cannot be parsed, an op is rejected or `done` is false, dawg makes another call with the per-op results, up to 3 calls and within the normal turn budgets. Esc aborts the turn, which terminates the xcb child and keeps every accepted revision. The child timeout is `timeoutMs + 75 s`, because the first `generate` per binding (and after an xcb or provider update) admits the account and can take up to a minute longer. A `busy` result, when two first calls hit one account, is retried with backoff. Accounts come from `xcb --json generate --capabilities`, parsed field by field from `unknown`. dawg never runs the xcb installer.
|
|
186
201
|
|
|
@@ -188,7 +203,7 @@ The xcb provider (`src/agent/xcb.ts`, `src/agent/xcb-agent.ts`) calls `xcb --jso
|
|
|
188
203
|
|
|
189
204
|
Playback renders the score to interleaved stereo 16-bit PCM with deterministic sine, piano, pluck, bass, saw, square, and triangle voices and a synthesized kit whose noise comes from a PRNG seeded by each note, so every render is byte-identical. Track volume and pan automation, the low-pass filter (with cutoff and resonance lanes), the delay send (with feedback and mix lanes), and the reverb send are applied per track; mute always silences a track and any solo silences unsoloed tracks. Pan uses an equal-power law (-1 left, 1 right). The delay is a stereo ping-pong (first repeat on the panned side, later repeats alternate) and the reverb is a Freeverb-style network of eight parallel damped combs and four series allpasses per channel, with the right channel's delay lines offset for width; both use only integer delay lengths and fixed coefficients, so renders stay deterministic.
|
|
190
205
|
|
|
191
|
-
Audio engine. With `dawgd` running only the daemon plays audio; on the file-lock fallback a per-session audio lock keeps multiple TUI windows from starting duplicate voices. The engine renders one loop with every tail (release, delay, reverb) folded back onto the loop start, so the buffer repeats seamlessly, and streams it as raw s16le stereo into one long-lived player process, paced by the wall clock with about 200 ms queued. An edit renders the new loop and swaps it in at the current loop position without restarting the player; a tempo change keeps the musical beat; a seek or a drift above 30 ms re-anchors the write position to the shared transport clock, offset by the queued audio, so the transport matches what you hear. Backends, in order: `ffplay -f s16le -i -`, then SoX `play -t raw -`, then (macOS) `afplay` re-rendering a loop-folded WAV rotated to the current beat on each edit, the only backend that restarts. `DAWG_AUDIO_BACKEND=ffplay|sox|afplay|none` forces one, `DAWG_AUDIO_PLAYER="cmd {rate} {channels}"` streams into any stdin player, and `DAWG_AUDIO=0` disables sound. `dawg auth status` and `/auth` print the detected backend. The renderer is deterministic and independently testable; a native or sample-backed instrument backend can replace it behind the same player port.
|
|
206
|
+
Audio engine. With `dawgd` running only the daemon plays audio; on the file-lock fallback a per-session audio lock keeps multiple TUI windows from starting duplicate voices. The engine renders one loop with every tail (release, delay, reverb) folded back onto the loop start, so the buffer repeats seamlessly, and streams it as raw s16le stereo into one long-lived player process, paced by the wall clock with about 200 ms queued. An edit renders the new loop and swaps it in at the current loop position without restarting the player; a tempo change keeps the musical beat; a seek or a drift above 30 ms re-anchors the write position to the shared transport clock, offset by the queued audio, so the transport matches what you hear. Backends, in order: the native sink (`native/sink`, a Rust/cpal ring drained by the device's audio callback, loaded through bun:ffi after its sha256 matches the shipped manifest; it receives the same s16le stream, converted exactly to f32, paced by a 5 ms pump with a 15 ms play lead), then `ffplay -f s16le -i -`, then SoX `play -t raw -`, then (macOS) `afplay` re-rendering a loop-folded WAV rotated to the current beat on each edit, the only backend that restarts. `DAWG_AUDIO_BACKEND=native|ffplay|sox|afplay|none` forces one, `DAWG_AUDIO_PLAYER="cmd {rate} {channels}"` streams into any stdin player, and `DAWG_AUDIO=0` disables sound. `dawg auth status` and `/auth` print the detected backend; `dawg doctor` also prints why the native sink is or is not in use, the play lead, and the audio devices. The renderer is deterministic and independently testable; a native or sample-backed instrument backend can replace it behind the same player port.
|
|
192
207
|
Set `DAWG_AUDIO=0` for headless sessions.
|
|
193
208
|
|
|
194
209
|
Use `DAWG_DEMO=1 bun run src/main.ts` for a deterministic non-interactive frame stream while developing the renderer.
|
|
@@ -212,16 +227,16 @@ tracks/<slug>/samples/ audio a sampler references by relative path
|
|
|
212
227
|
|
|
213
228
|
SDK. `core/sdk/v1.ts` is one dependency-free file with JSDoc on every export, because its signatures are what an agent reads. Authors write beats; `song()` returns a `track.loop/v1` document in integer ticks (`round(beat × ticksPerBeat)`). Builders: `note(pitch, start, length = 1, velocity = 0.8)`, `seq("E2 . G2", {from, step, len, vel})` (`.`, `-`, `_` rest), `hit(voice, start, velocity, length = 0.25)` and `hits(voice, beats)` for `kit` voices (`kick`, `snare`, `hat`, …) and sampler voices, `every(step, {from, until})`, `sampler(voices, {mode})`, `slices(src, count)`, `chord(symbol, start, length, opts)`, `progression(chords, opts)` (see [Chords](#chords)), `track({...})` and `song({...})`. Note ids are content hashes, so the files never carry them and dawg keeps the session's ids for notes that did not change.
|
|
214
229
|
|
|
215
|
-
Evaluation. `evaluateProject(dir)` (`core/sdk/eval.ts`) imports `song.ts` in a fresh `bun --no-addons --no-install` child with cwd at the project, an environment of only `PATH`, `HOME` and `TMPDIR`, a 10 s timeout and
|
|
230
|
+
Evaluation. `evaluateProject(dir)` (`core/sdk/eval.ts`) imports `song.ts` in a fresh `bun --no-addons --no-install` child with cwd at the project, an environment of only `PATH`, `HOME` and `TMPDIR`, a 10 s timeout and 32 MiB of output (above the largest score `SCORE_LIMITS` allows; an overflow is reported as such, not as a timeout), then decodes the document through the ordinary score validator. Failures are diagnostics `file:line:col message`, never throws. Evaluation is an isolated process, not a sandbox: project code runs with your user's file and network access, like any build script, and the sync evaluates it on startup and on every change, so open only projects you trust.
|
|
216
231
|
|
|
217
232
|
Typecheck. `typecheckProject(dir)` (`src/project/typecheck.ts`) runs the native TypeScript 7 compiler from the `typescript` dependency with `--incremental` state in `.dawg/tsbuild`. A cold check of a three-track project takes about 65 ms and a warm one about 26 ms on an M-series Mac. The header shows `types ✓` or `types ✗ N`.
|
|
218
233
|
|
|
219
234
|
Two-way sync (`src/project/sync.ts`) runs in every window of a project:
|
|
220
235
|
|
|
221
236
|
- Files to score: `fs.watch` on the project and `tracks/` (plus a 1.5 s poll) with a 150 ms debounce. A changed source is evaluated, its notes adopt the session's ids, and `diffScores` (`core/diff.ts`) turns the difference into the smallest list of score operations, committed as one `files.apply` revision through the session port, so dawgd rebases it like an agent intent and undo drops it as one step. The window shows `applied from files · 2 notes, 1 track`. A file that fails to evaluate leaves the score untouched and shows `files rejected · <diagnostic>` once per distinct error.
|
|
222
|
-
- Score to files: after any accepted revision (TUI, agent, another window) the window reprints only the files whose
|
|
223
|
-
- Startup: if any source differs from the hash in `.dawg/sync.json` (edited while dawg was closed, or never written by dawg), the files win;
|
|
224
|
-
- Echo: a window
|
|
237
|
+
- Score to files: after any accepted revision (TUI, agent, another window) the window reprints only the files whose own track (or song) slice changed since their last evaluation, so hand formatting and comments in untouched files survive. A file whose bytes no longer hash to what dawg last wrote (edited in an editor, or failing to evaluate) is never overwritten; the window shows `<file> edited · not overwritten` and the next look applies it. `.dawg/sync.json` is read, written and merged under `.dawg/sync.lock`, and every write is fsynced with its directory. A track file left behind by a rename or removal is deleted only if its hash still matches what dawg wrote; a hand-edited one is kept.
|
|
238
|
+
- Startup: the files belong to one session at a time (recorded in `.dawg/sync.json`). A window on another session while the owner is still open stays detached and shows `files belong to session <id>`. Otherwise, if any source differs from the hash in `.dawg/sync.json` (edited while dawg was closed, or never written by dawg), the files win; else the session wins and the files are reprinted.
|
|
239
|
+
- Echo: a file whose hash is one dawg wrote (in any window) or one this window already evaluated is skipped, so a window never reverts its own newer edit by applying another window's reprint. Hashes cover every project source (`.ts`/`.js`/`.json` helper modules, `.scl`/`.kbm` tunings), so editing an imported helper syncs too; a look with nothing new reports `files unchanged`.
|
|
225
240
|
- The agent's `write_file`/`edit_file` on a `.ts` source applies before the tool result returns; the result carries the outcome line, `types ✓` or `types ✗ N` and up to eight diagnostics.
|
|
226
241
|
|
|
227
242
|
The printer (`core/sdk/print.ts`) is deterministic and Prettier-stable (`prettier --check` passes on its output), prints only non-default fields, and satisfies `print(evaluate(print(score))) = print(score)`.
|
|
@@ -235,7 +250,7 @@ Score format. The score stays `track.loop/v1` with `version: 1`: every addition
|
|
|
235
250
|
Every track has one fixed effects chain (`FX_CHAIN` in `core/fx.ts`, DSP in `src/audio/effects/`):
|
|
236
251
|
|
|
237
252
|
```text
|
|
238
|
-
filter → djf → autofilter → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
|
|
253
|
+
filter → djf → autofilter → formant → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
|
|
239
254
|
```
|
|
240
255
|
|
|
241
256
|
Stages before `pan` run on the track's mono voice sum; pan spreads it to stereo with the equal-power law; the rest run on the stereo pair. An effect that is off costs nothing. The core set — **filter, auto filter, distortion, tremolo, compressor, chorus, delay, reverb** — leads the Effects menu and the agent brief; dj filter, vowel, bitcrush, phaser, leslie, post gain, orbit and duck are under **more effects** for Strudel parity.
|
|
@@ -264,7 +279,7 @@ automate distort-drive points 0:1 8:6 every numeric fx param has a lane
|
|
|
264
279
|
|
|
265
280
|
Aliases: `dist`, `comp`, `room`, `bitcrush`, `trem`, `auto-filter`, `bus`/`o` (orbit), `sidechain`/`duckorbit` (duck), `lpf`/`hpf`/`bpf` (filter with that type). The menu's Effects section opens each effect on its on/off toggle, presets and simple parameters; **advanced** lists every parameter with its Strudel names. The agent's `set_fx` tool takes the same names and presets.
|
|
266
281
|
|
|
267
|
-
Presets: filter `warm dark acid thin telephone`; autofilter `slow-sweep wobble s&h hpf-rise env-follow`; distort `warm crunch fuzz fold shape`; tremolo `gentle eighth-chop pulse`; compressor `gentle punch squash`; chorus `subtle wide seasick`; delay `ping-pong dotted-eighth slapback dub`; reverb `room hall plate ambient`; djf `dark thin`; vowel `a o ee`; crush `8-bit lofi destroy`; phaser `slow fast`; leslie `fast slow`; duck `pump subtle gate`.
|
|
282
|
+
Presets: filter `warm dark acid thin telephone`; autofilter `slow-sweep wobble s&h hpf-rise env-follow`; distort `warm crunch fuzz fold shape`; tremolo `gentle eighth-chop pulse`; compressor `gentle punch squash`; chorus `subtle wide seasick`; delay `ping-pong dotted-eighth slapback dub`; reverb `room hall plate ambient`; djf `dark thin`; formant `deep giant bright tiny`; vowel `a o ee`; crush `8-bit lofi destroy`; phaser `slow fast`; leslie `fast slow`; duck `pump subtle gate`.
|
|
268
283
|
|
|
269
284
|
The DSP is clean-room, written from public documentation of the parameters and standard literature (RBJ biquads, a Stilson/Smith-style ladder, Freeverb-style combs and allpasses, the Giannoulis–Massberg–Reiss compressor), not from Strudel or superdough source (AGPL). Renders stay deterministic: the random S&H shape hashes the cycle index, so cold, cached and worker renders are byte-identical (`src/audio/renderer.test.ts`).
|
|
270
285
|
|
|
@@ -286,8 +301,12 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
|
|
|
286
301
|
| **autofilter** | shape | sine / tri / square / saw / ramp / random | sine | | |
|
|
287
302
|
| autofilter | phase | 0..1 | 0 | | |
|
|
288
303
|
| autofilter | follow | -6..6 oct | 0 | `lpenv (per note, see synth)` | `autofilter-follow` |
|
|
304
|
+
| **formant** | shift | -12..12 st | 0 | | `formant-shift` |
|
|
305
|
+
| **formant** | mix | 0..1 | 1 | | `formant-mix` |
|
|
289
306
|
| **vowel** | vowel | a / e / i / o / u / ae / aa / oe / ue / y / uh / un / en / an / on | a | `vowel` | |
|
|
290
307
|
| **vowel** | mix | 0..1 | 1 | | `vowel-mix` |
|
|
308
|
+
| vowel | to (optional) | a / e / i / o / u / ae / aa / oe / ue / y / uh / un / en / an / on | (none) | | |
|
|
309
|
+
| vowel | morph (optional) | 0..1 | 0 | | `vowel-morph` |
|
|
291
310
|
| **crush** | bits | 1..16 | 8 | `crush` | `crush-bits` |
|
|
292
311
|
| **crush** | coarse | 1..64 | 1 | `coarse` | |
|
|
293
312
|
| **crush** | mix | 0..1 | 1 | | `crush-mix` |
|
|
@@ -352,16 +371,16 @@ rig show the focused track's rig
|
|
|
352
371
|
rig reset remove all three stages
|
|
353
372
|
stomp fuzz | stomp gain 7 head lead | head treble 7 gate -55 | cab 4x12 | cab mic 0.6
|
|
354
373
|
fx head gain 4 the same stages through the generic fx grammar
|
|
355
|
-
|
|
374
|
+
rig jangle the jangle rig on the focused track (alias: track jangle makes a new guitar track)
|
|
356
375
|
```
|
|
357
376
|
|
|
358
377
|
- **stomp** `type` `fuzz` (Big Muff-style, with its tone stack), `face` (Fuzz Face-style), `od` (Tube Screamer-style mid hump and soft clip), `rat` (op-amp hard clip and filter), `octave` (Octavia-style full-wave rectifier, `octave` sets the blend). Each pedal is level-matched to bypass from a fixed -18 dBFS 196 Hz sine (within 1 dB at every type and gain), so kicking on a fuzz does not jump the track; `level` is a trim on top. Presets `muff face screamer rat octavia boost`.
|
|
359
378
|
- **head** `type` `clean` (Fender-style blackface), `chime` (Vox AC-style top boost), `crunch` and `lead` (Marshall-style), `high` (modern high gain), `solid` (clean solid state), `bass` (bass amp). Each has its own Yeh–Smith passive tone stack (`bass mid treble`), a `presence` shelf, power-amp `sag` and a `master`. Each type's makeup gain is calibrated once from a fixed -30 dBFS 196 Hz sine, so switching heads keeps the level within 1 dB. `gate` (dB threshold, absent = off) is a noise gate before the preamp with hysteresis and a short hold. Presets `blackface ac plexi lead modern jc svt`.
|
|
360
|
-
- **cab** `type` `1x12 2x12 4x12 1x10 open 8x10 1x15 di`: biquad speaker models (low resonance, presence peak, cone break-up roll-off); `mic` moves from the cone
|
|
379
|
+
- **cab** `type` `1x12 2x12 4x12 1x10 open 8x10 1x15 di`: biquad speaker models (low resonance, presence peak, cone break-up roll-off); `mic` moves from the cone center (bright) to the edge (dark); `di` is the band-limited direct box for bass.
|
|
361
380
|
|
|
362
|
-
Rig presets (`RIG_PRESETS` in `core/fx.ts`): `clean crunch punk ragged lead metal fuzz octave funk wah bachata spring bassdrive reese jangle alt`. A rig writes its three stages and its companion effects (`funk` an envelope filter, `wah` an auto-wah, `bachata` a chorus, `jangle` a compressor, `spring` the track reverb); switching rigs or `rig reset` removes the previous rig's companions while they still hold the values the rig wrote, and keeps any you edited. Each stage stays editable afterwards, and the rig row then reads `—`.
|
|
381
|
+
Rig presets (`RIG_PRESETS` in `core/fx.ts`): `clean crunch punk ragged lead metal fuzz octave funk wah bachata spring bassdrive reese jangle alt`. A rig writes its three stages and its companion effects (`funk` an envelope filter, `wah` an auto-wah, `bachata` a chorus, `jangle` a compressor, `spring` the track reverb); switching rigs or `rig reset` removes the previous rig's companions while they still hold the values the rig wrote, and keeps any you edited. Each stage stays editable afterwards, and the rig row then reads `—`. `rig <preset>` is the one word for the amp and pedals. The track words `jangle punk funk ragged gtr-lead gtr-metal bachata` remain as aliases that create a guitar track with that rig on every path (`track jangle`, `dawg jangle`, `instrument jangle`, the agent's create_track and set_instrument, SDK `instrument: "jangle"`): the strings `electric` voice (the 12-string `jangle` preset for jangle) plus that rig. `lead` and `bass` keep their synth meaning.
|
|
363
382
|
|
|
364
|
-
`amp` stays Strudel's linear gain: `fx amp` and `amp crunch` answer "did you mean head (guitar amp)?". Lanes: `stomp-gain`, `stomp-tone`, `head-gain` (coefficients follow automation every 32 samples, only while automated). Menu: **Effects › Guitar rig** (rig preset row, then Stomp box, Amp head, Speaker cabinet). Agent: `set_rig`. SDK: `fx: { ...rig("crunch") }` or the stages by name. In play mode a held note through a rig sounds its first 0.75 s window at render quality at once and the rest renders on a background worker in key order, so key handling never waits on it (the stages are causal, so the window is the exact prefix of the whole note).
|
|
383
|
+
`amp` stays Strudel's linear gain: `fx amp` and `amp crunch` answer "did you mean head (guitar amp)?". Lanes: `stomp-gain`, `stomp-tone`, `head-gain` (coefficients follow automation every 32 samples, only while automated). Menu: **Ctrl-K › Effects › Guitar rig** (rig preset row, then Stomp box, Amp head, Speaker cabinet). Agent: `set_rig`. SDK: `fx: { ...rig("crunch") }` or the stages by name. In play mode a held note through a rig sounds its first 0.75 s window at render quality at once and the rest renders on a background worker in key order, so key handling never waits on it (the stages are causal, so the window is the exact prefix of the whole note).
|
|
365
384
|
|
|
366
385
|
| Effect | Param | Range | Default | Lane |
|
|
367
386
|
| --------- | --------------- | --------------------------------------------------- | ------- | ------------ |
|
|
@@ -397,7 +416,7 @@ fx swell time 0.5 volume swell: each strum fades in, no pick attack
|
|
|
397
416
|
fx double a second, seeded take a few ms late, spread left and right
|
|
398
417
|
rig shoegaze fuzz + chime head + wobble + bloom + double + a big reverb
|
|
399
418
|
fx reverb ir builtin:reverse reverse-gate swell after the attack, not a pre-verb (also builtin:gate and builtin:spring)
|
|
400
|
-
track shoegaze a new electric guitar track with the shoegaze rig
|
|
419
|
+
track shoegaze alias: a new electric guitar track named shoegaze, with the shoegaze rig
|
|
401
420
|
```
|
|
402
421
|
|
|
403
422
|
- **wobble** (`depth` 0..100 cents, `rate` Hz, `drift` 0 periodic .. 1 wandering) is a modulated fractional delay read with a 4-point interpolator, like a held vibrato arm or tape wow. Its peak deviation equals `depth` (a sustained A3 at depth 35 swings ±35 c). `depth` is automatable.
|
|
@@ -405,7 +424,7 @@ track shoegaze a new electric guitar track with the shoegaze rig
|
|
|
405
424
|
- **swell** (`time` s, `mix`) restarts a raised-cosine fade at each note onset: a volume-pedal swell without the pick.
|
|
406
425
|
- **double** (`time` 5..60 ms, `drift` ms, `width`) is automatic double tracking: a second take through a modulated delay whose timing wanders by `drift`, panned against the dry take. Left/right correlation stays between 0.3 and 0.8.
|
|
407
426
|
|
|
408
|
-
Rig presets gain `shoegaze glide dreampop swell ebow` (the `swell` rig is the swell effect plus a clean amp and a room; `ebow` is swell plus a bloom on the note itself). `jangle` now also writes a light `double`; a project that loaded jangle before 0.6.1 keeps its stored stages and renders unchanged until `rig jangle` is applied again. In `song.ts`, `rig("jangle")` and `instrument: "jangle"` keep the 0.6.0 stages (no double) so existing files render unchanged; add `double: {}` for the 0.6.1 sound. `instrument: "shoegaze"`, `"dreampop"` and `"ebow"` (the rig names that are also instrument words; `glide` and `swell` are reached with `rig glide`/`rig swell` or `fx: rig("glide")`) also set the rig's reverb wash, like `
|
|
427
|
+
Rig presets gain `shoegaze glide dreampop swell ebow` (the `swell` rig is the swell effect plus a clean amp and a room; `ebow` is swell plus a bloom on the note itself). `jangle` now also writes a light `double`; a project that loaded jangle before 0.6.1 keeps its stored stages and renders unchanged until `rig jangle` is applied again. In `song.ts`, `rig("jangle")` and `instrument: "jangle"` keep the 0.6.0 stages (no double) so existing files render unchanged; add `double: {}` for the 0.6.1 sound. `instrument: "shoegaze"`, `"dreampop"` and `"ebow"` (the rig names that are also instrument words; `glide` and `swell` are reached with `rig glide`/`rig swell` or `fx: rig("glide")`) also set the rig's reverb wash, like `rig shoegaze`; `rig()` returns effects only, so pair it with `reverb: rigReverb("shoegaze")`. The rigs carry a `postgain` trim so the shoegaze rigs land within 2.5 dB of `clean` on a held chord, and `shoegaze`, `glide` and `dreampop` also on a dense strum. `swell` and `ebow` restart their fade on every onset, so a fast strum through them sits several dB lower; they are made for held notes and slow chords. The built-in impulses `reverse` (energy rising to a hard stop: a reverse-gate swell that rises after each attack, not a pre-verb that swells into the note; no rig preset uses it, so reach it with `fx reverb ir builtin:reverse`), `gate` (a dense, flat 250 ms burst cut short) and `spring` (a dispersive, chirping tank) are generated from seeded noise like `room hall plate`; no recorded IR ships. Menu: **Ctrl-K › Effects › Shoegaze**, and the rig preset row in **Ctrl-K › Effects › Guitar rig**. Agent: `set_fx` and `set_rig`. SDK: `fx: { ...rig("shoegaze") }` or `fx: { wobble: { depth: 30 } }`. A full shoegaze rig costs under 25 ms per track-second.
|
|
409
428
|
|
|
410
429
|
| Effect | Param | Range | Default | Lane |
|
|
411
430
|
| ---------- | ------ | ------------ | ------- | -------------- |
|
|
@@ -437,7 +456,7 @@ The song master is an optional chain after every track, orbit bus and duck are s
|
|
|
437
456
|
|
|
438
457
|
Targets: `streaming` -14 LUFS / -1 dBTP, `apple` and `podcast` -16, `broadcast` -23 (EBU R 128), `classical` -20, `ambient` -18, `club` -8 / -2 dBTP (with the `loud` limiter) and `loud` -6 / -2 dBTP (with the `brick` limiter, for hyperpop, gabber and hardcore; masters louder than -14 LUFS stay under -2 dBTP because lossy encoding of loud, dense material adds inter-sample overs). With the limiter on, a target sets the limiter's input gain: a bracketing search on the measured integrated loudness that starts from the linear estimate, gives the same answer for the same mix every time, and stops early with `reached: false` once more drive no longer raises the loudness. At -6 LUFS the limiter alone may stop short; `tape preset crush` or a lower glue threshold before it helps; without it, the gain is capped so the true peak stays at or below -1 dBTP, so a quiet target is always met and a loud one may fall short (the measurement says so).
|
|
439
458
|
|
|
440
|
-
Measurement follows ITU-R BS.1770-4 and EBU R 128: K-weighting derived for any sample rate, 400 ms momentary and 3 s short-term windows (maxima on a 10 ms grid), the -70 LUFS absolute and -10 LU relative gates for integrated loudness, the EBU Tech 3342 loudness range (-20 LU gate, 10th to 95th percentile of short-term values), and true peak from the BS.1770-4 Annex 2 4x oversampling filter. Tests check EBU Tech 3341 cases 1–5, 9–14 (11 and 14
|
|
459
|
+
Measurement follows ITU-R BS.1770-4 and EBU R 128: K-weighting derived for any sample rate, 400 ms momentary and 3 s short-term windows (maxima on a 10 ms grid), the -70 LUFS absolute and -10 LU relative gates for integrated loudness, the EBU Tech 3342 loudness range (-20 LU gate, 10th to 95th percentile of short-term values), and true peak from the BS.1770-4 Annex 2 4x oversampling filter. Tests check EBU Tech 3341 cases 1–5, 9–14 (11 and 14 modeled) and 15–19 and Tech 3342 cases 1–4 within their tolerances. The parameters are in `core/master.ts` (`MASTER_SPECS`, `MASTER_PRESETS`, `LOUDNESS_TARGETS`).
|
|
441
460
|
|
|
442
461
|
Parameters (**bold** unit = shown in the simple menu; the rest are under `advanced`). Glue's `auto` make-up adds half the reduction a full-scale peak gets, so glue on and off compare near level-matched. Clean EQ corners above 0.45 × the render rate are clamped there.
|
|
443
462
|
|
|
@@ -448,10 +467,10 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
|
|
|
448
467
|
| **eq** | low | -12..12 dB | 0 | low shelf gain |
|
|
449
468
|
| eq | lowfreq | 20..1000 Hz | 100 | low shelf corner |
|
|
450
469
|
| **eq** | bell1 | -12..12 dB | 0 | first bell gain |
|
|
451
|
-
| eq | bell1freq | 40..16000 Hz | 400 | first bell
|
|
470
|
+
| eq | bell1freq | 40..16000 Hz | 400 | first bell center |
|
|
452
471
|
| eq | bell1q | 0.1..10 | 1 | first bell width: higher is narrower |
|
|
453
472
|
| **eq** | bell2 | -12..12 dB | 0 | second bell gain |
|
|
454
|
-
| eq | bell2freq | 200..18000 Hz | 3000 | second bell
|
|
473
|
+
| eq | bell2freq | 200..18000 Hz | 3000 | second bell center |
|
|
455
474
|
| eq | bell2q | 0.1..10 | 1 | second bell width: higher is narrower |
|
|
456
475
|
| **eq** | high | -12..12 dB | 0 | high shelf gain |
|
|
457
476
|
| eq | highfreq | 1000..20000 Hz | 10000 | high shelf corner |
|
|
@@ -469,7 +488,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
|
|
|
469
488
|
| tape | tone | 2000..20000 Hz | 20000 | high-frequency roll-off after the curve; 20000 is off |
|
|
470
489
|
| **tape** | mix | 0..1 | 1 | dry/wet |
|
|
471
490
|
| **width** | width | 0..2 | 1 | side level: 0 is mono, 1 unchanged, 2 twice as wide |
|
|
472
|
-
| **width** | mono | 0..300 Hz | 120 | below this the mix is mono (keeps bass
|
|
491
|
+
| **width** | mono | 0..300 Hz | 120 | below this the mix is mono (keeps bass centered); 0 is off |
|
|
473
492
|
| **limiter** | ceiling | -12..0 dBTP | -1 | highest true peak out |
|
|
474
493
|
| **limiter** | gain | 0..24 dB | 0 | drive into the limiter (a target sets it itself) |
|
|
475
494
|
| **limiter** | release | 1..1000 ms | 100 | recovery time; short is louder, long is cleaner |
|
|
@@ -478,7 +497,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
|
|
|
478
497
|
|
|
479
498
|
<!-- master-params:end -->
|
|
480
499
|
|
|
481
|
-
A song with a master renders at 48 kHz; a song without one keeps the engine's 22,050 Hz, as dawg 0.4 wrote it, so older projects export byte-identically. `--rate 48000` (or 44100) picks the rate for any render. `dawg render out.wav --normalize streaming` (or a LUFS number) applies a target for that export only, without changing the song; `--measure` prints the loudness line after any render. When samples hit 16-bit full scale, `dawg render` prints a `warning · N samples clip` line on stderr with the fix (lower volumes, or a master with a limiter). While the loop plays, the header shows `-14.1 LUFS (-14) · TP -1.0` for the last rendered loop, with or without a master, so you can read a mix before mastering it. The bracket is the target, and the reading turns to a warning
|
|
500
|
+
A song with a master renders at 48 kHz; a song without one keeps the engine's 22,050 Hz, as dawg 0.4 wrote it, so older projects export byte-identically. `--rate 48000` (or 44100) picks the rate for any render. `dawg render out.wav --normalize streaming` (or a LUFS number) applies a target for that export only, without changing the song; `--measure` prints the loudness line after any render. When samples hit 16-bit full scale, `dawg render` prints a `warning · N samples clip` line on stderr with the fix (lower volumes, or a master with a limiter). While the loop plays, the header shows `-14.1 LUFS (-14) · TP -1.0` for the last rendered loop, with or without a master, so you can read a mix before mastering it. The bracket is the target, and the reading turns to a warning color when it is more than 0.5 LU off the target or the true peak passes the limiter's ceiling (-1 dBTP without one). With a master the loop monitors at the engine's 22,050 Hz while exports and `master measure` run at 48 kHz; the header shows the 48 kHz reading once it is measured in the background (a `~` marks the monitor estimate until then). EQ above about 9.9 kHz is not audible while monitoring but is in the export. The menu has it under **Ctrl-K › Mix › master** (`/menu master`), where `Space` stages master changes for A/B like other sound edits. The agent has `set_master` and `measure_mix` (integrated, short-term and momentary LUFS, loudness range, true peak, spectral balance in five bands (sub, bass, low-mid, high-mid, high) and stereo correlation, with `bypass_master` to compare); it masters only when asked and measures before and after. In the SDK: `song({ master: { glue: { ratio: 2 }, limiter: { ceiling: -1 }, target: -14 } })` (SDK 1.17.0).
|
|
482
501
|
|
|
483
502
|
## Synth
|
|
484
503
|
|
|
@@ -613,11 +632,11 @@ FM operators 2–8 repeat the `fm` rows with a suffix (`fm2`, `fmh2`, `fmattack2
|
|
|
613
632
|
| sample controls `begin`, `end`, `speed`, `unit`, `loop`, `loopBegin`/`loopb`, `loopEnd`/`loope`, `clip`/`legato`, `fit`, `loopAt`, `accelerate`, `squiz`, `cut`, `gain`, `vel`, `rr` | sampler voice fields; `/sample set`, `set_sample` | done (see Samples) |
|
|
614
633
|
| fitting to tempo (Ableton Repitch/Beats/Tones; Strudel `fit`) | `bpm` `fitmode` `len`; `/fitmode`, `fit_sample` | done (see Fitting samples) |
|
|
615
634
|
|
|
616
|
-
## Keys (
|
|
635
|
+
## Keys (modeled piano)
|
|
617
636
|
|
|
618
|
-
A track whose `instrument` is a piano family (`grand`, `upright`, `felt`, `honkytonk`, `prepared`) and which has a `keys` field plays dawg's
|
|
637
|
+
A track whose `instrument` is a piano family (`grand`, `upright`, `felt`, `honkytonk`, `prepared`) and which has a `keys` field plays dawg's modeled piano (`src/audio/keys/`): a felt hammer of the chosen hardness strikes a bank of stretched, inharmonic string modes (two or three detuned unison strings per key, with a fast first stage and a slow aftersound), a soundboard knock, dampers that stop a released key in about a second, and a small body EQ per family. The 0.5 sustain pedal (down, half, up) holds the dampers off. It is built in: nothing downloads and every render is byte-identical.
|
|
619
638
|
|
|
620
|
-
`piano` keeps two meanings on purpose. A project already stored as `instrument: "piano"` keeps the legacy tone forever. Every new write of the word (`piano`, `instrument piano`, `set_instrument piano`, the menu) stores `instrument: "grand"` with `keys: { preset: "grand" }`. `organ` stays the synth preset (the
|
|
639
|
+
`piano` keeps two meanings on purpose. A project already stored as `instrument: "piano"` keeps the legacy tone forever. Every new write of the word (`piano`, `instrument piano`, `set_instrument piano`, the menu) stores `instrument: "grand"` with `keys: { preset: "grand" }`. `organ` stays the synth preset (the modeled organs are `tonewheel`, `combo` and `pipe`, below). The sampled Salamander grand is still in the browser under instruments.
|
|
621
640
|
|
|
622
641
|
Tuning: each key's first partial sits on the track's tuning (12-TET or any table, 19-EDO included; an unmapped degree is silent). By default the octaves are stretched from the strings' own inharmonicity, as a piano tuner would: low octaves are tuned between the 2:1 and 4:2 beats and the treble is beatless 2:1 to the stretched note below, so the octave from A3 to A4 beats under 1 Hz. `keys stretch 0` keeps every key exactly on the tuning. Bends and glides keep each string mode under the Nyquist limit (modes that would alias are muted), and each note fades over its last 250 ms so it ends inside the 8 s loop-tail window.
|
|
623
642
|
|
|
@@ -626,7 +645,7 @@ Polyphony is 64 voices; a new key steals the oldest released voice, then the old
|
|
|
626
645
|
Prompt grammar (one undo step per command):
|
|
627
646
|
|
|
628
647
|
```text
|
|
629
|
-
piano the
|
|
648
|
+
piano the modeled grand (also: grand)
|
|
630
649
|
piano ballad a preset: grand ballad upright felt lofi honkytonk prepared
|
|
631
650
|
upright | felt | honkytonk | prepared the preset word alone
|
|
632
651
|
keys list this track's piano settings
|
|
@@ -638,9 +657,9 @@ keys reset the family's own sound (keys: {})
|
|
|
638
657
|
automate keys-hardness points 0:0.2 8:0.8 automatable parameters have lanes
|
|
639
658
|
```
|
|
640
659
|
|
|
641
|
-
Presets: `grand` (concert grand, bright and long, three-string unisons), `ballad` (darker grand, softer hammer, more aftersound, plus a room reverb), `upright` (boxy, more inharmonic, shorter), `felt` (felt strip down, muted and intimate, audible mechanics, a small room), `lofi` (felt piano with tape wow, a 3.5 kHz low-pass filter and a 10-bit crush), `honkytonk` (16-cent unisons, bright saloon upright), `prepared` (bolts, rubber and screws on 60% of keys, seeded per key, so the same key always carries the same preparation). A preset is stored as `keys.preset`; its values are read at render, so overrides stay small. The effects a preset brings (`ballad`, `felt`, `lofi`) are ordinary track fields (`reverb`, `filter`, `fx.crush`) and stay editable; switching presets or `keys reset` removes them while they still hold the preset's values, and the same preset word in `song.ts` (`instrument: "lofi"`) brings the same effects. A bare `lofi` stays the
|
|
660
|
+
Presets: `grand` (concert grand, bright and long, three-string unisons), `ballad` (darker grand, softer hammer, more aftersound, plus a room reverb), `upright` (boxy, more inharmonic, shorter), `felt` (felt strip down, muted and intimate, audible mechanics, a small room), `lofi` (felt piano with tape wow, a 3.5 kHz low-pass filter and a 10-bit crush), `honkytonk` (16-cent unisons, bright saloon upright), `prepared` (bolts, rubber and screws on 60% of keys, seeded per key, so the same key always carries the same preparation). A preset is stored as `keys.preset`; its values are read at render, so overrides stay small. The effects a preset brings (`ballad`, `felt`, `lofi`) are ordinary track fields (`reverb`, `filter`, `fx.crush`) and stay editable; switching presets or `keys reset` removes them while they still hold the preset's values, and the same preset word in `song.ts` (`instrument: "lofi"`) brings the same effects. A bare `lofi` stays the kit and crush preset word; type `piano lofi`.
|
|
642
661
|
|
|
643
|
-
The menu has the pianos under **Sound
|
|
662
|
+
The menu has the pianos under **Ctrl-K › Sound › instruments › Keys**, and for a piano track a **Sound › keys** page and the simple rows (preset, hardness, decay, release, felt) on the **Sound** page. The agent's `set_keys` tool takes the same presets and names; `set_instrument` and `create_track` take the piano words. In the SDK: `track({ instrument: "grand", keys: { hardness: 0.3 } })`.
|
|
644
663
|
|
|
645
664
|
| Param | Range | Default | Lane | What it does |
|
|
646
665
|
| ------------ | ------------------------------------- | ------------------ | --------------- | -------------------------------------------------------------------- |
|
|
@@ -669,10 +688,10 @@ Lanes are read at each note's onset. The model is dawg's own, from public litera
|
|
|
669
688
|
|
|
670
689
|
### Soft pedal and sostenuto (0.6.1)
|
|
671
690
|
|
|
672
|
-
The
|
|
691
|
+
The modeled pianos have the other two pedals of a grand. Both are pedal lanes like the sustain pedal (`[{ tick, state }]`, at most 1024 events) and absent means today's sound:
|
|
673
692
|
|
|
674
693
|
- **Soft pedal** (`softPedal`, una corda, the left pedal): while it is down each note's hammer is shifted so it strikes fewer strings with a softer part of the felt: the unison narrows, the hammer's high partials are rolled off and the level drops about 3 dB, so the note is quieter and darker (a 10%+ lower spectral centroid and at least 3 dB less 2-4 kHz energy on middle C). `half` is half the shift. It is read at each note's onset, so a note struck before the pedal keeps its tone.
|
|
675
|
-
- **Sostenuto** (`sostenuto`, the middle pedal, `down` and `up` only): keys already held when it goes down keep their dampers up until it lifts; notes struck afterwards damp at their own release. A held key struck again while the pedal is down keeps ringing to the lift (the rod keeps its damper up), and a key still held through a lift is caught again by the next press. It works alongside the sustain pedal. Only the
|
|
694
|
+
- **Sostenuto** (`sostenuto`, the middle pedal, `down` and `up` only): keys already held when it goes down keep their dampers up until it lifts; notes struck afterwards damp at their own release. A held key struck again while the pedal is down keeps ringing to the lift (the rod keeps its damper up), and a key still held through a lift is caught again by the next press. It works alongside the sustain pedal. Only the modeled pianos with a `keys` object hear the soft pedal; `pedal soft` says so on any other track.
|
|
676
695
|
|
|
677
696
|
```text
|
|
678
697
|
pedal soft 0-8 una corda from beat 0 to 8 (also: down|half|up <beat>, bars, off)
|
|
@@ -680,7 +699,7 @@ pedal sost 0-4 sostenuto down at 0, up at 4 (holds the
|
|
|
680
699
|
pedal soft list the lane; pedal sost off clears it
|
|
681
700
|
```
|
|
682
701
|
|
|
683
|
-
Menu: **Sound › performance** has **soft pedal** and **sostenuto** rows (off, or held over every bar) on a
|
|
702
|
+
Menu: **Sound › performance** has **soft pedal** and **sostenuto** rows (off, or held over every bar) on a modeled piano track. Agent: `set_piano_pedals` (`soft`, `sostenuto`: events, `"bars"` or null). SDK (1.26.0): `track({ instrument: "grand", softPedal: [[0, "down"], [8, "up"]], sostenuto: [[1, "down"], [4, "up"]] })`.
|
|
684
703
|
|
|
685
704
|
### Electric keys (0.6.1)
|
|
686
705
|
|
|
@@ -711,7 +730,7 @@ automate keys-vibe points 0:0 8:0.8 automatable parameters have lanes
|
|
|
711
730
|
| **pickup** | clav | neck bridge both out | both | | pickup switch |
|
|
712
731
|
| **mute** | clav | 0..1 | 0 | | mute slider: damps the upper partials |
|
|
713
732
|
|
|
714
|
-
`hardness`, `touch`, `decay`, `release`, `width`, `vib` and `vibmod` apply to the electric families too. The menu has them under **Sound ›
|
|
733
|
+
`hardness`, `touch`, `decay`, `release`, `width`, `vib` and `vibmod` apply to the electric families too. The menu has them under **Sound › instruments › Keys › Electric**, with their rows on the **Sound** page; the agent's `set_instrument` and `set_keys` take the same words; the SDK takes `track({ instrument: "epiano", keys: { vibe: 0.6 } })` or `track({ instrument: "suitcase" })`. The models are dawg's own, from public descriptions of the instruments (tine and tone-bar cantilever, electromagnetic and electrostatic pickups, the Clavinet's pickup switching), with no sampled audio.
|
|
715
734
|
|
|
716
735
|
### Organs (tonewheel, combo, pipe)
|
|
717
736
|
|
|
@@ -741,7 +760,7 @@ automate keys-rotary points 0:1 4:2 spin the rotor up at beat 4
|
|
|
741
760
|
|
|
742
761
|
Presets: `tonewheel` (888000000, scanner C3, slow rotary), `gospel` (888800008, 3rd percussion, fast rotary, driven), `jazzorgan` (888000000 with soft 3rd percussion), `combo` (reed registers 08800 with vibrato), `vox` (bright 08880), `pipe` (plenum in a church reverb), `flutes` (gedackt 8' and flute 4' with tremulant), `cornet`, `reeds`, `celeste` (gamba and celeste beating).
|
|
743
762
|
|
|
744
|
-
The menu has them under **Sound
|
|
763
|
+
The menu has them under **Ctrl-K › Sound › instruments › Keys › Organs**; for an organ track the **Sound** page shows the preset, a **Drawbars** (tonewheel), **Registers** (combo) or **Stops** (pipe) sub-menu with one row per footage or stop, and the family's rows. The agent's `set_keys` takes `drawbars`, `registers`, `stops` and `rotary` next to `params`. In the SDK: `track({ instrument: "tonewheel", keys: { drawbars: "888800008", rotary: "fast" } })` or `keys: { stops: ["principal8", "octave4"] }`.
|
|
745
764
|
|
|
746
765
|
| Param | Family | Range | Default | Lane | What it does |
|
|
747
766
|
| ------------- | --------------- | --------------------------- | ------------------------------------- | ------------- | ---------------------------------------------- |
|
|
@@ -787,7 +806,7 @@ export default track({
|
|
|
787
806
|
|
|
788
807
|
Semantics follow Strudel's sampler:
|
|
789
808
|
|
|
790
|
-
| Strudel | dawg |
|
|
809
|
+
| Strudel | dawg | Behavior |
|
|
791
810
|
| ------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
792
811
|
| `samples({ kick: "kick.wav" })` | `sampler({ kick: "samples/kick.wav" })` | one voice per name |
|
|
793
812
|
| `s("kick hat")` | `hits("kick", …)`, `hit("hat", …)` (oneshot mode) | voices take pitch slots 36, 37, … in name order; a hit plays the whole sample, whatever the note length |
|
|
@@ -850,7 +869,7 @@ How they combine with Strudel's controls and the 0.5 tempo map:
|
|
|
850
869
|
|
|
851
870
|
`fade out 0.5` and `fade in 0.05` (Strudel `fadeTime` and `fadeInTime`, stored as `fadeTime` and `fadeInTime`, 0..2 s) shape a voice's start and end in place of the short default declick, and follow the fitted length when the voice is fitted. `fade off` clears both.
|
|
852
871
|
|
|
853
|
-
Both act on the focused sampler voice (`shift 7 vox` names one). The menu's **
|
|
872
|
+
Both act on the focused sampler voice (`shift 7 vox` names one). The menu's sample rows (**Ctrl-K › Sound**) list Shift, Formant, Fade in and Fade out, the agent's `set_sample` tool takes `shift`, `formant`, `fadeTime` and `fadeInTime`, and the SDK writes `sample("samples/vox.wav", { shift: 7, formant: 0, fadeTime: 0.5 })`.
|
|
854
873
|
|
|
855
874
|
### Resample (0.6.1)
|
|
856
875
|
|
|
@@ -861,7 +880,7 @@ Both act on the focused sampler voice (`shift 7 vox` names one). The menu's **So
|
|
|
861
880
|
- The voice records where it came from in `from: { source: "track:lead" | "orbit:2" | "master", section?, bars?, score }`, `score` being the sha256 of the score it was rendered from. It is informational; the renderer never reads it.
|
|
862
881
|
- Up to 600 s. `section` uses the song's sections; `bars 1-2` is 1-based and inclusive.
|
|
863
882
|
|
|
864
|
-
**Project ›
|
|
883
|
+
**Ctrl-K › Project › resample** lists each track, orbit and the mix with a `→ sampler` and `→ granular` row. The agent's `resample {source: track|orbit|master, trackId?, orbit?, section?, bars?, post?, grain?, as?}` tool does the same.
|
|
865
884
|
|
|
866
885
|
Fitted windows are computed once and kept in a 64 MB least-recently-used cache of the played window only. Only the frames a note can reach are fitted (a held keyed or clipped note fits its length plus the release; a one-shot or looped voice fits the whole window). Renders and exports always compute them; in play mode a fit up to 8 s (of source or output, whichever is longer) is computed on the spot (under 160 ms), and a longer one stays silent with "fitting" in the status line until it is ready ("fit ready" then), never at the wrong pitch. Audition previews fit synchronously.
|
|
867
886
|
|
|
@@ -869,7 +888,7 @@ Every voice starts and stops with a 1–3 ms fade, so cuts do not click. Without
|
|
|
869
888
|
|
|
870
889
|
Decoding: WAV (PCM 16/24/32-bit integer and 32-bit float, any channel count and rate) and AIFF/AIFF-C (8/16/24/32-bit) decode natively, mixed to mono and resampled to the engine rate on the fly with linear interpolation. MP3, FLAC, Ogg, M4A and anything else decode through `ffmpeg` when it is on `PATH` (dawg never installs it); without it the voice is skipped with `<voice> · <path> · not WAV/AIFF and ffmpeg is not on PATH · convert it to WAV, or install ffmpeg (e.g. brew install ffmpeg) and reload`. Decoded PCM is cached at `.dawg/assets/<sha256>.pcm`, least recently used first out past 512 MiB. Files over 50 MiB or 10 minutes, paths that leave the project (including through a symlink), and more than 64 voices are rejected. A `sha256` that no longer matches the file is a warning and the file still plays. Problems appear as receipts in the TUI and on stderr from `dawg render`; the track renders without the missing voices and nothing crashes.
|
|
871
890
|
|
|
872
|
-
In the TUI, oneshot sampler tracks show one highway lane per voice,
|
|
891
|
+
In the TUI, oneshot sampler tracks show one highway lane per voice, labeled by name; keyed tracks use the pitch axis. `/tracks` shows each sampler's sample count and how many failed to load. `/sample <path> [as <voice>]` adds a voice to the focused track: a file outside the track directory is copied into `tracks/<slug>/samples/`, the voice name defaults to the file name, and a focused synth track that already has notes gets a new `samples` track instead. Existing hits keep their voice when the new name shifts the slots. `/sample` alone lists the voices. The agent's `import_sample` media tool writes 48 kHz stereo WAVs to the same folder.
|
|
873
892
|
|
|
874
893
|
## Sample packs
|
|
875
894
|
|
|
@@ -896,11 +915,11 @@ instrument: sampler({
|
|
|
896
915
|
| `/pack cache [prune [<size>] \| clear]` | disk used by pack downloads and decoded audio against their caps; `prune` evicts down to the cap (or `<size>`, e.g. `500M`), `clear` evicts everything this project does not use |
|
|
897
916
|
| `/kit [<kit>]` | with no name, one picker of every kit (synth kits first, then sample kits); a synth kit name (`syn808`, `syn909`, `acoustic`, `lofi`, `electro`, `trap`, `default`) sets the offline drum synth; a bank (`909`, `808`, `707`, `606`, `linn`, `lm1`, `dmx`, `cr78`, `uzu`, `dirt`, or any bank name like `RolandTR909`) turns the focused drum track into a sampler on it; `/kit list` lists both |
|
|
898
917
|
|
|
899
|
-
**Bank nicknames.** Strudel's REPL registers short names for the drum machines with `aliasBank("https://strudel.b-cdn.net/tidal-drum-machines-alias.json")`, a JSON map of bank → nickname (`{"RolandTR909": "TR909", "AkaiLinn": "Linn", "EmuSP12": "SP12", …}`, 66 entries). dawg ships a snapshot of that file (taken 2026-10-07) so nicknames work offline, and refreshes it whenever it fetches the `tidal-drum-machines` manifest. A nickname works wherever a bank does: `/kit TR909`, `/kit tr808`, `/pack use tidal-drum-machines/TR909`, `pack:tidal-drum-machines/TR909_bd` in `track.ts`, the agent's `use_sound`, and the menu's **
|
|
918
|
+
**Bank nicknames.** Strudel's REPL registers short names for the drum machines with `aliasBank("https://strudel.b-cdn.net/tidal-drum-machines-alias.json")`, a JSON map of bank → nickname (`{"RolandTR909": "TR909", "AkaiLinn": "Linn", "EmuSP12": "SP12", …}`, 66 entries). dawg ships a snapshot of that file (taken 2026-10-07) so nicknames work offline, and refreshes it whenever it fetches the `tidal-drum-machines` manifest. A nickname works wherever a bank does: `/kit TR909`, `/kit tr808`, `/pack use tidal-drum-machines/TR909`, `pack:tidal-drum-machines/TR909_bd` in `track.ts`, the agent's `use_sound`, and the menu's **Rhythm › kits › Strudel banks** list. Resolution order: a nickname in its exact case (`Linn` → `AkaiLinn`, as in Strudel), then dawg's short names (`909`, `linn` → `LinnDrum`, unchanged from before), then a bank name in any case, then a nickname in any case (`sp12` → `EmuSP12`), then a bank suffix. Pins always store the full bank name (`pack:tidal-drum-machines/RolandTR909_bd`), so documents never depend on alias data.
|
|
900
919
|
|
|
901
920
|
**Cache sizes.** Pack downloads (`~/.cache/dawg/packs/files/`, shared by every project) are capped at 2 GiB and decoded audio (`<project>/.dawg/assets/`) at 1 GiB per project; `DAWG_PACKS_CACHE_MAX` and `DAWG_ASSETS_CACHE_MAX` override them (`500M`, `4G`, or bytes). Both evict least recently used files first, and neither evicts a file the open project's score uses, even when that project alone is over the cap. An evicted pack file is fetched again from its pinned URL and checked against its pinned sha256 the next time it plays, so eviction only ever costs a download. Manifests are small and never evicted. Decoded samples also share a 512 MiB in-memory LRU per process.
|
|
902
921
|
|
|
903
|
-
|
|
922
|
+
Ctrl-K › Sound › instruments lists kits, instruments (the Salamander piano and `gm_*` soundfonts, Strudel naming), "use a sample" and sample packs. The agent has `list_packs`, `search_sounds {query}` and `use_sound {sound, track?, voice?}`. A pack sound plays from keyboard play mode like any sampler voice. `kitFromBank(bank)` in `src/audio/packs.ts` returns the voice map for a bank (kick, snare, hat, …) for other kit lists.
|
|
904
923
|
|
|
905
924
|
Every pack is fully supported, whatever its license; dawg records each sample's pack and license (or `none stated`) in the pinned voice. A render that uses pack sounds names the packs in the WAV's INFO comment and prints a `credits ·` line, and a project that uses a CC-BY or CC-BY-SA pack gets a `CREDITS.md` with the required attribution (dawg leaves a hand-written `CREDITS.md` alone).
|
|
906
925
|
|
|
@@ -961,7 +980,7 @@ The agent's `make_wavetable` tool (`src/audio/wavetable-maker.ts`, `src/media/wa
|
|
|
961
980
|
- **Clean-up.** DC removed, fundamental rotated to start as a rising sine (so neighbouring frames line up), optional `smooth` across neighbours, and per-frame (default), whole-table or no normalisation to 0.98 peak.
|
|
962
981
|
- **Report.** The result gives the path, sha256, frame count, method, region, detected pitch and a short sweep description (spectral centroid per frame span, how smooth the morph is), and the `set_wavetable` call that plays it.
|
|
963
982
|
|
|
964
|
-
All arithmetic is float64 in a fixed order with no randomness, so the same input and options give the same bytes. Project tables live under `tracks/<slug>/wavetables/`; `/wt list` and the menu's table picker list them, `/wt vox.wav` (or a full `tracks/…` path) picks one for the focused track, and `track.ts` refers to it as `wavetable("./wavetables/vox.wav")`. The score keeps the project path and its sha256; evaluation re-hashes it like sampler files. A file that changed since it was picked plays with a warning; a missing one is a load problem naming `make_wavetable`.
|
|
983
|
+
All arithmetic is float64 in a fixed order with no randomness, so the same input and options give the same bytes. Project tables live under `tracks/<slug>/wavetables/`; `/wt list` and the menu's table picker list them, `/wt vox.wav` (or a full `tracks/…` path) picks one for the focused track, and `track.ts` refers to it as `wavetable("./wavetables/vox.wav")`. A track that switches to another instrument keeps its table, written `wavetable: wavetable(...)` beside `instrument`, so switching back restores it. The score keeps the project path and its sha256; evaluation re-hashes it like sampler files. A file that changed since it was picked plays with a warning; a missing one is a load problem naming `make_wavetable`.
|
|
965
984
|
|
|
966
985
|
## Plucked strings
|
|
967
986
|
|
|
@@ -1014,7 +1033,7 @@ instrument: stringed("sitar", { buzz: 0.8, sym: 0.5 }),
|
|
|
1014
1033
|
| `string <param> off` · `string reset` | back to the preset's value · drop every override |
|
|
1015
1034
|
| `string off` | back to the legacy `pluck` voice |
|
|
1016
1035
|
|
|
1017
|
-
The menu has the presets under Sound ›
|
|
1036
|
+
The menu has the presets under Sound › instruments › Strings and every string parameter on the Sound page of a string track (left/right adjust, `x` resets, space auditions with staged A/B). The agent's `set_string {trackId?, preset?, params?, reset?, off?}` runs the same command. SDK 1.21.0: `stringed(preset, params)` as a track's `instrument`, or `track({ instrument: "string", string: { preset: "koto", ring: 4 } })`; the printer writes `stringed(...)` back.
|
|
1018
1037
|
|
|
1019
1038
|
### Bowed strings
|
|
1020
1039
|
|
|
@@ -1050,7 +1069,7 @@ Measured: tuning within 1 cent to C7 at 22.05 and 48 kHz; 0 of 1296 pressure/spe
|
|
|
1050
1069
|
| `bowed` · `bowed <preset>` · `bowed presets` | the cello · a bowed preset · the bowed presets |
|
|
1051
1070
|
| `bowed <param> <value> …` | the same as `string <param> <value> …` (`bowed sord 1`) |
|
|
1052
1071
|
|
|
1053
|
-
The menu lists them under **Sound ›
|
|
1072
|
+
The menu lists them under **Sound › instruments › Strings › Bowed**, and the **Sound** page on a bowed track shows `pressure speed vib sord dyn bright ring body` first (the rest under advanced). SDK 1.31.0: `stringed("violin", { pressure: 0.7 })` or `stringed({ preset: "cello", sord: 1 })`.
|
|
1054
1073
|
|
|
1055
1074
|
## Granular
|
|
1056
1075
|
|
|
@@ -1086,7 +1105,7 @@ Quality and cost. Grains read a shared semitone-level band-limited bank (`src/au
|
|
|
1086
1105
|
| `track cloud` · `track hold-2` | a new granular track named after a preset |
|
|
1087
1106
|
| `track pad grain swarm` | focus or create a track and grain it in one step |
|
|
1088
1107
|
|
|
1089
|
-
|
|
1108
|
+
**Ctrl-K › Sound › granular**: it edits the preset, source and every parameter of a granular track (on any other pitched track the same row reads `grain this track's synth` and offers `grain on` and the presets); **Ctrl-K › Sound › instruments › Granular** lists the presets. `grain` edits stage in the audition loop, so space plays them and `a` compares A/B. The agent's `set_granular {trackId, preset?, src?, voice?, params?, reset?, off?}` tool takes the same names, and the SDK writes:
|
|
1090
1109
|
|
|
1091
1110
|
```ts
|
|
1092
1111
|
instrument: granular("cloud", { scan: 0.1, seed: 7 }),
|
|
@@ -1101,17 +1120,17 @@ Four optional parameters make a granular track playable like an instrument rathe
|
|
|
1101
1120
|
| Parameter | Values (default) | What it does |
|
|
1102
1121
|
| --------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1103
1122
|
| `sync` | `off` `1/64` `1/32` `1/16t` `1/16` `1/16d` `1/8t` `1/8` `1/8d` `1/4t` `1/4` `1/4d` `1/2` `1/1` | Grains start on a note-value grid that follows the song's tempo map (ramps included), as Ableton Granulator II and Output Portal sync their grain rate. `grain` still sets each grain's length; `overlap` is ignored. `jitter` moves onsets off the grid by up to a fraction of a step. Measured within 1 ms of 117.19 ms per 1/16 at 128 BPM. |
|
|
1104
|
-
| `quant` | `off` `scale` `chord` | Snaps each grain's pitch offset (`pitch`, `detune`, `shimmer`) to the nearest pitch of the song key's scale, or of the pitch classes sounding on the song's other pitched tracks at that grain's onset, falling back to the scale when nothing sounds (scale
|
|
1123
|
+
| `quant` | `off` `scale` `chord` | Snaps each grain's pitch offset (`pitch`, `detune`, `shimmer`) to the nearest pitch of the song key's scale, or of the pitch classes sounding on the song's other pitched tracks at that grain's onset, falling back to the scale when nothing sounds (scale quantize, as on Output Portal and Bitwig's Sampler). The played note itself is never moved. |
|
|
1105
1124
|
| `mono` | `on` `off` (off) | One voice: a note that starts before the previous one ends (legato) retargets that voice's pitch and keeps its grain stream and head, so a melody glides through one cloud; a detached note starts a new voice and cuts the old one's tail. |
|
|
1106
1125
|
| `pedal` | `on` `off` (off) | The track's sustain pedal freezes the head while it is down (the cloud keeps playing the same spot), as on Mutable Clouds' freeze and the Hologram Microcosm hold. |
|
|
1107
1126
|
|
|
1108
1127
|
`grain sync 1/16`, `grain quant scale`, `grain mono on` and `grain pedal on` set them (`off` unsets); **Sound › granular** lists them as rows, `set_granular` takes them in `params`, and the SDK writes `granular("cloud", { sync: "1/16", quant: "scale", mono: true })`.
|
|
1109
1128
|
|
|
1110
|
-
Lanes: every numeric parameter that moves well over time has a `grain-<param>` automation lane: `grain-pos`, `grain-scan`, `grain-grain`, `grain-overlap`, `grain-jitter`, `grain-spray`, `grain-pitch`, `grain-detune`, `grain-shimmer`, `grain-spread`, `grain-reverse`, `grain-repeat` and `grain-drift`. A lane replaces the parameter's value while it has points (`automate grain-pos points 0:0.1 8:0.9` sweeps the head across the source), is read every 32 frames, and latches per grain at its onset, so a block render equals a whole render. They are in **Mix
|
|
1129
|
+
Lanes: every numeric parameter that moves well over time has a `grain-<param>` automation lane: `grain-pos`, `grain-scan`, `grain-grain`, `grain-overlap`, `grain-jitter`, `grain-spray`, `grain-pitch`, `grain-detune`, `grain-shimmer`, `grain-spread`, `grain-reverse`, `grain-repeat` and `grain-drift`. A lane replaces the parameter's value while it has points (`automate grain-pos points 0:0.1 8:0.9` sweeps the head across the source), is read every 32 frames, and latches per grain at its onset, so a block render equals a whole render. They are in **Mix**'s lane picker on granular tracks and stored in `track.fxAutomation` like the effect lanes.
|
|
1111
1130
|
|
|
1112
1131
|
## Mallets and bells (modal)
|
|
1113
1132
|
|
|
1114
|
-
`instrument: "modal"` plays struck bars, tines, bells, bowls and drums on a modal resonator bank (`src/audio/dsp/modal.ts`, `src/audio/resonators.ts`): each note excites a table of measured mode ratios through a mallet pulse, each mode rings as a two-pole resonator with its own decay, and the strike point weights the modes the way it does on a real bar (the node at the
|
|
1133
|
+
`instrument: "modal"` plays struck bars, tines, bells, bowls and drums on a modal resonator bank (`src/audio/dsp/modal.ts`, `src/audio/resonators.ts`): each note excites a table of measured mode ratios through a mallet pulse, each mode rings as a two-pole resonator with its own decay, and the strike point weights the modes the way it does on a real bar (the node at the center of a marimba bar mutes the second mode). It is dawg's own engine, ported from the reviewed 0.6 prototype.
|
|
1115
1134
|
|
|
1116
1135
|
Presets (a word picks one): `marimba` `vibes` `xylophone` `glock` `celesta` `chimes` `kalimba` `mbira` `steelpan` `bowl` `gong` `timpani`; aliases `vibraphone`, `glockenspiel`, `tubular`, `thumbpiano`, `gongageng`, `steeldrum`, `singingbowl`, `kettledrum` and `tubularbells`. `instrument vibes` (or any preset word) switches the focused track. Plain `marimba` with no `modal` field keeps the pre-0.6 marimba voice byte-identical, so old projects sound the same; use `modal marimba` for the modal one (the `instrument marimba` receipt says so). One-shot renders let a modal tail ring up to 30 s (bowls and gongs ring out instead of stopping at the 8 s loop-fold cap). `dawg check` warns about tracks still on the legacy `marimba`, `modal` or `wind` words.
|
|
1117
1136
|
|
|
@@ -1119,7 +1138,7 @@ Presets (a word picks one): `marimba` `vibes` `xylophone` `glock` `celesta` `chi
|
|
|
1119
1138
|
| -------------------------- | ------------------ | --------------------------------------------------------------------------------- |
|
|
1120
1139
|
| `mallet` | yarn … brass | `yarn` `cord` `rubber` `plastic` `brass`; sets `hardness` |
|
|
1121
1140
|
| `hardness` | 0..1 | mallet hardness: soft rounds off the high modes, hard adds them; velocity adds |
|
|
1122
|
-
| `position` | 0..1 | strike point: 0 the end or edge, 0.5 the
|
|
1141
|
+
| `position` | 0..1 | strike point: 0 the end or edge, 0.5 the center |
|
|
1123
1142
|
| `ring` | 0.05..30 s (log) | ring time (T60) at middle C |
|
|
1124
1143
|
| `tilt` | 0..2 | how much faster high modes and high notes decay |
|
|
1125
1144
|
| `damp` `release` | 0..1, 0.005..2 s | damping at note-off (0 rings on, 1 chokes) and the choke time; the pedal lifts it |
|
|
@@ -1146,11 +1165,11 @@ instrument: modal("marimba", { mallet: "rubber", ring: 2 }),
|
|
|
1146
1165
|
| `modal reset` | clear overrides, keep the preset |
|
|
1147
1166
|
| `modal off` | leave the engine for the legacy marimba voice |
|
|
1148
1167
|
|
|
1149
|
-
The menu's **Sound ›
|
|
1168
|
+
The menu's **Sound › instruments › Mallets and bells** lists the presets, and the **Sound** page shows the preset, mallet, the simple parameters and an **advanced** group on a modal track. The agent's `set_modal {trackId, preset?, mallet?, params?, reset?}` tool takes the same names.
|
|
1150
1169
|
|
|
1151
1170
|
### Gamelan (0.6.1)
|
|
1152
1171
|
|
|
1153
|
-
The modal engine also plays Javanese and Balinese bronzes, small bells and frame drums: `saron` `demung` `slenthem` `gangsa` `gender` `bonang` `kenong` `kethuk` `kempul`, the bells `crotales` `musicbox` `toypiano`, and the drums `daf` `bodhran` `tabla` (aliases `crotale`, `musicalbox`, `framedrum`). `modal gamelan` lists them; the menu has them under **Sound ›
|
|
1172
|
+
The modal engine also plays Javanese and Balinese bronzes, small bells and frame drums: `saron` `demung` `slenthem` `gangsa` `gender` `bonang` `kenong` `kethuk` `kempul`, the bells `crotales` `musicbox` `toypiano`, and the drums `daf` `bodhran` `tabla` (aliases `crotale`, `musicalbox`, `framedrum`). `modal gamelan` lists them; the menu has them under **Ctrl-K › Sound › instruments › Mallets and bells** (Gamelan). Gamelan is tuned in slendro or pelog, not 12-TET, so pair the presets with `tuning slendro` or `tuning pelog` (the 0.5 tuning tables).
|
|
1154
1173
|
|
|
1155
1174
|
Balinese instruments come in pairs: the pengumbang is tuned low and the pengisep a few hertz high, so a unison beats (_ombak_, "wave") at that difference, typically 5 to 8 Hz. `modal pair <track>` makes the focused track the pengisep of the named partner: it sounds `ombak` Hz above it (gangsa's 7 Hz by default), the partner plays one voice at its own pitch, and the two together beat at exactly `ombak` Hz instead of each track beating on its own. `modal pair off` undoes it. `modal ombak 6` or `modal pair t2` on a track that is not modal yet starts it as a `gangsa`. In the SDK: `modal("gangsa", { ombak: 6, pair: "pengumbang" })`.
|
|
1156
1175
|
|
|
@@ -1160,7 +1179,7 @@ Live, an undamped bar (`damp 0`: gongs, kempul, bowls) keeps ringing after you r
|
|
|
1160
1179
|
|
|
1161
1180
|
`instrument: "wind"` with a `wind` field plays blown instruments on breath-driven digital waveguides (`src/audio/winds/`): a bore delay line tuned exactly with a Thiran allpass, closed by a reed table (clarinet), a conical reed (saxophones, oboe, bassoon), an air jet (flutes) or a lip resonator (brass), with loss and bell filters, breath noise and a breath envelope. Reed and jet run at 2x with first-order antiderivative antialiasing (`src/audio/dsp/shape.ts`, `oversample.ts`); each preset carries per-semitone pitch and level trims for 22.05, 44.1 and 48 kHz, generated by `bun scripts/calibrate-winds.ts`, so every preset plays within 3 cents across its range. It is dawg's own model, after Smith's digital waveguides, Cook's STK reed and jet models and Välimäki's fractional-delay filters, ported from the reviewed 0.6 prototype; no samples.
|
|
1162
1181
|
|
|
1163
|
-
Presets (a word picks one): flutes `flute` `recorder` `whistle` `ney` `shakuhachi` `panpipe` `suling` `bansuri`; reeds `clarinet` `bassclarinet` `oboe` `bassoon`; saxophones `sax` (tenor) `altosax` `barisax`; brass `trumpet` `harmon` `plunger` `trombone` `tuba` `horn`. Aliases: `tinwhistle` `pennywhistle` `nay` `panflute` `panpipes` `saxophone` `tenorsax` `tenor` `alto` `bari` `baritonesax` `frenchhorn` `mutedtrumpet` `wahtrumpet`. `instrument flute` (or any preset word) switches the focused track. The bare legacy word `wind` with no `wind` field keeps its pre-0.6 tone byte-identically; `wind flute` gives the engine. `presets wind` (or `wind presets`) lists them; the menu has them under **Sound ›
|
|
1182
|
+
Presets (a word picks one): flutes `flute` `recorder` `whistle` `ney` `shakuhachi` `panpipe` `suling` `bansuri`; reeds `clarinet` `bassclarinet` `oboe` `bassoon`; saxophones `sax` (tenor) `altosax` `barisax`; brass `trumpet` `harmon` `plunger` `trombone` `tuba` `horn`. Aliases: `tinwhistle` `pennywhistle` `nay` `panflute` `panpipes` `saxophone` `tenorsax` `tenor` `alto` `bari` `baritonesax` `frenchhorn` `mutedtrumpet` `wahtrumpet`. `instrument flute` (or any preset word) switches the focused track. The bare legacy word `wind` with no `wind` field keeps its pre-0.6 tone byte-identically; `wind flute` gives the engine. `presets wind` (or `wind presets`) lists them; the menu has them under **Sound › instruments › Winds and brass**, and the **Sound** page shows the simple rows `breath` `bright` `mute` `players` `growl` with the rest under advanced.
|
|
1164
1183
|
|
|
1165
1184
|
| Parameter | Range | Meaning |
|
|
1166
1185
|
| ------------------ | -------------------------------- | ----------------------------------------------------------------------------- |
|
|
@@ -1229,7 +1248,7 @@ export default track({
|
|
|
1229
1248
|
});
|
|
1230
1249
|
```
|
|
1231
1250
|
|
|
1232
|
-
| Field | Range (default) | T-1 parameter |
|
|
1251
|
+
| Field | Range (default) | T-1 parameter | Behavior |
|
|
1233
1252
|
| -------------------- | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------- |
|
|
1234
1253
|
| `steps` | 1..64 (16) | Steps | Length of one pass; the row repeats every pass to the end of the loop. |
|
|
1235
1254
|
| `pulses` | 0..steps (4) | Pulses | Hits spread over the steps by Bjorklund's algorithm. |
|
|
@@ -1241,7 +1260,7 @@ export default track({
|
|
|
1241
1260
|
| `pace` | -1..1 (0) | Pace | > 0 slows the repeats down progressively, < 0 speeds them up. |
|
|
1242
1261
|
| `ramp` | -1..1 (0) | Ramp | Velocity across the repeats: > 0 builds, < 0 fades. |
|
|
1243
1262
|
| `velocity` | 0..1 (0.8) | Velocity | Base velocity. |
|
|
1244
|
-
| `accent`, `accents` | 0..1 (0), 1..pulses (1) | Accent | Lifts `E(accents, pulses)` of the pulses (or the `X` steps)
|
|
1263
|
+
| `accent`, `accents` | 0..1 (0), 1..pulses (1) | Accent | Lifts `E(accents, pulses)` of the pulses (or the `X` steps) toward full velocity. |
|
|
1245
1264
|
| `gate`, `legato` | 0.05..4 steps (1), boolean | Sustain | Note length in steps; `legato` holds each hit to the next one (Strudel `euclidLegato`). |
|
|
1246
1265
|
| `probability`,`seed` | 0..1 (1), 0..1e6 (0) | Probability | Drops pulses (and their repeats) by a seeded hash: the same seed always drops the same hits. |
|
|
1247
1266
|
| `swing` | -0.5..0.5 step (0) | Timing | Every second step later (> 0) or earlier. |
|
|
@@ -1291,6 +1310,10 @@ dawg's own design:
|
|
|
1291
1310
|
- Perform modes `block`, `strum-up`, `strum-down` (1/32-beat gap), `arp-up`, `arp-down`, `arp-updown`, `arp-random` (seeded) with a grid-aligned `rate` and 1–4 `octaves`, `harp` (a 1/16-beat upward sweep across the octaves that rings to the end of the chord), and `slop` (Orchid's humanised block chord: each voice lands up to 1/16 beat late, chosen by the press's seed, so repeats differ but a recording replays exactly).
|
|
1292
1311
|
- Progressions (dawg's "auto"): eleven presets (`axis` I–V–vi–IV, `sad-pop` vi–IV–I–V, `fifties` I–vi–IV–V, `ii-v-i`, `turnaround` I–vi–ii–V, `canon`, `aeolian` i–VI–III–VII, `andalusian` i–VII–VI–V, `minor-ii-v`, `dorian-vamp`, `mixolydian-rock` I–♭VII–IV–I) and four styles, `pop`, `jazz`, `modal` and `classical`, that walk a weighted graph of scale-degree transitions (tonic → predominant → dominant → tonic, with plagal and vi–IV moves for pop and the cycle of fifths for jazz) from I with a seeded PRNG. A progression of four or more chords ends on a dominant-function chord (V or vii°; IV or vii in modal) so the loop leads home. The same key, style, length and seed always give the same chords.
|
|
1293
1312
|
|
|
1313
|
+
- Chord symbols (0.7) also take stacked alterations on any listed base, bare or in parentheses: `C7#5`, `Cmaj7+5`, `C9#11`, `C13#11`, `C7b9b13`, `C7(b9,#9)`, `C9b5`, `C7alt` (b9 #9 #11 b13), and the spellings `C-maj7`, `Cmmaj7`, `Cadd2`, `C2`, `C6add9`. Roman numerals are lossless: a numeral reads back as the chord it names. When the short form would read back differently, the numeral carries the chord's own suffix in brackets (`Idom7`, `I[5]`, `I[7#9]`, `V[13#11]`), and a major-scale degree the mode alters takes `♮` (`♮II` in Phrygian). Both forms parse wherever numerals do.
|
|
1314
|
+
|
|
1315
|
+
Prompt. `progression <chords> [each <beats>] [at <beat>] [bass]` (alias `prog`) writes sustained, voice-led `block` chords on the focused track: numerals in the song key (`progression i7 IV7 i7 IV7 each 8`) or symbols (`progression Am7 D9 bass`), `each` defaulting to one bar. It adds notes beside the existing ones, grows the song to fit, and writes the same notes as SDK `progression(chords, { key, from, each })`; `bass` adds the root under each chord. It is the carrier for a vocoder or talkbox without the agent. Menu: **Ctrl-K › Chords and key › progression**.
|
|
1316
|
+
|
|
1294
1317
|
Agent. `suggest_progression {key?, chords? | style?, length?, seed?, sevenths?, inversion?, spread?}` (read-only) returns each chord's name, roman numeral, voicing and bass. `write_chords {trackId?, chords, key?, start?, beatsPerChord?, perform?, pattern?, rate?, octaves?, strum?, velocity?, bass?, bassMode?, bassTrackId?, inversion?, spread?}` writes them as one revision. `chords` takes roman numerals in the key (`ii7`, `bVII`, `V/V`) or symbols (`Cm7`, `F/A`). The system prompt tells the agent to use these tools for chord parts, so its chords are diatonic and voice-led rather than hand-stacked.
|
|
1295
1318
|
|
|
1296
1319
|
SDK. `chord("Cm7", start, length, opts)` and `progression("ii7 V7 Imaj7", { key, from, each, perform, pattern, rate, octaves, strum, seed, voicing, spread, part, bass })` expand to notes at evaluation; see [docs/project-format.md](./docs/project-format.md). The vendored SDK stays one import-free file: `core/sdk/v1.ts` carries a generated copy of the engine (`bun core/sdk/sync-chords.ts`, checked by a test).
|
|
@@ -1320,23 +1343,29 @@ Per track:
|
|
|
1320
1343
|
|
|
1321
1344
|
Precedence with the synth: a note's settings override the track's synth. A note's `vibrato` replaces the synth's `vib`/`vibmod`, a `bend` replaces the pitch envelope (`penv`), and a gliding note ignores ZzFX `slide`. Render order: articulation, then humanize velocity, then pedal, then glide and mono voicing (on written timing), then humanize timing and length, then the velocity curve.
|
|
1322
1345
|
|
|
1323
|
-
Menu: **Sound › performance** has glide time (ms) and mode, sustain pedal (off or every bar), velocity curve, humanize timing, velocity and length, and new take; the loop stages them for A/B like any other sound change. Agent: `set_expression` (articulation, glide, bend, vibrato and humanize over note ids or a beat range) and `set_performance` (track glide, pedal, velocity curve and humanize). SDK (1.15.0): `note("C4", 0, 1, 0.8, { art: "staccato", glide: 0.05, bend: [[0, -200], [0.25, 0]], vibrato: { rate: 5.5, depth: 30 }, humanize: { timing: 10 } })`, `expr(notes, { art: "ghost" })` for many notes, and `track({ glide: 0.08, pedal: [[0, "down"], [4, "up"]], velocityCurve: "soft", humanize: { timing: 8, seed: 7 } })`.
|
|
1346
|
+
Menu: **Sound › performance** has glide time (ms) and mode, sustain pedal (off or every bar), velocity curve, humanize timing, velocity and length, and new take; the loop stages them for A/B like any other sound change. Agent: `set_expression` (articulation, glide, bend, vibrato and humanize over note ids or a beat range) and `set_performance` (the track's glide, pedal, velocity curve and humanize). SDK (1.15.0): `note("C4", 0, 1, 0.8, { art: "staccato", glide: 0.05, bend: [[0, -200], [0.25, 0]], vibrato: { rate: 5.5, depth: 30 }, humanize: { timing: 10 } })`, `expr(notes, { art: "ghost" })` for many notes, and `track({ glide: 0.08, pedal: [[0, "down"], [4, "up"]], velocityCurve: "soft", humanize: { timing: 8, seed: 7 } })`.
|
|
1347
|
+
|
|
1348
|
+
## Sound calibration
|
|
1349
|
+
|
|
1350
|
+
A song's `calibration` picks the revision of level, pitch and kit fixes the released engines render with. Absent or 0 keeps every 0.4 to 0.6.1 project byte-identical; `dawg init` writes the latest (1). `/calibration` shows it, `/calibration 1|latest|0|off` sets it; Ctrl-K › Project › calibration, the `set_calibration` agent tool and `song({ calibration: 1 })` do the same.
|
|
1351
|
+
|
|
1352
|
+
Revision 1: a closed (42) or pedal (44) hat chokes a sounding open hat (46) over 8 ms; GM toms 41 to 50 are pitched two thirds of a semitone per key around 45 (low tom); 49, 52, 55 and 57 play a crash, 51, 53 and 59 a ride and 56 a cowbell (they were a rim click), and section fills end on the crash; hat metal is band-limited; keys presets are leveled to within 3 dB of piano across notes 36 to 96; and lip brass locks its lip resonance to the sounding pitch with soft lip saturation, so held notes are steady and in tune.
|
|
1324
1353
|
|
|
1325
1354
|
## Tunings and scales
|
|
1326
1355
|
|
|
1327
|
-
Every project plays in 12-tone equal
|
|
1356
|
+
Every project plays in 12-tone equal tuning at A4 = 440 Hz until it says otherwise. A song tuning, a track tuning or a note's cents change only the frequencies; notes stay MIDI keys, so editing, chords, play mode and exports work the same. MIDI export carries a tuning with the MIDI Tuning Standard (a single-note tuning SysEx per tuned track, selected with RPN 3), and writes glides, bends, vibrato and note cents as pitch bend (range ±24 semitones) on notes that sound alone on their track; synths without MTS play 12-TET keys. A project without any of these renders byte-identically to 0.4.
|
|
1328
1357
|
|
|
1329
1358
|
Tunings (`core/tuning.ts`). A tuning is one table (`edo: 19`, `ratios: ["9/8", "5/4", …, "2/1"]`, `cents: [231, 474, …, 1200]`, a Scala `scl` file, or a library `name`) plus `ref` (the 12-TET A4 in Hz, default 440, that fixes the root key's pitch), `root` (the key of degree 0) and `map` (`linear` or `nearest`). The last table entry is the period, usually 1200 cents (2/1).
|
|
1330
1359
|
|
|
1331
|
-
- The library: `12-tet`, `19-edo`, `24-edo`, `31-edo`, `pythagorean`, `just` (5-limit), `7-limit`, `well-tuned-piano` (La Monte Young's 7-limit key map from E♭, after Kyle Gann's published ratios), `pelog` and `slendro` (Kunst's and Surjodiningrat's averages), `nyamaropa` (a Shona mbira after Berliner), `hindustani` (twelve just svaras), `shruti` (the 22 shrutis), maqam and dastgah tables (`rast`, `bayati`, `saba`, `sikah`, `huzam`, `shur`, `homayoun`, `chahargah`, `segah`, `nava`) and raga tables (`yaman`, `bhairav`, `kafi`, `bhairavi`, `todi`, `marwa`, `darbari`, `malkauns` and more, each built from the raga's own svaras over 5-limit defaults). `tuning list` shows each one with a line about it. The gamelan, mbira, maqam and raga tables are marked approximate: every gamelan and every mbira is tuned differently, and maqam and raga intonation varies by tradition and performer.
|
|
1360
|
+
- The library: `12-tet`, `19-edo`, `24-edo`, `31-edo`, `pythagorean`, `just` (5-limit), `7-limit`, `well-tuned-piano` (La Monte Young's 7-limit key map from E♭, after Kyle Gann's published ratios), `pelog` and `slendro` (Kunst's and Surjodiningrat's averages), `nyamaropa` (a Shona mbira after Berliner), `thai` (seven equal steps, the Thai and Khmer norm, also `7-edo`), `hindustani` (twelve just svaras), `shruti` (the 22 shrutis), maqam and dastgah tables (`rast`, `bayati`, `saba`, `sikah`, `huzam`, `shur`, `homayoun`, `chahargah`, `segah`, `nava`) and raga tables (`yaman`, `bhairav`, `kafi`, `bhairavi`, `todi`, `marwa`, `darbari`, `malkauns` and more, each built from the raga's own svaras over 5-limit defaults). `tuning list` shows each one with a line about it. The gamelan, mbira, maqam and raga tables are marked approximate: every gamelan and every mbira is tuned differently, and maqam and raga intonation varies by tradition and performer.
|
|
1332
1361
|
- Anchoring follows Scala and Surge's tuning library: the root key sounds at its 12-TET frequency under `ref`, and the table counts up from it. So `ref` is the 12-TET A4 that fixes the root, and A4 itself sounds at exactly `ref` only when the root is an A (in `just` from C, A4 is 5/3 above C4, about 436 Hz at ref 440). For an exact reference key and frequency, use a `.kbm` keyboard mapping. The root defaults to the library tuning's own (E♭ for the Well-Tuned Piano), else the song key's tonic in octave 4, else C4.
|
|
1333
1362
|
- Mapping. Twelve-step tables retune the twelve keys. Other sizes default to `linear`, one key per step, as Scala, Surge and Ableton's tuning system do (19-EDO puts the octave 19 keys up). `map: "nearest"` keeps the piano layout instead and plays each key at the nearest table pitch, which suits pentatonic gamelan tables on a normal keyboard.
|
|
1334
1363
|
- Frequencies at or above 20 kHz count as unmapped keys and stay silent, so a coarse table (`edo: 1`) cannot alias at the top of the keyboard.
|
|
1335
1364
|
- Scala. `tuning scl tunings/slendro.scl [kbm tunings/white.kbm]` copies a file from outside the project into `tunings/` and stores the path. The `.scl` parser follows the Scala specification: `!` comments, a description line, the count, then one pitch per line (a period means cents, otherwise a ratio or integer), the 1/1 implicit and the last pitch the period. A `.kbm` keyboard mapping (size, first and last key, middle key, reference key and frequency, octave degree, then the map with `x` for unmapped keys) overrides `ref` and `root`. Bad files are refused with a line number.
|
|
1336
|
-
- Track tuning overrides the song's field by field: a track with its own table uses it, and `ref`, `root` and `map` fall back to the song's.
|
|
1365
|
+
- Track tuning overrides the song's field by field: a track with its own table uses it, and `ref`, `root` and `map` fall back to the song's. Kits ignore tunings.
|
|
1337
1366
|
- Note cents: `note("E4-14c", 0)`, `add E4-14c at 0` or `cents n3 -14` give one note a static offset of up to ±1200 cents on top of any tuning.
|
|
1338
1367
|
|
|
1339
|
-
Scales (`core/chords.ts`). The song key string now names any scale: `"D dorian"`, `"A harmonic-minor"`, `"E hijaz"`, `"C yaman"`, `"C messiaen-3"`. The library has the church modes, harmonic and melodic minor, phrygian dominant, major and minor pentatonic, blues and major blues, the maqamat `hijaz`, `bayati`, `rast`, `saba`, `kurd`, `nahawand` and `nikriz`, the dastgahs `shur`, `homayoun`, `chahargah`, `segah` and `nava`, the neutral-third maqamat `sikah` and `huzam`, common Hindustani ragas (`yaman`, `bhairav`, `kafi`, `bhairavi`, `asavari`, `khamaj`, `todi`, `purvi`, `marwa`, `darbari`, `malkauns`, `bhupali`, `durga`) by their thaat or aroha notes,
|
|
1368
|
+
Scales (`core/chords.ts`). The song key string now names any scale: `"D dorian"`, `"A harmonic-minor"`, `"E hijaz"`, `"C yaman"`, `"C messiaen-3"`. The library has the church modes, harmonic and melodic minor, phrygian dominant, major and minor pentatonic, `yonanuki-minor` (1 2 b3 5 b6, the enka and trot pentatonic), blues and major blues, the maqamat `hijaz`, `bayati`, `rast`, `saba`, `kurd`, `nahawand` and `nikriz`, the dastgahs `shur`, `homayoun`, `chahargah`, `segah` and `nava`, the neutral-third maqamat `sikah` and `huzam`, common Hindustani ragas (`yaman`, `bhairav`, `kafi`, `bhairavi`, `asavari`, `khamaj`, `todi`, `purvi`, `marwa`, `darbari`, `malkauns`, `bhupali`, `durga`) by their thaat or aroha notes, Messiaen's seven modes of limited transposition, `chromatic` (all twelve pitch classes), `harmonic-series` (partials 8 to 15 over the tonic; `tuning harmonic-series` sounds them in just cents) and `quarter-tone` (each major degree beside its quarter-tone shadow, sounded as note cents). Every existing key string reads as before. Quarter-tone scales (Bayati, Rast, Saba) name their half-flat degrees; setting such a scale suggests the matching library tuning so they sound.
|
|
1340
1369
|
|
|
1341
1370
|
Chords. The chord engine stays twelve-tone: a scale outside the seven diatonic modes uses the nearest diatonic mode for key-mode chords. In a twelve-key tuning chords keep their keys (so in `just` they sound pure); in a linear non-12 tuning such as 19-EDO, the chord tools and play-mode chord phrases move each written pitch to the key that sounds nearest, so a C major triad becomes steps 0, 6 and 11.
|
|
1342
1371
|
|
|
@@ -1357,7 +1386,7 @@ Commands and menu.
|
|
|
1357
1386
|
| `scale [<tonic>] <name>` | the song key and scale (`scale D hijaz`) |
|
|
1358
1387
|
| `cents <id> <±c>` | detune one note |
|
|
1359
1388
|
|
|
1360
|
-
The menu has the same in
|
|
1389
|
+
The menu has the same in Ctrl-K › Chords and key › tuning (song tuning, ref, root, map, equal steps, ratios, cents, Scala file, keyboard map, scale and tonic; `/menu tuning` jumps there) and Ctrl-K › Sound › track tuning (the track's, showing inherited song values as `· song`). The Chords tonic row keeps a library scale (`D hijaz` stays hijaz). Agent: `set_tuning {target?, name? | edo? | ratios? | cents? | scl?, kbm?, ref?, root?, map?, off?}` and `set_scale {tonic?, scale}` run the same commands, `add_notes` and `update_notes` take an optional `cents` per note (0 clears it), and the agent brief carries the song and track tunings and a `cents` column when a focused note has one. SDK 1.16.0: `song({ tuning })`, `track({ tuning })` (a name string or an object), and pitch strings with a cents suffix (`note("E4-14c", 0)`, `seq("C4 E4-14c G4+2c")`).
|
|
1361
1390
|
|
|
1362
1391
|
Rendering. Synth and wavetable voices start at the tuned frequency; keyed samplers repitch by the ratio between the tuned and the 12-TET frequency, and one-shot samplers apply note cents only. Live playback, audition and export use the same table, so what you hear is what renders.
|
|
1363
1392
|
|
|
@@ -1403,11 +1432,11 @@ strum strum the block chords already on the track
|
|
|
1403
1432
|
- **Strokes.** A grid on `step` (an eighth by default) of `D` down, `U` up, `d` `u` light strokes, `x` a muted chuck, and `-` or `.` a rest (the strings ring on), or a named pattern: `down folk pop punk funk reggae waltz jangle island`. Down strokes go low to high, up strokes high to low over three or four strings, and a new stroke cuts the strings it restrikes. Patterns are dawg's own: the classic folk/pop eighth-note patterns of beginner method books.
|
|
1404
1433
|
- **Speed.** The time a full six-string down stroke takes, 22 ms by default (0..200 ms), the spread a real pick takes across the strings; it is in milliseconds so it stays the same at any tempo (`speed 1/32b` gives it in beats, converted at the song tempo). At 120 BPM the first-to-last note spread equals `speed` within 1 ms.
|
|
1405
1434
|
|
|
1406
|
-
Track.guitar is stored only when set (`{ tune, capo, hand, ring, position }`). Menu: **
|
|
1435
|
+
Track.guitar is stored only when set (`{ tune, capo, hand, ring, position }`). Menu: the **guitar** page of Ctrl-K › Sound (on guitar-like tracks: tune, capo, hand, ring, position, strum the chords, reset) and a **play** page in Ctrl-K › Chords and key (strokes, speed). Agent: `set_guitar`, `strum_chords`, and `write_chords` with `perform: "guitar"`, `strokes`, `speed`. SDK: `track({ guitar: { tune: "dadgad", capo: 2 } })` and `strum("G D Em C", { strokes: "folk", speed: 30 })`.
|
|
1407
1436
|
|
|
1408
1437
|
### Chord mode
|
|
1409
1438
|
|
|
1410
|
-
Play mode has a chord sub-mode
|
|
1439
|
+
Play mode has a chord sub-mode modeled on the Orchid's Key mode. It is `auto` by default when the focused track can play chords (pitched synths, piano, soundfonts, keyed samplers; not tracks whose instrument, name or id says bass, kit, drum or perc) in 12-TET without a mono or legato glide, otherwise `manual`, so a track in pelog, just intonation or another non-12 tuning, or a TB-303-style legato line, records single notes. Choosing a mode by hand (`Q`, `/chords`, the menu) sticks for the session.
|
|
1411
1440
|
|
|
1412
1441
|
- `auto`: each note key plays the diatonic chord of the song key on that root (C major: `S` plays Dm, `G` plays G). Keys outside the scale borrow from the parallel major or minor. The strip labels every white and black key with its chord.
|
|
1413
1442
|
- `manual`: note keys play single notes as before; latch a chord type or extension and they play that chord on the pressed root.
|
|
@@ -1465,7 +1494,7 @@ meter 7/8 at bar 5 meter change on a bar line, lasting until the next
|
|
|
1465
1494
|
meter remove bar 5 | meter clear
|
|
1466
1495
|
track rate 3/2 polytempo: the focused track plays at 1.5× the song tempo (0.125..8 or a/b)
|
|
1467
1496
|
track phase 0.5 start the track half a beat later
|
|
1468
|
-
track
|
|
1497
|
+
track loop 3 polymeter: loop the track's first 3 beats against the song's bars
|
|
1469
1498
|
track phasing 3 [over 48] continuous drift: a 3-beat cycle gains one cycle every 48 beats, then realigns (needs a loop of whole spans)
|
|
1470
1499
|
track phasing 3 hold 8 stepped, as in Piano Phase: hold in step 8 cycles, move a sixteenth ahead over 2 (drift 2, shift 0.25)
|
|
1471
1500
|
track time off follow the song again
|
|
@@ -1478,9 +1507,9 @@ track time off follow the song again
|
|
|
1478
1507
|
- **Track time** (`track.time: {rate, phase, cycle, steps?}`, phase, cycle and shift in ticks in the file) places a track's notes on the song timeline: the first `cycle` ticks repeat every `cycle / rate` song ticks, shifted `phase` song ticks later, restarting with every song loop. Two identical tracks with one on `track phasing 4` drift apart a little each cycle and line up again after `over` beats (default the loop), the continuous tape phasing of Reich's _It's Gonna Rain_ and _Come Out_; `over` and the cycle must divide the loop, and the prompt names the bars it needs otherwise. `track phasing 3 hold 8` stores `steps: {shift, hold, drift}` instead of a rate: the track holds in step for `hold` cycles, then moves `shift` ahead over `drift` cycles, and repeats, the shift-and-lock process of _Piano Phase_ and _Drumming_. Automation stays in song time.
|
|
1479
1508
|
- **Everywhere**: the offline renderer, the live engine and audition loop, the transport clock (beat ⇄ wall time, shared by every window through dawgd), click and count-in, recording quantization and the highway use the same map. `/export song.mid` and `dawg render song.mid` write a format-1 Standard MIDI File: track 0 carries the time-signature (FF 58) and tempo (FF 51) meta events, ramps are written as tempo steps every sixteenth whose BPM is the exact average over the step, so each step boundary lands on the same second as the WAV.
|
|
1480
1509
|
|
|
1481
|
-
The menu has the same controls under **Project ›
|
|
1510
|
+
The menu has the same controls under **Ctrl-K › Project › tempo and meter** (`/menu tempo`): the tempo map (add a change, a ramp, a rit or accel, a tempo, tempo primo, a fermata), meter changes, and the focused track's rate, phase, cycle, phasing and stepped phasing. The agent's `set_time` tool takes the same actions, and the SDK has `tempo`, `ramp`, `rit`, `accel`, `aTempo`, `tempoPrimo`, `fermata`, `meter`, `phasing` and `stepPhasing` (see **Project files and SDK**).
|
|
1482
1511
|
|
|
1483
|
-
##
|
|
1512
|
+
## Grooves and kits
|
|
1484
1513
|
|
|
1485
1514
|
**Patterns.** dawg ships a library of 31 starting grooves, written for dawg from the defining placements of each style (no transcriptions). A pattern is a set of rhythm rows, one per voice, so after applying it every part is still a few Euclidean or grid parameters you can change in `/euclid`, with the prompt grammar, or in `track.ts`.
|
|
1486
1515
|
|
|
@@ -1493,7 +1522,7 @@ The menu has the same controls under **Project › Tempo & meter** (`/menu tempo
|
|
|
1493
1522
|
|
|
1494
1523
|
Applying to a missing track creates a kit track; an empty melodic track becomes a kit track; a melodic track with notes is refused. Voices the track lacks (a sampler kit without a rim, say) are skipped and named in the receipt. Every apply is one revision and one undo step. In `track.ts`, `pattern("boom-bap")` returns the rows: `rhythm: pattern("boom-bap")`, or `[...pattern("house"), euclid("rim", 5, 16)]` to add one.
|
|
1495
1524
|
|
|
1496
|
-
Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-style`, `dnb`, `halftime`, `boom-bap`, `lofi`, `trap`, `drill`, `reggaeton`, `dancehall`, `one-drop`, `afrobeat`, `afrobeats`, `bembe`, `tresillo`, `son-clave`, `bossa-nova`, `samba`, `cumbia`, `garage`, `jersey-club`, `footwork`, `rock`, `funk`, `shuffle`, `euclid-poly`.
|
|
1525
|
+
Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-style`, `dnb`, `halftime`, `boom-bap`, `lofi`, `trap`, `drill`, `reggaeton`, `dancehall`, `one-drop`, `afrobeat`, `afrobeats`, `bembe`, `tresillo`, `son-clave`, `bossa-nova`, `samba`, `cumbia`, `garage`, `jersey-club`, `footwork`, `rock`, `funk`, `shuffle` (triplet 8ths), `half-time-shuffle`, `euclid-poly`. Ctrl-K › Rhythm › grooves lists them too.
|
|
1497
1526
|
|
|
1498
1527
|
**Kits.** A `kit` track plays the built-in drum synth. `kit: "<name>"` on the track (`/kit <name>`, or `set_drum_kit` for the agent) chooses one of six synthesized kits, all offline and deterministic; a track without `kit` sounds exactly as before.
|
|
1499
1528
|
|
|
@@ -1507,7 +1536,7 @@ Patterns: `house`, `disco`, `techno`, `minimal`, `electro`, `breakbeat`, `amen-s
|
|
|
1507
1536
|
| `electro` | tight short kick, clicky rim, ticking hats (alias `minimal`) |
|
|
1508
1537
|
| `trap` | distorted long 808, crisp hats, high snare |
|
|
1509
1538
|
|
|
1510
|
-
Sample kits from packs (`/kit 909` and the rest, see **Sample packs**) sit in the same picker after the synth kits. `/kit syn909` on a sampler kit turns it back into a synth kit track, moving hits and rows to the
|
|
1539
|
+
Sample kits from packs (`/kit 909` and the rest, see **Sample packs**) sit in the same picker after the synth kits. `/kit syn909` on a sampler kit turns it back into a synth kit track, moving hits and rows to the drums of the same name. The agent has `list_drum_patterns`, `apply_drum_pattern {name, trackId?, tempo: auto|keep|set}` and `set_drum_kit {kit, trackId?}`, and its prompt starts style grooves from a groove.
|
|
1511
1540
|
|
|
1512
1541
|
## Arrange (sections and form)
|
|
1513
1542
|
|
|
@@ -1535,7 +1564,7 @@ Sections are markers over the timeline, like the arranger track in Studio One or
|
|
|
1535
1564
|
| `drop [<section> \| at <bar>] [cut <beats>] [no impact]` | a pre-drop cut (1 beat of silence by default, up to two bars) and an impact on the downbeat; bare `drop` lands on the section named `drop` or `chorus`, else on the bar after the last build (adding that bar when the build ends the song); with neither it asks for `drop chorus` or `drop at 17`. A cut that clips a build's uplifter retunes its rise to end at the cut |
|
|
1536
1565
|
| `fill [<section> \| at <bar>] [toms\|roll\|kick] [<n> beats] [no crash]` | a drum fill on the beats before the section (1 by default, ½ beat up to two bars), or at every section boundary |
|
|
1537
1566
|
|
|
1538
|
-
Playback follows the form; with a section looped it loops just that section, with its mutes and variations, and the highway, play mode and auditions stay inside it (an audition region is clipped to the looped section). Export (`dawg render`, the agent's preview) always plays the whole form and ignores the section loop; `dawg render out.wav --section chorus` renders one section. Every WAV covers the whole song (or section) plus its tail; a long song renders in windows and is capped at 15 minutes, and a held note that crosses a window seam is crossfaded so long drones stay smooth. Exports mark the form: WAV renders carry a `cue ` point and `LIST adtl` label at each section start, and MIDI exports an FF 06 marker. Sections count bars in one meter: a compound meter held from bar 1 (`meter 6/8`, `song({ meter: [12, 8] })`) works, while meter changes later in the song and sections exclude each other.
|
|
1567
|
+
Playback follows the form; with a section looped it loops just that section, with its mutes and variations, and the highway, play mode and auditions stay inside it (an audition region is clipped to the looped section). Export (`dawg render`, the agent's preview) always plays the whole form and ignores the section loop; `dawg render out.wav --section chorus` renders one section on its own: notes and clips are cut at its edges and nothing before it plays, so stateful effects (a vocoder's band followers, a talkbox's first LPC frame, reverb tails) start fresh and the first and last few milliseconds differ from the same span of the full render. The windows a long render is split into internally do pre-roll and match the full render. Every WAV covers the whole song (or section) plus its tail; a long song renders in windows and is capped at 15 minutes, and a held note that crosses a window seam is crossfaded so long drones stay smooth. Exports mark the form: WAV renders carry a `cue ` point and `LIST adtl` label at each section start, and MIDI exports an FF 06 marker. Sections count bars in one meter: a compound meter held from bar 1 (`meter 6/8`, `song({ meter: [12, 8] })`) works, while meter changes later in the song and sections exclude each other.
|
|
1539
1568
|
|
|
1540
1569
|
Generators write ordinary notes, tracks and automation, so everything they make can be edited or undone (one step per command):
|
|
1541
1570
|
|
|
@@ -1550,33 +1579,229 @@ Section mutes and variations govern every bar of their section: a note held from
|
|
|
1550
1579
|
|
|
1551
1580
|
The arrangement strip is one row under the header that shows the sections over the timeline (`▏verse ▏chorus`), the looped section reversed, the section under the playhead bold, and the playhead as `▼`. It appears only when the song has sections. The menu's **Arrange** section (`/menu arrange`) lists every section; each opens loop, jump here, a mute toggle for every track, transpose and gain, build (into it, over it, or custom length and layers), drop (cut length and impact), fill (style, beats and crash), duplicate, duplicate as, move to, move left and right, rename, clear mutes and variations, unmark and delete. Agent tools: `list_sections`, `edit_section` (mark, add, duplicate, move, rename, delete, unmark, mute, unmute, vary, reset, loop, unloop), `set_form` and `add_transition` (build into or over a section, drop, fill). In `song.ts`: `song({ sections: [{ name: "verse", startBar: 0, bars: 8 }, …], form: "intro verse*2 chorus", loopSection: "chorus" })` (SDK 1.18.0).
|
|
1552
1581
|
|
|
1582
|
+
## Styles
|
|
1583
|
+
|
|
1584
|
+
`/style` writes a whole song in a style: drums, bass, chords, melody and form, generated from a **style card**, a set of theory patterns rather than recordings. A card names the meter (signature, additive grouping, hypermeter, cycles such as tala, iqa, gongan or clave timelines), the tempo range, the groove (subdivision, swing ratio, microtiming, per-role lay-back, seeded humanisation), the onset grids per role as abstract step weights, the pitch space (12-TET scales and modes, or a tuning such as maqam quarter tones, with scale weights), the harmony as a grammar (Roman-numeral presets, fixed forms such as the 12-bar blues, a Markov chain and cadences), melody contour, range and interval statistics, the texture (which roles are required) and the form. Cards inherit: a leaf patches its branch, which patches its family root, so `deep-house` only states what differs from `house` and `electronic`.
|
|
1585
|
+
|
|
1586
|
+
The taxonomy (`core/styles/taxonomy.ts`) has eight families and about 850 styles; every leaf is present, and a leaf without its own card generates from its nearest ancestor's card. Generation is deterministic: the same style, bars and seed always write the same song, and nothing is random beyond the seed.
|
|
1587
|
+
|
|
1588
|
+
| Command | Does |
|
|
1589
|
+
| -------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
1590
|
+
| `style` | the families and their root styles |
|
|
1591
|
+
| `style list [<id>]` | the children of a style |
|
|
1592
|
+
| `style search <words>` | ranked search over ids, names, aliases and regions |
|
|
1593
|
+
| `style info <id>` | path, meter, tempo, groove, tuning, harmony and roles |
|
|
1594
|
+
| `style <id> [bars] [seed]` | replace the song with one in that style (1 to 64 bars, default 8; one undo step) |
|
|
1595
|
+
| `style blend <a> <b> [weight] [bars] [seed]` | mix two cards; weight 0 is `a`, 1 is `b` (default 0.5) |
|
|
1596
|
+
| `style again` | the song's style with the next seed |
|
|
1597
|
+
|
|
1598
|
+
Every generated song is checked by `core/styles/validate.ts` before it is written: meter and tempo in range, onsets on the card's grid within tolerance, the swing ratio measured from the notes, every pitch in the scale or tuning, the chord sequence accepted by the harmony grammar, melody range and interval statistics, and every instrument resolving. The summary line reports how many checks passed. The song records its provenance in `style` (`{ id, seed, bars, blend? }`), which `/style again` reads.
|
|
1599
|
+
|
|
1600
|
+
Menu: **Ctrl-K › Arrange › style** (`/menu style`) walks the taxonomy, with `find`, `blend` and, once the song has a style, `again`; each style offers make 4, 8, 16 or 32 bars and about. Agent tools: `list_styles` (families, children or a query), `style_info` and `apply_style` (`id`, `bars`, `seed`, `blend`, `weight`). In `song.ts`: `song({ style: style("deep-house", { seed: 3, bars: 8 }) })` records the provenance (SDK 1.33.0); the notes live in the tracks as usual.
|
|
1601
|
+
|
|
1602
|
+
## Voice
|
|
1603
|
+
|
|
1604
|
+
Release 0.7 adds voice tools: audio clips and lyrics, pitch tracking, autotune, a formant shift, sung vowels and choirs, and a vocoder. Each arrives in its own subsection below. `/vocal` is the umbrella: bare `/vocal` lists every voice verb this build has, and `/help voice` shows them; `/help <command>` (`/help vocoder`, `/help clip`, `/help autotune`) shows one command with its full usage. Ctrl-K › Voice: every voice tool; on a track without a voice it offers "turn this track into a voice".
|
|
1605
|
+
|
|
1606
|
+
A track may store `clips`, `takes` and note `lyric`s (see docs/project-format.md). Projects without them sound exactly as before.
|
|
1607
|
+
|
|
1608
|
+
### Clips and lyrics
|
|
1609
|
+
|
|
1610
|
+
An audio clip is a window onto a WAV file in the project, placed at a bar on a track. Clips sum into the track before its effects, so the track's filter, compressor, reverb, sends and automation all act on them. Any track can hold clips: on a synth track the notes still play beside them.
|
|
1611
|
+
|
|
1612
|
+
- **`vocal`** is the instrument for a sung part. Its notes are guides: silent in exports and renders, a soft sine (about -12 dB under a plain sine) in play mode and the audition loop so you can sing or check against them. `instrument vocal` (or `/track vocal`) also fills the vocal chain into effects the track has not set: a 90 Hz high-pass for rumble and plosives, a 3:1 compressor, and a short plate. A `vocal` track without clips keeps the plain tone older projects had.
|
|
1613
|
+
- **`/vocal import <file> [bar]`** copies a WAV that is already 48 kHz mono 16-bit straight in, and converts anything else through the sample importer (ffmpeg, which dawg detects and never installs), into `tracks/<slug>/samples/` as 48 kHz mono 16-bit, pins its sha256 and places a clip at the bar (bar 1 when left out). On a fresh track (default sine, no notes) it also sets the instrument to `vocal`, without the chain. `instrument vocal` adds the vocal chain (90 Hz high-pass, 3:1 compressor, short plate) to effects the track does not set, and its receipt lists what it added. The clip gain is set so the file's peak sits at -6 dBFS, leaving headroom for the chain. `/vocal stem [bar]` places the vocals stem from the last `split_stems`. `/vocal setups` lists one-step setups; `/vocal setups <name>` applies only what this build supports and names what is missing (`hyper` sets `autotune hard`, formant +3.5, slapback and light distortion; setups needing harmony, recording or `/say` wait for those tools).
|
|
1614
|
+
- **`/clip`** lists the focused track's clips; `/clip [id] <edit>` changes one (the clip under the cursor when `id` is left out): `gain -3` (dB, -60..12, absolute; `gain by -3` nudges), `fade .01 .2` (in and out, seconds, equal-power; `fade in .01` or `fade out default` sets or resets one side), `move 9` and `split 7` (1-based bars, `5.3` is bar 5 beat 3), `trim [offset s] [dur s|end]` (seconds into the file; `dur end` plays to the file's end), `rev`, `mute` (toggles), `repeat 2 [to 32]` (copies every 2 bars up to bar 32, or to the song end; `repeat every 2 to 32` also works), `rm`.
|
|
1615
|
+
- **`/lyrics [bar] <text>`** puts syllables onto the focused track's notes from the bar, one per note in time order (the top note of a chord). `hel-lo` splits a word across notes, `_` holds the last syllable over another note (a melisma), `~` skips a note. A word without hyphens is split by an English syllable guess (th, sh, ch, ng and ck stay whole, a consonant plus `le` is its own syllable: `lit-tle`, `some-thing`, `for-ev-er`), and the receipt names the words it split; type hyphens where the guess is wrong. Notes starting together (a chord or a doubled note) take one syllable on the top note, and the others hold. `/lyrics` shows them; `/lyrics clear [bar]` removes them. On a sing track each syllable sings its vowel and a `_` holds the vowel before it; on other tracks the guide and the highway show them.
|
|
1616
|
+
|
|
1617
|
+
Placement follows the tempo map: a clip starts on its tick's time and plays at the file's own speed, so a tempo ramp moves its start but never stretches the audio (Ableton calls this warp off; a clip in a take with `ppm` is stretched by that drift only). A clip whose file is missing or whose bytes no longer match its sha256 renders silent and warns; `dawg check` re-hashes every clip and take and reports mismatches like a type error. Sections act on clips: a section mute silences the clips in it, and a clip crossing a section edge is cut there with a 5 ms equal-power fade, as are clips cut by `split`.
|
|
1618
|
+
|
|
1619
|
+
Ctrl-K › Voice › **clips** (each clip with gain, fade in, fade out, start in file, length, reverse, mute, move, split, repeat, file and remove; import a file, the vocals stem and the setups) and **lyrics**, and Ctrl-K › Voice › **voice presets**. The highway draws a clip row beside the focused track: each clip as a waveform block named by its file, a `┃` seam where one clip starts at another's end, and each note's lyric beside its head. Agent tools: `place_clip`, `edit_clip` (gain, fades, trim, move, split, rev, mute, repeat, remove), `set_lyrics`; the tools and commands take gain in dB and the tools' `fadeIn`/`fadeOut` are the SDK's `fadeInTime`/`fadeTime`. SDK:
|
|
1620
|
+
|
|
1621
|
+
```ts
|
|
1622
|
+
track({
|
|
1623
|
+
id: "vox",
|
|
1624
|
+
instrument: "vocal",
|
|
1625
|
+
clips: [
|
|
1626
|
+
audio("tracks/vox/samples/verse.wav", { at: 8, gain: 0.7, fadeTime: 0.2 }),
|
|
1627
|
+
...repeatAudio(audio("tracks/vox/samples/hey.wav", { at: 16 }), {
|
|
1628
|
+
every: 4,
|
|
1629
|
+
until: 32,
|
|
1630
|
+
}),
|
|
1631
|
+
],
|
|
1632
|
+
notes: lyrics("hel-lo _ world", [
|
|
1633
|
+
note("C4", 8),
|
|
1634
|
+
note("D4", 9),
|
|
1635
|
+
note("E4", 10),
|
|
1636
|
+
]),
|
|
1637
|
+
});
|
|
1638
|
+
```
|
|
1639
|
+
|
|
1640
|
+
`at`, `in` and `out` are in beats, `offset` and `dur` in seconds, and `gain` is linear (0.5 is about -6 dB; the printer writes the dB beside it as `gain: 0.5 /* -6.0 dB */`); `take(name, src, opts)` describes a take (the shape 0.7.1 recording writes). `audio()` prints its options in the order `id at offset dur gain fadeInTime fadeTime rev take mute text say sha256`, and a printed project reads back deep-equal.
|
|
1641
|
+
|
|
1642
|
+
### Formant shift and vowel morph
|
|
1643
|
+
|
|
1644
|
+
The `formant` effect moves a sound's formants (the resonances of the throat and mouth that make a voice sound big or small, male or female) without changing its pitch; it works on any source and most clearly on voices. It sits in the chain after `autofilter` and before `vowel`.
|
|
1645
|
+
|
|
1646
|
+
| Knob | Range | Default | Lane | Does |
|
|
1647
|
+
| ---------------- | ----------- | ------- | --------------- | --------------------------------------------------------------------------- |
|
|
1648
|
+
| formant shift | -12..12 st | 0 | `formant-shift` | moves the spectral envelope; negative is deeper or bigger, positive smaller |
|
|
1649
|
+
| formant mix | 0..1 | 1 | `formant-mix` | blends the shifted and the dry signal |
|
|
1650
|
+
| vowel morph (to) | 0..1 (to v) | 0 | `vowel-morph` | glides the vowel filter's five formants from `vowel` toward `to` (log Hz) |
|
|
1651
|
+
|
|
1652
|
+
```text
|
|
1653
|
+
/formant -4 deeper (pitch stays); /formant 3 0.5 is smaller at half mix
|
|
1654
|
+
/formant giant presets deep giant bright tiny; /formant off removes it
|
|
1655
|
+
/vowel a o 0.5 vowel filter halfway from a to o; /vowel morph 0.8, /vowel to u
|
|
1656
|
+
/vowel to off back to one vowel; changing the vowel keeps your mix
|
|
1657
|
+
/vocal formant -4 the same, under the voice umbrella
|
|
1658
|
+
automate formant-shift points 0:-6 8:6
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
Shifts of 2 to 4 st sound natural; 7 and beyond are a cartoon. The shift is a cepstral spectral-envelope warp (Röbel and Rodet 2005; Smith, Spectral Audio Signal Processing): each STFT frame (1024 points at 24 kHz and below, 2048 above, hop a quarter frame, Hann analysis and synthesis) is divided by its envelope and multiplied by the envelope read at `k / 2^(st/12)`, the gain clamped to 24 dB and the phase left alone. The envelope's lifter follows the voice: 0.75 of a pitch period from a 5-frame median autocorrelation f0, clamped to 1..2 ms, 1 ms when unvoiced. Measured on the synthetic voice fixture at 22.05 kHz, ±2 and ±4 st land within 1 cent of the original pitch and 3 to 5.5 dB RMS of the envelope of a voice synthesized with moved formants (against 4 to 10 dB unprocessed), pre-echo stays below -15 dB, and it costs about 4 ms per audio-second. Frames are anchored to absolute hop multiples, so a preview window plays exactly the same samples as the full render. `shift 0` without automation leaves the sound untouched. This is not the sampler's `shift … formant`, which keeps formants while the pitch moves.
|
|
1662
|
+
|
|
1663
|
+
Before 0.7, `fx formant` was an alias of the vowel filter. `/fx formant o` and `set_fx {effect: "formant", vowel}` now answer "formant now shifts formants at constant pitch; the vowel filter is `vowel`".
|
|
1664
|
+
|
|
1665
|
+
Ctrl-K › Voice › **formant**: shift and mix (left/right adjust, `x` resets, space auditions with staged A/B); **Effects › more effects › vowel** gains To and Morph. Mix lists the `formant-shift`, `formant-mix` and `vowel-morph` lanes. The agent's `set_formant` tool takes `shift`, `mix`, `preset` and `off` and can be previewed with `preview_sound`; `set_fx vowel` takes `to` and `morph`. In the SDK: `fx: { formant: { shift: -4 }, vowel: { vowel: "a", to: "o", morph: 0.5 } }`.
|
|
1666
|
+
|
|
1667
|
+
### Singing voice
|
|
1668
|
+
|
|
1669
|
+
`sing choir` (or `/sing choir`, or the instrument words `aah`, `ooh`, `choir`, `chorale`, `khoomei`, `sygyt`, `kargyraa`) turns the focused track into the built-in singing voice: a synthetic glottal source (the Liljencrants-Fant model, shaped by Fant's Rd voice quality and band-limited by pitch so nothing aliases) through a five-formant Klatt cascade with soprano, alto, tenor and bass vowel tables. It is a synthetic voice, not a recording and not anyone's voice, and it renders offline and deterministically. It reads pitch through the track's tuning, and glide, articulation, pedal and the tempo map work as they do for winds.
|
|
1670
|
+
|
|
1671
|
+
Presets: `aah ooh choir oohchoir chorale airy glass` (Choir), `lament soprano basso` (Solo) and `drone khoomei sygyt kargyraa` (Throat). `sing` with no arguments shows the track's voice; `sing list` lists the presets; `sing <preset> <param> <value>…` applies a preset and overrides; `sing <param> <value>` changes one value and `sing <param> off` returns it to the preset; `sing off` clears the voice and returns the track to the plain `sine` tone, so it sounds the same after a save and reload.
|
|
1672
|
+
|
|
1673
|
+
Parameters: `voice` (`auto`, `soprano`, `alto`, `tenor`, `bass`; auto picks one type per part from its median pitch, so an SATB part keeps its own timbre), `vowel` (`a e i o u`, or a morph such as `a>o` across each note), `morph`, `formant` (semitones, a bigger or smaller singer), `bright` (voice quality: breathy to pressed; velocity also presses), `breath`, `jitter`, `shimmer`, `attack`, `release`, `vib`, `vibmod`, `vibdelay` (vibrato that arrives after the onset), `voices` (1 to 8; more than one is a stereo ensemble with seeded scatter of pitch, timing, vibrato and formants), `spread` (cents), `ring` (the singer's formant near 3 kHz), `drone`, `overtone`, `harmonics` and `sub`, and `gain`. Lanes `sing-morph`, `sing-formant`, `sing-bright`, `sing-breath`, `sing-vibmod`, `sing-ring`, `sing-overtone` and `sing-sub` automate them.
|
|
1674
|
+
|
|
1675
|
+
Vowels per note: `/note vowel o` (or `a>u`, or `off`) on the selection, last note, a bar or note ids, and `sing vowels a e i o u` cycles vowels over the track's notes. A note's own `vowel` wins over the track's; a note with a `lyric` sings that syllable's vowel. High notes open the jaw: when the pitch rises above the first formant the voice raises it to follow, as trained sopranos do.
|
|
1676
|
+
|
|
1677
|
+
Throat singing: `khoomei`, `sygyt` and `kargyraa` hold one drone (`drone D3`, a note name or MIDI number) for each phrase, and each melody note picks the drone harmonic nearest to it (folded by octaves into `harmonics`, 6 to 12 by default) and sharpens the overtone filter onto it, so the melody whistles above the drone. `kargyraa` adds a subharmonic an octave below (`sub`). Without a `drone` of your own, the drone follows the song key: the key root nearest the preset's drone, so sygyt keeps its whistle near 2 kHz and kargyraa its growl near A2 (`sing` and the menu show it as, for example, `drone E2 (key)`). A throat preset on a track with no notes writes an 8-note demo line an octave above the drone, so `sing khoomei` sounds in one step. In play mode a throat track is one voice: a new key releases the last.
|
|
1678
|
+
|
|
1679
|
+
Menu: Ctrl-K › Voice › voice presets: a Choir, Solo or Throat preset (space auditions) with the sing rows, with the drone and overtone rows in a Throat sub-menu; on a sung track, the **Vowels** page sets the notes' vowels (Ctrl-K › Sound › performance). The agent tools are `set_sing` and `set_vowels` (both previewable), and `set_instrument` takes the instrument words. In the SDK: `track({ instrument: sing("khoomei", { drone: "D3" }) })`, `instrument: "choir"`, and `note("C4", 0, 1, 0.8, { vowel: "a>o" })`.
|
|
1680
|
+
|
|
1681
|
+
### Pitch
|
|
1682
|
+
|
|
1683
|
+
dawg can read the melody out of audio: a sampler voice (for example a stem from `resample` or `split_stems`) or, once clips land, an audio clip. Nothing in the score changes until you ask for guide notes.
|
|
1684
|
+
|
|
1685
|
+
- `/vocal pitch` reports the focused audio's detected key, median pitch and range with cents (`stem · voice stem · key · a major · median C#4 -8c · range A3 -5c to F#4 +30c · 14 notes`). Add `clip` or `voice` (a sampler voice), optionally followed by its name in any case, to choose the source, and `bass`, `tenor`, `alto` or `soprano` to narrow the search range (auto is 70-1400 Hz; bass goes down to 55 Hz).
|
|
1686
|
+
- `/vocal pitch trace on` draws the sung pitch over the note highway: one dot per column on the lane of the nearest semitone, in the warning color when it is more than 15 cents off. `/vocal pitch trace off` hides it.
|
|
1687
|
+
- `/vocal notes` turns the melody into a new guide-notes track (`<track>-notes`, or `as <name>`), one note per sung note, placed through the tempo map where the audio plays. Velocity follows the voicing confidence.
|
|
1688
|
+
- Ctrl-K › Voice › pitch shows the detected key and median, with Analyze, Trace and Make notes rows.
|
|
1689
|
+
- Agent tools: `analyze_pitch` (read-only: key, median, range and the note list in file seconds) and `pitch_to_notes`.
|
|
1690
|
+
|
|
1691
|
+
The tracker is a pYIN-style estimator on an exact 16 kHz copy of the audio, with a 5 ms hop, a voicing probability per frame and a Viterbi path that resists octave jumps; on the test voices it stays within 10 cents of the truth on held notes and costs about 20 ms per audio second. Curves are cached in `.dawg/analysis/` (`<sha256>.<voice>.v<version>.f0`, at most 64 MB, oldest removed first; `/pack cache` shows the size), so a second look is instant. A corrupt or old cache file is ignored and rebuilt, and the cached and fresh curves are identical.
|
|
1692
|
+
|
|
1693
|
+
### Vocoder
|
|
1694
|
+
|
|
1695
|
+
A vocoder makes one sound talk with another: a voice (the modulator) shapes the spectrum of an instrument (the carrier) band by band, so the instrument sings the voice's words at the instrument's pitch. dawg's vocoder sits on the carrier track: `vocoder.src` names the voice track, and the carrier is the track itself.
|
|
1696
|
+
|
|
1697
|
+
The one-step way: focus a vocal track (a sampler voice or clips) and type `/vocoder`. dawg adds a `<name> vocoder` track playing the built-in carrier, points it at the vocal and mutes the vocal, in one undo step; the carrier follows the song's chords when it has harmonic tracks, otherwise it drones on the song key's root. `/vocal vocoder` is the same command. On a synth track with one vocal in the song, `/vocoder` drives that synth instead. `/vocoder talkbox` or `/vocoder formant 3` on the vocal does the same with those settings, and on a vocal that already drives a carrier `/vocoder` focuses that carrier (`/vocoder new` makes another). With nothing to vocode it changes nothing and says how to bring a voice in. Muting the source does not silence the vocoder: the vocoder listens before the source's mute, volume, pan and sends.
|
|
1698
|
+
|
|
1699
|
+
`/vocoder` and `vocode` echo the modulator's license. Vocode only your own recordings or audio you hold the rights to. To vocode your own voice, load a recording of it onto a track (`/sample take.wav`), then `/vocoder talkbox` on that track.
|
|
1700
|
+
|
|
1701
|
+
| Command | Does |
|
|
1702
|
+
| -------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
1703
|
+
| `/vocoder [preset]` | vocode the focused vocal (a new carrier), or set the focused carrier's preset |
|
|
1704
|
+
| `/vocoder src <track>` | the modulator: a track id or name slug (`lead-vox`); preset words win over track names |
|
|
1705
|
+
| `/vocoder <param> <value>` / `<param> reset` | set or reset any parameter below (`att` and `rel` are Strudel spellings) |
|
|
1706
|
+
| `/vocoder reset` / `off` / `presets` | back to the preset's values; remove the vocoder; list presets |
|
|
1707
|
+
| `instrument vocoder` | the built-in carrier: saw, supersaw, pulse or noise following notes, chords or a drone |
|
|
1708
|
+
|
|
1709
|
+
Presets (all 24 bands or fewer): **classic** (70s and 80s band vocoder lead, the default), **robot** (12 bands, a pulse drone), **talkbox** (an LPC mouth filter on a saw), **choir** (stereo supersaw chord pad), **glass** (bright, formant +3), **whisper** (noise carrier), **smear** (long release wash; try `freeze`) and **lofi** (8 narrow bands under 4 kHz).
|
|
1710
|
+
|
|
1711
|
+
Register matters for the talkbox: a carrier note sounds only the harmonics of its pitch, so a vowel's first formant (250-700 Hz) needs a carrier fundamental well below it. Keep talkbox chords and lines around A2-A3 (`voicing -8` on a progression, or write them an octave or two down); above about 300 Hz /i/ and /u/ blur into /a/. `/track rm <name>` removes a track and drops any `vocoder.src` that named it, so the carrier plays alone; a project file whose src names a missing track fails `dawg check` with that fix.
|
|
1712
|
+
|
|
1713
|
+
| Parameter | Range | Default | Does |
|
|
1714
|
+
| ---------- | ----------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
|
|
1715
|
+
| `tap` | `chain`, `dry` | chain | listen to the source after its mono chain (before pan) or before its effects |
|
|
1716
|
+
| `mode` | `channel`, `talkbox` | channel | a band bank, or an LPC talkbox (order sr/2000, 20 ms frames) |
|
|
1717
|
+
| `carrier` | `saw`, `supersaw`, `pulse`, `noise` | supersaw | the built-in carrier (`instrument vocoder` only) |
|
|
1718
|
+
| `follow` | `notes`, `chords`, `drone` | notes | the built-in carrier's pitch: its notes, the song's chords, or `root` |
|
|
1719
|
+
| `root` | 24..96 | 45 | the drone pitch (MIDI), and the octave chords are voiced from |
|
|
1720
|
+
| `spread` | 0..1 st | 0.15 | supersaw detune |
|
|
1721
|
+
| `bands` | 4..40 | 16 | channel bands, spaced evenly in log frequency (heavy above 24) |
|
|
1722
|
+
| `lo`, `hi` | 50..1000 Hz, 2000..12000 Hz | 100, 8000 | the lowest and highest band centers |
|
|
1723
|
+
| `width` | 0.25..4 | 1 | band width as a multiple of the spacing |
|
|
1724
|
+
| `attack` | 0.0005..0.2 s | 0.005 | envelope follower attack |
|
|
1725
|
+
| `release` | 0.005..2 s | 0.04 | envelope follower release; long releases smear |
|
|
1726
|
+
| `formant` | ±24 st (talkbox ±12) | 0 | move the voice's formants: + is smaller and brighter |
|
|
1727
|
+
| `unvoiced` | 0..1 | 0.5 | noise in place of the carrier on s, f, sh and t |
|
|
1728
|
+
| `sens` | 0..1 | 0.5 | how readily a frame counts as unvoiced |
|
|
1729
|
+
| `hiss` | 0..1 | 0 | the source's top end passed straight through |
|
|
1730
|
+
| `gate` | -90..0 dBFS or `auto` | auto | silence below this source level; `auto` reads the source's noise floor |
|
|
1731
|
+
| `enhance` | on/off | on | whiten the carrier so every band speaks |
|
|
1732
|
+
| `depth` | 0..1 | 1 | how much the voice shapes the carrier |
|
|
1733
|
+
| `freeze` | on/off | off | hold the last sung vowel through every rest (a `vocoder-freeze` lane holds whatever is sounding) |
|
|
1734
|
+
| `mix` | 0..1 | 1 | wet against the plain carrier |
|
|
1735
|
+
| `gain` | ±24 dB | 0 | output trim (a fixed makeup gain and a soft peak guard at 1.0 come first) |
|
|
1736
|
+
| `seed` | integer | track id | the unvoiced noise seed |
|
|
1737
|
+
|
|
1738
|
+
How it works: channel mode splits both signals into the same bands (cascaded RBJ band-passes, laid out from `lo` to `hi` the same at every sample rate), follows each modulator band's level, and multiplies the carrier's band by it. Each band's envelope is advanced by its filter's group delay plus the attack, so consonants stay on time. A formant shift reads the envelopes at a fractional band index. Talkbox mode fits an all-pole mouth filter to each 20 ms frame of the voice (on an absolute hop grid, so windows agree) and runs the carrier through it, which keeps vowels sharper with fewer artefacts. Frames that are both high-band heavy and aperiodic count as unvoiced and get seeded noise (keyed to the song sample) instead of the carrier, as hardware vocoders do with their sibilance switch. A stereo carrier (a supersaw) gets one analysis and two synthesis banks.
|
|
1739
|
+
|
|
1740
|
+
Menu: **Ctrl-K › Voice › vocoder**: a Source picker, Preset and one row per parameter; **Ctrl-K › Voice › voice presets** (Vocoder) makes a carrier track or picks a preset. The Mix lane picker lists `vocoder-spread`, `-width`, `-release`, `-formant`, `-unvoiced`, `-hiss`, `-depth`, `-freeze` (a 0/1 step lane), `-mix` and `-gain`. Agent tools: `set_vocoder` (preset, src and a params object; previewable) and `vocode` (makes the carrier from a source and echoes the source's license). In `song.ts`: `vocoder("talkbox", { src: "lead-vox", formant: 2 })` as an instrument or as a track's `vocoder` field (SDK 1.32.0).
|
|
1741
|
+
|
|
1742
|
+
Cost: a 16-band channel vocoder renders at about 45 ms per audio-second, talkbox about 10 ms (measured on an M-series Mac); renders reuse the source's cached audio, and an edit to the source's pan, reverb, delay or sends does not re-render the vocoder.
|
|
1743
|
+
|
|
1744
|
+
### Autotune
|
|
1745
|
+
|
|
1746
|
+
`/autotune hard` corrects the pitch of a track's audio: every clip and every sampler voice on the track, after the sampler's shift step. One word is enough; the default is `pop`. Presets go from hard to gentle:
|
|
1747
|
+
|
|
1748
|
+
| Preset | Sound |
|
|
1749
|
+
| --------- | --------------------------------------- |
|
|
1750
|
+
| `hard` | instant, stepped notes |
|
|
1751
|
+
| `robot` | stepped on every step of the tuning |
|
|
1752
|
+
| `warble` | hard with wide synthetic vibrato |
|
|
1753
|
+
| `trap` | fast and glossy |
|
|
1754
|
+
| `pop` | polished but sung (the default) |
|
|
1755
|
+
| `natural` | keeps scoops and vibrato |
|
|
1756
|
+
| `gentle` | barely there |
|
|
1757
|
+
| `guided` | notes to a written melody, vibrato kept |
|
|
1758
|
+
| `locked` | hard tune locked to a melody |
|
|
1759
|
+
|
|
1760
|
+
Targets (`to`): `scale` (the default) uses the track or song key and the 0.5 tuning, so maqam, raga and n-EDO tunings work, and with no key it is chromatic; `chromatic` uses every step of the tuning; `chord` follows the chord timeline; `notes` follows another track's notes (`/autotune to notes lead`, a routing edge like a sidechain) or, without `from`, the track's own notes (so `/autotune guided` and `/autotune locked` work in one step; a track with no notes is told which tracks it could follow). A key override that names a maqam or raga (`key D bayati`, `key C yaman`) brings that scale's intonation when neither the song nor the track sets a tuning. The `chord` target leaves out the tuned track, its guide and other vocal or autotuned tracks, so sung pitches never count as chord tones. A note's `drift` expression overrides how much slow drift is removed under it.
|
|
1761
|
+
|
|
1762
|
+
Parameters: `speed` 0..400 ms (retune time, 0 is instant; `0.2s` reads as 200 ms), `relax` 0..1 (slower retune on held notes), `hold` 50..1000 ms (when a note counts as held), `flex` 0..100 (higher only pulls notes already near a target, like Antares Flex-Tune), `glide` 0..500 ms (time between targets; a bare number is ms, `40ms` and `0.04s` also work, as with `/glide`), `amount` 0..1, `vib` 0..12 Hz and `vibmod` 0..1 semitones (added vibrato, on voiced frames only), `center` and `drift` 0..1 (notes targets), `key` (overrides the song key) and `voice` (`auto`, `bass`, `tenor`, `alto`, `soprano`: the tracker's range). Corrections are weighted by voicing, guarded against octave errors, and use hysteresis so vibrato does not flip targets.
|
|
1763
|
+
|
|
1764
|
+
Reach it as `/autotune [preset] [param value …]`, `/autotune off`, `/autotune presets`, `/vocal autotune …`, Ctrl-K › Voice › autotune (rows for every parameter; left/right adjusts, `x` resets, space auditions), the agent tool `autotune_vocal`, and the SDK: `track({ autotune: "hard" })` or `autotune("pop", { speed: 40 })`. `/tune` stays the tuning command; `/tune hard` hints at `/autotune`.
|
|
1765
|
+
|
|
1766
|
+
Rendering is deterministic: tuned spans are cached (128 MB of their own) by the audio's identity (a shifted or fitted sample by its shift or fit key), the settings, the targets and the engine version; with chord or note targets a sampler voice tunes only the frames it plays, and stereo samples keep both channels. In play mode a span longer than 0.25 s plays untuned until its correction is ready; the correction runs in slices so play mode stays responsive. Correction uses the pitch tracker and PSOLA of `/vocal pitch`; a clip is tuned once per placement (its offset and take nudge join the key), and a reversed clip or voice plays untuned. License lines for tuned clips made by `/vocal say` come with `say` in 0.7.1.
|
|
1767
|
+
|
|
1553
1768
|
## Menus
|
|
1554
1769
|
|
|
1555
|
-
`/menu` or `Ctrl-K` (on an empty prompt, in play mode too) opens the edit menu, drawn with the same overlay as the model picker. Every edit the agent can make is reachable from it with keys alone. Each row shows a plain label and the current value with its unit (s, Hz, oct, st, dB, BPM, bars); the line under the list describes the focused row and shows, dimmed, the prompt command the row runs, so the menu teaches the commands. `/menu <section>` opens a section directly (`/menu effects`, `/menu
|
|
1770
|
+
`/menu` or `Ctrl-K` (on an empty prompt, in play mode too) opens the edit menu, drawn with the same overlay as the model picker. Every edit the agent can make is reachable from it with keys alone. Each row shows a plain label and the current value with its unit (s, Hz, oct, st, dB, BPM, bars); the line under the list describes the focused row and shows, dimmed, the prompt command the row runs, so the menu teaches the commands. `/menu <section>` opens a section directly (`/menu effects`, `/menu tuning`); the old names `parameters`, `sounds`, `track`, `automation` and `transport` still work.
|
|
1771
|
+
|
|
1772
|
+
| Section | Rows (most used first) |
|
|
1773
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1774
|
+
| Sound | instrument, preset, the instrument's parameters, **advanced**; **synth filter**; **keys**, **organ**, **guitar** or **granular** when the instrument has them; **track tuning**; **performance** (articulation, glide, bend, vibrato, pedal, humanize); **instruments** (Keys › Electric, Organs · Strings › Bowed · Mallets and bells · Winds and brass · Granular · Wavetable · all instruments · sample packs · use a sample) |
|
|
1775
|
+
| Voice | always shown (on a track without a voice it offers "turn this track into a voice"): **sing** (preset, vowels, throat), **lyrics**, **clips**, **pitch**, **autotune**, **formant**, **vocoder**, **voice presets** |
|
|
1776
|
+
| Effects | Filter, Auto filter, Distortion, Tremolo, Compressor, Chorus, Delay, Reverb, Guitar rig, Shoegaze, **more effects** (dj filter, vowel, bitcrush, phaser, leslie, post gain, …); a "voice effects → Voice" row |
|
|
1777
|
+
| Rhythm | the **euclid editor** (`/euclid`), **grooves** (`/pattern`), **kits** (`/kit`, synth then samples), **grid** |
|
|
1778
|
+
| Chords and key | **key** (tonic, scale), **tuning** (song tuning, reference, root, equal steps, Scala file, ratios, cents, keyboard map), **play** (mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves), **progression**, **idiom** |
|
|
1779
|
+
| Mix | the focused track's name, mute, solo, volume, pan; **all tracks** (choosing one focuses it); **automation** (each lane with its points as `beat N value` rows, add points, ramp, clear lane); **master** |
|
|
1780
|
+
| Arrange | **tracks** (add, focus, rename, move, remove), **sections** (each section's loop, jump, mute, transpose, gain, build, drop, fill, duplicate, move, rename, remove; mark bars, add section), **form**, **style** (find, blend, families) |
|
|
1781
|
+
| Project | play, tempo, beats per bar, **tempo and meter**, loop length, grid, click, count-in bars, calibration, **export** (project file, MIDI, WAV, stems), **resample**, **session** (rename, fork, resume), **agent** (model, show-me, model key), **help and guides** (help, guides, keys) |
|
|
1556
1782
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
| Effects | the core effects (filter, auto filter, distortion, tremolo, compressor, chorus, delay, reverb) with presets and simple parameters, **more effects** (dj filter, vowel, bitcrush, phaser, leslie, post gain, orbit, duck), and **advanced** per effect (see Effects) |
|
|
1561
|
-
| Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples) |
|
|
1562
|
-
| Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style |
|
|
1563
|
-
| Mix & automation | the focused track's name, mute, solo, volume, pan; **all tracks** (choosing one focuses it); **automation**: each `AUTOMATION_LANES` lane with its points as `beat N value` rows, add points, ramp, clear lane; **master**: target, eq, glue, tape, width, limiter |
|
|
1564
|
-
| Project | play, tempo, beats per bar, loop length, grid, click, count-in bars |
|
|
1565
|
-
| Arrange | every section (loop, jump, mute, transpose, gain, build, drop, fill, duplicate, move, rename, unmark, delete), mark bars, add section, form, loop off (see Arrange) |
|
|
1783
|
+
Breadcrumbs in the docs and guides use these labels, written `Ctrl-K › Chords and key › tuning`. `/menu <topic>` opens the same section that `/help <topic>` and `/guide <topic>` explain.
|
|
1784
|
+
|
|
1785
|
+
`/menu <id>` takes one of the ten topic ids (`sound`, `voice`, `effects`, `rhythm`, `chords`, `mix`, `arrange`, `project`, `keys`, `agent`), any topic alias (`drums`, `mixer`, `fx`) or any row name it knows (`tuning`, `performance`, `master`, `export`, `models`, …). `/menu keys` opens the `?` panel. An unknown id answers `no menu "sond" · did you mean /menu sound? · /menu <topic or row>`. In a fader, `x` resets any number row; tempo, meter and loop length apply at once rather than staging, and the hint says `applies at once`. Labels fit 16 columns. Below the root, sibling labels share one case rule. A row's path reads `Ctrl-K › Chords and key › tuning`, and show-me's finish hint uses the same path.
|
|
1566
1786
|
|
|
1567
1787
|
Every list, picker and editor uses the same keys (see **Keys** below). In the menu:
|
|
1568
1788
|
|
|
1569
|
-
| Key
|
|
1570
|
-
|
|
|
1571
|
-
| `↑` `↓` / `k` `j`
|
|
1572
|
-
| `Enter`
|
|
1573
|
-
|
|
|
1574
|
-
| `
|
|
1575
|
-
|
|
|
1576
|
-
|
|
|
1577
|
-
| `
|
|
1578
|
-
| `
|
|
1579
|
-
|
|
|
1789
|
+
| Key | Does |
|
|
1790
|
+
| -------------------- | ---------------------------------------------------------------------------------------- |
|
|
1791
|
+
| `↑` `↓` / `k` `j` | move |
|
|
1792
|
+
| `Enter` | open or confirm: open a section, run an action, open a number's fader, keep staged edits |
|
|
1793
|
+
| `→` / `l` | go in; on a value row, adjust up |
|
|
1794
|
+
| `←` / `h` | back one level; on a value row, adjust down |
|
|
1795
|
+
| `-` `+` | adjust a value by its step (cutoff moves 25%) or cycle a choice |
|
|
1796
|
+
| `Tab` / `Shift-Tab` | next / previous field (in the fader and the rhythm editor) |
|
|
1797
|
+
| `Space` | toggle on/off; elsewhere, hear the focused track (see Previewing changes) |
|
|
1798
|
+
| `0-9` `.` | type a value; `Enter` stages it in the fader drawer, `Esc` cancels |
|
|
1799
|
+
| `/` | filter the current list by name, value or command |
|
|
1800
|
+
| `x` / `d` / `Delete` | reset the focused value to its default; on an automation point, remove it |
|
|
1801
|
+
| `Esc` | revert staged edits, else clear the filter, then back one level, then close |
|
|
1802
|
+
| `?` | the keys for this screen, each key listed once |
|
|
1803
|
+
|
|
1804
|
+
The key sets live in `tui/grammar.ts` (`KEY_UP`, `KEY_DOWN`, `KEY_LEFT`, `KEY_RIGHT`, `KEY_TAB`, `KEY_BACKTAB`, …); the menu, fader, rhythm editor and prompt import them. Play mode's `Tab` is a sustain latch, the one documented exception, because letters are notes there.
|
|
1580
1805
|
|
|
1581
1806
|
Automation rows take `beat:value` pairs (`2:800` or `0:200 4:8000`); a ramp is two pairs, start and end, and the renderer interpolates between points. Turning an effect's first field up switches it on with defaults. Each change runs the command it shows through the normal prompt path, so it is one `ScoreOperation`, one receipt, one undo step, and it syncs to other windows and the project files.
|
|
1582
1807
|
|
|
@@ -1585,7 +1810,7 @@ Automation rows take `beat:value` pairs (`2:800` or `0:200 4:8000`); a ramp is t
|
|
|
1585
1810
|
Editing a number opens a fader drawer: a panel docked directly above the prompt, over the bottom of the piano roll, which stays visible above it. It opens from `Enter` (or a second click) on a number row in the menu, or from a bare parameter at the prompt: `volume`, `pan`, `fx filter` (every filter param, focused on the first number) or `fx reverb mix` (focused on mix). The drawer stacks every param of that device (all of the filter's, or the Mix screen's), so one drawer covers a device.
|
|
1586
1811
|
|
|
1587
1812
|
```text
|
|
1588
|
-
╭─ menu › Effects ›
|
|
1813
|
+
╭─ menu › Effects › filter ──── loop off · A/B: 1 change staged · enter keep · esc revert ─╮
|
|
1589
1814
|
│ type lpf │ hpf │ bpf │
|
|
1590
1815
|
│ │
|
|
1591
1816
|
│ › cutoff 1200 Hz ← 800 Hz 20 Hz … 20000 Hz │
|
|
@@ -1601,36 +1826,38 @@ Every change is staged on the audition loop (see **Previewing changes**), filed
|
|
|
1601
1826
|
| --------------------------- | ----------------------------------------------------- |
|
|
1602
1827
|
| `←` `→` / `-` `+` / `h` `l` | step by the param's step |
|
|
1603
1828
|
| `Shift`-`←` `→` / `{` `}` | coarse step (five steps) |
|
|
1604
|
-
| `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step)
|
|
1829
|
+
| `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step; skips detents) |
|
|
1605
1830
|
| `PgUp` `PgDn` | big step (twenty) |
|
|
1606
1831
|
| `Home` `End` | minimum / maximum |
|
|
1607
|
-
| `
|
|
1608
|
-
| `
|
|
1832
|
+
| `0`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels |
|
|
1833
|
+
| `x` / `d` / `Delete` | back to the default |
|
|
1609
1834
|
| `↑` `↓` / `Tab` `Shift-Tab` | previous / next param of this device |
|
|
1610
1835
|
| `Enter` | keep every staged change (one undo step); none: close |
|
|
1611
1836
|
| `Esc` | revert staged changes and close |
|
|
1612
1837
|
| `Space` `a` `c` | audition loop · A/B · solo ↔ in context |
|
|
1613
1838
|
| `?` | these keys |
|
|
1614
1839
|
|
|
1840
|
+
Detents catch the values people reach for: volume 1 (0 dB), pan 0 (center), whole-number tempo, mix 0, 0.5 and 1, and a filter's octave cutoffs. A drag or step that lands within 2% of the range snaps onto the detent, a key step that jumps over one stops on it, and a fine step skips them. The landing flashes the detent's name for one frame unless `/motion off`. While anything is staged the title reads `A/B: 1 change staged · enter keep · esc revert`, and the badge's key words are click targets.
|
|
1841
|
+
|
|
1615
1842
|
On a choice, `←` `→` move between options and `1`-`9` pick one by number. Sizes: two rows per field (value line, then bar) while the drawer takes at most half the piano roll; one row per field when shorter, as a window that follows the focused field; and at the 8-row terminal minimum a single borderless row with the focused field. The piano roll always keeps at least 40% of its rows above the drawer (at least three).
|
|
1616
1843
|
|
|
1617
1844
|
## Mouse
|
|
1618
1845
|
|
|
1619
1846
|
dawg turns on SGR mouse reporting (modes 1000, 1002 and 1006) and turns it off again on exit, on `SIGTERM`/`SIGHUP`, on a crash, and around external editors. `--no-mouse` or `DAWG_MOUSE=0` (and `TERM=dumb`) leave it off, so the terminal's own selection and scrollback work; a terminal without mouse support ignores the modes and every key still works. Legacy X10 reports are swallowed rather than typed into the prompt.
|
|
1620
1847
|
|
|
1621
|
-
| Where
|
|
1622
|
-
|
|
|
1623
|
-
| fader `[−]` `[+]`
|
|
1624
|
-
| fader bar
|
|
1625
|
-
| fader option
|
|
1626
|
-
| fader name
|
|
1627
|
-
| `
|
|
1628
|
-
| wheel on a fader
|
|
1629
|
-
| list / menu row
|
|
1630
|
-
| wheel on a list
|
|
1631
|
-
| header `▶/⏸ BPM`
|
|
1632
|
-
| header track name
|
|
1633
|
-
| header model
|
|
1848
|
+
| Where | Click / wheel |
|
|
1849
|
+
| ------------------------------- | -------------------------------------------------------------------- |
|
|
1850
|
+
| fader `[−]` `[+]` | step (shift-click: coarse) |
|
|
1851
|
+
| fader bar | set the value at that point; drag to slide it (past the ends clamps) |
|
|
1852
|
+
| fader option | choose it |
|
|
1853
|
+
| fader name | focus that field |
|
|
1854
|
+
| badge `enter keep` `esc revert` | the same as `Enter` / `Esc` |
|
|
1855
|
+
| wheel on a fader | step it (up raises; shift: coarse) |
|
|
1856
|
+
| list / menu row | select it; a click on the selected row opens it (`Enter`) |
|
|
1857
|
+
| wheel on a list | move through it |
|
|
1858
|
+
| header `▶/⏸ BPM` | play / pause |
|
|
1859
|
+
| header track name | the track list (`/tracks`); click a track to focus it |
|
|
1860
|
+
| header model | the model picker |
|
|
1634
1861
|
|
|
1635
1862
|
Hit-testing uses the same paint pass that draws the frame: each painter records its click regions into the frame's `HitMap` (`tui/hits.ts`), so targets never drift from what is on screen. The piano roll does not place notes on click (a note needs pitch, length and velocity that a click does not carry); clicks there are ignored.
|
|
1636
1863
|
|
|
@@ -1638,7 +1865,7 @@ Hit-testing uses the same paint pass that draws the frame: each painter records
|
|
|
1638
1865
|
|
|
1639
1866
|
Hear a sound change before you keep it. In the edit menu (every section: Sound, Effects, Rhythm, Chords, Mix), `Space` starts a short loop of the focused track; `Space` again stops it. The song pauses while the loop plays, so only one thing sounds at a time.
|
|
1640
1867
|
|
|
1641
|
-
The loop is the track's own notes over its loop
|
|
1868
|
+
The loop is the track's own notes over its loop when that is four bars or shorter, otherwise the two bars under the playhead (or the track's first two bars with notes, if those are empty). A track with no notes plays a short phrase by role: a chord for pads and keys, a riff for bass and leads, a groove for kits and drums, and one held note for wavetables so position and envelope changes are audible. It plays solo by default; `c` switches to the whole mix with the track in it.
|
|
1642
1869
|
|
|
1643
1870
|
While the loop plays, each change you make is **staged**, not committed. The loop re-renders only the changed track through the normal renderer and stem cache, in the render worker, and swaps it in within about 100 ms (a reverb mix change re-mixes the cached tail and is heard in about 65 ms). Every swap, here and in the song player, crossfades old to new over 20 ms at the same beat, so changes never click; renders and exports never pass through the crossfade. Held keys are coalesced so only the latest value renders. The menu title shows `●` and `B staged N`, and each changed row shows the staged value beside the committed one (`mix 0.5 ← 0.3`).
|
|
1644
1871
|
|
|
@@ -1656,7 +1883,7 @@ Kept changes are one `ScoreOperation` (`preview.commit`, listing the commands),
|
|
|
1656
1883
|
|
|
1657
1884
|
**The rhythm editor and the chord settings stage too.** `/euclid` and the chord settings (the menu's Chords section, `/menu chords`) use the same loop and keys. In `/euclid`, `Space` loops the drum track; pulses, steps, rotate, typed values, a new row, `off` and `freeze` are staged, the title shows `●` and `B staged N`, and a changed lane shows `E(5,16) ← E(4,16)`. Chord settings are window settings rather than score edits, so while the Chords section is open the loop plays the focused track's chord phrase (two bars of the song key's progression, voiced and performed by the current settings: inversion, spread, bass, sevenths, block/strum/arp, pattern); a staged setting changes the B phrase, and `a` flips back to the committed settings. `Enter` keeps every staged change as one undo step: rhythm edits as one `preview.commit` revision, and chord settings applied at once (a key change, the only one stored in the score, as one revision). `Esc` reverts with nothing written. With the loop off, both screens commit each change at once, as before.
|
|
1658
1885
|
|
|
1659
|
-
**Lists audition on hover.** With the loop on, moving the cursor through a list plays the highlighted item on the loop: the wavetable list (built-in, pack and project tables), instruments,
|
|
1886
|
+
**Lists audition on hover.** With the loop on, moving the cursor through a list plays the highlighted item on the loop: the wavetable list (built-in, pack and project tables), instruments, kits and grooves, and every choice list (filter type, warp mode, presets). It is the browser-preview model of Ableton and Bitwig, applied to the loop you are already hearing. Each move replaces the previous hover, so the staged count stays at one, and fast moves skip straight to the latest item. A pack item that has to be fetched shows `fetching…` in the title; the cursor keeps moving and the item plays once it arrives. `Enter` chooses the item (it stays staged until you keep), `Esc` or `←` leaves the list and drops the hover. The `/kit` and `/pattern` pickers work the same way: `Space` starts the loop, moving hears each kit or groove, `Enter` keeps it, `Esc` cancels.
|
|
1660
1887
|
|
|
1661
1888
|
| Key in a list | While auditioning |
|
|
1662
1889
|
| ------------- | --------------------------------------------------- |
|
|
@@ -1670,7 +1897,7 @@ Kept changes are one `ScoreOperation` (`preview.commit`, listing the commands),
|
|
|
1670
1897
|
**What you see.** While the loop plays, the menu title adds a level meter of the looping track or mix: RMS as an eight-cell bar over -48..0 dBFS, the peak in dB, and `!` in the last cell when the loop clips (`♪ solo · B staged 2 · 64 ms · █████··· -9 dB`; the `ms` is the last key-to-swap time). A focused cutoff row draws its low- or high-pass curve on a log axis, an attack/decay/sustain/release row draws the envelope with the other stages, and the wavetable position row marks its place in the table:
|
|
1671
1898
|
|
|
1672
1899
|
```text
|
|
1673
|
-
│ cutoff (lpf/hpf) or
|
|
1900
|
+
│ cutoff (lpf/hpf) or center (bpf) frequency ▇▇▇▇▇▇▇▇▇▇▇▅▂▁▁▁ › fx filt… │
|
|
1674
1901
|
```
|
|
1675
1902
|
|
|
1676
1903
|
**The agent can listen too.** The `preview_sound {trackId?, changes?, bars?, context?, play?}` tool renders the same loop score for a track, or for candidate sound tool calls (`changes: [{tool: "set_wavetable", args: {...}}]`, any of `set_fx`, `set_synth`, `set_wavetable`, `set_instrument`, `set_sample`, `set_effects`, `set_mix`, `set_automation`, `set_drum_kit`, `use_sound`) applied to a copy of the score. Nothing is committed. It returns RMS and peak dBFS, the spectral centroid and a one-line description for the current and the candidate sound, plus a comparison (`3.0 dB louder, brighter (×2.00 centroid)`). When the window is quiet (no song or audition loop playing) it plays the candidate once, so the agent can say how it sounds before committing with the normal tools. `/try agent off` keeps agent previews silent (numbers only), `/try agent on` turns them back on; `DAWG_AGENT_PREVIEW=off` starts with them off.
|