@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ctrl-k menu rows for the sing engine (0.7): the Choir, Solo and Throat
|
|
3
|
+
* groups under Voice › voice presets, the SING_PARAMS rows under
|
|
4
|
+
* Sound › Parameters on a sing track (with a Throat sub-menu) and the
|
|
5
|
+
* Vowels row under Sound › Performance. Every row runs a `sing …` or
|
|
6
|
+
* `note vowel …` command, so it stages for the audition loop's A/B.
|
|
7
|
+
*/
|
|
8
|
+
import type { EnumParam, NumberParam } from "../../core/params.ts";
|
|
9
|
+
import type { Note, Track } from "../../core/score.ts";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_SING_PRESET,
|
|
12
|
+
resolveSing,
|
|
13
|
+
SING_GROUPS,
|
|
14
|
+
SING_PARAMS,
|
|
15
|
+
SING_PRESETS,
|
|
16
|
+
SING_PRESET_NAMES,
|
|
17
|
+
SING_THROAT_PARAMS,
|
|
18
|
+
SING_VOWELS,
|
|
19
|
+
singNoteName,
|
|
20
|
+
singDroneFollows,
|
|
21
|
+
singTrack,
|
|
22
|
+
type SingPreset,
|
|
23
|
+
} from "../../core/sing.ts";
|
|
24
|
+
import { num, specStep, type MenuContext, type MenuNode } from "./menu.ts";
|
|
25
|
+
|
|
26
|
+
function presetRow(name: SingPreset): MenuNode {
|
|
27
|
+
return {
|
|
28
|
+
kind: "action",
|
|
29
|
+
label: `${name.padEnd(9)} ${SING_PRESETS[name].doc}`,
|
|
30
|
+
command: `sing ${name}`,
|
|
31
|
+
help: SING_PRESETS[name].styles,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Voice › voice presets: choir, solo and throat (sing lane). */
|
|
36
|
+
export function singBrowseRows(): MenuNode[] {
|
|
37
|
+
return SING_GROUPS.map((group): MenuNode => ({
|
|
38
|
+
kind: "menu",
|
|
39
|
+
id: `voices:${group.label.toLowerCase()}`,
|
|
40
|
+
label: group.label.toLowerCase(),
|
|
41
|
+
detail: group.presets.slice(0, 4).join(", ") + " …",
|
|
42
|
+
help:
|
|
43
|
+
group.label === "Throat"
|
|
44
|
+
? "Tuvan throat singing: a held drone, notes steer the overtone"
|
|
45
|
+
: `${group.label.toLowerCase()} on the built-in singing voice`,
|
|
46
|
+
build: () => group.presets.map(presetRow),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Parameters shown first; the rest sit under "advanced". */
|
|
51
|
+
const SING_SIMPLE = Object.freeze([
|
|
52
|
+
"vowel",
|
|
53
|
+
"voice",
|
|
54
|
+
"voices",
|
|
55
|
+
"bright",
|
|
56
|
+
"breath",
|
|
57
|
+
"vibmod",
|
|
58
|
+
"formant",
|
|
59
|
+
"ring",
|
|
60
|
+
] as const);
|
|
61
|
+
|
|
62
|
+
function numberRow(
|
|
63
|
+
name: string,
|
|
64
|
+
spec: NumberParam,
|
|
65
|
+
value: number,
|
|
66
|
+
fallback: number,
|
|
67
|
+
): MenuNode {
|
|
68
|
+
return {
|
|
69
|
+
kind: "number",
|
|
70
|
+
label: name,
|
|
71
|
+
value,
|
|
72
|
+
min: spec.min,
|
|
73
|
+
max: spec.max,
|
|
74
|
+
step: specStep(spec),
|
|
75
|
+
format: (v) => (spec.unit ? `${num(v)} ${spec.unit}` : num(v)),
|
|
76
|
+
command: (v) => `sing ${name} ${num(v)}`,
|
|
77
|
+
reset: `sing ${name} off`,
|
|
78
|
+
help: `${spec.doc} · preset ${num(fallback)} · x resets`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Every vowel and two-vowel morph, for the pickers. */
|
|
83
|
+
export function vowelOptions(): string[] {
|
|
84
|
+
const out: string[] = [...SING_VOWELS];
|
|
85
|
+
for (const from of SING_VOWELS)
|
|
86
|
+
for (const to of SING_VOWELS) if (from !== to) out.push(`${from}>${to}`);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Sound › Parameters rows on a sing track. */
|
|
91
|
+
export function singParameterNodes(track: Track, keyRoot?: number): MenuNode[] {
|
|
92
|
+
if (!singTrack(track)) return [];
|
|
93
|
+
const sing = track.sing!;
|
|
94
|
+
const follows = singDroneFollows(sing, keyRoot);
|
|
95
|
+
const resolved = resolveSing(sing, keyRoot) as unknown as Readonly<
|
|
96
|
+
Record<string, unknown>
|
|
97
|
+
>;
|
|
98
|
+
const fallback = SING_PRESETS[sing.preset ?? DEFAULT_SING_PRESET]
|
|
99
|
+
.settings as unknown as Readonly<Record<string, unknown>>;
|
|
100
|
+
const own = sing as Readonly<Record<string, unknown>>;
|
|
101
|
+
const row = (name: string): MenuNode | undefined => {
|
|
102
|
+
if (name === "vowel")
|
|
103
|
+
return {
|
|
104
|
+
kind: "choice",
|
|
105
|
+
label: "vowel",
|
|
106
|
+
value: String(resolved.vowel),
|
|
107
|
+
options: vowelOptions(),
|
|
108
|
+
command: (option) => `sing vowel ${option}`,
|
|
109
|
+
help: `${SING_PARAMS.vowel!.doc} · preset ${String(fallback.vowel)}`,
|
|
110
|
+
};
|
|
111
|
+
if (name === "drone") {
|
|
112
|
+
const drone = resolved.drone as number | undefined;
|
|
113
|
+
const spec = SING_PARAMS.drone as NumberParam;
|
|
114
|
+
return {
|
|
115
|
+
kind: "number",
|
|
116
|
+
label: "drone",
|
|
117
|
+
value: drone,
|
|
118
|
+
min: spec.min,
|
|
119
|
+
max: spec.max,
|
|
120
|
+
step: (v, direction) =>
|
|
121
|
+
Math.max(spec.min, Math.min(spec.max, Math.round(v) + direction)),
|
|
122
|
+
// The drone you hear: the preset's moved to the song key.
|
|
123
|
+
format: (v) =>
|
|
124
|
+
follows && v === drone ? `${singNoteName(v)} (key)` : singNoteName(v),
|
|
125
|
+
command: (v) => `sing drone ${singNoteName(v)}`,
|
|
126
|
+
off: "off (melody)",
|
|
127
|
+
start: 50,
|
|
128
|
+
reset: "sing drone off",
|
|
129
|
+
help: `${spec.doc} · x back to the preset`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (name === "harmonics") {
|
|
133
|
+
const [lo, hi] = resolved.harmonics as readonly [number, number];
|
|
134
|
+
const options = ["4-8", "4-12", "5-10", "6-12", "6-16", "8-16", "8-24"];
|
|
135
|
+
const value = `${lo}-${hi}`;
|
|
136
|
+
return {
|
|
137
|
+
kind: "choice",
|
|
138
|
+
label: "harmonics",
|
|
139
|
+
value,
|
|
140
|
+
options: options.includes(value) ? options : [value, ...options],
|
|
141
|
+
command: (option) => `sing harmonics ${option}`,
|
|
142
|
+
help: "the overtone range a throat track's notes pick from (khoomei 6-12, sygyt 8-16)",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const spec = SING_PARAMS[name]!;
|
|
146
|
+
if (spec.kind === "enum")
|
|
147
|
+
return {
|
|
148
|
+
kind: "choice",
|
|
149
|
+
label: name,
|
|
150
|
+
value: own[name] === undefined ? "preset" : String(own[name]),
|
|
151
|
+
options: ["preset", ...(spec as EnumParam).values],
|
|
152
|
+
command: (option) =>
|
|
153
|
+
option === "preset" ? `sing ${name} unset` : `sing ${name} ${option}`,
|
|
154
|
+
help: `${spec.doc} · preset ${String(fallback[name])}`,
|
|
155
|
+
};
|
|
156
|
+
if (spec.kind !== "number") return undefined;
|
|
157
|
+
return numberRow(
|
|
158
|
+
name,
|
|
159
|
+
spec,
|
|
160
|
+
Number(resolved[name]),
|
|
161
|
+
Number(fallback[name]),
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
const nodes: MenuNode[] = [
|
|
165
|
+
{
|
|
166
|
+
kind: "choice",
|
|
167
|
+
label: "preset",
|
|
168
|
+
value: sing.preset ?? DEFAULT_SING_PRESET,
|
|
169
|
+
options: SING_PRESET_NAMES,
|
|
170
|
+
command: (option) => `sing preset ${option}`,
|
|
171
|
+
help: "the voice: choir, solo or throat singing",
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
for (const name of SING_SIMPLE) {
|
|
175
|
+
const node = row(name);
|
|
176
|
+
if (node) nodes.push(node);
|
|
177
|
+
}
|
|
178
|
+
const throat = SING_THROAT_PARAMS as readonly string[];
|
|
179
|
+
nodes.push({
|
|
180
|
+
kind: "menu",
|
|
181
|
+
id: "sing:throat",
|
|
182
|
+
label: "throat",
|
|
183
|
+
detail:
|
|
184
|
+
resolved.drone === undefined
|
|
185
|
+
? "off · drone, overtone, harmonics, sub"
|
|
186
|
+
: `drone ${singNoteName(resolved.drone as number)} · ${throat.slice(1).join(", ")}`,
|
|
187
|
+
help: "throat singing: a drone, notes pick its overtones; sub for kargyraa",
|
|
188
|
+
build: () => throat.flatMap((name) => row(name) ?? []),
|
|
189
|
+
});
|
|
190
|
+
const rest = Object.keys(SING_PARAMS).filter(
|
|
191
|
+
(name) =>
|
|
192
|
+
!(SING_SIMPLE as readonly string[]).includes(name) &&
|
|
193
|
+
!throat.includes(name),
|
|
194
|
+
);
|
|
195
|
+
nodes.push({
|
|
196
|
+
kind: "menu",
|
|
197
|
+
id: "sing:advanced",
|
|
198
|
+
label: "advanced",
|
|
199
|
+
detail: rest.join(", "),
|
|
200
|
+
help: "every sing parameter",
|
|
201
|
+
build: () => rest.flatMap((name) => row(name) ?? []),
|
|
202
|
+
});
|
|
203
|
+
if (Object.keys(sing).some((key) => key !== "preset"))
|
|
204
|
+
nodes.push({
|
|
205
|
+
kind: "action",
|
|
206
|
+
label: "reset to preset",
|
|
207
|
+
command: "sing reset",
|
|
208
|
+
help: "clear overrides, keep the preset",
|
|
209
|
+
});
|
|
210
|
+
return nodes;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function sharedVowel(notes: readonly Note[]): string {
|
|
214
|
+
const values = new Set(notes.map((note) => note.vowel ?? "track"));
|
|
215
|
+
if (values.size === 0) return "track";
|
|
216
|
+
return values.size === 1 ? [...values][0]! : "mixed";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Sound › Performance › Vowels on a sing track: left/right steps the notes'
|
|
221
|
+
* vowel through a e i o u, enter opens the morph picker, and a pattern
|
|
222
|
+
* row cycles vowels over the notes in order.
|
|
223
|
+
*/
|
|
224
|
+
export function singVowelNodes(context: MenuContext): MenuNode[] {
|
|
225
|
+
const track = context.score.tracks.find(
|
|
226
|
+
(item) => item.id === context.trackId,
|
|
227
|
+
);
|
|
228
|
+
if (!singTrack(track)) return [];
|
|
229
|
+
const notes = context.score.notes.filter(
|
|
230
|
+
(note) => note.trackId === track!.id,
|
|
231
|
+
);
|
|
232
|
+
const current = sharedVowel(notes);
|
|
233
|
+
return [
|
|
234
|
+
{
|
|
235
|
+
kind: "menu",
|
|
236
|
+
id: "sing:vowels",
|
|
237
|
+
label: "vowels",
|
|
238
|
+
detail: `${current} · ${notes.length} notes`,
|
|
239
|
+
help: "the sung vowel of every note on the track (a note's vowel wins over the track's)",
|
|
240
|
+
build: (inner) => {
|
|
241
|
+
const now = inner.score.notes.filter(
|
|
242
|
+
(note) => note.trackId === context.trackId,
|
|
243
|
+
);
|
|
244
|
+
return [
|
|
245
|
+
{
|
|
246
|
+
kind: "choice",
|
|
247
|
+
label: "vowel",
|
|
248
|
+
value: sharedVowel(now),
|
|
249
|
+
options: ["track", ...SING_VOWELS],
|
|
250
|
+
command: (option) =>
|
|
251
|
+
`note vowel ${option === "track" ? "off" : option}`,
|
|
252
|
+
help: "left/right steps a e i o u; track uses the track's vowel",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
kind: "choice",
|
|
256
|
+
label: "morph",
|
|
257
|
+
value: sharedVowel(now),
|
|
258
|
+
options: [
|
|
259
|
+
"track",
|
|
260
|
+
...vowelOptions().filter((v) => v.includes(">")),
|
|
261
|
+
],
|
|
262
|
+
command: (option) =>
|
|
263
|
+
`note vowel ${option === "track" ? "off" : option}`,
|
|
264
|
+
help: "each note travels from the first vowel to the second",
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
kind: "choice",
|
|
268
|
+
label: "pattern",
|
|
269
|
+
value: "",
|
|
270
|
+
options: ["a e i o u", "a o", "o u", "a e i", "u o a"],
|
|
271
|
+
command: (option) => `sing vowels ${option}`,
|
|
272
|
+
help: "vowels over the notes in time order, cycled",
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ctrl-k › Style: the style taxonomy as a menu tree (families, then each
|
|
3
|
+
* branch down to its leaves). Every row is a `style` command, so the menu
|
|
4
|
+
* and the prompt share one grammar; typing filters the rows of a level.
|
|
5
|
+
*/
|
|
6
|
+
import { hasCard, STYLE_TREE } from "../../core/styles/index.ts";
|
|
7
|
+
import { STYLE_FAMILIES } from "../../core/styles/taxonomy.ts";
|
|
8
|
+
import type { StyleId } from "../../core/styles/schema.ts";
|
|
9
|
+
import type { MenuContext, MenuNode } from "./menu.ts";
|
|
10
|
+
|
|
11
|
+
const BAR_CHOICES = [4, 8, 16, 32] as const;
|
|
12
|
+
|
|
13
|
+
/** The Style row's detail: the song's style, or a hint. */
|
|
14
|
+
export function styleMenuDetail(context: MenuContext): string {
|
|
15
|
+
const style = context.score.style;
|
|
16
|
+
if (!style) return "a whole song from a style";
|
|
17
|
+
const name = style.blend ? `${style.id} + ${style.blend.id}` : style.id;
|
|
18
|
+
return `${name} · seed ${style.seed}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function styleActions(id: StyleId): MenuNode[] {
|
|
22
|
+
const node = STYLE_TREE.get(id)!;
|
|
23
|
+
return [
|
|
24
|
+
...BAR_CHOICES.map((bars): MenuNode => ({
|
|
25
|
+
kind: "action",
|
|
26
|
+
label: `make ${bars} bars`,
|
|
27
|
+
command: `/style ${id} ${bars}`,
|
|
28
|
+
help: `replace the song with ${bars} bars of ${node.title} (undo restores it)`,
|
|
29
|
+
})),
|
|
30
|
+
{
|
|
31
|
+
kind: "action",
|
|
32
|
+
label: "about",
|
|
33
|
+
command: `/style info ${id}`,
|
|
34
|
+
help: "meter, tempo, groove, tuning, harmony and roles",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function styleNode(id: StyleId): MenuNode {
|
|
40
|
+
const node = STYLE_TREE.get(id)!;
|
|
41
|
+
const card = hasCard(id) ? "" : " · inherits";
|
|
42
|
+
return {
|
|
43
|
+
kind: "menu",
|
|
44
|
+
id: `style:${id}`,
|
|
45
|
+
label: node.title,
|
|
46
|
+
detail: node.leaf
|
|
47
|
+
? `${id}${card}`
|
|
48
|
+
: `${node.children.length} styles${card}`,
|
|
49
|
+
help: node.region.length ? `${id} · ${node.region.join(", ")}` : id,
|
|
50
|
+
build: () => [
|
|
51
|
+
...styleActions(id),
|
|
52
|
+
...node.children.map((child) => styleNode(child)),
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Style: again, find, then the families. */
|
|
58
|
+
export function styleMenuNodes(context: MenuContext): MenuNode[] {
|
|
59
|
+
const current = context.score.style;
|
|
60
|
+
return [
|
|
61
|
+
...(current
|
|
62
|
+
? [
|
|
63
|
+
{
|
|
64
|
+
kind: "action",
|
|
65
|
+
label: "again",
|
|
66
|
+
command: "/style again",
|
|
67
|
+
help: `${current.id} with the next seed`,
|
|
68
|
+
} satisfies MenuNode,
|
|
69
|
+
]
|
|
70
|
+
: []),
|
|
71
|
+
{
|
|
72
|
+
kind: "entry",
|
|
73
|
+
label: "find",
|
|
74
|
+
value: "",
|
|
75
|
+
placeholder: "style name, alias or region",
|
|
76
|
+
command: (text) =>
|
|
77
|
+
text.trim() ? `/style search ${text.trim()}` : undefined,
|
|
78
|
+
example: "/style search samba",
|
|
79
|
+
help: "search every style by name, alias or region",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
kind: "entry",
|
|
83
|
+
label: "blend",
|
|
84
|
+
value: "",
|
|
85
|
+
placeholder: "a b [weight]",
|
|
86
|
+
command: (text) => {
|
|
87
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
88
|
+
return words.length >= 2 && words.length <= 3
|
|
89
|
+
? `/style blend ${words.join(" ")}`
|
|
90
|
+
: undefined;
|
|
91
|
+
},
|
|
92
|
+
example: "/style blend bebop bossa-nova 0.3",
|
|
93
|
+
help: "mix two styles; weight 0 is the first, 1 the second",
|
|
94
|
+
},
|
|
95
|
+
...STYLE_FAMILIES.map((family): MenuNode => ({
|
|
96
|
+
kind: "menu",
|
|
97
|
+
id: `family:${family.key}`,
|
|
98
|
+
label: family.title,
|
|
99
|
+
detail: family.roots.join(" · "),
|
|
100
|
+
help: `${family.key} family`,
|
|
101
|
+
build: () => family.roots.map((root) => styleNode(root)),
|
|
102
|
+
})),
|
|
103
|
+
];
|
|
104
|
+
}
|