@hraness/dawg 0.6.1 → 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 +118 -0
- package/DAWG.md +454 -140
- package/README.md +30 -26
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +184 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/ids.ts +52 -0
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +8 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/range.ts +581 -0
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +955 -14
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +287 -5
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +2132 -46
- package/core/sections.ts +480 -35
- package/core/sing.ts +815 -0
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +31 -0
- package/guides/audio.md +31 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +15 -7
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/panes.md +32 -0
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +26 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +18 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -13
- package/guides/styles.md +31 -0
- package/guides/tape.md +32 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +6 -2
- package/src/agent/agent.ts +130 -18
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +378 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +27 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +25 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/range-tools.ts +216 -0
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +110 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +13 -10
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +49 -5
- package/src/audio/audio-command.ts +218 -0
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/devices.ts +264 -0
- package/src/audio/doctor.ts +157 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +360 -40
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +82 -32
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +560 -0
- package/src/commands/help.ts +774 -366
- package/src/commands/history.ts +139 -10
- package/src/commands/keys.ts +25 -8
- package/src/commands/modal.ts +1 -1
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +135 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +474 -0
- package/src/commands/strum.ts +13 -1
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +617 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +511 -0
- package/src/launch-args.ts +267 -0
- package/src/main.ts +2508 -308
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +20 -1
- package/src/session/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +199 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +174 -0
- package/src/session/port.ts +109 -14
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +365 -7
- package/src/session/rebase.ts +63 -12
- package/src/session/receipt.ts +265 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +126 -45
- package/src/tui/arrange-menu.ts +225 -23
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +113 -27
- package/src/tui/fader.ts +282 -42
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +987 -179
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +243 -28
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +491 -84
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +193 -0
- package/tui/drawer.ts +233 -27
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +143 -65
- package/tui/guide.ts +50 -5
- package/tui/highway.ts +309 -30
- package/tui/hints.ts +197 -0
- package/tui/hits.ts +7 -1
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +80 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +151 -18
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +46 -1
package/core/vocoder.ts
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocoder (0.7 "Voice", lane vocoder): a track's sound (the carrier) shaped
|
|
3
|
+
* by another track's sound (the modulator, `src`). Pure and import-light,
|
|
4
|
+
* like `core/winds.ts`: the parameter table, the eight presets and the
|
|
5
|
+
* validator that the score, the prompt, the menu, the agent and the SDK
|
|
6
|
+
* printer share. The DSP lives in `src/audio/vocoder/`.
|
|
7
|
+
*
|
|
8
|
+
* Two ways in:
|
|
9
|
+
* - any track gains `vocoder: { src, ... }`: its own voice is the carrier;
|
|
10
|
+
* - instrument `vocoder` with a `vocoder` field plays a built-in carrier
|
|
11
|
+
* (saw, supersaw, pulse or noise) following the track's notes, the song's
|
|
12
|
+
* chords or a drone.
|
|
13
|
+
*
|
|
14
|
+
* The stage runs only when `vocoder.src` is present; without it the track
|
|
15
|
+
* plays dry (a `vocoder` instrument without a source is silent). Absent
|
|
16
|
+
* field: today's sound, byte for byte.
|
|
17
|
+
*/
|
|
18
|
+
import {
|
|
19
|
+
FxValidationError,
|
|
20
|
+
isRecord,
|
|
21
|
+
normalizeParams,
|
|
22
|
+
type NumberParam,
|
|
23
|
+
type ParamSpec,
|
|
24
|
+
} from "./params.ts";
|
|
25
|
+
|
|
26
|
+
/** Instrument value that selects the built-in carrier. */
|
|
27
|
+
export const VOCODER_INSTRUMENT = "vocoder" as const;
|
|
28
|
+
|
|
29
|
+
export const VOCODER_PRESET_NAMES = Object.freeze([
|
|
30
|
+
"classic",
|
|
31
|
+
"robot",
|
|
32
|
+
"talkbox",
|
|
33
|
+
"choir",
|
|
34
|
+
"glass",
|
|
35
|
+
"whisper",
|
|
36
|
+
"smear",
|
|
37
|
+
"lofi",
|
|
38
|
+
] as const);
|
|
39
|
+
export type VocoderPresetName = (typeof VOCODER_PRESET_NAMES)[number];
|
|
40
|
+
|
|
41
|
+
/** Default preset of a `vocoder` field that names none. */
|
|
42
|
+
export const DEFAULT_VOCODER_PRESET: VocoderPresetName = "classic";
|
|
43
|
+
|
|
44
|
+
/** Modulator taps (append-only). */
|
|
45
|
+
export const VOCODER_TAPS = Object.freeze(["chain", "dry"]);
|
|
46
|
+
/** Modes (append-only): a band bank or an LPC talkbox. */
|
|
47
|
+
export const VOCODER_MODES = Object.freeze(["channel", "talkbox"]);
|
|
48
|
+
/** Built-in carriers (append-only; `glottal` is 0.7.1). */
|
|
49
|
+
export const VOCODER_CARRIERS = Object.freeze([
|
|
50
|
+
"saw",
|
|
51
|
+
"supersaw",
|
|
52
|
+
"pulse",
|
|
53
|
+
"noise",
|
|
54
|
+
]);
|
|
55
|
+
/** Built-in carrier pitch (append-only; `voice` is 0.7.1). */
|
|
56
|
+
export const VOCODER_FOLLOWS = Object.freeze(["notes", "chords", "drone"]);
|
|
57
|
+
|
|
58
|
+
/** `gate` value that reads the threshold off the modulator's own assets. */
|
|
59
|
+
export const VOCODER_GATE_AUTO = "auto" as const;
|
|
60
|
+
|
|
61
|
+
/** Talkbox formant bound (the channel bank takes the full ±24). */
|
|
62
|
+
export const TALKBOX_FORMANT_MAX = 12;
|
|
63
|
+
|
|
64
|
+
const num = (
|
|
65
|
+
min: number,
|
|
66
|
+
max: number,
|
|
67
|
+
fallback: number,
|
|
68
|
+
step: number | "log",
|
|
69
|
+
doc: string,
|
|
70
|
+
extra: Partial<NumberParam> = {},
|
|
71
|
+
): NumberParam =>
|
|
72
|
+
Object.freeze({
|
|
73
|
+
kind: "number",
|
|
74
|
+
min,
|
|
75
|
+
max,
|
|
76
|
+
default: fallback,
|
|
77
|
+
step,
|
|
78
|
+
optional: true,
|
|
79
|
+
doc,
|
|
80
|
+
...extra,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const choice = (values: readonly string[], fallback: string, doc: string) =>
|
|
84
|
+
Object.freeze({
|
|
85
|
+
kind: "enum" as const,
|
|
86
|
+
values,
|
|
87
|
+
default: fallback,
|
|
88
|
+
optional: true,
|
|
89
|
+
doc,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Every vocoder parameter in table order (`src` and `preset` excluded). All
|
|
94
|
+
* optional: an absent one takes the preset's value, then the default here.
|
|
95
|
+
* `automate` ones have a `vocoder-<param>` lane. `gate` also takes "auto".
|
|
96
|
+
*/
|
|
97
|
+
export const VOCODER_PARAMS: Readonly<Record<string, ParamSpec>> =
|
|
98
|
+
Object.freeze({
|
|
99
|
+
tap: choice(
|
|
100
|
+
VOCODER_TAPS,
|
|
101
|
+
"chain",
|
|
102
|
+
"modulator after its mono chain (pre-pan) or dry before it",
|
|
103
|
+
),
|
|
104
|
+
mode: choice(VOCODER_MODES, "channel", "band bank or LPC talkbox"),
|
|
105
|
+
carrier: choice(
|
|
106
|
+
VOCODER_CARRIERS,
|
|
107
|
+
"supersaw",
|
|
108
|
+
"built-in carrier (instrument vocoder only)",
|
|
109
|
+
),
|
|
110
|
+
follow: choice(
|
|
111
|
+
VOCODER_FOLLOWS,
|
|
112
|
+
"notes",
|
|
113
|
+
"built-in carrier pitch: notes, song chords or a drone",
|
|
114
|
+
),
|
|
115
|
+
root: num(24, 96, 45, 1, "drone pitch (MIDI)", { integer: true }),
|
|
116
|
+
spread: num(0, 1, 0.15, 0.05, "supersaw detune spread (semitones)", {
|
|
117
|
+
unit: "st",
|
|
118
|
+
automate: true,
|
|
119
|
+
}),
|
|
120
|
+
bands: num(4, 40, 16, 1, "channel bands (heavy above 24)", {
|
|
121
|
+
integer: true,
|
|
122
|
+
}),
|
|
123
|
+
lo: num(50, 1000, 100, "log", "lowest band center", { unit: "Hz" }),
|
|
124
|
+
hi: num(2000, 12000, 8000, "log", "highest band center", { unit: "Hz" }),
|
|
125
|
+
width: num(0.25, 4, 1, 0.05, "band width (x spacing)", {
|
|
126
|
+
automate: true,
|
|
127
|
+
}),
|
|
128
|
+
attack: num(0.0005, 0.2, 0.005, "log", "follower attack", {
|
|
129
|
+
unit: "s",
|
|
130
|
+
strudel: ["att"],
|
|
131
|
+
}),
|
|
132
|
+
release: num(0.005, 2, 0.04, "log", "follower release (long = smear)", {
|
|
133
|
+
unit: "s",
|
|
134
|
+
automate: true,
|
|
135
|
+
strudel: ["rel"],
|
|
136
|
+
}),
|
|
137
|
+
formant: num(
|
|
138
|
+
-24,
|
|
139
|
+
24,
|
|
140
|
+
0,
|
|
141
|
+
0.5,
|
|
142
|
+
"formant shift (+ is smaller and brighter; talkbox ±12)",
|
|
143
|
+
{ unit: "st", automate: true },
|
|
144
|
+
),
|
|
145
|
+
unvoiced: num(0, 1, 0.5, 0.05, "noise carrier on fricatives", {
|
|
146
|
+
automate: true,
|
|
147
|
+
}),
|
|
148
|
+
sens: num(0, 1, 0.5, 0.05, "unvoiced detection sensitivity"),
|
|
149
|
+
hiss: num(0, 1, 0, 0.05, "high-passed modulator added through", {
|
|
150
|
+
automate: true,
|
|
151
|
+
}),
|
|
152
|
+
gate: num(-90, 0, -60, 1, 'modulator gate in dBFS, or "auto"', {
|
|
153
|
+
unit: "dB",
|
|
154
|
+
}),
|
|
155
|
+
enhance: Object.freeze({
|
|
156
|
+
kind: "boolean" as const,
|
|
157
|
+
default: true,
|
|
158
|
+
optional: true,
|
|
159
|
+
doc: "whiten the carrier so every band speaks",
|
|
160
|
+
}),
|
|
161
|
+
depth: num(0, 1, 1, 0.05, "how much the voice shapes the carrier", {
|
|
162
|
+
automate: true,
|
|
163
|
+
}),
|
|
164
|
+
freeze: Object.freeze({
|
|
165
|
+
kind: "boolean" as const,
|
|
166
|
+
default: false,
|
|
167
|
+
optional: true,
|
|
168
|
+
doc: "hold the last sung vowel through rests",
|
|
169
|
+
}),
|
|
170
|
+
mix: num(0, 1, 1, 0.05, "wet against the carrier", { automate: true }),
|
|
171
|
+
gain: num(-24, 24, 0, 0.5, "output trim", { unit: "dB", automate: true }),
|
|
172
|
+
seed: num(0, 2 ** 31, 1, 1, "noise seed (absent: from the track id)", {
|
|
173
|
+
integer: true,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export type VocoderTap = "chain" | "dry";
|
|
178
|
+
export type VocoderMode = "channel" | "talkbox";
|
|
179
|
+
export type VocoderCarrier = "saw" | "supersaw" | "pulse" | "noise";
|
|
180
|
+
export type VocoderFollow = "notes" | "chords" | "drone";
|
|
181
|
+
|
|
182
|
+
/** Overrides over a preset, all optional. */
|
|
183
|
+
export type VocoderOverrides = Readonly<{
|
|
184
|
+
tap?: VocoderTap;
|
|
185
|
+
mode?: VocoderMode;
|
|
186
|
+
carrier?: VocoderCarrier;
|
|
187
|
+
follow?: VocoderFollow;
|
|
188
|
+
root?: number;
|
|
189
|
+
spread?: number;
|
|
190
|
+
bands?: number;
|
|
191
|
+
lo?: number;
|
|
192
|
+
hi?: number;
|
|
193
|
+
width?: number;
|
|
194
|
+
attack?: number;
|
|
195
|
+
release?: number;
|
|
196
|
+
formant?: number;
|
|
197
|
+
unvoiced?: number;
|
|
198
|
+
sens?: number;
|
|
199
|
+
hiss?: number;
|
|
200
|
+
gate?: number | "auto";
|
|
201
|
+
enhance?: boolean;
|
|
202
|
+
depth?: number;
|
|
203
|
+
freeze?: boolean;
|
|
204
|
+
mix?: number;
|
|
205
|
+
gain?: number;
|
|
206
|
+
seed?: number;
|
|
207
|
+
}>;
|
|
208
|
+
|
|
209
|
+
/** A track's stored vocoder: the modulator, a preset and overrides. */
|
|
210
|
+
export type TrackVocoder = Readonly<
|
|
211
|
+
{ src?: string; preset?: VocoderPresetName } & VocoderOverrides
|
|
212
|
+
>;
|
|
213
|
+
|
|
214
|
+
/** Everything the DSP reads, resolved: defaults <- preset <- overrides. */
|
|
215
|
+
export type ResolvedVocoder = Readonly<{
|
|
216
|
+
tap: VocoderTap;
|
|
217
|
+
mode: VocoderMode;
|
|
218
|
+
carrier: VocoderCarrier;
|
|
219
|
+
follow: VocoderFollow;
|
|
220
|
+
root: number;
|
|
221
|
+
spread: number;
|
|
222
|
+
bands: number;
|
|
223
|
+
lo: number;
|
|
224
|
+
hi: number;
|
|
225
|
+
width: number;
|
|
226
|
+
attack: number;
|
|
227
|
+
release: number;
|
|
228
|
+
formant: number;
|
|
229
|
+
unvoiced: number;
|
|
230
|
+
sens: number;
|
|
231
|
+
hiss: number;
|
|
232
|
+
gate: number | "auto";
|
|
233
|
+
enhance: boolean;
|
|
234
|
+
depth: number;
|
|
235
|
+
freeze: boolean;
|
|
236
|
+
mix: number;
|
|
237
|
+
gain: number;
|
|
238
|
+
/** Undefined: derived from the track id by the renderer. */
|
|
239
|
+
seed?: number;
|
|
240
|
+
}>;
|
|
241
|
+
|
|
242
|
+
export const VOCODER_DEFAULTS: ResolvedVocoder = Object.freeze({
|
|
243
|
+
tap: "chain",
|
|
244
|
+
mode: "channel",
|
|
245
|
+
carrier: "supersaw",
|
|
246
|
+
follow: "notes",
|
|
247
|
+
root: 45,
|
|
248
|
+
spread: 0.15,
|
|
249
|
+
bands: 16,
|
|
250
|
+
lo: 100,
|
|
251
|
+
hi: 8000,
|
|
252
|
+
width: 1,
|
|
253
|
+
attack: 0.005,
|
|
254
|
+
release: 0.04,
|
|
255
|
+
formant: 0,
|
|
256
|
+
unvoiced: 0.5,
|
|
257
|
+
sens: 0.5,
|
|
258
|
+
hiss: 0,
|
|
259
|
+
gate: "auto",
|
|
260
|
+
enhance: true,
|
|
261
|
+
depth: 1,
|
|
262
|
+
freeze: false,
|
|
263
|
+
mix: 1,
|
|
264
|
+
gain: 0,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
export type VocoderPreset = Readonly<{
|
|
268
|
+
/** One line for the menu and /help. */
|
|
269
|
+
doc: string;
|
|
270
|
+
values: VocoderOverrides;
|
|
271
|
+
}>;
|
|
272
|
+
|
|
273
|
+
/** Presets over the defaults (all at 24 bands or fewer). */
|
|
274
|
+
export const VOCODER_PRESETS: Readonly<
|
|
275
|
+
Record<VocoderPresetName, VocoderPreset>
|
|
276
|
+
> = Object.freeze({
|
|
277
|
+
classic: { doc: "70s/80s band vocoder lead", values: {} },
|
|
278
|
+
robot: {
|
|
279
|
+
doc: "monotone machine voice on a drone",
|
|
280
|
+
values: {
|
|
281
|
+
bands: 12,
|
|
282
|
+
carrier: "pulse",
|
|
283
|
+
follow: "drone",
|
|
284
|
+
root: 45,
|
|
285
|
+
release: 0.03,
|
|
286
|
+
unvoiced: 0.3,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
talkbox: {
|
|
290
|
+
doc: "wet mouth-shaped lead, sharp vowels",
|
|
291
|
+
values: { mode: "talkbox", carrier: "saw", unvoiced: 0.3, hiss: 0.1 },
|
|
292
|
+
},
|
|
293
|
+
choir: {
|
|
294
|
+
doc: "stereo vocal-stack chord pad",
|
|
295
|
+
values: {
|
|
296
|
+
bands: 24,
|
|
297
|
+
spread: 0.25,
|
|
298
|
+
follow: "chords",
|
|
299
|
+
release: 0.08,
|
|
300
|
+
width: 1.3,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
glass: {
|
|
304
|
+
doc: "bright crystalline lead",
|
|
305
|
+
values: {
|
|
306
|
+
bands: 24,
|
|
307
|
+
carrier: "saw",
|
|
308
|
+
formant: 3,
|
|
309
|
+
hiss: 0.3,
|
|
310
|
+
attack: 0.001,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
whisper: {
|
|
314
|
+
doc: "breathy unpitched ghost voice",
|
|
315
|
+
values: { bands: 24, carrier: "noise", unvoiced: 0, hiss: 0.2 },
|
|
316
|
+
},
|
|
317
|
+
smear: {
|
|
318
|
+
doc: "blurred vowel wash (try freeze)",
|
|
319
|
+
values: {
|
|
320
|
+
bands: 24,
|
|
321
|
+
follow: "chords",
|
|
322
|
+
release: 0.6,
|
|
323
|
+
attack: 0.02,
|
|
324
|
+
width: 1.5,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
lofi: {
|
|
328
|
+
doc: "narrow murky 8-band",
|
|
329
|
+
values: {
|
|
330
|
+
bands: 8,
|
|
331
|
+
carrier: "pulse",
|
|
332
|
+
formant: -3,
|
|
333
|
+
width: 0.7,
|
|
334
|
+
hi: 4000,
|
|
335
|
+
// The narrow layout loses ~5 dB the fixed makeup does not restore.
|
|
336
|
+
gain: 5,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
/** Parameters with a `vocoder-<param>` lane (freeze is a 0/1 step lane). */
|
|
342
|
+
export const VOCODER_LANE_PARAMS: readonly Readonly<{
|
|
343
|
+
param: string;
|
|
344
|
+
spec: NumberParam;
|
|
345
|
+
}>[] = Object.freeze([
|
|
346
|
+
...Object.entries(VOCODER_PARAMS)
|
|
347
|
+
.filter(
|
|
348
|
+
(entry): entry is [string, NumberParam] =>
|
|
349
|
+
entry[1].kind === "number" && entry[1].automate === true,
|
|
350
|
+
)
|
|
351
|
+
.map(([param, spec]) => Object.freeze({ param, spec })),
|
|
352
|
+
Object.freeze({
|
|
353
|
+
param: "freeze",
|
|
354
|
+
spec: num(0, 1, 0, 1, "hold the vowel while 1", { automate: true }),
|
|
355
|
+
}),
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
export function isVocoderPreset(name: string): name is VocoderPresetName {
|
|
359
|
+
return (VOCODER_PRESET_NAMES as readonly string[]).includes(name);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function isVocoderParam(name: string): boolean {
|
|
363
|
+
return Object.prototype.hasOwnProperty.call(VOCODER_PARAMS, name);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Strudel spelling (`att`, `rel`) to a vocoder parameter name. */
|
|
367
|
+
export function vocoderParamName(name: string): string | undefined {
|
|
368
|
+
if (isVocoderParam(name)) return name;
|
|
369
|
+
for (const [key, spec] of Object.entries(VOCODER_PARAMS))
|
|
370
|
+
if (spec.strudel?.includes(name)) return key;
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Validates a `vocoder` field: `{ src?, preset?, ...overrides }`, null or
|
|
376
|
+
* absent meaning none. Keys come out in canonical order (src, preset, then
|
|
377
|
+
* `VOCODER_PARAMS` order). `{}` is kept: it means the default preset with no
|
|
378
|
+
* modulator (the track plays dry). Whether `src` names another track is the
|
|
379
|
+
* score's check.
|
|
380
|
+
*/
|
|
381
|
+
export function normalizeVocoder(input: unknown): TrackVocoder | undefined {
|
|
382
|
+
if (input === undefined || input === null) return undefined;
|
|
383
|
+
if (!isRecord(input))
|
|
384
|
+
throw new FxValidationError("track vocoder must be an object or null");
|
|
385
|
+
const params: Record<string, unknown> = {};
|
|
386
|
+
let presetName: VocoderPresetName | undefined;
|
|
387
|
+
let src: string | undefined;
|
|
388
|
+
let gate: number | "auto" | undefined;
|
|
389
|
+
for (const [key, value] of Object.entries(input)) {
|
|
390
|
+
if (value === undefined || value === null) continue;
|
|
391
|
+
if (key === "src") {
|
|
392
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 64)
|
|
393
|
+
throw new FxValidationError(
|
|
394
|
+
"vocoder src must be a track id (1 to 64 characters)",
|
|
395
|
+
);
|
|
396
|
+
src = value;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (key === "preset") {
|
|
400
|
+
const lower = typeof value === "string" ? value.toLowerCase() : "";
|
|
401
|
+
if (!isVocoderPreset(lower))
|
|
402
|
+
throw new FxValidationError(
|
|
403
|
+
`vocoder preset must be one of ${VOCODER_PRESET_NAMES.join(", ")}`,
|
|
404
|
+
);
|
|
405
|
+
presetName = lower;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const name = vocoderParamName(key);
|
|
409
|
+
if (!name)
|
|
410
|
+
throw new FxValidationError(
|
|
411
|
+
`vocoder has no parameter "${key.slice(0, 32)}"`,
|
|
412
|
+
);
|
|
413
|
+
if (name === "gate" && value === VOCODER_GATE_AUTO) {
|
|
414
|
+
gate = VOCODER_GATE_AUTO;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
params[name] = value;
|
|
418
|
+
}
|
|
419
|
+
const values = normalizeParams(VOCODER_PARAMS, params, "vocoder", false);
|
|
420
|
+
const out: Record<string, unknown> = {};
|
|
421
|
+
if (src !== undefined) out.src = src;
|
|
422
|
+
if (presetName) out.preset = presetName;
|
|
423
|
+
for (const key of Object.keys(VOCODER_PARAMS)) {
|
|
424
|
+
if (key === "gate" && gate !== undefined) out.gate = gate;
|
|
425
|
+
else if (values[key] !== undefined) out[key] = values[key];
|
|
426
|
+
}
|
|
427
|
+
return Object.freeze(out) as TrackVocoder;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** The preset a `vocoder` field plays. */
|
|
431
|
+
export function vocoderPresetOf(
|
|
432
|
+
vocoder: TrackVocoder | undefined,
|
|
433
|
+
): VocoderPresetName {
|
|
434
|
+
return vocoder?.preset ?? DEFAULT_VOCODER_PRESET;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The settings a `vocoder` field plays: defaults, then the preset, then
|
|
439
|
+
* overrides, then lane values from `lane`. A talkbox's formant is clamped
|
|
440
|
+
* to ±12 st.
|
|
441
|
+
*/
|
|
442
|
+
export function resolveVocoder(
|
|
443
|
+
vocoder: TrackVocoder | undefined,
|
|
444
|
+
lane?: (param: string) => number | undefined,
|
|
445
|
+
): ResolvedVocoder {
|
|
446
|
+
const out: Record<string, unknown> = {
|
|
447
|
+
...VOCODER_DEFAULTS,
|
|
448
|
+
...VOCODER_PRESETS[vocoderPresetOf(vocoder)].values,
|
|
449
|
+
};
|
|
450
|
+
if (vocoder)
|
|
451
|
+
for (const [key, value] of Object.entries(vocoder))
|
|
452
|
+
if (key !== "preset" && key !== "src" && value !== undefined)
|
|
453
|
+
out[key] = value;
|
|
454
|
+
if (lane)
|
|
455
|
+
for (const { param } of VOCODER_LANE_PARAMS) {
|
|
456
|
+
const value = lane(param);
|
|
457
|
+
if (value === undefined) continue;
|
|
458
|
+
out[param] = param === "freeze" ? value >= 0.5 : value;
|
|
459
|
+
}
|
|
460
|
+
if (out.mode === "talkbox")
|
|
461
|
+
out.formant = Math.max(
|
|
462
|
+
-TALKBOX_FORMANT_MAX,
|
|
463
|
+
Math.min(TALKBOX_FORMANT_MAX, out.formant as number),
|
|
464
|
+
);
|
|
465
|
+
return Object.freeze(out) as ResolvedVocoder;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** The value one key plays (for menu rows and `/vocoder <param>`). */
|
|
469
|
+
export function vocoderValue(
|
|
470
|
+
vocoder: TrackVocoder | undefined,
|
|
471
|
+
key: string,
|
|
472
|
+
): number | string | boolean | undefined {
|
|
473
|
+
return (resolveVocoder(vocoder) as Record<string, unknown>)[key] as
|
|
474
|
+
number | string | boolean | undefined;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Ring after the modulator stops: the release plus 50 ms, at most 2.05 s. */
|
|
478
|
+
export function vocoderTailSeconds(vocoder: TrackVocoder | undefined): number {
|
|
479
|
+
if (!vocoder) return 0;
|
|
480
|
+
return Math.min(2.05, resolveVocoder(vocoder).release + 0.05);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Pre-roll a window needs so its envelopes have settled. */
|
|
484
|
+
export function vocoderPrerollSeconds(
|
|
485
|
+
vocoder: TrackVocoder | undefined,
|
|
486
|
+
): number {
|
|
487
|
+
if (!vocoder) return 0;
|
|
488
|
+
return Math.max(0.5, 5 * resolveVocoder(vocoder).release + 0.1);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Score-level checks over a track list: `src` never names its own track,
|
|
493
|
+
* at most `maxTracks` vocoder tracks, and no modulator cycle (A vocodes B
|
|
494
|
+
* while B vocodes A). Returns an error message or undefined. `src` naming
|
|
495
|
+
* no track is left to the edit surfaces, because a preview or live score
|
|
496
|
+
* holds only some of the song's tracks.
|
|
497
|
+
*/
|
|
498
|
+
export function vocoderScoreError(
|
|
499
|
+
tracks: readonly Readonly<{ id: string; vocoder?: TrackVocoder }>[],
|
|
500
|
+
maxTracks: number,
|
|
501
|
+
): string | undefined {
|
|
502
|
+
const vocoded = tracks.filter((track) => track.vocoder);
|
|
503
|
+
if (vocoded.length > maxTracks)
|
|
504
|
+
return `a song holds at most ${maxTracks} vocoder tracks`;
|
|
505
|
+
const src = new Map<string, string>();
|
|
506
|
+
for (const track of vocoded) {
|
|
507
|
+
const from = track.vocoder!.src;
|
|
508
|
+
if (from === undefined) continue;
|
|
509
|
+
if (from === track.id)
|
|
510
|
+
return `track ${track.id} cannot vocode itself (vocoder src)`;
|
|
511
|
+
src.set(track.id, from);
|
|
512
|
+
}
|
|
513
|
+
for (const start of src.keys()) {
|
|
514
|
+
const path = [start];
|
|
515
|
+
let at = src.get(start);
|
|
516
|
+
while (at !== undefined) {
|
|
517
|
+
if (at === start) return `vocoder cycle: ${[...path, at].join(" -> ")}`;
|
|
518
|
+
if (path.includes(at)) break;
|
|
519
|
+
path.push(at);
|
|
520
|
+
at = src.get(at);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
package/guides/agent.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: agent
|
|
3
|
+
title: Agent
|
|
4
|
+
order: 11
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The agent is optional: commands work offline. With a provider, plain
|
|
8
|
+
requests go to it, and it types the commands it runs so you learn them.
|
|
9
|
+
|
|
10
|
+
## Ask
|
|
11
|
+
|
|
12
|
+
- "make the reverb wetter and play a C minor arpeggio"
|
|
13
|
+
|
|
14
|
+
## Type it yourself
|
|
15
|
+
|
|
16
|
+
- `model key` · `model` picker · `model fast` · `logout`
|
|
17
|
+
- `showme on` · `showme quiet` · `/auth --check`
|
|
18
|
+
|
|
19
|
+
## Menu
|
|
20
|
+
|
|
21
|
+
- Ctrl-K › Project › agent › model · show-me · model key
|
|
22
|
+
|
|
23
|
+
## Keys
|
|
24
|
+
|
|
25
|
+
- Enter sends · Alt-Enter runs next · Ctrl-Q NOW/NEXT · Esc stops
|
|
26
|
+
|
|
27
|
+
## Next
|
|
28
|
+
|
|
29
|
+
- `guide show-me` · `guide providers` · `guide web-search`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: arrange
|
|
3
|
+
title: Arrange
|
|
4
|
+
order: 9
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Notes make tracks, bars make sections, sections make the form. Name a
|
|
8
|
+
range of bars and loop, copy or move it.
|
|
9
|
+
|
|
10
|
+
## Ask
|
|
11
|
+
|
|
12
|
+
- "loop bars 5 to 6" · "copy the bass in 5-6 to bar 7 twice"
|
|
13
|
+
|
|
14
|
+
## Type it yourself
|
|
15
|
+
|
|
16
|
+
- `loop 5-6` · `loop chorus` · `loop next` · `loop off`
|
|
17
|
+
- `copy bass 5-6 to 7 x2` · `move bass 5-6 to 9` · `clear bass 5-6`
|
|
18
|
+
- `copy all 1-4` then `paste at 9` · `reverse bass 5-6` · `jump 5`
|
|
19
|
+
- `section chorus 9-16` · `form verse chorus` · `form print`
|
|
20
|
+
|
|
21
|
+
## Menu
|
|
22
|
+
|
|
23
|
+
- Ctrl-K › Arrange › tracks · sections · form · style
|
|
24
|
+
|
|
25
|
+
## Keys
|
|
26
|
+
|
|
27
|
+
- Ctrl-T TAPE: every track across the bars · `\` loops the section
|
|
28
|
+
|
|
29
|
+
## Next
|
|
30
|
+
|
|
31
|
+
- `guide tape` · `guide notes` · `guide tracks` · `guide panes`
|
package/guides/audio.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: audio
|
|
3
|
+
title: Audio output and input
|
|
4
|
+
parent: project
|
|
5
|
+
order: 3
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
One output and one input, or the system default. Saved on this
|
|
9
|
+
machine, not in the song. Needs the native sink (`dawg doctor`).
|
|
10
|
+
|
|
11
|
+
## Ask
|
|
12
|
+
|
|
13
|
+
- "which audio output am I using?"
|
|
14
|
+
|
|
15
|
+
## Type it yourself
|
|
16
|
+
|
|
17
|
+
- `audio` · `audio out USB Audio Interface` · `audio out default`
|
|
18
|
+
- `audio in Built-in Microphone` · `audio test` (tone, input meter)
|
|
19
|
+
|
|
20
|
+
## Menu
|
|
21
|
+
|
|
22
|
+
- Ctrl-K › Project › audio › output · input · test
|
|
23
|
+
|
|
24
|
+
## Keys
|
|
25
|
+
|
|
26
|
+
- In a device list: arrows move, Enter picks (a soft blip plays), Esc
|
|
27
|
+
|
|
28
|
+
## Next
|
|
29
|
+
|
|
30
|
+
- Unplugged mid-song: playback moves to the default, said once
|
|
31
|
+
- `DAWG_AUDIO_DEVICE` overrides · `guide project`
|
package/guides/audition.md
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: audition
|
|
3
3
|
title: Menu and audition loop
|
|
4
|
-
parent:
|
|
5
|
-
order:
|
|
4
|
+
parent: keys
|
|
5
|
+
order: 1
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Each menu row shows its value and, dimmed, the command it runs, so the
|
|
9
|
+
menu teaches the commands. Changes are staged on a loop until you keep.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Ask
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
- Enter on a number opens its fader drawer (see Faders and the mouse)
|
|
15
|
-
- `x` reset · `/` filter · Esc back
|
|
16
|
-
- `/menu effects` opens a section (sound effects rhythm chords mix project)
|
|
13
|
+
- "try a darker filter on the lead before keeping it"
|
|
17
14
|
|
|
18
|
-
##
|
|
15
|
+
## Type it yourself
|
|
19
16
|
|
|
20
|
-
-
|
|
21
|
-
|
|
17
|
+
- `menu effects` opens a topic · `try fx reverb mix 0.6` stages one
|
|
18
|
+
|
|
19
|
+
## Menu
|
|
20
|
+
|
|
21
|
+
- Ctrl-K opens Sound, Voice, Effects, Rhythm, Chords and key, Mix,
|
|
22
|
+
Arrange and Project: the same topics as `help` and `guide`
|
|
23
|
+
|
|
24
|
+
## Keys
|
|
25
|
+
|
|
26
|
+
- ↑↓ move · → or Enter open · ←→ adjust a value · `x` reset · Esc back
|
|
27
|
+
- Space loops the track · `a` A/B · `c` solo or in context
|
|
22
28
|
- Enter keeps every staged change as one undo step · Esc reverts
|
|
23
29
|
|
|
24
|
-
##
|
|
30
|
+
## Next
|
|
25
31
|
|
|
26
|
-
-
|
|
32
|
+
- `guide faders` · `guide keys`
|
package/guides/automation.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: automation
|
|
3
3
|
title: Automation
|
|
4
|
-
parent:
|
|
5
|
-
order:
|
|
4
|
+
parent: mix
|
|
5
|
+
order: 1
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
A lane moves a value over beats. Every numeric effect parameter has one.
|
|
@@ -16,12 +16,17 @@ A lane moves a value over beats. Every numeric effect parameter has one.
|
|
|
16
16
|
|
|
17
17
|
- `automate volume at 0 0.2` · `automate volume at 4 1`
|
|
18
18
|
- `automate filter points 0:400 4:6000` several points at once
|
|
19
|
-
- `automate
|
|
20
|
-
- `automate distort-drive points 0:1 8:6`
|
|
21
|
-
- `automate filter remove 4` delete one point
|
|
22
|
-
- `automate grain-pos points 0:0.1 8:0.9` sweeps a granular track's head (every movable `grain-<param>`)
|
|
19
|
+
- `automate delay-mix at 4 0` · `automate filter remove 4`
|
|
23
20
|
- `clear filter automation` · `clear automation`
|
|
24
21
|
|
|
25
22
|
## Menu
|
|
26
23
|
|
|
27
|
-
- Ctrl-K › Mix › automation: each lane, add points, ramp, clear
|
|
24
|
+
- Ctrl-K › Mix › automation: each lane, add points, ramp, clear
|
|
25
|
+
|
|
26
|
+
## Keys
|
|
27
|
+
|
|
28
|
+
- In a lane row ←→ moves the point · `x` clears the lane
|
|
29
|
+
|
|
30
|
+
## Next
|
|
31
|
+
|
|
32
|
+
- `guide mix` · `guide resample`
|