@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.js CHANGED
@@ -22,10 +22,13 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  A4_HZ: () => A4_HZ,
24
24
  A4_UNITS: () => A4_UNITS,
25
+ BLUES_SCALE: () => BLUES_SCALE,
25
26
  BREATH_MARK: () => BREATH_MARK,
26
27
  CENTS_PER_UNIT: () => CENTS_PER_UNIT,
27
28
  COMPOSE_KEYS: () => COMPOSE_KEYS,
28
29
  COMPOSE_MOOD_GROUPS: () => COMPOSE_MOOD_GROUPS,
30
+ COMPOSE_SCALES: () => COMPOSE_SCALES,
31
+ COMPOSE_SCALE_IDS: () => COMPOSE_SCALE_IDS,
29
32
  CORPUS_BANDS: () => CORPUS_BANDS,
30
33
  CORPUS_SIZE: () => CORPUS_SIZE,
31
34
  DAW_CSS: () => DAW_CSS,
@@ -45,7 +48,11 @@ __export(index_exports, {
45
48
  FADE_IN_MARK: () => FADE_IN_MARK,
46
49
  FADE_OUT_MARK: () => FADE_OUT_MARK,
47
50
  GLOBAL_STORAGE_KEYS: () => GLOBAL_STORAGE_KEYS,
51
+ GM_BRIGHT_CEILING: () => GM_BRIGHT_CEILING,
48
52
  GM_INSTRUMENT_NAMES: () => GM_INSTRUMENT_NAMES,
53
+ GM_INSTRUMENT_RANGE: () => GM_INSTRUMENT_RANGE,
54
+ HARMONIC_MINOR_SCALE: () => HARMONIC_MINOR_SCALE,
55
+ HUNGARIAN_SCALE: () => HUNGARIAN_SCALE,
49
56
  INSTRUMENT_PRESETS: () => INSTRUMENT_PRESETS,
50
57
  KEY_COUNT: () => KEY_COUNT,
51
58
  KOE_BASE_URL: () => KOE_BASE_URL,
@@ -55,6 +62,7 @@ __export(index_exports, {
55
62
  LinkedList: () => LinkedList,
56
63
  MACRO_STORAGE_KEYS: () => MACRO_STORAGE_KEYS,
57
64
  MAJOR_KEY_IDS: () => MAJOR_KEY_IDS,
65
+ MAJOR_SCALE: () => MAJOR_SCALE2,
58
66
  MAX_VOCAL_VOLUME: () => MAX_VOCAL_VOLUME,
59
67
  MICRO_STEP: () => MICRO_STEP,
60
68
  MINOR_KEY_IDS: () => MINOR_KEY_IDS,
@@ -123,10 +131,12 @@ __export(index_exports, {
123
131
  featureVector: () => featureVector,
124
132
  fifthToStep: () => fifthToStep,
125
133
  fifthToUnits: () => fifthToUnits,
134
+ fitInstrumentOctave: () => fitInstrumentOctave,
126
135
  formatMmlMeta: () => formatMmlMeta,
127
136
  freqFromPitch: () => freqFromPitch,
128
137
  generateRandomPattern: () => generateRandomPattern,
129
138
  getComposeKeyDescription: () => getComposeKeyDescription,
139
+ getComposeScaleDescription: () => getComposeScaleDescription,
130
140
  getDrumPatternKeys: () => getDrumPatternKeys,
131
141
  getMidiBPM: () => getMidiBPM,
132
142
  icon: () => icon,
@@ -162,9 +172,13 @@ __export(index_exports, {
162
172
  readGlobalSetting: () => readGlobalSetting,
163
173
  readMacroSections: () => readMacroSections,
164
174
  readMacroSetting: () => readMacroSetting,
175
+ resolveCenter: () => resolveCenter,
165
176
  resolveComposeKey: () => resolveComposeKey,
177
+ resolveComposeScale: () => resolveComposeScale,
166
178
  resolveDrumPattern: () => resolveDrumPattern,
167
179
  resolveLoopPoint: () => resolveLoopPoint,
180
+ scaleDegrees: () => scaleDegrees,
181
+ scaleSize: () => scaleSize,
168
182
  semitonesToUnits: () => semitonesToUnits,
169
183
  shiftNotes: () => shiftNotes,
170
184
  showLoadingOverlay: () => showLoadingOverlay,
@@ -8314,6 +8328,22 @@ var DAW_CSS = `
8314
8328
  opacity: 0.85;
8315
8329
  }
8316
8330
  .dtm-pill:not(.dtm-pill--active):active { transform: translate(1px,1px); box-shadow: none; }
8331
+ /* \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 */
8332
+ .dtm-pill--vocal::after {
8333
+ content: '';
8334
+ position: absolute;
8335
+ inset: 0;
8336
+ background-image: var(--dtm-pill-icon);
8337
+ background-size: contain;
8338
+ background-repeat: no-repeat;
8339
+ background-position: right center;
8340
+ opacity: 0.4;
8341
+ pointer-events: none;
8342
+ }
8343
+ .dtm-pill__label {
8344
+ position: relative;
8345
+ z-index: 1;
8346
+ }
8317
8347
  /* \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 */
8318
8348
  .dtm-pill--sounding {
8319
8349
  filter: brightness(1.6);
@@ -13653,6 +13683,650 @@ var featureDistance = (a, b) => {
13653
13683
  return Math.sqrt(sum);
13654
13684
  };
13655
13685
 
13686
+ // src/compose-scales.ts
13687
+ var MAJOR_SCALE2 = [
13688
+ { semi: 0, fifth: 0 },
13689
+ // C
13690
+ { semi: 2, fifth: 2 },
13691
+ // D
13692
+ { semi: 4, fifth: 4 },
13693
+ // E
13694
+ { semi: 5, fifth: -1 },
13695
+ // F
13696
+ { semi: 7, fifth: 1 },
13697
+ // G
13698
+ { semi: 9, fifth: 3 },
13699
+ // A
13700
+ { semi: 11, fifth: 5 }
13701
+ // B
13702
+ ];
13703
+ var HARMONIC_MINOR_SCALE = [
13704
+ { semi: 0, fifth: 0 },
13705
+ // C
13706
+ { semi: 2, fifth: 2 },
13707
+ // D
13708
+ { semi: 4, fifth: 4 },
13709
+ // E
13710
+ { semi: 5, fifth: -1 },
13711
+ // F
13712
+ { semi: 8, fifth: 8 },
13713
+ // G#
13714
+ { semi: 9, fifth: 3 },
13715
+ // A
13716
+ { semi: 11, fifth: 5 }
13717
+ // B
13718
+ ];
13719
+ var HUNGARIAN_SCALE = [
13720
+ { semi: 0, fifth: 0 },
13721
+ // C
13722
+ { semi: 3, fifth: 9 },
13723
+ // D#
13724
+ { semi: 4, fifth: 4 },
13725
+ // E
13726
+ { semi: 5, fifth: -1 },
13727
+ // F
13728
+ { semi: 8, fifth: 8 },
13729
+ // G#
13730
+ { semi: 9, fifth: 3 },
13731
+ // A
13732
+ { semi: 11, fifth: 5 }
13733
+ // B
13734
+ ];
13735
+ var BLUES_SCALE = [
13736
+ { semi: 0, fifth: 0 },
13737
+ // C
13738
+ { semi: 3, fifth: -3 },
13739
+ // Eb
13740
+ { semi: 5, fifth: -1 },
13741
+ // F
13742
+ { semi: 6, fifth: -6 },
13743
+ // Gb(ブルーノート)
13744
+ { semi: 7, fifth: 1 },
13745
+ // G
13746
+ { semi: 10, fifth: -2 }
13747
+ // Bb
13748
+ ];
13749
+ var RYUKYU_CENTER = {
13750
+ tonic: "C",
13751
+ half: "G",
13752
+ deceptive: "Em",
13753
+ tonicPattern: /^C(?![#b]|m)/,
13754
+ a: [
13755
+ ["C", "F", "G", "C"],
13756
+ ["C", "C", "F", "G"],
13757
+ ["F", "G", "C", "C"],
13758
+ ["C", "G", "F", "G"],
13759
+ ["CM7", "F", "G", "C"],
13760
+ ["C", "F", "C", "G"],
13761
+ ["C", "Em", "F", "G"],
13762
+ ["F", "C", "G", "C"]
13763
+ ],
13764
+ b: [
13765
+ ["F", "G", "Em", "C"],
13766
+ ["F", "G", "C", "G"],
13767
+ ["G", "F", "C", "C"],
13768
+ ["FM7", "G", "Em", "F"],
13769
+ ["C", "G", "F", "C"],
13770
+ ["F", "Em", "F", "G"]
13771
+ ],
13772
+ c: [
13773
+ ["Em", "F", "G", "C"],
13774
+ ["F", "C", "G", "Em"],
13775
+ ["C", "Em", "F", "G"]
13776
+ ]
13777
+ };
13778
+ var MIYAKOBUSHI_CENTER = {
13779
+ tonic: "Em",
13780
+ half: "Am",
13781
+ deceptive: "F",
13782
+ tonicPattern: /^E(?:m|sus)/,
13783
+ a: [
13784
+ ["Em", "F", "Em", "Em"],
13785
+ ["Esus4", "F", "Esus4", "Em"],
13786
+ ["Em", "Am", "F", "Em"],
13787
+ ["Am", "Em", "F", "Em"],
13788
+ ["Em", "F", "Am", "Em"],
13789
+ ["Em", "Em", "F", "F"],
13790
+ ["Am", "F", "Em", "Em"],
13791
+ ["Em", "FM7", "Am", "Em"]
13792
+ ],
13793
+ b: [
13794
+ ["F", "Am", "Em", "Em"],
13795
+ ["Am", "F", "Em", "Em"],
13796
+ ["F", "Em", "Am", "Em"],
13797
+ ["FM7", "Am", "F", "Em"],
13798
+ ["Am", "Em", "F", "Am"],
13799
+ ["F", "Am", "F", "Em"]
13800
+ ],
13801
+ c: [
13802
+ ["Am", "Em", "F", "Am"],
13803
+ ["F", "Am", "Esus4", "Em"],
13804
+ ["Am", "F", "Em", "Em"]
13805
+ ]
13806
+ };
13807
+ var RITSU_CENTER = {
13808
+ tonic: "Dsus4",
13809
+ half: "G",
13810
+ deceptive: "Em",
13811
+ tonicPattern: /^Dsus/,
13812
+ a: [
13813
+ ["Dsus4", "G", "Dsus4", "Dsus4"],
13814
+ ["Dsus4", "Em", "G", "Dsus4"],
13815
+ ["G", "Dsus4", "Em", "Dsus4"],
13816
+ ["Dsus4", "G", "Em", "G"],
13817
+ ["Em", "G", "Dsus4", "Dsus4"],
13818
+ ["Dsus4", "Em7", "G", "Dsus4"],
13819
+ ["G", "Em", "Dsus4", "G"],
13820
+ ["Dsus4", "Am", "G", "Dsus4"]
13821
+ ],
13822
+ b: [
13823
+ ["G", "Em", "Dsus4", "Dsus4"],
13824
+ ["Em", "G", "Em", "Dsus4"],
13825
+ ["G", "Am", "Em", "Dsus4"],
13826
+ ["Em7", "G", "Dsus4", "Dsus4"],
13827
+ ["Am", "G", "Em", "Dsus4"],
13828
+ ["Dsus4", "Em", "G", "Em"]
13829
+ ],
13830
+ c: [
13831
+ ["Em", "Dsus4", "G", "Em"],
13832
+ ["G", "Em7", "Am", "G"],
13833
+ ["Am", "G", "Em", "Dsus4"]
13834
+ ]
13835
+ };
13836
+ var HARMONIC_MINOR_CENTER = {
13837
+ tonic: "Am",
13838
+ half: "E7",
13839
+ deceptive: "F",
13840
+ tonicPattern: /^Am/,
13841
+ a: [
13842
+ ["Am", "Dm", "E7", "Am"],
13843
+ // i-iv-V7-i。和声的短音階の基本形
13844
+ ["Am", "F", "E7", "Am"],
13845
+ ["Am", "E7", "Am", "Am"],
13846
+ ["Dm", "E7", "Am", "Am"],
13847
+ ["Am", "AmM7", "Dm", "E7"],
13848
+ // 主和音に導音を重ねたクリシェ
13849
+ ["Am", "F", "Dm", "E7"],
13850
+ ["F", "E7", "Am", "Am"],
13851
+ ["Am", "Bm7-5", "E7", "Am"]
13852
+ ],
13853
+ b: [
13854
+ ["Dm", "E7", "Am", "Am"],
13855
+ ["F", "E7", "Am", "E7"],
13856
+ ["Dm7", "G#dim", "Am", "E7"],
13857
+ ["F", "Dm", "E7", "Am"],
13858
+ ["Am", "Dm", "Bm7-5", "E7"],
13859
+ ["FM7", "E7", "Am", "Am"]
13860
+ ],
13861
+ c: [
13862
+ ["Dm", "Am", "Bm7-5", "E7"],
13863
+ ["F", "C+", "Dm", "E7"],
13864
+ ["Am", "F", "Dm", "E7"]
13865
+ ]
13866
+ };
13867
+ var HIJAZ_CENTER = {
13868
+ tonic: "E",
13869
+ half: "F",
13870
+ deceptive: "Am",
13871
+ tonicPattern: /^E(?![#b]|m)/,
13872
+ a: [
13873
+ ["E", "F", "E", "E"],
13874
+ // I-♭II。ヒジャーズの顔
13875
+ ["E7", "F", "E", "E"],
13876
+ ["Am", "F", "E", "E"],
13877
+ ["E", "F", "Dm", "E"],
13878
+ ["F", "E", "F", "E"],
13879
+ ["Dm", "E", "F", "E"],
13880
+ ["E7", "Am", "F", "E"],
13881
+ ["E", "Dm", "F", "E"]
13882
+ ],
13883
+ b: [
13884
+ ["F", "E", "Am", "E"],
13885
+ ["Dm", "C+", "F", "E"],
13886
+ ["Am", "Dm", "F", "E"],
13887
+ ["F", "Dm", "E", "E"],
13888
+ ["E7", "F", "Dm", "E"],
13889
+ ["Bm7-5", "E7", "Am", "E"]
13890
+ ],
13891
+ c: [
13892
+ ["Am", "Dm", "F", "E"],
13893
+ ["Dm", "Am", "F", "E7"],
13894
+ ["F", "C+", "Dm", "E"]
13895
+ ]
13896
+ };
13897
+ var HUNGARIAN_CENTER = {
13898
+ tonic: "Am",
13899
+ half: "E",
13900
+ deceptive: "F",
13901
+ tonicPattern: /^Am/,
13902
+ a: [
13903
+ ["Am", "E", "Am", "Am"],
13904
+ ["Am", "F", "E", "Am"],
13905
+ ["Am", "AmM7", "F", "E"],
13906
+ ["F", "E", "Am", "Am"],
13907
+ ["Am", "Fm", "E", "Am"],
13908
+ // ♭VIm。増2度を和音の側でも鳴らす
13909
+ ["Am", "E", "F", "E"],
13910
+ ["FM7", "E", "Am", "Am"],
13911
+ ["Am", "C+", "F", "E"]
13912
+ ],
13913
+ b: [
13914
+ ["F", "E", "Am", "E"],
13915
+ ["Fm", "E", "Am", "Am"],
13916
+ ["Am", "F", "C+", "E"],
13917
+ ["FM7", "Am", "F", "E"],
13918
+ ["E", "F", "E", "Am"],
13919
+ ["Am", "AmM7", "Fm", "E"]
13920
+ ],
13921
+ c: [
13922
+ ["F", "Am", "Fm", "E"],
13923
+ ["C+", "F", "Am", "E"],
13924
+ ["Am", "Fm", "F", "E"]
13925
+ ]
13926
+ };
13927
+ var BLUES_CENTER = {
13928
+ tonic: "C7",
13929
+ half: "G7",
13930
+ deceptive: "F7",
13931
+ tonicPattern: /^C7/,
13932
+ a: [
13933
+ ["C7", "C7", "C7", "C7"],
13934
+ // 12小節ブルースの1〜4小節
13935
+ ["C7", "F7", "C7", "C7"],
13936
+ ["C7", "C7", "F7", "F7"],
13937
+ ["F7", "F7", "C7", "C7"],
13938
+ // 5〜8小節
13939
+ ["C7", "F7", "C7", "G7"],
13940
+ ["C7", "C7", "G7", "F7"],
13941
+ ["C7", "F7", "G7", "C7"],
13942
+ ["F7", "C7", "G7", "C7"]
13943
+ ],
13944
+ b: [
13945
+ ["F7", "F7", "C7", "C7"],
13946
+ ["G7", "F7", "C7", "C7"],
13947
+ // 9〜12小節(ターンアラウンド)
13948
+ ["F7", "G7", "C7", "C7"],
13949
+ ["C7", "F7", "G7", "C7"],
13950
+ ["F7", "C7", "G7", "F7"],
13951
+ ["G7", "G7", "F7", "C7"]
13952
+ ],
13953
+ c: [
13954
+ ["G7", "F7", "C7", "G7"],
13955
+ ["F7", "F7", "G7", "G7"],
13956
+ ["C7", "C7", "F7", "G7"]
13957
+ ]
13958
+ };
13959
+ var COMPOSE_SCALES = {
13960
+ yo: {
13961
+ id: "yo",
13962
+ label: "\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
13963
+ tonic: 0,
13964
+ core: [0, 1, 2, 4, 5],
13965
+ // ド レ ミ ソ ラ
13966
+ minorish: false,
13967
+ strict: false,
13968
+ 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"
13969
+ },
13970
+ minyo: {
13971
+ id: "minyo",
13972
+ label: "\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
13973
+ tonic: 5,
13974
+ core: [5, 0, 1, 2, 4],
13975
+ // ラ ド レ ミ ソ
13976
+ minorish: true,
13977
+ strict: false,
13978
+ 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"
13979
+ },
13980
+ ritsu: {
13981
+ id: "ritsu",
13982
+ label: "\u5F8B\u97F3\u968E",
13983
+ tonic: 1,
13984
+ core: [1, 2, 4, 5, 6],
13985
+ // レ ミ ソ ラ シ
13986
+ minorish: true,
13987
+ strict: true,
13988
+ center: RITSU_CENTER,
13989
+ 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"
13990
+ },
13991
+ miyakobushi: {
13992
+ id: "miyakobushi",
13993
+ label: "\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09",
13994
+ tonic: 2,
13995
+ core: [2, 3, 5, 6, 0],
13996
+ // ミ ファ ラ シ ド
13997
+ minorish: true,
13998
+ strict: true,
13999
+ center: MIYAKOBUSHI_CENTER,
14000
+ 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"
14001
+ },
14002
+ ryukyu: {
14003
+ id: "ryukyu",
14004
+ label: "\u7409\u7403\u97F3\u968E",
14005
+ tonic: 0,
14006
+ core: [0, 2, 3, 4, 6],
14007
+ // ド ミ ファ ソ シ
14008
+ minorish: false,
14009
+ strict: true,
14010
+ center: RYUKYU_CENTER,
14011
+ 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"
14012
+ },
14013
+ dorian: {
14014
+ id: "dorian",
14015
+ label: "\u30C9\u30EA\u30A2\u30F3",
14016
+ tonic: 1,
14017
+ core: [1, 3, 4, 5, 0],
14018
+ // レ ファ ソ ラ ド
14019
+ minorish: true,
14020
+ strict: false,
14021
+ 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"
14022
+ },
14023
+ phrygian: {
14024
+ id: "phrygian",
14025
+ label: "\u30D5\u30EA\u30B8\u30A2\u30F3",
14026
+ tonic: 2,
14027
+ core: [2, 3, 5, 6, 1],
14028
+ // ミ ファ ラ シ レ
14029
+ minorish: true,
14030
+ strict: false,
14031
+ 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"
14032
+ },
14033
+ lydian: {
14034
+ id: "lydian",
14035
+ label: "\u30EA\u30C7\u30A3\u30A2\u30F3",
14036
+ tonic: 3,
14037
+ core: [3, 4, 6, 0, 2],
14038
+ // ファ ソ シ ド ミ
14039
+ minorish: false,
14040
+ strict: false,
14041
+ 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"
14042
+ },
14043
+ mixolydian: {
14044
+ id: "mixolydian",
14045
+ label: "\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3",
14046
+ tonic: 4,
14047
+ core: [4, 5, 0, 1, 3],
14048
+ // ソ ラ ド レ ファ
14049
+ minorish: false,
14050
+ strict: false,
14051
+ 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"
14052
+ },
14053
+ harmonic_minor: {
14054
+ id: "harmonic_minor",
14055
+ label: "\u548C\u58F0\u7684\u77ED\u97F3\u968E",
14056
+ tonic: 5,
14057
+ parent: HARMONIC_MINOR_SCALE,
14058
+ core: [5, 0, 1, 2, 4],
14059
+ // ラ ド レ ミ ソ♯
14060
+ minorish: true,
14061
+ strict: false,
14062
+ center: HARMONIC_MINOR_CENTER,
14063
+ 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"
14064
+ },
14065
+ hijaz: {
14066
+ id: "hijaz",
14067
+ label: "\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09",
14068
+ tonic: 2,
14069
+ parent: HARMONIC_MINOR_SCALE,
14070
+ core: [2, 3, 4, 5, 6],
14071
+ // ミ ファ ソ♯ ラ シ
14072
+ minorish: false,
14073
+ strict: false,
14074
+ center: HIJAZ_CENTER,
14075
+ 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"
14076
+ },
14077
+ hungarian: {
14078
+ id: "hungarian",
14079
+ label: "\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09",
14080
+ tonic: 5,
14081
+ parent: HUNGARIAN_SCALE,
14082
+ core: [5, 0, 1, 2, 4],
14083
+ // ラ ド レ♯ ミ ソ♯
14084
+ minorish: true,
14085
+ strict: false,
14086
+ center: HUNGARIAN_CENTER,
14087
+ 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"
14088
+ },
14089
+ blues: {
14090
+ id: "blues",
14091
+ label: "\u30D6\u30EB\u30FC\u30B9\u97F3\u968E",
14092
+ tonic: 0,
14093
+ parent: BLUES_SCALE,
14094
+ core: [0, 1, 2, 4, 5],
14095
+ // ド ミ♭ ファ ソ シ♭
14096
+ minorish: true,
14097
+ strict: true,
14098
+ center: BLUES_CENTER,
14099
+ 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"
14100
+ }
14101
+ };
14102
+ var COMPOSE_SCALE_IDS = Object.keys(
14103
+ COMPOSE_SCALES
14104
+ );
14105
+ var TONIC_CENTERS = {
14106
+ // --- レ(律・ドリアン) ---
14107
+ 1: {
14108
+ tonic: "Dm",
14109
+ half: "G",
14110
+ deceptive: "F",
14111
+ tonicPattern: /^Dm/,
14112
+ a: [
14113
+ ["Dm", "G", "Dm", "Dm"],
14114
+ // i-IV の往復。ドリアンの顔
14115
+ ["Dm", "Am", "G", "Dm"],
14116
+ ["Dm", "C", "G", "Dm"],
14117
+ ["Dm7", "G", "Dm7", "C"],
14118
+ ["Dm", "F", "C", "G"],
14119
+ ["Dm", "Em", "F", "G"],
14120
+ ["Dm", "G", "F", "C"],
14121
+ ["Dm7", "Em7", "FM7", "G"]
14122
+ ],
14123
+ b: [
14124
+ ["F", "G", "Am", "Dm"],
14125
+ ["C", "G", "Dm", "Dm"],
14126
+ ["G", "F", "C", "Dm"],
14127
+ ["Am", "G", "F", "Dm"],
14128
+ ["FM7", "G", "Em7", "Dm7"],
14129
+ ["Dm", "G", "C", "Am"]
14130
+ ],
14131
+ c: [
14132
+ ["Am", "Dm", "G", "C"],
14133
+ ["F", "Em", "Dm", "G"],
14134
+ ["Dm", "Am", "Em", "G"]
14135
+ ]
14136
+ },
14137
+ // --- ミ(都節・フリジアン) ---
14138
+ 2: {
14139
+ tonic: "Em",
14140
+ half: "Am",
14141
+ deceptive: "C",
14142
+ tonicPattern: /^Em/,
14143
+ a: [
14144
+ ["Em", "F", "Em", "Em"],
14145
+ // i-♭II。フリジアン/都節の顔
14146
+ ["Em", "Am", "F", "Em"],
14147
+ ["Am", "Em", "F", "Em"],
14148
+ ["Em", "F", "G", "Em"],
14149
+ ["Em", "Em", "F", "F"],
14150
+ ["Em", "C", "F", "Em"],
14151
+ ["Am", "F", "Em", "Em"],
14152
+ ["Em7", "FM7", "Em7", "Am7"]
14153
+ ],
14154
+ b: [
14155
+ ["F", "G", "Am", "Em"],
14156
+ ["Am", "G", "F", "Em"],
14157
+ ["F", "Em", "Am", "Em"],
14158
+ ["C", "F", "Em", "Em"],
14159
+ ["FM7", "G", "Em7", "Am"],
14160
+ ["Am", "Em", "F", "G"]
14161
+ ],
14162
+ c: [
14163
+ ["Am", "Em", "F", "C"],
14164
+ ["C", "G", "Am", "Em"],
14165
+ ["F", "C", "Am", "Em"]
14166
+ ]
14167
+ },
14168
+ // --- ファ(リディアン) ---
14169
+ 3: {
14170
+ tonic: "FM7",
14171
+ half: "C",
14172
+ deceptive: "Dm",
14173
+ tonicPattern: /^F(?![#b]|m)/,
14174
+ a: [
14175
+ ["FM7", "G", "FM7", "FM7"],
14176
+ // I-II。リディアンの顔(♯4 が G の3度に居る)
14177
+ ["FM7", "G", "Em", "Am"],
14178
+ ["F", "G", "C", "F"],
14179
+ ["FM7", "G", "Am", "F"],
14180
+ ["F", "C", "G", "F"],
14181
+ ["FM7", "Em7", "Dm7", "G"],
14182
+ ["F", "G", "F", "C"],
14183
+ ["FM7", "G", "Dm7", "F"]
14184
+ ],
14185
+ b: [
14186
+ ["G", "F", "C", "F"],
14187
+ ["Am", "G", "FM7", "FM7"],
14188
+ ["C", "G", "Am", "F"],
14189
+ ["G", "Em", "Am", "F"],
14190
+ ["Dm7", "G", "FM7", "FM7"],
14191
+ ["FM7", "G", "Em7", "F"]
14192
+ ],
14193
+ c: [
14194
+ ["Dm", "Am", "F", "G"],
14195
+ ["Am", "Em", "F", "G"],
14196
+ ["C", "Am", "Dm", "F"]
14197
+ ]
14198
+ },
14199
+ // --- ソ(ミクソリディアン) ---
14200
+ 4: {
14201
+ tonic: "G",
14202
+ half: "Dm",
14203
+ deceptive: "Em",
14204
+ tonicPattern: /^G(?![#b]|m)/,
14205
+ a: [
14206
+ ["G", "F", "C", "G"],
14207
+ // I-♭VII-IV。ミクソリディアンの顔
14208
+ ["G", "C", "F", "G"],
14209
+ ["G", "F", "G", "G"],
14210
+ ["C", "G", "F", "G"],
14211
+ ["G", "Dm", "F", "G"],
14212
+ ["G", "Am", "F", "G"],
14213
+ ["G", "F", "Dm", "C"],
14214
+ ["G", "C", "G", "F"]
14215
+ ],
14216
+ b: [
14217
+ ["F", "C", "G", "G"],
14218
+ ["Am", "F", "C", "G"],
14219
+ ["C", "Dm", "F", "G"],
14220
+ ["Em", "F", "C", "G"],
14221
+ ["F", "G", "Am", "G"],
14222
+ ["Dm7", "F", "C", "G"]
14223
+ ],
14224
+ c: [
14225
+ ["Am", "Em", "F", "G"],
14226
+ ["C", "Am", "Dm", "G"],
14227
+ ["Em", "Am", "F", "C"]
14228
+ ]
14229
+ }
14230
+ };
14231
+ var scaleDegrees = (scale) => scale.parent ?? MAJOR_SCALE2;
14232
+ var scaleSize = (scale) => scaleDegrees(scale).length;
14233
+ var degreeToPitch = (scale, degree) => {
14234
+ const list = scaleDegrees(scale);
14235
+ const size = list.length;
14236
+ const index = (degree % size + size) % size;
14237
+ const octave = Math.floor(degree / size);
14238
+ const d = list[index];
14239
+ return { semi: d.semi + octave * 12, fifth: d.fifth };
14240
+ };
14241
+ var semitoneToDegree = (scale, semi) => {
14242
+ const list = scaleDegrees(scale);
14243
+ const octave = Math.floor(semi / 12);
14244
+ const within = semi - octave * 12;
14245
+ let best = 0;
14246
+ let bestDist = Number.POSITIVE_INFINITY;
14247
+ for (let i2 = 0; i2 < list.length; i2++) {
14248
+ const dist = Math.abs(list[i2].semi - within);
14249
+ if (dist < bestDist) {
14250
+ bestDist = dist;
14251
+ best = i2;
14252
+ }
14253
+ }
14254
+ return octave * list.length + best;
14255
+ };
14256
+ var walk = (scale, semi, delta) => degreeToPitch(scale, semitoneToDegree(scale, semi) + delta).semi;
14257
+ var scaleFifth = (scale, semi) => degreeToPitch(scale, semitoneToDegree(scale, semi)).fifth;
14258
+ var SCALE_PCS = /* @__PURE__ */ new Map();
14259
+ var scalePcs = (scale) => {
14260
+ const hit = SCALE_PCS.get(scale.id);
14261
+ if (hit) return hit;
14262
+ const set = new Set(
14263
+ scaleDegrees(scale).map((d) => (d.semi % 12 + 12) % 12)
14264
+ );
14265
+ SCALE_PCS.set(scale.id, set);
14266
+ return set;
14267
+ };
14268
+ var CORE_SORTED = /* @__PURE__ */ new Map();
14269
+ var sortedCore = (scale) => {
14270
+ const hit = CORE_SORTED.get(scale.id);
14271
+ if (hit) return hit;
14272
+ const sorted = [...scale.core].sort((a, b) => a - b);
14273
+ CORE_SORTED.set(scale.id, sorted);
14274
+ return sorted;
14275
+ };
14276
+ var CORE_PCS = /* @__PURE__ */ new Map();
14277
+ var corePcs = (scale) => {
14278
+ const hit = CORE_PCS.get(scale.id);
14279
+ if (hit) return hit;
14280
+ const list = scaleDegrees(scale);
14281
+ const set = new Set(scale.core.map((d) => (list[d].semi % 12 + 12) % 12));
14282
+ CORE_PCS.set(scale.id, set);
14283
+ return set;
14284
+ };
14285
+ var isCoreDegree = (scale, degree) => {
14286
+ const size = scaleSize(scale);
14287
+ return sortedCore(scale).includes((degree % size + size) % size);
14288
+ };
14289
+ var coreToDegree = (scale, step) => {
14290
+ const sorted = sortedCore(scale);
14291
+ const n = sorted.length;
14292
+ const index = (step % n + n) % n;
14293
+ const octave = Math.floor(step / n);
14294
+ return sorted[index] + octave * scaleSize(scale);
14295
+ };
14296
+ var degreeToCore = (scale, degree) => {
14297
+ const sorted = sortedCore(scale);
14298
+ const size = scaleSize(scale);
14299
+ const index = (degree % size + size) % size;
14300
+ const octave = Math.floor(degree / size);
14301
+ let best = 0;
14302
+ let bestDist = Number.POSITIVE_INFINITY;
14303
+ for (let i2 = 0; i2 < sorted.length; i2++) {
14304
+ const dist = Math.abs(sorted[i2] - index);
14305
+ if (dist < bestDist) {
14306
+ bestDist = dist;
14307
+ best = i2;
14308
+ }
14309
+ }
14310
+ return octave * sorted.length + best;
14311
+ };
14312
+ var isOutsideCore = (scale, semi) => scale.strict ? !corePcs(scale).has((semi % 12 + 12) % 12) : !isCoreDegree(scale, semitoneToDegree(scale, semi));
14313
+ var resolveComposeScale = (choice, minorKey, rnd = Math.random) => {
14314
+ const c = (choice ?? "").trim() || "auto";
14315
+ const hit = COMPOSE_SCALES[c];
14316
+ if (hit) return hit;
14317
+ if (c === "any")
14318
+ return COMPOSE_SCALES[COMPOSE_SCALE_IDS[Math.floor(rnd() * COMPOSE_SCALE_IDS.length)] ?? "yo"];
14319
+ return COMPOSE_SCALES[minorKey ? "minyo" : "yo"];
14320
+ };
14321
+ var getComposeScaleDescription = (choice) => {
14322
+ const hit = COMPOSE_SCALES[choice];
14323
+ if (hit) return hit.description;
14324
+ if (choice === "any")
14325
+ return `${COMPOSE_SCALE_IDS.length}\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059`;
14326
+ 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";
14327
+ };
14328
+ var resolveCenter = (scale) => scale.center ?? TONIC_CENTERS[scale.tonic] ?? null;
14329
+
13656
14330
  // src/compose-sections.ts
13657
14331
  var SECTION_LABELS = {
13658
14332
  intro: "\u30A4\u30F3\u30C8\u30ED",
@@ -14077,65 +14751,6 @@ var BPM_CHOICES = [
14077
14751
  180,
14078
14752
  185
14079
14753
  ];
14080
- var MAJOR_SCALE2 = [
14081
- { semi: 0, fifth: 0 },
14082
- // C
14083
- { semi: 2, fifth: 2 },
14084
- // D
14085
- { semi: 4, fifth: 4 },
14086
- // E
14087
- { semi: 5, fifth: -1 },
14088
- // F
14089
- { semi: 7, fifth: 1 },
14090
- // G
14091
- { semi: 9, fifth: 3 },
14092
- // A
14093
- { semi: 11, fifth: 5 }
14094
- // B
14095
- ];
14096
- var degreeToPitch = (degree) => {
14097
- const index = (degree % 7 + 7) % 7;
14098
- const octave = Math.floor(degree / 7);
14099
- const d = MAJOR_SCALE2[index];
14100
- return { semi: d.semi + octave * 12, fifth: d.fifth };
14101
- };
14102
- var semitoneToDegree = (semi) => {
14103
- const octave = Math.floor(semi / 12);
14104
- const within = semi - octave * 12;
14105
- let best = 0;
14106
- let bestDist = Number.POSITIVE_INFINITY;
14107
- for (let i2 = 0; i2 < MAJOR_SCALE2.length; i2++) {
14108
- const dist = Math.abs(MAJOR_SCALE2[i2].semi - within);
14109
- if (dist < bestDist) {
14110
- bestDist = dist;
14111
- best = i2;
14112
- }
14113
- }
14114
- return octave * 7 + best;
14115
- };
14116
- var walk = (semi, delta) => degreeToPitch(semitoneToDegree(semi) + delta).semi;
14117
- var NON_PENTATONIC_DEGREES = /* @__PURE__ */ new Set([3, 6]);
14118
- var isNonPentatonic = (semi) => NON_PENTATONIC_DEGREES.has((semitoneToDegree(semi) % 7 + 7) % 7);
14119
- var PENTATONIC_DEGREES = [0, 1, 2, 4, 5];
14120
- var pentaToDegree = (penta) => {
14121
- const index = (penta % 5 + 5) % 5;
14122
- const octave = Math.floor(penta / 5);
14123
- return PENTATONIC_DEGREES[index] + octave * 7;
14124
- };
14125
- var degreeToPenta = (degree) => {
14126
- const index = (degree % 7 + 7) % 7;
14127
- const octave = Math.floor(degree / 7);
14128
- let best = 0;
14129
- let bestDist = Number.POSITIVE_INFINITY;
14130
- for (let i2 = 0; i2 < PENTATONIC_DEGREES.length; i2++) {
14131
- const dist = Math.abs(PENTATONIC_DEGREES[i2] - index);
14132
- if (dist < bestDist) {
14133
- bestDist = dist;
14134
- best = i2;
14135
- }
14136
- }
14137
- return octave * 5 + best;
14138
- };
14139
14754
  var SECTION_A_PROGRESSIONS = [
14140
14755
  ["C", "G", "Am", "Em7"],
14141
14756
  // カノン進行の前半
@@ -14254,6 +14869,22 @@ var SECTION_DECEPTIVE_DERIVATIONS = [
14254
14869
  (a, t) => [a[0], "Dm7", "G7", t === "Am" ? "FM7" : "Am7"],
14255
14870
  (a, t) => [a[0], "F", "G7", t === "Am" ? "F" : "Am7"]
14256
14871
  ];
14872
+ var MODAL_FULL_DERIVATIONS = [
14873
+ (a, c) => [a[0], a[1], c.half, c.tonic],
14874
+ (a, c) => [a[0], c.half, a[2], c.tonic],
14875
+ (a, c) => [a[0], a[1], a[2], c.tonic],
14876
+ (a, c) => [c.tonic, c.half, a[2], c.tonic]
14877
+ ];
14878
+ var MODAL_HALF_DERIVATIONS = [
14879
+ (a, c) => [a[0], a[1], a[2], c.half],
14880
+ (a, c) => [a[0], a[1], c.tonic, c.half],
14881
+ (a, c) => [a[0], c.tonic, a[2], c.half]
14882
+ ];
14883
+ var MODAL_DECEPTIVE_DERIVATIONS = [
14884
+ (a, c) => [a[0], a[1], c.half, c.deceptive],
14885
+ (a, c) => [a[0], c.half, a[2], c.deceptive],
14886
+ (a, c) => [a[0], a[1], a[2], c.deceptive]
14887
+ ];
14257
14888
  var MODAL_BORROW = {
14258
14889
  F: "Fm",
14259
14890
  // IV → IVm(サブドミナントマイナー。いちばん定番)
@@ -14844,7 +15475,7 @@ var nearestChordTone = (targetSemi, tones, minWeight, preferColor = false) => {
14844
15475
  var MELODY_LOW = 60;
14845
15476
  var MELODY_HIGH = 81;
14846
15477
  var MELODY_CENTER = (MELODY_LOW + MELODY_HIGH) / 2;
14847
- var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) => {
15478
+ var harmonyPitch = (scale, melodySemi, tones, prevHarmony, offset, parallel, against) => {
14848
15479
  const lo = Math.max(-12, Math.min(offset, 0) - 7);
14849
15480
  const hi = Math.min(12, Math.max(offset, 0) + 7);
14850
15481
  let best = null;
@@ -14862,7 +15493,8 @@ var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) =
14862
15493
  const g = Math.abs(semi - against) % 12;
14863
15494
  clash = g === 3 || g === 4 || g === 8 || g === 9 || g === 7 ? 0 : g === 0 ? 0.5 : 1.2;
14864
15495
  }
14865
- const cost = stay + clash + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
15496
+ const offCore = scale.strict && isOutsideCore(scale, semi) ? 1.5 : 0;
15497
+ const cost = stay + clash + offCore + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
14866
15498
  if (cost < bestCost) {
14867
15499
  bestCost = cost;
14868
15500
  best = { semi, fifth: tone.fifth };
@@ -14907,40 +15539,43 @@ var leapTarget = (from, tones, rnd) => {
14907
15539
  ];
14908
15540
  return pick(weighted.length > 0 ? weighted : candidates, rnd);
14909
15541
  };
14910
- var landOn = (degrees, scaleIndex) => {
15542
+ var landOn = (scale, degrees, scaleIndex) => {
14911
15543
  if (degrees.length === 0) return;
15544
+ const size = scaleSize(scale);
14912
15545
  const last = degrees[degrees.length - 1];
14913
- const index = (last % 7 + 7) % 7;
15546
+ const index = (last % size + size) % size;
14914
15547
  let delta = scaleIndex - index;
14915
- if (delta > 3) delta -= 7;
14916
- if (delta < -3) delta += 7;
15548
+ if (delta > size / 2) delta -= size;
15549
+ if (delta < -size / 2) delta += size;
14917
15550
  degrees[degrees.length - 1] = last + delta;
14918
15551
  };
14919
- var landPitch = (pitches, scaleIndex) => {
15552
+ var landPitch = (scale, pitches, scaleIndex) => {
14920
15553
  if (pitches.length === 0) return;
15554
+ const size = scaleSize(scale);
14921
15555
  const last = pitches[pitches.length - 1];
14922
- const degree = semitoneToDegree(last);
14923
- const index = (degree % 7 + 7) % 7;
15556
+ const degree = semitoneToDegree(scale, last);
15557
+ const index = (degree % size + size) % size;
14924
15558
  let delta = scaleIndex - index;
14925
- if (delta > 3) delta -= 7;
14926
- if (delta < -3) delta += 7;
15559
+ if (delta > size / 2) delta -= size;
15560
+ if (delta < -size / 2) delta += size;
14927
15561
  pitches[pitches.length - 1] = clampSemi(
14928
- degreeToPitch(degree + delta).semi,
15562
+ degreeToPitch(scale, degree + delta).semi,
14929
15563
  MELODY_LOW,
14930
15564
  MELODY_HIGH
14931
15565
  );
14932
15566
  };
14933
- var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
15567
+ var barDegrees = (role, slots, tones, style, scale, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
14934
15568
  const noteCount = slots.length;
14935
15569
  const out = [];
14936
15570
  if (role === "motif" || role === "sequence" || role === "climax" || role === "answer") {
14937
15571
  const shift = (role === "sequence" ? pick([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
14938
15572
  if (style.pentatonicMotif) {
14939
- const startPenta = degreeToPenta(startDegree);
15573
+ const startCore = degreeToCore(scale, startDegree);
14940
15574
  for (let i2 = 0; i2 < noteCount; i2++)
14941
15575
  out.push(
14942
- pentaToDegree(
14943
- startPenta + shift + motifContour[(contourOffset + i2) % motifContour.length]
15576
+ coreToDegree(
15577
+ scale,
15578
+ startCore + shift + motifContour[(contourOffset + i2) % motifContour.length]
14944
15579
  )
14945
15580
  );
14946
15581
  } else {
@@ -14960,9 +15595,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14960
15595
  for (let i2 = 0; i2 < noteCount; i2++)
14961
15596
  out.push(startDegree + dir2 * (i2 < peak ? i2 : peak * 2 - i2 - 1));
14962
15597
  } else if (style.runShape === "broken") {
14963
- const arp = tones.map((t) => semitoneToDegree(clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
15598
+ const arp = tones.map((t) => semitoneToDegree(scale, clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
14964
15599
  for (let i2 = 0; i2 < noteCount; i2++) {
14965
- const oct = Math.floor(i2 / arp.length) * 7;
15600
+ const oct = Math.floor(i2 / arp.length) * scaleSize(scale);
14966
15601
  const idx = dir2 > 0 ? i2 % arp.length : arp.length - 1 - i2 % arp.length;
14967
15602
  out.push(arp[idx] + dir2 * oct);
14968
15603
  }
@@ -14985,7 +15620,10 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14985
15620
  return out;
14986
15621
  }
14987
15622
  if (role === "cadence") {
14988
- const tonic = semitoneToDegree(clampSemi(72, MELODY_LOW, MELODY_HIGH));
15623
+ const tonic = semitoneToDegree(
15624
+ scale,
15625
+ clampSemi(72, MELODY_LOW, MELODY_HIGH)
15626
+ );
14989
15627
  for (let i2 = 0; i2 < noteCount; i2++) {
14990
15628
  if (style.cadenceShape === "descend") {
14991
15629
  out.push(tonic + noteCount - 1 - i2);
@@ -14993,7 +15631,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14993
15631
  const shape = [4, 2, 0];
14994
15632
  out.push(tonic + shape[Math.min(i2, shape.length - 1)]);
14995
15633
  } else if (style.cadenceShape === "leap-up") {
14996
- out.push(i2 === noteCount - 1 ? tonic : tonic - 7 + Math.min(i2, 4));
15634
+ out.push(
15635
+ i2 === noteCount - 1 ? tonic : tonic - scaleSize(scale) + Math.min(i2, 4)
15636
+ );
14997
15637
  } else {
14998
15638
  out.push(i2 === 0 && noteCount > 1 ? tonic + 1 : tonic);
14999
15639
  }
@@ -15028,8 +15668,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
15028
15668
  for (let i2 = 0; i2 < out.length; i2++) {
15029
15669
  if (!slots[i2].isStrong) continue;
15030
15670
  out[i2] = semitoneToDegree(
15671
+ scale,
15031
15672
  nearestChordTone(
15032
- degreeToPitch(out[i2]).semi,
15673
+ degreeToPitch(scale, out[i2]).semi,
15033
15674
  tones,
15034
15675
  barHeadWeight,
15035
15676
  preferColor
@@ -15040,9 +15681,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
15040
15681
  if (slots[i2].isStrong) continue;
15041
15682
  if (slots[i2].value >= quarterSteps) {
15042
15683
  if (rnd() < style.leapAffinity) {
15043
- const from = degreeToPitch(out[i2 - 1]).semi;
15684
+ const from = degreeToPitch(scale, out[i2 - 1]).semi;
15044
15685
  const target = leapTarget(from, tones, rnd);
15045
- if (target !== null) out[i2] = semitoneToDegree(target);
15686
+ if (target !== null) out[i2] = semitoneToDegree(scale, target);
15046
15687
  }
15047
15688
  continue;
15048
15689
  }
@@ -15052,7 +15693,7 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
15052
15693
  }
15053
15694
  return out;
15054
15695
  };
15055
- var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, preferShift) => {
15696
+ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, scale, pentatonic, preferShift) => {
15056
15697
  let best = degrees;
15057
15698
  let bestShift = 0;
15058
15699
  let bestScore = Number.NEGATIVE_INFINITY;
@@ -15060,12 +15701,12 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, prefe
15060
15701
  let preferMoved = null;
15061
15702
  for (let shift = -3; shift <= 3; shift++) {
15062
15703
  const moved = degrees.map(
15063
- (d) => pentatonic ? pentaToDegree(degreeToPenta(d) + shift) : d + shift
15704
+ (d) => pentatonic ? coreToDegree(scale, degreeToCore(scale, d) + shift) : d + shift
15064
15705
  );
15065
15706
  let score = 0;
15066
15707
  for (let i2 = 0; i2 < moved.length; i2++) {
15067
15708
  const semi = clampSemi(
15068
- degreeToPitch(moved[i2]).semi,
15709
+ degreeToPitch(scale, moved[i2]).semi,
15069
15710
  MELODY_LOW,
15070
15711
  MELODY_HIGH
15071
15712
  );
@@ -15074,7 +15715,7 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, prefe
15074
15715
  score += important ? w * 3 : w;
15075
15716
  }
15076
15717
  const head = clampSemi(
15077
- degreeToPitch(moved[0]).semi,
15718
+ degreeToPitch(scale, moved[0]).semi,
15078
15719
  MELODY_LOW,
15079
15720
  MELODY_HIGH
15080
15721
  );
@@ -15097,7 +15738,7 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
15097
15738
  const out = [];
15098
15739
  let prev = prevSemi;
15099
15740
  if (opts.preserveContour) {
15100
- const raw = degrees.map((d) => degreeToPitch(d).semi);
15741
+ const raw = degrees.map((d) => degreeToPitch(opts.scale, d).semi);
15101
15742
  const lo = Math.min(...raw);
15102
15743
  const hi = Math.max(...raw);
15103
15744
  let shift = 0;
@@ -15111,30 +15752,42 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
15111
15752
  }
15112
15753
  for (let i2 = 0; i2 < degrees.length; i2++) {
15113
15754
  let semi = clampSemi(
15114
- degreeToPitch(degrees[i2]).semi,
15755
+ degreeToPitch(opts.scale, degrees[i2]).semi,
15115
15756
  MELODY_LOW,
15116
15757
  MELODY_HIGH
15117
15758
  );
15118
15759
  const limit = i2 === 0 ? MAX_BAR_LEAP_SEMITONES : MAX_LEAP_SEMITONES;
15119
15760
  if (!opts.allowLeap && Math.abs(semi - prev) > limit) {
15120
15761
  semi = clampSemi(
15121
- walk(prev, Math.sign(semi - prev) * 3),
15762
+ walk(opts.scale, prev, Math.sign(semi - prev) * 3),
15122
15763
  MELODY_LOW,
15123
15764
  MELODY_HIGH
15124
15765
  );
15125
15766
  }
15126
15767
  if (!opts.allowArpeggio && i2 >= 2 && Math.abs(out[i2 - 1] - out[i2 - 2]) > STEP_SEMITONES) {
15127
15768
  const back = -Math.sign(out[i2 - 1] - out[i2 - 2]);
15128
- semi = clampSemi(walk(out[i2 - 1], back), MELODY_LOW, MELODY_HIGH);
15769
+ semi = clampSemi(
15770
+ walk(opts.scale, out[i2 - 1], back),
15771
+ MELODY_LOW,
15772
+ MELODY_HIGH
15773
+ );
15129
15774
  }
15130
15775
  if (toneWeight(semi, tones) === 0 && (slots[i2].isStrong || slots[i2].value >= opts.quarterSteps)) {
15131
15776
  const resolved = semi + 1;
15132
- const useResolved = resolved <= MELODY_HIGH && !DIATONIC_PCS.has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
15777
+ const useResolved = resolved <= MELODY_HIGH && !scalePcs(opts.scale).has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
15133
15778
  if (useResolved) {
15134
15779
  semi = resolved;
15135
15780
  } else {
15136
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
15137
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
15781
+ const up = clampSemi(
15782
+ walk(opts.scale, semi, 1),
15783
+ MELODY_LOW,
15784
+ MELODY_HIGH
15785
+ );
15786
+ const down = clampSemi(
15787
+ walk(opts.scale, semi, -1),
15788
+ MELODY_LOW,
15789
+ MELODY_HIGH
15790
+ );
15138
15791
  const score = (s) => toneWeight(s, tones) * 2 + (i2 > 0 && s === prev ? -3 : 0);
15139
15792
  semi = score(down) >= score(up) ? down : up;
15140
15793
  }
@@ -15142,7 +15795,14 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
15142
15795
  out.push(semi);
15143
15796
  prev = semi;
15144
15797
  }
15145
- applyPentatonic(out, slots, tones, prevSemi, opts.quarterSteps / 2);
15798
+ applyPentatonic(
15799
+ out,
15800
+ slots,
15801
+ tones,
15802
+ prevSemi,
15803
+ opts.quarterSteps / 2,
15804
+ opts.scale
15805
+ );
15146
15806
  applyOctaveJumps(out, slots, opts.octaveAffinity, opts.rnd);
15147
15807
  return out;
15148
15808
  };
@@ -15159,27 +15819,27 @@ var applyOctaveJumps = (out, slots, affinity, rnd) => {
15159
15819
  out[i2] = canUp && (!canDown || rnd() < 0.5) ? up : down;
15160
15820
  }
15161
15821
  };
15162
- var applyPentatonic = (out, slots, tones, prevSemi, shortSteps) => {
15822
+ var applyPentatonic = (out, slots, tones, prevSemi, shortSteps, scale) => {
15163
15823
  for (let i2 = 0; i2 < out.length; i2++) {
15164
15824
  const semi = out[i2];
15165
- if (!isNonPentatonic(semi)) continue;
15166
- if (tones.some((t) => pitchClass(t.semi) === pitchClass(semi))) continue;
15825
+ if (!isOutsideCore(scale, semi)) continue;
15826
+ if (!scale.strict && tones.some((t) => pitchClass(t.semi) === pitchClass(semi)))
15827
+ continue;
15167
15828
  const before = i2 === 0 ? prevSemi : out[i2 - 1];
15168
15829
  const after = i2 + 1 < out.length ? out[i2 + 1] : null;
15169
15830
  const inByStep = Math.abs(semi - before) <= STEP_SEMITONES;
15170
15831
  const outByStep = after !== null && Math.abs(after - semi) <= STEP_SEMITONES;
15171
- if (inByStep || outByStep) continue;
15172
- if (!slots[i2].isStrong && slots[i2].value <= shortSteps) continue;
15173
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
15174
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
15175
- const score = (s) => (isNonPentatonic(s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
15832
+ if (scale.strict ? inByStep && outByStep : inByStep || outByStep) continue;
15833
+ if (!scale.strict && !slots[i2].isStrong && slots[i2].value <= shortSteps)
15834
+ continue;
15835
+ const up = clampSemi(walk(scale, semi, 1), MELODY_LOW, MELODY_HIGH);
15836
+ const down = clampSemi(walk(scale, semi, -1), MELODY_LOW, MELODY_HIGH);
15837
+ const score = (s) => (isOutsideCore(scale, s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
15176
15838
  out[i2] = score(down) >= score(up) ? down : up;
15177
15839
  }
15178
15840
  };
15179
- var DIATONIC_PCS = new Set(MAJOR_SCALE2.map((d) => d.semi));
15180
15841
  var SHARP_FIFTHS = [0, 7, 2, 9, 4, -1, 6, 1, 8, 3, 10, 5];
15181
15842
  var FLAT_FIFTHS = [0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5];
15182
- var diatonicFifth = (semi) => degreeToPitch(semitoneToDegree(semi)).fifth;
15183
15843
  var nearestOctaveOf = (near, semi) => {
15184
15844
  const pc = pitchClass(semi);
15185
15845
  let best = pc;
@@ -15194,7 +15854,7 @@ var nearestOctaveOf = (near, semi) => {
15194
15854
  }
15195
15855
  return best;
15196
15856
  };
15197
- var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15857
+ var applyChromatic = (scale, pitches, fifths, slots, tones, opts) => {
15198
15858
  const last = pitches.length - 1;
15199
15859
  for (let i2 = 0; i2 < pitches.length; i2++) {
15200
15860
  const tone = tones.find(
@@ -15202,7 +15862,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15202
15862
  );
15203
15863
  if (tone) fifths[i2] = tone.fifth;
15204
15864
  }
15205
- const altered = tones.filter((t) => !DIATONIC_PCS.has(pitchClass(t.semi)));
15865
+ const pcs = scalePcs(scale);
15866
+ const altered = tones.filter((t) => !pcs.has(pitchClass(t.semi)));
15206
15867
  for (let i2 = 0; i2 < pitches.length; i2++) {
15207
15868
  if (opts.keepLast && i2 === last) continue;
15208
15869
  if (!slots[i2].isStrong && slots[i2].value < opts.quarterSteps) continue;
@@ -15230,7 +15891,7 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15230
15891
  const span = Math.abs(after - before);
15231
15892
  const target = span === 2 ? before + dir : span === 3 ? after - dir : null;
15232
15893
  if (target === null) continue;
15233
- if (DIATONIC_PCS.has(pitchClass(target))) continue;
15894
+ if (pcs.has(pitchClass(target))) continue;
15234
15895
  if (target === before || target === after) continue;
15235
15896
  if (target < MELODY_LOW || target > MELODY_HIGH) continue;
15236
15897
  pitches[i2] = target;
@@ -15238,7 +15899,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15238
15899
  lastAltered = i2;
15239
15900
  }
15240
15901
  };
15241
- var draw = (options, resolvedKey, rnd) => {
15902
+ var draw = (options, resolvedKey, scale, rnd) => {
15903
+ const center = resolveCenter(scale);
15242
15904
  const stepsPerBar = options.stepsPerBar;
15243
15905
  const edo = options.edo === 31 ? 31 : 12;
15244
15906
  const scaleStep = (v) => Math.max(1, Math.round(Math.abs(v) * stepsPerBar / BASE_STEPS_PER_BAR)) * Math.sign(v);
@@ -15266,7 +15928,7 @@ var draw = (options, resolvedKey, rnd) => {
15266
15928
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
15267
15929
  const relativeKinds = /* @__PURE__ */ new Set();
15268
15930
  let relativeShift = 0;
15269
- if (rnd() < 0.25) {
15931
+ if (rnd() < 0.25 && !center) {
15270
15932
  for (const kind of pick(
15271
15933
  [["prechorus"], ["bridge"], ["prechorus", "bridge"]],
15272
15934
  rnd
@@ -15376,42 +16038,42 @@ var draw = (options, resolvedKey, rnd) => {
15376
16038
  barKeyShift[b] = s.keyShift;
15377
16039
  }
15378
16040
  }
15379
- 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;
16041
+ 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;
15380
16042
  const withoutTonic = (pool, root) => {
15381
- const isTonic = (c) => root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
16043
+ const isTonic = (c) => center ? center.tonicPattern.test(c) : root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15382
16044
  const out = pool.filter((p) => !p.some(isTonic));
15383
16045
  return out.length > 0 ? out : pool;
15384
16046
  };
15385
- const homeRoot = resolvedKey.mode === "minor" ? "Am" : "C";
16047
+ const homeRoot = center ? center.tonic : resolvedKey.mode === "minor" ? "Am" : "C";
15386
16048
  const progA = pick(
15387
16049
  floating ? withoutTonic(progAPool, homeRoot) : progAPool,
15388
16050
  rnd
15389
16051
  );
15390
- const progBPool = SECTION_B_PROGRESSIONS.filter(
16052
+ const progBPool = (center ? center.b : SECTION_B_PROGRESSIONS).filter(
15391
16053
  (p) => p.join("|") !== progA.join("|")
15392
16054
  );
15393
16055
  const progB = pick(
15394
16056
  floating ? withoutTonic(progBPool, homeRoot) : progBPool,
15395
16057
  rnd
15396
16058
  );
15397
- const progRelativePool = SECTION_A_PROGRESSIONS.filter(
16059
+ const progRelativePool = center ? center.a : SECTION_A_PROGRESSIONS.filter(
15398
16060
  (p) => resolvedKey.mode === "minor" ? !p[0].startsWith("Am") : p[0].startsWith("Am")
15399
16061
  );
15400
16062
  const progRelative = pick(
15401
16063
  floating ? withoutTonic(progRelativePool, homeRoot) : progRelativePool,
15402
16064
  rnd
15403
16065
  );
15404
- const progCPool = SECTION_C_PROGRESSIONS.filter(
16066
+ const progCPool = (center ? center.c : SECTION_C_PROGRESSIONS).filter(
15405
16067
  (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
15406
16068
  );
15407
16069
  const progC = pick(
15408
16070
  floating ? withoutTonic(progCPool, homeRoot) : progCPool,
15409
16071
  rnd
15410
16072
  );
15411
- const tonic = progA[0].startsWith("Am") ? "Am" : "C";
15412
- const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
15413
- const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15414
- const progDeceptive = pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
16073
+ const tonic = center ? center.tonic : progA[0].startsWith("Am") ? "Am" : "C";
16074
+ const progHalf = center ? pick(MODAL_HALF_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A2_DERIVATIONS, rnd)(progA);
16075
+ const progFull = center ? pick(MODAL_FULL_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
16076
+ const progDeceptive = center ? pick(MODAL_DECEPTIVE_DERIVATIONS, rnd)(progA, center) : pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15415
16077
  let lastChorusBar = -1;
15416
16078
  for (const section of sectionPlan)
15417
16079
  if (section.kind === "chorus" || section.kind === "outro")
@@ -15458,7 +16120,7 @@ var draw = (options, resolvedKey, rnd) => {
15458
16120
  progression[bar - 1] = transposeChordName("G7", shift - prev);
15459
16121
  }
15460
16122
  }
15461
- if (rnd() < 0.3) {
16123
+ if (rnd() < 0.3 && !center) {
15462
16124
  const tonicNames = tonic === "Am" ? ["Am", "Am7"] : ["C", "CM7"];
15463
16125
  const spots = [];
15464
16126
  for (let bar = 0; bar + 1 < totalBars; bar++) {
@@ -15479,10 +16141,12 @@ var draw = (options, resolvedKey, rnd) => {
15479
16141
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" || kind === "drop_chorus" ? "b" : kind === "prechorus" ? "a2" : kind === "bridge" ? "c" : "a";
15480
16142
  const landingOf = (section) => {
15481
16143
  if (section.spec.landing === null) return null;
15482
- const minorHere = resolvedKey.mode === "minor" !== relativeKinds.has(section.kind);
15483
- let landing = (section.spec.landing + (minorHere ? 5 : 0)) % 7;
15484
- if (floating && landing === (minorHere ? 5 : 0))
15485
- landing = (landing + pick([2, 4], rnd)) % 7;
16144
+ const relativeHere = relativeKinds.has(section.kind);
16145
+ const tonicDegree = relativeHere ? scale.tonic === 5 ? 0 : 5 : scale.tonic;
16146
+ const size = scaleSize(scale);
16147
+ let landing = (section.spec.landing + tonicDegree) % size;
16148
+ if (floating && landing === tonicDegree)
16149
+ landing = (landing + pick([2, 4], rnd)) % size;
15486
16150
  return landing;
15487
16151
  };
15488
16152
  const units2 = [];
@@ -15491,9 +16155,12 @@ var draw = (options, resolvedKey, rnd) => {
15491
16155
  const src = sourceOf(section.kind);
15492
16156
  for (let u = 0; u < unitCount; u++) {
15493
16157
  if (!section.spec.melody) {
16158
+ const solo2 = section.kind === "interlude";
15494
16159
  units2.push({
15495
- role: "hold",
15496
- source: "silent",
16160
+ // 見せ場なので走句と山を交互に置く。`hold` のままだと
16161
+ // リズム型が最も薄いものになり、ソロにならない。
16162
+ role: solo2 ? u % 2 === 0 ? "run" : "climax" : "hold",
16163
+ source: solo2 ? "solo" : "silent",
15497
16164
  landing: null,
15498
16165
  section
15499
16166
  });
@@ -15557,7 +16224,10 @@ var draw = (options, resolvedKey, rnd) => {
15557
16224
  arcAmp: 2 + rnd() * 3,
15558
16225
  // オクターブ跳躍は参考曲では音程の1.0%しかない。上げすぎると音域が広がる。
15559
16226
  octaveAffinity: 0.06 + rnd() * 0.12,
15560
- pentatonicMotif: rnd() < 0.55,
16227
+ // **音階を厳しく締める曲は必ず中核音の歩数で組む。** ダイアトニックの度数で輪郭を
16228
+ // 作ると、琉球音階なのにレやラが輪郭の中に入り込む。ファ・シを自由に使う
16229
+ // 陽・民謡だけが、曲ごとに掛けたり掛けなかったりする({@link ComposeScale.strict})。
16230
+ pentatonicMotif: rnd() < 0.55 || scale.strict,
15561
16231
  runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
15562
16232
  stepShape: pick(
15563
16233
  ["arch", "valley", "ascend", "descend", "wave", "pivot"],
@@ -15575,7 +16245,11 @@ var draw = (options, resolvedKey, rnd) => {
15575
16245
  // 界隈曲らしさ:調の外の音(クロマチック)や微小な逸脱を積極的に許容する。
15576
16246
  // 刻みを細かくすると経過音の置き場所が増えるので、同じ係数でも変化音は増える。
15577
16247
  // 参考曲の1.75倍まで伸びていたぶんを引く。
15578
- chromaticAffinity: rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33,
16248
+ // **音階を厳しく締める曲は変化音を控える。** 半音の経過音は長調・短調の泣きメロの
16249
+ // 芯だが、琉球・都節・律ではその半音が音階の外にしか無く、入れたぶんだけ
16250
+ // 音階の色が薄まる(実測で嬰ヘが3%出て、音階内のラ2%より多いという逆転が
16251
+ // 起きていた)。0 にはしない——民族音階の実際の曲にも装飾の半音は出る。
16252
+ chromaticAffinity: (rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33) * (scale.strict ? 0.4 : 1),
15579
16253
  barHeadWeight: rnd() < 0.5 ? 3 : 2,
15580
16254
  bassStyle: pick(
15581
16255
  [
@@ -15730,7 +16404,7 @@ var draw = (options, resolvedKey, rnd) => {
15730
16404
  if (breathBars.has(source)) breathBars.add(bar);
15731
16405
  continue;
15732
16406
  }
15733
- const isB = units2[u].source === "b";
16407
+ const isB = units2[u].source === "b" || units2[u].source === "solo";
15734
16408
  const isA2 = units2[u].source === "a2";
15735
16409
  const isC = units2[u].source === "c";
15736
16410
  const isPrechorusEnd = units2[u].section.kind === "prechorus" && bar === units2[u].section.startBar + units2[u].section.bars - 1;
@@ -15780,6 +16454,7 @@ var draw = (options, resolvedKey, rnd) => {
15780
16454
  const harmony = [];
15781
16455
  const harmony2 = [];
15782
16456
  const pad = [];
16457
+ const solo = [];
15783
16458
  const melodyDurations = [];
15784
16459
  const barTension = new Array(totalBars).fill(0);
15785
16460
  let restSteps = 0;
@@ -15790,9 +16465,9 @@ var draw = (options, resolvedKey, rnd) => {
15790
16465
  let chromaticNotes = 0;
15791
16466
  let sungBars = 0;
15792
16467
  let prevSemi = pick([60, 64, 65, 67, 69, 72, 74, 76], rnd);
15793
- const pickFigure = (gapSteps, scale) => {
16468
+ const pickFigure = (gapSteps, scale2) => {
15794
16469
  const fits = ANSWER_FIGURES.filter(
15795
- (f) => f.reduce((sum, v) => sum + scale(Math.abs(v)), 0) <= gapSteps
16470
+ (f) => f.reduce((sum, v) => sum + scale2(Math.abs(v)), 0) <= gapSteps
15796
16471
  );
15797
16472
  return fits.length === 0 ? null : pick(fits, rnd);
15798
16473
  };
@@ -15850,8 +16525,9 @@ var draw = (options, resolvedKey, rnd) => {
15850
16525
  slots,
15851
16526
  tones,
15852
16527
  style,
16528
+ scale,
15853
16529
  motifContour,
15854
- semitoneToDegree(headSemi),
16530
+ semitoneToDegree(scale, headSemi),
15855
16531
  contourOffset,
15856
16532
  repeatShift,
15857
16533
  headWeight,
@@ -15867,7 +16543,8 @@ var draw = (options, resolvedKey, rnd) => {
15867
16543
  ...plannedDegrees[source]
15868
16544
  );
15869
16545
  const landing = units2[unitOf(bar)].landing;
15870
- if (landing !== null && barInUnit(bar) === 1) landOn(degrees, landing);
16546
+ if (landing !== null && barInUnit(bar) === 1)
16547
+ landOn(scale, degrees, landing);
15871
16548
  plannedDegrees[bar] = [...degrees];
15872
16549
  const isMotifBar = source !== null || role === "motif" || role === "sequence" || role === "climax" || role === "answer";
15873
16550
  let fitted = degrees;
@@ -15879,6 +16556,7 @@ var draw = (options, resolvedKey, rnd) => {
15879
16556
  tones,
15880
16557
  prevSemi,
15881
16558
  quarterSteps,
16559
+ scale,
15882
16560
  style.pentatonicMotif,
15883
16561
  motifShiftMemo.get(shiftKey) ?? null
15884
16562
  );
@@ -15886,6 +16564,7 @@ var draw = (options, resolvedKey, rnd) => {
15886
16564
  motifShiftMemo.set(shiftKey, r.shift);
15887
16565
  }
15888
16566
  const pitches = shapeBar(fitted, slots, tones, prevSemi, {
16567
+ scale,
15889
16568
  allowLeap: role === "climax",
15890
16569
  allowArpeggio: role === "climax" || role === "run" && style.runShape === "broken" || role === "cadence" && style.cadenceShape !== "descend",
15891
16570
  quarterSteps,
@@ -15900,16 +16579,19 @@ var draw = (options, resolvedKey, rnd) => {
15900
16579
  rnd,
15901
16580
  preserveContour: isMotifBar
15902
16581
  });
15903
- if (landing !== null && barInUnit(bar) === 1) landPitch(pitches, landing);
15904
- const fifths = pitches.map(diatonicFifth);
15905
- applyChromatic(pitches, fifths, slots, tones, {
16582
+ if (landing !== null && barInUnit(bar) === 1)
16583
+ landPitch(scale, pitches, landing);
16584
+ const fifths = pitches.map((semi) => scaleFifth(scale, semi));
16585
+ applyChromatic(scale, pitches, fifths, slots, tones, {
15906
16586
  affinity: style.chromaticAffinity,
15907
16587
  quarterSteps,
15908
16588
  shortSteps: scaleStep(EIGHTH),
15909
16589
  keepLast: landing !== null && barInUnit(bar) === 1,
15910
16590
  rnd
15911
16591
  });
15912
- const silent = units2[unitOf(bar)].source === "silent";
16592
+ const unitSource = units2[unitOf(bar)].source;
16593
+ const isSolo = unitSource === "solo";
16594
+ const silent = unitSource === "silent" || isSolo;
15913
16595
  const barHead = pitches[0];
15914
16596
  let tensionSum = 0;
15915
16597
  let tensionSteps = 0;
@@ -15917,7 +16599,7 @@ var draw = (options, resolvedKey, rnd) => {
15917
16599
  const semi = pitches[i2];
15918
16600
  tensionSum += (3 - toneWeight(semi, tones)) / 3 * slots[i2].value;
15919
16601
  tensionSteps += slots[i2].value;
15920
- if (role !== "climax") {
16602
+ if (!silent && role !== "climax") {
15921
16603
  const gap = Math.abs(semi - prevSemi);
15922
16604
  maxLeap = Math.max(maxLeap, gap);
15923
16605
  intervals++;
@@ -15926,10 +16608,25 @@ var draw = (options, resolvedKey, rnd) => {
15926
16608
  }
15927
16609
  const slot = slots[i2];
15928
16610
  if (silent) {
16611
+ if (isSolo) {
16612
+ const ks = barKeyShift[bar];
16613
+ const fifthShiftSolo = ks === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(ks % 12 + 12) % 12];
16614
+ solo.push({
16615
+ startStep: barStart + slot.at,
16616
+ pitchUnits: spelledToUnits(
16617
+ semi + ks,
16618
+ fifths[i2] + fifthShiftSolo,
16619
+ edo
16620
+ ),
16621
+ durationSteps: slot.value,
16622
+ // ソロは前に出る声部なので、歌メロより気持ち強く弾く。
16623
+ velocity: slot.at === 0 ? 116 : slot.value <= scaleStep(SIXTEENTH) ? 96 : 106
16624
+ });
16625
+ }
15929
16626
  prevSemi = semi;
15930
16627
  continue;
15931
16628
  }
15932
- if (!DIATONIC_PCS.has(pitchClass(semi))) chromaticNotes++;
16629
+ if (!scalePcs(scale).has(pitchClass(semi))) chromaticNotes++;
15933
16630
  const k = barKeyShift[bar];
15934
16631
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15935
16632
  melody.push({
@@ -15983,7 +16680,7 @@ var draw = (options, resolvedKey, rnd) => {
15983
16680
  subSemi = pushSub(
15984
16681
  cursor,
15985
16682
  step,
15986
- i2 === 0 ? subSemi : walk(subSemi, subDir)
16683
+ i2 === 0 ? subSemi : walk(scale, subSemi, subDir)
15987
16684
  );
15988
16685
  placed++;
15989
16686
  }
@@ -16007,7 +16704,7 @@ var draw = (options, resolvedKey, rnd) => {
16007
16704
  subSemi = pushSub(
16008
16705
  cursor,
16009
16706
  step,
16010
- index === 0 ? subSemi : walk(subSemi, subDir * (index % 2 === 0 ? 1 : -1))
16707
+ index === 0 ? subSemi : walk(scale, subSemi, subDir * (index % 2 === 0 ? 1 : -1))
16011
16708
  );
16012
16709
  index++;
16013
16710
  }
@@ -16017,7 +16714,11 @@ var draw = (options, resolvedKey, rnd) => {
16017
16714
  pushSub(0, stepsPerBar, subSemi);
16018
16715
  } else if (subStyle === "long-short") {
16019
16716
  subSemi = pushSub(0, scaleStep(DOT_HALF), subSemi);
16020
- pushSub(scaleStep(DOT_HALF), scaleStep(QUARTER), walk(subSemi, subDir));
16717
+ pushSub(
16718
+ scaleStep(DOT_HALF),
16719
+ scaleStep(QUARTER),
16720
+ walk(scale, subSemi, subDir)
16721
+ );
16021
16722
  } else if (role === "cadence") {
16022
16723
  pushSub(0, stepsPerBar, subSemi);
16023
16724
  } else {
@@ -16032,6 +16733,7 @@ var draw = (options, resolvedKey, rnd) => {
16032
16733
  const written = [];
16033
16734
  for (let i2 = 0; i2 < slots.length; i2++) {
16034
16735
  const hTone = harmonyPitch(
16736
+ scale,
16035
16737
  pitches[i2],
16036
16738
  tones,
16037
16739
  last,
@@ -16102,6 +16804,7 @@ var draw = (options, resolvedKey, rnd) => {
16102
16804
  const nextTones = chordTones(progression[(bar + 1) % totalBars]);
16103
16805
  const A = clampSemi(
16104
16806
  walk(
16807
+ scale,
16105
16808
  nextTones[0] ? clampSemi(nextTones[0].semi, BASS_LOW, BASS_HIGH) : R,
16106
16809
  -1
16107
16810
  ),
@@ -16158,7 +16861,7 @@ var draw = (options, resolvedKey, rnd) => {
16158
16861
  let bassCursor = 0;
16159
16862
  for (const [semi, value] of bassCell) {
16160
16863
  const len = scaleStep(value);
16161
- const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : degreeToPitch(semitoneToDegree(semi)).fifth;
16864
+ const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : scaleFifth(scale, semi);
16162
16865
  const k = barKeyShift[bar];
16163
16866
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
16164
16867
  barBass.push({
@@ -16220,7 +16923,7 @@ var draw = (options, resolvedKey, rnd) => {
16220
16923
  const after = melody[i2 + 2];
16221
16924
  if (after && Math.floor(after.startStep / stepsPerBar) === barIdx) {
16222
16925
  const afterSemi = Math.round(after.pitchUnits / UNITS_PER_SEMITONE) - (barKeyShift[barIdx] ?? 0);
16223
- if (!DIATONIC_PCS.has(pitchClass(afterSemi))) continue;
16926
+ if (!scalePcs(scale).has(pitchClass(afterSemi))) continue;
16224
16927
  }
16225
16928
  const nextRole = barRoles[barIdx];
16226
16929
  let tieProb = 0.3;
@@ -16241,11 +16944,13 @@ var draw = (options, resolvedKey, rnd) => {
16241
16944
  melodyDurations[i2] += melodyDurations[i2 + 1];
16242
16945
  melodyDurations.splice(i2 + 1, 1);
16243
16946
  }
16244
- const hCurIdx = harmony.findIndex((h) => h.startStep === cur.startStep);
16245
- const hNxtIdx = harmony.findIndex((h) => h.startStep === nxt.startStep);
16246
- if (hCurIdx >= 0 && hNxtIdx >= 0) {
16247
- harmony[hCurIdx].durationSteps += harmony[hNxtIdx].durationSteps;
16248
- harmony.splice(hNxtIdx, 1);
16947
+ for (const voice of [harmony, harmony2]) {
16948
+ const nxtIdx = voice.findIndex((h) => h.startStep === nxt.startStep);
16949
+ if (nxtIdx < 0) continue;
16950
+ const curIdx = voice.findIndex((h) => h.startStep === cur.startStep);
16951
+ if (curIdx >= 0)
16952
+ voice[curIdx].durationSteps += voice[nxtIdx].durationSteps;
16953
+ voice.splice(nxtIdx, 1);
16249
16954
  }
16250
16955
  i2--;
16251
16956
  }
@@ -16299,7 +17004,7 @@ var draw = (options, resolvedKey, rnd) => {
16299
17004
  };
16300
17005
  const shiftUnits = semitonesToUnits(rootShift, edo);
16301
17006
  if (shiftUnits !== 0)
16302
- for (const list of [melody, submelody, bass, harmony, harmony2, pad])
17007
+ for (const list of [melody, submelody, bass, harmony, harmony2, pad, solo])
16303
17008
  for (const n of list) n.pitchUnits = n.pitchUnits + shiftUnits;
16304
17009
  const octave = useOctaveLayer ? melody.filter(
16305
17010
  (n) => n.durationSteps >= quarterSteps || rnd() < octaveCoverage
@@ -16310,6 +17015,8 @@ var draw = (options, resolvedKey, rnd) => {
16310
17015
  rootShift,
16311
17016
  keyName: resolvedKey.keyName,
16312
17017
  keyLabel: resolvedKey.keyLabel,
17018
+ scaleId: scale.id,
17019
+ scaleLabel: scale.label,
16313
17020
  moodLabel: resolvedKey.moodLabel,
16314
17021
  bpm,
16315
17022
  sections: sectionPlan,
@@ -16329,6 +17036,7 @@ var draw = (options, resolvedKey, rnd) => {
16329
17036
  harmony2,
16330
17037
  octave,
16331
17038
  pad,
17039
+ solo,
16332
17040
  melodyDurations,
16333
17041
  restSteps,
16334
17042
  totalSteps: Math.max(1, sungBars) * stepsPerBar,
@@ -16458,12 +17166,17 @@ var composeSong = (options) => {
16458
17166
  const recent = options.recent ?? [];
16459
17167
  const count = Math.max(1, options.drawCount ?? DRAW_COUNT);
16460
17168
  const resolvedKey = resolveComposeKey(options.baseKey, rnd);
17169
+ const scale = resolveComposeScale(
17170
+ options.scale,
17171
+ resolvedKey.mode === "minor",
17172
+ rnd
17173
+ );
16461
17174
  let best = null;
16462
17175
  let bestScore = Number.NEGATIVE_INFINITY;
16463
17176
  let bestIsValid = false;
16464
17177
  let rejected = 0;
16465
17178
  for (let attempt = 1; attempt <= count; attempt++) {
16466
- const d = draw(options, resolvedKey, rnd);
17179
+ const d = draw(options, resolvedKey, scale, rnd);
16467
17180
  const { stats, ok } = evaluate(d, recent);
16468
17181
  if (!ok) rejected++;
16469
17182
  const better = ok === bestIsValid ? stats.score > bestScore : ok;
@@ -16471,15 +17184,18 @@ var composeSong = (options) => {
16471
17184
  bestScore = stats.score;
16472
17185
  bestIsValid = ok;
16473
17186
  best = {
16474
- // ドラム・楽器は勝った候補にだけ後から付ける(メロディに依存しないので
16475
- // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
17187
+ // ドラム・楽器・編曲プランは勝った候補にだけ後から付ける(メロディに
17188
+ // 依存しないので候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
16476
17189
  drum: "",
16477
17190
  instrument: "",
17191
+ arrange: EMPTY_ARRANGE,
16478
17192
  chordProgression: d.chordProgression,
16479
17193
  chordPattern: d.chordPattern,
16480
17194
  rootShift: d.rootShift,
16481
17195
  keyName: d.keyName,
16482
17196
  keyLabel: d.keyLabel,
17197
+ scaleId: scale.id,
17198
+ scaleLabel: scale.label,
16483
17199
  moodLabel: d.moodLabel,
16484
17200
  bpm: d.bpm,
16485
17201
  sections: d.sections,
@@ -16493,6 +17209,7 @@ var composeSong = (options) => {
16493
17209
  harmony2: d.harmony2,
16494
17210
  octave: d.octave,
16495
17211
  pad: d.pad,
17212
+ solo: d.solo,
16496
17213
  stats: { ...stats, attempts: attempt, rejected }
16497
17214
  };
16498
17215
  }
@@ -16501,6 +17218,7 @@ var composeSong = (options) => {
16501
17218
  result.stats.rejected = rejected;
16502
17219
  result.drum = pickBuiltinDrum(result, rnd);
16503
17220
  result.instrument = pickBuiltinInstrument(result, rnd);
17221
+ result.arrange = buildArrangePlan(result, rnd);
16504
17222
  return result;
16505
17223
  };
16506
17224
  var pickBuiltinDrum = (song, rnd) => {
@@ -16512,6 +17230,80 @@ var pickBuiltinDrum = (song, rnd) => {
16512
17230
  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"];
16513
17231
  return pick(pool, rnd);
16514
17232
  };
17233
+ var EMPTY_ARRANGE = {
17234
+ backing: [],
17235
+ sparkle: null,
17236
+ padSections: [],
17237
+ lead: null,
17238
+ bassLayer: null
17239
+ };
17240
+ var LOUD_KINDS = ["prechorus", "chorus", "bridge"];
17241
+ var QUIET_KINDS = [
17242
+ "intro",
17243
+ "verse",
17244
+ "interlude",
17245
+ "drop_chorus",
17246
+ "outro"
17247
+ ];
17248
+ var CHORUS_KINDS = ["chorus", "drop_chorus"];
17249
+ var buildArrangePlan = (song, rnd) => {
17250
+ const present = new Set(song.sections.map((s) => s.kind));
17251
+ const narrow = (kinds) => {
17252
+ const hit = kinds.filter((k) => present.has(k));
17253
+ return hit.length === 0 ? null : hit;
17254
+ };
17255
+ const base = {
17256
+ pattern: song.chordPattern,
17257
+ sections: null,
17258
+ octave: 0
17259
+ };
17260
+ const others = chordPatternPool(song.bpm).filter((p) => p !== base.pattern);
17261
+ const backing = [base];
17262
+ const addKinds = pick(
17263
+ [LOUD_KINDS, CHORUS_KINDS, LOUD_KINDS, QUIET_KINDS],
17264
+ rnd
17265
+ );
17266
+ const second = pick(others, rnd);
17267
+ backing.push({
17268
+ pattern: second,
17269
+ sections: narrow(addKinds),
17270
+ octave: 0
17271
+ });
17272
+ if (rnd() < 0.55) {
17273
+ const rest = others.filter((p) => p !== second);
17274
+ backing.push({
17275
+ pattern: pick(rest.length > 0 ? rest : others, rnd),
17276
+ // 2本目と逆側へ置く(両方サビに寄せると、サビだけ団子になる)。
17277
+ sections: narrow(addKinds === QUIET_KINDS ? LOUD_KINDS : QUIET_KINDS),
17278
+ octave: 0
17279
+ });
17280
+ }
17281
+ const sparklePool = chordPatternPool(song.bpm).filter(
17282
+ (p) => p !== "block" && !backing.some((b) => b.pattern === p)
17283
+ );
17284
+ const sparkle = sparklePool.length > 0 && rnd() < 0.7 ? {
17285
+ pattern: pick(sparklePool, rnd),
17286
+ sections: narrow(pick([LOUD_KINDS, CHORUS_KINDS], rnd)) ?? CHORUS_KINDS,
17287
+ octave: 1
17288
+ } : null;
17289
+ const padSections = narrow(
17290
+ pick(
17291
+ [
17292
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17293
+ ["chorus", "drop_chorus"],
17294
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17295
+ ["bridge", "chorus"]
17296
+ ],
17297
+ rnd
17298
+ )
17299
+ ) ?? [];
17300
+ const lead = rnd() < 0.75 ? {
17301
+ sections: narrow(pick([CHORUS_KINDS, LOUD_KINDS], rnd)) ?? CHORUS_KINDS,
17302
+ octave: rnd() < 0.5 ? 0 : 1
17303
+ } : null;
17304
+ const bassLayer = rnd() < 0.25 ? { sections: narrow(CHORUS_KINDS) ?? CHORUS_KINDS, octave: 1 } : null;
17305
+ return { backing, sparkle, padSections, lead, bassLayer };
17306
+ };
16515
17307
  var pickBuiltinInstrument = (song, rnd) => {
16516
17308
  const eighth = BASE_STEPS_PER_BAR / 8;
16517
17309
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
@@ -17042,6 +17834,34 @@ var buildUI = (target, options) => {
17042
17834
  <span class="dtm-grow"></span>
17043
17835
  <span class="dtm-hint" data-dtm="compose-key-hint"></span>
17044
17836
  </div>
17837
+ <div class="dtm-row" data-dtm="compose-scale-row">
17838
+ <span class="dtm-label">\u97F3\u968E</span>
17839
+ <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">
17840
+ <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>
17841
+ <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>
17842
+ <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
17843
+ <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>
17844
+ <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>
17845
+ <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>
17846
+ <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>
17847
+ <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>
17848
+ </optgroup>
17849
+ <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
17850
+ <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>
17851
+ <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>
17852
+ <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>
17853
+ <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>
17854
+ </optgroup>
17855
+ <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
17856
+ <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>
17857
+ <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>
17858
+ <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>
17859
+ <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>
17860
+ </optgroup>
17861
+ </select>
17862
+ <span class="dtm-grow"></span>
17863
+ <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
17864
+ </div>
17045
17865
  </div>
17046
17866
  <div class="dtm-row">
17047
17867
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
@@ -17244,6 +18064,8 @@ var buildUI = (target, options) => {
17244
18064
  composeSectionsLen: sel("compose-sections-len"),
17245
18065
  composeKey: sel("compose-key"),
17246
18066
  composeKeyHint: sel("compose-key-hint"),
18067
+ composeScale: sel("compose-scale"),
18068
+ composeScaleHint: sel("compose-scale-hint"),
17247
18069
  macroComposeVocal: sel("macro-compose-vocal"),
17248
18070
  macroComposeInfo: sel("macro-compose-info"),
17249
18071
  macroClear: sel("macro-clear"),
@@ -17287,7 +18109,12 @@ var INSTRUMENT_PRESETS = {
17287
18109
  melody: "Acoustic Grand Piano",
17288
18110
  submelody: "Vibraphone",
17289
18111
  bass: "Electric Bass (finger)",
17290
- chord: "Pad 2 (warm)"
18112
+ chord: "Pad 2 (warm)",
18113
+ solo: "Electric Guitar (clean)",
18114
+ // グロッケンは実物の音域が G5(79)〜 の高音楽器で、旋律の音域へそのまま置くと
18115
+ // 金切り音になる。**外すのではなくオクターブで下げて使う**
18116
+ // ({@link GM_BRIGHT_CEILING} / {@link fitInstrumentOctave})。
18117
+ chorusLead: "Glockenspiel"
17291
18118
  },
17292
18119
  acoustic: {
17293
18120
  displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
@@ -17295,7 +18122,9 @@ var INSTRUMENT_PRESETS = {
17295
18122
  melody: "Acoustic Guitar (steel)",
17296
18123
  submelody: "Harmonica",
17297
18124
  bass: "Acoustic Bass",
17298
- chord: "Acoustic Guitar (nylon)"
18125
+ chord: "Acoustic Guitar (nylon)",
18126
+ solo: "Overdriven Guitar",
18127
+ chorusLead: "String Ensemble 1"
17299
18128
  },
17300
18129
  jazz_night: {
17301
18130
  displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
@@ -17303,7 +18132,9 @@ var INSTRUMENT_PRESETS = {
17303
18132
  melody: "Electric Piano 1",
17304
18133
  submelody: "Flute",
17305
18134
  bass: "Acoustic Bass",
17306
- chord: "Electric Guitar (jazz)"
18135
+ chord: "Electric Guitar (jazz)",
18136
+ solo: "Tenor Sax",
18137
+ chorusLead: "Muted Trumpet"
17307
18138
  },
17308
18139
  // --- MODERN & VIBE: エッジの効いた現代的な響き ---
17309
18140
  synth_pop: {
@@ -17312,7 +18143,9 @@ var INSTRUMENT_PRESETS = {
17312
18143
  melody: "Lead 2 (sawtooth)",
17313
18144
  submelody: "Lead 4 (chiff)",
17314
18145
  bass: "Synth Bass 2",
17315
- chord: "Pad 3 (polysynth)"
18146
+ chord: "Pad 3 (polysynth)",
18147
+ solo: "Distortion Guitar",
18148
+ chorusLead: "Synth Brass 1"
17316
18149
  },
17317
18150
  cyber_punk: {
17318
18151
  displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
@@ -17320,7 +18153,9 @@ var INSTRUMENT_PRESETS = {
17320
18153
  melody: "Lead 8 (bass + lead)",
17321
18154
  submelody: "Lead 5 (charang)",
17322
18155
  bass: "Synth Bass 2",
17323
- chord: "Pad 8 (sweep)"
18156
+ chord: "Pad 8 (sweep)",
18157
+ solo: "Distortion Guitar",
18158
+ chorusLead: "Lead 7 (fifths)"
17324
18159
  },
17325
18160
  rock: {
17326
18161
  displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
@@ -17328,7 +18163,9 @@ var INSTRUMENT_PRESETS = {
17328
18163
  melody: "Distortion Guitar",
17329
18164
  submelody: "Rock Organ",
17330
18165
  bass: "Electric Bass (pick)",
17331
- chord: "Overdriven Guitar"
18166
+ chord: "Overdriven Guitar",
18167
+ solo: "Distortion Guitar",
18168
+ chorusLead: "Brass Section"
17332
18169
  },
17333
18170
  // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
17334
18171
  orchestra: {
@@ -17337,7 +18174,9 @@ var INSTRUMENT_PRESETS = {
17337
18174
  melody: "French Horn",
17338
18175
  submelody: "Pizzicato Strings",
17339
18176
  bass: "Cello",
17340
- chord: "Tremolo Strings"
18177
+ chord: "Tremolo Strings",
18178
+ solo: "Violin",
18179
+ chorusLead: "Trumpet"
17341
18180
  },
17342
18181
  japanese_wa: {
17343
18182
  displayName: "\u548C\u98A8\u30FB\u96C5",
@@ -17345,7 +18184,9 @@ var INSTRUMENT_PRESETS = {
17345
18184
  melody: "Koto",
17346
18185
  submelody: "Shamisen",
17347
18186
  bass: "Taiko Drum",
17348
- chord: "Shakuhachi"
18187
+ chord: "Shakuhachi",
18188
+ solo: "Shakuhachi",
18189
+ chorusLead: "Glockenspiel"
17349
18190
  },
17350
18191
  arabic_exotic: {
17351
18192
  displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
@@ -17353,7 +18194,9 @@ var INSTRUMENT_PRESETS = {
17353
18194
  melody: "Sitar",
17354
18195
  submelody: "Bagpipe",
17355
18196
  bass: "Fretless Bass",
17356
- chord: "Kalimba"
18197
+ chord: "Kalimba",
18198
+ solo: "Shanai",
18199
+ chorusLead: "Steel Drums"
17357
18200
  },
17358
18201
  // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
17359
18202
  fantasy_rpg: {
@@ -17362,7 +18205,9 @@ var INSTRUMENT_PRESETS = {
17362
18205
  melody: "Ocarina",
17363
18206
  submelody: "Celesta",
17364
18207
  bass: "Timpani",
17365
- chord: "Orchestral Harp"
18208
+ chord: "Orchestral Harp",
18209
+ solo: "Pan Flute",
18210
+ chorusLead: "Choir Aahs"
17366
18211
  },
17367
18212
  ambient_cloud: {
17368
18213
  displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
@@ -17370,7 +18215,9 @@ var INSTRUMENT_PRESETS = {
17370
18215
  melody: "Lead 6 (voice)",
17371
18216
  submelody: "Music Box",
17372
18217
  bass: "Synth Bass 1",
17373
- chord: "Pad 7 (halo)"
18218
+ chord: "Pad 7 (halo)",
18219
+ solo: "Lead 3 (calliope)",
18220
+ chorusLead: "Synth Choir"
17374
18221
  },
17375
18222
  retro_game: {
17376
18223
  displayName: "8-bit \u30EC\u30C8\u30ED",
@@ -17378,8 +18225,99 @@ var INSTRUMENT_PRESETS = {
17378
18225
  melody: "Lead 1 (square)",
17379
18226
  submelody: "Lead 2 (sawtooth)",
17380
18227
  bass: "Synth Bass 1",
17381
- chord: "Clavinet"
17382
- }
18228
+ chord: "Clavinet",
18229
+ solo: "Lead 8 (bass + lead)",
18230
+ chorusLead: "Lead 4 (chiff)"
18231
+ }
18232
+ };
18233
+ var GM_INSTRUMENT_RANGE = {
18234
+ // 鍵盤・音板
18235
+ "Acoustic Grand Piano": [21, 108],
18236
+ "Electric Piano 1": [28, 103],
18237
+ Clavinet: [36, 96],
18238
+ Celesta: [60, 108],
18239
+ Glockenspiel: [79, 108],
18240
+ "Music Box": [72, 108],
18241
+ Vibraphone: [53, 89],
18242
+ Kalimba: [60, 84],
18243
+ "Orchestral Harp": [23, 104],
18244
+ // 弦・撥弦
18245
+ "Acoustic Guitar (steel)": [40, 83],
18246
+ "Acoustic Guitar (nylon)": [40, 83],
18247
+ "Electric Guitar (clean)": [40, 86],
18248
+ "Electric Guitar (jazz)": [40, 86],
18249
+ "Overdriven Guitar": [40, 88],
18250
+ "Distortion Guitar": [40, 88],
18251
+ Violin: [55, 103],
18252
+ Cello: [36, 76],
18253
+ "String Ensemble 1": [28, 100],
18254
+ "Tremolo Strings": [28, 100],
18255
+ "Pizzicato Strings": [28, 96],
18256
+ Sitar: [48, 79],
18257
+ Shamisen: [48, 84],
18258
+ Koto: [41, 77],
18259
+ // ベース
18260
+ "Acoustic Bass": [28, 60],
18261
+ "Electric Bass (finger)": [28, 67],
18262
+ "Electric Bass (pick)": [28, 67],
18263
+ "Fretless Bass": [28, 67],
18264
+ "Synth Bass 1": [24, 72],
18265
+ "Synth Bass 2": [24, 72],
18266
+ // 管
18267
+ Flute: [60, 96],
18268
+ "Pan Flute": [60, 91],
18269
+ Shakuhachi: [62, 86],
18270
+ Ocarina: [60, 84],
18271
+ Harmonica: [60, 96],
18272
+ Bagpipe: [62, 86],
18273
+ Shanai: [60, 86],
18274
+ "Tenor Sax": [44, 75],
18275
+ Trumpet: [55, 82],
18276
+ "Muted Trumpet": [55, 82],
18277
+ "French Horn": [41, 77],
18278
+ "Brass Section": [41, 84],
18279
+ // 声・打・オルガン
18280
+ "Choir Aahs": [43, 84],
18281
+ "Synth Choir": [43, 84],
18282
+ "Steel Drums": [55, 86],
18283
+ "Taiko Drum": [30, 60],
18284
+ Timpani: [36, 57],
18285
+ "Rock Organ": [36, 96],
18286
+ // シンセ(実物が無いので一般的な使用域)
18287
+ "Synth Brass 1": [36, 96],
18288
+ "Lead 1 (square)": [36, 96],
18289
+ "Lead 2 (sawtooth)": [36, 96],
18290
+ "Lead 3 (calliope)": [48, 96],
18291
+ "Lead 4 (chiff)": [48, 96],
18292
+ "Lead 5 (charang)": [40, 96],
18293
+ "Lead 6 (voice)": [43, 91],
18294
+ "Lead 7 (fifths)": [36, 84],
18295
+ "Lead 8 (bass + lead)": [28, 91],
18296
+ "Pad 2 (warm)": [24, 96],
18297
+ "Pad 3 (polysynth)": [24, 96],
18298
+ "Pad 7 (halo)": [24, 96],
18299
+ "Pad 8 (sweep)": [24, 96]
18300
+ };
18301
+ var GM_BRIGHT_CEILING = {
18302
+ Glockenspiel: 72,
18303
+ "Tinkle Bell": 72,
18304
+ "Music Box": 79,
18305
+ Celesta: 84,
18306
+ Kalimba: 79,
18307
+ "Steel Drums": 84,
18308
+ "FX 3 (crystal)": 79
18309
+ };
18310
+ var OCTAVE_FIT_TOLERANCE = 3;
18311
+ var fitInstrumentOctave = (semitoneRange, instrument, wanted) => {
18312
+ const range = GM_INSTRUMENT_RANGE[instrument];
18313
+ if (!range || !semitoneRange) return wanted;
18314
+ const hi = Math.min(range[1], GM_BRIGHT_CEILING[instrument] ?? range[1]);
18315
+ let octave = wanted;
18316
+ while (octave > wanted - 2) {
18317
+ if (semitoneRange[1] + octave * 12 <= hi + OCTAVE_FIT_TOLERANCE) break;
18318
+ octave--;
18319
+ }
18320
+ return octave;
17383
18321
  };
17384
18322
 
17385
18323
  // src/macro-state.ts
@@ -17387,6 +18325,7 @@ var MACRO_STORAGE_KEYS = {
17387
18325
  template: "dtm-macro:template",
17388
18326
  sections: "dtm-macro:sections",
17389
18327
  key: "dtm-macro:key",
18328
+ scale: "dtm-macro:scale",
17390
18329
  shift: "dtm-macro:shift",
17391
18330
  transpose: "dtm-macro:transpose"
17392
18331
  };
@@ -19546,6 +20485,7 @@ var COMPOSE_INFO_HTML = `
19546
20485
  <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>
19547
20486
  <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>
19548
20487
  <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>
20488
+ <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>
19549
20489
  </ul>
19550
20490
  <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>
19551
20491
  <h4>\u3067\u304D\u3042\u304C\u308A\u306E\u9078\u3073\u65B9</h4>
@@ -19564,6 +20504,18 @@ var COMPOSE_INFO_HTML = `
19564
20504
  <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>
19565
20505
  </ul>
19566
20506
  <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>
20507
+ <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>
20508
+ <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>
20509
+ <ul>
20510
+ <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>
20511
+ <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>
20512
+ <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>
20513
+ <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>
20514
+ </ul>
20515
+ <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>
20516
+ <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>
20517
+ <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>
20518
+ <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>
19567
20519
  <h4>\u305D\u306E\u307B\u304B</h4>
19568
20520
  <ul>
19569
20521
  <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>
@@ -19830,26 +20782,128 @@ var pickComposeVocal = (exclude) => {
19830
20782
  const list = pool.length > 0 ? pool : COMPOSE_VOCAL_POOL;
19831
20783
  return list[Math.floor(Math.random() * list.length)] ?? "klatt";
19832
20784
  };
19833
- var ADVANCED_COMPOSE_LAYOUT = [
19834
- { index: 0, part: "melody", octave: 0, volume: 104 },
19835
- { index: 1, part: "melody", octave: 1, volume: 62 },
19836
- { index: 2, part: "harmony", octave: 0, volume: 82 },
19837
- { index: 3, part: "submelody", octave: 0, volume: 86 },
19838
- { index: 4, part: "bass", octave: 0, volume: 92 },
19839
- { index: 5, part: "bass", octave: -1, volume: 58 },
19840
- { index: 6, part: "pad", octave: 0, volume: 64 },
19841
- { index: 7, part: "block", octave: 0, volume: 62 },
19842
- { index: 8, part: "arpeggio", octave: 0, volume: 54 },
19843
- { index: 9, part: "offbeat", octave: 0, volume: 50 },
19844
- { index: 10, part: "uwamono", octave: 1, volume: 56 },
19845
- // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
19846
- // 作曲だけならメロディは t0 が全部持つので、ここは空のまま。
19847
- { index: 11, part: "duet", octave: 0, volume: 104 },
19848
- // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
19849
- // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
19850
- { index: 12, part: "harmony2", octave: 0, volume: 74 },
19851
- { index: 13, part: "melody", octave: -1, volume: 56 }
19852
- ];
20785
+ var buildAdvancedLayers = (song, config) => {
20786
+ const { edo, stepsPerBar, preset } = config;
20787
+ const semitoneRange = (notes) => {
20788
+ if (notes.length === 0) return null;
20789
+ let lo = Number.POSITIVE_INFINITY;
20790
+ let hi = Number.NEGATIVE_INFINITY;
20791
+ for (const n of notes) {
20792
+ const semi = n.pitchUnits / UNITS_PER_SEMITONE;
20793
+ if (semi < lo) lo = semi;
20794
+ if (semi > hi) hi = semi;
20795
+ }
20796
+ return [Math.round(lo), Math.round(hi)];
20797
+ };
20798
+ const fit = (notes, slot, wanted) => fitInstrumentOctave(semitoneRange(notes), preset[slot], wanted);
20799
+ const kindAtBar = (bar) => song.sections.find(
20800
+ (sec) => bar >= sec.startBar && bar < sec.startBar + sec.bars
20801
+ )?.kind ?? null;
20802
+ const onlyIn = (notes, kinds) => {
20803
+ if (!kinds) return notes;
20804
+ const want = new Set(kinds);
20805
+ return notes.filter((n) => {
20806
+ const kind = kindAtBar(Math.floor(n.startStep / stepsPerBar));
20807
+ return kind !== null && want.has(kind);
20808
+ });
20809
+ };
20810
+ const chordNotes = (pattern, velocityShift = 0) => buildChordPlacements({
20811
+ edo,
20812
+ chordStr: song.chordProgression,
20813
+ patternType: pattern,
20814
+ rootShift: song.rootShift,
20815
+ bpm: song.bpm,
20816
+ stepsPerBar
20817
+ }).map((p) => ({
20818
+ startStep: p.startStep,
20819
+ pitchUnits: p.pitchUnits,
20820
+ durationSteps: p.durationSteps,
20821
+ velocity: Math.max(30, p.velocity + velocityShift)
20822
+ }));
20823
+ const plan = song.arrange;
20824
+ const leadNotes = plan.lead ? onlyIn(song.melody, plan.lead.sections) : [];
20825
+ const leadOctave = fit(leadNotes, "chorusLead", plan.lead?.octave ?? 0);
20826
+ const leadLayer = {
20827
+ index: 1,
20828
+ notes: leadNotes,
20829
+ octave: leadOctave,
20830
+ // ユニゾンで重ねるときは、オクターブ上より前に出やすいので少し引く。
20831
+ volume: leadOctave === 0 ? 54 : 62,
20832
+ slot: "chorusLead"
20833
+ };
20834
+ const bassNotes = plan.bassLayer ? onlyIn(song.bass, plan.bassLayer.sections) : [];
20835
+ const bassOctave = fit(bassNotes, "bass", plan.bassLayer?.octave ?? 0);
20836
+ const bassLayer = {
20837
+ index: 5,
20838
+ notes: bassOctave === 0 ? [] : bassNotes,
20839
+ octave: bassOctave,
20840
+ volume: 58,
20841
+ slot: "bass"
20842
+ };
20843
+ const padNotes = onlyIn(song.pad, plan.padSections);
20844
+ const layers = [
20845
+ { index: 0, notes: song.melody, octave: 0, volume: 104, slot: "melody" },
20846
+ leadLayer,
20847
+ { index: 2, notes: song.harmony, octave: 0, volume: 82 },
20848
+ {
20849
+ index: 3,
20850
+ notes: song.submelody,
20851
+ octave: 0,
20852
+ volume: 86,
20853
+ slot: "submelody"
20854
+ },
20855
+ { index: 4, notes: song.bass, octave: 0, volume: 92, slot: "bass" },
20856
+ bassLayer,
20857
+ // **パッドは伴奏用の楽器で、伴奏より1.5オクターブ高いところを鳴らす。**
20858
+ // そのまま置くとナイロンギターで10半音、カリンバで9半音、尺八で7半音ぶん
20859
+ // 音域を突き抜ける。楽器に合わせて下げる。
20860
+ {
20861
+ index: 6,
20862
+ notes: padNotes,
20863
+ octave: fit(padNotes, "chord", 0),
20864
+ volume: 64,
20865
+ slot: "chord"
20866
+ }
20867
+ ];
20868
+ const backingVolumes = [62, 54, 50];
20869
+ for (let i2 = 0; i2 < 3; i2++) {
20870
+ const layer = plan.backing[i2];
20871
+ layers.push({
20872
+ index: 7 + i2,
20873
+ notes: layer ? onlyIn(chordNotes(layer.pattern), layer.sections) : [],
20874
+ octave: layer?.octave ?? 0,
20875
+ volume: backingVolumes[i2],
20876
+ slot: "chord"
20877
+ });
20878
+ }
20879
+ const sparkleNotes = plan.sparkle ? onlyIn(chordNotes(plan.sparkle.pattern, -14), plan.sparkle.sections) : [];
20880
+ layers.push(
20881
+ {
20882
+ index: 10,
20883
+ notes: sparkleNotes,
20884
+ octave: fit(sparkleNotes, "chord", plan.sparkle?.octave ?? 0),
20885
+ volume: 56,
20886
+ slot: "chord"
20887
+ },
20888
+ // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
20889
+ { index: 11, notes: [], octave: 0, volume: 104, slot: "melody" },
20890
+ // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
20891
+ // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
20892
+ { index: 12, notes: song.harmony2, octave: 0, volume: 74 },
20893
+ { index: 13, notes: song.octave, octave: -1, volume: 56, slot: "melody" },
20894
+ // **間奏のソロ。** 音が入るのは間奏の小節だけなので、この1本だけを
20895
+ // 別の楽器にしても他のセクションの鳴りは変わらない。歌の音域をそのまま
20896
+ // 渡すと管楽器が上へ抜ける(テナーサックスで10半音)ので、ここも合わせる。
20897
+ {
20898
+ index: 14,
20899
+ notes: song.solo,
20900
+ octave: fit(song.solo, "solo", 0),
20901
+ volume: 100,
20902
+ slot: "solo"
20903
+ }
20904
+ );
20905
+ return layers;
20906
+ };
19853
20907
  var LYRIC_MODEL_CATEGORIES = [
19854
20908
  {
19855
20909
  label: "kusa\u30D7\u30EA\u30BB\u30C3\u30C8",
@@ -20005,7 +21059,7 @@ var mountDAW = (target, options = {}) => {
20005
21059
  showChord,
20006
21060
  showMidiSearch,
20007
21061
  // 「作曲」は simple では役割固定の4トラックへ、advanced では15トラックへ
20008
- // 編曲を展開する({@link ADVANCED_COMPOSE_LAYOUT})。どちらでも出す。
21062
+ // 編曲を展開する({@link buildAdvancedLayers})。どちらでも出す。
20009
21063
  showCompose: true
20010
21064
  });
20011
21065
  refs.masterVolume.value = String(options.masterVolume ?? 50);
@@ -20199,6 +21253,14 @@ var mountDAW = (target, options = {}) => {
20199
21253
  while (customVocalsMap.has(`custom${n}`)) n++;
20200
21254
  return `custom${n}`;
20201
21255
  };
21256
+ const getVocalIconSrc = (lyricModel) => {
21257
+ if (!lyricModel) return void 0;
21258
+ const customDef = customVocalsMap.get(lyricModel);
21259
+ if (customDef !== void 0)
21260
+ return customDef.iconUrl || FALLBACK_VOCAL_ICON;
21261
+ const imgKey = VOICE_IMAGE_KEY[lyricModel.toLowerCase()];
21262
+ return imgKey ? VOICE_IMAGES[imgKey] : void 0;
21263
+ };
20202
21264
  let selectedNotes = [];
20203
21265
  let selectionRect = null;
20204
21266
  let copiedNotes = [];
@@ -20332,6 +21394,7 @@ var mountDAW = (target, options = {}) => {
20332
21394
  vocalTension: t1?.vocalTension ?? 50,
20333
21395
  vocalOctaveUnison: t1?.vocalOctaveUnison ?? "none",
20334
21396
  trackInstrument: t1?.trackInstrument ?? "",
21397
+ composeSlot: null,
20335
21398
  trackCompression: t1?.trackCompression ?? 0,
20336
21399
  trackWidth: t1?.trackWidth ?? 100,
20337
21400
  trackReverbSend: t1?.trackReverbSend ?? 0,
@@ -21383,25 +22446,38 @@ var mountDAW = (target, options = {}) => {
21383
22446
  refs.undoBtn.disabled = !core.canUndo();
21384
22447
  refs.redoBtn.disabled = !core.canRedo();
21385
22448
  };
21386
- const updateTrackPanel = () => {
22449
+ const updateTrackTabs = () => {
21387
22450
  refs.trackTabs.innerHTML = "";
21388
22451
  trackPillEls.clear();
21389
22452
  for (const [i2, t] of trackStates.entries()) {
21390
22453
  const [r, g, b] = t.config.color;
21391
22454
  const isActive = t.config.id === activeTrackId;
21392
22455
  const btn = document.createElement("button");
21393
- btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""}`;
22456
+ const vocalIconSrc = getVocalIconSrc(t.lyricModel);
22457
+ btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""} ${vocalIconSrc ? "dtm-pill--vocal" : ""}`;
21394
22458
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
22459
+ if (vocalIconSrc) {
22460
+ btn.style.setProperty(
22461
+ "--dtm-pill-icon",
22462
+ `url(${JSON.stringify(vocalIconSrc)})`
22463
+ );
22464
+ }
21395
22465
  btn.title = `Track ${i2 + 1}: ${t.config.name}`;
21396
22466
  btn.setAttribute(
21397
22467
  "aria-label",
21398
- `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}`
22468
+ `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}${vocalIconSrc ? "\uFF08\u30DC\u30FC\u30AB\u30EB\u9078\u629E\u4E2D\uFF09" : ""}`
21399
22469
  );
21400
- btn.textContent = String(i2 + 1);
22470
+ const labelEl = document.createElement("span");
22471
+ labelEl.className = "dtm-pill__label";
22472
+ labelEl.textContent = String(i2 + 1);
22473
+ btn.appendChild(labelEl);
21401
22474
  btn.addEventListener("click", () => switchTrack(t.config.id));
21402
22475
  refs.trackTabs.appendChild(btn);
21403
22476
  trackPillEls.set(t.config.id, btn);
21404
22477
  }
22478
+ };
22479
+ const updateTrackPanel = () => {
22480
+ updateTrackTabs();
21405
22481
  const active = getActive();
21406
22482
  const activeIndex = trackStates.findIndex(
21407
22483
  (t) => t.config.id === activeTrackId
@@ -21718,6 +22794,7 @@ var mountDAW = (target, options = {}) => {
21718
22794
  syncInstDisabled();
21719
22795
  instSel.addEventListener("change", () => {
21720
22796
  active.trackInstrument = instSel.value;
22797
+ active.composeSlot = null;
21721
22798
  const trackIndex = trackStates.indexOf(active);
21722
22799
  options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
21723
22800
  persistTrack1(active);
@@ -22121,6 +23198,7 @@ var mountDAW = (target, options = {}) => {
22121
23198
  redrawAll();
22122
23199
  fireLyricsChange(active);
22123
23200
  reloadVoicesForModel(active.lyricModel);
23201
+ updateTrackTabs();
22124
23202
  });
22125
23203
  lyricCustomGuide.addEventListener("click", () => {
22126
23204
  showModal("\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)\u306E\u4F7F\u3044\u65B9", KOE_INFO_HTML);
@@ -22654,6 +23732,7 @@ var mountDAW = (target, options = {}) => {
22654
23732
  }
22655
23733
  trackStates.forEach((t, i2) => {
22656
23734
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
23735
+ t.composeSlot = null;
22657
23736
  const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
22658
23737
  if (t.trackInstrument !== name) {
22659
23738
  t.trackInstrument = name;
@@ -22905,7 +23984,7 @@ var mountDAW = (target, options = {}) => {
22905
23984
  const p = programOfInstrumentName(t.trackInstrument);
22906
23985
  if (p !== null) program = p;
22907
23986
  } else if (!t.lyricModel) {
22908
- const role = DECLARED_ROLE[t.config.id] ?? "melody";
23987
+ const role = t.composeSlot ?? DECLARED_ROLE[t.config.id] ?? "melody";
22909
23988
  const instName = autoPreset[role] ?? autoPreset.melody;
22910
23989
  const p = programOfInstrumentName(instName);
22911
23990
  if (p !== null) program = p;
@@ -23274,7 +24353,7 @@ var mountDAW = (target, options = {}) => {
23274
24353
  if (!stats) continue;
23275
24354
  const role = DECLARED_ROLE[t.config.id] ?? classifyTrackRole(stats, t.config.id === topSingleVoiceId);
23276
24355
  roleByTrackId.set(t.config.id, role);
23277
- const instName = autoPreset[role];
24356
+ const instName = autoPreset[t.composeSlot ?? role] ?? autoPreset[role];
23278
24357
  t.trackInstrument = instName;
23279
24358
  const trackIndex = trackStates.indexOf(t);
23280
24359
  options.onTrackInstrumentChange?.(trackIndex, instName);
@@ -23425,6 +24504,15 @@ var mountDAW = (target, options = {}) => {
23425
24504
  refs.composeKey.value = savedKey;
23426
24505
  }
23427
24506
  }
24507
+ const savedScale = readMacroSetting("scale");
24508
+ if (savedScale && refs.composeScale) {
24509
+ const hasOption = Array.from(refs.composeScale.options).some(
24510
+ (opt) => opt.value === savedScale
24511
+ );
24512
+ if (hasOption) {
24513
+ refs.composeScale.value = savedScale;
24514
+ }
24515
+ }
23428
24516
  const savedShift = readMacroSetting("shift");
23429
24517
  if (savedShift && refs.shiftSelect) {
23430
24518
  const hasOption = Array.from(refs.shiftSelect.options).some(
@@ -23487,6 +24575,20 @@ var mountDAW = (target, options = {}) => {
23487
24575
  });
23488
24576
  updateComposeKeyHint();
23489
24577
  }
24578
+ const updateComposeScaleHint = () => {
24579
+ if (!refs.composeScale || !refs.composeScaleHint) return;
24580
+ const desc = getComposeScaleDescription(refs.composeScale.value);
24581
+ refs.composeScaleHint.textContent = desc;
24582
+ refs.composeScaleHint.title = desc;
24583
+ };
24584
+ const composeScale = refs.composeScale;
24585
+ if (composeScale) {
24586
+ composeScale.addEventListener("change", () => {
24587
+ writeMacroSetting("scale", composeScale.value);
24588
+ updateComposeScaleHint();
24589
+ });
24590
+ updateComposeScaleHint();
24591
+ }
23490
24592
  if (refs.shiftSelect) {
23491
24593
  refs.shiftSelect.addEventListener("change", () => {
23492
24594
  writeMacroSetting("shift", refs.shiftSelect.value);
@@ -23507,6 +24609,7 @@ var mountDAW = (target, options = {}) => {
23507
24609
  sections: selectedComposeSections(),
23508
24610
  template: tmpl,
23509
24611
  baseKey: refs.composeKey?.value ?? "any",
24612
+ scale: refs.composeScale?.value ?? "auto",
23510
24613
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
23511
24614
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
23512
24615
  recent: recentComposeFingerprints
@@ -23540,45 +24643,39 @@ var mountDAW = (target, options = {}) => {
23540
24643
  }
23541
24644
  track.core.endBatch();
23542
24645
  };
24646
+ const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
24647
+ if (shouldAutoInstrument && song.instrument) {
24648
+ currentInstrument = song.instrument;
24649
+ autoComposeInstrument = song.instrument;
24650
+ options.onInstrumentChange?.(song.instrument);
24651
+ }
23543
24652
  if (isAdvanced) {
23544
- for (const layer of ADVANCED_COMPOSE_LAYOUT) {
24653
+ const layers = buildAdvancedLayers(song, {
24654
+ edo: renderConfig.edo,
24655
+ stepsPerBar: renderConfig.stepsPerBar,
24656
+ preset: INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano
24657
+ });
24658
+ for (const layer of layers) {
23545
24659
  const track = trackStates[layer.index];
23546
24660
  if (!track) continue;
23547
- 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({
23548
- edo: renderConfig.edo,
23549
- chordStr: song.chordProgression,
23550
- patternType: "arpeggio",
23551
- rootShift: song.rootShift,
23552
- bpm: song.bpm,
23553
- stepsPerBar: renderConfig.stepsPerBar
23554
- }).map((p) => ({
23555
- startStep: p.startStep,
23556
- pitchUnits: p.pitchUnits,
23557
- durationSteps: p.durationSteps,
23558
- velocity: Math.max(30, p.velocity - 14)
23559
- })) : buildChordPlacements({
23560
- edo: renderConfig.edo,
23561
- chordStr: song.chordProgression,
23562
- patternType: layer.part,
23563
- rootShift: song.rootShift,
23564
- bpm: song.bpm,
23565
- stepsPerBar: renderConfig.stepsPerBar
23566
- }).map((p) => ({
23567
- startStep: p.startStep,
23568
- pitchUnits: p.pitchUnits,
23569
- durationSteps: p.durationSteps,
23570
- velocity: p.velocity
23571
- }));
23572
- writeTrackAt(layer.index, notes);
24661
+ writeTrackAt(layer.index, layer.notes);
23573
24662
  track.trackOctave = layer.octave;
23574
24663
  track.volume = layer.volume;
23575
24664
  track.core.setVolume(layer.volume);
24665
+ track.composeSlot = layer.notes.length > 0 ? layer.slot ?? null : null;
24666
+ if (layer.notes.length === 0 && track.trackInstrument) {
24667
+ track.trackInstrument = "";
24668
+ options.onTrackInstrumentChange?.(layer.index, "");
24669
+ }
23576
24670
  }
23577
24671
  for (let i2 = 0; i2 < trackStates.length; i2++) {
23578
- if (ADVANCED_COMPOSE_LAYOUT.some((l) => l.index === i2)) continue;
24672
+ if (layers.some((l) => l.index === i2)) continue;
23579
24673
  writeTrackAt(i2, []);
24674
+ const t = trackStates[i2];
24675
+ if (t) t.composeSlot = null;
23580
24676
  }
23581
24677
  } else {
24678
+ for (const t of trackStates) t.composeSlot = null;
23582
24679
  writeTrack("melody", song.melody);
23583
24680
  writeTrack("submelody", song.submelody);
23584
24681
  writeTrack("bass", song.bass);
@@ -23595,12 +24692,6 @@ var mountDAW = (target, options = {}) => {
23595
24692
  refs.drumSelect.value = song.drum;
23596
24693
  options.onDrumChange?.(song.drum);
23597
24694
  applyDrumPatternFont(song.drum);
23598
- const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
23599
- if (shouldAutoInstrument && song.instrument) {
23600
- currentInstrument = song.instrument;
23601
- autoComposeInstrument = song.instrument;
23602
- options.onInstrumentChange?.(song.instrument);
23603
- }
23604
24695
  if (withVocal) {
23605
24696
  autoComposeVocalTracks.clear();
23606
24697
  const melodyTrack = isAdvanced ? trackStates[0] : trackStates.find((t) => t.config.id === "melody");
@@ -24837,6 +25928,7 @@ var mountDAW = (target, options = {}) => {
24837
25928
  if (!t) return;
24838
25929
  const name = normalizeInstrumentName(instrumentName);
24839
25930
  t.trackInstrument = name;
25931
+ t.composeSlot = null;
24840
25932
  if (t.config.id === activeTrackId) updateTrackPanel();
24841
25933
  },
24842
25934
  noteToCanvas: (step, pitch) => {
@@ -27106,10 +28198,13 @@ var createDtmStudio = async (options = {}) => {
27106
28198
  0 && (module.exports = {
27107
28199
  A4_HZ,
27108
28200
  A4_UNITS,
28201
+ BLUES_SCALE,
27109
28202
  BREATH_MARK,
27110
28203
  CENTS_PER_UNIT,
27111
28204
  COMPOSE_KEYS,
27112
28205
  COMPOSE_MOOD_GROUPS,
28206
+ COMPOSE_SCALES,
28207
+ COMPOSE_SCALE_IDS,
27113
28208
  CORPUS_BANDS,
27114
28209
  CORPUS_SIZE,
27115
28210
  DAW_CSS,
@@ -27129,7 +28224,11 @@ var createDtmStudio = async (options = {}) => {
27129
28224
  FADE_IN_MARK,
27130
28225
  FADE_OUT_MARK,
27131
28226
  GLOBAL_STORAGE_KEYS,
28227
+ GM_BRIGHT_CEILING,
27132
28228
  GM_INSTRUMENT_NAMES,
28229
+ GM_INSTRUMENT_RANGE,
28230
+ HARMONIC_MINOR_SCALE,
28231
+ HUNGARIAN_SCALE,
27133
28232
  INSTRUMENT_PRESETS,
27134
28233
  KEY_COUNT,
27135
28234
  KOE_BASE_URL,
@@ -27139,6 +28238,7 @@ var createDtmStudio = async (options = {}) => {
27139
28238
  LinkedList,
27140
28239
  MACRO_STORAGE_KEYS,
27141
28240
  MAJOR_KEY_IDS,
28241
+ MAJOR_SCALE,
27142
28242
  MAX_VOCAL_VOLUME,
27143
28243
  MICRO_STEP,
27144
28244
  MINOR_KEY_IDS,
@@ -27207,10 +28307,12 @@ var createDtmStudio = async (options = {}) => {
27207
28307
  featureVector,
27208
28308
  fifthToStep,
27209
28309
  fifthToUnits,
28310
+ fitInstrumentOctave,
27210
28311
  formatMmlMeta,
27211
28312
  freqFromPitch,
27212
28313
  generateRandomPattern,
27213
28314
  getComposeKeyDescription,
28315
+ getComposeScaleDescription,
27214
28316
  getDrumPatternKeys,
27215
28317
  getMidiBPM,
27216
28318
  icon,
@@ -27246,9 +28348,13 @@ var createDtmStudio = async (options = {}) => {
27246
28348
  readGlobalSetting,
27247
28349
  readMacroSections,
27248
28350
  readMacroSetting,
28351
+ resolveCenter,
27249
28352
  resolveComposeKey,
28353
+ resolveComposeScale,
27250
28354
  resolveDrumPattern,
27251
28355
  resolveLoopPoint,
28356
+ scaleDegrees,
28357
+ scaleSize,
27252
28358
  semitonesToUnits,
27253
28359
  shiftNotes,
27254
28360
  showLoadingOverlay,