@onjmin/dtm 2.1.9 → 2.1.10

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/dist/index.mjs CHANGED
@@ -8121,6 +8121,22 @@ var DAW_CSS = `
8121
8121
  opacity: 0.85;
8122
8122
  }
8123
8123
  .dtm-pill:not(.dtm-pill--active):active { transform: translate(1px,1px); box-shadow: none; }
8124
+ /* \u30DC\u30FC\u30AB\u30EB\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u308B\u30C8\u30E9\u30C3\u30AF\u306E\u30BF\u30D6\u306B\u3001\u3046\u3063\u3059\u3089\u58F0\u306E\u30A2\u30A4\u30B3\u30F3\u3092\u91CD\u306D\u308B\uFF08\u80CC\u666F\u8272\u30FB\u30B5\u30A4\u30BA\u306F\u5909\u3048\u306A\u3044\uFF09 */
8125
+ .dtm-pill--vocal::after {
8126
+ content: '';
8127
+ position: absolute;
8128
+ inset: 0;
8129
+ background-image: var(--dtm-pill-icon);
8130
+ background-size: contain;
8131
+ background-repeat: no-repeat;
8132
+ background-position: right center;
8133
+ opacity: 0.4;
8134
+ pointer-events: none;
8135
+ }
8136
+ .dtm-pill__label {
8137
+ position: relative;
8138
+ z-index: 1;
8139
+ }
8124
8140
  /* \u518D\u751F\u4E2D\u3001\u5B9F\u969B\u306B\u767A\u97F3\u3057\u305F\u77AC\u9593\u3060\u3051\u70B9\u706F\uFF08\u3069\u306E\u30BF\u30D6\u304C\u4ECA\u9CF4\u3063\u3066\u3044\u308B\u304B\u8996\u899A\u7684\u306B\u5206\u304B\u308B\u3088\u3046\u306B\uFF09 */
8125
8141
  .dtm-pill--sounding {
8126
8142
  filter: brightness(1.6);
@@ -13460,6 +13476,650 @@ var featureDistance = (a, b) => {
13460
13476
  return Math.sqrt(sum);
13461
13477
  };
13462
13478
 
13479
+ // src/compose-scales.ts
13480
+ var MAJOR_SCALE2 = [
13481
+ { semi: 0, fifth: 0 },
13482
+ // C
13483
+ { semi: 2, fifth: 2 },
13484
+ // D
13485
+ { semi: 4, fifth: 4 },
13486
+ // E
13487
+ { semi: 5, fifth: -1 },
13488
+ // F
13489
+ { semi: 7, fifth: 1 },
13490
+ // G
13491
+ { semi: 9, fifth: 3 },
13492
+ // A
13493
+ { semi: 11, fifth: 5 }
13494
+ // B
13495
+ ];
13496
+ var HARMONIC_MINOR_SCALE = [
13497
+ { semi: 0, fifth: 0 },
13498
+ // C
13499
+ { semi: 2, fifth: 2 },
13500
+ // D
13501
+ { semi: 4, fifth: 4 },
13502
+ // E
13503
+ { semi: 5, fifth: -1 },
13504
+ // F
13505
+ { semi: 8, fifth: 8 },
13506
+ // G#
13507
+ { semi: 9, fifth: 3 },
13508
+ // A
13509
+ { semi: 11, fifth: 5 }
13510
+ // B
13511
+ ];
13512
+ var HUNGARIAN_SCALE = [
13513
+ { semi: 0, fifth: 0 },
13514
+ // C
13515
+ { semi: 3, fifth: 9 },
13516
+ // D#
13517
+ { semi: 4, fifth: 4 },
13518
+ // E
13519
+ { semi: 5, fifth: -1 },
13520
+ // F
13521
+ { semi: 8, fifth: 8 },
13522
+ // G#
13523
+ { semi: 9, fifth: 3 },
13524
+ // A
13525
+ { semi: 11, fifth: 5 }
13526
+ // B
13527
+ ];
13528
+ var BLUES_SCALE = [
13529
+ { semi: 0, fifth: 0 },
13530
+ // C
13531
+ { semi: 3, fifth: -3 },
13532
+ // Eb
13533
+ { semi: 5, fifth: -1 },
13534
+ // F
13535
+ { semi: 6, fifth: -6 },
13536
+ // Gb(ブルーノート)
13537
+ { semi: 7, fifth: 1 },
13538
+ // G
13539
+ { semi: 10, fifth: -2 }
13540
+ // Bb
13541
+ ];
13542
+ var RYUKYU_CENTER = {
13543
+ tonic: "C",
13544
+ half: "G",
13545
+ deceptive: "Em",
13546
+ tonicPattern: /^C(?![#b]|m)/,
13547
+ a: [
13548
+ ["C", "F", "G", "C"],
13549
+ ["C", "C", "F", "G"],
13550
+ ["F", "G", "C", "C"],
13551
+ ["C", "G", "F", "G"],
13552
+ ["CM7", "F", "G", "C"],
13553
+ ["C", "F", "C", "G"],
13554
+ ["C", "Em", "F", "G"],
13555
+ ["F", "C", "G", "C"]
13556
+ ],
13557
+ b: [
13558
+ ["F", "G", "Em", "C"],
13559
+ ["F", "G", "C", "G"],
13560
+ ["G", "F", "C", "C"],
13561
+ ["FM7", "G", "Em", "F"],
13562
+ ["C", "G", "F", "C"],
13563
+ ["F", "Em", "F", "G"]
13564
+ ],
13565
+ c: [
13566
+ ["Em", "F", "G", "C"],
13567
+ ["F", "C", "G", "Em"],
13568
+ ["C", "Em", "F", "G"]
13569
+ ]
13570
+ };
13571
+ var MIYAKOBUSHI_CENTER = {
13572
+ tonic: "Em",
13573
+ half: "Am",
13574
+ deceptive: "F",
13575
+ tonicPattern: /^E(?:m|sus)/,
13576
+ a: [
13577
+ ["Em", "F", "Em", "Em"],
13578
+ ["Esus4", "F", "Esus4", "Em"],
13579
+ ["Em", "Am", "F", "Em"],
13580
+ ["Am", "Em", "F", "Em"],
13581
+ ["Em", "F", "Am", "Em"],
13582
+ ["Em", "Em", "F", "F"],
13583
+ ["Am", "F", "Em", "Em"],
13584
+ ["Em", "FM7", "Am", "Em"]
13585
+ ],
13586
+ b: [
13587
+ ["F", "Am", "Em", "Em"],
13588
+ ["Am", "F", "Em", "Em"],
13589
+ ["F", "Em", "Am", "Em"],
13590
+ ["FM7", "Am", "F", "Em"],
13591
+ ["Am", "Em", "F", "Am"],
13592
+ ["F", "Am", "F", "Em"]
13593
+ ],
13594
+ c: [
13595
+ ["Am", "Em", "F", "Am"],
13596
+ ["F", "Am", "Esus4", "Em"],
13597
+ ["Am", "F", "Em", "Em"]
13598
+ ]
13599
+ };
13600
+ var RITSU_CENTER = {
13601
+ tonic: "Dsus4",
13602
+ half: "G",
13603
+ deceptive: "Em",
13604
+ tonicPattern: /^Dsus/,
13605
+ a: [
13606
+ ["Dsus4", "G", "Dsus4", "Dsus4"],
13607
+ ["Dsus4", "Em", "G", "Dsus4"],
13608
+ ["G", "Dsus4", "Em", "Dsus4"],
13609
+ ["Dsus4", "G", "Em", "G"],
13610
+ ["Em", "G", "Dsus4", "Dsus4"],
13611
+ ["Dsus4", "Em7", "G", "Dsus4"],
13612
+ ["G", "Em", "Dsus4", "G"],
13613
+ ["Dsus4", "Am", "G", "Dsus4"]
13614
+ ],
13615
+ b: [
13616
+ ["G", "Em", "Dsus4", "Dsus4"],
13617
+ ["Em", "G", "Em", "Dsus4"],
13618
+ ["G", "Am", "Em", "Dsus4"],
13619
+ ["Em7", "G", "Dsus4", "Dsus4"],
13620
+ ["Am", "G", "Em", "Dsus4"],
13621
+ ["Dsus4", "Em", "G", "Em"]
13622
+ ],
13623
+ c: [
13624
+ ["Em", "Dsus4", "G", "Em"],
13625
+ ["G", "Em7", "Am", "G"],
13626
+ ["Am", "G", "Em", "Dsus4"]
13627
+ ]
13628
+ };
13629
+ var HARMONIC_MINOR_CENTER = {
13630
+ tonic: "Am",
13631
+ half: "E7",
13632
+ deceptive: "F",
13633
+ tonicPattern: /^Am/,
13634
+ a: [
13635
+ ["Am", "Dm", "E7", "Am"],
13636
+ // i-iv-V7-i。和声的短音階の基本形
13637
+ ["Am", "F", "E7", "Am"],
13638
+ ["Am", "E7", "Am", "Am"],
13639
+ ["Dm", "E7", "Am", "Am"],
13640
+ ["Am", "AmM7", "Dm", "E7"],
13641
+ // 主和音に導音を重ねたクリシェ
13642
+ ["Am", "F", "Dm", "E7"],
13643
+ ["F", "E7", "Am", "Am"],
13644
+ ["Am", "Bm7-5", "E7", "Am"]
13645
+ ],
13646
+ b: [
13647
+ ["Dm", "E7", "Am", "Am"],
13648
+ ["F", "E7", "Am", "E7"],
13649
+ ["Dm7", "G#dim", "Am", "E7"],
13650
+ ["F", "Dm", "E7", "Am"],
13651
+ ["Am", "Dm", "Bm7-5", "E7"],
13652
+ ["FM7", "E7", "Am", "Am"]
13653
+ ],
13654
+ c: [
13655
+ ["Dm", "Am", "Bm7-5", "E7"],
13656
+ ["F", "C+", "Dm", "E7"],
13657
+ ["Am", "F", "Dm", "E7"]
13658
+ ]
13659
+ };
13660
+ var HIJAZ_CENTER = {
13661
+ tonic: "E",
13662
+ half: "F",
13663
+ deceptive: "Am",
13664
+ tonicPattern: /^E(?![#b]|m)/,
13665
+ a: [
13666
+ ["E", "F", "E", "E"],
13667
+ // I-♭II。ヒジャーズの顔
13668
+ ["E7", "F", "E", "E"],
13669
+ ["Am", "F", "E", "E"],
13670
+ ["E", "F", "Dm", "E"],
13671
+ ["F", "E", "F", "E"],
13672
+ ["Dm", "E", "F", "E"],
13673
+ ["E7", "Am", "F", "E"],
13674
+ ["E", "Dm", "F", "E"]
13675
+ ],
13676
+ b: [
13677
+ ["F", "E", "Am", "E"],
13678
+ ["Dm", "C+", "F", "E"],
13679
+ ["Am", "Dm", "F", "E"],
13680
+ ["F", "Dm", "E", "E"],
13681
+ ["E7", "F", "Dm", "E"],
13682
+ ["Bm7-5", "E7", "Am", "E"]
13683
+ ],
13684
+ c: [
13685
+ ["Am", "Dm", "F", "E"],
13686
+ ["Dm", "Am", "F", "E7"],
13687
+ ["F", "C+", "Dm", "E"]
13688
+ ]
13689
+ };
13690
+ var HUNGARIAN_CENTER = {
13691
+ tonic: "Am",
13692
+ half: "E",
13693
+ deceptive: "F",
13694
+ tonicPattern: /^Am/,
13695
+ a: [
13696
+ ["Am", "E", "Am", "Am"],
13697
+ ["Am", "F", "E", "Am"],
13698
+ ["Am", "AmM7", "F", "E"],
13699
+ ["F", "E", "Am", "Am"],
13700
+ ["Am", "Fm", "E", "Am"],
13701
+ // ♭VIm。増2度を和音の側でも鳴らす
13702
+ ["Am", "E", "F", "E"],
13703
+ ["FM7", "E", "Am", "Am"],
13704
+ ["Am", "C+", "F", "E"]
13705
+ ],
13706
+ b: [
13707
+ ["F", "E", "Am", "E"],
13708
+ ["Fm", "E", "Am", "Am"],
13709
+ ["Am", "F", "C+", "E"],
13710
+ ["FM7", "Am", "F", "E"],
13711
+ ["E", "F", "E", "Am"],
13712
+ ["Am", "AmM7", "Fm", "E"]
13713
+ ],
13714
+ c: [
13715
+ ["F", "Am", "Fm", "E"],
13716
+ ["C+", "F", "Am", "E"],
13717
+ ["Am", "Fm", "F", "E"]
13718
+ ]
13719
+ };
13720
+ var BLUES_CENTER = {
13721
+ tonic: "C7",
13722
+ half: "G7",
13723
+ deceptive: "F7",
13724
+ tonicPattern: /^C7/,
13725
+ a: [
13726
+ ["C7", "C7", "C7", "C7"],
13727
+ // 12小節ブルースの1〜4小節
13728
+ ["C7", "F7", "C7", "C7"],
13729
+ ["C7", "C7", "F7", "F7"],
13730
+ ["F7", "F7", "C7", "C7"],
13731
+ // 5〜8小節
13732
+ ["C7", "F7", "C7", "G7"],
13733
+ ["C7", "C7", "G7", "F7"],
13734
+ ["C7", "F7", "G7", "C7"],
13735
+ ["F7", "C7", "G7", "C7"]
13736
+ ],
13737
+ b: [
13738
+ ["F7", "F7", "C7", "C7"],
13739
+ ["G7", "F7", "C7", "C7"],
13740
+ // 9〜12小節(ターンアラウンド)
13741
+ ["F7", "G7", "C7", "C7"],
13742
+ ["C7", "F7", "G7", "C7"],
13743
+ ["F7", "C7", "G7", "F7"],
13744
+ ["G7", "G7", "F7", "C7"]
13745
+ ],
13746
+ c: [
13747
+ ["G7", "F7", "C7", "G7"],
13748
+ ["F7", "F7", "G7", "G7"],
13749
+ ["C7", "C7", "F7", "G7"]
13750
+ ]
13751
+ };
13752
+ var COMPOSE_SCALES = {
13753
+ yo: {
13754
+ id: "yo",
13755
+ label: "\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
13756
+ tonic: 0,
13757
+ core: [0, 1, 2, 4, 5],
13758
+ // ド レ ミ ソ ラ
13759
+ minorish: false,
13760
+ strict: false,
13761
+ description: "J-POP\u306E\u6A19\u6E96\u3002\u660E\u308B\u304F\u7D20\u76F4\u3067\u6B4C\u3044\u3084\u3059\u3044\u3002\u5F93\u6765\u306E\u9577\u8ABF\u3068\u540C\u3058"
13762
+ },
13763
+ minyo: {
13764
+ id: "minyo",
13765
+ label: "\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
13766
+ tonic: 5,
13767
+ core: [5, 0, 1, 2, 4],
13768
+ // ラ ド レ ミ ソ
13769
+ minorish: true,
13770
+ strict: false,
13771
+ description: "\u308F\u3089\u3079\u6B4C\u30FB\u6C11\u8B21\u306E\u97F3\u968E\u3002\u7FF3\u308A\u304C\u3042\u308B\u304C\u6697\u3059\u304E\u306A\u3044\u3002\u5F93\u6765\u306E\u77ED\u8ABF\u3068\u540C\u3058"
13772
+ },
13773
+ ritsu: {
13774
+ id: "ritsu",
13775
+ label: "\u5F8B\u97F3\u968E",
13776
+ tonic: 1,
13777
+ core: [1, 2, 4, 5, 6],
13778
+ // レ ミ ソ ラ シ
13779
+ minorish: true,
13780
+ strict: true,
13781
+ center: RITSU_CENTER,
13782
+ description: "\u96C5\u697D\u30FB\u58F0\u660E\u306E\u97F3\u968E\u3002\u534A\u97F3\u3092\u542B\u307E\u305A\u3001\u5E73\u3089\u3067\u8358\u91CD\u306B\u6D41\u308C\u308B"
13783
+ },
13784
+ miyakobushi: {
13785
+ id: "miyakobushi",
13786
+ label: "\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09",
13787
+ tonic: 2,
13788
+ core: [2, 3, 5, 6, 0],
13789
+ // ミ ファ ラ シ ド
13790
+ minorish: true,
13791
+ strict: true,
13792
+ center: MIYAKOBUSHI_CENTER,
13793
+ description: "\u300E\u3055\u304F\u3089\u3055\u304F\u3089\u300F\u306E\u97F3\u968E\u3002\u4E3B\u97F3\u306E\u3059\u3050\u4E0A\u304C\u534A\u97F3\u3067\u3001\u7FF3\u308A\u304C\u6FC3\u3044"
13794
+ },
13795
+ ryukyu: {
13796
+ id: "ryukyu",
13797
+ label: "\u7409\u7403\u97F3\u968E",
13798
+ tonic: 0,
13799
+ core: [0, 2, 3, 4, 6],
13800
+ // ド ミ ファ ソ シ
13801
+ minorish: false,
13802
+ strict: true,
13803
+ center: RYUKYU_CENTER,
13804
+ description: "\u6C96\u7E04\u97F3\u968E\u3002\u30EC\u3068\u30E9\u3092\u629C\u304D\u3001\u30D5\u30A1\u3068\u30B7\u3092\u67F1\u306B\u3059\u308B\u3002\u660E\u308B\u304F\u8DF3\u306D\u308B"
13805
+ },
13806
+ dorian: {
13807
+ id: "dorian",
13808
+ label: "\u30C9\u30EA\u30A2\u30F3",
13809
+ tonic: 1,
13810
+ core: [1, 3, 4, 5, 0],
13811
+ // レ ファ ソ ラ ド
13812
+ minorish: true,
13813
+ strict: false,
13814
+ description: "\u77ED\u8ABF\u3060\u304C6\u5EA6\u304C\u660E\u308B\u3044\u3002\u30B1\u30EB\u30C8\u30FB\u30ED\u30C3\u30AF\u30FB\u30B7\u30C6\u30A3\u30DD\u30C3\u30D7"
13815
+ },
13816
+ phrygian: {
13817
+ id: "phrygian",
13818
+ label: "\u30D5\u30EA\u30B8\u30A2\u30F3",
13819
+ tonic: 2,
13820
+ core: [2, 3, 5, 6, 1],
13821
+ // ミ ファ ラ シ レ
13822
+ minorish: true,
13823
+ strict: false,
13824
+ description: "\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3002\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\uFF0F\u30E1\u30BF\u30EB\u306E\u7DCA\u8FEB\u3057\u305F\u97FF\u304D"
13825
+ },
13826
+ lydian: {
13827
+ id: "lydian",
13828
+ label: "\u30EA\u30C7\u30A3\u30A2\u30F3",
13829
+ tonic: 3,
13830
+ core: [3, 4, 6, 0, 2],
13831
+ // ファ ソ シ ド ミ
13832
+ minorish: false,
13833
+ strict: false,
13834
+ description: "4\u5EA6\u304C\u9AD8\u304F\u3001\u6D6E\u904A\u3057\u3066\u5E83\u304C\u308B\u3002\u6620\u753B\u97F3\u697D\u30FB\u30B2\u30FC\u30E0\u306E\u7A7A\u306E\u8272"
13835
+ },
13836
+ mixolydian: {
13837
+ id: "mixolydian",
13838
+ label: "\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3",
13839
+ tonic: 4,
13840
+ core: [4, 5, 0, 1, 3],
13841
+ // ソ ラ ド レ ファ
13842
+ minorish: false,
13843
+ strict: false,
13844
+ description: "\u9577\u8ABF\u3060\u304C7\u5EA6\u304C\u4F4E\u3044\u3002\u30D6\u30EB\u30FC\u30B9\u30ED\u30C3\u30AF\u30FB\u6C11\u65CF\u97F3\u697D\u306E\u571F\u304F\u3055\u3055"
13845
+ },
13846
+ harmonic_minor: {
13847
+ id: "harmonic_minor",
13848
+ label: "\u548C\u58F0\u7684\u77ED\u97F3\u968E",
13849
+ tonic: 5,
13850
+ parent: HARMONIC_MINOR_SCALE,
13851
+ core: [5, 0, 1, 2, 4],
13852
+ // ラ ド レ ミ ソ♯
13853
+ minorish: true,
13854
+ strict: false,
13855
+ center: HARMONIC_MINOR_CENTER,
13856
+ description: "\u5C0E\u97F3\u30BD\u266F\u3092\u6301\u3064\u77ED\u8ABF\u3002\u58972\u5EA6\u304C\u6CE3\u304D\u3092\u4F5C\u308B\u3002\u30AF\u30E9\u30B7\u30C3\u30AF\u30FBV\u7CFB\u30FB\u5287\u4F34"
13857
+ },
13858
+ hijaz: {
13859
+ id: "hijaz",
13860
+ label: "\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09",
13861
+ tonic: 2,
13862
+ parent: HARMONIC_MINOR_SCALE,
13863
+ core: [2, 3, 4, 5, 6],
13864
+ // ミ ファ ソ♯ ラ シ
13865
+ minorish: false,
13866
+ strict: false,
13867
+ center: HIJAZ_CENTER,
13868
+ description: "\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3001\u4E3B\u548C\u97F3\u306F\u9577\u4E09\u548C\u97F3\u3002\u4E2D\u6771\u30FB\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\u30FB\u30E1\u30BF\u30EB"
13869
+ },
13870
+ hungarian: {
13871
+ id: "hungarian",
13872
+ label: "\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09",
13873
+ tonic: 5,
13874
+ parent: HUNGARIAN_SCALE,
13875
+ core: [5, 0, 1, 2, 4],
13876
+ // ラ ド レ♯ ミ ソ♯
13877
+ minorish: true,
13878
+ strict: false,
13879
+ center: HUNGARIAN_CENTER,
13880
+ description: "\u58972\u5EA6\u304C2\u304B\u6240\u3002\u97F3\u968E\u306E\u4E2D\u3067\u3044\u3061\u3070\u3093\u8DF3\u306D\u305F\u3001\u7570\u56FD\u3081\u3044\u305F\u97FF\u304D"
13881
+ },
13882
+ blues: {
13883
+ id: "blues",
13884
+ label: "\u30D6\u30EB\u30FC\u30B9\u97F3\u968E",
13885
+ tonic: 0,
13886
+ parent: BLUES_SCALE,
13887
+ core: [0, 1, 2, 4, 5],
13888
+ // ド ミ♭ ファ ソ シ♭
13889
+ minorish: true,
13890
+ strict: true,
13891
+ center: BLUES_CENTER,
13892
+ description: "\u30D6\u30EB\u30FC\u30CE\u30FC\u30C8\u5165\u308A\u306E6\u97F3\u97F3\u968E\u3002\u77ED3\u5EA6\u3067\u6B4C\u3044\u3001\u4F34\u594F\u306F\u95773\u5EA6\u3067\u9CF4\u308B"
13893
+ }
13894
+ };
13895
+ var COMPOSE_SCALE_IDS = Object.keys(
13896
+ COMPOSE_SCALES
13897
+ );
13898
+ var TONIC_CENTERS = {
13899
+ // --- レ(律・ドリアン) ---
13900
+ 1: {
13901
+ tonic: "Dm",
13902
+ half: "G",
13903
+ deceptive: "F",
13904
+ tonicPattern: /^Dm/,
13905
+ a: [
13906
+ ["Dm", "G", "Dm", "Dm"],
13907
+ // i-IV の往復。ドリアンの顔
13908
+ ["Dm", "Am", "G", "Dm"],
13909
+ ["Dm", "C", "G", "Dm"],
13910
+ ["Dm7", "G", "Dm7", "C"],
13911
+ ["Dm", "F", "C", "G"],
13912
+ ["Dm", "Em", "F", "G"],
13913
+ ["Dm", "G", "F", "C"],
13914
+ ["Dm7", "Em7", "FM7", "G"]
13915
+ ],
13916
+ b: [
13917
+ ["F", "G", "Am", "Dm"],
13918
+ ["C", "G", "Dm", "Dm"],
13919
+ ["G", "F", "C", "Dm"],
13920
+ ["Am", "G", "F", "Dm"],
13921
+ ["FM7", "G", "Em7", "Dm7"],
13922
+ ["Dm", "G", "C", "Am"]
13923
+ ],
13924
+ c: [
13925
+ ["Am", "Dm", "G", "C"],
13926
+ ["F", "Em", "Dm", "G"],
13927
+ ["Dm", "Am", "Em", "G"]
13928
+ ]
13929
+ },
13930
+ // --- ミ(都節・フリジアン) ---
13931
+ 2: {
13932
+ tonic: "Em",
13933
+ half: "Am",
13934
+ deceptive: "C",
13935
+ tonicPattern: /^Em/,
13936
+ a: [
13937
+ ["Em", "F", "Em", "Em"],
13938
+ // i-♭II。フリジアン/都節の顔
13939
+ ["Em", "Am", "F", "Em"],
13940
+ ["Am", "Em", "F", "Em"],
13941
+ ["Em", "F", "G", "Em"],
13942
+ ["Em", "Em", "F", "F"],
13943
+ ["Em", "C", "F", "Em"],
13944
+ ["Am", "F", "Em", "Em"],
13945
+ ["Em7", "FM7", "Em7", "Am7"]
13946
+ ],
13947
+ b: [
13948
+ ["F", "G", "Am", "Em"],
13949
+ ["Am", "G", "F", "Em"],
13950
+ ["F", "Em", "Am", "Em"],
13951
+ ["C", "F", "Em", "Em"],
13952
+ ["FM7", "G", "Em7", "Am"],
13953
+ ["Am", "Em", "F", "G"]
13954
+ ],
13955
+ c: [
13956
+ ["Am", "Em", "F", "C"],
13957
+ ["C", "G", "Am", "Em"],
13958
+ ["F", "C", "Am", "Em"]
13959
+ ]
13960
+ },
13961
+ // --- ファ(リディアン) ---
13962
+ 3: {
13963
+ tonic: "FM7",
13964
+ half: "C",
13965
+ deceptive: "Dm",
13966
+ tonicPattern: /^F(?![#b]|m)/,
13967
+ a: [
13968
+ ["FM7", "G", "FM7", "FM7"],
13969
+ // I-II。リディアンの顔(♯4 が G の3度に居る)
13970
+ ["FM7", "G", "Em", "Am"],
13971
+ ["F", "G", "C", "F"],
13972
+ ["FM7", "G", "Am", "F"],
13973
+ ["F", "C", "G", "F"],
13974
+ ["FM7", "Em7", "Dm7", "G"],
13975
+ ["F", "G", "F", "C"],
13976
+ ["FM7", "G", "Dm7", "F"]
13977
+ ],
13978
+ b: [
13979
+ ["G", "F", "C", "F"],
13980
+ ["Am", "G", "FM7", "FM7"],
13981
+ ["C", "G", "Am", "F"],
13982
+ ["G", "Em", "Am", "F"],
13983
+ ["Dm7", "G", "FM7", "FM7"],
13984
+ ["FM7", "G", "Em7", "F"]
13985
+ ],
13986
+ c: [
13987
+ ["Dm", "Am", "F", "G"],
13988
+ ["Am", "Em", "F", "G"],
13989
+ ["C", "Am", "Dm", "F"]
13990
+ ]
13991
+ },
13992
+ // --- ソ(ミクソリディアン) ---
13993
+ 4: {
13994
+ tonic: "G",
13995
+ half: "Dm",
13996
+ deceptive: "Em",
13997
+ tonicPattern: /^G(?![#b]|m)/,
13998
+ a: [
13999
+ ["G", "F", "C", "G"],
14000
+ // I-♭VII-IV。ミクソリディアンの顔
14001
+ ["G", "C", "F", "G"],
14002
+ ["G", "F", "G", "G"],
14003
+ ["C", "G", "F", "G"],
14004
+ ["G", "Dm", "F", "G"],
14005
+ ["G", "Am", "F", "G"],
14006
+ ["G", "F", "Dm", "C"],
14007
+ ["G", "C", "G", "F"]
14008
+ ],
14009
+ b: [
14010
+ ["F", "C", "G", "G"],
14011
+ ["Am", "F", "C", "G"],
14012
+ ["C", "Dm", "F", "G"],
14013
+ ["Em", "F", "C", "G"],
14014
+ ["F", "G", "Am", "G"],
14015
+ ["Dm7", "F", "C", "G"]
14016
+ ],
14017
+ c: [
14018
+ ["Am", "Em", "F", "G"],
14019
+ ["C", "Am", "Dm", "G"],
14020
+ ["Em", "Am", "F", "C"]
14021
+ ]
14022
+ }
14023
+ };
14024
+ var scaleDegrees = (scale) => scale.parent ?? MAJOR_SCALE2;
14025
+ var scaleSize = (scale) => scaleDegrees(scale).length;
14026
+ var degreeToPitch = (scale, degree) => {
14027
+ const list = scaleDegrees(scale);
14028
+ const size = list.length;
14029
+ const index = (degree % size + size) % size;
14030
+ const octave = Math.floor(degree / size);
14031
+ const d = list[index];
14032
+ return { semi: d.semi + octave * 12, fifth: d.fifth };
14033
+ };
14034
+ var semitoneToDegree = (scale, semi) => {
14035
+ const list = scaleDegrees(scale);
14036
+ const octave = Math.floor(semi / 12);
14037
+ const within = semi - octave * 12;
14038
+ let best = 0;
14039
+ let bestDist = Number.POSITIVE_INFINITY;
14040
+ for (let i2 = 0; i2 < list.length; i2++) {
14041
+ const dist = Math.abs(list[i2].semi - within);
14042
+ if (dist < bestDist) {
14043
+ bestDist = dist;
14044
+ best = i2;
14045
+ }
14046
+ }
14047
+ return octave * list.length + best;
14048
+ };
14049
+ var walk = (scale, semi, delta) => degreeToPitch(scale, semitoneToDegree(scale, semi) + delta).semi;
14050
+ var scaleFifth = (scale, semi) => degreeToPitch(scale, semitoneToDegree(scale, semi)).fifth;
14051
+ var SCALE_PCS = /* @__PURE__ */ new Map();
14052
+ var scalePcs = (scale) => {
14053
+ const hit = SCALE_PCS.get(scale.id);
14054
+ if (hit) return hit;
14055
+ const set = new Set(
14056
+ scaleDegrees(scale).map((d) => (d.semi % 12 + 12) % 12)
14057
+ );
14058
+ SCALE_PCS.set(scale.id, set);
14059
+ return set;
14060
+ };
14061
+ var CORE_SORTED = /* @__PURE__ */ new Map();
14062
+ var sortedCore = (scale) => {
14063
+ const hit = CORE_SORTED.get(scale.id);
14064
+ if (hit) return hit;
14065
+ const sorted = [...scale.core].sort((a, b) => a - b);
14066
+ CORE_SORTED.set(scale.id, sorted);
14067
+ return sorted;
14068
+ };
14069
+ var CORE_PCS = /* @__PURE__ */ new Map();
14070
+ var corePcs = (scale) => {
14071
+ const hit = CORE_PCS.get(scale.id);
14072
+ if (hit) return hit;
14073
+ const list = scaleDegrees(scale);
14074
+ const set = new Set(scale.core.map((d) => (list[d].semi % 12 + 12) % 12));
14075
+ CORE_PCS.set(scale.id, set);
14076
+ return set;
14077
+ };
14078
+ var isCoreDegree = (scale, degree) => {
14079
+ const size = scaleSize(scale);
14080
+ return sortedCore(scale).includes((degree % size + size) % size);
14081
+ };
14082
+ var coreToDegree = (scale, step) => {
14083
+ const sorted = sortedCore(scale);
14084
+ const n = sorted.length;
14085
+ const index = (step % n + n) % n;
14086
+ const octave = Math.floor(step / n);
14087
+ return sorted[index] + octave * scaleSize(scale);
14088
+ };
14089
+ var degreeToCore = (scale, degree) => {
14090
+ const sorted = sortedCore(scale);
14091
+ const size = scaleSize(scale);
14092
+ const index = (degree % size + size) % size;
14093
+ const octave = Math.floor(degree / size);
14094
+ let best = 0;
14095
+ let bestDist = Number.POSITIVE_INFINITY;
14096
+ for (let i2 = 0; i2 < sorted.length; i2++) {
14097
+ const dist = Math.abs(sorted[i2] - index);
14098
+ if (dist < bestDist) {
14099
+ bestDist = dist;
14100
+ best = i2;
14101
+ }
14102
+ }
14103
+ return octave * sorted.length + best;
14104
+ };
14105
+ var isOutsideCore = (scale, semi) => scale.strict ? !corePcs(scale).has((semi % 12 + 12) % 12) : !isCoreDegree(scale, semitoneToDegree(scale, semi));
14106
+ var resolveComposeScale = (choice, minorKey, rnd = Math.random) => {
14107
+ const c = (choice ?? "").trim() || "auto";
14108
+ const hit = COMPOSE_SCALES[c];
14109
+ if (hit) return hit;
14110
+ if (c === "any")
14111
+ return COMPOSE_SCALES[COMPOSE_SCALE_IDS[Math.floor(rnd() * COMPOSE_SCALE_IDS.length)] ?? "yo"];
14112
+ return COMPOSE_SCALES[minorKey ? "minyo" : "yo"];
14113
+ };
14114
+ var getComposeScaleDescription = (choice) => {
14115
+ const hit = COMPOSE_SCALES[choice];
14116
+ if (hit) return hit.description;
14117
+ if (choice === "any")
14118
+ return `${COMPOSE_SCALE_IDS.length}\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059`;
14119
+ return "\u30D9\u30FC\u30B9\u8ABF\u306E\u9577\u77ED\u306B\u5408\u308F\u305B\u3066\u3001\u967D\u97F3\u968E\uFF08\u9577\u8ABF\uFF09\u304B\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\uFF09\u3092\u4F7F\u3044\u307E\u3059";
14120
+ };
14121
+ var resolveCenter = (scale) => scale.center ?? TONIC_CENTERS[scale.tonic] ?? null;
14122
+
13463
14123
  // src/compose-sections.ts
13464
14124
  var SECTION_LABELS = {
13465
14125
  intro: "\u30A4\u30F3\u30C8\u30ED",
@@ -13884,65 +14544,6 @@ var BPM_CHOICES = [
13884
14544
  180,
13885
14545
  185
13886
14546
  ];
13887
- var MAJOR_SCALE2 = [
13888
- { semi: 0, fifth: 0 },
13889
- // C
13890
- { semi: 2, fifth: 2 },
13891
- // D
13892
- { semi: 4, fifth: 4 },
13893
- // E
13894
- { semi: 5, fifth: -1 },
13895
- // F
13896
- { semi: 7, fifth: 1 },
13897
- // G
13898
- { semi: 9, fifth: 3 },
13899
- // A
13900
- { semi: 11, fifth: 5 }
13901
- // B
13902
- ];
13903
- var degreeToPitch = (degree) => {
13904
- const index = (degree % 7 + 7) % 7;
13905
- const octave = Math.floor(degree / 7);
13906
- const d = MAJOR_SCALE2[index];
13907
- return { semi: d.semi + octave * 12, fifth: d.fifth };
13908
- };
13909
- var semitoneToDegree = (semi) => {
13910
- const octave = Math.floor(semi / 12);
13911
- const within = semi - octave * 12;
13912
- let best = 0;
13913
- let bestDist = Number.POSITIVE_INFINITY;
13914
- for (let i2 = 0; i2 < MAJOR_SCALE2.length; i2++) {
13915
- const dist = Math.abs(MAJOR_SCALE2[i2].semi - within);
13916
- if (dist < bestDist) {
13917
- bestDist = dist;
13918
- best = i2;
13919
- }
13920
- }
13921
- return octave * 7 + best;
13922
- };
13923
- var walk = (semi, delta) => degreeToPitch(semitoneToDegree(semi) + delta).semi;
13924
- var NON_PENTATONIC_DEGREES = /* @__PURE__ */ new Set([3, 6]);
13925
- var isNonPentatonic = (semi) => NON_PENTATONIC_DEGREES.has((semitoneToDegree(semi) % 7 + 7) % 7);
13926
- var PENTATONIC_DEGREES = [0, 1, 2, 4, 5];
13927
- var pentaToDegree = (penta) => {
13928
- const index = (penta % 5 + 5) % 5;
13929
- const octave = Math.floor(penta / 5);
13930
- return PENTATONIC_DEGREES[index] + octave * 7;
13931
- };
13932
- var degreeToPenta = (degree) => {
13933
- const index = (degree % 7 + 7) % 7;
13934
- const octave = Math.floor(degree / 7);
13935
- let best = 0;
13936
- let bestDist = Number.POSITIVE_INFINITY;
13937
- for (let i2 = 0; i2 < PENTATONIC_DEGREES.length; i2++) {
13938
- const dist = Math.abs(PENTATONIC_DEGREES[i2] - index);
13939
- if (dist < bestDist) {
13940
- bestDist = dist;
13941
- best = i2;
13942
- }
13943
- }
13944
- return octave * 5 + best;
13945
- };
13946
14547
  var SECTION_A_PROGRESSIONS = [
13947
14548
  ["C", "G", "Am", "Em7"],
13948
14549
  // カノン進行の前半
@@ -14061,6 +14662,22 @@ var SECTION_DECEPTIVE_DERIVATIONS = [
14061
14662
  (a, t) => [a[0], "Dm7", "G7", t === "Am" ? "FM7" : "Am7"],
14062
14663
  (a, t) => [a[0], "F", "G7", t === "Am" ? "F" : "Am7"]
14063
14664
  ];
14665
+ var MODAL_FULL_DERIVATIONS = [
14666
+ (a, c) => [a[0], a[1], c.half, c.tonic],
14667
+ (a, c) => [a[0], c.half, a[2], c.tonic],
14668
+ (a, c) => [a[0], a[1], a[2], c.tonic],
14669
+ (a, c) => [c.tonic, c.half, a[2], c.tonic]
14670
+ ];
14671
+ var MODAL_HALF_DERIVATIONS = [
14672
+ (a, c) => [a[0], a[1], a[2], c.half],
14673
+ (a, c) => [a[0], a[1], c.tonic, c.half],
14674
+ (a, c) => [a[0], c.tonic, a[2], c.half]
14675
+ ];
14676
+ var MODAL_DECEPTIVE_DERIVATIONS = [
14677
+ (a, c) => [a[0], a[1], c.half, c.deceptive],
14678
+ (a, c) => [a[0], c.half, a[2], c.deceptive],
14679
+ (a, c) => [a[0], a[1], a[2], c.deceptive]
14680
+ ];
14064
14681
  var MODAL_BORROW = {
14065
14682
  F: "Fm",
14066
14683
  // IV → IVm(サブドミナントマイナー。いちばん定番)
@@ -14651,7 +15268,7 @@ var nearestChordTone = (targetSemi, tones, minWeight, preferColor = false) => {
14651
15268
  var MELODY_LOW = 60;
14652
15269
  var MELODY_HIGH = 81;
14653
15270
  var MELODY_CENTER = (MELODY_LOW + MELODY_HIGH) / 2;
14654
- var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) => {
15271
+ var harmonyPitch = (scale, melodySemi, tones, prevHarmony, offset, parallel, against) => {
14655
15272
  const lo = Math.max(-12, Math.min(offset, 0) - 7);
14656
15273
  const hi = Math.min(12, Math.max(offset, 0) + 7);
14657
15274
  let best = null;
@@ -14669,7 +15286,8 @@ var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) =
14669
15286
  const g = Math.abs(semi - against) % 12;
14670
15287
  clash = g === 3 || g === 4 || g === 8 || g === 9 || g === 7 ? 0 : g === 0 ? 0.5 : 1.2;
14671
15288
  }
14672
- const cost = stay + clash + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
15289
+ const offCore = scale.strict && isOutsideCore(scale, semi) ? 1.5 : 0;
15290
+ const cost = stay + clash + offCore + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
14673
15291
  if (cost < bestCost) {
14674
15292
  bestCost = cost;
14675
15293
  best = { semi, fifth: tone.fifth };
@@ -14714,40 +15332,43 @@ var leapTarget = (from, tones, rnd) => {
14714
15332
  ];
14715
15333
  return pick(weighted.length > 0 ? weighted : candidates, rnd);
14716
15334
  };
14717
- var landOn = (degrees, scaleIndex) => {
15335
+ var landOn = (scale, degrees, scaleIndex) => {
14718
15336
  if (degrees.length === 0) return;
15337
+ const size = scaleSize(scale);
14719
15338
  const last = degrees[degrees.length - 1];
14720
- const index = (last % 7 + 7) % 7;
15339
+ const index = (last % size + size) % size;
14721
15340
  let delta = scaleIndex - index;
14722
- if (delta > 3) delta -= 7;
14723
- if (delta < -3) delta += 7;
15341
+ if (delta > size / 2) delta -= size;
15342
+ if (delta < -size / 2) delta += size;
14724
15343
  degrees[degrees.length - 1] = last + delta;
14725
15344
  };
14726
- var landPitch = (pitches, scaleIndex) => {
15345
+ var landPitch = (scale, pitches, scaleIndex) => {
14727
15346
  if (pitches.length === 0) return;
15347
+ const size = scaleSize(scale);
14728
15348
  const last = pitches[pitches.length - 1];
14729
- const degree = semitoneToDegree(last);
14730
- const index = (degree % 7 + 7) % 7;
15349
+ const degree = semitoneToDegree(scale, last);
15350
+ const index = (degree % size + size) % size;
14731
15351
  let delta = scaleIndex - index;
14732
- if (delta > 3) delta -= 7;
14733
- if (delta < -3) delta += 7;
15352
+ if (delta > size / 2) delta -= size;
15353
+ if (delta < -size / 2) delta += size;
14734
15354
  pitches[pitches.length - 1] = clampSemi(
14735
- degreeToPitch(degree + delta).semi,
15355
+ degreeToPitch(scale, degree + delta).semi,
14736
15356
  MELODY_LOW,
14737
15357
  MELODY_HIGH
14738
15358
  );
14739
15359
  };
14740
- var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
15360
+ var barDegrees = (role, slots, tones, style, scale, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
14741
15361
  const noteCount = slots.length;
14742
15362
  const out = [];
14743
15363
  if (role === "motif" || role === "sequence" || role === "climax" || role === "answer") {
14744
15364
  const shift = (role === "sequence" ? pick([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
14745
15365
  if (style.pentatonicMotif) {
14746
- const startPenta = degreeToPenta(startDegree);
15366
+ const startCore = degreeToCore(scale, startDegree);
14747
15367
  for (let i2 = 0; i2 < noteCount; i2++)
14748
15368
  out.push(
14749
- pentaToDegree(
14750
- startPenta + shift + motifContour[(contourOffset + i2) % motifContour.length]
15369
+ coreToDegree(
15370
+ scale,
15371
+ startCore + shift + motifContour[(contourOffset + i2) % motifContour.length]
14751
15372
  )
14752
15373
  );
14753
15374
  } else {
@@ -14767,9 +15388,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14767
15388
  for (let i2 = 0; i2 < noteCount; i2++)
14768
15389
  out.push(startDegree + dir2 * (i2 < peak ? i2 : peak * 2 - i2 - 1));
14769
15390
  } else if (style.runShape === "broken") {
14770
- const arp = tones.map((t) => semitoneToDegree(clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
15391
+ const arp = tones.map((t) => semitoneToDegree(scale, clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
14771
15392
  for (let i2 = 0; i2 < noteCount; i2++) {
14772
- const oct = Math.floor(i2 / arp.length) * 7;
15393
+ const oct = Math.floor(i2 / arp.length) * scaleSize(scale);
14773
15394
  const idx = dir2 > 0 ? i2 % arp.length : arp.length - 1 - i2 % arp.length;
14774
15395
  out.push(arp[idx] + dir2 * oct);
14775
15396
  }
@@ -14792,7 +15413,10 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14792
15413
  return out;
14793
15414
  }
14794
15415
  if (role === "cadence") {
14795
- const tonic = semitoneToDegree(clampSemi(72, MELODY_LOW, MELODY_HIGH));
15416
+ const tonic = semitoneToDegree(
15417
+ scale,
15418
+ clampSemi(72, MELODY_LOW, MELODY_HIGH)
15419
+ );
14796
15420
  for (let i2 = 0; i2 < noteCount; i2++) {
14797
15421
  if (style.cadenceShape === "descend") {
14798
15422
  out.push(tonic + noteCount - 1 - i2);
@@ -14800,7 +15424,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14800
15424
  const shape = [4, 2, 0];
14801
15425
  out.push(tonic + shape[Math.min(i2, shape.length - 1)]);
14802
15426
  } else if (style.cadenceShape === "leap-up") {
14803
- out.push(i2 === noteCount - 1 ? tonic : tonic - 7 + Math.min(i2, 4));
15427
+ out.push(
15428
+ i2 === noteCount - 1 ? tonic : tonic - scaleSize(scale) + Math.min(i2, 4)
15429
+ );
14804
15430
  } else {
14805
15431
  out.push(i2 === 0 && noteCount > 1 ? tonic + 1 : tonic);
14806
15432
  }
@@ -14835,8 +15461,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14835
15461
  for (let i2 = 0; i2 < out.length; i2++) {
14836
15462
  if (!slots[i2].isStrong) continue;
14837
15463
  out[i2] = semitoneToDegree(
15464
+ scale,
14838
15465
  nearestChordTone(
14839
- degreeToPitch(out[i2]).semi,
15466
+ degreeToPitch(scale, out[i2]).semi,
14840
15467
  tones,
14841
15468
  barHeadWeight,
14842
15469
  preferColor
@@ -14847,9 +15474,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14847
15474
  if (slots[i2].isStrong) continue;
14848
15475
  if (slots[i2].value >= quarterSteps) {
14849
15476
  if (rnd() < style.leapAffinity) {
14850
- const from = degreeToPitch(out[i2 - 1]).semi;
15477
+ const from = degreeToPitch(scale, out[i2 - 1]).semi;
14851
15478
  const target = leapTarget(from, tones, rnd);
14852
- if (target !== null) out[i2] = semitoneToDegree(target);
15479
+ if (target !== null) out[i2] = semitoneToDegree(scale, target);
14853
15480
  }
14854
15481
  continue;
14855
15482
  }
@@ -14859,7 +15486,7 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14859
15486
  }
14860
15487
  return out;
14861
15488
  };
14862
- var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, preferShift) => {
15489
+ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, scale, pentatonic, preferShift) => {
14863
15490
  let best = degrees;
14864
15491
  let bestShift = 0;
14865
15492
  let bestScore = Number.NEGATIVE_INFINITY;
@@ -14867,12 +15494,12 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, prefe
14867
15494
  let preferMoved = null;
14868
15495
  for (let shift = -3; shift <= 3; shift++) {
14869
15496
  const moved = degrees.map(
14870
- (d) => pentatonic ? pentaToDegree(degreeToPenta(d) + shift) : d + shift
15497
+ (d) => pentatonic ? coreToDegree(scale, degreeToCore(scale, d) + shift) : d + shift
14871
15498
  );
14872
15499
  let score = 0;
14873
15500
  for (let i2 = 0; i2 < moved.length; i2++) {
14874
15501
  const semi = clampSemi(
14875
- degreeToPitch(moved[i2]).semi,
15502
+ degreeToPitch(scale, moved[i2]).semi,
14876
15503
  MELODY_LOW,
14877
15504
  MELODY_HIGH
14878
15505
  );
@@ -14881,7 +15508,7 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, prefe
14881
15508
  score += important ? w * 3 : w;
14882
15509
  }
14883
15510
  const head = clampSemi(
14884
- degreeToPitch(moved[0]).semi,
15511
+ degreeToPitch(scale, moved[0]).semi,
14885
15512
  MELODY_LOW,
14886
15513
  MELODY_HIGH
14887
15514
  );
@@ -14904,7 +15531,7 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14904
15531
  const out = [];
14905
15532
  let prev = prevSemi;
14906
15533
  if (opts.preserveContour) {
14907
- const raw = degrees.map((d) => degreeToPitch(d).semi);
15534
+ const raw = degrees.map((d) => degreeToPitch(opts.scale, d).semi);
14908
15535
  const lo = Math.min(...raw);
14909
15536
  const hi = Math.max(...raw);
14910
15537
  let shift = 0;
@@ -14918,30 +15545,42 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14918
15545
  }
14919
15546
  for (let i2 = 0; i2 < degrees.length; i2++) {
14920
15547
  let semi = clampSemi(
14921
- degreeToPitch(degrees[i2]).semi,
15548
+ degreeToPitch(opts.scale, degrees[i2]).semi,
14922
15549
  MELODY_LOW,
14923
15550
  MELODY_HIGH
14924
15551
  );
14925
15552
  const limit = i2 === 0 ? MAX_BAR_LEAP_SEMITONES : MAX_LEAP_SEMITONES;
14926
15553
  if (!opts.allowLeap && Math.abs(semi - prev) > limit) {
14927
15554
  semi = clampSemi(
14928
- walk(prev, Math.sign(semi - prev) * 3),
15555
+ walk(opts.scale, prev, Math.sign(semi - prev) * 3),
14929
15556
  MELODY_LOW,
14930
15557
  MELODY_HIGH
14931
15558
  );
14932
15559
  }
14933
15560
  if (!opts.allowArpeggio && i2 >= 2 && Math.abs(out[i2 - 1] - out[i2 - 2]) > STEP_SEMITONES) {
14934
15561
  const back = -Math.sign(out[i2 - 1] - out[i2 - 2]);
14935
- semi = clampSemi(walk(out[i2 - 1], back), MELODY_LOW, MELODY_HIGH);
15562
+ semi = clampSemi(
15563
+ walk(opts.scale, out[i2 - 1], back),
15564
+ MELODY_LOW,
15565
+ MELODY_HIGH
15566
+ );
14936
15567
  }
14937
15568
  if (toneWeight(semi, tones) === 0 && (slots[i2].isStrong || slots[i2].value >= opts.quarterSteps)) {
14938
15569
  const resolved = semi + 1;
14939
- const useResolved = resolved <= MELODY_HIGH && !DIATONIC_PCS.has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
15570
+ const useResolved = resolved <= MELODY_HIGH && !scalePcs(opts.scale).has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
14940
15571
  if (useResolved) {
14941
15572
  semi = resolved;
14942
15573
  } else {
14943
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
14944
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
15574
+ const up = clampSemi(
15575
+ walk(opts.scale, semi, 1),
15576
+ MELODY_LOW,
15577
+ MELODY_HIGH
15578
+ );
15579
+ const down = clampSemi(
15580
+ walk(opts.scale, semi, -1),
15581
+ MELODY_LOW,
15582
+ MELODY_HIGH
15583
+ );
14945
15584
  const score = (s) => toneWeight(s, tones) * 2 + (i2 > 0 && s === prev ? -3 : 0);
14946
15585
  semi = score(down) >= score(up) ? down : up;
14947
15586
  }
@@ -14949,7 +15588,14 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14949
15588
  out.push(semi);
14950
15589
  prev = semi;
14951
15590
  }
14952
- applyPentatonic(out, slots, tones, prevSemi, opts.quarterSteps / 2);
15591
+ applyPentatonic(
15592
+ out,
15593
+ slots,
15594
+ tones,
15595
+ prevSemi,
15596
+ opts.quarterSteps / 2,
15597
+ opts.scale
15598
+ );
14953
15599
  applyOctaveJumps(out, slots, opts.octaveAffinity, opts.rnd);
14954
15600
  return out;
14955
15601
  };
@@ -14966,27 +15612,27 @@ var applyOctaveJumps = (out, slots, affinity, rnd) => {
14966
15612
  out[i2] = canUp && (!canDown || rnd() < 0.5) ? up : down;
14967
15613
  }
14968
15614
  };
14969
- var applyPentatonic = (out, slots, tones, prevSemi, shortSteps) => {
15615
+ var applyPentatonic = (out, slots, tones, prevSemi, shortSteps, scale) => {
14970
15616
  for (let i2 = 0; i2 < out.length; i2++) {
14971
15617
  const semi = out[i2];
14972
- if (!isNonPentatonic(semi)) continue;
14973
- if (tones.some((t) => pitchClass(t.semi) === pitchClass(semi))) continue;
15618
+ if (!isOutsideCore(scale, semi)) continue;
15619
+ if (!scale.strict && tones.some((t) => pitchClass(t.semi) === pitchClass(semi)))
15620
+ continue;
14974
15621
  const before = i2 === 0 ? prevSemi : out[i2 - 1];
14975
15622
  const after = i2 + 1 < out.length ? out[i2 + 1] : null;
14976
15623
  const inByStep = Math.abs(semi - before) <= STEP_SEMITONES;
14977
15624
  const outByStep = after !== null && Math.abs(after - semi) <= STEP_SEMITONES;
14978
- if (inByStep || outByStep) continue;
14979
- if (!slots[i2].isStrong && slots[i2].value <= shortSteps) continue;
14980
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
14981
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
14982
- const score = (s) => (isNonPentatonic(s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
15625
+ if (scale.strict ? inByStep && outByStep : inByStep || outByStep) continue;
15626
+ if (!scale.strict && !slots[i2].isStrong && slots[i2].value <= shortSteps)
15627
+ continue;
15628
+ const up = clampSemi(walk(scale, semi, 1), MELODY_LOW, MELODY_HIGH);
15629
+ const down = clampSemi(walk(scale, semi, -1), MELODY_LOW, MELODY_HIGH);
15630
+ const score = (s) => (isOutsideCore(scale, s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
14983
15631
  out[i2] = score(down) >= score(up) ? down : up;
14984
15632
  }
14985
15633
  };
14986
- var DIATONIC_PCS = new Set(MAJOR_SCALE2.map((d) => d.semi));
14987
15634
  var SHARP_FIFTHS = [0, 7, 2, 9, 4, -1, 6, 1, 8, 3, 10, 5];
14988
15635
  var FLAT_FIFTHS = [0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5];
14989
- var diatonicFifth = (semi) => degreeToPitch(semitoneToDegree(semi)).fifth;
14990
15636
  var nearestOctaveOf = (near, semi) => {
14991
15637
  const pc = pitchClass(semi);
14992
15638
  let best = pc;
@@ -15001,7 +15647,7 @@ var nearestOctaveOf = (near, semi) => {
15001
15647
  }
15002
15648
  return best;
15003
15649
  };
15004
- var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15650
+ var applyChromatic = (scale, pitches, fifths, slots, tones, opts) => {
15005
15651
  const last = pitches.length - 1;
15006
15652
  for (let i2 = 0; i2 < pitches.length; i2++) {
15007
15653
  const tone = tones.find(
@@ -15009,7 +15655,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15009
15655
  );
15010
15656
  if (tone) fifths[i2] = tone.fifth;
15011
15657
  }
15012
- const altered = tones.filter((t) => !DIATONIC_PCS.has(pitchClass(t.semi)));
15658
+ const pcs = scalePcs(scale);
15659
+ const altered = tones.filter((t) => !pcs.has(pitchClass(t.semi)));
15013
15660
  for (let i2 = 0; i2 < pitches.length; i2++) {
15014
15661
  if (opts.keepLast && i2 === last) continue;
15015
15662
  if (!slots[i2].isStrong && slots[i2].value < opts.quarterSteps) continue;
@@ -15037,7 +15684,7 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15037
15684
  const span = Math.abs(after - before);
15038
15685
  const target = span === 2 ? before + dir : span === 3 ? after - dir : null;
15039
15686
  if (target === null) continue;
15040
- if (DIATONIC_PCS.has(pitchClass(target))) continue;
15687
+ if (pcs.has(pitchClass(target))) continue;
15041
15688
  if (target === before || target === after) continue;
15042
15689
  if (target < MELODY_LOW || target > MELODY_HIGH) continue;
15043
15690
  pitches[i2] = target;
@@ -15045,7 +15692,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15045
15692
  lastAltered = i2;
15046
15693
  }
15047
15694
  };
15048
- var draw = (options, resolvedKey, rnd) => {
15695
+ var draw = (options, resolvedKey, scale, rnd) => {
15696
+ const center = resolveCenter(scale);
15049
15697
  const stepsPerBar = options.stepsPerBar;
15050
15698
  const edo = options.edo === 31 ? 31 : 12;
15051
15699
  const scaleStep = (v) => Math.max(1, Math.round(Math.abs(v) * stepsPerBar / BASE_STEPS_PER_BAR)) * Math.sign(v);
@@ -15073,7 +15721,7 @@ var draw = (options, resolvedKey, rnd) => {
15073
15721
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
15074
15722
  const relativeKinds = /* @__PURE__ */ new Set();
15075
15723
  let relativeShift = 0;
15076
- if (rnd() < 0.25) {
15724
+ if (rnd() < 0.25 && !center) {
15077
15725
  for (const kind of pick(
15078
15726
  [["prechorus"], ["bridge"], ["prechorus", "bridge"]],
15079
15727
  rnd
@@ -15183,42 +15831,42 @@ var draw = (options, resolvedKey, rnd) => {
15183
15831
  barKeyShift[b] = s.keyShift;
15184
15832
  }
15185
15833
  }
15186
- const progAPool = resolvedKey.mode === "major" ? SECTION_A_PROGRESSIONS.filter((p) => !p[0].startsWith("Am")) : resolvedKey.mode === "minor" ? SECTION_A_PROGRESSIONS.filter((p) => p[0].startsWith("Am")) : SECTION_A_PROGRESSIONS;
15834
+ const progAPool = center ? center.a : resolvedKey.mode === "major" ? SECTION_A_PROGRESSIONS.filter((p) => !p[0].startsWith("Am")) : resolvedKey.mode === "minor" ? SECTION_A_PROGRESSIONS.filter((p) => p[0].startsWith("Am")) : SECTION_A_PROGRESSIONS;
15187
15835
  const withoutTonic = (pool, root) => {
15188
- const isTonic = (c) => root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15836
+ const isTonic = (c) => center ? center.tonicPattern.test(c) : root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15189
15837
  const out = pool.filter((p) => !p.some(isTonic));
15190
15838
  return out.length > 0 ? out : pool;
15191
15839
  };
15192
- const homeRoot = resolvedKey.mode === "minor" ? "Am" : "C";
15840
+ const homeRoot = center ? center.tonic : resolvedKey.mode === "minor" ? "Am" : "C";
15193
15841
  const progA = pick(
15194
15842
  floating ? withoutTonic(progAPool, homeRoot) : progAPool,
15195
15843
  rnd
15196
15844
  );
15197
- const progBPool = SECTION_B_PROGRESSIONS.filter(
15845
+ const progBPool = (center ? center.b : SECTION_B_PROGRESSIONS).filter(
15198
15846
  (p) => p.join("|") !== progA.join("|")
15199
15847
  );
15200
15848
  const progB = pick(
15201
15849
  floating ? withoutTonic(progBPool, homeRoot) : progBPool,
15202
15850
  rnd
15203
15851
  );
15204
- const progRelativePool = SECTION_A_PROGRESSIONS.filter(
15852
+ const progRelativePool = center ? center.a : SECTION_A_PROGRESSIONS.filter(
15205
15853
  (p) => resolvedKey.mode === "minor" ? !p[0].startsWith("Am") : p[0].startsWith("Am")
15206
15854
  );
15207
15855
  const progRelative = pick(
15208
15856
  floating ? withoutTonic(progRelativePool, homeRoot) : progRelativePool,
15209
15857
  rnd
15210
15858
  );
15211
- const progCPool = SECTION_C_PROGRESSIONS.filter(
15859
+ const progCPool = (center ? center.c : SECTION_C_PROGRESSIONS).filter(
15212
15860
  (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
15213
15861
  );
15214
15862
  const progC = pick(
15215
15863
  floating ? withoutTonic(progCPool, homeRoot) : progCPool,
15216
15864
  rnd
15217
15865
  );
15218
- const tonic = progA[0].startsWith("Am") ? "Am" : "C";
15219
- const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
15220
- const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15221
- const progDeceptive = pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15866
+ const tonic = center ? center.tonic : progA[0].startsWith("Am") ? "Am" : "C";
15867
+ const progHalf = center ? pick(MODAL_HALF_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A2_DERIVATIONS, rnd)(progA);
15868
+ const progFull = center ? pick(MODAL_FULL_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15869
+ const progDeceptive = center ? pick(MODAL_DECEPTIVE_DERIVATIONS, rnd)(progA, center) : pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15222
15870
  let lastChorusBar = -1;
15223
15871
  for (const section of sectionPlan)
15224
15872
  if (section.kind === "chorus" || section.kind === "outro")
@@ -15265,7 +15913,7 @@ var draw = (options, resolvedKey, rnd) => {
15265
15913
  progression[bar - 1] = transposeChordName("G7", shift - prev);
15266
15914
  }
15267
15915
  }
15268
- if (rnd() < 0.3) {
15916
+ if (rnd() < 0.3 && !center) {
15269
15917
  const tonicNames = tonic === "Am" ? ["Am", "Am7"] : ["C", "CM7"];
15270
15918
  const spots = [];
15271
15919
  for (let bar = 0; bar + 1 < totalBars; bar++) {
@@ -15286,10 +15934,12 @@ var draw = (options, resolvedKey, rnd) => {
15286
15934
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" || kind === "drop_chorus" ? "b" : kind === "prechorus" ? "a2" : kind === "bridge" ? "c" : "a";
15287
15935
  const landingOf = (section) => {
15288
15936
  if (section.spec.landing === null) return null;
15289
- const minorHere = resolvedKey.mode === "minor" !== relativeKinds.has(section.kind);
15290
- let landing = (section.spec.landing + (minorHere ? 5 : 0)) % 7;
15291
- if (floating && landing === (minorHere ? 5 : 0))
15292
- landing = (landing + pick([2, 4], rnd)) % 7;
15937
+ const relativeHere = relativeKinds.has(section.kind);
15938
+ const tonicDegree = relativeHere ? scale.tonic === 5 ? 0 : 5 : scale.tonic;
15939
+ const size = scaleSize(scale);
15940
+ let landing = (section.spec.landing + tonicDegree) % size;
15941
+ if (floating && landing === tonicDegree)
15942
+ landing = (landing + pick([2, 4], rnd)) % size;
15293
15943
  return landing;
15294
15944
  };
15295
15945
  const units2 = [];
@@ -15298,9 +15948,12 @@ var draw = (options, resolvedKey, rnd) => {
15298
15948
  const src = sourceOf(section.kind);
15299
15949
  for (let u = 0; u < unitCount; u++) {
15300
15950
  if (!section.spec.melody) {
15951
+ const solo2 = section.kind === "interlude";
15301
15952
  units2.push({
15302
- role: "hold",
15303
- source: "silent",
15953
+ // 見せ場なので走句と山を交互に置く。`hold` のままだと
15954
+ // リズム型が最も薄いものになり、ソロにならない。
15955
+ role: solo2 ? u % 2 === 0 ? "run" : "climax" : "hold",
15956
+ source: solo2 ? "solo" : "silent",
15304
15957
  landing: null,
15305
15958
  section
15306
15959
  });
@@ -15364,7 +16017,10 @@ var draw = (options, resolvedKey, rnd) => {
15364
16017
  arcAmp: 2 + rnd() * 3,
15365
16018
  // オクターブ跳躍は参考曲では音程の1.0%しかない。上げすぎると音域が広がる。
15366
16019
  octaveAffinity: 0.06 + rnd() * 0.12,
15367
- pentatonicMotif: rnd() < 0.55,
16020
+ // **音階を厳しく締める曲は必ず中核音の歩数で組む。** ダイアトニックの度数で輪郭を
16021
+ // 作ると、琉球音階なのにレやラが輪郭の中に入り込む。ファ・シを自由に使う
16022
+ // 陽・民謡だけが、曲ごとに掛けたり掛けなかったりする({@link ComposeScale.strict})。
16023
+ pentatonicMotif: rnd() < 0.55 || scale.strict,
15368
16024
  runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
15369
16025
  stepShape: pick(
15370
16026
  ["arch", "valley", "ascend", "descend", "wave", "pivot"],
@@ -15382,7 +16038,11 @@ var draw = (options, resolvedKey, rnd) => {
15382
16038
  // 界隈曲らしさ:調の外の音(クロマチック)や微小な逸脱を積極的に許容する。
15383
16039
  // 刻みを細かくすると経過音の置き場所が増えるので、同じ係数でも変化音は増える。
15384
16040
  // 参考曲の1.75倍まで伸びていたぶんを引く。
15385
- chromaticAffinity: rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33,
16041
+ // **音階を厳しく締める曲は変化音を控える。** 半音の経過音は長調・短調の泣きメロの
16042
+ // 芯だが、琉球・都節・律ではその半音が音階の外にしか無く、入れたぶんだけ
16043
+ // 音階の色が薄まる(実測で嬰ヘが3%出て、音階内のラ2%より多いという逆転が
16044
+ // 起きていた)。0 にはしない——民族音階の実際の曲にも装飾の半音は出る。
16045
+ chromaticAffinity: (rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33) * (scale.strict ? 0.4 : 1),
15386
16046
  barHeadWeight: rnd() < 0.5 ? 3 : 2,
15387
16047
  bassStyle: pick(
15388
16048
  [
@@ -15537,7 +16197,7 @@ var draw = (options, resolvedKey, rnd) => {
15537
16197
  if (breathBars.has(source)) breathBars.add(bar);
15538
16198
  continue;
15539
16199
  }
15540
- const isB = units2[u].source === "b";
16200
+ const isB = units2[u].source === "b" || units2[u].source === "solo";
15541
16201
  const isA2 = units2[u].source === "a2";
15542
16202
  const isC = units2[u].source === "c";
15543
16203
  const isPrechorusEnd = units2[u].section.kind === "prechorus" && bar === units2[u].section.startBar + units2[u].section.bars - 1;
@@ -15587,6 +16247,7 @@ var draw = (options, resolvedKey, rnd) => {
15587
16247
  const harmony = [];
15588
16248
  const harmony2 = [];
15589
16249
  const pad = [];
16250
+ const solo = [];
15590
16251
  const melodyDurations = [];
15591
16252
  const barTension = new Array(totalBars).fill(0);
15592
16253
  let restSteps = 0;
@@ -15597,9 +16258,9 @@ var draw = (options, resolvedKey, rnd) => {
15597
16258
  let chromaticNotes = 0;
15598
16259
  let sungBars = 0;
15599
16260
  let prevSemi = pick([60, 64, 65, 67, 69, 72, 74, 76], rnd);
15600
- const pickFigure = (gapSteps, scale) => {
16261
+ const pickFigure = (gapSteps, scale2) => {
15601
16262
  const fits = ANSWER_FIGURES.filter(
15602
- (f) => f.reduce((sum, v) => sum + scale(Math.abs(v)), 0) <= gapSteps
16263
+ (f) => f.reduce((sum, v) => sum + scale2(Math.abs(v)), 0) <= gapSteps
15603
16264
  );
15604
16265
  return fits.length === 0 ? null : pick(fits, rnd);
15605
16266
  };
@@ -15657,8 +16318,9 @@ var draw = (options, resolvedKey, rnd) => {
15657
16318
  slots,
15658
16319
  tones,
15659
16320
  style,
16321
+ scale,
15660
16322
  motifContour,
15661
- semitoneToDegree(headSemi),
16323
+ semitoneToDegree(scale, headSemi),
15662
16324
  contourOffset,
15663
16325
  repeatShift,
15664
16326
  headWeight,
@@ -15674,7 +16336,8 @@ var draw = (options, resolvedKey, rnd) => {
15674
16336
  ...plannedDegrees[source]
15675
16337
  );
15676
16338
  const landing = units2[unitOf(bar)].landing;
15677
- if (landing !== null && barInUnit(bar) === 1) landOn(degrees, landing);
16339
+ if (landing !== null && barInUnit(bar) === 1)
16340
+ landOn(scale, degrees, landing);
15678
16341
  plannedDegrees[bar] = [...degrees];
15679
16342
  const isMotifBar = source !== null || role === "motif" || role === "sequence" || role === "climax" || role === "answer";
15680
16343
  let fitted = degrees;
@@ -15686,6 +16349,7 @@ var draw = (options, resolvedKey, rnd) => {
15686
16349
  tones,
15687
16350
  prevSemi,
15688
16351
  quarterSteps,
16352
+ scale,
15689
16353
  style.pentatonicMotif,
15690
16354
  motifShiftMemo.get(shiftKey) ?? null
15691
16355
  );
@@ -15693,6 +16357,7 @@ var draw = (options, resolvedKey, rnd) => {
15693
16357
  motifShiftMemo.set(shiftKey, r.shift);
15694
16358
  }
15695
16359
  const pitches = shapeBar(fitted, slots, tones, prevSemi, {
16360
+ scale,
15696
16361
  allowLeap: role === "climax",
15697
16362
  allowArpeggio: role === "climax" || role === "run" && style.runShape === "broken" || role === "cadence" && style.cadenceShape !== "descend",
15698
16363
  quarterSteps,
@@ -15707,16 +16372,19 @@ var draw = (options, resolvedKey, rnd) => {
15707
16372
  rnd,
15708
16373
  preserveContour: isMotifBar
15709
16374
  });
15710
- if (landing !== null && barInUnit(bar) === 1) landPitch(pitches, landing);
15711
- const fifths = pitches.map(diatonicFifth);
15712
- applyChromatic(pitches, fifths, slots, tones, {
16375
+ if (landing !== null && barInUnit(bar) === 1)
16376
+ landPitch(scale, pitches, landing);
16377
+ const fifths = pitches.map((semi) => scaleFifth(scale, semi));
16378
+ applyChromatic(scale, pitches, fifths, slots, tones, {
15713
16379
  affinity: style.chromaticAffinity,
15714
16380
  quarterSteps,
15715
16381
  shortSteps: scaleStep(EIGHTH),
15716
16382
  keepLast: landing !== null && barInUnit(bar) === 1,
15717
16383
  rnd
15718
16384
  });
15719
- const silent = units2[unitOf(bar)].source === "silent";
16385
+ const unitSource = units2[unitOf(bar)].source;
16386
+ const isSolo = unitSource === "solo";
16387
+ const silent = unitSource === "silent" || isSolo;
15720
16388
  const barHead = pitches[0];
15721
16389
  let tensionSum = 0;
15722
16390
  let tensionSteps = 0;
@@ -15724,7 +16392,7 @@ var draw = (options, resolvedKey, rnd) => {
15724
16392
  const semi = pitches[i2];
15725
16393
  tensionSum += (3 - toneWeight(semi, tones)) / 3 * slots[i2].value;
15726
16394
  tensionSteps += slots[i2].value;
15727
- if (role !== "climax") {
16395
+ if (!silent && role !== "climax") {
15728
16396
  const gap = Math.abs(semi - prevSemi);
15729
16397
  maxLeap = Math.max(maxLeap, gap);
15730
16398
  intervals++;
@@ -15733,10 +16401,25 @@ var draw = (options, resolvedKey, rnd) => {
15733
16401
  }
15734
16402
  const slot = slots[i2];
15735
16403
  if (silent) {
16404
+ if (isSolo) {
16405
+ const ks = barKeyShift[bar];
16406
+ const fifthShiftSolo = ks === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(ks % 12 + 12) % 12];
16407
+ solo.push({
16408
+ startStep: barStart + slot.at,
16409
+ pitchUnits: spelledToUnits(
16410
+ semi + ks,
16411
+ fifths[i2] + fifthShiftSolo,
16412
+ edo
16413
+ ),
16414
+ durationSteps: slot.value,
16415
+ // ソロは前に出る声部なので、歌メロより気持ち強く弾く。
16416
+ velocity: slot.at === 0 ? 116 : slot.value <= scaleStep(SIXTEENTH) ? 96 : 106
16417
+ });
16418
+ }
15736
16419
  prevSemi = semi;
15737
16420
  continue;
15738
16421
  }
15739
- if (!DIATONIC_PCS.has(pitchClass(semi))) chromaticNotes++;
16422
+ if (!scalePcs(scale).has(pitchClass(semi))) chromaticNotes++;
15740
16423
  const k = barKeyShift[bar];
15741
16424
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15742
16425
  melody.push({
@@ -15790,7 +16473,7 @@ var draw = (options, resolvedKey, rnd) => {
15790
16473
  subSemi = pushSub(
15791
16474
  cursor,
15792
16475
  step,
15793
- i2 === 0 ? subSemi : walk(subSemi, subDir)
16476
+ i2 === 0 ? subSemi : walk(scale, subSemi, subDir)
15794
16477
  );
15795
16478
  placed++;
15796
16479
  }
@@ -15814,7 +16497,7 @@ var draw = (options, resolvedKey, rnd) => {
15814
16497
  subSemi = pushSub(
15815
16498
  cursor,
15816
16499
  step,
15817
- index === 0 ? subSemi : walk(subSemi, subDir * (index % 2 === 0 ? 1 : -1))
16500
+ index === 0 ? subSemi : walk(scale, subSemi, subDir * (index % 2 === 0 ? 1 : -1))
15818
16501
  );
15819
16502
  index++;
15820
16503
  }
@@ -15824,7 +16507,11 @@ var draw = (options, resolvedKey, rnd) => {
15824
16507
  pushSub(0, stepsPerBar, subSemi);
15825
16508
  } else if (subStyle === "long-short") {
15826
16509
  subSemi = pushSub(0, scaleStep(DOT_HALF), subSemi);
15827
- pushSub(scaleStep(DOT_HALF), scaleStep(QUARTER), walk(subSemi, subDir));
16510
+ pushSub(
16511
+ scaleStep(DOT_HALF),
16512
+ scaleStep(QUARTER),
16513
+ walk(scale, subSemi, subDir)
16514
+ );
15828
16515
  } else if (role === "cadence") {
15829
16516
  pushSub(0, stepsPerBar, subSemi);
15830
16517
  } else {
@@ -15839,6 +16526,7 @@ var draw = (options, resolvedKey, rnd) => {
15839
16526
  const written = [];
15840
16527
  for (let i2 = 0; i2 < slots.length; i2++) {
15841
16528
  const hTone = harmonyPitch(
16529
+ scale,
15842
16530
  pitches[i2],
15843
16531
  tones,
15844
16532
  last,
@@ -15909,6 +16597,7 @@ var draw = (options, resolvedKey, rnd) => {
15909
16597
  const nextTones = chordTones(progression[(bar + 1) % totalBars]);
15910
16598
  const A = clampSemi(
15911
16599
  walk(
16600
+ scale,
15912
16601
  nextTones[0] ? clampSemi(nextTones[0].semi, BASS_LOW, BASS_HIGH) : R,
15913
16602
  -1
15914
16603
  ),
@@ -15965,7 +16654,7 @@ var draw = (options, resolvedKey, rnd) => {
15965
16654
  let bassCursor = 0;
15966
16655
  for (const [semi, value] of bassCell) {
15967
16656
  const len = scaleStep(value);
15968
- const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : degreeToPitch(semitoneToDegree(semi)).fifth;
16657
+ const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : scaleFifth(scale, semi);
15969
16658
  const k = barKeyShift[bar];
15970
16659
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15971
16660
  barBass.push({
@@ -16027,7 +16716,7 @@ var draw = (options, resolvedKey, rnd) => {
16027
16716
  const after = melody[i2 + 2];
16028
16717
  if (after && Math.floor(after.startStep / stepsPerBar) === barIdx) {
16029
16718
  const afterSemi = Math.round(after.pitchUnits / UNITS_PER_SEMITONE) - (barKeyShift[barIdx] ?? 0);
16030
- if (!DIATONIC_PCS.has(pitchClass(afterSemi))) continue;
16719
+ if (!scalePcs(scale).has(pitchClass(afterSemi))) continue;
16031
16720
  }
16032
16721
  const nextRole = barRoles[barIdx];
16033
16722
  let tieProb = 0.3;
@@ -16048,11 +16737,13 @@ var draw = (options, resolvedKey, rnd) => {
16048
16737
  melodyDurations[i2] += melodyDurations[i2 + 1];
16049
16738
  melodyDurations.splice(i2 + 1, 1);
16050
16739
  }
16051
- const hCurIdx = harmony.findIndex((h) => h.startStep === cur.startStep);
16052
- const hNxtIdx = harmony.findIndex((h) => h.startStep === nxt.startStep);
16053
- if (hCurIdx >= 0 && hNxtIdx >= 0) {
16054
- harmony[hCurIdx].durationSteps += harmony[hNxtIdx].durationSteps;
16055
- harmony.splice(hNxtIdx, 1);
16740
+ for (const voice of [harmony, harmony2]) {
16741
+ const nxtIdx = voice.findIndex((h) => h.startStep === nxt.startStep);
16742
+ if (nxtIdx < 0) continue;
16743
+ const curIdx = voice.findIndex((h) => h.startStep === cur.startStep);
16744
+ if (curIdx >= 0)
16745
+ voice[curIdx].durationSteps += voice[nxtIdx].durationSteps;
16746
+ voice.splice(nxtIdx, 1);
16056
16747
  }
16057
16748
  i2--;
16058
16749
  }
@@ -16106,7 +16797,7 @@ var draw = (options, resolvedKey, rnd) => {
16106
16797
  };
16107
16798
  const shiftUnits = semitonesToUnits(rootShift, edo);
16108
16799
  if (shiftUnits !== 0)
16109
- for (const list of [melody, submelody, bass, harmony, harmony2, pad])
16800
+ for (const list of [melody, submelody, bass, harmony, harmony2, pad, solo])
16110
16801
  for (const n of list) n.pitchUnits = n.pitchUnits + shiftUnits;
16111
16802
  const octave = useOctaveLayer ? melody.filter(
16112
16803
  (n) => n.durationSteps >= quarterSteps || rnd() < octaveCoverage
@@ -16117,6 +16808,8 @@ var draw = (options, resolvedKey, rnd) => {
16117
16808
  rootShift,
16118
16809
  keyName: resolvedKey.keyName,
16119
16810
  keyLabel: resolvedKey.keyLabel,
16811
+ scaleId: scale.id,
16812
+ scaleLabel: scale.label,
16120
16813
  moodLabel: resolvedKey.moodLabel,
16121
16814
  bpm,
16122
16815
  sections: sectionPlan,
@@ -16136,6 +16829,7 @@ var draw = (options, resolvedKey, rnd) => {
16136
16829
  harmony2,
16137
16830
  octave,
16138
16831
  pad,
16832
+ solo,
16139
16833
  melodyDurations,
16140
16834
  restSteps,
16141
16835
  totalSteps: Math.max(1, sungBars) * stepsPerBar,
@@ -16265,12 +16959,17 @@ var composeSong = (options) => {
16265
16959
  const recent = options.recent ?? [];
16266
16960
  const count = Math.max(1, options.drawCount ?? DRAW_COUNT);
16267
16961
  const resolvedKey = resolveComposeKey(options.baseKey, rnd);
16962
+ const scale = resolveComposeScale(
16963
+ options.scale,
16964
+ resolvedKey.mode === "minor",
16965
+ rnd
16966
+ );
16268
16967
  let best = null;
16269
16968
  let bestScore = Number.NEGATIVE_INFINITY;
16270
16969
  let bestIsValid = false;
16271
16970
  let rejected = 0;
16272
16971
  for (let attempt = 1; attempt <= count; attempt++) {
16273
- const d = draw(options, resolvedKey, rnd);
16972
+ const d = draw(options, resolvedKey, scale, rnd);
16274
16973
  const { stats, ok } = evaluate(d, recent);
16275
16974
  if (!ok) rejected++;
16276
16975
  const better = ok === bestIsValid ? stats.score > bestScore : ok;
@@ -16278,15 +16977,18 @@ var composeSong = (options) => {
16278
16977
  bestScore = stats.score;
16279
16978
  bestIsValid = ok;
16280
16979
  best = {
16281
- // ドラム・楽器は勝った候補にだけ後から付ける(メロディに依存しないので
16282
- // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
16980
+ // ドラム・楽器・編曲プランは勝った候補にだけ後から付ける(メロディに
16981
+ // 依存しないので候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
16283
16982
  drum: "",
16284
16983
  instrument: "",
16984
+ arrange: EMPTY_ARRANGE,
16285
16985
  chordProgression: d.chordProgression,
16286
16986
  chordPattern: d.chordPattern,
16287
16987
  rootShift: d.rootShift,
16288
16988
  keyName: d.keyName,
16289
16989
  keyLabel: d.keyLabel,
16990
+ scaleId: scale.id,
16991
+ scaleLabel: scale.label,
16290
16992
  moodLabel: d.moodLabel,
16291
16993
  bpm: d.bpm,
16292
16994
  sections: d.sections,
@@ -16300,6 +17002,7 @@ var composeSong = (options) => {
16300
17002
  harmony2: d.harmony2,
16301
17003
  octave: d.octave,
16302
17004
  pad: d.pad,
17005
+ solo: d.solo,
16303
17006
  stats: { ...stats, attempts: attempt, rejected }
16304
17007
  };
16305
17008
  }
@@ -16308,6 +17011,7 @@ var composeSong = (options) => {
16308
17011
  result.stats.rejected = rejected;
16309
17012
  result.drum = pickBuiltinDrum(result, rnd);
16310
17013
  result.instrument = pickBuiltinInstrument(result, rnd);
17014
+ result.arrange = buildArrangePlan(result, rnd);
16311
17015
  return result;
16312
17016
  };
16313
17017
  var pickBuiltinDrum = (song, rnd) => {
@@ -16319,6 +17023,80 @@ var pickBuiltinDrum = (song, rnd) => {
16319
17023
  const pool = dotted >= 0.08 ? ["shuffle", "8beat", "16beat"] : song.bpm >= 150 ? ["4beat", "dance", "16beat", "disco"] : short >= 0.85 ? ["16beat", "dance", "disco"] : song.bpm <= 115 ? ["bossa", "8beat", "shuffle", "4beat"] : ["8beat", "4beat", "16beat", "dance"];
16320
17024
  return pick(pool, rnd);
16321
17025
  };
17026
+ var EMPTY_ARRANGE = {
17027
+ backing: [],
17028
+ sparkle: null,
17029
+ padSections: [],
17030
+ lead: null,
17031
+ bassLayer: null
17032
+ };
17033
+ var LOUD_KINDS = ["prechorus", "chorus", "bridge"];
17034
+ var QUIET_KINDS = [
17035
+ "intro",
17036
+ "verse",
17037
+ "interlude",
17038
+ "drop_chorus",
17039
+ "outro"
17040
+ ];
17041
+ var CHORUS_KINDS = ["chorus", "drop_chorus"];
17042
+ var buildArrangePlan = (song, rnd) => {
17043
+ const present = new Set(song.sections.map((s) => s.kind));
17044
+ const narrow = (kinds) => {
17045
+ const hit = kinds.filter((k) => present.has(k));
17046
+ return hit.length === 0 ? null : hit;
17047
+ };
17048
+ const base = {
17049
+ pattern: song.chordPattern,
17050
+ sections: null,
17051
+ octave: 0
17052
+ };
17053
+ const others = chordPatternPool(song.bpm).filter((p) => p !== base.pattern);
17054
+ const backing = [base];
17055
+ const addKinds = pick(
17056
+ [LOUD_KINDS, CHORUS_KINDS, LOUD_KINDS, QUIET_KINDS],
17057
+ rnd
17058
+ );
17059
+ const second = pick(others, rnd);
17060
+ backing.push({
17061
+ pattern: second,
17062
+ sections: narrow(addKinds),
17063
+ octave: 0
17064
+ });
17065
+ if (rnd() < 0.55) {
17066
+ const rest = others.filter((p) => p !== second);
17067
+ backing.push({
17068
+ pattern: pick(rest.length > 0 ? rest : others, rnd),
17069
+ // 2本目と逆側へ置く(両方サビに寄せると、サビだけ団子になる)。
17070
+ sections: narrow(addKinds === QUIET_KINDS ? LOUD_KINDS : QUIET_KINDS),
17071
+ octave: 0
17072
+ });
17073
+ }
17074
+ const sparklePool = chordPatternPool(song.bpm).filter(
17075
+ (p) => p !== "block" && !backing.some((b) => b.pattern === p)
17076
+ );
17077
+ const sparkle = sparklePool.length > 0 && rnd() < 0.7 ? {
17078
+ pattern: pick(sparklePool, rnd),
17079
+ sections: narrow(pick([LOUD_KINDS, CHORUS_KINDS], rnd)) ?? CHORUS_KINDS,
17080
+ octave: 1
17081
+ } : null;
17082
+ const padSections = narrow(
17083
+ pick(
17084
+ [
17085
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17086
+ ["chorus", "drop_chorus"],
17087
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17088
+ ["bridge", "chorus"]
17089
+ ],
17090
+ rnd
17091
+ )
17092
+ ) ?? [];
17093
+ const lead = rnd() < 0.75 ? {
17094
+ sections: narrow(pick([CHORUS_KINDS, LOUD_KINDS], rnd)) ?? CHORUS_KINDS,
17095
+ octave: rnd() < 0.5 ? 0 : 1
17096
+ } : null;
17097
+ const bassLayer = rnd() < 0.25 ? { sections: narrow(CHORUS_KINDS) ?? CHORUS_KINDS, octave: 1 } : null;
17098
+ return { backing, sparkle, padSections, lead, bassLayer };
17099
+ };
16322
17100
  var pickBuiltinInstrument = (song, rnd) => {
16323
17101
  const eighth = BASE_STEPS_PER_BAR / 8;
16324
17102
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
@@ -16849,6 +17627,34 @@ var buildUI = (target, options) => {
16849
17627
  <span class="dtm-grow"></span>
16850
17628
  <span class="dtm-hint" data-dtm="compose-key-hint"></span>
16851
17629
  </div>
17630
+ <div class="dtm-row" data-dtm="compose-scale-row">
17631
+ <span class="dtm-label">\u97F3\u968E</span>
17632
+ <select class="dtm-select" data-dtm="compose-scale" title="\u65CB\u5F8B\u304C\u4F7F\u3046\u97F3\u968E\u3092\u9078\u3073\u307E\u3059\u3002\u30D9\u30FC\u30B9\u8ABF\uFF08\u4E3B\u97F3\u306E\u9AD8\u3055\uFF09\u3068\u306F\u72EC\u7ACB\u3057\u305F\u8A2D\u5B9A\u3067\u3059">
17633
+ <option value="auto" title="\u30D9\u30FC\u30B9\u8ABF\u306E\u9577\u77ED\u306B\u5408\u308F\u305B\u3066\u3001\u967D\u97F3\u968E\uFF08\u9577\u8ABF\uFF09\u304B\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\uFF09\u3092\u4F7F\u3044\u307E\u3059">\u304A\u307E\u304B\u305B\uFF08\u5F93\u6765\u3069\u304A\u308A\uFF09</option>
17634
+ <option value="any" title="9\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057\uFF08\u5168\u97F3\u968E\u304B\u3089\u62BD\u9078\uFF09</option>
17635
+ <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
17636
+ <option value="yo" title="J-POP\u306E\u6A19\u6E96\u3002\u660E\u308B\u304F\u7D20\u76F4\u3067\u6B4C\u3044\u3084\u3059\u3044\u3002\u5F93\u6765\u306E\u9577\u8ABF\u3068\u540C\u3058">\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
17637
+ <option value="minyo" title="\u308F\u3089\u3079\u6B4C\u30FB\u6C11\u8B21\u306E\u97F3\u968E\u3002\u7FF3\u308A\u304C\u3042\u308B\u304C\u6697\u3059\u304E\u306A\u3044\u3002\u5F93\u6765\u306E\u77ED\u8ABF\u3068\u540C\u3058">\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
17638
+ <option value="ryukyu" title="\u6C96\u7E04\u97F3\u968E\u3002\u30EC\u3068\u30E9\u3092\u629C\u304D\u3001\u30D5\u30A1\u3068\u30B7\u3092\u67F1\u306B\u3059\u308B\u3002\u660E\u308B\u304F\u8DF3\u306D\u308B">\u7409\u7403\u97F3\u968E\uFF08\u6C96\u7E04\uFF09</option>
17639
+ <option value="miyakobushi" title="\u300E\u3055\u304F\u3089\u3055\u304F\u3089\u300F\u306E\u97F3\u968E\u3002\u4E3B\u97F3\u306E\u3059\u3050\u4E0A\u304C\u534A\u97F3\u3067\u3001\u7FF3\u308A\u304C\u6FC3\u3044">\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09</option>
17640
+ <option value="ritsu" title="\u96C5\u697D\u30FB\u58F0\u660E\u306E\u97F3\u968E\u3002\u534A\u97F3\u3092\u542B\u307E\u305A\u3001\u5E73\u3089\u3067\u8358\u91CD\u306B\u6D41\u308C\u308B">\u5F8B\u97F3\u968E\uFF08\u96C5\u697D\uFF09</option>
17641
+ </optgroup>
17642
+ <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
17643
+ <option value="dorian" title="\u77ED\u8ABF\u3060\u304C6\u5EA6\u304C\u660E\u308B\u3044\u3002\u30B1\u30EB\u30C8\u30FB\u30ED\u30C3\u30AF\u30FB\u30B7\u30C6\u30A3\u30DD\u30C3\u30D7">\u30C9\u30EA\u30A2\u30F3</option>
17644
+ <option value="phrygian" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3002\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\uFF0F\u30E1\u30BF\u30EB\u306E\u7DCA\u8FEB\u3057\u305F\u97FF\u304D">\u30D5\u30EA\u30B8\u30A2\u30F3</option>
17645
+ <option value="lydian" title="4\u5EA6\u304C\u9AD8\u304F\u3001\u6D6E\u904A\u3057\u3066\u5E83\u304C\u308B\u3002\u6620\u753B\u97F3\u697D\u30FB\u30B2\u30FC\u30E0\u306E\u7A7A\u306E\u8272">\u30EA\u30C7\u30A3\u30A2\u30F3</option>
17646
+ <option value="mixolydian" title="\u9577\u8ABF\u3060\u304C7\u5EA6\u304C\u4F4E\u3044\u3002\u30D6\u30EB\u30FC\u30B9\u30ED\u30C3\u30AF\u30FB\u6C11\u65CF\u97F3\u697D\u306E\u571F\u304F\u3055\u3055">\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3</option>
17647
+ </optgroup>
17648
+ <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
17649
+ <option value="harmonic_minor" title="\u5C0E\u97F3\u30BD\u266F\u3092\u6301\u3064\u77ED\u8ABF\u3002\u58972\u5EA6\u304C\u6CE3\u304D\u3092\u4F5C\u308B\u3002\u30AF\u30E9\u30B7\u30C3\u30AF\u30FBV\u7CFB\u30FB\u5287\u4F34">\u548C\u58F0\u7684\u77ED\u97F3\u968E</option>
17650
+ <option value="hijaz" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3001\u4E3B\u548C\u97F3\u306F\u9577\u4E09\u548C\u97F3\u3002\u4E2D\u6771\u30FB\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\u30FB\u30E1\u30BF\u30EB">\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09</option>
17651
+ <option value="hungarian" title="\u58972\u5EA6\u304C2\u304B\u6240\u3002\u97F3\u968E\u306E\u4E2D\u3067\u3044\u3061\u3070\u3093\u8DF3\u306D\u305F\u3001\u7570\u56FD\u3081\u3044\u305F\u97FF\u304D">\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09</option>
17652
+ <option value="blues" title="\u30D6\u30EB\u30FC\u30CE\u30FC\u30C8\u5165\u308A\u306E6\u97F3\u97F3\u968E\u3002\u77ED3\u5EA6\u3067\u6B4C\u3044\u3001\u4F34\u594F\u306F\u95773\u5EA6\u3067\u9CF4\u308B">\u30D6\u30EB\u30FC\u30B9\u97F3\u968E</option>
17653
+ </optgroup>
17654
+ </select>
17655
+ <span class="dtm-grow"></span>
17656
+ <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
17657
+ </div>
16852
17658
  </div>
16853
17659
  <div class="dtm-row">
16854
17660
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
@@ -17051,6 +17857,8 @@ var buildUI = (target, options) => {
17051
17857
  composeSectionsLen: sel("compose-sections-len"),
17052
17858
  composeKey: sel("compose-key"),
17053
17859
  composeKeyHint: sel("compose-key-hint"),
17860
+ composeScale: sel("compose-scale"),
17861
+ composeScaleHint: sel("compose-scale-hint"),
17054
17862
  macroComposeVocal: sel("macro-compose-vocal"),
17055
17863
  macroComposeInfo: sel("macro-compose-info"),
17056
17864
  macroClear: sel("macro-clear"),
@@ -17094,7 +17902,12 @@ var INSTRUMENT_PRESETS = {
17094
17902
  melody: "Acoustic Grand Piano",
17095
17903
  submelody: "Vibraphone",
17096
17904
  bass: "Electric Bass (finger)",
17097
- chord: "Pad 2 (warm)"
17905
+ chord: "Pad 2 (warm)",
17906
+ solo: "Electric Guitar (clean)",
17907
+ // グロッケンは実物の音域が G5(79)〜 の高音楽器で、旋律の音域へそのまま置くと
17908
+ // 金切り音になる。**外すのではなくオクターブで下げて使う**
17909
+ // ({@link GM_BRIGHT_CEILING} / {@link fitInstrumentOctave})。
17910
+ chorusLead: "Glockenspiel"
17098
17911
  },
17099
17912
  acoustic: {
17100
17913
  displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
@@ -17102,7 +17915,9 @@ var INSTRUMENT_PRESETS = {
17102
17915
  melody: "Acoustic Guitar (steel)",
17103
17916
  submelody: "Harmonica",
17104
17917
  bass: "Acoustic Bass",
17105
- chord: "Acoustic Guitar (nylon)"
17918
+ chord: "Acoustic Guitar (nylon)",
17919
+ solo: "Overdriven Guitar",
17920
+ chorusLead: "String Ensemble 1"
17106
17921
  },
17107
17922
  jazz_night: {
17108
17923
  displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
@@ -17110,7 +17925,9 @@ var INSTRUMENT_PRESETS = {
17110
17925
  melody: "Electric Piano 1",
17111
17926
  submelody: "Flute",
17112
17927
  bass: "Acoustic Bass",
17113
- chord: "Electric Guitar (jazz)"
17928
+ chord: "Electric Guitar (jazz)",
17929
+ solo: "Tenor Sax",
17930
+ chorusLead: "Muted Trumpet"
17114
17931
  },
17115
17932
  // --- MODERN & VIBE: エッジの効いた現代的な響き ---
17116
17933
  synth_pop: {
@@ -17119,7 +17936,9 @@ var INSTRUMENT_PRESETS = {
17119
17936
  melody: "Lead 2 (sawtooth)",
17120
17937
  submelody: "Lead 4 (chiff)",
17121
17938
  bass: "Synth Bass 2",
17122
- chord: "Pad 3 (polysynth)"
17939
+ chord: "Pad 3 (polysynth)",
17940
+ solo: "Distortion Guitar",
17941
+ chorusLead: "Synth Brass 1"
17123
17942
  },
17124
17943
  cyber_punk: {
17125
17944
  displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
@@ -17127,7 +17946,9 @@ var INSTRUMENT_PRESETS = {
17127
17946
  melody: "Lead 8 (bass + lead)",
17128
17947
  submelody: "Lead 5 (charang)",
17129
17948
  bass: "Synth Bass 2",
17130
- chord: "Pad 8 (sweep)"
17949
+ chord: "Pad 8 (sweep)",
17950
+ solo: "Distortion Guitar",
17951
+ chorusLead: "Lead 7 (fifths)"
17131
17952
  },
17132
17953
  rock: {
17133
17954
  displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
@@ -17135,7 +17956,9 @@ var INSTRUMENT_PRESETS = {
17135
17956
  melody: "Distortion Guitar",
17136
17957
  submelody: "Rock Organ",
17137
17958
  bass: "Electric Bass (pick)",
17138
- chord: "Overdriven Guitar"
17959
+ chord: "Overdriven Guitar",
17960
+ solo: "Distortion Guitar",
17961
+ chorusLead: "Brass Section"
17139
17962
  },
17140
17963
  // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
17141
17964
  orchestra: {
@@ -17144,7 +17967,9 @@ var INSTRUMENT_PRESETS = {
17144
17967
  melody: "French Horn",
17145
17968
  submelody: "Pizzicato Strings",
17146
17969
  bass: "Cello",
17147
- chord: "Tremolo Strings"
17970
+ chord: "Tremolo Strings",
17971
+ solo: "Violin",
17972
+ chorusLead: "Trumpet"
17148
17973
  },
17149
17974
  japanese_wa: {
17150
17975
  displayName: "\u548C\u98A8\u30FB\u96C5",
@@ -17152,7 +17977,9 @@ var INSTRUMENT_PRESETS = {
17152
17977
  melody: "Koto",
17153
17978
  submelody: "Shamisen",
17154
17979
  bass: "Taiko Drum",
17155
- chord: "Shakuhachi"
17980
+ chord: "Shakuhachi",
17981
+ solo: "Shakuhachi",
17982
+ chorusLead: "Glockenspiel"
17156
17983
  },
17157
17984
  arabic_exotic: {
17158
17985
  displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
@@ -17160,7 +17987,9 @@ var INSTRUMENT_PRESETS = {
17160
17987
  melody: "Sitar",
17161
17988
  submelody: "Bagpipe",
17162
17989
  bass: "Fretless Bass",
17163
- chord: "Kalimba"
17990
+ chord: "Kalimba",
17991
+ solo: "Shanai",
17992
+ chorusLead: "Steel Drums"
17164
17993
  },
17165
17994
  // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
17166
17995
  fantasy_rpg: {
@@ -17169,7 +17998,9 @@ var INSTRUMENT_PRESETS = {
17169
17998
  melody: "Ocarina",
17170
17999
  submelody: "Celesta",
17171
18000
  bass: "Timpani",
17172
- chord: "Orchestral Harp"
18001
+ chord: "Orchestral Harp",
18002
+ solo: "Pan Flute",
18003
+ chorusLead: "Choir Aahs"
17173
18004
  },
17174
18005
  ambient_cloud: {
17175
18006
  displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
@@ -17177,7 +18008,9 @@ var INSTRUMENT_PRESETS = {
17177
18008
  melody: "Lead 6 (voice)",
17178
18009
  submelody: "Music Box",
17179
18010
  bass: "Synth Bass 1",
17180
- chord: "Pad 7 (halo)"
18011
+ chord: "Pad 7 (halo)",
18012
+ solo: "Lead 3 (calliope)",
18013
+ chorusLead: "Synth Choir"
17181
18014
  },
17182
18015
  retro_game: {
17183
18016
  displayName: "8-bit \u30EC\u30C8\u30ED",
@@ -17185,8 +18018,99 @@ var INSTRUMENT_PRESETS = {
17185
18018
  melody: "Lead 1 (square)",
17186
18019
  submelody: "Lead 2 (sawtooth)",
17187
18020
  bass: "Synth Bass 1",
17188
- chord: "Clavinet"
17189
- }
18021
+ chord: "Clavinet",
18022
+ solo: "Lead 8 (bass + lead)",
18023
+ chorusLead: "Lead 4 (chiff)"
18024
+ }
18025
+ };
18026
+ var GM_INSTRUMENT_RANGE = {
18027
+ // 鍵盤・音板
18028
+ "Acoustic Grand Piano": [21, 108],
18029
+ "Electric Piano 1": [28, 103],
18030
+ Clavinet: [36, 96],
18031
+ Celesta: [60, 108],
18032
+ Glockenspiel: [79, 108],
18033
+ "Music Box": [72, 108],
18034
+ Vibraphone: [53, 89],
18035
+ Kalimba: [60, 84],
18036
+ "Orchestral Harp": [23, 104],
18037
+ // 弦・撥弦
18038
+ "Acoustic Guitar (steel)": [40, 83],
18039
+ "Acoustic Guitar (nylon)": [40, 83],
18040
+ "Electric Guitar (clean)": [40, 86],
18041
+ "Electric Guitar (jazz)": [40, 86],
18042
+ "Overdriven Guitar": [40, 88],
18043
+ "Distortion Guitar": [40, 88],
18044
+ Violin: [55, 103],
18045
+ Cello: [36, 76],
18046
+ "String Ensemble 1": [28, 100],
18047
+ "Tremolo Strings": [28, 100],
18048
+ "Pizzicato Strings": [28, 96],
18049
+ Sitar: [48, 79],
18050
+ Shamisen: [48, 84],
18051
+ Koto: [41, 77],
18052
+ // ベース
18053
+ "Acoustic Bass": [28, 60],
18054
+ "Electric Bass (finger)": [28, 67],
18055
+ "Electric Bass (pick)": [28, 67],
18056
+ "Fretless Bass": [28, 67],
18057
+ "Synth Bass 1": [24, 72],
18058
+ "Synth Bass 2": [24, 72],
18059
+ // 管
18060
+ Flute: [60, 96],
18061
+ "Pan Flute": [60, 91],
18062
+ Shakuhachi: [62, 86],
18063
+ Ocarina: [60, 84],
18064
+ Harmonica: [60, 96],
18065
+ Bagpipe: [62, 86],
18066
+ Shanai: [60, 86],
18067
+ "Tenor Sax": [44, 75],
18068
+ Trumpet: [55, 82],
18069
+ "Muted Trumpet": [55, 82],
18070
+ "French Horn": [41, 77],
18071
+ "Brass Section": [41, 84],
18072
+ // 声・打・オルガン
18073
+ "Choir Aahs": [43, 84],
18074
+ "Synth Choir": [43, 84],
18075
+ "Steel Drums": [55, 86],
18076
+ "Taiko Drum": [30, 60],
18077
+ Timpani: [36, 57],
18078
+ "Rock Organ": [36, 96],
18079
+ // シンセ(実物が無いので一般的な使用域)
18080
+ "Synth Brass 1": [36, 96],
18081
+ "Lead 1 (square)": [36, 96],
18082
+ "Lead 2 (sawtooth)": [36, 96],
18083
+ "Lead 3 (calliope)": [48, 96],
18084
+ "Lead 4 (chiff)": [48, 96],
18085
+ "Lead 5 (charang)": [40, 96],
18086
+ "Lead 6 (voice)": [43, 91],
18087
+ "Lead 7 (fifths)": [36, 84],
18088
+ "Lead 8 (bass + lead)": [28, 91],
18089
+ "Pad 2 (warm)": [24, 96],
18090
+ "Pad 3 (polysynth)": [24, 96],
18091
+ "Pad 7 (halo)": [24, 96],
18092
+ "Pad 8 (sweep)": [24, 96]
18093
+ };
18094
+ var GM_BRIGHT_CEILING = {
18095
+ Glockenspiel: 72,
18096
+ "Tinkle Bell": 72,
18097
+ "Music Box": 79,
18098
+ Celesta: 84,
18099
+ Kalimba: 79,
18100
+ "Steel Drums": 84,
18101
+ "FX 3 (crystal)": 79
18102
+ };
18103
+ var OCTAVE_FIT_TOLERANCE = 3;
18104
+ var fitInstrumentOctave = (semitoneRange, instrument, wanted) => {
18105
+ const range = GM_INSTRUMENT_RANGE[instrument];
18106
+ if (!range || !semitoneRange) return wanted;
18107
+ const hi = Math.min(range[1], GM_BRIGHT_CEILING[instrument] ?? range[1]);
18108
+ let octave = wanted;
18109
+ while (octave > wanted - 2) {
18110
+ if (semitoneRange[1] + octave * 12 <= hi + OCTAVE_FIT_TOLERANCE) break;
18111
+ octave--;
18112
+ }
18113
+ return octave;
17190
18114
  };
17191
18115
 
17192
18116
  // src/macro-state.ts
@@ -17194,6 +18118,7 @@ var MACRO_STORAGE_KEYS = {
17194
18118
  template: "dtm-macro:template",
17195
18119
  sections: "dtm-macro:sections",
17196
18120
  key: "dtm-macro:key",
18121
+ scale: "dtm-macro:scale",
17197
18122
  shift: "dtm-macro:shift",
17198
18123
  transpose: "dtm-macro:transpose"
17199
18124
  };
@@ -19353,6 +20278,7 @@ var COMPOSE_INFO_HTML = `
19353
20278
  <li><strong>\u30E1\u30ED\u30C7\u30A3\u306E\u66F8\u6CD5</strong>\u2014\u2014\u8D70\u53E5\u306E\u5F62\uFF08\u97F3\u968E\uFF0F\u6298\u308A\u8FD4\u3057\uFF0F\u5206\u6563\u548C\u97F3\uFF0F\u30B8\u30B0\u30B6\u30B0\uFF09\u3001\u3064\u306A\u304E\u306E\u5F62\uFF08\u5C71\u306A\u308A\uFF0F\u8C37\uFF0F\u4E0A\u884C\uFF0F\u4E0B\u884C\uFF0F\u3046\u306D\u308A\uFF0F\u8EF8\u97F3\u307E\u308F\u308A\uFF09\u3001\u7D42\u6B62\u306E\u5F62\uFF08\u9806\u6B21\u4E0B\u964D\uFF0F\u30BD\u30DF\u30C9\uFF0F\u8DF3\u306D\u4E0A\u304C\u308A\uFF0F\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\uFF09\u3001\u30E2\u30C1\u30FC\u30D5\u306E\u539F\u578B\u3001\u8DF3\u8E8D\u306E\u6DF7\u305C\u5177\u5408\u3001\u958B\u59CB\u97F3\u3001\u57FA\u6E96\u306E\u523B\u307F\uFF088\u5206\uFF0F16\u5206\uFF0F\u4E09\u9023\uFF09\u3001\u30B9\u30A6\u30A3\u30F3\u30B0\u91CF</li>
