@hraness/dawg 0.5.0 → 0.6.1
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 +63 -0
- package/DAWG.md +592 -49
- package/README.md +4 -4
- package/core/chords.ts +492 -4
- package/core/diff.ts +9 -0
- package/core/expression.ts +143 -6
- package/core/fx.ts +689 -2
- package/core/granular.ts +619 -0
- package/core/instruments.ts +442 -0
- package/core/keys.ts +955 -0
- package/core/resonators.ts +862 -0
- package/core/score.ts +529 -8
- package/core/sdk/eval-child.ts +2 -0
- package/core/sdk/print.ts +247 -16
- package/core/sdk/sync-instruments.ts +58 -0
- package/core/sdk/v1.ts +2417 -40
- package/core/sections.ts +44 -10
- package/core/strings.ts +1080 -0
- package/core/winds.ts +652 -0
- package/guides/automation.md +1 -0
- package/guides/chords.md +3 -1
- package/guides/effects.md +5 -2
- package/guides/media.md +1 -1
- package/guides/performance.md +3 -0
- package/guides/resample.md +26 -0
- package/guides/sounds.md +7 -2
- package/guides/tempo.md +1 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +13 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/granular-tools.ts +138 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +42 -2
- package/src/agent/preview-tool.ts +7 -1
- package/src/agent/resample-tool.ts +131 -0
- package/src/agent/tools.ts +726 -17
- package/src/agent/xcb-agent.ts +13 -0
- package/src/audio/arrange.ts +42 -3
- package/src/audio/dsp/bank.ts +233 -0
- package/src/audio/dsp/envelope.ts +161 -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/shift.ts +256 -0
- package/src/audio/dsp/stft.ts +73 -0
- package/src/audio/dsp/window.ts +77 -0
- package/src/audio/effects/chain.ts +23 -3
- package/src/audio/effects/common.ts +14 -0
- package/src/audio/effects/convolution.ts +106 -1
- package/src/audio/effects/gaze.ts +354 -0
- 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 +56 -6
- package/src/audio/fit.ts +447 -0
- package/src/audio/granular.ts +995 -0
- package/src/audio/instrument-check.ts +137 -0
- package/src/audio/instruments.ts +140 -0
- package/src/audio/keys/dsp.ts +323 -0
- package/src/audio/keys/electric.ts +420 -0
- package/src/audio/keys/engine.ts +485 -0
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +476 -0
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/live-worker.ts +56 -0
- package/src/audio/live.ts +361 -15
- package/src/audio/loudness.ts +65 -20
- package/src/audio/preview.ts +17 -1
- package/src/audio/resample.ts +285 -0
- package/src/audio/resonators.ts +295 -0
- package/src/audio/sampler.ts +328 -35
- package/src/audio/samples.ts +56 -8
- package/src/audio/strings/body.ts +263 -0
- package/src/audio/strings/bow.ts +656 -0
- package/src/audio/strings/engine.ts +615 -0
- package/src/audio/strings/loop.ts +119 -0
- package/src/audio/strings/measure.test-helpers.ts +200 -0
- package/src/audio/strings/pluck.ts +354 -0
- package/src/audio/warp.ts +61 -0
- package/src/audio/wav.ts +191 -19
- package/src/audio/winds/engine.ts +302 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +80 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/voice.ts +479 -0
- package/src/commands/expression.ts +111 -23
- package/src/commands/fit.ts +135 -0
- package/src/commands/fx.ts +35 -1
- package/src/commands/granular.ts +432 -0
- package/src/commands/help.ts +159 -5
- package/src/commands/keys.ts +629 -0
- package/src/commands/modal.ts +310 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rig.ts +264 -0
- package/src/commands/sample.ts +52 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/string.ts +201 -0
- package/src/commands/strum.ts +473 -0
- package/src/commands/time.ts +4 -1
- package/src/commands/wind.ts +244 -0
- package/src/main.ts +339 -15
- package/src/project/check.ts +17 -0
- package/src/render.ts +49 -4
- package/src/session/presence.ts +16 -3
- package/src/tui/audition.ts +2 -2
- package/src/tui/granular-menu.ts +278 -0
- package/src/tui/menu.ts +934 -9
- package/src/tui/modal-menu.ts +145 -0
- package/src/tui/performance-menu.ts +42 -0
- package/src/tui/play-chords.ts +104 -3
- package/src/tui/play-mode.ts +1 -0
- package/src/tui/play-session.ts +70 -2
- package/src/tui/wind-menu.ts +144 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `grain` prompt command: the focused track's granular instrument
|
|
3
|
+
* (core/granular.ts, `Track.granular`).
|
|
4
|
+
*
|
|
5
|
+
* grain this track's preset, source and overrides
|
|
6
|
+
* grain presets the granular presets
|
|
7
|
+
* grain <preset> | preset <name> play a preset (instrument "granular")
|
|
8
|
+
* grain on [voice V] grain this track's own sound
|
|
9
|
+
* grain src synth:<name>[@note] a built-in synth source
|
|
10
|
+
* grain src voice <V> a sampler voice of this track as source
|
|
11
|
+
* grain <param> <value> [...] override parameters (`off` unsets)
|
|
12
|
+
* grain reset drop the overrides, keep preset and source
|
|
13
|
+
* grain off back to the track's previous voice
|
|
14
|
+
*
|
|
15
|
+
* On a synth track the source is that synth (`synth:<instrument>`), on a
|
|
16
|
+
* sampler the first (or named) voice, pinned like the sampler voice. Each
|
|
17
|
+
* command is one `updateTrack` revision and one undo step.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_GRANULAR_SOURCE,
|
|
21
|
+
GRANULAR_INSTRUMENT,
|
|
22
|
+
GRANULAR_PARAMS,
|
|
23
|
+
GRANULAR_PRESET_NAMES,
|
|
24
|
+
GRANULAR_PRESETS,
|
|
25
|
+
granularSourceLabel,
|
|
26
|
+
isGranularInstrument,
|
|
27
|
+
isGranularPreset,
|
|
28
|
+
normalizeGranular,
|
|
29
|
+
resolveGranular,
|
|
30
|
+
parseSynthSource,
|
|
31
|
+
synthSourceNames,
|
|
32
|
+
SYNTH_SOURCE_PREFIX,
|
|
33
|
+
type GranularPresetName,
|
|
34
|
+
type GranularSource,
|
|
35
|
+
type TrackGranular,
|
|
36
|
+
} from "../../core/granular.ts";
|
|
37
|
+
import { isDrumInstrument } from "../../core/drums.ts";
|
|
38
|
+
import { FxValidationError } from "../../core/params.ts";
|
|
39
|
+
import {
|
|
40
|
+
ScoreValidationError,
|
|
41
|
+
updateTrack,
|
|
42
|
+
type Track,
|
|
43
|
+
type TrackScore,
|
|
44
|
+
} from "../../core/score.ts";
|
|
45
|
+
import { pitchToMidi } from "../../core/pitch.ts";
|
|
46
|
+
import { normalizeSynth, SYNTH_PRESETS } from "../../core/synth.ts";
|
|
47
|
+
import { parseParamValue } from "./fx.ts";
|
|
48
|
+
|
|
49
|
+
/** `grain 0.12s · overlap 6 · scan 0.25x …`; overrides marked `*`. */
|
|
50
|
+
function basicsLine(settings: TrackGranular | undefined): string {
|
|
51
|
+
const resolved = resolveGranular(settings) as Record<string, unknown>;
|
|
52
|
+
return GRANULAR_SIMPLE_PARAMS.map((name) => {
|
|
53
|
+
const spec = GRANULAR_PARAMS[name]!;
|
|
54
|
+
const value = resolved[name];
|
|
55
|
+
const unit =
|
|
56
|
+
spec.kind === "number" && spec.unit
|
|
57
|
+
? spec.unit
|
|
58
|
+
: name === "scan"
|
|
59
|
+
? "x"
|
|
60
|
+
: "";
|
|
61
|
+
const shown =
|
|
62
|
+
typeof value === "number"
|
|
63
|
+
? `${Number(value.toFixed(3))}${unit}`
|
|
64
|
+
: String(value);
|
|
65
|
+
const mark =
|
|
66
|
+
settings && Object.prototype.hasOwnProperty.call(settings, name)
|
|
67
|
+
? "*"
|
|
68
|
+
: "";
|
|
69
|
+
return `${name} ${shown}${mark}`;
|
|
70
|
+
}).join(" · ");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The basics shown first in the menu and the `grain` listing. */
|
|
74
|
+
export const GRANULAR_SIMPLE_PARAMS = Object.freeze([
|
|
75
|
+
"grain",
|
|
76
|
+
"overlap",
|
|
77
|
+
"scan",
|
|
78
|
+
"pos",
|
|
79
|
+
"spray",
|
|
80
|
+
"pitch",
|
|
81
|
+
"shimmer",
|
|
82
|
+
"spread",
|
|
83
|
+
"freeze",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
export type GranularCommand =
|
|
87
|
+
| { type: "grain-list" }
|
|
88
|
+
| { type: "grain-presets" }
|
|
89
|
+
| { type: "grain-reset" }
|
|
90
|
+
| { type: "grain-off" }
|
|
91
|
+
| { type: "grain-on"; voice?: string }
|
|
92
|
+
| { type: "grain-preset"; preset: GranularPresetName; voice?: string }
|
|
93
|
+
| { type: "grain-src"; synth?: string; voice?: string }
|
|
94
|
+
| {
|
|
95
|
+
type: "grain-set";
|
|
96
|
+
/** `null` unsets a parameter (the preset's value again). */
|
|
97
|
+
values: Readonly<Record<string, number | string | boolean | null>>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const VOICE_NAME = /^[a-z0-9._-]{1,64}$/;
|
|
101
|
+
|
|
102
|
+
export function parseGranularCommand(
|
|
103
|
+
prompt: string,
|
|
104
|
+
): GranularCommand | undefined {
|
|
105
|
+
const words = prompt.trim().split(/\s+/);
|
|
106
|
+
const head = words[0]?.toLowerCase();
|
|
107
|
+
if (head !== "grain" && head !== "granular") return undefined;
|
|
108
|
+
if (words.length === 1) return { type: "grain-list" };
|
|
109
|
+
if (prompt.length > 1_024) return undefined;
|
|
110
|
+
const rest = words.slice(1).map((word) => word.toLowerCase());
|
|
111
|
+
const voiceAt = (index: number): string | undefined | null => {
|
|
112
|
+
if (rest.length === index) return undefined;
|
|
113
|
+
if (
|
|
114
|
+
rest.length === index + 2 &&
|
|
115
|
+
rest[index] === "voice" &&
|
|
116
|
+
VOICE_NAME.test(rest[index + 1]!)
|
|
117
|
+
)
|
|
118
|
+
return rest[index + 1]!;
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
if (rest[0] === "reset" && rest.length === 1) return { type: "grain-reset" };
|
|
122
|
+
if (rest[0] === "off" && rest.length === 1) return { type: "grain-off" };
|
|
123
|
+
if ((rest[0] === "presets" || rest[0] === "list") && rest.length === 1)
|
|
124
|
+
return { type: "grain-presets" };
|
|
125
|
+
if (rest[0] === "on") {
|
|
126
|
+
const voice = voiceAt(1);
|
|
127
|
+
return voice === null ? undefined : { type: "grain-on", voice };
|
|
128
|
+
}
|
|
129
|
+
// `hold` is a preset and a parameter: `grain hold 4` sets the parameter.
|
|
130
|
+
const presetWord =
|
|
131
|
+
rest[0] === "preset" ||
|
|
132
|
+
(isGranularPreset(rest[0]!) &&
|
|
133
|
+
(rest.length === 1 ||
|
|
134
|
+
rest[1] === "voice" ||
|
|
135
|
+
!GRANULAR_PARAMS[rest[0]!] ||
|
|
136
|
+
rest.length % 2 !== 0));
|
|
137
|
+
if (presetWord) {
|
|
138
|
+
const at = rest[0] === "preset" ? 1 : 0;
|
|
139
|
+
const name = rest[at];
|
|
140
|
+
if (!name || !isGranularPreset(name)) return undefined;
|
|
141
|
+
const voice = voiceAt(at + 1);
|
|
142
|
+
return voice === null
|
|
143
|
+
? undefined
|
|
144
|
+
: { type: "grain-preset", preset: name, voice };
|
|
145
|
+
}
|
|
146
|
+
if (rest[0] === "src" || rest[0] === "source") {
|
|
147
|
+
if (rest.length === 3 && rest[1] === "voice" && VOICE_NAME.test(rest[2]!))
|
|
148
|
+
return { type: "grain-src", voice: rest[2]! };
|
|
149
|
+
if (rest.length !== 2) return undefined;
|
|
150
|
+
const text = rest[1]!.startsWith(SYNTH_SOURCE_PREFIX)
|
|
151
|
+
? rest[1]!
|
|
152
|
+
: `${SYNTH_SOURCE_PREFIX}${rest[1]!}`;
|
|
153
|
+
return parseSynthSource(text)
|
|
154
|
+
? { type: "grain-src", synth: text }
|
|
155
|
+
: undefined;
|
|
156
|
+
}
|
|
157
|
+
if (rest.length % 2 !== 0) return undefined;
|
|
158
|
+
const values: Record<string, number | string | boolean | null> = {};
|
|
159
|
+
for (let index = 0; index < rest.length; index += 2) {
|
|
160
|
+
const name = rest[index]!;
|
|
161
|
+
const spec = GRANULAR_PARAMS[name];
|
|
162
|
+
if (!spec) return undefined;
|
|
163
|
+
// Dotted note values as the rest of dawg writes them: `1/8.` is `1/8d`.
|
|
164
|
+
const word =
|
|
165
|
+
name === "sync" ? rest[index + 1]!.replace(/\.$/, "d") : rest[index + 1]!;
|
|
166
|
+
if ((word === "off" || word === "unset") && spec.kind !== "boolean") {
|
|
167
|
+
values[name] = null;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const value =
|
|
171
|
+
name === "root" && !/^\d/.test(word)
|
|
172
|
+
? noteNumber(word)
|
|
173
|
+
: parseParamValue(spec, word);
|
|
174
|
+
if (value === undefined) return undefined;
|
|
175
|
+
// 0.6.1 switches: off removes the field, so on-then-off leaves the
|
|
176
|
+
// score (and its sha256) as it was.
|
|
177
|
+
values[name] =
|
|
178
|
+
value === false && (name === "mono" || name === "pedal") ? null : value;
|
|
179
|
+
}
|
|
180
|
+
return { type: "grain-set", values };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** A note name (`c4`, `f#3`) as its MIDI number. */
|
|
184
|
+
function noteNumber(word: string): number | undefined {
|
|
185
|
+
const midi = pitchToMidi(word);
|
|
186
|
+
return Number.isFinite(midi) ? midi : undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** `cloud · synth:pad · scan 0.1`, preset first, then source and overrides. */
|
|
190
|
+
export function describeGranular(settings: TrackGranular | undefined): string {
|
|
191
|
+
const parts = [
|
|
192
|
+
settings?.preset ?? "default",
|
|
193
|
+
granularSourceLabel(settings?.src),
|
|
194
|
+
];
|
|
195
|
+
for (const [key, value] of Object.entries(settings ?? {}))
|
|
196
|
+
if (
|
|
197
|
+
key !== "preset" &&
|
|
198
|
+
key !== "src" &&
|
|
199
|
+
key !== "from" &&
|
|
200
|
+
value !== undefined
|
|
201
|
+
)
|
|
202
|
+
parts.push(`${key} ${String(value)}`);
|
|
203
|
+
return parts.join(" · ");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The source a track's own sound gives: a sampler voice (the named one or
|
|
208
|
+
* the first), the track's synth when it is a known synth source, else the
|
|
209
|
+
* built-in pad.
|
|
210
|
+
*/
|
|
211
|
+
export function ownGranularSource(
|
|
212
|
+
track: Track,
|
|
213
|
+
voice?: string,
|
|
214
|
+
): { src: GranularSource } | { error: string } {
|
|
215
|
+
if (track.sampler) {
|
|
216
|
+
const names = Object.keys(track.sampler.voices);
|
|
217
|
+
const name = voice ?? names[0];
|
|
218
|
+
const ref = name === undefined ? undefined : track.sampler.voices[name];
|
|
219
|
+
if (!ref)
|
|
220
|
+
return {
|
|
221
|
+
error: `no sampler voice ${voice ?? ""} on ${track.id} (${names.join(" ") || "none"})`,
|
|
222
|
+
};
|
|
223
|
+
return { src: ref };
|
|
224
|
+
}
|
|
225
|
+
if (voice !== undefined)
|
|
226
|
+
return { error: `${track.id} has no sampler voices` };
|
|
227
|
+
if (track.granular?.src !== undefined) return { src: track.granular.src };
|
|
228
|
+
// `synth preset pad` stores supersaw plus pad's params: grain the preset.
|
|
229
|
+
const preset = synthPresetOf(track);
|
|
230
|
+
if (preset) return { src: `${SYNTH_SOURCE_PREFIX}${preset}` };
|
|
231
|
+
// A sound plays with the track's own synth params (src/audio/granular.ts).
|
|
232
|
+
if (synthSourceNames().includes(track.instrument))
|
|
233
|
+
return { src: `${SYNTH_SOURCE_PREFIX}${track.instrument}` };
|
|
234
|
+
return { src: DEFAULT_GRANULAR_SOURCE };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The synth preset a track's instrument and synth params are, if any. */
|
|
238
|
+
export function synthPresetOf(track: Track): string | undefined {
|
|
239
|
+
if (!track.synth) return undefined;
|
|
240
|
+
const own = JSON.stringify(track.synth);
|
|
241
|
+
for (const [name, preset] of Object.entries(SYNTH_PRESETS))
|
|
242
|
+
if (
|
|
243
|
+
preset.instrument === track.instrument &&
|
|
244
|
+
JSON.stringify(normalizeSynth({ ...preset.synth })) === own
|
|
245
|
+
)
|
|
246
|
+
return name;
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export type GranularResult = Readonly<{
|
|
251
|
+
ok: boolean;
|
|
252
|
+
message: string;
|
|
253
|
+
next?: TrackScore;
|
|
254
|
+
kind?: string;
|
|
255
|
+
payload?: Record<string, unknown>;
|
|
256
|
+
}>;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The patch a granular change writes: instrument granular plus the
|
|
260
|
+
* validated settings, or `null` settings to turn it off.
|
|
261
|
+
*/
|
|
262
|
+
export function applyGranularCommand(
|
|
263
|
+
score: TrackScore,
|
|
264
|
+
trackId: string,
|
|
265
|
+
command: GranularCommand,
|
|
266
|
+
): GranularResult {
|
|
267
|
+
const track = score.tracks.find((candidate) => candidate.id === trackId);
|
|
268
|
+
if (!track) return { ok: false, message: `no track · ${trackId}` };
|
|
269
|
+
if (command.type === "grain-presets")
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
message: `grain presets · ${GRANULAR_PRESET_NAMES.map((name) => `${name} (${GRANULAR_PRESETS[name].doc})`).join(" · ")}`,
|
|
273
|
+
};
|
|
274
|
+
if (track.kit || isDrumInstrument(track.instrument))
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
message: `grain · ${trackId} is a drum track; granular plays pitched notes`,
|
|
278
|
+
};
|
|
279
|
+
const active = isGranularInstrument(track.instrument);
|
|
280
|
+
if (command.type === "grain-list")
|
|
281
|
+
return {
|
|
282
|
+
ok: true,
|
|
283
|
+
message: active
|
|
284
|
+
? `grain · ${track.granular?.preset ?? "default"} · ${granularSourceLabel(track.granular?.src)} · ${basicsLine(track.granular)}`
|
|
285
|
+
: `grain · off (${track.instrument}) · grain cloud turns it on · grain presets`,
|
|
286
|
+
};
|
|
287
|
+
let instrument = track.instrument;
|
|
288
|
+
let granular: Record<string, unknown> | null;
|
|
289
|
+
let synthPatch: Record<string, unknown> | undefined;
|
|
290
|
+
const keepSrc = (): Record<string, unknown> | { error: string } => {
|
|
291
|
+
const own = ownGranularSource(track);
|
|
292
|
+
if ("error" in own) return own;
|
|
293
|
+
return own.src === DEFAULT_GRANULAR_SOURCE ? {} : { src: own.src };
|
|
294
|
+
};
|
|
295
|
+
if (command.type === "grain-off") {
|
|
296
|
+
if (!active) return { ok: true, message: "grain · already off" };
|
|
297
|
+
// A sampler keeps its voices; anything else returns to its synth
|
|
298
|
+
// source's voice (or sine), and the settings stay for `grain on`.
|
|
299
|
+
// The voice granular turned on from (`from`), else a wavetable, else
|
|
300
|
+
// the synth source's own instrument (a preset maps to its instrument
|
|
301
|
+
// and params), else sine.
|
|
302
|
+
const src = track.granular?.src;
|
|
303
|
+
const parsed = typeof src === "string" ? parseSynthSource(src) : undefined;
|
|
304
|
+
if (track.sampler) instrument = "sampler";
|
|
305
|
+
else if (track.granular?.from) instrument = track.granular.from;
|
|
306
|
+
else if (track.wavetable) instrument = "wavetable";
|
|
307
|
+
else if (parsed && SYNTH_PRESETS[parsed.name]) {
|
|
308
|
+
instrument = SYNTH_PRESETS[parsed.name]!.instrument;
|
|
309
|
+
if (!track.synth) synthPatch = { ...SYNTH_PRESETS[parsed.name]!.synth };
|
|
310
|
+
} else if (parsed) instrument = parsed.name;
|
|
311
|
+
else instrument = "sine";
|
|
312
|
+
granular = track.granular ? { ...track.granular } : null;
|
|
313
|
+
} else if (command.type === "grain-reset") {
|
|
314
|
+
if (!active) return { ok: true, message: "grain · already off" };
|
|
315
|
+
granular = {};
|
|
316
|
+
if (track.granular?.preset) granular.preset = track.granular.preset;
|
|
317
|
+
if (track.granular?.src !== undefined) granular.src = track.granular.src;
|
|
318
|
+
} else if (command.type === "grain-on" || command.type === "grain-preset") {
|
|
319
|
+
instrument = GRANULAR_INSTRUMENT;
|
|
320
|
+
const base: Record<string, unknown> =
|
|
321
|
+
active || (track.granular && command.voice === undefined)
|
|
322
|
+
? { ...track.granular }
|
|
323
|
+
: {};
|
|
324
|
+
if (command.voice !== undefined || base.src === undefined) {
|
|
325
|
+
const own =
|
|
326
|
+
command.voice !== undefined
|
|
327
|
+
? ownGranularSource(track, command.voice)
|
|
328
|
+
: ((): { src: GranularSource } | { error: string } => {
|
|
329
|
+
const kept = keepSrc();
|
|
330
|
+
if ("error" in kept) return kept as { error: string };
|
|
331
|
+
return {
|
|
332
|
+
src: (kept.src as GranularSource) ?? DEFAULT_GRANULAR_SOURCE,
|
|
333
|
+
};
|
|
334
|
+
})();
|
|
335
|
+
if ("error" in own) return { ok: false, message: `grain · ${own.error}` };
|
|
336
|
+
if (own.src === DEFAULT_GRANULAR_SOURCE) delete base.src;
|
|
337
|
+
else base.src = own.src;
|
|
338
|
+
}
|
|
339
|
+
if (command.type === "grain-preset") {
|
|
340
|
+
// A preset replaces the overrides; the source stays.
|
|
341
|
+
granular = { preset: command.preset };
|
|
342
|
+
if (base.src !== undefined) granular.src = base.src;
|
|
343
|
+
} else granular = base;
|
|
344
|
+
} else if (command.type === "grain-src") {
|
|
345
|
+
instrument = GRANULAR_INSTRUMENT;
|
|
346
|
+
granular = active || track.granular ? { ...track.granular } : {};
|
|
347
|
+
if (command.voice !== undefined) {
|
|
348
|
+
const own = ownGranularSource(track, command.voice);
|
|
349
|
+
if ("error" in own) return { ok: false, message: `grain · ${own.error}` };
|
|
350
|
+
granular.src = own.src;
|
|
351
|
+
} else if (command.synth === DEFAULT_GRANULAR_SOURCE) delete granular.src;
|
|
352
|
+
else granular.src = command.synth;
|
|
353
|
+
} else {
|
|
354
|
+
instrument = GRANULAR_INSTRUMENT;
|
|
355
|
+
granular = active && track.granular ? { ...track.granular } : {};
|
|
356
|
+
if (!active) {
|
|
357
|
+
const kept = keepSrc();
|
|
358
|
+
if ("error" in kept)
|
|
359
|
+
return { ok: false, message: `grain · ${kept.error}` };
|
|
360
|
+
Object.assign(granular, track.granular ?? {}, kept);
|
|
361
|
+
}
|
|
362
|
+
for (const [key, value] of Object.entries(command.values)) {
|
|
363
|
+
if (value === null) delete granular[key];
|
|
364
|
+
else granular[key] = value;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Remember the voice to go back to when granular turns on.
|
|
368
|
+
if (granular !== null && command.type !== "grain-off") {
|
|
369
|
+
const from = active
|
|
370
|
+
? track.granular?.from
|
|
371
|
+
: track.sampler || isGranularInstrument(track.instrument)
|
|
372
|
+
? undefined
|
|
373
|
+
: track.instrument;
|
|
374
|
+
if (from) granular.from = from;
|
|
375
|
+
else delete granular.from;
|
|
376
|
+
}
|
|
377
|
+
let next: TrackScore;
|
|
378
|
+
try {
|
|
379
|
+
next = updateTrack(score, trackId, {
|
|
380
|
+
instrument,
|
|
381
|
+
granular: granular === null ? null : granularOrEmpty(granular),
|
|
382
|
+
...(synthPatch ? { synth: synthPatch as never } : {}),
|
|
383
|
+
});
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (
|
|
386
|
+
error instanceof ScoreValidationError ||
|
|
387
|
+
error instanceof FxValidationError
|
|
388
|
+
)
|
|
389
|
+
return { ok: false, message: `grain · ${error.message}` };
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
const stored = next.tracks.find((candidate) => candidate.id === trackId);
|
|
393
|
+
return {
|
|
394
|
+
ok: true,
|
|
395
|
+
message:
|
|
396
|
+
command.type === "grain-off"
|
|
397
|
+
? `grain · off · ${instrument}`
|
|
398
|
+
: `grain · ${describeGranular(stored?.granular)}`,
|
|
399
|
+
next,
|
|
400
|
+
kind: "score.granular",
|
|
401
|
+
payload: {
|
|
402
|
+
trackId,
|
|
403
|
+
instrument: stored?.instrument ?? instrument,
|
|
404
|
+
granular: stored?.granular ?? null,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Validated settings; the score's normaliser re-checks the SampleRef. */
|
|
410
|
+
function granularOrEmpty(value: Record<string, unknown>): TrackGranular {
|
|
411
|
+
return normalizeGranular(value, (ref) => ref as never) ?? Object.freeze({});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Track names that create a granular track: `cloud`, `hold-2`, … */
|
|
415
|
+
export function granularTrackPreset(
|
|
416
|
+
trackId: string,
|
|
417
|
+
): GranularPresetName | undefined {
|
|
418
|
+
const match = /^([a-z]+)(?:-\d{1,3})?$/.exec(trackId);
|
|
419
|
+
return match && isGranularPreset(match[1]!) ? match[1] : undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The local answer for `grain src <something else>` (`grain src bus:guitars`),
|
|
424
|
+
* so a source dawg cannot read yet never goes to the agent.
|
|
425
|
+
*/
|
|
426
|
+
export function grainSrcHint(prompt: string): string | undefined {
|
|
427
|
+
const words = prompt.trim().toLowerCase().split(/\s+/);
|
|
428
|
+
if (words[0] !== "grain" || (words[1] !== "src" && words[1] !== "source"))
|
|
429
|
+
return undefined;
|
|
430
|
+
if (parseGranularCommand(prompt)) return undefined;
|
|
431
|
+
return "grain src synth:<preset>[@note] | voice <name> · a bus or another track is not a source yet: render it and load the file as a sampler voice";
|
|
432
|
+
}
|
package/src/commands/help.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* app commands take a slash (bare aliases keep working but are listed once,
|
|
5
5
|
* in their canonical form).
|
|
6
6
|
*/
|
|
7
|
+
import { GUITAR_TUNING_NAMES } from "../../core/chords.ts";
|
|
7
8
|
import { EXPRESSION_USAGE } from "./expression.ts";
|
|
8
9
|
|
|
9
10
|
export type HelpGroup = "music" | "session" | "window" | "keys";
|
|
@@ -53,6 +54,12 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
|
|
|
53
54
|
command: "wtenv|wtattack|wtdecay|wtrate|wtdepth|warp <n>",
|
|
54
55
|
summary: "scan the table · warpmode bendp",
|
|
55
56
|
},
|
|
57
|
+
{
|
|
58
|
+
command:
|
|
59
|
+
"grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off",
|
|
60
|
+
summary:
|
|
61
|
+
"granular cloud · grain cloud · grain scan 0.2 · track swarm-2 · grain presets",
|
|
62
|
+
},
|
|
56
63
|
{
|
|
57
64
|
command: "fx <effect> <param> <value> | on | off | preset <name>",
|
|
58
65
|
summary: "effects · fx delay mix 0.3 · fx reverb on · fx lists them",
|
|
@@ -61,6 +68,80 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
|
|
|
61
68
|
command: "synth <param> <value> | preset <name>",
|
|
62
69
|
summary: "synth voice · synth lpf 1200 · synth lists every param",
|
|
63
70
|
},
|
|
71
|
+
{
|
|
72
|
+
command: "string <preset> | <param> <value> | presets | reset | off",
|
|
73
|
+
summary:
|
|
74
|
+
"plucked strings · string sitar · string buzz 0.8 · string ring 6",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
command: "bowed [<preset>] | <param> <value> | presets",
|
|
78
|
+
summary:
|
|
79
|
+
"bowed strings · bowed violin · bowed cellos · bowed pressure 0.7 · bowed sord 1",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
command: "rig <preset> | reset",
|
|
83
|
+
summary:
|
|
84
|
+
"guitar rig · rig crunch · rig metal · rig shoegaze · rig lists them",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
command:
|
|
88
|
+
"guitar tune <name|notes> | capo | hand | ring | position | reset",
|
|
89
|
+
summary: "guitar fretting · guitar tune dadgad · guitar capo 2",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
command: "strum [chords] [pattern] [strokes D-DU-UDU] [speed 22ms]",
|
|
93
|
+
summary:
|
|
94
|
+
"strummed guitar chords · strum G D Em C folk · strum I V vi IV · strum alone strums the track's chords",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
command: "fx wobble|bloom|swell|double [param value]",
|
|
98
|
+
summary:
|
|
99
|
+
"shoegaze · fx wobble depth 30 · fx double · fx reverb ir builtin:reverse",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
command: "track jangle|punk|funk|ragged|gtr-lead|gtr-metal|bachata",
|
|
103
|
+
summary: "new guitar track with that rig",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
command: "stomp|head|cab <type> | <param> <value>",
|
|
107
|
+
summary:
|
|
108
|
+
"pedal, amp, cabinet · stomp rat · head gain 7 gate -55 · cab 4x12",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
command: "piano [<preset>] | grand | upright | felt | honkytonk",
|
|
112
|
+
summary: "modelled piano · piano ballad · piano lofi · piano prepared",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
command: "keys <param> <value> | preset <name> | reset",
|
|
116
|
+
summary: "piano params · keys hardness 0.3 decay 1.5 · keys lists them",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
command: "epiano|wurli|clav [preset <name>] | <param> <value>",
|
|
120
|
+
summary:
|
|
121
|
+
"electric keys · suitcase dyno funkclav · epiano vibe 0.6 · clav pickup bridge",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
command:
|
|
125
|
+
"tonewheel [<drawbars>] | combo [<registers>] [<voice>] | pipe [<stops>] [<row> <value> …]",
|
|
126
|
+
summary:
|
|
127
|
+
"organs · tonewheel 888800008 perc 3rd · gospel · combo 08880 flute · pipe plenum · keys perc 3rd",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
command: "rotary slow|fast|stop [at <beat>]",
|
|
131
|
+
summary:
|
|
132
|
+
"organ rotary speaker speed · rotary fast at 16 writes the keys-rotary lane",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
command:
|
|
136
|
+
"modal <preset> | <param> <value> | mallet <name> | reset | off",
|
|
137
|
+
summary:
|
|
138
|
+
"mallets and bells · modal vibes · modal hardness 0.8 · modal presets",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
command: "wind <preset> | <param> <value> | mute <name> | reset | off",
|
|
142
|
+
summary:
|
|
143
|
+
"winds and brass · wind flute · wind sax · wind players 4 · wind presets",
|
|
144
|
+
},
|
|
64
145
|
{
|
|
65
146
|
command: "art <articulation>|off [target]",
|
|
66
147
|
summary:
|
|
@@ -83,6 +164,11 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
|
|
|
83
164
|
command: "pedal <beat>-<beat>... | bars | down|half|up <beat> | off",
|
|
84
165
|
summary: "sustain pedal · pedal 0-3.5 4-7.5 · pedal bars",
|
|
85
166
|
},
|
|
167
|
+
{
|
|
168
|
+
command: "pedal soft|sost <beat>-<beat>... | bars | off",
|
|
169
|
+
summary:
|
|
170
|
+
"piano una corda and sostenuto · pedal soft 0-8 · pedal sost 0-4",
|
|
171
|
+
},
|
|
86
172
|
{
|
|
87
173
|
command: "velcurve linear|soft|hard|fixed [<v>]",
|
|
88
174
|
summary: "how velocity maps to level · velcurve fixed 0.6 (0..1)",
|
|
@@ -203,6 +289,20 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
|
|
|
203
289
|
summary: "repeats pace accent prob swing …",
|
|
204
290
|
},
|
|
205
291
|
{ command: "grid <voice> <x.X.>", summary: "explicit steps · X accent" },
|
|
292
|
+
{
|
|
293
|
+
command: "shift <semitones> [formant keep|follow|<n>] [<voice>]",
|
|
294
|
+
summary:
|
|
295
|
+
"a sample's pitch without changing its length · shift 0 clears",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
command: "fade [in|out] <seconds> [<voice>]",
|
|
299
|
+
summary: "a sample's fade in and out (Strudel fadeInTime/fadeTime)",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
command:
|
|
303
|
+
"resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
|
|
304
|
+
summary: "render to a pinned WAV on a new sampler (or granular) track",
|
|
305
|
+
},
|
|
206
306
|
{ command: "undo", summary: "step back · Ctrl-Z" },
|
|
207
307
|
{ command: "redo", summary: "step forward · Ctrl-Y" },
|
|
208
308
|
],
|
|
@@ -224,6 +324,19 @@ export const HELP_SECTIONS: readonly HelpSection[] = [
|
|
|
224
324
|
command: "/sample [<path> [as <voice>]]",
|
|
225
325
|
summary: "add a sample voice · list voices",
|
|
226
326
|
},
|
|
327
|
+
{
|
|
328
|
+
command: "/bpm <n> [<voice>]",
|
|
329
|
+
summary:
|
|
330
|
+
"the sample's own tempo (slash needed: bare bpm is song tempo)",
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
command: "/fitmode [repitch|beats|tones|auto] [<voice>]",
|
|
334
|
+
summary: "how it fits · alone suggests one from the sound",
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
command: "/len <beats> [<voice>]",
|
|
338
|
+
summary: "the sample lasts n beats of the song",
|
|
339
|
+
},
|
|
227
340
|
],
|
|
228
341
|
},
|
|
229
342
|
{
|
|
@@ -366,7 +479,7 @@ export const HELP_GUIDE: readonly HelpSection[] = [
|
|
|
366
479
|
entries: [
|
|
367
480
|
{
|
|
368
481
|
command: "ctrl-k › Sound",
|
|
369
|
-
summary: "instrument, envelope, filter, wavetable",
|
|
482
|
+
summary: "instrument, strings, envelope, filter, wavetable",
|
|
370
483
|
},
|
|
371
484
|
{ command: "ctrl-k › Effects", summary: "delay, reverb, distortion …" },
|
|
372
485
|
{
|
|
@@ -551,7 +664,7 @@ export const USAGE: Readonly<Record<string, string>> = {
|
|
|
551
664
|
click: "/click on|off|<volume> · /click 50%",
|
|
552
665
|
"count-in": "/count-in 0|1|2",
|
|
553
666
|
chords:
|
|
554
|
-
"/chords auto|manual|off · voicing <n> · spread · bass · perform · rate · octaves · sevenths · preset · style",
|
|
667
|
+
"/chords auto|manual|off · voicing <n> · spread · bass · perform · rate · octaves · sevenths · preset · style · strokes · speed",
|
|
555
668
|
key: "key <tonic> <mode> | none · key A minor",
|
|
556
669
|
scale: "scale [<tonic>] <name> | list · scale D hijaz",
|
|
557
670
|
tuning:
|
|
@@ -561,7 +674,7 @@ export const USAGE: Readonly<Record<string, string>> = {
|
|
|
561
674
|
grid: "/grid 1/4|1/8|1/8T|1/16|1/16T|1/32",
|
|
562
675
|
tempo:
|
|
563
676
|
"tempo takes 20…300 · tempo 120 · tempo 90 at bar 9 [ramp|exp] · tempo remove bar 9 · tempo clear · tempo map",
|
|
564
|
-
bpm: "tempo takes 20…300 · tempo 120",
|
|
677
|
+
bpm: "tempo takes 20…300 · tempo 120 · a sample's own tempo: /bpm 174 [<voice>] · /bpm off",
|
|
565
678
|
rit: "rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rit 4 bars to 80",
|
|
566
679
|
ritardando:
|
|
567
680
|
"rit [<n> bars|beats] [to <bpm>] [at bar <n>|<beat>] [exp] · rit 4 bars to 80",
|
|
@@ -583,12 +696,16 @@ export const USAGE: Readonly<Record<string, string>> = {
|
|
|
583
696
|
bars: "bars takes 1…256 · bars 8",
|
|
584
697
|
extend: "extend <count> bars · extend 4 bars",
|
|
585
698
|
instrument:
|
|
586
|
-
"instrument <name> · sine piano pluck bass saw square triangle wavetable kit · synth: sawtooth supersaw pulse white pink z_square…",
|
|
699
|
+
"instrument <name> · sine piano pluck bass saw square triangle wavetable kit · pianos: grand upright felt honkytonk prepared · electric: epiano suitcase dyno wurli clav funkclav · synth: sawtooth supersaw pulse white pink z_square…",
|
|
587
700
|
volume: "volume takes 0…1 · volume 0.8",
|
|
588
701
|
vol: "volume takes 0…1 · volume 0.8",
|
|
589
702
|
pan: "pan takes -1…1 · pan -0.5",
|
|
590
703
|
wt: "wt <table> | wt <0..1> | wt list · wt basic · wt wt_digital:2",
|
|
591
704
|
wavetable: "wt <table> | wt <0..1> | wt list · wt basic · wt wt_digital:2",
|
|
705
|
+
grain:
|
|
706
|
+
"grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · sync 1/64…1/1 (t triplet, d or . dotted) · grain cloud · grain pitch 12 · grain sync 1/8.",
|
|
707
|
+
granular:
|
|
708
|
+
"grain <preset> | <param> <value> | on [voice V] | src synth:<name>|voice V | reset | off | presets · grain cloud",
|
|
592
709
|
warpmode: "warpmode none|asym|bendp|bendm|bendmp|sync|quant",
|
|
593
710
|
filter: "filter <hz> [res] · filter 800 0.3 · filter off",
|
|
594
711
|
delay: "delay <beats> [fb] [mix] · delay 0.75 0.4 0.3 · delay off",
|
|
@@ -609,8 +726,17 @@ export const USAGE: Readonly<Record<string, string>> = {
|
|
|
609
726
|
export: "/export <file> · /export loop.track.json",
|
|
610
727
|
import: "/import <file> · /import loop.track.json",
|
|
611
728
|
sample:
|
|
612
|
-
"/sample <path> [as <voice>] · /sample set <voice> <control> <value>… · /sample set brk fit on clip 1",
|
|
729
|
+
"/sample <path> [as <voice>] · /sample set <voice> <control> <value>… · /sample set brk fit on clip 1 · /sample set soft vel 0-63 rr a",
|
|
613
730
|
samples: "/sample · lists the focused track's voices",
|
|
731
|
+
fitmode:
|
|
732
|
+
"/fitmode [repitch|beats|tones|auto|off] [<voice>] · /fitmode beats · /fitmode auto brk",
|
|
733
|
+
len: "/len <beats> [<voice>] · /len 16 · /len off",
|
|
734
|
+
shift:
|
|
735
|
+
"shift <semitones> [formant keep|follow|<n>] [<voice>] · shift 7 formant keep · shift 0",
|
|
736
|
+
fade: "fade [in|out] <seconds> [<voice>] · fade out 0.5 · fade in 0.05 · fade off",
|
|
737
|
+
resample:
|
|
738
|
+
"resample <track>|orbit <n>|master [section <name>|bars a-b] [post] [grain] [as <id>] · resample lead · resample drums bars 1-2 grain · resample master section chorus",
|
|
739
|
+
bounce: "resample <track>|orbit <n>|master [section <name>|bars a-b] [grain]",
|
|
614
740
|
view: "/view focus | all",
|
|
615
741
|
transcript: "/transcript",
|
|
616
742
|
log: "/transcript",
|
|
@@ -624,6 +750,34 @@ export const USAGE: Readonly<Record<string, string>> = {
|
|
|
624
750
|
guide: "/guide [topic] · /guide chords · F1",
|
|
625
751
|
fx: "fx <effect> <param> <value> | on | off | preset <name> · fx delay mix 0.3",
|
|
626
752
|
synth: "synth <param> <value> | preset <name> · synth lpf 1200",
|
|
753
|
+
string:
|
|
754
|
+
"string <preset> | preset <name> | <param> <value> | presets | reset | off · string koto",
|
|
755
|
+
bowed:
|
|
756
|
+
"bowed [violin|viola|cello|contrabass|fiddle|erhu|kamancheh|violins|violas|cellos|contrabasses|pizz|trem] | <param> <value> | presets · bowed violin · bowed pressure 0.7",
|
|
757
|
+
rig: "rig clean|crunch|punk|ragged|lead|metal|fuzz|octave|funk|wah|bachata|spring|bassdrive|reese|jangle|alt|shoegaze|glide|dreampop|swell|ebow | reset",
|
|
758
|
+
guitar: `guitar tune ${GUITAR_TUNING_NAMES.join("|")} | E A D G B E · capo 0..12 · hand 3..6 · ring 0..1 · position · reset`,
|
|
759
|
+
strum:
|
|
760
|
+
"strum G D Em C [folk|pop|punk|…] [strokes D-DU-UDU] [speed 22ms] [each 4] [at 0] · strum alone strums the track's chords",
|
|
761
|
+
stomp:
|
|
762
|
+
"stomp fuzz|face|od|rat|octave | gain <0-10> tone <0-1> level <dB> | off",
|
|
763
|
+
head: "head clean|chime|crunch|lead|high|solid|bass | gain bass mid treble presence master <0-10> | gate <dB> | off",
|
|
764
|
+
cab: "cab 1x12|2x12|4x12|1x10|open|8x10|1x15|di | mic <0-1> | off",
|
|
765
|
+
keys: "keys <param> <value> | preset <name> | reset | presets · keys hardness 0.3 · keys stretch 0 · keys sym 0.5 (sympathetic bloom under the sustain pedal)",
|
|
766
|
+
piano:
|
|
767
|
+
"piano [grand|ballad|upright|felt|lofi|honkytonk|prepared] · piano ballad",
|
|
768
|
+
epiano:
|
|
769
|
+
"epiano [preset epiano|suitcase|dyno] | bark bell tone vibe vibehz decay release <value> · epiano vibe 0.6 · rhodes",
|
|
770
|
+
wurli:
|
|
771
|
+
"wurli [preset wurli] | bark bell tone trem decay release <value> · wurli trem 0.5",
|
|
772
|
+
clav: "clav [preset clav|funkclav] | pickup neck|bridge|both|out | mute tone decay release <value> · clav pickup bridge",
|
|
773
|
+
tonewheel:
|
|
774
|
+
"tonewheel [<9 drawbar digits>] | hammond | b3 | gospel | jazzorgan · tonewheel 888800008",
|
|
775
|
+
combo: "combo [<5 register digits>] | farfisa | vox · combo 08880",
|
|
776
|
+
pipe: "pipe [plenum|flutes|cornet|reeds|strings|full | <stop> …] | church · pipe principal8,octave4",
|
|
777
|
+
rotary: "rotary slow|fast|stop [at <beat>] · rotary fast · rotary fast at 16",
|
|
778
|
+
modal:
|
|
779
|
+
"modal <preset> | <body> | <param> <value> | mallet <name> | pair <track> | gamelan | reset | off | presets · modal vibes · modal gangsa · modal ring 3",
|
|
780
|
+
wind: "wind <preset> | <param> <value> | mute <name> | reset | off | presets · wind flute · wind trumpet mute harmon · wind players 4",
|
|
627
781
|
pack: "/pack list | info <name> | use <pack>/<sound> | add <url>",
|
|
628
782
|
kit: "/kit [name] · /kit syn909",
|
|
629
783
|
euclid: "/euclid [voice] · euclid hat 7 16",
|