@hraness/dawg 0.4.1 → 0.5.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 +70 -0
- package/DAWG.md +293 -31
- package/README.md +4 -4
- package/core/chords.ts +288 -7
- package/core/diff.ts +37 -12
- package/core/expression.ts +1241 -0
- package/core/loop.ts +23 -0
- package/core/master.ts +455 -0
- package/core/midi.ts +452 -0
- package/core/rhythm.ts +7 -2
- package/core/score.ts +536 -56
- package/core/sdk/eval-child.ts +38 -3
- package/core/sdk/print.ts +445 -14
- package/core/sdk/v1.ts +1709 -23
- package/core/sections.ts +2046 -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 +27 -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 +25 -0
- package/guides/tempo.md +26 -0
- package/guides/tracks.md +25 -0
- package/guides/web-search.md +21 -0
- package/package.json +3 -1
- package/src/agent/agent.ts +2 -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/master-tools.ts +299 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +29 -4
- package/src/agent/planner.ts +45 -2
- package/src/agent/preview-tool.ts +21 -3
- package/src/agent/section-tools.ts +411 -0
- package/src/agent/time-tools.ts +290 -0
- package/src/agent/tools.ts +36 -2
- package/src/agent/tuning-tools.ts +301 -0
- package/src/agent/xcb-agent.ts +2 -0
- package/src/audio/arrange.ts +471 -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/effects/bus.ts +5 -4
- 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/space.ts +92 -2
- package/src/audio/engine.ts +90 -20
- package/src/audio/live.ts +33 -6
- package/src/audio/loudness.ts +551 -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 +74 -14
- package/src/audio/render-worker.ts +6 -1
- package/src/audio/renderer.ts +7 -1
- package/src/audio/sampler.ts +108 -19
- package/src/audio/synth/voice.ts +60 -13
- package/src/audio/synth/zzfx.ts +10 -4
- package/src/audio/warp.ts +86 -0
- package/src/audio/wav.ts +301 -26
- package/src/commands/arrange.ts +949 -0
- package/src/commands/expression.ts +934 -0
- package/src/commands/help.ts +281 -24
- package/src/commands/master.ts +361 -0
- package/src/commands/music.ts +6 -1
- package/src/commands/synth.ts +11 -1
- package/src/commands/time.ts +964 -0
- package/src/commands/tuning.ts +490 -0
- package/src/main.ts +709 -33
- package/src/render.ts +109 -7
- 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/menu-time.ts +401 -0
- package/src/tui/menu.ts +635 -14
- 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 +414 -41
- 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/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,
|
|
@@ -62,6 +67,23 @@ export function diffScores(
|
|
|
62
67
|
if (a.tempoBpm !== b.tempoBpm)
|
|
63
68
|
ops.push({ type: "setTempo", tempoBpm: b.tempoBpm });
|
|
64
69
|
if (a.key !== b.key) ops.push({ type: "setKey", key: b.key });
|
|
70
|
+
if (!deepEqual(a.time, b.time))
|
|
71
|
+
ops.push({ type: "setTime", time: b.time ?? null });
|
|
72
|
+
if (!deepEqual(a.tuning, b.tuning))
|
|
73
|
+
ops.push({ type: "setTuning", tuning: b.tuning ?? null });
|
|
74
|
+
if (!deepEqual(a.master, b.master))
|
|
75
|
+
ops.push({ type: "setMaster", master: b.master ?? null });
|
|
76
|
+
if (
|
|
77
|
+
!deepEqual(a.sections, b.sections) ||
|
|
78
|
+
!deepEqual(a.form, b.form) ||
|
|
79
|
+
a.loopSection !== b.loopSection
|
|
80
|
+
)
|
|
81
|
+
ops.push({
|
|
82
|
+
type: "setSections",
|
|
83
|
+
sections: b.sections,
|
|
84
|
+
form: b.form,
|
|
85
|
+
...(b.loopSection === undefined ? {} : { loopSection: b.loopSection }),
|
|
86
|
+
});
|
|
65
87
|
|
|
66
88
|
const aTracks = new Map(a.tracks.map((track) => [track.id, track]));
|
|
67
89
|
const bTracks = new Map(b.tracks.map((track) => [track.id, track]));
|
|
@@ -133,15 +155,8 @@ export function applyScoreOperations(
|
|
|
133
155
|
return operations.reduce(applyScoreOperation, score);
|
|
134
156
|
}
|
|
135
157
|
|
|
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> = {};
|
|
158
|
+
function notePatch(a: Note, b: Note): NotePatch | undefined {
|
|
159
|
+
const patch: Record<string, unknown> = {};
|
|
145
160
|
for (const key of [
|
|
146
161
|
"startTick",
|
|
147
162
|
"durationTicks",
|
|
@@ -149,7 +164,10 @@ function notePatch(
|
|
|
149
164
|
"velocity",
|
|
150
165
|
] as const)
|
|
151
166
|
if (a[key] !== b[key]) patch[key] = b[key];
|
|
152
|
-
|
|
167
|
+
for (const key of NOTE_EXPRESSION_FIELDS)
|
|
168
|
+
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
169
|
+
if ((a.cents ?? 0) !== (b.cents ?? 0)) patch.cents = b.cents ?? 0;
|
|
170
|
+
return Object.keys(patch).length > 0 ? (patch as NotePatch) : undefined;
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
function trackPatch(a: Track, b: Track): TrackPatch | undefined {
|
|
@@ -159,7 +177,13 @@ function trackPatch(a: Track, b: Track): TrackPatch | undefined {
|
|
|
159
177
|
if ((a.solo ?? false) !== (b.solo ?? false)) patch.solo = b.solo ?? false;
|
|
160
178
|
for (const key of EFFECTS)
|
|
161
179
|
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
162
|
-
for (const key of [
|
|
180
|
+
for (const key of [
|
|
181
|
+
"rhythm",
|
|
182
|
+
"kit",
|
|
183
|
+
"time",
|
|
184
|
+
"tuning",
|
|
185
|
+
...TRACK_PERFORMANCE_FIELDS,
|
|
186
|
+
] as const)
|
|
163
187
|
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
164
188
|
for (const key of LANES)
|
|
165
189
|
if (!deepEqual(a[key] ?? [], b[key] ?? [])) patch[key] = b[key] ?? [];
|
|
@@ -235,7 +259,8 @@ export function adoptNoteIds(
|
|
|
235
259
|
}
|
|
236
260
|
|
|
237
261
|
function exactKey(note: Note): string {
|
|
238
|
-
|
|
262
|
+
const cents = note.cents ? `|${note.cents}` : "";
|
|
263
|
+
return `${note.trackId}|${note.pitch}|${note.startTick}|${note.durationTicks}|${note.velocity}${cents}`;
|
|
239
264
|
}
|
|
240
265
|
|
|
241
266
|
function looseKey(note: Note): string {
|