@hraness/dawg 0.4.1 → 0.6.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 +99 -0
- package/DAWG.md +551 -33
- package/README.md +4 -4
- package/core/chords.ts +288 -7
- package/core/diff.ts +41 -12
- package/core/expression.ts +1241 -0
- package/core/fx.ts +437 -2
- package/core/granular.ts +528 -0
- package/core/instruments.ts +281 -0
- package/core/keys.ts +386 -0
- package/core/loop.ts +23 -0
- package/core/master.ts +455 -0
- package/core/midi.ts +452 -0
- package/core/resonators.ts +574 -0
- package/core/rhythm.ts +7 -2
- package/core/score.ts +717 -62
- package/core/sdk/eval-child.ts +40 -3
- package/core/sdk/print.ts +614 -27
- package/core/sdk/sync-instruments.ts +58 -0
- package/core/sdk/v1.ts +2824 -45
- package/core/sections.ts +2072 -0
- package/core/strings.ts +845 -0
- package/core/synth.ts +11 -1
- package/core/tempo.ts +1318 -0
- package/core/tuning.ts +1180 -0
- package/guides/audition.md +26 -0
- package/guides/automation.md +26 -0
- package/guides/chords.md +28 -0
- package/guides/effects.md +29 -0
- package/guides/faders.md +29 -0
- package/guides/files.md +28 -0
- package/guides/getting-started.md +26 -0
- package/guides/index.ts +75 -0
- package/guides/keys.md +27 -0
- package/guides/media.md +27 -0
- package/guides/mix.md +23 -0
- package/guides/music.md +14 -0
- package/guides/notes.md +26 -0
- package/guides/performance.md +26 -0
- package/guides/play.md +24 -0
- package/guides/project.md +13 -0
- package/guides/providers.md +25 -0
- package/guides/rhythm.md +26 -0
- package/guides/sessions.md +21 -0
- package/guides/sound.md +15 -0
- package/guides/sounds.md +29 -0
- package/guides/tempo.md +27 -0
- package/guides/tracks.md +25 -0
- package/guides/web-search.md +21 -0
- package/package.json +3 -1
- package/src/agent/agent.ts +9 -0
- package/src/agent/brief.ts +77 -3
- package/src/agent/chord-tools.ts +9 -1
- package/src/agent/expression-tools.ts +336 -0
- package/src/agent/granular-tools.ts +138 -0
- package/src/agent/master-tools.ts +299 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +68 -6
- package/src/agent/planner.ts +45 -2
- package/src/agent/preview-tool.ts +27 -4
- package/src/agent/section-tools.ts +411 -0
- package/src/agent/time-tools.ts +290 -0
- package/src/agent/tools.ts +574 -16
- package/src/agent/tuning-tools.ts +301 -0
- package/src/agent/xcb-agent.ts +9 -0
- package/src/audio/arrange.ts +489 -0
- package/src/audio/audition.ts +16 -3
- package/src/audio/click.ts +113 -1
- package/src/audio/clock.ts +71 -5
- package/src/audio/dsp/bank.ts +233 -0
- package/src/audio/dsp/fft.ts +6 -0
- package/src/audio/dsp/filters.ts +57 -0
- package/src/audio/dsp/interp.ts +112 -0
- package/src/audio/dsp/modal.ts +684 -0
- package/src/audio/dsp/onset.ts +197 -0
- package/src/audio/dsp/oversample.ts +202 -0
- package/src/audio/dsp/rng.ts +37 -0
- package/src/audio/dsp/shape.ts +81 -0
- package/src/audio/dsp/stft.ts +73 -0
- package/src/audio/dsp/window.ts +77 -0
- package/src/audio/effects/bus.ts +5 -4
- package/src/audio/effects/chain.ts +6 -1
- package/src/audio/effects/common.ts +30 -1
- package/src/audio/effects/dynamics.ts +2 -1
- package/src/audio/effects/filter.ts +5 -3
- package/src/audio/effects/modulation.ts +5 -1
- package/src/audio/effects/rig/cab.ts +99 -0
- package/src/audio/effects/rig/filters.ts +152 -0
- package/src/audio/effects/rig/gate.ts +39 -0
- package/src/audio/effects/rig/head.ts +487 -0
- package/src/audio/effects/rig/index.ts +170 -0
- package/src/audio/effects/rig/section.ts +78 -0
- package/src/audio/effects/rig/stomp.ts +238 -0
- package/src/audio/effects/space.ts +92 -2
- package/src/audio/engine.ts +100 -21
- package/src/audio/fit.ts +402 -0
- package/src/audio/granular.ts +664 -0
- package/src/audio/instrument-check.ts +133 -0
- package/src/audio/instruments.ts +111 -0
- package/src/audio/keys/dsp.ts +323 -0
- package/src/audio/keys/engine.ts +361 -0
- package/src/audio/keys/piano.ts +432 -0
- package/src/audio/live-worker.ts +54 -0
- package/src/audio/live.ts +263 -17
- package/src/audio/loudness.ts +596 -0
- package/src/audio/master.ts +660 -0
- package/src/audio/measure-worker.ts +45 -0
- package/src/audio/measure.ts +110 -0
- package/src/audio/player.ts +11 -6
- package/src/audio/preview.ts +76 -14
- package/src/audio/render-worker.ts +6 -1
- package/src/audio/renderer.ts +7 -1
- package/src/audio/resonators.ts +287 -0
- package/src/audio/sampler.ts +269 -34
- package/src/audio/samples.ts +30 -7
- package/src/audio/strings/body.ts +250 -0
- package/src/audio/strings/engine.ts +369 -0
- package/src/audio/strings/loop.ts +119 -0
- package/src/audio/strings/measure.test-helpers.ts +198 -0
- package/src/audio/strings/pluck.ts +354 -0
- package/src/audio/synth/voice.ts +60 -13
- package/src/audio/synth/zzfx.ts +10 -4
- package/src/audio/warp.ts +147 -0
- package/src/audio/wav.ts +377 -30
- package/src/commands/arrange.ts +949 -0
- package/src/commands/expression.ts +941 -0
- package/src/commands/fit.ts +135 -0
- package/src/commands/fx.ts +31 -0
- package/src/commands/granular.ts +427 -0
- package/src/commands/help.ts +355 -27
- package/src/commands/keys.ts +353 -0
- package/src/commands/master.ts +361 -0
- package/src/commands/modal.ts +247 -0
- package/src/commands/music.ts +6 -1
- package/src/commands/rig.ts +260 -0
- package/src/commands/sample.ts +3 -0
- package/src/commands/string.ts +175 -0
- package/src/commands/synth.ts +11 -1
- package/src/commands/time.ts +967 -0
- package/src/commands/tuning.ts +490 -0
- package/src/main.ts +919 -41
- package/src/project/check.ts +13 -0
- package/src/render.ts +139 -8
- package/src/session/daemon.ts +18 -8
- package/src/session/naming.ts +8 -1
- package/src/session/rebase.ts +21 -0
- package/src/tui/arrange-menu.ts +390 -0
- package/src/tui/audition.ts +38 -5
- package/src/tui/fader.ts +409 -0
- package/src/tui/granular-menu.ts +278 -0
- package/src/tui/menu-time.ts +401 -0
- package/src/tui/menu.ts +967 -23
- package/src/tui/modal-menu.ts +118 -0
- package/src/tui/performance-menu.ts +235 -0
- package/src/tui/play-chords.ts +3 -1
- package/src/tui/play-mode.ts +150 -6
- package/src/tui/play-session.ts +459 -43
- package/tui/app.ts +262 -13
- package/tui/arrange-strip.ts +174 -0
- package/tui/drawer.ts +478 -0
- package/tui/grammar.ts +63 -1
- package/tui/guide.ts +351 -0
- package/tui/highway.ts +87 -4
- package/tui/hits.ts +68 -0
- package/tui/input.ts +7 -0
- package/tui/keys.ts +72 -0
package/README.md
CHANGED
|
@@ -15,17 +15,17 @@ curl -fsSL https://dawg.sh/install | sh
|
|
|
15
15
|
or install the release tarball from GitHub directly:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
bun add -g https://github.com/hraness/dawg/releases/download/v0.
|
|
18
|
+
bun add -g https://github.com/hraness/dawg/releases/download/v0.6.0/hraness-dawg-0.6.0.tgz
|
|
19
19
|
dawg --help
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Each [release](https://github.com/hraness/dawg/releases) is immutable and ships the tarball, a `SHA256SUMS` file and a build provenance attestation. To check a download before installing it:
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
gh release download v0.
|
|
25
|
+
gh release download v0.6.0 --repo hraness/dawg
|
|
26
26
|
shasum -a 256 -c SHA256SUMS
|
|
27
|
-
gh attestation verify hraness-dawg-0.
|
|
28
|
-
bun add -g "$PWD/hraness-dawg-0.
|
|
27
|
+
gh attestation verify hraness-dawg-0.6.0.tgz --repo hraness/dawg
|
|
28
|
+
bun add -g "$PWD/hraness-dawg-0.6.0.tgz"
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
dawg is also on npm as [`@hraness/dawg`](https://www.npmjs.com/package/@hraness/dawg):
|
package/core/chords.ts
CHANGED
|
@@ -410,6 +410,8 @@ export const MODES = Object.freeze({
|
|
|
410
410
|
mixolydian: [0, 2, 4, 5, 7, 9, 10],
|
|
411
411
|
locrian: [0, 1, 3, 5, 6, 8, 10],
|
|
412
412
|
"harmonic-minor": [0, 2, 3, 5, 7, 8, 11],
|
|
413
|
+
"melodic-minor": [0, 2, 3, 5, 7, 9, 11],
|
|
414
|
+
"phrygian-dominant": [0, 1, 4, 5, 7, 8, 10],
|
|
413
415
|
} as const);
|
|
414
416
|
export type ModeName = keyof typeof MODES;
|
|
415
417
|
export const MODE_NAMES = Object.keys(MODES) as ModeName[];
|
|
@@ -432,25 +434,302 @@ const MODE_ALIASES: Readonly<Record<string, ModeName>> = Object.freeze({
|
|
|
432
434
|
"harmonic-minor": "harmonic-minor",
|
|
433
435
|
"harmonic minor": "harmonic-minor",
|
|
434
436
|
harmonic: "harmonic-minor",
|
|
437
|
+
"melodic-minor": "melodic-minor",
|
|
438
|
+
"melodic minor": "melodic-minor",
|
|
439
|
+
melodic: "melodic-minor",
|
|
440
|
+
"jazz minor": "melodic-minor",
|
|
441
|
+
"phrygian-dominant": "phrygian-dominant",
|
|
442
|
+
"phrygian dominant": "phrygian-dominant",
|
|
443
|
+
freygish: "phrygian-dominant",
|
|
444
|
+
spanish: "phrygian-dominant",
|
|
445
|
+
ajam: "major",
|
|
446
|
+
mahur: "major",
|
|
447
|
+
bilawal: "major",
|
|
435
448
|
});
|
|
436
449
|
|
|
437
|
-
export type
|
|
450
|
+
export type ScaleFamily =
|
|
451
|
+
"pentatonic" | "blues" | "maqam" | "dastgah" | "raga" | "messiaen";
|
|
438
452
|
|
|
439
453
|
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
454
|
+
* Scales beyond the chord modes, for keys such as `D bayati`, `C yaman` or
|
|
455
|
+
* `C messiaen-3`. `steps` are semitones above the tonic and may be
|
|
456
|
+
* fractional (a quarter tone is .5); `mode` is the seven-note mode the
|
|
457
|
+
* chord engine harmonizes with (the closest one; see DAWG.md). A raga's
|
|
458
|
+
* `intonation` is each step's traditional just pitch in cents (shruti
|
|
459
|
+
* offsets), which its named tuning applies (`tuning yaman`): Pythagorean
|
|
460
|
+
* ati-komal re and dha for Bhairavi, Bhairav, Purvi and Todi, the high
|
|
461
|
+
* tivra ma (729/512) for Yaman, 9/5 komal ni for Kafi, after Daniélou and
|
|
462
|
+
* Jairazbhoy. Maqam and dastgah quarter tones follow the 24-tone convention;
|
|
463
|
+
* Segah and Sikah start on a half-flat note, so their tonic is the key.
|
|
464
|
+
*/
|
|
465
|
+
export type ScaleInfo = Readonly<{
|
|
466
|
+
steps: readonly number[];
|
|
467
|
+
mode: ModeName;
|
|
468
|
+
family: ScaleFamily;
|
|
469
|
+
intonation?: readonly number[];
|
|
470
|
+
aliases?: readonly string[];
|
|
471
|
+
}>;
|
|
472
|
+
|
|
473
|
+
export const SCALES = Object.freeze({
|
|
474
|
+
"major-pentatonic": {
|
|
475
|
+
steps: [0, 2, 4, 7, 9],
|
|
476
|
+
mode: "major",
|
|
477
|
+
family: "pentatonic",
|
|
478
|
+
aliases: ["pentatonic", "major pentatonic", "pent"],
|
|
479
|
+
},
|
|
480
|
+
"minor-pentatonic": {
|
|
481
|
+
steps: [0, 3, 5, 7, 10],
|
|
482
|
+
mode: "minor",
|
|
483
|
+
family: "pentatonic",
|
|
484
|
+
aliases: ["minor pentatonic", "m pentatonic", "min pentatonic"],
|
|
485
|
+
},
|
|
486
|
+
blues: {
|
|
487
|
+
steps: [0, 3, 5, 6, 7, 10],
|
|
488
|
+
mode: "minor",
|
|
489
|
+
family: "blues",
|
|
490
|
+
aliases: ["minor blues"],
|
|
491
|
+
},
|
|
492
|
+
"major-blues": {
|
|
493
|
+
steps: [0, 2, 3, 4, 7, 9],
|
|
494
|
+
mode: "major",
|
|
495
|
+
family: "blues",
|
|
496
|
+
aliases: ["major blues"],
|
|
497
|
+
},
|
|
498
|
+
hijaz: {
|
|
499
|
+
steps: [0, 1, 4, 5, 7, 8, 10],
|
|
500
|
+
mode: "phrygian-dominant",
|
|
501
|
+
family: "maqam",
|
|
502
|
+
},
|
|
503
|
+
bayati: {
|
|
504
|
+
steps: [0, 1.5, 3, 5, 7, 8, 10],
|
|
505
|
+
mode: "phrygian",
|
|
506
|
+
family: "maqam",
|
|
507
|
+
},
|
|
508
|
+
rast: { steps: [0, 2, 3.5, 5, 7, 9, 10.5], mode: "major", family: "maqam" },
|
|
509
|
+
saba: { steps: [0, 1.5, 3, 4, 7, 8, 10], mode: "phrygian", family: "maqam" },
|
|
510
|
+
kurd: { steps: [0, 1, 3, 5, 7, 8, 10], mode: "phrygian", family: "maqam" },
|
|
511
|
+
nahawand: {
|
|
512
|
+
steps: [0, 2, 3, 5, 7, 8, 11],
|
|
513
|
+
mode: "harmonic-minor",
|
|
514
|
+
family: "maqam",
|
|
515
|
+
},
|
|
516
|
+
sikah: {
|
|
517
|
+
steps: [0, 1.5, 3.5, 5.5, 7, 8.5, 10.5],
|
|
518
|
+
mode: "phrygian",
|
|
519
|
+
family: "maqam",
|
|
520
|
+
aliases: ["sika"],
|
|
521
|
+
},
|
|
522
|
+
huzam: {
|
|
523
|
+
steps: [0, 1.5, 3.5, 4.5, 7.5, 8.5, 10.5],
|
|
524
|
+
mode: "phrygian",
|
|
525
|
+
family: "maqam",
|
|
526
|
+
aliases: ["houzam"],
|
|
527
|
+
},
|
|
528
|
+
nikriz: { steps: [0, 2, 3, 6, 7, 9, 10], mode: "dorian", family: "maqam" },
|
|
529
|
+
shur: {
|
|
530
|
+
steps: [0, 1.5, 3, 5, 7, 8, 10],
|
|
531
|
+
mode: "phrygian",
|
|
532
|
+
family: "dastgah",
|
|
533
|
+
},
|
|
534
|
+
homayoun: {
|
|
535
|
+
steps: [0, 1.5, 4, 5, 7, 8, 10],
|
|
536
|
+
mode: "phrygian-dominant",
|
|
537
|
+
family: "dastgah",
|
|
538
|
+
aliases: ["homayun"],
|
|
539
|
+
},
|
|
540
|
+
chahargah: {
|
|
541
|
+
steps: [0, 1.5, 4, 5, 7, 8.5, 11],
|
|
542
|
+
mode: "phrygian-dominant",
|
|
543
|
+
family: "dastgah",
|
|
544
|
+
aliases: ["chahar-gah"],
|
|
545
|
+
},
|
|
546
|
+
segah: {
|
|
547
|
+
steps: [0, 1.5, 3.5, 5, 6.5, 8.5, 10.5],
|
|
548
|
+
mode: "phrygian",
|
|
549
|
+
family: "dastgah",
|
|
550
|
+
aliases: ["sehgah", "se-gah"],
|
|
551
|
+
},
|
|
552
|
+
nava: {
|
|
553
|
+
steps: [0, 2, 3.5, 5, 7, 8, 10],
|
|
554
|
+
mode: "minor",
|
|
555
|
+
family: "dastgah",
|
|
556
|
+
},
|
|
557
|
+
yaman: {
|
|
558
|
+
steps: [0, 2, 4, 6, 7, 9, 11],
|
|
559
|
+
mode: "lydian",
|
|
560
|
+
family: "raga",
|
|
561
|
+
intonation: [0, 203.91, 386.31, 611.73, 701.96, 884.36, 1088.27],
|
|
562
|
+
aliases: ["kalyan", "yaman kalyan"],
|
|
563
|
+
},
|
|
564
|
+
bhairav: {
|
|
565
|
+
steps: [0, 1, 4, 5, 7, 8, 11],
|
|
566
|
+
mode: "phrygian-dominant",
|
|
567
|
+
family: "raga",
|
|
568
|
+
intonation: [0, 90.22, 386.31, 498.04, 701.96, 792.18, 1088.27],
|
|
569
|
+
},
|
|
570
|
+
kafi: {
|
|
571
|
+
steps: [0, 2, 3, 5, 7, 9, 10],
|
|
572
|
+
mode: "dorian",
|
|
573
|
+
family: "raga",
|
|
574
|
+
intonation: [0, 203.91, 315.64, 498.04, 701.96, 884.36, 1017.6],
|
|
575
|
+
},
|
|
576
|
+
bhairavi: {
|
|
577
|
+
steps: [0, 1, 3, 5, 7, 8, 10],
|
|
578
|
+
mode: "phrygian",
|
|
579
|
+
family: "raga",
|
|
580
|
+
intonation: [0, 90.22, 294.13, 498.04, 701.96, 792.18, 996.09],
|
|
581
|
+
},
|
|
582
|
+
asavari: {
|
|
583
|
+
steps: [0, 2, 3, 5, 7, 8, 10],
|
|
584
|
+
mode: "minor",
|
|
585
|
+
family: "raga",
|
|
586
|
+
intonation: [0, 203.91, 315.64, 498.04, 701.96, 813.69, 996.09],
|
|
587
|
+
},
|
|
588
|
+
khamaj: {
|
|
589
|
+
steps: [0, 2, 4, 5, 7, 9, 10],
|
|
590
|
+
mode: "mixolydian",
|
|
591
|
+
family: "raga",
|
|
592
|
+
intonation: [0, 203.91, 386.31, 498.04, 701.96, 884.36, 996.09],
|
|
593
|
+
},
|
|
594
|
+
todi: {
|
|
595
|
+
steps: [0, 1, 3, 6, 7, 8, 11],
|
|
596
|
+
mode: "phrygian",
|
|
597
|
+
family: "raga",
|
|
598
|
+
intonation: [0, 95, 294, 606, 702, 792, 1107],
|
|
599
|
+
},
|
|
600
|
+
purvi: {
|
|
601
|
+
steps: [0, 1, 4, 6, 7, 8, 11],
|
|
602
|
+
mode: "phrygian-dominant",
|
|
603
|
+
family: "raga",
|
|
604
|
+
intonation: [0, 90.22, 386.31, 590.22, 701.96, 792.18, 1088.27],
|
|
605
|
+
},
|
|
606
|
+
marwa: {
|
|
607
|
+
steps: [0, 1, 4, 6, 9, 11],
|
|
608
|
+
mode: "lydian",
|
|
609
|
+
family: "raga",
|
|
610
|
+
intonation: [0, 111.73, 386.31, 590.22, 884.36, 1088.27],
|
|
611
|
+
},
|
|
612
|
+
darbari: {
|
|
613
|
+
steps: [0, 2, 3, 5, 7, 8, 10],
|
|
614
|
+
mode: "minor",
|
|
615
|
+
family: "raga",
|
|
616
|
+
intonation: [0, 203.91, 294.13, 498.04, 701.96, 792.18, 996.09],
|
|
617
|
+
aliases: ["darbari kanada"],
|
|
618
|
+
},
|
|
619
|
+
malkauns: {
|
|
620
|
+
steps: [0, 3, 5, 8, 10],
|
|
621
|
+
mode: "minor",
|
|
622
|
+
family: "raga",
|
|
623
|
+
intonation: [0, 315.64, 498.04, 813.69, 996.09],
|
|
624
|
+
},
|
|
625
|
+
bhupali: {
|
|
626
|
+
steps: [0, 2, 4, 7, 9],
|
|
627
|
+
mode: "major",
|
|
628
|
+
family: "raga",
|
|
629
|
+
intonation: [0, 203.91, 386.31, 701.96, 884.36],
|
|
630
|
+
},
|
|
631
|
+
durga: {
|
|
632
|
+
steps: [0, 2, 5, 7, 9],
|
|
633
|
+
mode: "major",
|
|
634
|
+
family: "raga",
|
|
635
|
+
intonation: [0, 203.91, 498.04, 701.96, 884.36],
|
|
636
|
+
},
|
|
637
|
+
"messiaen-1": {
|
|
638
|
+
steps: [0, 2, 4, 6, 8, 10],
|
|
639
|
+
mode: "lydian",
|
|
640
|
+
family: "messiaen",
|
|
641
|
+
aliases: ["whole-tone", "whole tone", "wholetone"],
|
|
642
|
+
},
|
|
643
|
+
"messiaen-2": {
|
|
644
|
+
steps: [0, 1, 3, 4, 6, 7, 9, 10],
|
|
645
|
+
mode: "mixolydian",
|
|
646
|
+
family: "messiaen",
|
|
647
|
+
aliases: ["octatonic", "diminished", "half-whole"],
|
|
648
|
+
},
|
|
649
|
+
"messiaen-3": {
|
|
650
|
+
steps: [0, 2, 3, 4, 6, 7, 8, 10, 11],
|
|
651
|
+
mode: "minor",
|
|
652
|
+
family: "messiaen",
|
|
653
|
+
},
|
|
654
|
+
"messiaen-4": {
|
|
655
|
+
steps: [0, 1, 2, 5, 6, 7, 8, 11],
|
|
656
|
+
mode: "harmonic-minor",
|
|
657
|
+
family: "messiaen",
|
|
658
|
+
},
|
|
659
|
+
"messiaen-5": {
|
|
660
|
+
steps: [0, 1, 5, 6, 7, 11],
|
|
661
|
+
mode: "lydian",
|
|
662
|
+
family: "messiaen",
|
|
663
|
+
},
|
|
664
|
+
"messiaen-6": {
|
|
665
|
+
steps: [0, 2, 4, 5, 6, 8, 10, 11],
|
|
666
|
+
mode: "major",
|
|
667
|
+
family: "messiaen",
|
|
668
|
+
},
|
|
669
|
+
"messiaen-7": {
|
|
670
|
+
steps: [0, 1, 2, 3, 5, 6, 7, 8, 9, 11],
|
|
671
|
+
mode: "harmonic-minor",
|
|
672
|
+
family: "messiaen",
|
|
673
|
+
},
|
|
674
|
+
} as const satisfies Record<string, ScaleInfo>);
|
|
675
|
+
export type ScaleName = keyof typeof SCALES;
|
|
676
|
+
export const SCALE_NAMES = Object.keys(SCALES) as ScaleName[];
|
|
677
|
+
|
|
678
|
+
const SCALE_ALIASES: Readonly<Record<string, ScaleName>> = (() => {
|
|
679
|
+
const aliases: Record<string, ScaleName> = {};
|
|
680
|
+
for (const name of SCALE_NAMES) {
|
|
681
|
+
const info: ScaleInfo = SCALES[name];
|
|
682
|
+
aliases[name] = name;
|
|
683
|
+
aliases[name.replace(/-/g, " ")] = name;
|
|
684
|
+
for (const alias of info.aliases ?? []) aliases[alias] = name;
|
|
685
|
+
}
|
|
686
|
+
return Object.freeze(aliases);
|
|
687
|
+
})();
|
|
688
|
+
|
|
689
|
+
/** The library scale named `text` (case and `-`/space insensitive). */
|
|
690
|
+
export function scaleNamed(text: string): ScaleName | undefined {
|
|
691
|
+
const word = text.trim().toLowerCase().replace(/\s+/g, " ");
|
|
692
|
+
return SCALE_ALIASES[word] ?? SCALE_ALIASES[word.replace(/ /g, "-")];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* A key: a tonic and the seven-note `mode` the chord engine uses, plus the
|
|
697
|
+
* library `scale` when the key names one (`D bayati`, `C yaman`).
|
|
698
|
+
*/
|
|
699
|
+
export type Key = Readonly<{
|
|
700
|
+
tonic: number;
|
|
701
|
+
mode: ModeName;
|
|
702
|
+
scale?: ScaleName;
|
|
703
|
+
}>;
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Parse a key: `C`, `c major`, `Am`, `a minor`, `F# dorian`, `Eb mixo`,
|
|
707
|
+
* `D bayati`, `C messiaen-3`. Accepts the `<note> <mode>` form
|
|
708
|
+
* `core/key.ts` writes.
|
|
442
709
|
*/
|
|
443
710
|
export function parseKey(text: string | null | undefined): Key | undefined {
|
|
444
711
|
if (typeof text !== "string" || text.length > 40) return undefined;
|
|
445
712
|
const match = text
|
|
446
713
|
.trim()
|
|
447
|
-
.match(/^([a-gA-G])(#|b|♯|♭)?\s*(m(?![a-z])|[a-zA-Z][a-zA-
|
|
714
|
+
.match(/^([a-gA-G])(#|b|♯|♭)?\s*(m(?![a-z])|[a-zA-Z][a-zA-Z0-9 -]*)?$/);
|
|
448
715
|
if (!match) return undefined;
|
|
449
716
|
const tonic = parsePitchClass(`${match[1]}${match[2] ?? ""}`);
|
|
717
|
+
if (tonic === undefined) return undefined;
|
|
450
718
|
const word = (match[3] ?? "").trim();
|
|
451
719
|
const mode = MODE_ALIASES[word === "m" ? "m" : word.toLowerCase()];
|
|
452
|
-
if (
|
|
453
|
-
|
|
720
|
+
if (mode !== undefined) return Object.freeze({ tonic, mode });
|
|
721
|
+
const scale = scaleNamed(word);
|
|
722
|
+
if (scale === undefined) return undefined;
|
|
723
|
+
return Object.freeze({ tonic, mode: SCALES[scale].mode, scale });
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Steps of the key's whole scale in semitones above the tonic (fractional
|
|
728
|
+
* for quarter tones): the library scale when the key names one, else the
|
|
729
|
+
* mode.
|
|
730
|
+
*/
|
|
731
|
+
export function scaleSteps(key: Key): number[] {
|
|
732
|
+
return [...(key.scale ? SCALES[key.scale].steps : MODES[key.mode])];
|
|
454
733
|
}
|
|
455
734
|
|
|
456
735
|
/** True when names in the key read better with flats (F, Bb, Eb, d minor…). */
|
|
@@ -465,6 +744,8 @@ export function keyUsesFlats(key: Key): boolean {
|
|
|
465
744
|
minor: 9,
|
|
466
745
|
locrian: 11,
|
|
467
746
|
"harmonic-minor": 9,
|
|
747
|
+
"melodic-minor": 9,
|
|
748
|
+
"phrygian-dominant": 4,
|
|
468
749
|
};
|
|
469
750
|
const parent = mod12(key.tonic - parentOffset[key.mode]);
|
|
470
751
|
return [5, 10, 3, 8, 1].includes(parent);
|
|
@@ -472,7 +753,7 @@ export function keyUsesFlats(key: Key): boolean {
|
|
|
472
753
|
|
|
473
754
|
/** `C major`, `F# dorian`, `Bb minor`. */
|
|
474
755
|
export function keyName(key: Key): string {
|
|
475
|
-
return `${noteName(key.tonic, keyUsesFlats(key))} ${key.mode}`;
|
|
756
|
+
return `${noteName(key.tonic, keyUsesFlats(key))} ${key.scale ?? key.mode}`;
|
|
476
757
|
}
|
|
477
758
|
|
|
478
759
|
/** Pitch classes of the key's scale, tonic first. */
|
package/core/diff.ts
CHANGED
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
* canonical order, so insertion order never matters).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {
|
|
14
|
+
NOTE_EXPRESSION_FIELDS,
|
|
15
|
+
TRACK_PERFORMANCE_FIELDS,
|
|
16
|
+
} from "./expression.ts";
|
|
13
17
|
import {
|
|
14
18
|
applyScoreOperation,
|
|
15
19
|
TrackScore,
|
|
16
20
|
type Note,
|
|
21
|
+
type NotePatch,
|
|
17
22
|
type ScoreOperation,
|
|
18
23
|
type Track,
|
|
19
24
|
type TrackPatch,
|
|
@@ -35,6 +40,10 @@ const EFFECTS = [
|
|
|
35
40
|
"fxAutomation",
|
|
36
41
|
"synth",
|
|
37
42
|
"wavetable",
|
|
43
|
+
"string",
|
|
44
|
+
"granular",
|
|
45
|
+
"keys",
|
|
46
|
+
"modal",
|
|
38
47
|
] as const;
|
|
39
48
|
const LANES = [
|
|
40
49
|
"volumeAutomation",
|
|
@@ -62,6 +71,23 @@ export function diffScores(
|
|
|
62
71
|
if (a.tempoBpm !== b.tempoBpm)
|
|
63
72
|
ops.push({ type: "setTempo", tempoBpm: b.tempoBpm });
|
|
64
73
|
if (a.key !== b.key) ops.push({ type: "setKey", key: b.key });
|
|
74
|
+
if (!deepEqual(a.time, b.time))
|
|
75
|
+
ops.push({ type: "setTime", time: b.time ?? null });
|
|
76
|
+
if (!deepEqual(a.tuning, b.tuning))
|
|
77
|
+
ops.push({ type: "setTuning", tuning: b.tuning ?? null });
|
|
78
|
+
if (!deepEqual(a.master, b.master))
|
|
79
|
+
ops.push({ type: "setMaster", master: b.master ?? null });
|
|
80
|
+
if (
|
|
81
|
+
!deepEqual(a.sections, b.sections) ||
|
|
82
|
+
!deepEqual(a.form, b.form) ||
|
|
83
|
+
a.loopSection !== b.loopSection
|
|
84
|
+
)
|
|
85
|
+
ops.push({
|
|
86
|
+
type: "setSections",
|
|
87
|
+
sections: b.sections,
|
|
88
|
+
form: b.form,
|
|
89
|
+
...(b.loopSection === undefined ? {} : { loopSection: b.loopSection }),
|
|
90
|
+
});
|
|
65
91
|
|
|
66
92
|
const aTracks = new Map(a.tracks.map((track) => [track.id, track]));
|
|
67
93
|
const bTracks = new Map(b.tracks.map((track) => [track.id, track]));
|
|
@@ -133,15 +159,8 @@ export function applyScoreOperations(
|
|
|
133
159
|
return operations.reduce(applyScoreOperation, score);
|
|
134
160
|
}
|
|
135
161
|
|
|
136
|
-
function notePatch(
|
|
137
|
-
|
|
138
|
-
b: Note,
|
|
139
|
-
):
|
|
140
|
-
| Readonly<
|
|
141
|
-
Partial<Pick<Note, "startTick" | "durationTicks" | "pitch" | "velocity">>
|
|
142
|
-
>
|
|
143
|
-
| undefined {
|
|
144
|
-
const patch: Record<string, number> = {};
|
|
162
|
+
function notePatch(a: Note, b: Note): NotePatch | undefined {
|
|
163
|
+
const patch: Record<string, unknown> = {};
|
|
145
164
|
for (const key of [
|
|
146
165
|
"startTick",
|
|
147
166
|
"durationTicks",
|
|
@@ -149,7 +168,10 @@ function notePatch(
|
|
|
149
168
|
"velocity",
|
|
150
169
|
] as const)
|
|
151
170
|
if (a[key] !== b[key]) patch[key] = b[key];
|
|
152
|
-
|
|
171
|
+
for (const key of NOTE_EXPRESSION_FIELDS)
|
|
172
|
+
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
173
|
+
if ((a.cents ?? 0) !== (b.cents ?? 0)) patch.cents = b.cents ?? 0;
|
|
174
|
+
return Object.keys(patch).length > 0 ? (patch as NotePatch) : undefined;
|
|
153
175
|
}
|
|
154
176
|
|
|
155
177
|
function trackPatch(a: Track, b: Track): TrackPatch | undefined {
|
|
@@ -159,7 +181,13 @@ function trackPatch(a: Track, b: Track): TrackPatch | undefined {
|
|
|
159
181
|
if ((a.solo ?? false) !== (b.solo ?? false)) patch.solo = b.solo ?? false;
|
|
160
182
|
for (const key of EFFECTS)
|
|
161
183
|
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
162
|
-
for (const key of [
|
|
184
|
+
for (const key of [
|
|
185
|
+
"rhythm",
|
|
186
|
+
"kit",
|
|
187
|
+
"time",
|
|
188
|
+
"tuning",
|
|
189
|
+
...TRACK_PERFORMANCE_FIELDS,
|
|
190
|
+
] as const)
|
|
163
191
|
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
164
192
|
for (const key of LANES)
|
|
165
193
|
if (!deepEqual(a[key] ?? [], b[key] ?? [])) patch[key] = b[key] ?? [];
|
|
@@ -235,7 +263,8 @@ export function adoptNoteIds(
|
|
|
235
263
|
}
|
|
236
264
|
|
|
237
265
|
function exactKey(note: Note): string {
|
|
238
|
-
|
|
266
|
+
const cents = note.cents ? `|${note.cents}` : "";
|
|
267
|
+
return `${note.trackId}|${note.pitch}|${note.startTick}|${note.durationTicks}|${note.velocity}${cents}`;
|
|
239
268
|
}
|
|
240
269
|
|
|
241
270
|
function looseKey(note: Note): string {
|