19354
20279
  <li><strong>\u30D9\u30FC\u30B9\u306E\u594F\u6CD5</strong>\uFF084\u5206\u6253\u3061\uFF0F\u30AA\u30EB\u30BF\u30CD\u30A4\u30C8\uFF0F2\u5206\uFF0F8\u5206\u30C9\u30E9\u30A4\u30D6\uFF0F\u30B7\u30F3\u30B3\u30DA\uFF0F\u30A6\u30A9\u30FC\u30AD\u30F3\u30B0\uFF0F\u30AA\u30AF\u30BF\u30FC\u30D6\uFF09</li>
19355
20280
  <li><strong>\u30B5\u30D6\u30E1\u30ED\u306E\u66F8\u304D\u65B9</strong>\u2014\u2014\u5408\u3044\u306E\u624B\uFF08\u30E1\u30ED\u30C7\u30A3\u304C\u4F11\u3093\u3060\u9699\u9593\u306B\u3060\u3051\u5165\u308B\uFF09\u3001\u30CF\u30E2\u30EA\uFF08\u30E1\u30ED\u30C7\u30A3\u306E\u30EA\u30BA\u30E0\u3092\u306A\u305E\u3063\u30663\u5EA6\u30FB6\u5EA6\u4E0B\u3092\u6B4C\u3046\uFF09\u3001\u5BFE\u65CB\u5F8B\uFF088\u5206\u3067\u30E1\u30ED\u30C7\u30A3\u3068\u53CD\u884C\u3059\u308B\uFF09\u3001\u4FDD\u7D9A\u97F3\uFF08\u540C\u3058\u97F3\u3092\u4F38\u3070\u3057\u7D9A\u3051\u308B\uFF09\u3001\u30D1\u30C3\u30C9\u3002\u5C0F\u7BC0\u3054\u3068\u306B\u3001\u30E1\u30ED\u30C7\u30A3\u304C\u606F\u7D99\u304E\u3057\u3066\u3044\u308B\u5834\u6240\u3067\u306F\u81EA\u52D5\u3067\u5408\u3044\u306E\u624B\u306B\u5207\u308A\u66FF\u308F\u308A\u307E\u3059\u3002</li>
