@hraness/dawg 0.7.0 → 0.8.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 +26 -0
- package/DAWG.md +117 -30
- package/README.md +7 -5
- package/core/diff.ts +2 -0
- package/core/ids.ts +52 -0
- package/core/loop.ts +3 -0
- package/core/range.ts +581 -0
- package/core/score.ts +123 -1
- package/core/sdk/print.ts +11 -2
- package/core/sdk/v1.ts +281 -2
- package/core/sections.ts +198 -19
- package/guides/arrange.md +9 -8
- package/guides/audio.md +31 -0
- package/guides/mix.md +3 -1
- package/guides/panes.md +32 -0
- package/guides/project.md +3 -2
- package/guides/rhythm.md +2 -1
- package/guides/tape.md +32 -0
- package/package.json +2 -1
- package/src/agent/agent.ts +4 -4
- package/src/agent/command-agent.ts +19 -10
- package/src/agent/ops.ts +15 -0
- package/src/agent/planner.ts +12 -0
- package/src/agent/range-tools.ts +216 -0
- package/src/agent/show-me.ts +1 -1
- package/src/agent/tools.ts +2 -0
- package/src/agent/xcb-agent.ts +2 -3
- package/src/audio/arrange.ts +12 -2
- package/src/audio/audio-command.ts +218 -0
- package/src/audio/devices.ts +264 -0
- package/src/audio/doctor.ts +84 -13
- package/src/audio/engine.ts +216 -28
- package/src/commands/arrange.ts +60 -25
- package/src/commands/grammar.ts +3 -1
- package/src/commands/help.ts +187 -9
- package/src/commands/history.ts +122 -11
- package/src/commands/keys.ts +17 -0
- package/src/commands/parses.ts +6 -1
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +2 -6
- package/src/commands/vocal-pitch.ts +2 -1
- package/src/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +18 -0
- package/src/launch-args.ts +105 -1
- package/src/main.ts +1142 -86
- package/src/session/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +196 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/origin.ts +20 -0
- package/src/session/port.ts +87 -10
- package/src/session/protocol.ts +360 -6
- package/src/session/rebase.ts +46 -8
- package/src/session/receipt.ts +7 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +61 -2
- package/src/tui/arrange-menu.ts +169 -1
- package/src/tui/euclid.ts +95 -14
- package/src/tui/fader.ts +55 -2
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- package/src/tui/menu.ts +228 -2
- package/src/tui/play-session.ts +196 -21
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -0
- package/tui/app.ts +217 -44
- package/tui/delight.ts +49 -0
- package/tui/drawer.ts +167 -9
- package/tui/grammar.ts +34 -5
- package/tui/guide.ts +8 -1
- package/tui/highway.ts +40 -5
- package/tui/hints.ts +5 -0
- package/tui/hits.ts +7 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +12 -0
- package/tui/screen.ts +7 -4
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to dawg are recorded here. Versions follow [semantic version
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.8.0
|
|
8
|
+
|
|
9
|
+
Panes and arrangement: several terminals over one session with per-pane undo, ranges you can loop, copy and move, four knobs on every sound page, a TAPE screen with loop recording, one audio output and input, and a UI that holds from 60x16 to very large terminals.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Panes.** Every `dawg` window in a project joins one dawgd session and gets a pane letter (A, B, C, reused when freed); the header shows `3 panes · B`. A second pane may open a track another pane is on (`another pane is on drums too`). `dawg pane <screen> [track]` (`dawg pane play drums`, `dawg pane tape`) opens a pane on a screen, `pane` lists them, and `pin`, `unpin` and `follow <letter|*>` tie a pane's view to another's. Receipts from another pane carry its letter (`B · +16 kick hits`). Live keys from every pane play through dawgd's one engine, so two panes in play mode make one stream and one click.
|
|
14
|
+
- **Per-pane undo.** `undo` / Ctrl-Z steps back this pane's last edit and keeps other panes' later edits; it refuses, naming the pane, when another pane has since changed the same track and property (`pane C changed drums volume since · undo all steps back anyway`). `undo all` and `redo all` keep the global history. Edits travel as score operations, and conflicts are checked per (track, property), so two panes on one track editing different properties both land. Drawer rows and `tracks` rows show which other panes are on a track (`●` while recording).
|
|
15
|
+
- **Network seams.** No network code yet, only what a relay will need: a per-machine actor identity, events stamped with `{actorId, clientId, seq}` and the ops they applied (dawgd drops retried duplicates), a versioned `hello` with capability flags (v1 clients keep working), a transport quantum and per-client clock offset, and new sample voices pinned by sha256.
|
|
16
|
+
- **Range commands.** `loop 5-6` sets the loop range, stored as `score.loop` (`loop: "5-6"` in `song.ts`, SDK 1.34.0) instead of a hidden `loop` section; `loop next` and `loop prev` step it. `copy bass 5-6 to 7 x2` tiles bars (`merge`, `insert`), `move`, `clear` and `reverse` take a track, `all` and a range or section, `copy` without `to` fills a per-window clipboard for `paste at 9`, `bars insert 2 at 3` and `bars remove 3-4` shift later sections, clips, automation, tempo points and the loop, `jump 5.3` moves the playhead, `section split chorus at 13` and `section join chorus` cut and merge sections, and `form print` aliases `form bake`. Each is a typed command, a row under ctrl-k **Arrange › range**, the agent's `edit_range` tool and a /help row; the SDK adds `bars()`, `place()`, `reversed()` and `insertBars()`. Songs saved with the old hidden `loop` section load with it as the loop range.
|
|
17
|
+
- **Four knobs.** The fader drawer opens on four color-coded knobs (blue ● moves, green ▲ sizes, white ■ shapes, orange ◆ level) for the focused instrument, effect, mixer, master, tempo or euclid editor: ↑↓ picks, ←→ turns, ⇧←→ turns coarse, x resets, Tab pages to every parameter. Each knob runs the same typed command as its Ctrl-K row, and the glyphs carry it under `NO_COLOR` and the mono theme. `mix` opens the mixer as a drawer page (one level knob per track); `volume`, `synth` and `knobs [page]` open knob pages; `volume <track> v` and `pan <track> v` target any track.
|
|
18
|
+
- **Header.** Bar.beat and the loop range follow the transport; the pane letter and the pane count sit on the right, and the session name moves to the footer.
|
|
19
|
+
- **TAPE screen.** Ctrl-T, `tape` or Arrange › tape draws every track across the bars with a ruler, playhead, loop brackets, sections (form repeats ×N), a tempo row, a range line and the pane's clipboard chip. c / x / v, delete, `~`, `\`, `< > [ ] { }`, `,` `.`, s / S, h / H and 1-9 / Tab echo and run the matching typed range commands, and the first paste after a cut folds into one move. Its knobs turn the playhead, loop, tempo and volume, and other panes on a track show in the right gutter.
|
|
20
|
+
- **Form on TAPE.** With a form set, Ctrl-T draws the song unrolled in play order: later passes of a section are ghosted `░` (ASCII `~`, dim, never color alone) and labeled `chorus ×2`. A gesture on a ghost edits the source section, and the range line says `edits chorus (plays 2×)`. `form print` answers `printed form to tape · 16 bars`.
|
|
21
|
+
- **Loop recording in play mode.** With a loop set (`loop 5-6`, `\` on TAPE), recording commits once per pass over it instead of once per bar, so each pass is one revision and one Ctrl-Z; an empty pass writes nothing. The card reads `pass 3 · +5 notes` and the play header `↻ 5–6 · pass 3`. Replace erases every bar the pass crossed. `keys record [replace|off]`, r / R in play mode and on TAPE (which enters play mode), Ctrl-K › Arrange › **record the loop** and a /help row. Playing over a form's repeat (a `░` ghost pass on TAPE) records into the source section. With panes, a recording pane is marked `● overdub` in `pane`, and replace refuses while another pane records the same track, naming it (`pane B is recording keys · overdub instead (r)`); a replace pass armed before then lands as an overdub.
|
|
22
|
+
- **TAPE reels and loop flash.** On TAPE the reels `◐◓◑◒` (`|/-\` in ASCII) turn one step a beat while the transport runs, and the loop brackets draw reversed with a `━` fill for a moment as each pass closes: a shape change, so it reads in mono and with `NO_COLOR`. `/motion off` keeps both still.
|
|
23
|
+
- **Audio devices.** One output and one input, from Ctrl-K › Project › audio or `audio`, `audio out <name|default>`, `audio in <name|default>` and `audio test` (a short tone, then a peak meter on the input). Picking an output plays a soft blip on it. The choice is saved per machine in `<config>/audio.json` and every engine follows it while playing; an unplugged output falls back to the system default without stopping and says so once. Choosing a device needs the native sink (SoX takes a named output only); the input is chosen and metered, not recorded.
|
|
24
|
+
- **`dawg doctor` shows the saved audio choice**: `chosen: output … · input …`, flagging a saved device that is no longer connected.
|
|
25
|
+
- **Terminal sizes.** The UI works from 60x16 up; below that a live screen reads `terminal too small · W×H · need ≥ 60×16` while playback, the daemon and the agent keep running (space plays, q quits) and the real UI returns unchanged. Drag-resizes coalesce through the frame gate and keep playback, the menu path, the selected knob, the TAPE range and a streaming reply; a help or guide page scrolled to the end stays at the end. On large terminals the highway, drawer and knob strip use the space while help, guides, menus and the transcript hold a 100-column measure with the song beside them. `bun run sizes` runs a PTY matrix of 19 screens from 20x6 to 500x150.
|
|
26
|
+
- **Docs and show-me.** `/help arrange` is one screen of range lines and `/help panes` lists the pane verbs; new tape and panes guides, an arrange guide rewritten around ranges, glossary rows for range, clipboard and pane, and placeholder hints. Show-me treats `tape`, `knobs`, `mix`, `panes`, `pin`, `follow` and `audio` as window lines the agent never runs, and the command agent writes `loop`, `copy … to` and `move … to`. README and `docs/show-me.md` cover TAPE, ranges, knobs, panes and audio.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **Drum kit knobs.** The kit's sound page now fills all four knobs (humanize timing, reverb mix, cutoff, volume) instead of two.
|
|
31
|
+
- **Installer hint.** The last line of `site/install.sh` now suggests `dawg model key` to connect a model, not the retired `dawg login` alias.
|
|
32
|
+
|
|
7
33
|
## 0.7.0
|
|
8
34
|
|
|
9
35
|
Voice: pitch tracking, formant shift and vowel morph, sung voices and choirs, audio clips with lyrics, vocoder and talkbox, and autotune. Plus a theory-driven style library with a card for every leaf, show-me command lines, faster agent turns with `/model fast`, a native audio sink, and a coherent design pass with one language, one command grammar and one Ctrl-K tree.
|
package/DAWG.md
CHANGED
|
@@ -15,7 +15,7 @@ Run `dawg` from any directory. It creates `.dawg/session` on first use and reuse
|
|
|
15
15
|
|
|
16
16
|
### One command grammar
|
|
17
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`
|
|
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` sets the loop range; it makes no section), `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 (on its four knobs where it is one; see **Four knobs**). The agent tools `set_effects`, `vocode` and `add_drums` still run but are not advertised; use `set_fx`, `set_vocoder` and `set_rhythm`.
|
|
19
19
|
|
|
20
20
|
### Sessions, names and forks
|
|
21
21
|
|
|
@@ -203,7 +203,9 @@ The xcb provider (`src/agent/xcb.ts`, `src/agent/xcb-agent.ts`) calls `xcb --jso
|
|
|
203
203
|
|
|
204
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.
|
|
205
205
|
|
|
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
|
|
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, the audio devices, and the output and input saved from the audio menu (`chosen: output USB Audio Interface · input default (Built-in Microphone)`, `(missing, using default)` when a saved device is gone, and the file it lives in; `chosen` in `--json`). The renderer is deterministic and independently testable; a native or sample-backed instrument backend can replace it behind the same player port.
|
|
207
|
+
Audio devices (`src/audio/devices.ts`, `src/audio/audio-command.ts`). One output and one input, each a device name or the system default; there is no routing matrix. Ctrl-K › Project › audio shows two rows (output and input), each opening the device list (arrows, Enter picks, Esc back); `audio` shows the choice, `audio out <name|default>` and `audio in <name|default>` set it (exact, case-insensitive or a unique prefix of a listed name), and `audio test` plays a short tone on the output, then meters one second of the input (peak dBFS). Picking an output plays a soft blip on it through its own short stream. The choice is saved per machine in `<config>/audio.json` (`~/.config/dawg/audio.json`, or under `DAWG_CONFIG_DIR`), never in the project; `DAWG_AUDIO_DEVICE` still overrides the output. Every engine (the window's, the file-lock fallback's, dawgd's) follows the file: it reads it before each player starts and checks it about once a second while playing, so a pick moves playback at the frame being heard. When the chosen output is gone (unplugged mid-song, or missing at start) the engine opens the system default and says so once (`audio output "<name>" is unavailable · playing on the system default`); the saved choice is kept for the next start. Choosing needs the native sink, which lists and opens devices by name. On SoX, `audio out <name>` sets `AUDIODEV` for `play` but cannot list devices or choose an input; ffplay and afplay always play on the system default, and `audio` says why (with the native sink's unavailable reason). The input is chosen and metered only; recording takes is a later lane.
|
|
208
|
+
|
|
207
209
|
Set `DAWG_AUDIO=0` for headless sessions.
|
|
208
210
|
|
|
209
211
|
Use `DAWG_DEMO=1 bun run src/main.ts` for a deterministic non-interactive frame stream while developing the renderer.
|
|
@@ -1271,19 +1273,19 @@ Rows regenerate when the loop length or meter changes. Editing a generated lane
|
|
|
1271
1273
|
|
|
1272
1274
|
Prompt grammar: `euclid kick 4 16`, `euclid hat 7 16 rotate 2`, `euclid hat swing 0.2 prob 0.8 seed 3` (named fields merge into the existing row, `default` resets one), `euclid snare off|freeze`, `grid snare ....X.......x...`. The agent's `set_rhythm` tool takes the same rows and its prompt prefers it for drums.
|
|
1273
1275
|
|
|
1274
|
-
**Editor.** `/euclid [voice]`, or Rhythm in `/menu`, opens a T-1-style editor on the focused kit or oneshot sampler track: one row per voice with its step grid (`x` hit, `X` accent, `·` rest) and summary (`E(4,16)`). Every change runs one `euclid …` command, so it is one receipt and one undo step, and the edited voice plays once (audition) after it lands. The hits show on the highway like any notes.
|
|
1276
|
+
**Editor.** `/euclid [voice]`, or Rhythm in `/menu`, opens a T-1-style editor on the focused kit or oneshot sampler track: one row per voice with its step grid (`x` hit, `X` accent, `·` rest) and summary (`E(4,16)`). Every change runs one `euclid …` command, so it is one receipt and one undo step, and the edited voice plays once (audition) after it lands. The hits show on the highway like any notes. The bottom row is the four-knob strip (see **Four knobs**; `KNOB_MAPS.euclid`): `●›kick ▲ pulses 4 ■ rotate 0 ◆ velocity 0.8`. `/euclid hat` opens on `▲` pulses; bare `/euclid` on `●`.
|
|
1275
1277
|
|
|
1276
|
-
| Key | Action
|
|
1277
|
-
| --------------------------- |
|
|
1278
|
-
| `↑ ↓` / `j k` |
|
|
1279
|
-
| `← →` / `h l` / `- +` |
|
|
1280
|
-
| `Tab` / `Shift-Tab` (`] [`) | next / previous parameter
|
|
1281
|
-
| digits, `.`, `-`, Backspace | type a value, Enter applies
|
|
1282
|
-
| Enter | add a row for a voice without one; keep (looping)
|
|
1283
|
-
| Space | start or stop the audition loop (staging)
|
|
1284
|
-
| `a` / `c` | A/B / solo ↔ in context, while the loop plays
|
|
1285
|
-
| `x` / Delete, `f` | remove the row and its notes / freeze it to notes
|
|
1286
|
-
| Esc | cancel typing, revert staged changes, then close
|
|
1278
|
+
| Key | Action |
|
|
1279
|
+
| --------------------------- | ----------------------------------------------------------- |
|
|
1280
|
+
| `↑ ↓` / `j k` | pick a knob: `●` drum, `▲` pulses, `■` rotate, `◆` velocity |
|
|
1281
|
+
| `← →` / `h l` / `- +` | turn it (`●` moves to the next or previous drum row) |
|
|
1282
|
+
| `Tab` / `Shift-Tab` (`] [`) | next / previous parameter, every field |
|
|
1283
|
+
| digits, `.`, `-`, Backspace | type a value, Enter applies |
|
|
1284
|
+
| Enter | add a row for a voice without one; keep (looping) |
|
|
1285
|
+
| Space | start or stop the audition loop (staging) |
|
|
1286
|
+
| `a` / `c` | A/B / solo ↔ in context, while the loop plays |
|
|
1287
|
+
| `x` / Delete, `f` | remove the row and its notes / freeze it to notes |
|
|
1288
|
+
| Esc | cancel typing, revert staged changes, then close |
|
|
1287
1289
|
|
|
1288
1290
|
## Chords
|
|
1289
1291
|
|
|
@@ -1472,6 +1474,8 @@ Terminals send key presses and auto-repeats, never key releases, so held notes a
|
|
|
1472
1474
|
|
|
1473
1475
|
Recording: with record armed and the transport running, each note is quantized to the grid (`/grid 1/16` by default; `1/4 1/8 1/8T 1/16 1/16T 1/32`), wrapped into the loop, and appended to the focused track as `addNote` operations when the playhead leaves the bar, so each recorded bar is one revision: one `Ctrl-Z` undoes a bar, other windows and the project files see it like any edit. Stopping commits the rest. The same pitch on the same step twice is one note. Replace removes the bar's earlier notes in the same revision. No agent and no network are involved.
|
|
1474
1476
|
|
|
1477
|
+
**Loop recording.** With a loop set (`loop 5-6`, `\` on TAPE), recording commits once per pass instead of once per bar: each wrap is one revision and one `Ctrl-Z`, the card reads `pass 3 · +5 notes`, the header shows `↻ 5–6 · pass 3`, and a pass that played nothing writes nothing. `keys record` arms it (`keys record replace` replaces, `keys record off` disarms), opening PLAY on the focused track first; on TAPE `r` and `R` run those, and `Esc` goes back to TAPE. Replace erases only the bars each pass crossed, inside the loop. Without a loop, playing over a form's repeat (a `░` ghost pass on TAPE) records into the source section's bars, one revision a bar. Live notes sound through the shared daemon engine, so every pane hears them, and each carries its beat. Other panes see the recording pane as `C●`; while another pane is recording a track, replace on it is refused and names that pane (`✗ pane B is recording bass · overdub instead (r)`), and a pass already armed for replace lands as an overdub. On TAPE the reels `◐◓◑◒` turn one step a beat while the transport runs (`|/-\` in ASCII), and as each pass closes the loop brackets draw reversed with the fill `━` for a moment, a shape change rather than a color; `/motion off` keeps both still.
|
|
1478
|
+
|
|
1475
1479
|
## Click track
|
|
1476
1480
|
|
|
1477
1481
|
`/click on|off|<volume>` (`/click 40%`, `/click 0.4`) or `M` in play mode. An accented downbeat and lighter beats at the transport tempo and the score's meter, mixed as a separate monitoring bus. It is never part of a loop render, a stem, `dawg render`, or `/export`; tests compare those byte for byte with the click on. `/count-in 0|1|2` sets how many bars of click play before recording starts (default 1); the header counts down and flashes the beat, so it also works with backend `none`.
|
|
@@ -1577,7 +1581,36 @@ Generators write ordinary notes, tracks and automation, so everything they make
|
|
|
1577
1581
|
|
|
1578
1582
|
Section mutes and variations govern every bar of their section: a note held from an earlier section stops at the bar where a section that mutes its track begins, so a song sounds the same played straight through and through the form. Generators place their cut and crash by bar order in the score; with a form that reorders sections, run them on the bars that precede the target in the form (`build 13-16`, `fill at 17`).
|
|
1579
1583
|
|
|
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",
|
|
1584
|
+
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", loop: "chorus" })` (SDK 1.18.0; `loopSection: "chorus"` still reads).
|
|
1585
|
+
|
|
1586
|
+
## Ranges (loop, copy, move, bars)
|
|
1587
|
+
|
|
1588
|
+
The **loop range** is the bars playback cycles. `loop 5-6` sets it, `loop chorus` loops a section, `loop next` and `loop prev` step it one length along, and `loop off` plays the song again. A loop range is not a section: it never shows on the arrangement strip or in the form, and it is saved as `score.loop` (`loop: "5-6"` in `song.ts`, SDK 1.34.0). Setting one clears a looped section and the other way round. Export ignores it, like the section loop. Songs saved by 0.7 with a hidden section named `loop` load with that section turned into the loop range.
|
|
1589
|
+
|
|
1590
|
+
Range commands take bars as the ruler counts them (from 1): `5-6`, `5`, or a section name. Without a range they act on the loop range, else the section under the playhead, else the bar under it, which is what the keyed gestures use. `<track>` is a track id or name, `all` is every track, and a bare command acts on the focused track. Each command is one undo step and one diff operation set, so other windows and the agent see the edit, not a new score.
|
|
1591
|
+
|
|
1592
|
+
| Command | Does |
|
|
1593
|
+
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1594
|
+
| `loop <a>-<b>` / `loop <section>` / `loop next` / `loop prev` / `loop off` | set, step or clear the loop range |
|
|
1595
|
+
| `copy [<track>\|all] <a>-<b> to <bar> [x<N>] [insert\|merge]` | copy the notes, automation and clips in those bars to `<bar>`, replacing what is there; `x2` tiles it end to end, `merge` keeps what is there, `insert` shifts later music right |
|
|
1596
|
+
| `move [<track>\|all] <a>-<b> to <bar> [insert]` | copy, then empty the source |
|
|
1597
|
+
| `clear [<track>\|all] <a>-<b>` | empty the bars; the bars stay (bare `clear` still empties the focused track) |
|
|
1598
|
+
| `copy [<track>\|all] <a>-<b>` then `paste [at <bar>] [x<N>] [insert\|merge]` | the clipboard: `copy` without `to` fills it, `paste` lays it down at the playhead's bar or `<bar>`; it is per window and not saved |
|
|
1599
|
+
| `reverse [<track>\|all] <a>-<b>` | mirror the bars in time |
|
|
1600
|
+
| `bars insert <n> at <bar>` / `bars remove <a>-<b>` | add empty bars before `<bar>`, or cut bars out; later sections, clips, automation, tempo and meter points and the loop range move with the music |
|
|
1601
|
+
| `jump <bar>[.<beat>]` / `jump <section>` | move the playhead |
|
|
1602
|
+
| `section split <name> at <bar>` / `section join <name>` | cut a section in two (the second half is `<name> 2`), or merge it with the section after it |
|
|
1603
|
+
| `form print` | the form written out as plain bars (`form bake`) |
|
|
1604
|
+
|
|
1605
|
+
Copy, move, clear and reverse never touch tempo or meter points (except that `insert` opens new bars first); `bars insert` and `bars remove` move them. The menu's **Arrange › range** runs the same commands (loop bars, loop next and prev, copy, move, clear, reverse, paste at, insert and remove bars), the agent's `edit_range` tool types them, and show-me can type any of them. In `song.ts`, `bars(notes, 5, 6)` takes the notes in bars 5-6 starting at 0, `place(notes, { at: 7, times: 3 })` lays them at bar 7 three times, `reversed(notes, { bars: 2 })` mirrors them, and `insertBars(song, { at: 7, bars: 2 })` inserts bars into a whole song (SDK 1.34.0).
|
|
1606
|
+
|
|
1607
|
+
## TAPE (Ctrl-T)
|
|
1608
|
+
|
|
1609
|
+
`Ctrl-T` or `/tape` opens TAPE in place of the highway: every track as a row across the bars, each cell a density glyph (` ▁▂▃▄▅▆▇█`, the notes starting there weighted by velocity; a held note `▁`, an audio clip `▃`, empty `·`). Above the rows sit the bar ruler with the playhead `▼` and the loop brackets `[══]` read from `score.loop`, and the sections (`▀name▀`, the looped one reversed). The focused row carries `›`, a muted row is dim, and color is never the only cue (NO_COLOR and the mono theme keep every glyph). `-` and `=` zoom between a bar, a beat and half a beat a cell, and the view scrolls to keep the playhead in sight. The highway stays home: `Esc` or `Ctrl-T` again goes back, and `dawg pane tape` opens a pane on it.
|
|
1610
|
+
|
|
1611
|
+
The footer is the range line (`range: bass · bars 5–6 (loop)`, the loop range, else the section, else the bar under the playhead) with the clipboard chip, the four knobs (● playhead, ▲ loop length, ■ tempo, ◆ volume, from `tui/knobs.ts`; `↑` `↓` pick, `←` `→` turn, `⇧` coarse, `Enter` opens the drawer) and the key hints. Every key echoes and runs a typed command, so TAPE teaches the range commands above: `c` (`C` all tracks) is `copy bass 5-6`, `x` is `copy` then `clear`, `v` pastes at the playhead's bar (`V` inserts), `delete` clears, `~` reverses, `\` loops the section here (again: off), `<` `>` slide the loop, `[` `]` `{` `}` move its end and start, `,` `.` jump between sections, `s` `S` split and join the section, `h` `H` mute and solo, and `1`-`9` or `Tab` focus a track row (number keys pick tracks on TAPE, not knobs). The first paste after a cut lands as one `move`, one revision. The clipboard is per pane and kept in `.dawg/clipboard/` next to the session, so it survives a restart. With the mouse, a click on the ruler jumps, a drag on it sets the loop, a click on a row focuses it and a drag along a row sets the loop over those bars; the wheel walks the playhead a bar at a time. Other panes on a track show in the right gutter (`B`, `C●` while that pane records). Score edits commit like any other command, so other windows and the agent see them.
|
|
1612
|
+
|
|
1613
|
+
With a form set (`form verse chorus*2`), TAPE draws the song unrolled, in the order it plays: the first pass of each section is solid and every later pass is a ghost, shaded `░` (`~` without Unicode) and dimmed, with the section row labeled `chorus ×2`. Each column still names its score bar, so a key, click or drag on a ghost acts on the source section, and the range line says so (`range: bass · bars 5–8 (chorus) · edits chorus (plays 2×)`). The playhead follows the pass that is playing; bars the form never plays sit after it. `form print` (or `form bake`) writes the form out as plain bars: `printed form to tape · 16 bars`.
|
|
1581
1614
|
|
|
1582
1615
|
## Styles
|
|
1583
1616
|
|
|
@@ -1776,7 +1809,7 @@ Rendering is deterministic: tuned spans are cached (128 MB of their own) by the
|
|
|
1776
1809
|
| 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
1810
|
| Rhythm | the **euclid editor** (`/euclid`), **grooves** (`/pattern`), **kits** (`/kit`, synth then samples), **grid** |
|
|
1778
1811
|
| 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**
|
|
1812
|
+
| Mix | the focused track's name, mute, solo, volume, pan; **mixer** (every track's level, the drawer's `mix` page); **all tracks** (choosing one focuses it); **automation** (each lane with its points as `beat N value` rows, add points, ramp, clear lane); **master** |
|
|
1780
1813
|
| 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
1814
|
| 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) |
|
|
1782
1815
|
|
|
@@ -1822,20 +1855,53 @@ Each field shows its name, current value with unit, the committed value while a
|
|
|
1822
1855
|
|
|
1823
1856
|
Every change is staged on the audition loop (see **Previewing changes**), filed under its field so repeated nudges replace one staged edit; the piano roll and the loop, when it plays, follow at once. `Enter` keeps everything staged as one revision and one undo step; `Esc` reverts. A setting the loop cannot stage (tempo, loop length) applies directly.
|
|
1824
1857
|
|
|
1825
|
-
| Key | In the drawer
|
|
1826
|
-
| --------------------------- |
|
|
1827
|
-
| `←` `→` / `-` `+` / `h` `l` | step by the param's step
|
|
1828
|
-
| `Shift`-`←` `→` / `{` `}` | coarse step (five steps)
|
|
1829
|
-
| `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step; skips detents)
|
|
1830
|
-
| `PgUp` `PgDn` | big step (twenty)
|
|
1831
|
-
| `Home` `End` | minimum / maximum
|
|
1832
|
-
| `0`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels
|
|
1833
|
-
| `x` / `d` / `Delete` | back to the default
|
|
1834
|
-
| `↑` `↓` / `Tab` `Shift-Tab` | previous / next param of this device
|
|
1835
|
-
| `Enter` | keep every staged change (one undo step); none: close
|
|
1836
|
-
| `Esc` | revert staged changes and close
|
|
1837
|
-
| `Space` `a` `c` | audition loop · A/B · solo ↔ in context
|
|
1838
|
-
| `?` | these keys
|
|
1858
|
+
| Key | In the drawer |
|
|
1859
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
1860
|
+
| `←` `→` / `-` `+` / `h` `l` | step by the param's step |
|
|
1861
|
+
| `Shift`-`←` `→` / `{` `}` | coarse step (five steps) |
|
|
1862
|
+
| `[` `]` / `Alt`-`←` `→` | fine step (a tenth of a step; skips detents) |
|
|
1863
|
+
| `PgUp` `PgDn` | big step (twenty) |
|
|
1864
|
+
| `Home` `End` | minimum / maximum |
|
|
1865
|
+
| `0`-`9` `.` | type an exact value; `Enter` sets it, `Esc` cancels |
|
|
1866
|
+
| `x` / `d` / `Delete` | back to the default |
|
|
1867
|
+
| `↑` `↓` / `Tab` `Shift-Tab` | previous / next param of this device (on a knob page `↑` `↓` pick a knob and `Tab` pages knobs ↔ every param) |
|
|
1868
|
+
| `Enter` | keep every staged change (one undo step); none: close |
|
|
1869
|
+
| `Esc` | revert staged changes and close |
|
|
1870
|
+
| `Space` `a` `c` | audition loop · A/B · solo ↔ in context |
|
|
1871
|
+
| `?` | these keys |
|
|
1872
|
+
|
|
1873
|
+
### Four knobs
|
|
1874
|
+
|
|
1875
|
+
Where a level has them, the drawer opens on four knobs first: the OP-1's four color encoders, the same four on every screen.
|
|
1876
|
+
|
|
1877
|
+
| Knob | Color | Means | Examples |
|
|
1878
|
+
| ---- | ------ | ------ | -------------------------------------------- |
|
|
1879
|
+
| `●` | blue | moves | preset, position, pan, filter type, tempo |
|
|
1880
|
+
| `▲` | green | sizes | attack, decay, length, reverb send, feedback |
|
|
1881
|
+
| `■` | white | shapes | filter, tone, brightness, cutoff |
|
|
1882
|
+
| `◆` | orange | level | track volume, effect mix, loudness target |
|
|
1883
|
+
|
|
1884
|
+
```text
|
|
1885
|
+
╭─ menu › Sound ───────────────────────────────────────────────────────────────╮
|
|
1886
|
+
│ ●›preset pad │ lead │ pluck │ bass │ sub │ acid │ keys │ bell │ › │
|
|
1887
|
+
│ ▲ attack 0.003 s [−] ───────────────────────────────── [+] │
|
|
1888
|
+
│ ■ synth filter off [−] ───────────────────────────────── [+] │
|
|
1889
|
+
│ ◆ volume 1 · 0.0 dB [−] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━● [+] │
|
|
1890
|
+
╰─────────── ↑↓ knob · ←→ turn · tab all · enter keep · ⇧ coarse · esc revert ─╯
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
The shape glyph always goes with the color, so `NO_COLOR` and the mono theme keep every cue; without Unicode the glyphs are `o ^ # *`. `↑` `↓` pick a knob (an empty slot, drawn `·`, is skipped), `←` `→` turn it, `Shift` turns it coarse and `x` resets it. `Tab` pages to every param of the level (today's drawer) and back. Every turn runs the row's own command (`synth cutoff 1760`, `fx reverb mix 0.3`, `volume 0.8`), so the receipt teaches the words and show-me types the same lines.
|
|
1894
|
+
|
|
1895
|
+
The drawer has knobs on Sound (by engine: synth, wavetable, piano, electric, organ, string, wind, modal, sing, granular, sampler, kit), Mix, Mix › master, Project (tempo, loop length, beats per bar) and each effect. Which row each knob turns is one table, `src/tui/knob-map.ts` (`KNOB_MAPS`): four Ctrl-K paths per page, checked against every instrument family by a test, so a patch's macros can later map onto the same four knobs by adding rows. An effect without a row takes its first three number params and its mix.
|
|
1896
|
+
|
|
1897
|
+
| Command | Opens |
|
|
1898
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
1899
|
+
| `synth` | the focused track's sound knobs (and the synth summary receipt) |
|
|
1900
|
+
| `knobs [sound\|mix\|master\|tempo\|fx <effect>]` | that page's four knobs (bare: sound) |
|
|
1901
|
+
| `volume` / `pan` / `fx reverb mix` | the level's knobs, on that knob (a param that is not a knob opens every param) |
|
|
1902
|
+
| `mix` | the mixer page: every track's level as an orange fader; `Tab` flips to this track's knobs |
|
|
1903
|
+
|
|
1904
|
+
The mixer page is also `Ctrl-K › Mix › mixer`. Each row runs `volume <track> <0..1>` (`volume drums 0.5`, and `pan <track> <-1..1>`), which sets that track without moving the focus.
|
|
1839
1905
|
|
|
1840
1906
|
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
1907
|
|
|
@@ -1861,6 +1927,27 @@ dawg turns on SGR mouse reporting (modes 1000, 1002 and 1006) and turns it off a
|
|
|
1861
1927
|
|
|
1862
1928
|
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.
|
|
1863
1929
|
|
|
1930
|
+
## Terminal sizes
|
|
1931
|
+
|
|
1932
|
+
80x24 is the design target; the UI also works from **60x16** up to as large as the terminal goes, and redraws cleanly on every resize.
|
|
1933
|
+
|
|
1934
|
+
**Minimum.** Below 60 columns the play-mode key strip and the header's bar position drop out and the prompt's spend line clips; below 16 rows the prompt loses its footer and the drawer its last knob. So under 60x16 dawg shows `terminal too small · W×H · need ≥ 60×16`, centered, updating as you resize (ASCII without unicode). Playback, the daemon and the agent keep running; `ctrl-c` and `q` quit, `space` plays or pauses, and every other key is ignored, so nothing changes until the real UI returns, on the first frame the size allows. A 1x1 or 0-column terminal draws nothing and never throws.
|
|
1935
|
+
|
|
1936
|
+
**Resize.** `SIGWINCH` bursts from a drag coalesce into at most one frame per 33 ms plus a trailing one (72 signals measured as about 16 repaints). Each size change clears the screen and resets the frame diff, so no stale cells survive. Focus, the selected knob, the menu path, drawer state, the loop and the playhead view are model state and are untouched; scroll positions re-clamp to the new page, and a panel scrolled to its end (the transcript, a guide) stays pinned to the end. Agent streaming and show-me typing carry on through it.
|
|
1937
|
+
|
|
1938
|
+
**Large terminals.** Data views use the space, text holds a measure, and no glyph is stretched past legibility:
|
|
1939
|
+
|
|
1940
|
+
| View | On a large terminal |
|
|
1941
|
+
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1942
|
+
| highway | lanes stop at 10 columns; a wider highway shows more pitches (one to three octaves) and centers the rest; a taller one looks further ahead (at most 4 rows per beat) |
|
|
1943
|
+
| TAPE | more bars across and every track down, until the song runs out |
|
|
1944
|
+
| drawer (knobs, mixer) | rows stop at 96 columns; a wide drawer lists every param of the page beside the four knobs (what Tab shows); a taller drawer fits more mixer tracks |
|
|
1945
|
+
| knob strip | each slot at most 40 columns, left-aligned |
|
|
1946
|
+
| help, guides, menus, transcript, `?` keys | a panel at most 100 columns of text, left-aligned; with 36 or more columns free, the song keeps drawing beside it |
|
|
1947
|
+
| prompt, header, footer, cards | full width (one line each; long text truncates with `…`) |
|
|
1948
|
+
|
|
1949
|
+
A steady frame (compose plus encode) stays under 16 ms at 500x150 (measured 2 to 3 ms; text measurement skips grapheme segmentation for ASCII and caches the rest). `bun run sizes` walks every screen through the full size matrix and flags a frame over budget, a wrapped or scrolled frame, a lost header, footer, screen or focus, and a text panel past its measure; `bun run check` runs a sample of it.
|
|
1950
|
+
|
|
1864
1951
|
## Previewing changes
|
|
1865
1952
|
|
|
1866
1953
|
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.
|
package/README.md
CHANGED
|
@@ -15,17 +15,17 @@ curl -fsSL https://dawg.sh/install | sh
|
|
|
15
15
|
or install the release tarball from GitHub directly:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
bun add -g https://github.com/hraness/dawg/releases/download/v0.
|
|
18
|
+
bun add -g https://github.com/hraness/dawg/releases/download/v0.8.0/hraness-dawg-0.8.0.tgz
|
|
19
19
|
dawg --help
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Each [release](https://github.com/hraness/dawg/releases) is immutable and ships the tarball, a `SHA256SUMS` file and a build provenance attestation. To check a download before installing it:
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
gh release download v0.
|
|
25
|
+
gh release download v0.8.0 --repo hraness/dawg
|
|
26
26
|
shasum -a 256 -c SHA256SUMS
|
|
27
|
-
gh attestation verify hraness-dawg-0.
|
|
28
|
-
bun add -g "$PWD/hraness-dawg-0.
|
|
27
|
+
gh attestation verify hraness-dawg-0.8.0.tgz --repo hraness/dawg
|
|
28
|
+
bun add -g "$PWD/hraness-dawg-0.8.0.tgz"
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
dawg is also on npm as [`@hraness/dawg`](https://www.npmjs.com/package/@hraness/dawg):
|
|
@@ -113,7 +113,7 @@ duration note <id> 0.25
|
|
|
113
113
|
|
|
114
114
|
A track named `drums` (or `kit`) starts with the `kit` instrument; `instrument kit` turns any track into a drum track. Drums are `kick` (`bd`), `snare` (`sd`), `clap` (`cp`), `rim` (`perc`), `tom`, `hat` (`hh`), and `openhat` (`oh`); the highway shows one lane per voice. `pattern <voice> <beats...>` takes up to 64 beats, `pattern <voice> every <step>` (step ≥ 0.125) fills the loop, `vel <0..1>` sets velocity, and `clear <voice>` removes only that voice. `filter <hz> [resonance]` is a per-track low-pass (20–20000 Hz, resonance 0–1), `delay <beats> [feedback] [mix]` is a tempo-synced stereo ping-pong echo send (0.0625–4 beats, feedback ≤ 0.9, mix 0–1), `reverb <mix> [size]` is an algorithmic stereo reverb send (mix 0–1, size 0–1, default 0.5; `reverb off` removes it), and `automate filter|resonance|delay-feedback|delay-mix at <beat> <value>` writes the cutoff (Hz), resonance (0–1), delay feedback (0–0.9) and delay mix (0–1) lanes. `solo` isolates the focused track in playback across every window; `redo` re-applies the last undone edit.
|
|
115
115
|
|
|
116
|
-
The screen has four parts. A one-line header shows `dawg` · track · ▶/⏸ BPM · key · session · window count (when more than one) on the left and model · rev · sync state on the right; narrower terminals drop the least important segments first. The highway overlays every unmuted track, each in its own stable accent, with the focused track bright and the others dimmed; `/view focus` shows only the focused track and `/view all` (the default) brings the rest back. A track with no hits shows `main · empty · add C4 at 0 to start` in place of lane labels. Below it, an activity strip shows operation cards (`✓ +8 bass notes · rev 41→42 · ^z undo`; the undo hint accompanies the first three score edits), queue depth, a braille spinner while the agent works, and errors in red with an `✗` prefix in one shape, `<what> · <why> · <next step>` (`no such file · nope.json`). The prompt panel is filled with a background color. It wraps by grapheme, grows from 1 to 8 rows (capped at 30% of the screen, then scrolls internally) and keeps the draft when the terminal is resized. Narrow terminals collapse the header and hints
|
|
116
|
+
The screen has four parts. A one-line header shows `dawg` · track · ▶/⏸ BPM · key · session · window count (when more than one) on the left and model · rev · sync state on the right; narrower terminals drop the least important segments first. The highway overlays every unmuted track, each in its own stable accent, with the focused track bright and the others dimmed; `/view focus` shows only the focused track and `/view all` (the default) brings the rest back. A track with no hits shows `main · empty · add C4 at 0 to start` in place of lane labels. Below it, an activity strip shows operation cards (`✓ +8 bass notes · rev 41→42 · ^z undo`; the undo hint accompanies the first three score edits), queue depth, a braille spinner while the agent works, and errors in red with an `✗` prefix in one shape, `<what> · <why> · <next step>` (`no such file · nope.json`). The prompt panel is filled with a background color. It wraps by grapheme, grows from 1 to 8 rows (capped at 30% of the screen, then scrolls internally) and keeps the draft when the terminal is resized. Narrow terminals collapse the header and hints. Below 60×16 the screen shows `terminal too small · W×H · need ≥ 60×16`, centered and updating as you resize; playback, the daemon and the agent keep going, space plays and stops, q and ctrl-c quit, other keys are dropped, and the real UI comes back unchanged once the terminal is big enough.
|
|
117
117
|
|
|
118
118
|
| Key | Action |
|
|
119
119
|
| --------------------- | ---------------------------------------------------------------------------- |
|
|
@@ -128,6 +128,8 @@ The screen has four parts. A one-line header shows `dawg` · track · ▶/⏸ BP
|
|
|
128
128
|
| Ctrl-L | full redraw |
|
|
129
129
|
| Ctrl-C | exit |
|
|
130
130
|
|
|
131
|
+
Ctrl-T opens TAPE: every track across the bars, one row each, with sections and the loop bracket on top. Each key runs one typed command and echoes it on the prompt (`c` copies the range, `v` pastes it, `\` loops the section here), so the screen teaches the typed form. The same edits as commands: `loop 5-6`, `copy bass 5-6 to 7 x2`, `move bass 5-6 to 9`, `clear bass 5-6`, `reverse bass 5-6`, `bars insert 2 at 9`, `jump 5.3`, `section split chorus at 13`, `form print`. `knobs` turns four knobs (● ▲ ■ ◆) on the sound, `mix`, master, tempo or an effect. Several terminals can share one session as panes: `panes` lists them, `pane tape` turns this one into a tape pane, `pin` keeps the drawer on one parameter and `follow b` mirrors pane B. `undo` undoes this pane's last edit; `undo all` anyone's. `audio out <name>` and `audio in <name>` pick devices on this machine (`audio test` plays a tone; `dawg doctor` shows the choice). `/help arrange`, `/help panes` and `/guide tape` teach each of these.
|
|
132
|
+
|
|
131
133
|
A NOW send runs ahead of work waiting as NEXT. Bracketed paste preserves multiline input.
|
|
132
134
|
|
|
133
135
|
`/theme default|high-contrast|mono` and `--theme <name>` (or `DAWG_THEME`) pick a theme. Semantic color tokens map to truecolor, 256, 16 or no color. `NO_COLOR` and `TERM=dumb` force monochrome. `/motion off`, `--reduce-motion` or `DAWG_REDUCE_MOTION=1` replace animations with static states in the same positions. Every color has a non-color cue as well: glyph density, `✓`/`✗`/`!` prefixes, the mode pill text and `▶`/`⏸`.
|
package/core/diff.ts
CHANGED
|
@@ -257,6 +257,8 @@ function songOperations(a: TrackScore, b: TrackScore): ScoreOperation[] {
|
|
|
257
257
|
if (!deepEqual(song.style, b.style))
|
|
258
258
|
emit({ type: "setStyle", style: b.style ?? null });
|
|
259
259
|
if (sectionsDiffer()) setSections();
|
|
260
|
+
if (!deepEqual(song.loop, b.loop))
|
|
261
|
+
emit({ type: "setLoop", loop: b.loop ?? null });
|
|
260
262
|
return ops;
|
|
261
263
|
}
|
|
262
264
|
|
package/core/ids.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collision-free ids for things two writers can create at once (notes from
|
|
3
|
+
* agent tools, resamples, guide tracks). A readable prefix keeps receipts
|
|
4
|
+
* legible; the suffix is random and salted with this machine's actor so two
|
|
5
|
+
* actors never draw the same id even without an authority to serialise them.
|
|
6
|
+
*
|
|
7
|
+
* Track ids and patcher node ids stay readable and are allocated by the
|
|
8
|
+
* authority (dawgd's `claimTrack`), never chosen here.
|
|
9
|
+
*/
|
|
10
|
+
import { SCORE_LIMITS } from "./score.ts";
|
|
11
|
+
|
|
12
|
+
const ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
13
|
+
const SALT_LENGTH = 3;
|
|
14
|
+
const RANDOM_LENGTH = 8;
|
|
15
|
+
|
|
16
|
+
let salt = "";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set once per process from the actor id (src/identity/actor.ts). Tests and
|
|
20
|
+
* one-shot tools that never set it still get random ids.
|
|
21
|
+
*/
|
|
22
|
+
export function setIdSalt(actorId: string): void {
|
|
23
|
+
const clean = actorId.replace(/^a_/, "").toLowerCase();
|
|
24
|
+
salt = [...clean]
|
|
25
|
+
.filter((char) => ALPHABET.includes(char))
|
|
26
|
+
.slice(0, SALT_LENGTH)
|
|
27
|
+
.join("");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function idSalt(): string {
|
|
31
|
+
return salt;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `count` characters of base32 from the platform CSPRNG. */
|
|
35
|
+
export function randomBase32(count: number): string {
|
|
36
|
+
const bytes = new Uint8Array(count);
|
|
37
|
+
crypto.getRandomValues(bytes);
|
|
38
|
+
let out = "";
|
|
39
|
+
for (const byte of bytes) out += ALPHABET[byte & 31];
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `<prefix>-<salt><random>`, bounded to a score id. The prefix is trimmed so
|
|
45
|
+
* the random part always survives.
|
|
46
|
+
*/
|
|
47
|
+
export function newId(prefix: string): string {
|
|
48
|
+
const suffix = `${salt}${randomBase32(RANDOM_LENGTH)}`;
|
|
49
|
+
const room = SCORE_LIMITS.maxIdLength - suffix.length - 1;
|
|
50
|
+
const head = prefix.slice(0, Math.max(0, room)).replace(/-+$/, "");
|
|
51
|
+
return head.length > 0 ? `${head}-${suffix}` : suffix;
|
|
52
|
+
}
|
package/core/loop.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type TrackLoopV1 = Readonly<{
|
|
|
37
37
|
sections?: readonly Section[];
|
|
38
38
|
/** Song form (0.5); omitted when empty. */
|
|
39
39
|
form?: readonly FormEntry[];
|
|
40
|
+
/** The loop range (0.8), 0-based; absent plays the song. */
|
|
41
|
+
loop?: Readonly<{ startBar: number; bars: number }>;
|
|
40
42
|
}>;
|
|
41
43
|
|
|
42
44
|
/** Return the stable object form used by files and IPC messages. */
|
|
@@ -62,6 +64,7 @@ export function encodeLoopDocument(score: TrackScore): TrackLoopV1 {
|
|
|
62
64
|
...(score.loopSection === undefined
|
|
63
65
|
? {}
|
|
64
66
|
: { loopSection: score.loopSection }),
|
|
67
|
+
...(score.loop ? { loop: score.loop } : {}),
|
|
65
68
|
...(score.calibration ? { calibration: score.calibration } : {}),
|
|
66
69
|
});
|
|
67
70
|
}
|