@hraness/dawg 0.6.0 → 0.7.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 +126 -0
- package/DAWG.md +655 -141
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +759 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -99
- package/core/expression.ts +158 -6
- package/core/fx.ts +354 -32
- package/core/granular.ts +91 -0
- package/core/instruments.ts +180 -0
- package/core/keys.ts +573 -4
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +306 -4
- package/core/routing.ts +165 -0
- package/core/score.ts +1181 -16
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +354 -6
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +3160 -69
- package/core/sections.ts +298 -24
- package/core/sing.ts +815 -0
- package/core/strings.ts +237 -2
- 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/core/winds.ts +652 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -6
- package/guides/chords.md +16 -14
- package/guides/effects.md +17 -15
- 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 +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -10
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +32 -0
- package/guides/rhythm.md +17 -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 -12
- package/guides/styles.md +31 -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 +5 -2
- package/src/agent/agent.ts +131 -13
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +15 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +5 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/resample-tool.ts +131 -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 +307 -37
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +16 -6
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +58 -3
- 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/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +171 -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/dsp/shift.ts +256 -0
- package/src/audio/effects/chain.ts +21 -4
- package/src/audio/effects/common.ts +57 -0
- package/src/audio/effects/convolution.ts +113 -5
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/engine.ts +206 -33
- package/src/audio/fit.ts +80 -3
- package/src/audio/granular.ts +352 -21
- package/src/audio/instrument-check.ts +67 -47
- package/src/audio/instruments.ts +36 -3
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +427 -0
- package/src/audio/keys/engine.ts +156 -20
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +72 -8
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/kits.ts +135 -6
- package/src/audio/live-worker.ts +3 -1
- package/src/audio/live.ts +247 -26
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +45 -3
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +286 -0
- package/src/audio/resonators.ts +10 -2
- package/src/audio/sampler.ts +252 -24
- package/src/audio/samples.ts +46 -3
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/body.ts +29 -16
- package/src/audio/strings/bow.ts +699 -0
- package/src/audio/strings/engine.ts +274 -22
- package/src/audio/strings/measure.test-helpers.ts +2 -0
- 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 +608 -85
- package/src/audio/winds/engine.ts +306 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +104 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +492 -0
- 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 +44 -29
- 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 +104 -23
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +105 -33
- package/src/commands/grammar.ts +558 -0
- package/src/commands/granular.ts +7 -2
- package/src/commands/help.ts +652 -339
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +293 -17
- package/src/commands/modal.ts +67 -4
- 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 +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +8 -25
- package/src/commands/sample.ts +49 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/sing.ts +478 -0
- package/src/commands/string.ts +28 -2
- package/src/commands/strum.ts +485 -0
- 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 +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +244 -0
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1521 -255
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +26 -3
- 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 +36 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +50 -7
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- 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 +1377 -193
- package/src/tui/modal-menu.ts +38 -11
- package/src/tui/performance-menu.ts +46 -1
- package/src/tui/play-chords.ts +108 -5
- package/src/tui/play-mode.ts +16 -1
- package/src/tui/play-session.ts +81 -16
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +144 -0
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
package/src/tui/modal-menu.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ctrl-k menu rows for modal percussion (0.6): the "
|
|
3
|
-
* group under Sound ›
|
|
2
|
+
* Ctrl-k menu rows for modal percussion (0.6): the "mallets"
|
|
3
|
+
* group under Sound › instruments, and the MODAL_PARAMS rows under
|
|
4
4
|
* Sound › Parameters on a modal track. Every row runs a `modal …` command.
|
|
5
5
|
*/
|
|
6
6
|
import type { EnumParam, NumberParam } from "../../core/params.ts";
|
|
7
7
|
import {
|
|
8
|
+
GAMELAN_PRESETS,
|
|
8
9
|
MODAL_INSTRUMENT,
|
|
9
10
|
MODAL_MALLET_NAMES,
|
|
10
11
|
MODAL_PARAMS,
|
|
@@ -12,25 +13,51 @@ import {
|
|
|
12
13
|
MODAL_PRESET_NAMES,
|
|
13
14
|
MODAL_SIMPLE,
|
|
14
15
|
modalSettings,
|
|
16
|
+
type ModalPresetName,
|
|
15
17
|
} from "../../core/resonators.ts";
|
|
16
18
|
import type { Track } from "../../core/score.ts";
|
|
17
19
|
import { num, specStep, type MenuNode } from "./menu.ts";
|
|
18
20
|
|
|
19
|
-
/** Sound ›
|
|
21
|
+
/** Sound › instruments › mallets: one row per preset. */
|
|
20
22
|
export function malletsMenu(): MenuNode {
|
|
21
23
|
return {
|
|
22
24
|
kind: "menu",
|
|
23
25
|
id: "mallets",
|
|
24
|
-
label: "
|
|
26
|
+
label: "mallets",
|
|
25
27
|
detail: `${MODAL_PRESET_NAMES.length} modal presets · marimba, vibes, gong …`,
|
|
26
28
|
help: "struck bars, bells and bowls on the modal resonator engine",
|
|
27
|
-
build: () =>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
build: () => [
|
|
30
|
+
// The core presets keep their rows (gong too); the gamelan bronzes
|
|
31
|
+
// added in 0.6.1 sit in their own sub-group.
|
|
32
|
+
...MODAL_PRESET_NAMES.filter(
|
|
33
|
+
(name) => name === "gong" || !GAMELAN_PRESETS.includes(name),
|
|
34
|
+
).map(presetRow),
|
|
35
|
+
gamelanMenu(),
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function presetRow(name: ModalPresetName): MenuNode {
|
|
41
|
+
return {
|
|
42
|
+
kind: "action",
|
|
43
|
+
label: `${name} ${MODAL_PRESETS[name].doc}`,
|
|
44
|
+
command: `modal ${name}`,
|
|
45
|
+
help: MODAL_PRESETS[name].styles,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* mallets › gamelan (f061-gamelan-winds): the Javanese and
|
|
51
|
+
* Balinese bronzes. `modal gamelan` points here.
|
|
52
|
+
*/
|
|
53
|
+
export function gamelanMenu(): MenuNode {
|
|
54
|
+
return {
|
|
55
|
+
kind: "menu",
|
|
56
|
+
id: "gamelan",
|
|
57
|
+
label: "gamelan",
|
|
58
|
+
detail: `${GAMELAN_PRESETS.length} bronzes · saron, gangsa (ombak), gong …`,
|
|
59
|
+
help: "Javanese and Balinese gamelan; pair two gangsa tracks with modal pair, tune with tuning slendro or pelog",
|
|
60
|
+
build: () => GAMELAN_PRESETS.map(presetRow),
|
|
34
61
|
};
|
|
35
62
|
}
|
|
36
63
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* runs the same prompt command a user could type (src/commands/expression.ts),
|
|
4
4
|
* so the menu, the prompt and the agent share one grammar.
|
|
5
5
|
*/
|
|
6
|
+
import { singVowelNodes } from "./sing-menu.ts";
|
|
6
7
|
import {
|
|
7
8
|
ARTICULATIONS,
|
|
8
9
|
DEFAULT_FIXED_VELOCITY,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
VELOCITY_CURVES,
|
|
13
14
|
} from "../../core/expression.ts";
|
|
14
15
|
import type { Note, Track } from "../../core/score.ts";
|
|
16
|
+
import { isPianoFamily } from "../../core/keys.ts";
|
|
15
17
|
import {
|
|
16
18
|
BEND_SHAPES,
|
|
17
19
|
DEFAULT_HUMANIZE,
|
|
@@ -50,12 +52,50 @@ export function performanceDetail(track: Track | undefined): string {
|
|
|
50
52
|
const parts = [
|
|
51
53
|
track.glide ? `glide ${track.glide.mode}` : "",
|
|
52
54
|
track.pedal?.length ? "pedal" : "",
|
|
55
|
+
track.softPedal?.length ? "soft" : "",
|
|
56
|
+
track.sostenuto?.length ? "sost" : "",
|
|
53
57
|
track.velocityCurve ? `vel ${track.velocityCurve.curve}` : "",
|
|
54
58
|
track.humanize ? "humanized" : "",
|
|
55
59
|
].filter(Boolean);
|
|
56
60
|
return parts.length ? parts.join(" · ") : "as written";
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
/** Soft pedal and Sostenuto rows: off, the current lane, or the whole loop. */
|
|
64
|
+
function pianoPedalNodes(track: Track): MenuNode[] {
|
|
65
|
+
const rows = [
|
|
66
|
+
{
|
|
67
|
+
lane: "soft",
|
|
68
|
+
label: "soft pedal",
|
|
69
|
+
events: track.softPedal,
|
|
70
|
+
help: "una corda: the hammers shift to strike fewer strings, softer and darker; read at each note's onset",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
lane: "sost",
|
|
74
|
+
label: "sostenuto",
|
|
75
|
+
events: track.sostenuto,
|
|
76
|
+
help: "holds only the keys already down when it presses (the middle pedal); later notes damp normally",
|
|
77
|
+
},
|
|
78
|
+
] as const;
|
|
79
|
+
return rows
|
|
80
|
+
.filter((row) => isPianoFamily(track.instrument) || row.events?.length)
|
|
81
|
+
.map((row): MenuNode => {
|
|
82
|
+
const value = row.events?.length ? `${row.events.length} events` : "off";
|
|
83
|
+
return {
|
|
84
|
+
kind: "choice",
|
|
85
|
+
label: row.label,
|
|
86
|
+
value,
|
|
87
|
+
options: ["off", ...(value === "off" ? [] : [value]), "bars"],
|
|
88
|
+
command: (option) =>
|
|
89
|
+
option === "bars"
|
|
90
|
+
? `pedal ${row.lane} bars`
|
|
91
|
+
: option === "off"
|
|
92
|
+
? `pedal ${row.lane} off`
|
|
93
|
+
: `pedal ${row.lane}`,
|
|
94
|
+
help: row.help,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
59
99
|
export function performanceNodes(context: MenuContext): MenuNode[] {
|
|
60
100
|
const track = context.score.tracks.find(
|
|
61
101
|
(item) => item.id === context.trackId,
|
|
@@ -148,6 +188,9 @@ export function performanceNodes(context: MenuContext): MenuNode[] {
|
|
|
148
188
|
: "pedal",
|
|
149
189
|
help: "bars replaces the lane with a re-pedal on each downbeat; in play mode press Tab (latch) or hold Shift while recording",
|
|
150
190
|
},
|
|
191
|
+
// keys-electric (0.6.1): the piano's other two pedals, on modeled piano
|
|
192
|
+
// tracks (or wherever a lane is already set).
|
|
193
|
+
...pianoPedalNodes(track),
|
|
151
194
|
{
|
|
152
195
|
kind: "choice",
|
|
153
196
|
label: "velocity curve",
|
|
@@ -191,7 +234,7 @@ export function performanceNodes(context: MenuContext): MenuNode[] {
|
|
|
191
234
|
},
|
|
192
235
|
{
|
|
193
236
|
kind: "number",
|
|
194
|
-
label: "humanize
|
|
237
|
+
label: "humanize vel (%)",
|
|
195
238
|
value: humanize ? velocity : undefined,
|
|
196
239
|
min: 0,
|
|
197
240
|
max: EXPRESSION_LIMITS.maxHumanizePercent,
|
|
@@ -231,5 +274,7 @@ export function performanceNodes(context: MenuContext): MenuNode[] {
|
|
|
231
274
|
command: "humanize reseed",
|
|
232
275
|
help: "the same amounts with a different seeded performance",
|
|
233
276
|
});
|
|
277
|
+
// 0.7 sing: Vowels on a sing track.
|
|
278
|
+
nodes.push(...singVowelNodes(context));
|
|
234
279
|
return nodes;
|
|
235
280
|
}
|
package/src/tui/play-chords.ts
CHANGED
|
@@ -35,6 +35,10 @@ import {
|
|
|
35
35
|
EXTENSIONS,
|
|
36
36
|
MAX_VOICING_STEP,
|
|
37
37
|
PERFORM_MODES,
|
|
38
|
+
DEFAULT_STROKE_SPEED,
|
|
39
|
+
STROKE_PATTERN_NAMES,
|
|
40
|
+
strokeGrid,
|
|
41
|
+
type PerformOptions,
|
|
38
42
|
PROGRESSION_PRESETS,
|
|
39
43
|
PROGRESSION_STYLES,
|
|
40
44
|
SPREADS,
|
|
@@ -100,6 +104,10 @@ export type ChordSettings = {
|
|
|
100
104
|
/** `none` or a PROGRESSION_PRESETS name; drives the "next" suggestion. */
|
|
101
105
|
preset: string;
|
|
102
106
|
style: ProgressionStyle;
|
|
107
|
+
/** Guitar perform mode: a STROKE_PATTERNS name or a D U d u x - . grid. */
|
|
108
|
+
strokes: string;
|
|
109
|
+
/** Guitar perform mode: ms a full six-string down stroke takes. */
|
|
110
|
+
speed: number;
|
|
103
111
|
};
|
|
104
112
|
|
|
105
113
|
export function defaultChordSettings(): ChordSettings {
|
|
@@ -116,9 +124,45 @@ export function defaultChordSettings(): ChordSettings {
|
|
|
116
124
|
sevenths: false,
|
|
117
125
|
preset: "none",
|
|
118
126
|
style: "pop",
|
|
127
|
+
strokes: "down",
|
|
128
|
+
speed: DEFAULT_STROKE_SPEED * 1000,
|
|
119
129
|
};
|
|
120
130
|
}
|
|
121
131
|
|
|
132
|
+
/** Play mode's `[` / `]` strum speed step in ms. */
|
|
133
|
+
export const STROKE_SPEED_STEP = 5;
|
|
134
|
+
|
|
135
|
+
/** Strum speed bounds in ms (0 = every string at once). */
|
|
136
|
+
export const STROKE_SPEED_MS = Object.freeze({ min: 0, max: 200 });
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A strum speed from `/chords speed` text: milliseconds (`22`, `22ms`) or
|
|
140
|
+
* beats (`1/32b`, `0.05b`) at `bpm`. Undefined when not a speed.
|
|
141
|
+
*/
|
|
142
|
+
export function parseStrokeSpeed(
|
|
143
|
+
text: string | undefined,
|
|
144
|
+
bpm: number,
|
|
145
|
+
): number | undefined {
|
|
146
|
+
const raw = (text ?? "").trim().toLowerCase();
|
|
147
|
+
const beats = /^(\d+(?:\.\d+)?)(?:\/(\d+))?\s*(?:b|beats?)$/.exec(raw);
|
|
148
|
+
let ms: number;
|
|
149
|
+
if (beats) {
|
|
150
|
+
const value = Number(beats[1]) / (beats[2] ? Number(beats[2]) : 1);
|
|
151
|
+
ms = (value * 60_000) / bpm;
|
|
152
|
+
} else {
|
|
153
|
+
const plain = /^(\d+(?:\.\d+)?)\s*(?:ms)?$/.exec(raw);
|
|
154
|
+
if (!plain) return undefined;
|
|
155
|
+
ms = Number(plain[1]);
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
!Number.isFinite(ms) ||
|
|
159
|
+
ms < STROKE_SPEED_MS.min ||
|
|
160
|
+
ms > STROKE_SPEED_MS.max
|
|
161
|
+
)
|
|
162
|
+
return undefined;
|
|
163
|
+
return Math.round(ms * 10) / 10;
|
|
164
|
+
}
|
|
165
|
+
|
|
122
166
|
/**
|
|
123
167
|
* Tracks where auto chords are the default: anything pitched that is not a
|
|
124
168
|
* bass, a drum kit or a one-shot sampler.
|
|
@@ -236,6 +280,16 @@ export class ChordPad {
|
|
|
236
280
|
s.perform = PERFORM_MODES[(index + 1) % PERFORM_MODES.length]!;
|
|
237
281
|
return { type: "status", status: `perform ${s.perform}` };
|
|
238
282
|
}
|
|
283
|
+
if ((value === "[" || value === "]") && s.perform === "guitar") {
|
|
284
|
+
// Strum speed: slower ([) or faster (]) in 5 ms steps.
|
|
285
|
+
const wanted =
|
|
286
|
+
s.speed + (value === "[" ? STROKE_SPEED_STEP : -STROKE_SPEED_STEP);
|
|
287
|
+
s.speed = Math.max(
|
|
288
|
+
STROKE_SPEED_MS.min,
|
|
289
|
+
Math.min(STROKE_SPEED_MS.max, Math.round(wanted)),
|
|
290
|
+
);
|
|
291
|
+
return { type: "status", status: `strum speed ${s.speed}ms` };
|
|
292
|
+
}
|
|
239
293
|
if (value === "b") {
|
|
240
294
|
const index = BASS_MODES.indexOf(s.bass);
|
|
241
295
|
s.bass = BASS_MODES[(index + 1) % BASS_MODES.length]!;
|
|
@@ -392,6 +446,9 @@ export class ChordPad {
|
|
|
392
446
|
{ key: "0", label: "clear", on: false },
|
|
393
447
|
{ key: "-=", label: `voicing ${signed(s.inversion)}`, on: false },
|
|
394
448
|
{ key: "9", label: perform, on: false },
|
|
449
|
+
...(s.perform === "guitar"
|
|
450
|
+
? [{ key: "[]", label: `speed ${s.speed}ms`, on: false }]
|
|
451
|
+
: []),
|
|
395
452
|
{ key: "b", label: `bass ${s.bass}`, on: false },
|
|
396
453
|
{ key: "n", label: "next", on: false },
|
|
397
454
|
{ key: "q", label: s.mode === "auto" ? "auto" : "manual", on: false },
|
|
@@ -412,6 +469,8 @@ export class ChordPad {
|
|
|
412
469
|
if (s.spread !== "close") parts.push(s.spread);
|
|
413
470
|
if (s.perform === "pattern")
|
|
414
471
|
parts.push(`pattern ${patternLabel(s.pattern)}`);
|
|
472
|
+
else if (s.perform === "guitar")
|
|
473
|
+
parts.push(`guitar ${s.strokes} ${s.speed}ms`);
|
|
415
474
|
else if (s.perform !== "block") parts.push(s.perform);
|
|
416
475
|
if (s.bass !== "off") parts.push(`bass ${s.bass}`);
|
|
417
476
|
return parts.join(" · ");
|
|
@@ -435,15 +494,18 @@ function signed(value: number): string {
|
|
|
435
494
|
/**
|
|
436
495
|
* `/chords …`: `auto|manual|off`, `voicing <-12..12>`, `spread <close|open|
|
|
437
496
|
* wide>`, `bass off|chords|unison|single|solo` (`on` = chords), `perform
|
|
438
|
-
* <mode>`, `pattern <1..13|name>`, `
|
|
497
|
+
* <mode>`, `pattern <1..13|name>`, `strokes <name|D-DU-UDU>` (guitar
|
|
498
|
+
* mode), `speed <ms|1/32b>`, `rate <grid|1/8…>`, `octaves
|
|
439
499
|
* <1..4>`, `sevenths on|off`, `preset <name|none>`, `style <pop|jazz|…>`.
|
|
440
500
|
* Mutates `settings`; returns the receipt or an error.
|
|
441
501
|
*/
|
|
442
502
|
export function applyChordsCommand(
|
|
443
503
|
settings: ChordSettings,
|
|
444
504
|
argument: string,
|
|
505
|
+
bpm = 120,
|
|
445
506
|
): { ok: boolean; message: string } {
|
|
446
|
-
const
|
|
507
|
+
const raw = argument.trim().split(/\s+/).filter(Boolean);
|
|
508
|
+
const words = raw.map((word) => word.toLowerCase());
|
|
447
509
|
const [head, value] = words;
|
|
448
510
|
const done = (message: string) => ({ ok: true, message });
|
|
449
511
|
const bad = (message: string) => ({ ok: false, message });
|
|
@@ -505,6 +567,28 @@ export function applyChordsCommand(
|
|
|
505
567
|
return bad(`rate takes ${ARP_RATES.join("|")}`);
|
|
506
568
|
settings.rate = value as ArpRate;
|
|
507
569
|
return done(`chords rate ${value}`);
|
|
570
|
+
case "strokes": {
|
|
571
|
+
// Case matters: D is a full down stroke, d a light one.
|
|
572
|
+
const grid = raw.slice(1).join("");
|
|
573
|
+
if (!strokeGrid(grid))
|
|
574
|
+
return bad(
|
|
575
|
+
`strokes takes ${STROKE_PATTERN_NAMES.join("|")} or a grid of D U d u x - .`,
|
|
576
|
+
);
|
|
577
|
+
settings.strokes = (STROKE_PATTERN_NAMES as readonly string[]).includes(
|
|
578
|
+
grid.toLowerCase(),
|
|
579
|
+
)
|
|
580
|
+
? grid.toLowerCase()
|
|
581
|
+
: grid;
|
|
582
|
+
settings.perform = "guitar";
|
|
583
|
+
return done(`chords strokes ${settings.strokes}`);
|
|
584
|
+
}
|
|
585
|
+
case "speed": {
|
|
586
|
+
const ms = parseStrokeSpeed(words.slice(1).join(""), bpm);
|
|
587
|
+
if (ms === undefined)
|
|
588
|
+
return bad("speed takes 0..200 ms (22, 22ms) or beats (1/32b)");
|
|
589
|
+
settings.speed = ms;
|
|
590
|
+
return done(`chords speed ${ms}ms`);
|
|
591
|
+
}
|
|
508
592
|
case "octaves": {
|
|
509
593
|
const n = Number(value);
|
|
510
594
|
if (!Number.isInteger(n) || n < 1 || n > 4)
|
|
@@ -521,12 +605,14 @@ export function applyChordsCommand(
|
|
|
521
605
|
return done(`chords preset ${value}`);
|
|
522
606
|
case "style":
|
|
523
607
|
if (!(PROGRESSION_STYLES as readonly string[]).includes(value ?? ""))
|
|
524
|
-
return bad(
|
|
608
|
+
return bad(
|
|
609
|
+
`idiom takes ${PROGRESSION_STYLES.join("|")} · chords idiom jazz`,
|
|
610
|
+
);
|
|
525
611
|
settings.style = value as ProgressionStyle;
|
|
526
|
-
return done(`chords
|
|
612
|
+
return done(`chords idiom ${value}`);
|
|
527
613
|
default:
|
|
528
614
|
return bad(
|
|
529
|
-
"/chords auto|manual|off · voicing · spread · bass · perform · pattern · rate · octaves · sevenths · preset · style",
|
|
615
|
+
"/chords auto|manual|off · voicing · spread · bass · perform · pattern · strokes · speed · rate · octaves · sevenths · preset · style",
|
|
530
616
|
);
|
|
531
617
|
}
|
|
532
618
|
}
|
|
@@ -539,6 +625,8 @@ export function chordsSummary(settings: ChordSettings): string {
|
|
|
539
625
|
s.spread,
|
|
540
626
|
s.perform,
|
|
541
627
|
`pattern ${patternLabel(s.pattern)}`,
|
|
628
|
+
`strokes ${s.strokes}`,
|
|
629
|
+
`speed ${s.speed}ms`,
|
|
542
630
|
`rate ${s.rate}`,
|
|
543
631
|
`octaves ${s.octaves}`,
|
|
544
632
|
`bass ${s.bass}`,
|
|
@@ -603,6 +691,7 @@ export function chordPhrase(
|
|
|
603
691
|
seed: 1,
|
|
604
692
|
velocity: 0.8,
|
|
605
693
|
...(mode === "pattern" ? { pattern: settings.pattern } : {}),
|
|
694
|
+
...(mode === "guitar" ? guitarPerform(settings, score, track) : {}),
|
|
606
695
|
},
|
|
607
696
|
});
|
|
608
697
|
// With chords off a key plays one note: the root, in the chord's octave.
|
|
@@ -630,3 +719,17 @@ export function chordPhrase(
|
|
|
630
719
|
}
|
|
631
720
|
return notes;
|
|
632
721
|
}
|
|
722
|
+
|
|
723
|
+
/** Perform options for the guitar mode: strokes, speed, tempo and fretting. */
|
|
724
|
+
export function guitarPerform(
|
|
725
|
+
settings: Pick<ChordSettings, "strokes" | "speed">,
|
|
726
|
+
score: Pick<TrackScore, "tempoBpm">,
|
|
727
|
+
track: Pick<Track, "guitar"> | undefined,
|
|
728
|
+
): Partial<PerformOptions> {
|
|
729
|
+
return {
|
|
730
|
+
strokes: settings.strokes,
|
|
731
|
+
speed: settings.speed / 1000,
|
|
732
|
+
tempo: score.tempoBpm,
|
|
733
|
+
...(track?.guitar ? { guitar: track.guitar } : {}),
|
|
734
|
+
};
|
|
735
|
+
}
|
package/src/tui/play-mode.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* Shift sustain while held Tab sustain latch Esc leave the mode
|
|
13
13
|
* (while recording, sustain changes record as track pedal events)
|
|
14
14
|
* I scale degrees: the home row plays the key's scale (any tuning)
|
|
15
|
+
* [ / ] strum slower / faster (chords perform guitar, 5 ms steps)
|
|
15
16
|
*
|
|
16
17
|
* Terminals report key-down only, so a held key is synthesized from the
|
|
17
18
|
* keyboard's auto-repeat: a press sounds for the gate (one grid step by
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
type Track,
|
|
32
33
|
} from "../../core/score.ts";
|
|
33
34
|
import type { TuningTable } from "../../core/tuning.ts";
|
|
35
|
+
import { DRUM_VOICES, isDrumInstrument } from "../../core/drums.ts";
|
|
34
36
|
import { pitchName } from "../../tui/highway.ts";
|
|
35
37
|
|
|
36
38
|
/** Semitone offset from the base C for each note key. */
|
|
@@ -111,10 +113,21 @@ export function defaultBaseFor(instrument: string | undefined): number {
|
|
|
111
113
|
*/
|
|
112
114
|
export type PlayLayout = Readonly<{
|
|
113
115
|
base: number;
|
|
114
|
-
/** Pitch → label (voice names on one-shot samplers). */
|
|
116
|
+
/** Pitch → label (voice names on one-shot samplers, drums on kits). */
|
|
115
117
|
labels: ReadonlyMap<number, string>;
|
|
118
|
+
/** A drum kit: keys name drums and the chord row stays hidden. */
|
|
119
|
+
drums?: boolean | undefined;
|
|
116
120
|
}>;
|
|
117
121
|
|
|
122
|
+
/**
|
|
123
|
+
* A kit's keys on the GM map from C2 (36), the same keys the agent's
|
|
124
|
+
* show-me gestures press: A kick, W rim, S snare, E clap, T hat, H tom,
|
|
125
|
+
* U open hat. Other keys still sound their nearest drum, unlabeled.
|
|
126
|
+
*/
|
|
127
|
+
export function drumKeyLabels(): ReadonlyMap<number, string> {
|
|
128
|
+
return new Map(DRUM_VOICES.map((info) => [info.pitch, info.label]));
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
export function playLayoutFor(track: Track | undefined): PlayLayout {
|
|
119
132
|
const labels = new Map<number, string>();
|
|
120
133
|
if (track && isSamplerInstrument(track.instrument) && track.sampler) {
|
|
@@ -129,6 +142,8 @@ export function playLayoutFor(track: Track | undefined): PlayLayout {
|
|
|
129
142
|
const lowest = roots.length > 0 ? Math.min(...roots) : 60;
|
|
130
143
|
return { base: clampBase(Math.floor(lowest / 12) * 12), labels };
|
|
131
144
|
}
|
|
145
|
+
if (track && isDrumInstrument(track.instrument))
|
|
146
|
+
return { base: 36, labels: drumKeyLabels(), drums: true };
|
|
132
147
|
// A plain instrument takes its range from the track's name (`bass`, `lead`).
|
|
133
148
|
const byInstrument = defaultBaseFor(track?.instrument);
|
|
134
149
|
const base =
|
package/src/tui/play-session.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import {
|
|
51
51
|
LiveFullRenderer,
|
|
52
52
|
LiveSynth,
|
|
53
|
+
warmLive,
|
|
53
54
|
MAX_LIVE_NOTE_SECONDS,
|
|
54
55
|
type LiveNotePcm,
|
|
55
56
|
} from "../audio/live.ts";
|
|
@@ -57,20 +58,24 @@ import type { SampleBank } from "../audio/samples.ts";
|
|
|
57
58
|
import { fitting, onFitReady } from "../audio/fit.ts";
|
|
58
59
|
import {
|
|
59
60
|
DEFAULT_STRUM,
|
|
61
|
+
degreeOf,
|
|
60
62
|
perform,
|
|
61
63
|
type PerformMode,
|
|
64
|
+
type PerformOptions,
|
|
62
65
|
type PerformedNote,
|
|
63
66
|
} from "../../core/chords.ts";
|
|
64
67
|
import {
|
|
65
68
|
ChordPad,
|
|
66
69
|
chordCapable,
|
|
67
70
|
defaultChordSettings,
|
|
71
|
+
guitarPerform,
|
|
68
72
|
songKey,
|
|
69
73
|
type ChordSettings,
|
|
70
74
|
type PlayedChord,
|
|
71
75
|
} from "./play-chords.ts";
|
|
72
76
|
import type { PlayHeaderView, PlayStripKey } from "../../tui/play-strip.ts";
|
|
73
77
|
import { resolveTuning, type Tuning } from "../../core/tuning.ts";
|
|
78
|
+
import { singThroat } from "../../core/sing.ts";
|
|
74
79
|
import {
|
|
75
80
|
NOTE_KEYS,
|
|
76
81
|
PlayKeyboard,
|
|
@@ -89,6 +94,10 @@ export interface LiveEngine {
|
|
|
89
94
|
readonly sampleRate: number;
|
|
90
95
|
readonly canMonitor: boolean;
|
|
91
96
|
readonly leadMs: number;
|
|
97
|
+
/** The lead play mode pins (15 ms on the native sink, else 60 ms). */
|
|
98
|
+
readonly playLeadMs?: number;
|
|
99
|
+
/** How play mode sounds (`native sink`, `ffplay · no native sink`). */
|
|
100
|
+
readonly audioNote?: string;
|
|
92
101
|
monitor(on: boolean): Promise<void>;
|
|
93
102
|
setLeadMs(ms: number | undefined): void;
|
|
94
103
|
noteOn(id: number, note: LiveNotePcm): number;
|
|
@@ -122,6 +131,8 @@ export interface PlayHost {
|
|
|
122
131
|
startTransport(beat: number): Promise<void>;
|
|
123
132
|
stopTransport(): Promise<void>;
|
|
124
133
|
card(text: string, tone: "info" | "success" | "warning" | "error"): void;
|
|
134
|
+
/** A played key that is not recorded: the screen glows its lane. */
|
|
135
|
+
ghost?(pitch: number): void;
|
|
125
136
|
newNoteId(): string;
|
|
126
137
|
}
|
|
127
138
|
|
|
@@ -181,6 +192,8 @@ export type RecordedChord = Readonly<{
|
|
|
181
192
|
seed: number;
|
|
182
193
|
/** CHORD_PATTERNS name when `mode` is `pattern`. */
|
|
183
194
|
pattern?: string;
|
|
195
|
+
/** Guitar mode: strokes, speed, tempo, fretting and the chord root. */
|
|
196
|
+
guitar?: Partial<PerformOptions>;
|
|
184
197
|
}>;
|
|
185
198
|
|
|
186
199
|
/** A chord sounding live: its voices start and stop from `tick()`. */
|
|
@@ -236,6 +249,8 @@ export class PlaySession {
|
|
|
236
249
|
private synth: LiveSynth | undefined;
|
|
237
250
|
/** Rig voices awaiting their full render: id → released since. */
|
|
238
251
|
private readonly windows = new Map<number, boolean>();
|
|
252
|
+
/** f07-sing: the sounding key on a throat track (one drone, mono). */
|
|
253
|
+
private throatId: number | undefined;
|
|
239
254
|
private readonly fullRenderer = new LiveFullRenderer();
|
|
240
255
|
private readonly pending = new Map<number, Pending>();
|
|
241
256
|
/** Sustain changes while recording (Tab latch or Shift), as pedal events. */
|
|
@@ -285,7 +300,8 @@ export class PlaySession {
|
|
|
285
300
|
settings.mode =
|
|
286
301
|
chordCapable(track) &&
|
|
287
302
|
twelveTet(this.host.score(), track) &&
|
|
288
|
-
(track?.glide === undefined || track.glide.mode === "poly")
|
|
303
|
+
(track?.glide === undefined || track.glide.mode === "poly") &&
|
|
304
|
+
!singThroat(track)
|
|
289
305
|
? "auto"
|
|
290
306
|
: "manual";
|
|
291
307
|
this.chords = new ChordPad(settings, () => this.host.score().key);
|
|
@@ -327,8 +343,12 @@ export class PlaySession {
|
|
|
327
343
|
this.active = true;
|
|
328
344
|
const engine = this.host.engine();
|
|
329
345
|
if (engine?.canMonitor) {
|
|
330
|
-
engine.setLeadMs(PLAY_LEAD_MS);
|
|
346
|
+
engine.setLeadMs(engine.playLeadMs ?? PLAY_LEAD_MS);
|
|
347
|
+
// The voice's tables and loops are built now, not on the first key.
|
|
348
|
+
warmLive(this.host.score(), this.trackId, engine.sampleRate);
|
|
331
349
|
await engine.monitor(true);
|
|
350
|
+
if (engine.audioNote)
|
|
351
|
+
this.status = `${engine.audioNote} · ${Math.round(engine.leadMs)} ms lead`;
|
|
332
352
|
} else this.status = "no audio · keys still record";
|
|
333
353
|
this.applyClick();
|
|
334
354
|
}
|
|
@@ -479,6 +499,8 @@ export class PlaySession {
|
|
|
479
499
|
return { type: "handled" };
|
|
480
500
|
case "note": {
|
|
481
501
|
this.release(action.released, now);
|
|
502
|
+
// A key that is not recording glows its lane for a moment.
|
|
503
|
+
if (!this.recording) this.host.ghost?.(action.note.pitch);
|
|
482
504
|
const chord = this.chords.chordFor(action.note.pitch);
|
|
483
505
|
if (chord) {
|
|
484
506
|
const played = this.chords.voice(chord, action.note.pitch);
|
|
@@ -497,6 +519,12 @@ export class PlaySession {
|
|
|
497
519
|
);
|
|
498
520
|
return { type: "handled" };
|
|
499
521
|
}
|
|
522
|
+
// f07-sing: a throat track is one drone; a new key releases the last.
|
|
523
|
+
if (singThroat(this.trackData())) {
|
|
524
|
+
if (this.throatId !== undefined && this.throatId !== action.note.id)
|
|
525
|
+
this.release([this.throatId], now);
|
|
526
|
+
this.throatId = action.note.id;
|
|
527
|
+
}
|
|
500
528
|
this.sound(action.note.id, action.note);
|
|
501
529
|
this.record(action.note);
|
|
502
530
|
return { type: "handled" };
|
|
@@ -643,16 +671,21 @@ export class PlaySession {
|
|
|
643
671
|
// The full pass runs on a worker, in key order, so a strummed chord or a
|
|
644
672
|
// fast run never stalls key handling behind an oversampled render.
|
|
645
673
|
if (pcm.partial)
|
|
646
|
-
void this.fullRenderer
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
this.windows.get(id) === false &&
|
|
651
|
-
engine.voicePosition?.(id) !== undefined
|
|
674
|
+
void this.fullRenderer
|
|
675
|
+
.full(
|
|
676
|
+
synth,
|
|
677
|
+
pcm.clock === undefined ? request : { ...request, clock: pcm.clock },
|
|
652
678
|
)
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
679
|
+
.then((full) => {
|
|
680
|
+
// Not after note-off: a swap would cancel the release fade.
|
|
681
|
+
if (
|
|
682
|
+
full &&
|
|
683
|
+
this.windows.get(id) === false &&
|
|
684
|
+
engine.voicePosition?.(id) !== undefined
|
|
685
|
+
)
|
|
686
|
+
engine.noteOn(id, full);
|
|
687
|
+
this.windows.delete(id);
|
|
688
|
+
});
|
|
656
689
|
}
|
|
657
690
|
|
|
658
691
|
private release(ids: readonly number[], atMs: number): void {
|
|
@@ -729,6 +762,20 @@ export class PlaySession {
|
|
|
729
762
|
octaves: settings.octaves,
|
|
730
763
|
seed,
|
|
731
764
|
...(mode === "pattern" ? { pattern: settings.pattern } : {}),
|
|
765
|
+
...(mode === "guitar" ? { guitar: this.guitarOptions(played) } : {}),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Guitar-mode perform options for a played chord (strokes, speed, frets). */
|
|
770
|
+
private guitarOptions(played: PlayedChord): Partial<PerformOptions> {
|
|
771
|
+
return {
|
|
772
|
+
...guitarPerform(
|
|
773
|
+
this.chords.settings,
|
|
774
|
+
{ tempoBpm: this.currentBpm() },
|
|
775
|
+
this.trackData(),
|
|
776
|
+
),
|
|
777
|
+
root: played.chord.root,
|
|
778
|
+
...(played.chord.bass !== undefined ? { slash: played.chord.bass } : {}),
|
|
732
779
|
};
|
|
733
780
|
}
|
|
734
781
|
|
|
@@ -749,6 +796,7 @@ export class PlaySession {
|
|
|
749
796
|
seed: note.id,
|
|
750
797
|
pattern: settings.pattern,
|
|
751
798
|
velocity: 1,
|
|
799
|
+
...(mode === "guitar" ? this.guitarOptions(played) : {}),
|
|
752
800
|
});
|
|
753
801
|
if (played.bass !== undefined)
|
|
754
802
|
plan.push({
|
|
@@ -1119,7 +1167,7 @@ export class PlaySession {
|
|
|
1119
1167
|
};
|
|
1120
1168
|
}
|
|
1121
1169
|
return {
|
|
1122
|
-
range: this.keyboard.range,
|
|
1170
|
+
range: this.layout.drums ? "drums" : this.keyboard.range,
|
|
1123
1171
|
velocity: this.keyboard.velocity,
|
|
1124
1172
|
armed: this.armed,
|
|
1125
1173
|
recording: this.recording,
|
|
@@ -1129,10 +1177,14 @@ export class PlaySession {
|
|
|
1129
1177
|
countIn,
|
|
1130
1178
|
beat,
|
|
1131
1179
|
grid: `grid ${this.grid}`,
|
|
1132
|
-
|
|
1180
|
+
// A kit plays drums, not harmony: no chord row and no key chip.
|
|
1181
|
+
chords: this.layout.drums
|
|
1182
|
+
? undefined
|
|
1183
|
+
: this.chords.glance(songKey(this.host.score().key).set),
|
|
1133
1184
|
status: this.status,
|
|
1134
1185
|
keys: this.strip(now),
|
|
1135
|
-
legend:
|
|
1186
|
+
legend:
|
|
1187
|
+
this.chords.on && !this.layout.drums ? this.chords.legend() : undefined,
|
|
1136
1188
|
};
|
|
1137
1189
|
}
|
|
1138
1190
|
|
|
@@ -1154,8 +1206,20 @@ export class PlaySession {
|
|
|
1154
1206
|
).map((cell) => {
|
|
1155
1207
|
const chord = this.keyboard.pitchFor(cell.key);
|
|
1156
1208
|
const name =
|
|
1157
|
-
chord === undefined
|
|
1158
|
-
|
|
1209
|
+
chord === undefined || this.layout.drums
|
|
1210
|
+
? undefined
|
|
1211
|
+
: this.chords.keyLabel(chord);
|
|
1212
|
+
// Chords in the key are the safe ones to reach for: the strip lights
|
|
1213
|
+
// them; a borrowed chord still plays but stays plain.
|
|
1214
|
+
return name && chord !== undefined
|
|
1215
|
+
? {
|
|
1216
|
+
...cell,
|
|
1217
|
+
label: name,
|
|
1218
|
+
chord: true,
|
|
1219
|
+
diatonic:
|
|
1220
|
+
degreeOf(this.chords.key, ((chord % 12) + 12) % 12) !== undefined,
|
|
1221
|
+
}
|
|
1222
|
+
: this.noteCell(cell);
|
|
1159
1223
|
});
|
|
1160
1224
|
}
|
|
1161
1225
|
|
|
@@ -1213,6 +1277,7 @@ export function chordNotes(
|
|
|
1213
1277
|
seed: chord.seed,
|
|
1214
1278
|
...(chord.pattern !== undefined ? { pattern: chord.pattern } : {}),
|
|
1215
1279
|
velocity: 1,
|
|
1280
|
+
...chord.guitar,
|
|
1216
1281
|
}).map((note) => ({
|
|
1217
1282
|
pitch: note.pitch,
|
|
1218
1283
|
velocity: scaleVelocity(press.velocity, note.velocity),
|