20281
+ <li><strong>\u7DE8\u66F2\u30D7\u30E9\u30F3</strong>\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306E\u307F\uFF09\u2014\u2014\u4F34\u594F\u3092\u4F55\u5C64\u306B\u3059\u308B\u304B\u3001\u305D\u308C\u305E\u308C\u3069\u306E\u594F\u6CD5\u3067\u3069\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u9CF4\u3089\u3059\u304B\u3001\u30B5\u30D3\u306E\u91CD\u306D\u3092\u51FA\u3059\u304B\u30FB\u30E6\u30CB\u30BE\u30F3\u304B\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u304B\u3001\u88C5\u98FE\u3092\u3069\u3053\u306B\u7F6E\u304F\u304B\u3002400\u66F2\u5F15\u304F\u3068392\u901A\u308A\u306E\u578B\u304C\u51FA\u307E\u3059\u3002</li>
19356
20282
  </ul>
19357
20283
  <p>\u3055\u3089\u306B\u3001\u76F4\u524D\u306B\u4F5C\u3063\u305F5\u66F2\u3068\u7279\u5FB4\u304C\u4F3C\u3066\u3044\u308B\u5019\u88DC\u306B\u306F\u6E1B\u70B9\u3057\u3066\u3044\u307E\u3059\u3002\u7D9A\u3051\u3066\u62BC\u3057\u305F\u3068\u304D\u306B\u4F3C\u305F\u66F2\u304C\u4E26\u3070\u306A\u3044\u3088\u3046\u306B\u3059\u308B\u305F\u3081\u3067\u3059\u3002</p>
