@hraness/dawg 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/DAWG.md +454 -140
- package/README.md +30 -26
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +184 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/ids.ts +52 -0
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +8 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/range.ts +581 -0
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +955 -14
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +287 -5
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +2132 -46
- package/core/sections.ts +480 -35
- package/core/sing.ts +815 -0
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +31 -0
- package/guides/audio.md +31 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +15 -7
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/panes.md +32 -0
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +26 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +18 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -13
- package/guides/styles.md +31 -0
- package/guides/tape.md +32 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +6 -2
- package/src/agent/agent.ts +130 -18
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +378 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +27 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +25 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/range-tools.ts +216 -0
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +110 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +13 -10
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +49 -5
- package/src/audio/audio-command.ts +218 -0
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/devices.ts +264 -0
- package/src/audio/doctor.ts +157 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +360 -40
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +82 -32
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +560 -0
- package/src/commands/help.ts +774 -366
- package/src/commands/history.ts +139 -10
- package/src/commands/keys.ts +25 -8
- package/src/commands/modal.ts +1 -1
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +135 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +474 -0
- package/src/commands/strum.ts +13 -1
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +617 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +511 -0
- package/src/launch-args.ts +267 -0
- package/src/main.ts +2508 -308
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +20 -1
- package/src/session/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +199 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +174 -0
- package/src/session/port.ts +109 -14
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +365 -7
- package/src/session/rebase.ts +63 -12
- package/src/session/receipt.ts +265 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +126 -45
- package/src/tui/arrange-menu.ts +225 -23
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +113 -27
- package/src/tui/fader.ts +282 -42
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +987 -179
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +243 -28
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +491 -84
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +193 -0
- package/tui/drawer.ts +233 -27
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +143 -65
- package/tui/guide.ts +50 -5
- package/tui/highway.ts +309 -30
- package/tui/hints.ts +197 -0
- package/tui/hits.ts +7 -1
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +80 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +151 -18
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +46 -1
package/core/chords.ts
CHANGED
|
@@ -49,6 +49,7 @@ export const QUALITIES = [
|
|
|
49
49
|
"mb6",
|
|
50
50
|
"b6",
|
|
51
51
|
"7#9",
|
|
52
|
+
"b5",
|
|
52
53
|
] as const;
|
|
53
54
|
export type Quality = (typeof QUALITIES)[number];
|
|
54
55
|
|
|
@@ -65,6 +66,7 @@ const QUALITY_INTERVALS: Readonly<Record<Quality, readonly number[]>> =
|
|
|
65
66
|
mb6: [0, 3, 7, 8],
|
|
66
67
|
b6: [0, 4, 7, 8],
|
|
67
68
|
"7#9": [0, 4, 7, 10, 15],
|
|
69
|
+
b5: [0, 4, 6],
|
|
68
70
|
});
|
|
69
71
|
|
|
70
72
|
/**
|
|
@@ -117,6 +119,11 @@ export type Chord = Readonly<{
|
|
|
117
119
|
* such as `C11`, `G13` or `A7b9` carry them (0.6.1).
|
|
118
120
|
*/
|
|
119
121
|
tensions?: readonly number[] | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* The root's letter, 0..6 for C..B, when a roman numeral spelled it:
|
|
124
|
+
* `bVII` in C names Bb (not A#) and `vii` in F# names E# (not F).
|
|
125
|
+
*/
|
|
126
|
+
letter?: number | undefined;
|
|
120
127
|
}>;
|
|
121
128
|
|
|
122
129
|
export function makeChord(
|
|
@@ -195,6 +202,25 @@ export function noteName(pc: number, flats = false): string {
|
|
|
195
202
|
return (flats ? FLAT_NAMES : SHARP_NAMES)[mod12(pc)]!;
|
|
196
203
|
}
|
|
197
204
|
|
|
205
|
+
const LETTERS = "CDEFGAB";
|
|
206
|
+
const LETTER_PCS = [0, 2, 4, 5, 7, 9, 11] as const;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* `pc` spelled on letter `letter` (0..6, C..B) with one accidental at
|
|
210
|
+
* most (E#, Cb, Bb); undefined when that would need a double accidental.
|
|
211
|
+
*/
|
|
212
|
+
export function spellOnLetter(pc: number, letter: number): string | undefined {
|
|
213
|
+
const l = ((Math.trunc(letter) % 7) + 7) % 7;
|
|
214
|
+
const diff = ((mod12(pc) - LETTER_PCS[l]! + 18) % 12) - 6;
|
|
215
|
+
if (Math.abs(diff) > 1) return undefined;
|
|
216
|
+
return `${LETTERS[l]}${diff === 1 ? "#" : diff === -1 ? "b" : ""}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The letter (0..6, C..B) a key's tonic is spelled on. */
|
|
220
|
+
function tonicLetter(key: Key): number {
|
|
221
|
+
return LETTERS.indexOf(noteName(key.tonic, keyUsesFlats(key))[0]!);
|
|
222
|
+
}
|
|
223
|
+
|
|
198
224
|
const SECRET_SUFFIX: Readonly<Partial<Record<Quality, string>>> = Object.freeze(
|
|
199
225
|
{ madd4: "m(add4)", mb6: "m(b6)", b6: "(b6)", "7#9": "7#9" },
|
|
200
226
|
);
|
|
@@ -273,6 +299,9 @@ export function chordSuffix(chord: Chord): string {
|
|
|
273
299
|
base = `${seventh}(no3)`;
|
|
274
300
|
if (nine) extras.push("9");
|
|
275
301
|
break;
|
|
302
|
+
case "b5":
|
|
303
|
+
base = `${ninth}b5`;
|
|
304
|
+
break;
|
|
276
305
|
default:
|
|
277
306
|
base = seventh; // secret qualities returned above
|
|
278
307
|
}
|
|
@@ -291,6 +320,7 @@ export function chordSuffix(chord: Chord): string {
|
|
|
291
320
|
mb6: "m(b6)",
|
|
292
321
|
b6: "(b6)",
|
|
293
322
|
"7#9": "7#9",
|
|
323
|
+
b5: "(b5)",
|
|
294
324
|
};
|
|
295
325
|
base = triad[q];
|
|
296
326
|
if (six && nine && (q === "maj" || q === "min")) return `${base}6/9`;
|
|
@@ -310,7 +340,11 @@ export function chordSuffix(chord: Chord): string {
|
|
|
310
340
|
export function chordName(chord: Chord, flats = false): string {
|
|
311
341
|
const slash =
|
|
312
342
|
chord.bass === undefined ? "" : `/${noteName(chord.bass, flats)}`;
|
|
313
|
-
|
|
343
|
+
const root =
|
|
344
|
+
(chord.letter === undefined
|
|
345
|
+
? undefined
|
|
346
|
+
: spellOnLetter(chord.root, chord.letter)) ?? noteName(chord.root, flats);
|
|
347
|
+
return `${root}${chordSuffix(chord)}${slash}`;
|
|
314
348
|
}
|
|
315
349
|
|
|
316
350
|
/** Suffix → quality and extensions, longest first when parsing. */
|
|
@@ -375,6 +409,31 @@ const SUFFIXES: readonly (readonly [string, Quality, readonly Extension[]])[] =
|
|
|
375
409
|
["(b6)", "b6", []],
|
|
376
410
|
["addb6", "b6", []],
|
|
377
411
|
["7#9", "7#9", []],
|
|
412
|
+
// 0.7: more spellings of the same chords.
|
|
413
|
+
["-maj7", "min", ["M7"]],
|
|
414
|
+
["mmaj7", "min", ["M7"]],
|
|
415
|
+
["mMaj7", "min", ["M7"]],
|
|
416
|
+
["m(maj7)", "min", ["M7"]],
|
|
417
|
+
["-Δ7", "min", ["M7"]],
|
|
418
|
+
["mΔ7", "min", ["M7"]],
|
|
419
|
+
["add2", "maj", ["9"]],
|
|
420
|
+
["2", "maj", ["9"]],
|
|
421
|
+
["madd2", "min", ["9"]],
|
|
422
|
+
["6add9", "maj", ["6", "9"]],
|
|
423
|
+
["m6add9", "min", ["6", "9"]],
|
|
424
|
+
["-6", "min", ["6"]],
|
|
425
|
+
["-9", "min", ["m7", "9"]],
|
|
426
|
+
["dom9", "maj", ["m7", "9"]],
|
|
427
|
+
["aug(maj7)", "aug", ["M7"]],
|
|
428
|
+
["augmaj7", "aug", ["M7"]],
|
|
429
|
+
["+maj7", "aug", ["M7"]],
|
|
430
|
+
["aug9", "aug", ["m7", "9"]],
|
|
431
|
+
["+9", "aug", ["m7", "9"]],
|
|
432
|
+
["m(maj9)", "min", ["M7", "9"]],
|
|
433
|
+
["mmaj9", "min", ["M7", "9"]],
|
|
434
|
+
["dim(maj7)", "dim", ["M7"]],
|
|
435
|
+
["7(no3)", "5", ["m7"]],
|
|
436
|
+
["maj7(no3)", "5", ["M7"]],
|
|
378
437
|
];
|
|
379
438
|
|
|
380
439
|
/** Typed upper-tension chords (0.6.1): suffix, quality, buttons, tensions. */
|
|
@@ -409,10 +468,7 @@ const TENSION_NAMES: Readonly<Record<number, string>> = Object.freeze({
|
|
|
409
468
|
21: "13",
|
|
410
469
|
});
|
|
411
470
|
|
|
412
|
-
const SUFFIX_TABLE = new Map<
|
|
413
|
-
string,
|
|
414
|
-
{ quality: Quality; ext: readonly Extension[]; tensions?: readonly number[] }
|
|
415
|
-
>([
|
|
471
|
+
const SUFFIX_TABLE = new Map<string, SuffixEntry>([
|
|
416
472
|
...SUFFIXES.map(
|
|
417
473
|
([suffix, quality, ext]) => [suffix, { quality, ext }] as const,
|
|
418
474
|
),
|
|
@@ -445,6 +501,81 @@ export function parsePitchClass(text: string): number | undefined {
|
|
|
445
501
|
return mod12(LETTER[match[1]!.toLowerCase()]! + accidental);
|
|
446
502
|
}
|
|
447
503
|
|
|
504
|
+
/** Alteration → upper tension (semitones above the root). */
|
|
505
|
+
const ALTERATION_TENSION: Readonly<Record<string, number>> = Object.freeze({
|
|
506
|
+
b9: 13,
|
|
507
|
+
"#9": 15,
|
|
508
|
+
"11": 17,
|
|
509
|
+
"#11": 18,
|
|
510
|
+
"+11": 18,
|
|
511
|
+
b13: 20,
|
|
512
|
+
"13": 21,
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const TOKEN = "b5|#5|\\+5|b9|#9|#11|\\+11|b13|alt|add9|add11|add13|11|13|9|6";
|
|
516
|
+
/** Bare alterations or parenthesized comma lists of them, in any order. */
|
|
517
|
+
const ALTERATIONS = new RegExp(
|
|
518
|
+
`^(?:(?:${TOKEN})|\\((?:${TOKEN})(?:,(?:${TOKEN}))*\\))+$`,
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
type SuffixEntry = {
|
|
522
|
+
quality: Quality;
|
|
523
|
+
ext: readonly Extension[];
|
|
524
|
+
tensions?: readonly number[];
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* A suffix the table does not list, read as a listed base followed by
|
|
529
|
+
* alterations, bare or in parentheses: `7#5`, `9#11`, `13#11`, `7b9b13`,
|
|
530
|
+
* `9b5`, `maj7+5`, `7alt`, `7(b9,#9)`. A raised fifth makes the triad
|
|
531
|
+
* augmented, a lowered one makes a major triad `b5` (a minor one
|
|
532
|
+
* diminished); `alt` is b9, #9, #11 and b13 over a dominant seventh.
|
|
533
|
+
*/
|
|
534
|
+
function parseAlteredSuffix(suffix: string): SuffixEntry | undefined {
|
|
535
|
+
for (let cut = suffix.length - 1; cut >= 0; cut -= 1) {
|
|
536
|
+
const base = SUFFIX_TABLE.get(suffix.slice(0, cut));
|
|
537
|
+
if (!base) continue;
|
|
538
|
+
const raw = suffix.slice(cut);
|
|
539
|
+
if (!ALTERATIONS.test(raw)) continue;
|
|
540
|
+
const rest = raw.replace(/[(),]/g, "");
|
|
541
|
+
const tokens = rest.match(
|
|
542
|
+
/b5|#5|\+5|b9|#9|#11|\+11|b13|alt|add9|add11|add13|11|13|9|6/g,
|
|
543
|
+
);
|
|
544
|
+
if (!tokens || tokens.join("") !== rest) continue;
|
|
545
|
+
let quality: Quality = base.quality;
|
|
546
|
+
const ext = new Set<Extension>(base.ext);
|
|
547
|
+
const tensions = new Set<number>(base.tensions ?? []);
|
|
548
|
+
let ok = true;
|
|
549
|
+
for (const token of tokens) {
|
|
550
|
+
if (token === "#5" || token === "+5") {
|
|
551
|
+
if (quality === "maj" || quality === "aug") quality = "aug";
|
|
552
|
+
else ok = false;
|
|
553
|
+
} else if (token === "b5") {
|
|
554
|
+
if (quality === "maj" || quality === "b5") quality = "b5";
|
|
555
|
+
else if (quality === "min" || quality === "dim") quality = "dim";
|
|
556
|
+
else ok = false;
|
|
557
|
+
} else if (token === "alt") {
|
|
558
|
+
if (quality !== "maj") ok = false;
|
|
559
|
+
ext.add("m7");
|
|
560
|
+
for (const step of [13, 15, 18, 20]) tensions.add(step);
|
|
561
|
+
} else if (token === "9" || token === "add9") ext.add("9");
|
|
562
|
+
else if (token === "6") ext.add("6");
|
|
563
|
+
else {
|
|
564
|
+
const step = ALTERATION_TENSION[token.replace(/^add/, "")];
|
|
565
|
+
if (step === undefined) ok = false;
|
|
566
|
+
else tensions.add(step);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (!ok) continue;
|
|
570
|
+
return {
|
|
571
|
+
quality,
|
|
572
|
+
ext: [...ext],
|
|
573
|
+
tensions: [...tensions].sort((a, b) => a - b),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
|
|
448
579
|
/** Parse a chord symbol (`Cm7`, `F#dim`, `Bbmaj9`, `G7sus4`, `C/E`). */
|
|
449
580
|
export function parseChord(symbol: string): Chord | undefined {
|
|
450
581
|
if (typeof symbol !== "string" || symbol.length > 24) return undefined;
|
|
@@ -455,7 +586,8 @@ export function parseChord(symbol: string): Chord | undefined {
|
|
|
455
586
|
const match = trimmed.match(/^([A-Ga-g])(#|b|♯|♭)?([^/]*)(?:\/(.+))?$/);
|
|
456
587
|
if (!match) return undefined;
|
|
457
588
|
const root = parsePitchClass(`${match[1]}${match[2] ?? ""}`);
|
|
458
|
-
const entry =
|
|
589
|
+
const entry =
|
|
590
|
+
SUFFIX_TABLE.get(match[3] ?? "") ?? parseAlteredSuffix(match[3] ?? "");
|
|
459
591
|
if (root === undefined || !entry) return undefined;
|
|
460
592
|
let bass: number | undefined;
|
|
461
593
|
if (match[4] !== undefined) {
|
|
@@ -515,7 +647,15 @@ const MODE_ALIASES: Readonly<Record<string, ModeName>> = Object.freeze({
|
|
|
515
647
|
});
|
|
516
648
|
|
|
517
649
|
export type ScaleFamily =
|
|
518
|
-
|
|
650
|
+
| "pentatonic"
|
|
651
|
+
| "blues"
|
|
652
|
+
| "maqam"
|
|
653
|
+
| "dastgah"
|
|
654
|
+
| "raga"
|
|
655
|
+
| "messiaen"
|
|
656
|
+
| "chromatic"
|
|
657
|
+
| "overtone"
|
|
658
|
+
| "quarter-tone";
|
|
519
659
|
|
|
520
660
|
/**
|
|
521
661
|
* Scales beyond the chord modes, for keys such as `D bayati`, `C yaman` or
|
|
@@ -562,6 +702,12 @@ export const SCALES = Object.freeze({
|
|
|
562
702
|
family: "blues",
|
|
563
703
|
aliases: ["major blues"],
|
|
564
704
|
},
|
|
705
|
+
"yonanuki-minor": {
|
|
706
|
+
steps: [0, 2, 3, 7, 8],
|
|
707
|
+
mode: "minor",
|
|
708
|
+
family: "pentatonic",
|
|
709
|
+
aliases: ["yonanuki", "yonanuki minor", "enka minor"],
|
|
710
|
+
},
|
|
565
711
|
hijaz: {
|
|
566
712
|
steps: [0, 1, 4, 5, 7, 8, 10],
|
|
567
713
|
mode: "phrygian-dominant",
|
|
@@ -738,6 +884,32 @@ export const SCALES = Object.freeze({
|
|
|
738
884
|
mode: "harmonic-minor",
|
|
739
885
|
family: "messiaen",
|
|
740
886
|
},
|
|
887
|
+
// All twelve pitch classes: the field of free atonality and the row.
|
|
888
|
+
chromatic: {
|
|
889
|
+
steps: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
|
890
|
+
mode: "minor",
|
|
891
|
+
family: "chromatic",
|
|
892
|
+
aliases: ["twelve-tone", "aggregate"],
|
|
893
|
+
},
|
|
894
|
+
// Partials 8 to 15 of the harmonic series over the tonic (Grisey,
|
|
895
|
+
// Murail): the acoustic scale with the 11th and 13th partials' and the
|
|
896
|
+
// 7th's just pitches, which its named tuning applies.
|
|
897
|
+
"harmonic-series": {
|
|
898
|
+
steps: [0, 2, 4, 6, 7, 9, 10, 11],
|
|
899
|
+
mode: "mixolydian",
|
|
900
|
+
family: "overtone",
|
|
901
|
+
intonation: [0, 204, 386, 551, 702, 841, 969, 1088],
|
|
902
|
+
aliases: ["overtone", "overtone-scale", "partials"],
|
|
903
|
+
},
|
|
904
|
+
// Each tempered degree of the major scale beside its quarter-tone
|
|
905
|
+
// shadow (Haba, Wyschnegradsky): neutral 2nd, 3rd, 6th and 7th and a
|
|
906
|
+
// quarter-sharp 4th, sounded as note cents over twelve-tone keys.
|
|
907
|
+
"quarter-tone": {
|
|
908
|
+
steps: [0, 1.5, 2, 3.5, 4, 5, 5.5, 7, 8.5, 9, 10.5],
|
|
909
|
+
mode: "major",
|
|
910
|
+
family: "quarter-tone",
|
|
911
|
+
aliases: ["quartertone", "24-tone"],
|
|
912
|
+
},
|
|
741
913
|
} as const satisfies Record<string, ScaleInfo>);
|
|
742
914
|
export type ScaleName = keyof typeof SCALES;
|
|
743
915
|
export const SCALE_NAMES = Object.keys(SCALES) as ScaleName[];
|
|
@@ -944,9 +1116,14 @@ export function romanOf(key: Key, chord: Chord): string {
|
|
|
944
1116
|
if (degree < 0) {
|
|
945
1117
|
// Name chromatic roots against the major scale: bIII, #iv°.
|
|
946
1118
|
const major = MODES.major.map((step) => mod12(key.tonic + step));
|
|
1119
|
+
const natural = major.indexOf(chord.root);
|
|
947
1120
|
const flat = major.indexOf(mod12(chord.root + 1));
|
|
948
1121
|
const sharp = major.indexOf(mod12(chord.root - 1));
|
|
949
|
-
if (
|
|
1122
|
+
if (natural >= 0) {
|
|
1123
|
+
// A major-scale note the mode alters: ♮II in Phrygian.
|
|
1124
|
+
degree = natural;
|
|
1125
|
+
accidental = "♮";
|
|
1126
|
+
} else if (flat >= 0) {
|
|
950
1127
|
degree = flat;
|
|
951
1128
|
accidental = "b";
|
|
952
1129
|
} else {
|
|
@@ -957,7 +1134,6 @@ export function romanOf(key: Key, chord: Chord): string {
|
|
|
957
1134
|
const lower =
|
|
958
1135
|
chord.quality === "min" ||
|
|
959
1136
|
chord.quality === "dim" ||
|
|
960
|
-
chord.quality === "5" ||
|
|
961
1137
|
chord.quality === "madd4" ||
|
|
962
1138
|
chord.quality === "mb6";
|
|
963
1139
|
const numeral = NUMERALS[degree]!;
|
|
@@ -974,7 +1150,33 @@ export function romanOf(key: Key, chord: Chord): string {
|
|
|
974
1150
|
: ext.has("M7")
|
|
975
1151
|
? "maj7"
|
|
976
1152
|
: "";
|
|
977
|
-
|
|
1153
|
+
// The short numeral when it reads back as this chord; else the chord's
|
|
1154
|
+
// own suffix in brackets (`I[7]`, `i[m6]`, `V[7#9]`), which is exact.
|
|
1155
|
+
const plain = `${accidental}${body}${mark}${seventh}`;
|
|
1156
|
+
const bare = makeChord(
|
|
1157
|
+
chord.root,
|
|
1158
|
+
chord.quality,
|
|
1159
|
+
chord.extensions,
|
|
1160
|
+
undefined,
|
|
1161
|
+
chord.tensions,
|
|
1162
|
+
);
|
|
1163
|
+
for (const candidate of [
|
|
1164
|
+
plain,
|
|
1165
|
+
`${accidental}${body}${mark}${seventh === "7" ? "dom7" : seventh}`,
|
|
1166
|
+
])
|
|
1167
|
+
if (sameChord(parseRoman(key, candidate), bare)) return candidate;
|
|
1168
|
+
return `${accidental}${body}[${chordSuffix(bare)}]`;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function sameChord(a: Chord | undefined, b: Chord): boolean {
|
|
1172
|
+
return (
|
|
1173
|
+
a !== undefined &&
|
|
1174
|
+
a.root === b.root &&
|
|
1175
|
+
a.quality === b.quality &&
|
|
1176
|
+
a.bass === b.bass &&
|
|
1177
|
+
a.extensions.join() === b.extensions.join() &&
|
|
1178
|
+
(a.tensions ?? []).join() === (b.tensions ?? []).join()
|
|
1179
|
+
);
|
|
978
1180
|
}
|
|
979
1181
|
|
|
980
1182
|
/**
|
|
@@ -987,31 +1189,64 @@ export function romanOf(key: Key, chord: Chord): string {
|
|
|
987
1189
|
* major). `7` adds the diatonic seventh; `maj7`/`M7` and `dom7` are exact.
|
|
988
1190
|
*/
|
|
989
1191
|
export function parseRoman(key: Key, text: string): Chord | undefined {
|
|
990
|
-
|
|
1192
|
+
return romanIn(key, text, tonicLetter(key));
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/** `parseRoman` with the tonic spelled on `tonic` (0..6, C..B). */
|
|
1196
|
+
function romanIn(key: Key, text: string, tonic: number): Chord | undefined {
|
|
1197
|
+
if (typeof text !== "string" || text.length > 32) return undefined;
|
|
991
1198
|
const trimmed = text.trim();
|
|
1199
|
+
const exact = trimmed.match(
|
|
1200
|
+
/^(b|#|♭|♯|♮)?(vii|vi|v|iv|iii|ii|i|VII|VI|V|IV|III|II|I)\[([^\]]*)\]$/,
|
|
1201
|
+
);
|
|
1202
|
+
if (exact) {
|
|
1203
|
+
// `I[7]`: the numeral names the root, the bracket is a chord suffix.
|
|
1204
|
+
const degree = NUMERALS.indexOf(exact[2]!.toLowerCase());
|
|
1205
|
+
const shift =
|
|
1206
|
+
exact[1] === "b" || exact[1] === "♭"
|
|
1207
|
+
? -1
|
|
1208
|
+
: exact[1] === "♮" || !exact[1]
|
|
1209
|
+
? 0
|
|
1210
|
+
: 1;
|
|
1211
|
+
const root = !exact[1]
|
|
1212
|
+
? scaleOf(key)[degree]
|
|
1213
|
+
: mod12(key.tonic + MODES.major[degree]! + shift);
|
|
1214
|
+
if (root === undefined) return undefined;
|
|
1215
|
+
const letter = (tonic + degree) % 7;
|
|
1216
|
+
const chord = parseChord(
|
|
1217
|
+
`${spellOnLetter(root, letter) ?? noteName(root)}${exact[3]!}`,
|
|
1218
|
+
);
|
|
1219
|
+
return chord && Object.freeze({ ...chord, letter });
|
|
1220
|
+
}
|
|
992
1221
|
const slash = trimmed.match(/^(.+)\/(.+)$/);
|
|
993
1222
|
if (slash) {
|
|
994
1223
|
// V/x: the chord built on the degree of x in the key (secondary function).
|
|
995
|
-
const target =
|
|
1224
|
+
const target = romanIn(key, slash[2]!, tonic);
|
|
996
1225
|
if (!target) return undefined;
|
|
997
|
-
|
|
998
|
-
|
|
1226
|
+
return romanIn(
|
|
1227
|
+
{ tonic: target.root, mode: "major" },
|
|
1228
|
+
slash[1]!,
|
|
1229
|
+
target.letter ?? tonic,
|
|
1230
|
+
);
|
|
999
1231
|
}
|
|
1000
1232
|
const match = trimmed.match(
|
|
1001
|
-
/^(b
|
|
1233
|
+
/^(b|#|♭|♯|♮)?(vii|vi|v|iv|iii|ii|i|VII|VI|V|IV|III|II|I)(°|o|ø|\+)?(maj7|M7|dom7|7|9|maj9|6|sus4|sus2|sus|add9)?$/,
|
|
1002
1234
|
);
|
|
1003
1235
|
if (!match) return undefined;
|
|
1004
1236
|
const accidental =
|
|
1005
|
-
match[1] === "b" || match[1] === "♭"
|
|
1237
|
+
match[1] === "b" || match[1] === "♭"
|
|
1238
|
+
? -1
|
|
1239
|
+
: match[1] === "#" || match[1] === "♯"
|
|
1240
|
+
? 1
|
|
1241
|
+
: 0;
|
|
1006
1242
|
const numeral = match[2]!;
|
|
1007
1243
|
const lower = numeral === numeral.toLowerCase();
|
|
1008
1244
|
const degree = NUMERALS.indexOf(numeral.toLowerCase());
|
|
1009
1245
|
const mark = match[3];
|
|
1010
1246
|
const suffix = match[4] ?? "";
|
|
1011
|
-
const root =
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
: mod12(key.tonic + MODES.major[degree]! + accidental);
|
|
1247
|
+
const root = !match[1]
|
|
1248
|
+
? scaleOf(key)[degree]!
|
|
1249
|
+
: mod12(key.tonic + MODES.major[degree]! + accidental);
|
|
1015
1250
|
const inKey = degreeOf(key, root);
|
|
1016
1251
|
const triad = inKey === undefined ? undefined : diatonicChord(key, inKey);
|
|
1017
1252
|
const seventh =
|
|
@@ -1065,7 +1300,10 @@ export function parseRoman(key: Key, text: string): Chord | undefined {
|
|
|
1065
1300
|
quality = "sus2";
|
|
1066
1301
|
break;
|
|
1067
1302
|
}
|
|
1068
|
-
return
|
|
1303
|
+
return Object.freeze({
|
|
1304
|
+
...makeChord(root, quality, ext),
|
|
1305
|
+
letter: (tonic + degree) % 7,
|
|
1306
|
+
});
|
|
1069
1307
|
}
|
|
1070
1308
|
|
|
1071
1309
|
// ---------------------------------------------------------------------------
|
|
@@ -1107,7 +1345,17 @@ export function rotate(pitches: readonly number[], steps: number): number[] {
|
|
|
1107
1345
|
|
|
1108
1346
|
export function rootPosition(chord: Chord, anchor = 60): number[] {
|
|
1109
1347
|
const rootPitch = anchor + mod12(chord.root - anchor);
|
|
1110
|
-
|
|
1348
|
+
const steps = chordIntervals(chord);
|
|
1349
|
+
// A natural 11 over a major 3rd and a 7th is the avoid-note clash (E
|
|
1350
|
+
// under F a minor 9th up in C11), so the voicing drops the 3rd: C11 is
|
|
1351
|
+
// C G Bb D F, the sus voicing; C13 already leaves the 11 out.
|
|
1352
|
+
const clash =
|
|
1353
|
+
steps.includes(4) &&
|
|
1354
|
+
steps.includes(17) &&
|
|
1355
|
+
(steps.includes(10) || steps.includes(11));
|
|
1356
|
+
return steps
|
|
1357
|
+
.filter((step) => !(clash && step === 4))
|
|
1358
|
+
.map((step) => rootPitch + step);
|
|
1111
1359
|
}
|
|
1112
1360
|
|
|
1113
1361
|
/** Open voicings: `open` drops the second voice from the top an octave
|
|
@@ -2488,6 +2736,6 @@ export function renderProgression(options: RenderOptions): RenderedProgression {
|
|
|
2488
2736
|
export const CHORD_PROCESS = [
|
|
2489
2737
|
"Chords (Orchid-style): pick chords from the song key's diatonic set (in C major: C Dm Em F G Am Bdim; minor keys i ii° III iv v VI VII, V major for cadences).",
|
|
2490
2738
|
"Move tonic (I vi iii) → predominant (IV ii) → dominant (V vii°) → tonic; loops end on V or IV to lead home.",
|
|
2491
|
-
"Add sevenths/9ths as
|
|
2739
|
+
"Add sevenths/9ths as color (6, m7, M7, 9 extensions); voice-lead each chord to the inversion nearest the previous one in C3–G5 so common tones hold; put the root an octave or two below as bass.",
|
|
2492
2740
|
"Use suggest_progression to get voice-led chords and write_chords to write them (block, strum, arpeggio, harp, slop or a rhythm pattern; bassMode chords|unison|single|solo), instead of hand-placing chord notes with add_notes.",
|
|
2493
2741
|
].join(" ");
|