@hraness/dawg 0.5.0 → 0.6.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 +29 -0
- package/DAWG.md +269 -13
- package/README.md +4 -4
- package/core/diff.ts +4 -0
- package/core/fx.ts +437 -2
- package/core/granular.ts +528 -0
- package/core/instruments.ts +281 -0
- package/core/keys.ts +386 -0
- package/core/resonators.ts +574 -0
- package/core/score.ts +181 -6
- package/core/sdk/eval-child.ts +2 -0
- package/core/sdk/print.ts +169 -13
- package/core/sdk/sync-instruments.ts +58 -0
- package/core/sdk/v1.ts +1114 -21
- package/core/sections.ts +34 -8
- package/core/strings.ts +845 -0
- package/guides/effects.md +3 -1
- package/guides/sounds.md +4 -0
- package/guides/tempo.md +1 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +7 -0
- package/src/agent/granular-tools.ts +138 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +39 -2
- package/src/agent/preview-tool.ts +6 -1
- package/src/agent/tools.ts +538 -14
- package/src/agent/xcb-agent.ts +7 -0
- package/src/audio/arrange.ts +21 -3
- package/src/audio/dsp/bank.ts +233 -0
- package/src/audio/dsp/fft.ts +6 -0
- package/src/audio/dsp/filters.ts +57 -0
- package/src/audio/dsp/interp.ts +112 -0
- package/src/audio/dsp/modal.ts +684 -0
- package/src/audio/dsp/onset.ts +197 -0
- package/src/audio/dsp/oversample.ts +202 -0
- package/src/audio/dsp/rng.ts +37 -0
- package/src/audio/dsp/shape.ts +81 -0
- package/src/audio/dsp/stft.ts +73 -0
- package/src/audio/dsp/window.ts +77 -0
- package/src/audio/effects/chain.ts +6 -1
- package/src/audio/effects/rig/cab.ts +99 -0
- package/src/audio/effects/rig/filters.ts +152 -0
- package/src/audio/effects/rig/gate.ts +39 -0
- package/src/audio/effects/rig/head.ts +487 -0
- package/src/audio/effects/rig/index.ts +170 -0
- package/src/audio/effects/rig/section.ts +78 -0
- package/src/audio/effects/rig/stomp.ts +238 -0
- package/src/audio/engine.ts +10 -1
- package/src/audio/fit.ts +402 -0
- package/src/audio/granular.ts +664 -0
- package/src/audio/instrument-check.ts +133 -0
- package/src/audio/instruments.ts +111 -0
- package/src/audio/keys/dsp.ts +323 -0
- package/src/audio/keys/engine.ts +361 -0
- package/src/audio/keys/piano.ts +432 -0
- package/src/audio/live-worker.ts +54 -0
- package/src/audio/live.ts +231 -12
- package/src/audio/loudness.ts +65 -20
- package/src/audio/preview.ts +2 -0
- package/src/audio/resonators.ts +287 -0
- package/src/audio/sampler.ts +163 -17
- package/src/audio/samples.ts +30 -7
- package/src/audio/strings/body.ts +250 -0
- package/src/audio/strings/engine.ts +369 -0
- package/src/audio/strings/loop.ts +119 -0
- package/src/audio/strings/measure.test-helpers.ts +198 -0
- package/src/audio/strings/pluck.ts +354 -0
- package/src/audio/warp.ts +61 -0
- package/src/audio/wav.ts +78 -6
- package/src/commands/expression.ts +9 -2
- package/src/commands/fit.ts +135 -0
- package/src/commands/fx.ts +31 -0
- package/src/commands/granular.ts +427 -0
- package/src/commands/help.ts +74 -3
- package/src/commands/keys.ts +353 -0
- package/src/commands/modal.ts +247 -0
- package/src/commands/rig.ts +260 -0
- package/src/commands/sample.ts +3 -0
- package/src/commands/string.ts +175 -0
- package/src/commands/time.ts +4 -1
- package/src/main.ts +210 -8
- package/src/project/check.ts +13 -0
- package/src/render.ts +33 -4
- package/src/tui/audition.ts +2 -2
- package/src/tui/granular-menu.ts +278 -0
- package/src/tui/menu.ts +332 -9
- package/src/tui/modal-menu.ts +118 -0
- package/src/tui/play-session.ts +45 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to dawg are recorded here. Versions follow [semantic version
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.6.0
|
|
8
|
+
|
|
9
|
+
Instruments: plucked strings, mallets and bells, a modelled piano, granular clouds, fitting samples to the tempo map, and a guitar rig, all built on the 0.5 foundations. Every new field is optional, so 0.4 and 0.5 projects load, print and render byte-identically.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Fitting samples to the song.** Sampler voices take `bpm` (their own tempo) or `len` (beats), and follow the song's tempo map, ramps included. `fitmode` picks how: `repitch` (tape, the default), `beats` (onset slices placed on time, hits stay sharp) or `tones` (phase-vocoder stretch, pitch kept). `/bpm 174`, `/len 16`, `/fitmode beats` (or `/fitmode auto`, which listens and suggests), the Sound menu's sample rows, the agent's `fit_sample` tool, and `sample(path, { bpm, fitmode, len })` in the SDK. Only the frames a note can reach are fitted. In play mode a fit up to 8 s (source or output) is computed at once; longer ones say "fitting" until ready, then "fit ready". A bare `bpm <n>` stays the song tempo. SDK 1.20.0 (additive).
|
|
14
|
+
- **Plucked strings.** A physical string model with 22 presets: nylon, steel, electric and 12-string (`jangle`) guitars, electric, slap, upright and Motown bass, sitar and tanpura with jawari buzz and sympathetic strings, harpsichord, lute, oud, setar, tar, santur, hammered dulcimer, koto, harp, banjo, tres and requinto. Strings follow tunings, bends and articulations; `string sitar`, `string buzz 0.8`, Sound › browse sounds › Strings, the `set_string` agent tool and `stringed()` in the SDK (1.21.0). Legacy instrument words keep their tone.
|
|
15
|
+
- **Guitar rig.** Three new effects after `distort`: a `stomp` box (fuzz, face, od, rat, octave), an amp `head` (clean, chime, crunch, lead, high, solid, bass) with Yeh–Smith tone stacks, power-amp sag, calibrated makeup gain and an optional noise `gate`, and a speaker `cab` (1x12, 2x12, 4x12, 1x10, open, 8x10, 1x15, di). Oversampled and antialiased: aliases of a 1 kHz fuzz stay at or below -60 dB. `rig <preset>` loads a whole rig (clean crunch punk ragged lead metal fuzz octave funk wah bachata spring bassdrive reese jangle alt); `stomp`, `head` and `cab` edit one stage; `fx amp` points to `head` (amp stays Strudel gain). Menu Effects › Guitar rig, agent tool `set_rig`, SDK `rig()` (1.22.0). Stomps are level-matched to bypass, so switching rigs keeps the level; a rig's companion effects (funk's envelope filter, jangle's compressor) leave with it. Track words `jangle punk funk ragged gtr-lead gtr-metal bachata` make a guitar track (the strings `electric` voice, `12string` for jangle) with that rig from the prompt, the agent and the SDK. Play mode sounds the first 0.75 s of a rig note at once and renders the rest in the background. Projects without a rig render byte-identically.
|
|
16
|
+
- **Granular instrument.** `grain cloud` turns any pitched track into a seeded, deterministic grain cloud of its own sound (a synth or a sampler voice; `synth:pad` with nothing loaded), with eight presets (`cloud hold sparkle swarm stutter microloop backwards dust`) and short parameters (`pos scan grain overlap jitter spray pitch detune shimmer spread window reverse freeze drift`…). Grains read a shared band-limited semitone bank (`src/audio/dsp/bank.ts`, 64 MB cap) with window tables in `src/audio/dsp/window.ts`: within 1 cent, alias-free upward, under 10 ms cold note-on and 7 ms per voice-second. Reachable as `grain …` and `track cloud`, Sound › granular and Sound › browse sounds › Granular, the `set_granular` agent tool and `granular()` in the SDK (1.23.0). Notes keep sounding through `release` after note-off, the built-in synth sources are held seamless loops of the track's own synth sound, and `grain off` restores the voice the track had (`granular.from`).
|
|
17
|
+
- **A modelled piano.** `piano` (or `grand`, `upright`, `felt`, `honkytonk`, `prepared`, and the presets `ballad` and `lofi`) puts a built-in physical piano on the focused track: felt hammers, stretched inharmonic three-string unisons with a two-stage decay, a soundboard knock, dampers and the sustain pedal, in any tuning. `keys hardness 0.3`, `keys decay 1.5`, `keys preset lofi` and `keys reset` shape it, `keys-<param>` lanes automate it, the menu has it under Sound > browse sounds > Keys and Sound > Keys, the agent has `set_keys`, and the SDK takes `track({ instrument: "grand", keys: { ... } })` (SDK 1.24.0). A project stored with `instrument: "piano"` keeps its old tone; only new writes of the word choose the modelled grand. A preset's effects (the lofi filter and crush, the ballad and felt rooms) come with the word on every surface, including `song.ts`, and leave again when you switch presets or `keys reset`. In play mode a released key damps as the real damper would (a short tail low down, none from F6 up).
|
|
18
|
+
- **Mallets and bells.** A modal resonator engine with 12 presets: marimba, vibes, xylophone, glock, celesta, chimes, kalimba, mbira, steelpan, bowl, gong and timpani (aliases vibraphone, glockenspiel, tubular, thumbpiano, gongageng, steeldrum, singingbowl, kettledrum, tubularbells). Mallet, hardness, strike position, ring, tilt, damping, vibraphone motor, ombak beating, buzz, click and a strike glide; `modal-<param>` automation lanes; tunings, cents, bends, articulation, pedal, velocity curves, humanize and the tempo map apply. Reach it with `modal <preset>`, `modal <param> <value>`, the menu's Sound › browse sounds › Mallets and bells, the agent's `set_modal` tool, or `modal()` in the SDK (1.25.0). Plain `marimba` keeps its old tone and `dawg check` points to the new one.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Long songs keep sounding.** A song without sections whose tracks use `time` (cycle or rate) now renders all of its windows instead of going silent after 20 s, and a long phasing arrangement with sections no longer fails at the 4096-note score limit while baking; `dawg check` warns when an arrangement bakes more notes than that, and a render error is one line.
|
|
23
|
+
- **Unknown instrument words are caught.** `dawg check` warns about an instrument word that names no voice (`violin`, `clav`) with the nearest real one, and the `instrument` command refuses it with a suggestion instead of playing a sine.
|
|
24
|
+
- **Quieter voice steals.** A stolen modal voice fades over 80 ms with a raised cosine, so long gong and kempul tails no longer click.
|
|
25
|
+
- **Beat fitting on the grid.** `fitmode beats` places each slice start at the hit's transient, within 2 ms of the grid after a 174 to 128 BPM fit.
|
|
26
|
+
- **True peak.** The meter and the master limiter check seven inter-sample phases evenly, so a peak between samples is no longer under-read by about 0.2 dB.
|
|
27
|
+
- **Missing samples fail the render.** A sample that cannot load (a pin mismatch, an offline pack) makes `dawg render` exit with an error instead of writing a song with the voice missing.
|
|
28
|
+
- **Strings in tune.** Harpsichord and other unison courses centre their seeded detune on the tuning table, per key, so the course sits on pitch and level is flat across velocity.
|
|
29
|
+
- **Granular cost.** The grain bank looks chunks up without string keys; a 16-voice swarm costs about 4 ms per voice-second at 48 kHz.
|
|
30
|
+
- **Smaller fixes.** `pedal half` receipts count half pedals, `modal saron` (or any body word) sets the body, `fx wobble` and `grain src bus:…` answer locally instead of going to the agent, and a tempo point past the song end says `bars <n>` extends it. DAWG.md says when exports run at 48 kHz.
|
|
31
|
+
|
|
32
|
+
### Internal
|
|
33
|
+
|
|
34
|
+
- **0.6 instrument plumbing.** Shared DSP primitives under `src/audio/dsp/` (counter-based seeded randomness, Hermite reads and a Thiran fractional delay, half-band 2x/4x oversampling, antiderivative-antialiased shapers, DC blocker and one-pole), an instrument engine registry (`src/audio/instruments.ts`) consulted by renders, the stem cache and live play, and one instrument word resolver (`core/instruments.ts`, copied into the SDK) used by the `instrument` prompt, `set_instrument` and `track()`. Nothing is registered yet, so every project sounds and prints exactly as before.
|
|
35
|
+
|
|
7
36
|
## 0.5.0
|
|
8
37
|
|
|
9
38
|
Foundations: tunings and microtones, expressive performance, tempo maps and phasing, song sections, and a master bus with loudness metering. Every new field is optional, so 0.4 projects load, print and render byte-identically.
|
package/DAWG.md
CHANGED
|
@@ -180,7 +180,7 @@ Every subprocess goes through the injectable `CommandRunner` in `src/auth/runner
|
|
|
180
180
|
|
|
181
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.
|
|
182
182
|
|
|
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 · dawg login` offline; 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` (≈
|
|
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 · dawg login` offline; 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` (≈ 29,800 input + 600 output tokens, measured from the system prompt, tool schemas and a fixture brief over about 2 requests) × price.
|
|
184
184
|
|
|
185
185
|
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
186
|
|
|
@@ -235,7 +235,7 @@ Score format. The score stays `track.loop/v1` with `version: 1`: every addition
|
|
|
235
235
|
Every track has one fixed effects chain (`FX_CHAIN` in `core/fx.ts`, DSP in `src/audio/effects/`):
|
|
236
236
|
|
|
237
237
|
```text
|
|
238
|
-
filter → djf → autofilter → vowel → crush → distort → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
|
|
238
|
+
filter → djf → autofilter → vowel → crush → distort → stomp → head → cab → tremolo → compressor → pan → phaser → chorus → leslie → postgain → delay → reverb → [mix: orbit → duck]
|
|
239
239
|
```
|
|
240
240
|
|
|
241
241
|
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.
|
|
@@ -342,6 +342,49 @@ Parameters (**bold** effect = shown in the simple menu; Lane = automation lane):
|
|
|
342
342
|
|
|
343
343
|
Strudel mapping notes: Strudel's `lpf`/`hpf`/`bpf` each set a separate filter; dawg has one track filter whose `type` selects the response, so `lpf(800)` is `filter {type: "lpf", cutoff: 800}` and `lpq`/`hpq`/`bpq` map to `resonance`. `delay` in Strudel is the wet level (dawg `delay.mix`), `delaytime` is seconds (dawg `delay.time`; `beats` is the tempo-synced form), `delayfeedback` is `delay.feedback`. `room` is `reverb.mix`, `size`/`roomsize` is `reverb.size`, `roomfade`/`roomlp`/`roomdim` are `fade`/`lowpass`/`dim`. `distort` and `shape` are the distortion drive with `type: "shape"` for Strudel's `shape` curve; `crush` is bits and `coarse` is the sample-hold factor. `phaser`/`phaserdepth`/`phasercenter`/`phasersweep`, `tremolo*`, `leslie`/`lrate`/`lsize`, `postgain` and `compressor` keep their names. `orbit` groups tracks for `duckorbit`/`duckdepth`/`duckattack`/`duckonset` sidechaining; By default each track keeps its own delay and reverb; `fx orbit 2 shared on` makes the track send to its orbit's one shared delay and reverb, as Strudel orbits do (see **Orbit buses**). `iresponse`/`ir` is `reverb.ir`.
|
|
344
344
|
|
|
345
|
+
### Guitar rig (stomp, head, cab)
|
|
346
|
+
|
|
347
|
+
A guitar rig is three effects in the chain after `distort` (`src/audio/effects/rig/`): a **stomp** box, an amp **head** with an optional noise gate, and a speaker **cab**inet. They run once per track on the mono voice sum, never per voice, and each nonlinear section has its own half-band oversampler (4x at 22.05 kHz, 2x at 44.1/48 kHz) with antiderivative-antialiased shapers, so a 1 kHz fuzz keeps its aliases at or below -60 dB in the audible band. A full rig costs about 15-25 ms per track-second.
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
rig crunch a whole rig: stomp + head + cab (one undo step)
|
|
351
|
+
rig show the focused track's rig
|
|
352
|
+
rig reset remove all three stages
|
|
353
|
+
stomp fuzz | stomp gain 7 head lead | head treble 7 gate -55 | cab 4x12 | cab mic 0.6
|
|
354
|
+
fx head gain 4 the same stages through the generic fx grammar
|
|
355
|
+
track jangle a new guitar track: a guitar voice plus the jangle rig
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
- **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
|
+
- **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 centre (bright) to the edge (dark); `di` is the band-limited direct box for bass.
|
|
361
|
+
|
|
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 `—`. Track words `jangle punk funk ragged gtr-lead gtr-metal bachata` create a guitar track 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
|
+
|
|
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).
|
|
365
|
+
|
|
366
|
+
| Effect | Param | Range | Default | Lane |
|
|
367
|
+
| --------- | --------------- | --------------------------------------------------- | ------- | ------------ |
|
|
368
|
+
| **stomp** | type | fuzz / face / od / rat / octave | od | |
|
|
369
|
+
| **stomp** | gain | 0..10 | 5 | `stomp-gain` |
|
|
370
|
+
| **stomp** | tone | 0..1 | 0.5 | `stomp-tone` |
|
|
371
|
+
| **stomp** | level | -24..12 dB | 0 | |
|
|
372
|
+
| stomp | octave | 0..1 | 0.7 | |
|
|
373
|
+
| stomp | mix | 0..1 | 1 | |
|
|
374
|
+
| **head** | type | clean / chime / crunch / lead / high / solid / bass | crunch | |
|
|
375
|
+
| **head** | gain | 0..10 | 5 | `head-gain` |
|
|
376
|
+
| **head** | bass | 0..10 | 5 | |
|
|
377
|
+
| **head** | mid | 0..10 | 5 | |
|
|
378
|
+
| **head** | treble | 0..10 | 5 | |
|
|
379
|
+
| head | presence | 0..10 | 5 | |
|
|
380
|
+
| head | master | 0..10 | 5 | |
|
|
381
|
+
| head | sag (optional) | 0..1 | 0.3 | |
|
|
382
|
+
| head | gate (optional) | -96..0 dB | off | |
|
|
383
|
+
| head | level | -24..12 dB | 0 | |
|
|
384
|
+
| **cab** | type | 1x12 / 2x12 / 4x12 / 1x10 / open / 8x10 / 1x15 / di | 2x12 | |
|
|
385
|
+
| **cab** | mic | 0..1 | 0.3 | |
|
|
386
|
+
| **cab** | mix | 0..1 | 1 | |
|
|
387
|
+
|
|
345
388
|
## Master and loudness
|
|
346
389
|
|
|
347
390
|
The song master is an optional chain after every track, orbit bus and duck are summed: **EQ** (low shelf, two bells, high shelf) → **glue** (stereo-linked bus compressor with soft knee, parallel mix and a sidechain high-pass) → **tape** (saturation with bias and a tone roll-off) → **width** (mid/side, mono below a cutoff) → **limiter** (true-peak brickwall with lookahead). A song with no master renders byte-identically to 0.4; an absent unit is skipped and a 0 dB EQ band is skipped.
|
|
@@ -398,7 +441,7 @@ Parameters (**bold** unit = shown in the simple menu; the rest are under `advanc
|
|
|
398
441
|
|
|
399
442
|
<!-- master-params:end -->
|
|
400
443
|
|
|
401
|
-
`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. 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 colour 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 **Mix & automation › 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).
|
|
444
|
+
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. 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 colour 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 **Mix & automation › 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).
|
|
402
445
|
|
|
403
446
|
## Synth
|
|
404
447
|
|
|
@@ -531,6 +574,58 @@ FM operators 2–8 repeat the `fm` rows with a suffix (`fm2`, `fmh2`, `fmattack2
|
|
|
531
574
|
| raw `zzfx([...])` parameter array | `synth zzfx …`, SDK `zzfx([...])`, `set_synth {zzfx}` | done: ZzFX's documented layout → named controls |
|
|
532
575
|
| soundfonts `gm_*`, drum banks, dirt-samples | sampler and sample packs | not this engine: hosted samples, see Sample packs |
|
|
533
576
|
| sample controls `begin`, `end`, `speed`, `unit`, `loop`, `loopBegin`/`loopb`, `loopEnd`/`loope`, `clip`/`legato`, `fit`, `loopAt`, `accelerate`, `squiz`, `cut`, `gain` | sampler voice fields; `/sample set`, `set_sample` | done (see Samples) |
|
|
577
|
+
| fitting to tempo (Ableton Repitch/Beats/Tones; Strudel `fit`) | `bpm` `fitmode` `len`; `/fitmode`, `fit_sample` | done (see Fitting samples) |
|
|
578
|
+
|
|
579
|
+
## Keys (modelled piano)
|
|
580
|
+
|
|
581
|
+
A track whose `instrument` is a piano family (`grand`, `upright`, `felt`, `honkytonk`, `prepared`) and which has a `keys` field plays dawg's modelled 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.
|
|
582
|
+
|
|
583
|
+
`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 sampled Salamander grand is still in the browser under instruments.
|
|
584
|
+
|
|
585
|
+
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.
|
|
586
|
+
|
|
587
|
+
Polyphony is 64 voices; a new key steals the oldest released voice, then the oldest held one.
|
|
588
|
+
|
|
589
|
+
Prompt grammar (one undo step per command):
|
|
590
|
+
|
|
591
|
+
```text
|
|
592
|
+
piano the modelled grand (also: grand)
|
|
593
|
+
piano ballad a preset: grand ballad upright felt lofi honkytonk prepared
|
|
594
|
+
upright | felt | honkytonk | prepared the preset word alone
|
|
595
|
+
keys list this track's piano settings
|
|
596
|
+
keys presets every preset with its styles
|
|
597
|
+
keys preset lofi load a preset (instrument, keys and its effects)
|
|
598
|
+
keys hardness 0.3 decay 1.5 any parameter
|
|
599
|
+
keys hardness off unset one parameter (back to the preset)
|
|
600
|
+
keys reset the family's own sound (keys: {})
|
|
601
|
+
automate keys-hardness points 0:0.2 8:0.8 automatable parameters have lanes
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
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 drum kit and crush preset word; type `piano lofi`.
|
|
605
|
+
|
|
606
|
+
The menu has the pianos under **Sound > browse sounds > Keys**, and for a piano track a **Sound > Keys** page and the simple rows (preset, hardness, decay, release, felt) in **Sound > Parameters**. 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 } })`.
|
|
607
|
+
|
|
608
|
+
| Param | Range | Default | Lane | What it does |
|
|
609
|
+
| ------------ | ------------------------------------- | ------------------ | --------------- | -------------------------------------------------------------------- |
|
|
610
|
+
| **hardness** | 0..1 | 0.5 | `keys-hardness` | hammer felt hardness: brightness at a given velocity |
|
|
611
|
+
| **touch** | 0..1 | 1 | `keys-touch` | velocity sensitivity (0 plays every note at 0.8) |
|
|
612
|
+
| **inharm** | 0..4 x | 1 (upright 2.5) | | inharmonicity multiplier (0 harmonic) |
|
|
613
|
+
| **unison** | 0..30 cents | 0.7 (honkytonk 16) | | detune spread of the unison strings |
|
|
614
|
+
| **decay** | 0.1..4 x | 1 (upright 0.6) | `keys-decay` | sustain time multiplier |
|
|
615
|
+
| **release** | 0.1..4 x | 1 | `keys-release` | damper time multiplier (how fast a released key stops) |
|
|
616
|
+
| **strike** | 0.04..0.3 | 0.12 | | hammer position along the string |
|
|
617
|
+
| **after** | 0..1 | 0.3 | | aftersound share (the slow second stage of the decay) |
|
|
618
|
+
| **knock** | 0..1 | 0.5 | `keys-knock` | soundboard knock and hammer thump |
|
|
619
|
+
| **noise** | 0..1 | 0.25 (felt 0.6) | `keys-noise` | key-off and damper mechanics |
|
|
620
|
+
| **felt** | 0..1 | 0 (felt 1) | `keys-felt` | felt strip between hammers and strings |
|
|
621
|
+
| **prep** | 0..1 | 0 (prepared 0.6) | | share of keys carrying a preparation (seeded per key) |
|
|
622
|
+
| **width** | 0..1 | 0.6 | | keyboard stereo spread, bass left and treble right |
|
|
623
|
+
| **stretch** | 0..1 | 1 | | octave stretch from the strings' inharmonicity; 0 keeps tuning exact |
|
|
624
|
+
| **body** | grand upright felt honkytonk prepared | the family's own | | body EQ voicing |
|
|
625
|
+
| **vib** | 0..64 Hz | 0 | | pitch wobble rate (tape wow); note vibrato replaces it |
|
|
626
|
+
| **vibmod** | 0..24 semitones | 0.5 | | pitch wobble depth |
|
|
627
|
+
|
|
628
|
+
Lanes are read at each note's onset. The model is dawg's own, from public literature (Fletcher's inharmonicity B·n² law, Railsback stretch, Weinreich's coupled unison strings and two-stage decay, Chaigne and Askenfelt's felt-hammer model, Bank's modal piano synthesis), with no sampled audio.
|
|
534
629
|
|
|
535
630
|
## Samples
|
|
536
631
|
|
|
@@ -578,7 +673,34 @@ Semantics follow Strudel's sampler:
|
|
|
578
673
|
|
|
579
674
|
`/sample set <voice> <control> <value>…` edits these on the focused sampler track (`/sample set brk fit on clip 1`, `/sample set hat cut hats`, `off` unsets one), each voice in the menu's Parameters section has the same controls, and the agent's `set_sample` tool takes them by name.
|
|
580
675
|
|
|
581
|
-
|
|
676
|
+
### Fitting samples to the song (0.6)
|
|
677
|
+
|
|
678
|
+
A voice can follow the song's time instead of its own rate. Give it its own tempo (`bpm`) or a length in beats (`len`), and pick how it changes time with `fitmode`:
|
|
679
|
+
|
|
680
|
+
| Field | Values | What it does |
|
|
681
|
+
| --------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
682
|
+
| `bpm` | 20..400 | the sample's own tempo: the window advances one source beat per song beat (times `abs(speed)`), through the tempo map, ramps and fermatas included; a 174 BPM break in a 128 BPM song plays 128/174 as fast |
|
|
683
|
+
| `len` | beats, > 0 | the window lasts `len` song beats through the tempo map |
|
|
684
|
+
| `fitmode` | `repitch` (default), `beats`, `tones` | `repitch` is tape: speed and pitch move together. `beats` cuts the window at its onsets (SuperFlux, 30 ms minimum gap) and places each slice on its new time unstretched, so every hit stays sharp: drums, speech. `tones` time-stretches with a phase vocoder with identity phase locking and keeps the pitch: pads, loops, vocals |
|
|
685
|
+
|
|
686
|
+
How they combine with Strudel's controls and the 0.5 tempo map:
|
|
687
|
+
|
|
688
|
+
| Set | Window length | Pitch |
|
|
689
|
+
| ----------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------- |
|
|
690
|
+
| none of them | `speed`, `unit`, `fit`, `loopAt` as before | moves with speed |
|
|
691
|
+
| `fit` + `fitmode beats/tones` | the note (fit wins over `bpm` and `len`) | kept |
|
|
692
|
+
| `bpm` | source beats ÷ `bpm` × song beats, following tempo changes | `repitch`: moves; `beats`/`tones`: kept |
|
|
693
|
+
| `len` (no `bpm`) | `len` song beats, following tempo changes | `repitch`: moves; `beats`/`tones`: kept |
|
|
694
|
+
| `speed` with `bpm` | `abs(speed)` source beats per song beat (2 = double time); negative reverses | as above |
|
|
695
|
+
| `unit` with `bpm`/`len` | ignored once fitted | — |
|
|
696
|
+
| keyed root, `cents`, glide | the fitted buffer is repitched on top: play the root to keep the time | moves |
|
|
697
|
+
| `loopBegin`/`loopEnd`, `clip`, `accelerate`, `squiz`, `cut` | apply to the fitted buffer | as before |
|
|
698
|
+
|
|
699
|
+
`fitmode beats` or `tones` needs `bpm`, `len` or `fit` (validation error otherwise). `/bpm 174`, `/len 16` and `/fitmode beats` act on the focused sampler voice (name it when the track has several: `/bpm 174 brk`; `off` unsets; song tempo stays the bare word `tempo <n>`, and a bare `bpm <n>` without the slash also sets the song tempo, so only `/bpm` sets the sample's); `/fitmode auto` suggests a mode from the sound (crest factor above 5 and more than 2 onsets a second fit as `beats`, the rest as `tones`). The menu's Sound section lists `bpm`, `fitmode` and `len` on each voice, the agent's `fit_sample` tool takes them, and the SDK is `sample("samples/amen.wav", { bpm: 174, fitmode: "beats" })`.
|
|
700
|
+
|
|
701
|
+
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.
|
|
702
|
+
|
|
703
|
+
Every voice starts and stops with a 1–3 ms fade, so cuts do not click. Without `bpm`, `len` or `fitmode` there is no time-stretch, as in Strudel's default. A sampler track goes through the same volume and pan automation, filter, delay and reverb as any other track and is a cached stem like any other; the stem's cache key includes each voice's sha256, so replacing a file re-renders it.
|
|
582
704
|
|
|
583
705
|
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.
|
|
584
706
|
|
|
@@ -676,6 +798,140 @@ The agent's `make_wavetable` tool (`src/audio/wavetable-maker.ts`, `src/media/wa
|
|
|
676
798
|
|
|
677
799
|
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`.
|
|
678
800
|
|
|
801
|
+
## Plucked strings
|
|
802
|
+
|
|
803
|
+
`instrument: "string"` with a `string` field plays a physical model of a plucked or struck string (`src/audio/strings/`): an extended Karplus-Strong loop (Jaffe and Smith 1983) tuned exactly with a first-order Thiran allpass, a one-pole loss designed from two decay times (Välimäki et al. 1996), an allpass dispersion cascade for stiff strings (Van Duyne and Smith 1994), a raised-cosine pluck shaped by the pluck-position comb, a modal body, a sympathetic string bank and seeded unison courses. Pitches come from the 0.5 tuning tables and pitch curves (bends, glides, cents), so a string track plays 19-EDO or just intonation and follows `bend`. Bare legacy words (`sitar`, `ebass`, `pluck`, `cello`) keep their old tone; the engine runs only when the track has a `string` field.
|
|
804
|
+
|
|
805
|
+
`string sitar`, or `instrument nylon` / `instrument koto` with the resolver words (`nylon`, `steel`, `harpsichord`, `koto` …; the bare words `sitar`, `ebass` and `pluck` keep their legacy voices), picks one of 22 presets, each a full parameter set frozen by a hash test:
|
|
806
|
+
|
|
807
|
+
| Preset | Sound |
|
|
808
|
+
| --------------------------------------- | ---------------------------------------------------------------------- |
|
|
809
|
+
| `nylon` `steel` `electric` `jangle` | classical, steel-string, clean electric and electric 12-string guitars |
|
|
810
|
+
| `ebass` `slap` `upright` `motown` | electric bass, slap, upright pizzicato, flatwound muted P-bass |
|
|
811
|
+
| `sitar` `tanpura` | jawari buzz with taraf sympathetic strings; open-string drone |
|
|
812
|
+
| `harpsichord` `lute` `harp` | 8'+8' harpsichord (velocity-flat), gut lute courses, concert harp |
|
|
813
|
+
| `oud` `setar` `tar` `santur` `dulcimer` | fretless oud, Persian setar and tar, santur and hammered dulcimer |
|
|
814
|
+
| `koto` `banjo` `tres` `requinto` | koto, 5-string banjo, Cuban tres, bachata requinto |
|
|
815
|
+
|
|
816
|
+
Preset aliases (after `string`): `classical` (nylon), `acoustic` and `guitar` (steel), `12string` (jangle), `bassguitar` and `fender` (ebass), `doublebass` (upright), `cembalo` (harpsichord), `hammered` (dulcimer), `sehtar` (setar).
|
|
817
|
+
|
|
818
|
+
| Parameter | Range | Meaning |
|
|
819
|
+
| -------------------------- | ------------------------ | --------------------------------------------------------------------------- |
|
|
820
|
+
| `ring` (Strudel `decay`) | 0.05..60 s | how long a note rings: the fundamental's T60 at C4 |
|
|
821
|
+
| `track` | 0..1.5 | higher notes ring shorter: T60 x (f/C4)^-track |
|
|
822
|
+
| `damp` `bright` | 0..1 | high-frequency loss; excitation brightness at full velocity |
|
|
823
|
+
| `pos` | 0.02..0.5 | pluck position from the bridge (0.5 round, 0.04 nasal) |
|
|
824
|
+
| `exciter` | pick finger hammer noise | how the string is set in motion |
|
|
825
|
+
| `mute` | 0..1 | palm mute (staccato and ghost articulations damp the same way) |
|
|
826
|
+
| `buzz` (`jawari`) | 0..1 | bridge buzz: a zero-mean, energy-preserving bridge allpass, pitch-locked |
|
|
827
|
+
| `stiff` | 0..1 | inharmonicity B = 1e-6 x 400^stiff |
|
|
828
|
+
| `body` `size` | type, 0.25..5 | body resonance (`guitar`, `gourd`, `board`, `skin`, `bass` …) and its scale |
|
|
829
|
+
| `sym` `symtune` | 0..1, mode | sympathetic strings, tuned to the song `scale`, `open` strings or a `drone` |
|
|
830
|
+
| `unison` `detune` `spread` | 1..8, 0..1, 0..1 | strings per course, their detune and stereo spread |
|
|
831
|
+
| `oct` `octbelow` | 0..1, key | octave strings (12-string, harpsichord 4'), only below a key |
|
|
832
|
+
| `vel` `pickup` `noise` | 0..1 | velocity sensitivity, magnetic pickup position, excitation noise |
|
|
833
|
+
| `vib` `vibmod` `vibdelay` | Hz, st, s | preset vibrato (a note's own vibrato wins) |
|
|
834
|
+
| `release` `voices` `gain` | s, 1..32, 0..2 | damping after note-off, polyphony cap, level |
|
|
835
|
+
|
|
836
|
+
Sympathetic strings follow the song key's own scale, so `key C bhairav` tunes the taraf to Bhairav (shuddha Ni, komal Re and Dha) and a maqam key keeps its quarter tones. The `drone` tuning is Sa-Pa-Sa, or Sa-Ma-Sa (tivra Ma when the scale has it, else Sa-Ni-Sa) in a raga without Pa such as Marwa. Changing the key re-renders a string track's stem.
|
|
837
|
+
|
|
838
|
+
`ring`, `damp`, `pos`, `bright`, `mute`, `buzz`, `vib`, `vibmod` and `gain` automate as `string-<param>` lanes (`automate string-buzz points 0:0 4:0.8`). Measured on the renderer, on one string with the body and sympathetic strings off: every fifth key within 0.1 cent of the tuning table (sitar with buzz within 1 cent above C4). With the preset defaults a unison course (the harpsichord's 8'+8') is tuned around the table pitch and beats, and the sitar's sympathetic strings ring with the played note, so a single-peak pitch reading of the whole sound can drift a few cents; a course's tuning belongs to its key, so every strike of a key beats the same way and the harpsichord's level stays within 0.5 dB across velocities. Also measured: the fundamental's decay within 10% of `ring` at every fifth key, a C-major triad at velocity 0.8 near -6 dBFS for every preset, and under 20 ms of render per voice-second.
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
instrument: stringed("sitar", { buzz: 0.8, sym: 0.5 }),
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
| Command | Does |
|
|
845
|
+
| ------------------------------------------ | ----------------------------------------------------------------- |
|
|
846
|
+
| `string` · `string presets` | the focused track's preset and overrides · the preset list |
|
|
847
|
+
| `string <preset>` | make the focused track that string instrument |
|
|
848
|
+
| `string <param> <value> [<param> <value>]` | override parameters (`string buzz 0.8 sym 0.5`, `string decay 6`) |
|
|
849
|
+
| `string <param> off` · `string reset` | back to the preset's value · drop every override |
|
|
850
|
+
| `string off` | back to the legacy `pluck` voice |
|
|
851
|
+
|
|
852
|
+
The menu has the presets under Sound › browse sounds › Strings and every string parameter in Sound › Parameters on 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.
|
|
853
|
+
|
|
854
|
+
## Granular
|
|
855
|
+
|
|
856
|
+
`instrument: "granular"` plays a track's sound as a cloud of short grains. A read head moves through a source; every few milliseconds a grain (a windowed slice, repitched to the note) starts at the head plus a little random spray. It is the classic asynchronous granular model (Roads, _Microsound_; Granulator II, Mutable Clouds), with Strudel-style short names. Everything renders offline and deterministically: grains draw from the counter-based generator in `src/audio/dsp/rng.ts`, keyed on (`seed`, track, pitch, start tick, occurrence), so the same project gives the same bytes and a repeated note varies the way a real cloud does.
|
|
857
|
+
|
|
858
|
+
Sources. With nothing loaded a granular track grains a built-in synth render (`synth:pad` at C4, made into a held, seamless loop of its sustained part so a long note never falls into silence), so `grain cloud` sounds at once. `src` takes any synth preset or sound (`synth:bell`, `synth:supersaw@48`, `synth:pad@c3`; a sound plays with the track's own `synth` params, and `grain on` after `synth preset pad` grains `synth:pad`), or a sample: on a sampler track `grain on` (or `grain on voice vox`) grains that voice, pinned by sha256 like the sampler file, and the track keeps its sampler so `grain off` goes back. A missing source is a sample load problem in `/tracks`, never a silent track. `root` is the note that plays the source at its own pitch (a MIDI number or a note name: `grain root c4`). `grain off` returns to the voice the track had when granular turned on (stored as `granular.from`).
|
|
859
|
+
|
|
860
|
+
Presets (each is a few overrides over the defaults; `microloop`, `sparkle` and `backwards` come into their own on a resampled phrase, and are textures on a synth source):
|
|
861
|
+
|
|
862
|
+
| preset | sound |
|
|
863
|
+
| ----------- | ---------------------------------------------------- |
|
|
864
|
+
| `cloud` | slow-scanning soft cloud, wide (the `granular` word) |
|
|
865
|
+
| `hold` | held, shimmering sustain of one moment |
|
|
866
|
+
| `sparkle` | octave-and-fifth sparkle over a slow scan |
|
|
867
|
+
| `swarm` | dense, detuned, fully wide |
|
|
868
|
+
| `stutter` | dry 45 ms repeats that latch and follow the music |
|
|
869
|
+
| `microloop` | tight looping grains that slowly advance |
|
|
870
|
+
| `backwards` | backwards swells |
|
|
871
|
+
| `dust` | sparse random crackles across the source |
|
|
872
|
+
|
|
873
|
+
Parameters: `begin`/`end` (the region, 0..1), `pos` (head start), `scan` (head speed, 1 = the source's own speed, 0 held, negative backwards), `grain` (s, 5 ms..2 s), `overlap` (grains sounding at once; density = overlap / grain), `jitter` (onset randomness), `spray` (s of read offset), `pitch` and `detune` (semitones), `shimmer` and `shimint` (chance a grain plays an interval up, 12 by default), `spread` (stereo), `window` (`hann tukey gauss tri perc rperc`), `reverse` (chance a grain plays backwards), `freeze`, `repeat` and `hold` (beat-repeat latches), `drift` and `drate` (a slow random walk of the head), `attack`, `release`, `veltone`, `gain`, `seed` and `root`. After a note ends the grains keep coming while the voice fades over `release`, and the note rings for `release + 1.5 × grain` (at most 10 s). `hold` is both a preset (`grain hold`) and a parameter (`grain hold 4`). Past 16 voices the oldest released voice is stolen first, then the oldest held one.
|
|
874
|
+
|
|
875
|
+
Quality and cost. Grains read a shared semitone-level band-limited bank (`src/audio/dsp/bank.ts`: windowed-sinc levels built on demand in 4096-frame Float32 chunks, a 64 MB LRU, the level re-picked every 32 frames), so upward shifts and shimmer do not alias. Pitch is exact to within 1 cent across C1..C8 and in other tunings; a cold note-on renders its first block in under 10 ms and the densest preset costs at most 7 ms per voice-second at 48 kHz, so play mode and the audition loop stay live.
|
|
876
|
+
|
|
877
|
+
| Command | What it does |
|
|
878
|
+
| ----------------------------------------- | ------------------------------------------------------------------- |
|
|
879
|
+
| `grain` · `grain presets` | the focused track's granular settings with values · the presets |
|
|
880
|
+
| `grain <preset>` | make the focused track a grain cloud of its own sound with a preset |
|
|
881
|
+
| `grain on [voice V]` · `grain off` | grain this track's sound (a sampler voice) · back to its voice |
|
|
882
|
+
| `grain src synth:<name>[@note]` | a built-in synth source |
|
|
883
|
+
| `grain src voice <V>` | one of this track's sampler voices as source |
|
|
884
|
+
| `grain <param> <value> …` · `grain reset` | override parameters (`off` unsets one) · drop the overrides |
|
|
885
|
+
| `track cloud` · `track hold-2` | a new granular track named after a preset |
|
|
886
|
+
| `track pad grain swarm` | focus or create a track and grain it in one step |
|
|
887
|
+
|
|
888
|
+
In the ctrl-k menu, **Sound › granular** edits the preset, source and every parameter of a granular track (on any other pitched track it reads **granular (convert)** and offers `grain on` and the presets); **Sound › browse sounds › 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:
|
|
889
|
+
|
|
890
|
+
```ts
|
|
891
|
+
instrument: granular("cloud", { scan: 0.1, seed: 7 }),
|
|
892
|
+
instrument: granular({ src: "synth:bell@72", grain: 0.08, shimmer: 0.3 }),
|
|
893
|
+
instrument: granular("hold", { src: "samples/choir.wav", root: "A3" }),
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
## Mallets and bells (modal)
|
|
897
|
+
|
|
898
|
+
`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 centre of a marimba bar mutes the second mode). It is dawg's own engine, ported from the reviewed 0.6 prototype.
|
|
899
|
+
|
|
900
|
+
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.
|
|
901
|
+
|
|
902
|
+
| Parameter | Range | Meaning |
|
|
903
|
+
| -------------------------- | ------------------ | --------------------------------------------------------------------------------- |
|
|
904
|
+
| `mallet` | yarn … brass | `yarn` `cord` `rubber` `plastic` `brass`; sets `hardness` |
|
|
905
|
+
| `hardness` | 0..1 | mallet hardness: soft rounds off the high modes, hard adds them; velocity adds |
|
|
906
|
+
| `position` | 0..1 | strike point: 0 the end or edge, 0.5 the centre |
|
|
907
|
+
| `ring` | 0.05..30 s (log) | ring time (T60) at middle C |
|
|
908
|
+
| `tilt` | 0..2 | how much faster high modes and high notes decay |
|
|
909
|
+
| `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 |
|
|
910
|
+
| `motor` `motordepth` | 0..12 Hz, 0..1 | vibraphone motor tremolo |
|
|
911
|
+
| `ombak` | 0..12 Hz | paired-instrument beating (gamelan) |
|
|
912
|
+
| `buzz` `click` | 0..1 | mbira bottle-cap buzz, mallet contact click |
|
|
913
|
+
| `strikebend` `strikedecay` | ±24 st, 0.001..2 s | the pitch glide at the strike (timpani); Strudel `penv`/`pdecay` |
|
|
914
|
+
| `gain` | 0..2 | level |
|
|
915
|
+
|
|
916
|
+
`hardness`, `position`, `ring`, `tilt`, `damp`, `motordepth`, `buzz`, `click` and `gain` have `modal-<param>` automation lanes, read at each note's onset. Notes honour the track or song tuning, note `cents`, bends, articulation (accents strike harder, staccato damps), the sustain pedal (holds dampers off), velocity curves, humanize and the tempo map. Up to 32 voices ring at once (the oldest is stolen with a short fade); tails ring up to 30 s and stop early once silent. Rendering is seeded and float64 in a fixed order, so renders are byte-identical across runs and workers.
|
|
917
|
+
|
|
918
|
+
```ts
|
|
919
|
+
instrument: modal("vibes", { motor: 4, hardness: 0.6 }),
|
|
920
|
+
instrument: modal("marimba", { mallet: "rubber", ring: 2 }),
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
| Command | Does |
|
|
924
|
+
| ---------------------------------------- | ---------------------------------------------------------- |
|
|
925
|
+
| `modal` · `modal presets` (`modal list`) | the focused track's preset and overrides · every preset |
|
|
926
|
+
| `modal <preset>` · `modal preset <name>` | make the focused track a modal track with that preset |
|
|
927
|
+
| `modal mallet <name>` | pick a mallet (sets hardness; the later of the two wins) |
|
|
928
|
+
| `modal <body>` | set the body (`modal saron`, `modal kempul`): `modal body` |
|
|
929
|
+
| `modal <param> <value> …` | set parameters (`modal ring 3 hardness 0.7`); `off` clears |
|
|
930
|
+
| `modal reset` | clear overrides, keep the preset |
|
|
931
|
+
| `modal off` | leave the engine for the legacy marimba voice |
|
|
932
|
+
|
|
933
|
+
The menu's **Sound › browse sounds › Mallets and bells** lists the presets, and **Sound › Parameters** 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.
|
|
934
|
+
|
|
679
935
|
## Rhythm (Euclidean rows)
|
|
680
936
|
|
|
681
937
|
A drum part can be stored as generators instead of notes: each row owns one voice of a `kit` or oneshot `sampler` track and dawg expands it into ordinary notes, so rendering, diffs and sync are unchanged while you, the agent and `track.ts` edit four numbers instead of sixteen hits. The model follows the Torso T-1's Shape and Groove sections; the Euclidean patterns and rotation match Strudel's `euclid`/`euclidRot` exactly (`E(3,8)` is `x..x..x.`, a positive rotate moves the pattern later).
|
|
@@ -1011,15 +1267,15 @@ The arrangement strip is one row under the header that shows the sections over t
|
|
|
1011
1267
|
|
|
1012
1268
|
`/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 arrange`); the old names `parameters`, `sounds`, `track`, `automation` and `transport` still work.
|
|
1013
1269
|
|
|
1014
|
-
| Section | Rows (most used first)
|
|
1015
|
-
| ---------------- |
|
|
1016
|
-
| Sound | instrument, preset; a synth's attack, decay, sustain, release, filter cutoff/res/env, detune, vibrato, FM amount, then **advanced** with every synth parameter; a wavetable track's table and wavetable parameters; a sampler's mode and voices; **performance** (glide, pedal, velocity curve, humanize); **browse sounds** (instruments, wavetables, packs) |
|
|
1017
|
-
| 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)
|
|
1018
|
-
| Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples)
|
|
1019
|
-
| Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style
|
|
1020
|
-
| 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
|
|
1021
|
-
| Project | play, tempo, beats per bar, loop length, grid, click, count-in bars
|
|
1022
|
-
| 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)
|
|
1270
|
+
| Section | Rows (most used first) |
|
|
1271
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1272
|
+
| Sound | instrument, preset; a synth's attack, decay, sustain, release, filter cutoff/res/env, detune, vibrato, FM amount, then **advanced** with every synth parameter; a wavetable track's table and wavetable parameters; **granular** (a granular track's preset, source and parameters; **granular (convert)** elsewhere); a sampler's mode and voices; **performance** (glide, pedal, velocity curve, humanize); **browse sounds** (instruments, wavetables, Granular, packs) |
|
|
1273
|
+
| 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) |
|
|
1274
|
+
| Rhythm | the euclid editor (`/euclid`), drum patterns (`/pattern`), drum kits (`/kit`, synth then samples) |
|
|
1275
|
+
| Chords | play-mode chord mode, key tonic and mode, voicing, spread, bass, sevenths, perform, pattern, arp rate, arp octaves, progression, style |
|
|
1276
|
+
| 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 |
|
|
1277
|
+
| Project | play, tempo, beats per bar, loop length, grid, click, count-in bars |
|
|
1278
|
+
| 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) |
|
|
1023
1279
|
|
|
1024
1280
|
Every list, picker and editor uses the same keys (see **Keys** below). In the menu:
|
|
1025
1281
|
|
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.6.0/hraness-dawg-0.6.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.6.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.6.0.tgz --repo hraness/dawg
|
|
28
|
+
bun add -g "$PWD/hraness-dawg-0.6.0.tgz"
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
dawg is also on npm as [`@hraness/dawg`](https://www.npmjs.com/package/@hraness/dawg):
|