19358
20284
  <h4>\u3067\u304D\u3042\u304C\u308A\u306E\u9078\u3073\u65B9</h4>
@@ -19371,6 +20297,18 @@ var COMPOSE_INFO_HTML = `
19371
20297
  <li><strong>\u639B\u3051\u5408\u3044\uFF08\u30C7\u30E5\u30A8\u30C3\u30C8\uFF09</strong> \u2014 4\u5272\u307B\u3069\u306E\u66F2\u304C2\u4EBA\u6B4C\u3044\u306B\u306A\u308A\u307E\u3059\u3002\u30BB\u30AF\u30B7\u30E7\u30F3\u3054\u3068\uFF0FA\u30E1\u30ED\u30672\u5C0F\u7BC0\u3054\u3068\uFF0F\u639B\u3051\u5408\u3044\u30B5\u30D3\uFF0FA\u30E1\u30ED\u3060\u3051\u3001\u306E4\u901A\u308A\u3002<strong>\u53D7\u3051\u6E21\u3057\u306F\u5C0F\u7BC0\u7DDA\u3074\u3063\u305F\u308A\u3067\u306F\u306A\u304F\u3001\u6B21\u306E\u4EBA\u304C\u624B\u524D\u304B\u3089\u98DF\u3044\u6C17\u5473\u306B\u5165\u308A\u307E\u3059\u3002</strong>\u4E00\u7DD2\u306B\u6B4C\u3046\u30B5\u30D3\u3067\u306F\u76F8\u65B9\u304C\u30CF\u30E2\u30EA\u3078\u56DE\u308A\u307E\u3059\uFF08\u540C\u3058\u97F3\u3092\u306A\u305E\u308B\u30E6\u30CB\u30BE\u30F3\u306B\u306F\u3057\u307E\u305B\u3093\uFF09\u3002</li>
19372
20298
  </ul>
19373
20299
  <p>\u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\uFF084\u30C8\u30E9\u30C3\u30AF\uFF09\u306F\u72EC\u5531\u3067\u3059\u30024\u672C\u304C\u57CB\u307E\u3063\u3066\u3044\u3066\u30CF\u30E2\u30EA\u306E\u7F6E\u304D\u5834\u6240\u304C\u7121\u3044\u305F\u3081\u3067\u3059\u3002</p>
20300
+ <h4>\u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u697D\u5668\u30FB\u4F34\u594F\u304C\u5909\u308F\u308B\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306E\u307F\uFF09</h4>
20301
+ <p>1\u672C\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3001\u66F2\u306E\u6700\u521D\u304B\u3089\u6700\u5F8C\u307E\u30671\u3064\u306E\u697D\u5668\u3067\u9CF4\u308A\u307E\u3059\u3002\u3064\u307E\u308A<strong>\u300C\u30B5\u30D3\u3060\u3051\u5225\u306E\u697D\u5668\u306B\u3059\u308B\u300D\u306F\u30011\u672C\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u4E2D\u3067\u306F\u4F5C\u308C\u307E\u305B\u3093</strong>\u3002\u4F5C\u308C\u308B\u306E\u306F\u3001\u9CF4\u3089\u3057\u305F\u3044\u533A\u9593\u3092\u5225\u306E\u30C8\u30E9\u30C3\u30AF\u3078\u66F8\u304D\u5206\u3051\u305F\u3068\u304D\u3060\u3051\u3067\u3059\u3002\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\uFF0815\u30C8\u30E9\u30C3\u30AF\uFF09\u304C15\u672C\u3042\u308B\u306E\u306F\u305D\u306E\u305F\u3081\u3067\u3001\u300C\u4F5C\u66F2\u300D\u306F\u5834\u6240\u3054\u3068\u306B\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u307E\u3059\u3002</p>
20302
+ <ul>
20303
+ <li><strong>\u4F34\u594F\u306E\u624B\u89E6\u308A\u304C\u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u5909\u308F\u308B</strong> \u2014 \u540C\u3058\u30B3\u30FC\u30C9\u9032\u884C\u3092\u3001\u30D6\u30ED\u30C3\u30AF\u30FB\u30A2\u30EB\u30DA\u30B8\u30AA\u30FB\u88CF\u62CD\u30FB\u516B\u5206\u98DF\u3044\u30FB\u5206\u6563\u306E\u4E2D\u304B\u3089<strong>\u5225\u3005\u306E\u594F\u6CD5</strong>\u30671\u301C3\u5C64\u306B\u91CD\u306D\u307E\u3059\u3002\u3069\u306E\u594F\u6CD5\u3092\u3069\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u9CF4\u3089\u3059\u304B\u306F\u66F2\u3054\u3068\u306B\u5F15\u304F\u306E\u3067\u3001A\u30E1\u30ED\u3068\u30B5\u30D3\u3067\u4F34\u594F\u306E\u523B\u307F\u65B9\u305D\u306E\u3082\u306E\u304C\u5909\u308F\u308A\u307E\u3059\u3002\u66F2\u5168\u4F53\u3092\u901A\u308B\u300C\u5730\u300D\u306E\u5C64\u304C1\u3064\u3042\u308A\u3001\u6B8B\u308A\u306F\u8DB3\u3059\u5834\u6240\u3092\u7D5E\u308A\u307E\u3059\u3002</li>
20304
+ <li><strong>\u30B5\u30D3\u306E\u91CD\u306D</strong> \u2014 \u4E3B\u65CB\u5F8B\u3092\u3082\u30461\u672C\u306E\u5225\u697D\u5668\uFF08\u30D7\u30EA\u30BB\u30C3\u30C8\u306B\u3088\u3063\u3066\u30D6\u30E9\u30B9\u30FB\u30B9\u30C8\u30EA\u30F3\u30B0\u30B9\u30FB\u30B0\u30ED\u30C3\u30B1\u30F3\u306A\u3069\uFF09\u3067\u91CD\u306D\u307E\u3059\u3002<strong>\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3068\u30E6\u30CB\u30BE\u30F3\u306E\u4E21\u65B9\u3092\u5F15\u304D\u307E\u3059</strong>\u2014\u2014\u540C\u3058\u9AD8\u3055\u3092\u5225\u306E\u697D\u5668\u3067\u91CD\u306D\u308B\u30682\u3064\u306E\u97F3\u8272\u304C\u6EB6\u3051\u3066\u5225\u306E\u97F3\u8272\u306B\u306A\u308B\u306E\u3067\u3001\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B\u3088\u308A\u60C5\u5831\u91CF\u304C\u591A\u3044\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002\u91CD\u306D\u306A\u3044\u66F2\u3082\u3042\u308A\u307E\u3059\u3002</li>
20305
+ <li><strong>\u9593\u594F\u306E\u30BD\u30ED</strong> \u2014 \u9593\u594F\u306F\u6B4C\u304C\u4F11\u3080\u5834\u6240\u3067\u3042\u3063\u3066\u3001\u97F3\u697D\u304C\u4F11\u3080\u5834\u6240\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u6B4C\u30E1\u30ED\u306E\u4EE3\u308F\u308A\u306B\u5668\u697D\u306E\u30BD\u30ED\u3092\u66F8\u304D\u3001\u5C02\u7528\u306E\u30C8\u30E9\u30C3\u30AF\u3067\u9CF4\u3089\u3057\u307E\u3059\u3002\u30D7\u30EA\u30BB\u30C3\u30C8\u306B\u3088\u3063\u3066\u6B6A\u307F\u30AE\u30BF\u30FC\u30FB\u30B5\u30C3\u30AF\u30B9\u30FB\u5C3A\u516B\u306A\u3069\u306B\u5909\u308F\u308A\u307E\u3059\u3002\u7D20\u6750\u306F\u30B5\u30D3\u3068\u540C\u3058\u306A\u306E\u3067\u3001\u9593\u594F\u304C\u30B5\u30D3\u306E\u4E3B\u984C\u3092\u5F3E\u304F\u5F62\u306B\u306A\u308A\u307E\u3059\u3002</li>
20306
+ <li><strong>\u30A6\u30EF\u30E2\u30CE</strong> \u2014 \u304D\u3089\u3073\u3084\u304B\u306A\u88C5\u98FE\u3092\u3001\u76DB\u308A\u4E0A\u304C\u308B\u30BB\u30AF\u30B7\u30E7\u30F3\u306B\u3060\u3051\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3067\u8DB3\u3057\u307E\u3059\u3002<strong>\u5730\u306E\u4F34\u594F\u3068\u540C\u3058\u594F\u6CD5\u306F\u4F7F\u3044\u307E\u305B\u3093</strong>\u2014\u2014\u540C\u3058\u3082\u306E\u3092\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u305F\u3060\u3051\u306E\u5C64\u306F\u88C5\u98FE\u3067\u306F\u306A\u304F\u5199\u3057\u3060\u304B\u3089\u3067\u3059\u3002\u30D6\u30ED\u30C3\u30AF\u3082\u5916\u3057\u307E\u3059\uFF08\u548C\u97F3\u3092\u4E38\u3054\u3068\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3067\u9CF4\u3089\u3059\u306E\u306F\u88C5\u98FE\u3067\u306F\u306A\u304F\u58C1\u306B\u306A\u308A\u307E\u3059\uFF09\u3002</li>
20307
+ </ul>
20308
+ <p><strong>\u697D\u5668\u306E\u97F3\u57DF\u306B\u5408\u308F\u305B\u3066\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u4E0B\u3052\u307E\u3059\u3002</strong>1\u30C8\u30E9\u30C3\u30AF1\u697D\u5668\u3067\u3001\u3057\u304B\u3082\u5C64\u3054\u3068\u306B\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u5909\u3048\u308B\u306E\u3067\u3001\u6C7A\u3081\u305F\u30AA\u30AF\u30BF\u30FC\u30D6\u304C\u305D\u306E\u697D\u5668\u306E\u51FA\u305B\u306A\u3044\u9AD8\u3055\u306B\u306A\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002\u5B9F\u6E2C\u3059\u308B\u3068\u3001\u30B5\u30D3\u306E\u91CD\u306D\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B\u6307\u5B9A\u306F\u30DF\u30E5\u30FC\u30C8\u30C8\u30E9\u30F3\u30DA\u30C3\u30C8\u3084\u30C8\u30E9\u30F3\u30DA\u30C3\u30C8\u306714\u534A\u97F3\u3076\u3093\u3001\u30B3\u30FC\u30C9\u30D1\u30C3\u30C9\uFF08\u5B9F\u97F377\u301C93\uFF09\u306F\u30CA\u30A4\u30ED\u30F3\u30AE\u30BF\u30FC\u306710\u534A\u97F3\u30FB\u30AB\u30EA\u30F3\u30D0\u30679\u534A\u97F3\u3076\u3093\u97F3\u57DF\u3092\u7A81\u304D\u629C\u3051\u3066\u3044\u307E\u3057\u305F\u3002\u30B5\u30F3\u30D7\u30EB\u304C\u5F15\u304D\u4F38\u3070\u3055\u308C\u3066<strong>\u91D1\u5207\u308A\u97F3</strong>\u306B\u306A\u308A\u3001\u8074\u304D\u624B\u306B\u306F\u300C\u8033\u304C\u75DB\u3044\u300D\u3068\u3057\u304B\u611F\u3058\u3089\u308C\u307E\u305B\u3093\u3002\u305D\u3053\u3067\u3001\u9CF4\u3089\u3059\u97F3\u57DF\u304C\u305D\u306E\u697D\u5668\u306E\u5B9F\u7528\u4E0A\u9650\u306B\u53CE\u307E\u308B\u3068\u3053\u308D\u307E\u3067<strong>\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u4E0B\u3052\u3066\u304B\u3089</strong>\u7F6E\u304D\u307E\u3059\uFF08\u30C8\u30E9\u30C3\u30AF\u306E\u30AA\u30AF\u30BF\u30FC\u30D6\u8A2D\u5B9A\u306F\u3001\u307E\u3055\u306B\u3053\u3046\u3044\u3046\u300C\u5F97\u610F\u306A\u97F3\u57DF\u304C\u504F\u3063\u305F\u97F3\u6E90\u300D\u3092\u4F7F\u3048\u308B\u3088\u3046\u306B\u3059\u308B\u305F\u3081\u306B\u5728\u308B\u3082\u306E\u3067\u3059\uFF09\u3002<strong>\u4E0B\u3052\u308B\u65B9\u5411\u306B\u3057\u304B\u52D5\u304B\u3057\u307E\u305B\u3093</strong>\u2014\u2014\u4E0A\u3052\u308B\u5074\u304C\u75DB\u307F\u3092\u4F5C\u308B\u5074\u306A\u306E\u3067\u3002</p>
20309
+ <p>\u5B9F\u7269\u306E\u97F3\u57DF\u306B\u53CE\u307E\u3063\u3066\u3044\u3066\u3082\u75DB\u304F\u306A\u308B\u97F3\u8272\u304C\u3042\u308A\u307E\u3059\u3002\u30B0\u30ED\u30C3\u30B1\u30F3\u306F\u5B9F\u7269\u306E\u97F3\u57DF\u304CG5\u301CC8\u306A\u306E\u3067\u3001\u65CB\u5F8B\u306E\u97F3\u57DF\uFF08\u301CC6\uFF09\u306F\u300C\u4F59\u88D5\u3067\u7BC4\u56F2\u5185\u300D\u3067\u3059\u304C\u3001\u91D1\u5C5E\u4F53\u306E\u500D\u97F3\u306F\u4EBA\u306E\u8033\u304C\u3044\u3061\u3070\u3093\u654F\u611F\u306A2\u301C4kHz\u306B\u96C6\u307E\u308B\u305F\u3081\u3001\u305D\u306E\u9AD8\u3055\u3067\u9CF4\u3089\u3057\u7D9A\u3051\u308B\u3068\u523A\u3055\u308A\u307E\u3059\u3002\u3053\u3046\u3044\u3046\u97F3\u8272\u306B\u306F\u97F3\u57DF\u3068\u306F\u5225\u306B<strong>\u660E\u308B\u3055\u306E\u4E0A\u9650</strong>\u3092\u6301\u305F\u305B\u3066\u3042\u308A\u3001\u30B0\u30ED\u30C3\u30B1\u30F3\u306A\u3089C5\u3088\u308A\u4E0A\u3067\u9CF4\u3089\u306A\u3044\u3068\u3053\u308D\u307E\u3067\u4E0B\u3052\u307E\u3059\u3002<strong>\u5019\u88DC\u304B\u3089\u5916\u3059\u306E\u3067\u306F\u306A\u304F\u7F6E\u304D\u5834\u6240\u3092\u5909\u3048\u307E\u3059</strong>\u2014\u2014\u5916\u3059\u3068\u97F3\u8272\u306E\u5E45\u304C\u305D\u306E\u3076\u3093\u6E1B\u308B\u3060\u3051\u3067\u3001\u4F4E\u304F\u9CF4\u3089\u3057\u305F\u30B0\u30ED\u30C3\u30B1\u30F3\u306F\u30DD\u30C3\u30D7\u30B9\u3067\u666E\u901A\u306B\u4F7F\u308F\u308C\u308B\u67D4\u3089\u304B\u3044\u97F3\u3067\u3059\u3002</p>
20310
+ <p><strong>\u30AA\u30AF\u30BF\u30FC\u30D6\u306E\u91CD\u306D\u3092\u4E3B\u5F79\u306B\u3057\u3066\u3044\u307E\u305B\u3093\u3002</strong>\u300C\u65E2\u306B\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u52D5\u304B\u3057\u3066\u5225\u30C8\u30E9\u30C3\u30AF\u3078\u5199\u3059\u300D\u5C64\u306F\u3001\u97F3\u697D\u7684\u306A\u4FA1\u5024\u304C\u9AD8\u304F\u3042\u308A\u307E\u305B\u3093\u3002\u4EBA\u306E\u8033\u306F\u30AA\u30AF\u30BF\u30FC\u30D6\u9055\u3044\u3092<strong>\u540C\u3058\u97F3</strong>\u3068\u3057\u3066\u805E\u304F\u306E\u3067\uFF08\u30AA\u30AF\u30BF\u30FC\u30D6\u7B49\u4FA1\uFF09\u3001\u5199\u3057\u305F\u5C64\u306F\u65B0\u3057\u3044\u58F0\u90E8\u306B\u306A\u3089\u305A\u3001\u97F3\u91CF\u3068\u97F3\u8272\u304C\u308F\u305A\u304B\u306B\u5909\u308F\u308B\u3060\u3051\u3067\u3059\u3002\u5F37\u8ABF\u3068\u3057\u3066\u306E\u610F\u5473\u306F\u3042\u308B\u306E\u3067\u4F7F\u3044\u306F\u3057\u307E\u3059\u304C\u3001\u5E38\u8A2D\u306B\u306F\u3057\u307E\u305B\u3093\u3002\u30D9\u30FC\u30B9\u306E\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\u306E\u91CD\u306D\u306F\u7279\u306B\u300130Hz\u524D\u5F8C\u307E\u3067\u843D\u3061\u3066\u8F2A\u90ED\u304C\u6FC1\u308B\u306E\u3067\u65E2\u5B9A\u3067\u306F\u51FA\u3057\u307E\u305B\u3093\uFF08\u51FA\u3059\u3068\u304D\u3082\u4E0A\u306E\u30AA\u30AF\u30BF\u30FC\u30D6\u3078\u3001\u76DB\u308A\u4E0A\u304C\u308B\u5834\u6240\u3060\u3051\u306B\u7F6E\u304D\u307E\u3059\uFF09\u3002</p>
20311
+ <p>\u300C\u4F5C\u66F2\u300D\u304C\u6C7A\u3081\u305F\u3053\u308C\u3089\u306E\u697D\u5668\u306F\u3001\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u306E\u5F79\u5272\u63A8\u5B9A\u3088\u308A\u512A\u5148\u3055\u308C\u307E\u3059\uFF08\u6F14\u594F\u5185\u5BB9\u3060\u3051\u3092\u898B\u308B\u3068\u3001\u9593\u594F\u306E\u30BD\u30ED\u3082\u30B5\u30D3\u306E\u91CD\u306D\u3082\u300C\u97F3\u306E\u5C11\u306A\u3044\u5358\u65CB\u5F8B\u300D\u3067\u3001\u4E3B\u65CB\u5F8B\u3068\u533A\u5225\u304C\u4ED8\u304B\u306A\u3044\u305F\u3081\u3067\u3059\uFF09\u3002\u697D\u5668\u3092\u624B\u3067\u9078\u3073\u76F4\u3057\u305F\u30C8\u30E9\u30C3\u30AF\u306F\u3001\u4EE5\u5F8C\u3069\u3061\u3089\u306B\u3082\u4E0A\u66F8\u304D\u3055\u308C\u307E\u305B\u3093\u3002</p>
19374
20312
  <h4>\u305D\u306E\u307B\u304B</h4>
19375
20313
  <ul>
19376
20314
  <li><strong>\u66F2\u306E\u9014\u4E2D\u3067\u8EE2\u8ABF\u3057\u307E\u3059</strong>\uFF08\u304A\u3088\u305D\u534A\u5206\u306E\u66F2\uFF09\u3002\u4E94\u5EA6\u570F\u3067\u8FD1\u3044\u5C5E\u8ABF\u30FB\u4E0B\u5C5E\u8ABF\u3078\u306F\u5171\u901A\u3059\u308B\u548C\u97F3\uFF08\u30D4\u30DC\u30C3\u30C8\u30B3\u30FC\u30C9\uFF09\u304B\u65B0\u3057\u3044\u8ABF\u306E\u30C9\u30DF\u30CA\u30F3\u30C8\u3067\u6A4B\u6E21\u3057\u3057\u3001\u30E9\u30B9\u30B5\u30D3\u306E\u534A\u97F3\u4E0A\u3052\u306F\u6E96\u5099\u306A\u3057\u306E\u76F4\u63A5\u8EE2\u8ABF\u306B\u3057\u307E\u3059\u3002\u8ABF\u53F7\u3092\u5909\u3048\u305A\u306B\u660E\u6697\u3060\u3051\u5165\u308C\u66FF\u3048\u308B<strong>\u5E73\u884C\u8ABF</strong>\uFF08\u30CF\u9577\u8ABF\u2194\u30A4\u77ED\u8ABF\uFF09\u3068\u3001\u4E3B\u97F3\u3092\u4FDD\u3063\u305F\u307E\u307E\u6697\u304F\u3059\u308B<strong>\u540C\u4E3B\u8ABF</strong>\uFF08\u30CF\u9577\u8ABF\u2192\u30CF\u77ED\u8ABF\uFF09\u3082\u5F15\u304D\u307E\u3059\u30021\u5272\u5F37\u306E\u66F2\u306F\u4E3B\u548C\u97F3\u3092\u907F\u3051\u3066\u300C\u660E\u308B\u3044\u306E\u304B\u6697\u3044\u306E\u304B\u5206\u304B\u3089\u306A\u3044\u300D\u6D6E\u904A\u611F\u3067\u901A\u3057\u307E\u3059\u3002</li>
@@ -19637,26 +20575,128 @@ var pickComposeVocal = (exclude) => {
19637
20575
  const list = pool.length > 0 ? pool : COMPOSE_VOCAL_POOL;
19638
20576
  return list[Math.floor(Math.random() * list.length)] ?? "klatt";
19639
20577
  };
19640
- var ADVANCED_COMPOSE_LAYOUT = [
19641
- { index: 0, part: "melody", octave: 0, volume: 104 },
19642
- { index: 1, part: "melody", octave: 1, volume: 62 },
19643
- { index: 2, part: "harmony", octave: 0, volume: 82 },
19644
- { index: 3, part: "submelody", octave: 0, volume: 86 },
19645
- { index: 4, part: "bass", octave: 0, volume: 92 },
19646
- { index: 5, part: "bass", octave: -1, volume: 58 },
19647
- { index: 6, part: "pad", octave: 0, volume: 64 },
19648
- { index: 7, part: "block", octave: 0, volume: 62 },
19649
- { index: 8, part: "arpeggio", octave: 0, volume: 54 },
19650
- { index: 9, part: "offbeat", octave: 0, volume: 50 },
19651
- { index: 10, part: "uwamono", octave: 1, volume: 56 },
19652
- // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
19653
- // 作曲だけならメロディは t0 が全部持つので、ここは空のまま。
19654
- { index: 11, part: "duet", octave: 0, volume: 104 },
19655
- // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
19656
- // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
19657
- { index: 12, part: "harmony2", octave: 0, volume: 74 },
19658
- { index: 13, part: "melody", octave: -1, volume: 56 }
19659
- ];
20578
+ var buildAdvancedLayers = (song, config) => {
20579
+ const { edo, stepsPerBar, preset } = config;
20580
+ const semitoneRange = (notes) => {
20581
+ if (notes.length === 0) return null;
20582
+ let lo = Number.POSITIVE_INFINITY;
20583
+ let hi = Number.NEGATIVE_INFINITY;
20584
+ for (const n of notes) {
20585
+ const semi = n.pitchUnits / UNITS_PER_SEMITONE;
20586
+ if (semi < lo) lo = semi;
20587
+ if (semi > hi) hi = semi;
20588
+ }
20589
+ return [Math.round(lo), Math.round(hi)];
20590
+ };
20591
+ const fit = (notes, slot, wanted) => fitInstrumentOctave(semitoneRange(notes), preset[slot], wanted);
20592
+ const kindAtBar = (bar) => song.sections.find(
20593
+ (sec) => bar >= sec.startBar && bar < sec.startBar + sec.bars
20594
+ )?.kind ?? null;
20595
+ const onlyIn = (notes, kinds) => {
20596
+ if (!kinds) return notes;
20597
+ const want = new Set(kinds);
20598
+ return notes.filter((n) => {
20599
+ const kind = kindAtBar(Math.floor(n.startStep / stepsPerBar));
20600
+ return kind !== null && want.has(kind);
20601
+ });
20602
+ };
20603
+ const chordNotes = (pattern, velocityShift = 0) => buildChordPlacements({
20604
+ edo,
20605
+ chordStr: song.chordProgression,
20606
+ patternType: pattern,
20607
+ rootShift: song.rootShift,
20608
+ bpm: song.bpm,
20609
+ stepsPerBar
20610
+ }).map((p) => ({
20611
+ startStep: p.startStep,
20612
+ pitchUnits: p.pitchUnits,
20613
+ durationSteps: p.durationSteps,
20614
+ velocity: Math.max(30, p.velocity + velocityShift)
20615
+ }));
20616
+ const plan = song.arrange;
20617
+ const leadNotes = plan.lead ? onlyIn(song.melody, plan.lead.sections) : [];
20618
+ const leadOctave = fit(leadNotes, "chorusLead", plan.lead?.octave ?? 0);
20619
+ const leadLayer = {
20620
+ index: 1,
20621
+ notes: leadNotes,
20622
+ octave: leadOctave,
20623
+ // ユニゾンで重ねるときは、オクターブ上より前に出やすいので少し引く。
20624
+ volume: leadOctave === 0 ? 54 : 62,
20625
+ slot: "chorusLead"
20626
+ };
20627
+ const bassNotes = plan.bassLayer ? onlyIn(song.bass, plan.bassLayer.sections) : [];
20628
+ const bassOctave = fit(bassNotes, "bass", plan.bassLayer?.octave ?? 0);
20629
+ const bassLayer = {
20630
+ index: 5,
20631
+ notes: bassOctave === 0 ? [] : bassNotes,
20632
+ octave: bassOctave,
20633
+ volume: 58,
20634
+ slot: "bass"
20635
+ };
20636
+ const padNotes = onlyIn(song.pad, plan.padSections);
20637
+ const layers = [
20638
+ { index: 0, notes: song.melody, octave: 0, volume: 104, slot: "melody" },
20639
+ leadLayer,
20640
+ { index: 2, notes: song.harmony, octave: 0, volume: 82 },
20641
+ {
20642
+ index: 3,
20643
+ notes: song.submelody,
20644
+ octave: 0,
20645
+ volume: 86,
20646
+ slot: "submelody"
20647
+ },
20648
+ { index: 4, notes: song.bass, octave: 0, volume: 92, slot: "bass" },
20649
+ bassLayer,
20650
+ // **パッドは伴奏用の楽器で、伴奏より1.5オクターブ高いところを鳴らす。**
20651
+ // そのまま置くとナイロンギターで10半音、カリンバで9半音、尺八で7半音ぶん
20652
+ // 音域を突き抜ける。楽器に合わせて下げる。
20653
+ {
20654
+ index: 6,
20655
+ notes: padNotes,
20656
+ octave: fit(padNotes, "chord", 0),
20657
+ volume: 64,
20658
+ slot: "chord"
20659
+ }
20660
+ ];
20661
+ const backingVolumes = [62, 54, 50];
20662
+ for (let i2 = 0; i2 < 3; i2++) {
20663
+ const layer = plan.backing[i2];
20664
+ layers.push({
20665
+ index: 7 + i2,
20666
+ notes: layer ? onlyIn(chordNotes(layer.pattern), layer.sections) : [],
20667
+ octave: layer?.octave ?? 0,
20668
+ volume: backingVolumes[i2],
20669
+ slot: "chord"
20670
+ });
20671
+ }
20672
+ const sparkleNotes = plan.sparkle ? onlyIn(chordNotes(plan.sparkle.pattern, -14), plan.sparkle.sections) : [];
20673
+ layers.push(
20674
+ {
20675
+ index: 10,
20676
+ notes: sparkleNotes,
20677
+ octave: fit(sparkleNotes, "chord", plan.sparkle?.octave ?? 0),
20678
+ volume: 56,
20679
+ slot: "chord"
20680
+ },
20681
+ // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
20682
+ { index: 11, notes: [], octave: 0, volume: 104, slot: "melody" },
20683
+ // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
20684
+ // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
20685
+ { index: 12, notes: song.harmony2, octave: 0, volume: 74 },
20686
+ { index: 13, notes: song.octave, octave: -1, volume: 56, slot: "melody" },
20687
+ // **間奏のソロ。** 音が入るのは間奏の小節だけなので、この1本だけを
20688
+ // 別の楽器にしても他のセクションの鳴りは変わらない。歌の音域をそのまま
20689
+ // 渡すと管楽器が上へ抜ける(テナーサックスで10半音)ので、ここも合わせる。
20690
+ {
20691
+ index: 14,
20692
+ notes: song.solo,
20693
+ octave: fit(song.solo, "solo", 0),
20694
+ volume: 100,
20695
+ slot: "solo"
20696
+ }
20697
+ );
20698
+ return layers;
20699
+ };
19660
20700
  var LYRIC_MODEL_CATEGORIES = [
19661
20701
  {
19662
20702
  label: "kusa\u30D7\u30EA\u30BB\u30C3\u30C8",
@@ -19812,7 +20852,7 @@ var mountDAW = (target, options = {}) => {
19812
20852
  showChord,
19813
20853
  showMidiSearch,
19814
20854
  // 「作曲」は simple では役割固定の4トラックへ、advanced では15トラックへ
19815
- // 編曲を展開する({@link ADVANCED_COMPOSE_LAYOUT})。どちらでも出す。
20855
+ // 編曲を展開する({@link buildAdvancedLayers})。どちらでも出す。
19816
20856
  showCompose: true
19817
20857
  });
19818
20858
  refs.masterVolume.value = String(options.masterVolume ?? 50);
@@ -20006,6 +21046,14 @@ var mountDAW = (target, options = {}) => {
20006
21046
  while (customVocalsMap.has(`custom${n}`)) n++;
20007
21047
  return `custom${n}`;
20008
21048
  };
21049
+ const getVocalIconSrc = (lyricModel) => {
21050
+ if (!lyricModel) return void 0;
21051
+ const customDef = customVocalsMap.get(lyricModel);
21052
+ if (customDef !== void 0)
21053
+ return customDef.iconUrl || FALLBACK_VOCAL_ICON;
21054
+ const imgKey = VOICE_IMAGE_KEY[lyricModel.toLowerCase()];
21055
+ return imgKey ? VOICE_IMAGES[imgKey] : void 0;
21056
+ };
20009
21057
  let selectedNotes = [];
20010
21058
  let selectionRect = null;
20011
21059
  let copiedNotes = [];
@@ -20139,6 +21187,7 @@ var mountDAW = (target, options = {}) => {
20139
21187
  vocalTension: t1?.vocalTension ?? 50,
20140
21188
  vocalOctaveUnison: t1?.vocalOctaveUnison ?? "none",
20141
21189
  trackInstrument: t1?.trackInstrument ?? "",
21190
+ composeSlot: null,
20142
21191
  trackCompression: t1?.trackCompression ?? 0,
20143
21192
  trackWidth: t1?.trackWidth ?? 100,
20144
21193
  trackReverbSend: t1?.trackReverbSend ?? 0,
@@ -21190,25 +22239,38 @@ var mountDAW = (target, options = {}) => {
21190
22239
  refs.undoBtn.disabled = !core.canUndo();
21191
22240
  refs.redoBtn.disabled = !core.canRedo();
21192
22241
  };
21193
- const updateTrackPanel = () => {
22242
+ const updateTrackTabs = () => {
21194
22243
  refs.trackTabs.innerHTML = "";
21195
22244
  trackPillEls.clear();
21196
22245
  for (const [i2, t] of trackStates.entries()) {
21197
22246
  const [r, g, b] = t.config.color;
21198
22247
  const isActive = t.config.id === activeTrackId;
21199
22248
  const btn = document.createElement("button");
21200
- btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""}`;
22249
+ const vocalIconSrc = getVocalIconSrc(t.lyricModel);
22250
+ btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""} ${vocalIconSrc ? "dtm-pill--vocal" : ""}`;
21201
22251
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
22252
+ if (vocalIconSrc) {
22253
+ btn.style.setProperty(
22254
+ "--dtm-pill-icon",
22255
+ `url(${JSON.stringify(vocalIconSrc)})`
22256
+ );
22257
+ }
21202
22258
  btn.title = `Track ${i2 + 1}: ${t.config.name}`;
21203
22259
  btn.setAttribute(
21204
22260
  "aria-label",
21205
- `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}`
22261
+ `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}${vocalIconSrc ? "\uFF08\u30DC\u30FC\u30AB\u30EB\u9078\u629E\u4E2D\uFF09" : ""}`
21206
22262
  );
21207
- btn.textContent = String(i2 + 1);
22263
+ const labelEl = document.createElement("span");
22264
+ labelEl.className = "dtm-pill__label";
22265
+ labelEl.textContent = String(i2 + 1);
22266
+ btn.appendChild(labelEl);
21208
22267
  btn.addEventListener("click", () => switchTrack(t.config.id));
21209
22268
  refs.trackTabs.appendChild(btn);
21210
22269
  trackPillEls.set(t.config.id, btn);
21211
22270
  }
22271
+ };
22272
+ const updateTrackPanel = () => {
22273
+ updateTrackTabs();
21212
22274
  const active = getActive();
21213
22275
  const activeIndex = trackStates.findIndex(
21214
22276
  (t) => t.config.id === activeTrackId
@@ -21525,6 +22587,7 @@ var mountDAW = (target, options = {}) => {
21525
22587
  syncInstDisabled();
21526
22588
  instSel.addEventListener("change", () => {
21527
22589
  active.trackInstrument = instSel.value;
22590
+ active.composeSlot = null;
21528
22591
  const trackIndex = trackStates.indexOf(active);
21529
22592
  options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
21530
22593
  persistTrack1(active);
@@ -21928,6 +22991,7 @@ var mountDAW = (target, options = {}) => {
21928
22991
  redrawAll();
21929
22992
  fireLyricsChange(active);
21930
22993
  reloadVoicesForModel(active.lyricModel);
22994
+ updateTrackTabs();
21931
22995
  });
21932
22996
  lyricCustomGuide.addEventListener("click", () => {
21933
22997
  showModal("\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)\u306E\u4F7F\u3044\u65B9", KOE_INFO_HTML);
@@ -22461,6 +23525,7 @@ var mountDAW = (target, options = {}) => {
22461
23525
  }
22462
23526
  trackStates.forEach((t, i2) => {
22463
23527
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
23528
+ t.composeSlot = null;
22464
23529
  const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
22465
23530
  if (t.trackInstrument !== name) {
22466
23531
  t.trackInstrument = name;
@@ -22712,7 +23777,7 @@ var mountDAW = (target, options = {}) => {
22712
23777
  const p = programOfInstrumentName(t.trackInstrument);
22713
23778
  if (p !== null) program = p;
22714
23779
  } else if (!t.lyricModel) {
22715
- const role = DECLARED_ROLE[t.config.id] ?? "melody";
23780
+ const role = t.composeSlot ?? DECLARED_ROLE[t.config.id] ?? "melody";
22716
23781
  const instName = autoPreset[role] ?? autoPreset.melody;
22717
23782
  const p = programOfInstrumentName(instName);
22718
23783
  if (p !== null) program = p;
@@ -23081,7 +24146,7 @@ var mountDAW = (target, options = {}) => {
23081
24146
  if (!stats) continue;
23082
24147
  const role = DECLARED_ROLE[t.config.id] ?? classifyTrackRole(stats, t.config.id === topSingleVoiceId);
23083
24148
  roleByTrackId.set(t.config.id, role);
23084
- const instName = autoPreset[role];
24149
+ const instName = autoPreset[t.composeSlot ?? role] ?? autoPreset[role];
23085
24150
  t.trackInstrument = instName;
23086
24151
  const trackIndex = trackStates.indexOf(t);
23087
24152
  options.onTrackInstrumentChange?.(trackIndex, instName);
@@ -23232,6 +24297,15 @@ var mountDAW = (target, options = {}) => {
23232
24297
  refs.composeKey.value = savedKey;
23233
24298
  }
23234
24299
  }
24300
+ const savedScale = readMacroSetting("scale");
24301
+ if (savedScale && refs.composeScale) {
24302
+ const hasOption = Array.from(refs.composeScale.options).some(
24303
+ (opt) => opt.value === savedScale
24304
+ );
24305
+ if (hasOption) {
24306
+ refs.composeScale.value = savedScale;
24307
+ }
24308
+ }
23235
24309
  const savedShift = readMacroSetting("shift");
23236
24310
  if (savedShift && refs.shiftSelect) {
23237
24311
  const hasOption = Array.from(refs.shiftSelect.options).some(
@@ -23294,6 +24368,20 @@ var mountDAW = (target, options = {}) => {
23294
24368
  });
23295
24369
  updateComposeKeyHint();
23296
24370
  }
24371
+ const updateComposeScaleHint = () => {
24372
+ if (!refs.composeScale || !refs.composeScaleHint) return;
24373
+ const desc = getComposeScaleDescription(refs.composeScale.value);
24374
+ refs.composeScaleHint.textContent = desc;
24375
+ refs.composeScaleHint.title = desc;
24376
+ };
24377
+ const composeScale = refs.composeScale;
24378
+ if (composeScale) {
24379
+ composeScale.addEventListener("change", () => {
24380
+ writeMacroSetting("scale", composeScale.value);
24381
+ updateComposeScaleHint();
24382
+ });
24383
+ updateComposeScaleHint();
24384
+ }
23297
24385
  if (refs.shiftSelect) {
23298
24386
  refs.shiftSelect.addEventListener("change", () => {
23299
24387
  writeMacroSetting("shift", refs.shiftSelect.value);
@@ -23314,6 +24402,7 @@ var mountDAW = (target, options = {}) => {
23314
24402
  sections: selectedComposeSections(),
23315
24403
  template: tmpl,
23316
24404
  baseKey: refs.composeKey?.value ?? "any",
24405
+ scale: refs.composeScale?.value ?? "auto",
23317
24406
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
23318
24407
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
23319
24408
  recent: recentComposeFingerprints
@@ -23347,45 +24436,39 @@ var mountDAW = (target, options = {}) => {
23347
24436
  }
23348
24437
  track.core.endBatch();
23349
24438
  };
24439
+ const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
24440
+ if (shouldAutoInstrument && song.instrument) {
24441
+ currentInstrument = song.instrument;
24442
+ autoComposeInstrument = song.instrument;
24443
+ options.onInstrumentChange?.(song.instrument);
24444
+ }
23350
24445
  if (isAdvanced) {
23351
- for (const layer of ADVANCED_COMPOSE_LAYOUT) {
24446
+ const layers = buildAdvancedLayers(song, {
24447
+ edo: renderConfig.edo,
24448
+ stepsPerBar: renderConfig.stepsPerBar,
24449
+ preset: INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano
24450
+ });
24451
+ for (const layer of layers) {
23352
24452
  const track = trackStates[layer.index];
23353
24453
  if (!track) continue;
23354
- const notes = layer.part === "duet" ? [] : layer.part === "harmony2" ? song.harmony2 : layer.index === 13 ? song.octave : layer.part === "melody" ? song.melody : layer.part === "harmony" ? song.harmony : layer.part === "submelody" ? song.submelody : layer.part === "bass" ? song.bass : layer.part === "pad" ? song.pad : layer.part === "uwamono" ? buildChordPlacements({
23355
- edo: renderConfig.edo,
23356
- chordStr: song.chordProgression,
23357
- patternType: "arpeggio",
23358
- rootShift: song.rootShift,
23359
- bpm: song.bpm,
23360
- stepsPerBar: renderConfig.stepsPerBar
23361
- }).map((p) => ({
23362
- startStep: p.startStep,
23363
- pitchUnits: p.pitchUnits,
23364
- durationSteps: p.durationSteps,
23365
- velocity: Math.max(30, p.velocity - 14)
23366
- })) : buildChordPlacements({
23367
- edo: renderConfig.edo,
23368
- chordStr: song.chordProgression,
23369
- patternType: layer.part,
23370
- rootShift: song.rootShift,
23371
- bpm: song.bpm,
23372
- stepsPerBar: renderConfig.stepsPerBar
23373
- }).map((p) => ({
23374
- startStep: p.startStep,
23375
- pitchUnits: p.pitchUnits,
23376
- durationSteps: p.durationSteps,
23377
- velocity: p.velocity
23378
- }));
23379
- writeTrackAt(layer.index, notes);
24454
+ writeTrackAt(layer.index, layer.notes);
23380
24455
  track.trackOctave = layer.octave;
23381
24456
  track.volume = layer.volume;
23382
24457
  track.core.setVolume(layer.volume);
24458
+ track.composeSlot = layer.notes.length > 0 ? layer.slot ?? null : null;
24459
+ if (layer.notes.length === 0 && track.trackInstrument) {
24460
+ track.trackInstrument = "";
24461
+ options.onTrackInstrumentChange?.(layer.index, "");
24462
+ }
23383
24463
  }
23384
24464
  for (let i2 = 0; i2 < trackStates.length; i2++) {
23385
- if (ADVANCED_COMPOSE_LAYOUT.some((l) => l.index === i2)) continue;
24465
+ if (layers.some((l) => l.index === i2)) continue;
23386
24466
  writeTrackAt(i2, []);
24467
+ const t = trackStates[i2];
24468
+ if (t) t.composeSlot = null;
23387
24469
  }
23388
24470
  } else {
24471
+ for (const t of trackStates) t.composeSlot = null;
23389
24472
  writeTrack("melody", song.melody);
23390
24473
  writeTrack("submelody", song.submelody);
23391
24474
  writeTrack("bass", song.bass);
@@ -23402,12 +24485,6 @@ var mountDAW = (target, options = {}) => {
23402
24485
  refs.drumSelect.value = song.drum;
23403
24486
  options.onDrumChange?.(song.drum);
23404
24487
  applyDrumPatternFont(song.drum);
23405
- const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
23406
- if (shouldAutoInstrument && song.instrument) {
23407
- currentInstrument = song.instrument;
23408
- autoComposeInstrument = song.instrument;
23409
- options.onInstrumentChange?.(song.instrument);
23410
- }
23411
24488
  if (withVocal) {
23412
24489
  autoComposeVocalTracks.clear();
23413
24490
  const melodyTrack = isAdvanced ? trackStates[0] : trackStates.find((t) => t.config.id === "melody");
@@ -24644,6 +25721,7 @@ var mountDAW = (target, options = {}) => {
24644
25721
  if (!t) return;
24645
25722
  const name = normalizeInstrumentName(instrumentName);
24646
25723
  t.trackInstrument = name;
25724
+ t.composeSlot = null;
24647
25725
  if (t.config.id === activeTrackId) updateTrackPanel();
24648
25726
  },
24649
25727
  noteToCanvas: (step, pitch) => {
@@ -26911,10 +27989,13 @@ var createDtmStudio = async (options = {}) => {
26911
27989
  export {
26912
27990
  A4_HZ,
26913
27991
  A4_UNITS,
27992
+ BLUES_SCALE,
26914
27993
  BREATH_MARK,
26915
27994
  CENTS_PER_UNIT,
26916
27995
  COMPOSE_KEYS,
26917
27996
  COMPOSE_MOOD_GROUPS,
27997
+ COMPOSE_SCALES,
27998
+ COMPOSE_SCALE_IDS,
26918
27999
  CORPUS_BANDS,
26919
28000
  CORPUS_SIZE,
26920
28001
  DAW_CSS,
@@ -26934,7 +28015,11 @@ export {
26934
28015
  FADE_IN_MARK,
26935
28016
  FADE_OUT_MARK,
26936
28017
  GLOBAL_STORAGE_KEYS,
28018
+ GM_BRIGHT_CEILING,
26937
28019
  GM_INSTRUMENT_NAMES,
28020
+ GM_INSTRUMENT_RANGE,
28021
+ HARMONIC_MINOR_SCALE,
28022
+ HUNGARIAN_SCALE,
26938
28023
  INSTRUMENT_PRESETS,
26939
28024
  KEY_COUNT,
26940
28025
  KOE_BASE_URL,
@@ -26944,6 +28029,7 @@ export {
26944
28029
  LinkedList,
26945
28030
  MACRO_STORAGE_KEYS,
26946
28031
  MAJOR_KEY_IDS,
28032
+ MAJOR_SCALE2 as MAJOR_SCALE,
26947
28033
  MAX_VOCAL_VOLUME,
26948
28034
  MICRO_STEP,
26949
28035
  MINOR_KEY_IDS,
@@ -27012,10 +28098,12 @@ export {
27012
28098
  featureVector,
27013
28099
  fifthToStep,
27014
28100
  fifthToUnits,
28101
+ fitInstrumentOctave,
27015
28102
  formatMmlMeta,
27016
28103
  freqFromPitch,
27017
28104
  generateRandomPattern,
27018
28105
  getComposeKeyDescription,
28106
+ getComposeScaleDescription,
27019
28107
  getDrumPatternKeys,
27020
28108
  getMidiBPM,
27021
28109
  icon,
@@ -27051,9 +28139,13 @@ export {
27051
28139
  readGlobalSetting,
27052
28140
  readMacroSections,
27053
28141
  readMacroSetting,
28142
+ resolveCenter,
27054
28143
  resolveComposeKey,
28144
+ resolveComposeScale,
27055
28145
  resolveDrumPattern,
27056
28146
  resolveLoopPoint,
28147
+ scaleDegrees,
28148
+ scaleSize,
27057
28149
  semitonesToUnits,
27058
28150
  shiftNotes,
27059
28151
  showLoadingOverlay,