@onjmin/dtm 2.1.6 → 2.1.9

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
@@ -156,6 +156,7 @@ __export(index_exports, {
156
156
  playNote: () => playNote,
157
157
  playPlacements: () => playPlacements,
158
158
  playSingingMML: () => playSingingMML,
159
+ programOfInstrumentName: () => programOfInstrumentName,
159
160
  readGlobalBool: () => readGlobalBool,
160
161
  readGlobalNumber: () => readGlobalNumber,
161
162
  readGlobalSetting: () => readGlobalSetting,
@@ -339,6 +340,14 @@ async function buildNameToKeyMapping() {
339
340
  return nameToKey;
340
341
  }
341
342
  var GM_INSTRUMENT_NAMES = FONT_NAME_SURIKOV.trim().split("\n").map((line) => line.slice(line.indexOf(" ") + 1));
343
+ var programOfInstrumentName = (name) => {
344
+ if (!name) return null;
345
+ const stripped = name.replace(/\s+/g, "").toLowerCase();
346
+ const idx = GM_INSTRUMENT_NAMES.findIndex(
347
+ (n) => n.replace(/\s+/g, "").toLowerCase() === stripped
348
+ );
349
+ return idx >= 0 ? idx : null;
350
+ };
342
351
 
343
352
  // node_modules/.pnpm/@onjmin+chord-parser@1.1.1/node_modules/@onjmin/chord-parser/dist/index.mjs
344
353
  var SHARP_NAMES = [
@@ -12702,44 +12711,365 @@ var mountChordPlayer = (target, chords, options = {}) => {
12702
12711
  // src/compose-corpus.ts
12703
12712
  var CORPUS_SIZE = 91;
12704
12713
  var CORPUS_BANDS = {
12705
- entropy: [0.127, 0.621, 1.182, 1.754],
12706
- valueKinds: [2, 3, 5.5, 7],
12707
- restRatio: [9e-3, 0.06, 0.167, 0.351],
12708
- leapRatio: [0.255, 0.306, 0.653, 0.959],
12709
- stepRatio: [0.023, 0.202, 0.61, 0.693],
12710
- chromaticRatio: [0, 0.018, 0.121, 0.237],
12711
- maxLeap: [6.5, 9, 15, 19],
12712
- melodyRange: [11, 17, 24, 29.5],
12713
- notesPerBar: [3.192, 5.043, 7.261, 11.995],
12714
- shortNoteRatio: [0.428, 0.756, 0.957, 1],
12715
- sim1: [0.362, 0.483, 0.709, 0.898],
12716
- sim2: [0.397, 0.508, 0.781, 0.909],
12717
- sim4: [0.437, 0.569, 0.786, 0.916],
12718
- sim8: [0.063, 0.585, 0.784, 0.972],
12719
- phraseBreath: [0, 0.058, 0.26, 0.884],
12720
- climaxPosition: [0.026, 0.132, 0.625, 0.92],
12721
- climaxPeaks: [1, 2, 14.5, 26],
12722
- complementarity: [0, 0.013, 0.115, 0.388]
12714
+ entropy: [0.25, 0.711, 1.237, 1.754],
12715
+ valueKinds: [2, 3, 6, 7],
12716
+ restRatio: [0.016, 0.091, 0.223, 0.464],
12717
+ leapRatio: [0.202, 0.28, 0.412, 0.71],
12718
+ stepRatio: [0.188, 0.438, 0.617, 0.693],
12719
+ chromaticRatio: [0, 0.017, 0.121, 0.242],
12720
+ maxLeap: [5, 8, 12, 16],
12721
+ melodyRange: [8, 17, 22, 28],
12722
+ notesPerBar: [2.743, 4.77, 6.473, 11.995],
12723
+ shortNoteRatio: [0.055, 0.753, 0.91, 1],
12724
+ barDensityCv: [0.148, 0.206, 0.328, 0.451],
12725
+ densityCliff: [0, 0, 0.043, 0.125],
12726
+ sim1: [0.345, 0.465, 0.598, 0.803],
12727
+ sim2: [0.389, 0.5, 0.647, 0.845],
12728
+ sim4: [0.437, 0.549, 0.723, 0.882],
12729
+ sim8: [0.063, 0.561, 0.771, 0.972],
12730
+ phraseBreath: [0, 0.115, 0.323, 0.884],
12731
+ turnRatio: [0.4, 0.478, 0.556, 0.769],
12732
+ climaxPosition: [0.016, 0.14, 0.655, 0.92],
12733
+ climaxPeaks: [1, 2, 13.5, 26],
12734
+ complementarity: [0, 0.026, 0.179, 0.405]
12723
12735
  };
12724
12736
  var CORPUS_MEDIANS = {
12725
- entropy: 0.898,
12737
+ entropy: 0.907,
12726
12738
  valueKinds: 4,
12727
- restRatio: 0.116,
12728
- leapRatio: 0.391,
12729
- stepRatio: 0.504,
12730
- chromaticRatio: 0.06,
12739
+ restRatio: 0.149,
12740
+ leapRatio: 0.339,
12741
+ stepRatio: 0.538,
12742
+ chromaticRatio: 0.052,
12731
12743
  maxLeap: 12,
12732
- melodyRange: 21,
12733
- notesPerBar: 5.898,
12734
- shortNoteRatio: 0.842,
12735
- sim1: 0.554,
12736
- sim2: 0.575,
12737
- sim4: 0.676,
12738
- sim8: 0.699,
12739
- phraseBreath: 0.167,
12744
+ melodyRange: 20,
12745
+ notesPerBar: 5.511,
12746
+ shortNoteRatio: 0.816,
12747
+ barDensityCv: 0.279,
12748
+ densityCliff: 0.026,
12749
+ sim1: 0.518,
12750
+ sim2: 0.552,
12751
+ sim4: 0.616,
12752
+ sim8: 0.676,
12753
+ phraseBreath: 0.227,
12754
+ turnRatio: 0.519,
12740
12755
  climaxPosition: 0.273,
12741
- climaxPeaks: 7,
12742
- complementarity: 0.045
12756
+ climaxPeaks: 6,
12757
+ complementarity: 0.075
12758
+ };
12759
+ var CORPUS_CELL_WEIGHTS = {
12760
+ "0,2,4,6,8,10,12,14": 584,
12761
+ "0,2,4,6,8,12,14": 512,
12762
+ "0,4,6,8,10,12,14": 246,
12763
+ "0,8": 187,
12764
+ "0,4,8,10,12,14": 158,
12765
+ "0,6,8,10,12,14": 153,
12766
+ "2,5,10,13": 128,
12767
+ "0,12,14": 123,
12768
+ "0,4,6,8,12,14": 114,
12769
+ "0,4,8,12": 110,
12770
+ "0": 105,
12771
+ "0,2,4,6,8,12": 103,
12772
+ "0,4,8,12,14": 102,
12773
+ "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15": 89,
12774
+ "0,2,4,6,8": 76,
12775
+ "0,6,8,12,14": 58,
12776
+ "0,6,8,14": 48,
12777
+ "0,4,6,8,12": 44,
12778
+ "0,4,8,14": 44,
12779
+ "0,2,4,5,6,8,9,10,12,13,14,15": 44,
12780
+ "2,6,10,14": 42,
12781
+ "0,8,12": 40,
12782
+ "0,2,4,6,8,10,12": 39,
12783
+ "0,3,9,10,11,12,13,14,15": 36,
12784
+ "0,3,6,8,11,14": 32,
12785
+ "0,2,4,6,7,9,10,12,14": 32,
12786
+ "0,2,4,6,10,12,14": 31,
12787
+ "0,10,12,14": 28,
12788
+ "12,14": 27,
12789
+ "0,2,4,6,8,14": 27,
12790
+ "0,6,8,12": 25,
12791
+ "0,4,6,8,10,12": 24,
12792
+ "0,1,2,4,5,6,7,8,9,10,11,12,13,14,15": 24,
12793
+ "2,4,6,8,10,12,14": 22,
12794
+ "0,6,8": 19,
12795
+ "0,12": 19,
12796
+ "8,12": 17,
12797
+ "0,2,4,8,12,14": 16,
12798
+ "0,3,5,6,7,8,11,13,14,15": 16,
12799
+ "0,3,4,5,6,7,8,10,12,14": 16,
12800
+ "0,14": 15,
12801
+ "0,4,8": 15,
12802
+ "0,4,6,8,14": 14,
12803
+ "0,4,6,8,10,11,12,14": 14,
12804
+ "2,4,6,8": 13,
12805
+ "0,4,6,10,12,14": 13,
12806
+ "0,4,6,8": 13,
12807
+ "0,2,4,8,9,11,14,15": 12,
12808
+ "0,4,8,10,11,12,14": 12,
12809
+ "0,2,4,6,8,9,12,14": 12,
12810
+ "0,3,4,6,8,12,14": 12,
12811
+ "0,2,4,6,8,11,14": 12,
12812
+ "0,2,4,5,6,7,8,9,10,11,12,14": 12,
12813
+ "2,4,6,8,12,14": 11,
12814
+ "0,2,4,8,10,12": 11,
12815
+ "0,1,2,3,4,5,6,7,8,9,10,11,12": 11,
12816
+ "0,2,4,6,8,10,11,12,14": 10,
12817
+ "0,4,6,7,8,14,15": 10,
12818
+ "0,8,10,12,14": 10,
12819
+ "0,3,4,6,8,10,12,14": 9,
12820
+ "0,2,3,4,6,8,12,14": 9,
12821
+ "0,2,4,5,6,8,12,14": 9,
12822
+ "0,3,4,6,8,9,10,11,12": 9,
12823
+ "0,3,4,6,8,9,10,11,12,14,15": 9,
12824
+ "0,3,4,6,7,8,10,12,14,15": 9,
12825
+ "0,2,4,5,6,7,8,9,10,12,13,14,15": 9,
12826
+ "0,2,4,8,10,12,14": 8,
12827
+ "0,2,4,6,8,10,12,14,15": 8,
12828
+ "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15": 8,
12829
+ "0,3,6,8,12,14": 7,
12830
+ "0,1,2,3,4,5,6,7,8": 7,
12831
+ "0,4,8,10,12": 7,
12832
+ "0,2,3,4,5,6,7,8": 7,
12833
+ "0,3,4,6,8,12": 6,
12834
+ "0,2,4,6,10,14": 6,
12835
+ "4,12": 6,
12836
+ "0,6,8,10,14": 6,
12837
+ "0,2,3,4,5,6,7,8,9,10,11,12": 6,
12838
+ "0,1,2,3,4,6,7,8,10,12,14,15": 6,
12839
+ "0,3,6,8,12": 6,
12840
+ "2,4,6,8,10,11,12,14": 6,
12841
+ "6,8,10,12,14": 5,
12842
+ "2,6,8,10,12,14": 5,
12843
+ "0,8,12,14": 5,
12844
+ "0,3,4,6,8": 5,
12845
+ "0,8,11,14": 5,
12846
+ "0,3,4,6,8,10,11,12,14": 5,
12847
+ "0,3,6,8": 5,
12848
+ "0,1,4,8,9,12,13": 5,
12849
+ "10,12,14": 5,
12850
+ "0,4,6,8,10,11,12,13,14": 5,
12851
+ "12,13,14,15": 5,
12852
+ "0,2,4,7,8,14,15": 5,
12853
+ "0,4,8,9,10,11,12,13,14,15": 5,
12854
+ "0,4,7,8,14,15": 5,
12855
+ "0,4,8,9,10,11,12": 5,
12856
+ "0,2,6,8,10,12,14": 5,
12857
+ "2,4,6,12,14": 4,
12858
+ "0,2,4,6,7,8,10,12,14": 4,
12859
+ "4,5,7,8,10,12,13,14": 4,
12860
+ "0,2,3,4,5,8,9,12,13": 4,
12861
+ "2,3,4,6,8,12,13,14": 4,
12862
+ "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14": 4,
12863
+ "0,2,4,8,12": 4,
12864
+ "4,6,8,10,12": 4,
12865
+ "0,2,4,5,6,8,12,13,14,15": 4,
12866
+ "0,2,3,4,6,8": 4,
12867
+ "0,4,6,8,10,12,13,14": 4,
12868
+ "0,6,8,12,14,15": 4,
12869
+ "0,4,8,9,10,12,14": 4,
12870
+ "0,3,4,7,8,9,10,11,12": 4,
12871
+ "0,1,2,3,4,5,7,8,9,10,11,12": 4,
12872
+ "2,4,8,12": 4,
12873
+ "0,2,4,6,10,12": 4,
12874
+ "0,3,4,6,7,8,9,10,12,14": 4,
12875
+ "0,3,6,9,11,12": 4,
12876
+ "0,1,4,6,8,12,14": 4,
12877
+ "4,8,12": 4,
12878
+ "2,4,6,14": 3,
12879
+ "0,3,4,8": 3,
12880
+ "0,2,4,12,14": 3,
12881
+ "14": 3,
12882
+ "0,4,6,12,14": 3,
12883
+ "0,6,10,12,14": 3,
12884
+ "4,8,9,11,14": 3,
12885
+ "0,2,4,8,9,11,14": 3,
12886
+ "0,4,8,9,11,14": 3,
12887
+ "0,2,4,5,7,9,11,12": 3,
12888
+ "0,3,6,8,10,12,14": 3,
12889
+ "0,3,4,6,8,11,12,14": 3,
12890
+ "0,1,4,5,8,9,12,13": 3,
12891
+ "0,2,4,5,8,9": 3,
12892
+ "2,3,4,6,8,9,12,14": 3,
12893
+ "0,2,4,8": 3,
12894
+ "0,2,3,4,5,6,8,9": 3,
12895
+ "2,3,4,6,8,10,12,13": 3,
12896
+ "12,14,15": 3,
12897
+ "0,2,3,4,6,7,8,10,12": 3,
12898
+ "0,2,3,4,5,6,7,8,9,10,12": 3,
12899
+ "0,1,2,3,4,5,6,7,8,9,10,11,12,14": 3,
12900
+ "0,2,3,4,6,7,8,9,10,11,12": 3,
12901
+ "0,4,6,12": 3,
12902
+ "0,2,3,6,8,11,14": 3,
12903
+ "0,2,6,8,12,14": 3,
12904
+ "2,4,6,8,14,15": 3,
12905
+ "0,2,4,6,12,14": 2,
12906
+ "2,4,6,10,12,14": 2,
12907
+ "4,6,10,12,14": 2,
12908
+ "0,8,14": 2,
12909
+ "2,4,8,10,12,14": 2,
12910
+ "8": 2,
12911
+ "0,2,4,6,8,9,10,11,12,14": 2,
12912
+ "0,2,4,6,8,12,13,14": 2,
12913
+ "0,6,8,11,12,14": 2,
12914
+ "0,1,2,3,4,5,6,7,8,9,11,12,13,14,15": 2,
12915
+ "0,1,3,4,5,6,7,8,9,10,11,12,13,14,15": 2,
12916
+ "0,6,7,8,10,12": 2,
12917
+ "0,3,6,7,8,10,12": 2,
12918
+ "1,2,3,4,6,8,10,12,13": 2,
12919
+ "0,2,8,10,12,14": 2,
12920
+ "0,2,7,8,9,10": 2,
12921
+ "4,8,9,10,12,13": 2,
12922
+ "0,2,4,7,8,9,10": 2,
12923
+ "2,4,7,8,12,15": 2,
12924
+ "0,4,7,8,10": 2,
12925
+ "0,4,8,9,12,13": 2,
12926
+ "0,2,4,5,6,8,9": 2,
12927
+ "0,1,4": 2,
12928
+ "0,4": 2,
12929
+ "0,3,6,10,12,14": 2,
12930
+ "0,2,6,10,12,14": 2,
12931
+ "0,2,3,4,6,8,14,15": 2,
12932
+ "2,4,6,8,12,14,15": 2,
12933
+ "0,2,4,6,8,9,10,12": 2,
12934
+ "0,3,6,8,10,12": 2,
12935
+ "0,3,4,6,8,10,14": 2,
12936
+ "0,4,8,11,12,14": 2,
12937
+ "0,2,4,6,8,9,10,11,12,13,14": 2,
12938
+ "0,2,4,5,6,7,8,9,10,12": 2,
12939
+ "0,1,2,3,4,6,7,8,9,10,12,14": 2,
12940
+ "0,1,2,4,5,6,8,9,10,11,12,13,14": 2,
12941
+ "0,1,2,3,4,6,7,8,9,10,11,12,13,14": 2,
12942
+ "0,1,2,3,4,6,7,8,9,10,11,12,13,14,15": 2,
12943
+ "0,1,2,4,6,8,9,10,11,12,13,14": 2,
12944
+ "0,1,2,3,4,5,6,8,9,10,11,12,13,14": 2,
12945
+ "0,1,2,3,4,6,7,8,9,10,12,13,14,15": 2,
12946
+ "0,2,4,6,8,9,10,11,12,13,14,15": 2,
12947
+ "0,2,3,4,5,6,8,10,12": 2,
12948
+ "0,2,3,4,5,6,7,8,10,11,12": 2,
12949
+ "0,2,3,4,6,7,8,9,10,12": 2,
12950
+ "0,1,2,3,4,6,7,8,9,10,11,12": 2,
12951
+ "0,4,5,8,12,13": 2,
12952
+ "0,3,4,5,8,11,12,13": 2,
12953
+ "0,2,4,7,8,9": 2,
12954
+ "0,4,6,7,8,12": 2,
12955
+ "0,2,3,4,6,7,8,10,11,12,14": 2,
12956
+ "0,2,3,4,5,6,7,8,10,11,12,13": 2,
12957
+ "12": 2,
12958
+ "0,2,4,6,8,13": 2,
12959
+ "2,4,6,8,11,14": 2,
12960
+ "0,5,8,13": 2,
12961
+ "0,2,4,6,8,10,11,12,13,14": 2,
12962
+ "0,4,6,8,10,11,12,13,14,15": 2,
12963
+ "0,5,8,12": 2,
12964
+ "2,4,6,8,14": 1,
12965
+ "0,6,7,8,14": 1,
12966
+ "0,6,8,14,15": 1,
12967
+ "0,6,7,8,10,11,12,13": 1,
12968
+ "0,6,7,8,9,10,11,12,14": 1,
12969
+ "0,1,2,4,6,7,8,10,11,12,13,14,15": 1,
12970
+ "0,4,8,10,11,12": 1,
12971
+ "0,6,7,8,11,14": 1,
12972
+ "0,2,5,8,11,14": 1,
12973
+ "0,2,4,6,8,10,11,12": 1,
12974
+ "0,11,14": 1,
12975
+ "0,8,11,12,13,14,15": 1,
12976
+ "0,1,12": 1,
12977
+ "0,3,5,8,12": 1,
12978
+ "0,4,8,11,14": 1,
12979
+ "2,6,8,11,14": 1,
12980
+ "0,2,3,4,6,8,12": 1,
12981
+ "0,4,6,8,11,12,14": 1,
12982
+ "0,1,12,14": 1,
12983
+ "0,9,12": 1,
12984
+ "0,3": 1,
12985
+ "0,1": 1,
12986
+ "4,6,8,14": 1,
12987
+ "2,12,14": 1,
12988
+ "2,8,12": 1,
12989
+ "0,3,6,8,11": 1,
12990
+ "10,12,14,15": 1,
12991
+ "0,2,3,6,8,9,11,14": 1,
12992
+ "0,2,4,8,10,11": 1,
12993
+ "0,2,4,6,7,8,10,12,13": 1,
12994
+ "2,3,4,5,8,10,12,13,15": 1,
12995
+ "0,2,3,4,5,6,8,10,11,12": 1,
12996
+ "4,6,8,10,11,12": 1,
12997
+ "0,1,2,3,4,6,8,9,10,13,15": 1,
12998
+ "0,1,2,3,4,10,11,12": 1,
12999
+ "0,3,6,7,8,9,11": 1,
13000
+ "0,2,4,5,8,10,11,15": 1,
13001
+ "0,2,4,6,7,8,9,12,13": 1,
13002
+ "0,1,2,4,5,8,10,12,13": 1,
13003
+ "0,2,3,4,5,6,8,10,11,12,13": 1,
13004
+ "4,6,8,10,11,12,13": 1,
13005
+ "0,3,8,12,14": 1,
13006
+ "0,3,4,5,7,8,9,10,12,14,15": 1,
13007
+ "0,2,4,6,8,9,11": 1,
13008
+ "10,14": 1,
13009
+ "0,2,4,6,9,10,12,14": 1,
13010
+ "0,2,4,6,7,9,10,12": 1,
13011
+ "0,2,3,5,8,10,12,13,14": 1,
13012
+ "0,1,2,3,5,6,7,8,9,10,12,13,14": 1,
13013
+ "2,3,4,6,7,8,12,13,14": 1,
13014
+ "0,1,2,3,6,7,8,10,12,13,14": 1,
13015
+ "2,3,4,5,6,8,9,10,11,12,13,14": 1,
13016
+ "0,2,3,4,5,6,7,8,9,10,11,12,13,14": 1,
13017
+ "2,3,4,6,8,9,10,11,12,13,14,15": 1,
13018
+ "0,3,4,7,8,11,12": 1,
13019
+ "0,4,10,12,14": 1,
13020
+ "0,3,4,7,8,11,12,15": 1,
13021
+ "0,4,12,14": 1,
13022
+ "0,4,12": 1,
13023
+ "0,4,7,8,10,11,12,14": 1,
13024
+ "2,6,8,12": 1,
13025
+ "0,2,4,5,6,8": 1,
13026
+ "2,6,8,10,12": 1,
13027
+ "0,2,4,6,8,12,13,14,15": 1,
13028
+ "0,4,6,8,10,11,12": 1,
13029
+ "0,4,8,10,11,12,13,14": 1,
13030
+ "0,2,3,4,6,7,8,9,10,12,13,14,15": 1,
13031
+ "0,2,4,5,6,8,9,10,11,12,13,14": 1,
13032
+ "0,3,4,7,8,9,10,12,13": 1,
13033
+ "0,1,2,4,6,7,8,9,10,12,13,14,15": 1,
13034
+ "0,2,3,4,5,6,8,9,10,11,12,13,14": 1,
13035
+ "0,4,6,7,8,10,12,14,15": 1,
13036
+ "0,2,4,6,7,8,10,12,14,15": 1,
13037
+ "0,1,2,3,4,5,7,8,10,11,12": 1,
13038
+ "0,2,4,6,7,8,9,10,11,12,14": 1,
13039
+ "0,1,2,4,5,6,7,8,10,12,14,15": 1,
13040
+ "0,2,3,4,5,7,8,9,10,11,12": 1,
13041
+ "0,2,3,4,6,7,8,10,11,12": 1,
13042
+ "0,8,9,10,11,12,13,14,15": 1,
13043
+ "14,15": 1,
13044
+ "2,4,6,8,12": 1,
13045
+ "2,4,8,10,12": 1,
13046
+ "0,2,4,8,9,12,14": 1,
13047
+ "0,4,5,8,9,12": 1,
13048
+ "0,4,8,10,14": 1,
13049
+ "0,6,7,12": 1,
13050
+ "0,4,5,8,11,12,13": 1,
13051
+ "0,2,4,8,9": 1,
13052
+ "2,4,7,8,11,12": 1,
13053
+ "0,4,7,8,11": 1,
13054
+ "0,4,8,12,14,15": 1,
13055
+ "0,1,2,4,5,6,7,8,9,10,12": 1,
13056
+ "0,1,2,3,4,5,6,7,8,9,10,12": 1,
13057
+ "0,2,3,4,5,6,7,8,9,10,12,13,14,15": 1,
13058
+ "0,3,4,7,8,9,11,12": 1,
13059
+ "0,1,2,3,4,5,6,7,8,10,11,12,13": 1,
13060
+ "0,6,8,10,12": 1,
13061
+ "0,2,3,4,6,8,10,11,12,14": 1,
13062
+ "0,2,4,6,8,10,13": 1,
13063
+ "2,6,8": 1,
13064
+ "0,2,3,4,6,7,8,12,14": 1,
13065
+ "2,4,6,8,10,14": 1,
13066
+ "0,2,4": 1,
13067
+ "0,2,4,5,6,7,8,9,10": 1,
13068
+ "0,2,4,5,6,7,8,9,10,12,13,14": 1,
13069
+ "0,3,6,10,14": 1,
13070
+ "0,14,15": 1,
13071
+ "0,6,7,12,14": 1,
13072
+ "0,6,8,10,11,12,14": 1
12743
13073
  };
12744
13074
 
12745
13075
  // src/compose-keys.ts
@@ -13074,8 +13404,7 @@ var resolveComposeKey = (choice, rnd = Math.random) => {
13074
13404
  const target = COMPOSE_KEYS[pickedKeyId];
13075
13405
  const targetMood = COMPOSE_MOOD_GROUPS.find((m) => m.id === target.moodId);
13076
13406
  return {
13077
- mode: void 0,
13078
- // コード進行の長短制約は掛けず従来通りランダム
13407
+ mode: target.mode,
13079
13408
  rootShift: target.rootShift,
13080
13409
  keyName: target.name,
13081
13410
  keyLabel: target.label,
@@ -13202,9 +13531,24 @@ var complementarity = (melody, submelody, opts) => {
13202
13531
  }
13203
13532
  return free / submelody.length;
13204
13533
  };
13534
+ var turnRatio = (notes) => {
13535
+ const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
13536
+ let turns = 0;
13537
+ let intervals = 0;
13538
+ let prevDir = 0;
13539
+ for (let i2 = 1; i2 < sorted.length; i2++) {
13540
+ const dir = Math.sign(sorted[i2].pitchSemi - sorted[i2 - 1].pitchSemi);
13541
+ if (dir === 0) continue;
13542
+ intervals++;
13543
+ if (prevDir !== 0 && dir !== prevDir) turns++;
13544
+ prevDir = dir;
13545
+ }
13546
+ return intervals === 0 ? 0 : turns / intervals;
13547
+ };
13205
13548
  var structureFeatures = (melody, submelody, opts) => {
13206
13549
  const sigs = barSignatures(melody, opts);
13207
13550
  return {
13551
+ turnRatio: turnRatio(melody),
13208
13552
  sim1: selfSimilarity(sigs, 1),
13209
13553
  sim2: selfSimilarity(sigs, 2),
13210
13554
  sim4: selfSimilarity(sigs, 4),
@@ -13214,13 +13558,40 @@ var structureFeatures = (melody, submelody, opts) => {
13214
13558
  complementarity: complementarity(melody, submelody, opts)
13215
13559
  };
13216
13560
  };
13561
+ var barNoteCounts = (notes, opts) => {
13562
+ const out = new Array(Math.max(1, opts.bars)).fill(0);
13563
+ for (const n of notes) {
13564
+ const bar = Math.floor(n.startStep / opts.stepsPerBar);
13565
+ if (bar >= 0 && bar < out.length) out[bar]++;
13566
+ }
13567
+ return out;
13568
+ };
13217
13569
  var densityFeatures = (notes, opts) => {
13218
- if (notes.length === 0) return { notesPerBar: 0, shortNoteRatio: 0 };
13570
+ if (notes.length === 0)
13571
+ return {
13572
+ notesPerBar: 0,
13573
+ shortNoteRatio: 0,
13574
+ barDensityCv: 0,
13575
+ densityCliff: 0
13576
+ };
13219
13577
  const eighth = opts.stepsPerBar / 8;
13220
13578
  const short = notes.filter((n) => n.durationSteps <= eighth).length;
13579
+ const counts = barNoteCounts(notes, opts);
13580
+ const sung = counts.filter((c) => c > 0);
13581
+ const mean2 = sung.reduce((a, b) => a + b, 0) / Math.max(1, sung.length);
13582
+ const variance = sung.reduce((a, b) => a + (b - mean2) ** 2, 0) / Math.max(1, sung.length);
13583
+ let cliffs = 0;
13584
+ let pairs = 0;
13585
+ for (let i2 = 1; i2 < counts.length; i2++) {
13586
+ if (counts[i2 - 1] < 3 || counts[i2] === 0) continue;
13587
+ pairs++;
13588
+ if (counts[i2] <= counts[i2 - 1] * 0.3) cliffs++;
13589
+ }
13221
13590
  return {
13222
13591
  notesPerBar: notes.length / Math.max(1, opts.bars),
13223
- shortNoteRatio: short / notes.length
13592
+ shortNoteRatio: short / notes.length,
13593
+ barDensityCv: mean2 === 0 ? 0 : Math.sqrt(variance) / mean2,
13594
+ densityCliff: pairs === 0 ? 0 : cliffs / pairs
13224
13595
  };
13225
13596
  };
13226
13597
  var tensionFeatures = (barTension, opts) => {
@@ -13516,8 +13887,8 @@ var sectionAt = (plan, bar) => {
13516
13887
 
13517
13888
  // src/compose.ts
13518
13889
  var STEP_SEMITONES = 2;
13519
- var MAX_LEAP_SEMITONES = 12;
13520
- var MAX_BAR_LEAP_SEMITONES = 12;
13890
+ var MAX_LEAP_SEMITONES = 10;
13891
+ var MAX_BAR_LEAP_SEMITONES = 10;
13521
13892
  var HARD = {
13522
13893
  /** メロディが1音も無い、音域が半音未満(同じ音を並べただけ)。 */
13523
13894
  minMelodyRange: 3,
@@ -13543,6 +13914,9 @@ var WEIGHTS = {
13543
13914
  */
13544
13915
  notesPerBar: 0.8,
13545
13916
  shortNoteRatio: 0.6,
13917
+ /** 小節ごとの音数のばらつきと崖。平均だけを見ていて見逃していた偏り。 */
13918
+ barDensityCv: 0.6,
13919
+ densityCliff: 1,
13546
13920
  /** 順次進行の比率。跳躍率だけを見ていると、跳躍の帯の上端に張り付く。 */
13547
13921
  stepRatio: 0.8,
13548
13922
  /** 調の外の音。0のままだと全曲が同じ音階をなぞるだけになる。 */
@@ -13555,10 +13929,15 @@ var WEIGHTS = {
13555
13929
  sim4: 1.4,
13556
13930
  sim8: 1.4,
13557
13931
  phraseBreath: 1,
13932
+ /** 折り返しの多さ。跳躍率・順次進行率が揃っていても、これは別に足りなくなる。 */
13933
+ turnRatio: 0.8,
13558
13934
  climaxPosition: 1,
13559
13935
  climaxPeaks: 1,
13560
13936
  /** メロディとサブメロが呼応しているか。 */
13561
13937
  complementarity: 1,
13938
+ /** サブメロの音数と、その崖。メロディと同じ物差しで見る。 */
13939
+ subDensity: 0.6,
13940
+ subDensityCliff: 0.5,
13562
13941
  // --- 和声(コードが要るのでコーパスからは較正できない) ---
13563
13942
  /** B部でテンションが上がるか。 */
13564
13943
  tensionRise: 1,
@@ -13634,6 +14013,10 @@ var FLAT_NOTE_NAMES = [
13634
14013
  "Bb",
13635
14014
  "B"
13636
14015
  ];
14016
+ var fifthsDistance = (semitones) => {
14017
+ const steps = (semitones * 7 % 12 + 12) % 12;
14018
+ return Math.min(steps, 12 - steps);
14019
+ };
13637
14020
  var SEMITONE_TO_FIFTH_SHIFT = [
13638
14021
  0,
13639
14022
  // 0: C
@@ -13786,6 +14169,22 @@ var SECTION_A_PROGRESSIONS = [
13786
14169
  // ジャズ寄りの2-5-1
13787
14170
  ["Dm7", "G7", "Em7", "Am7"],
13788
14171
  // 2-5-3-6
14172
+ // --- ライン・クリシェ(内声が半音ずつ動く) ---
14173
+ // 和音の1声だけを半音で動かして、和音記号は変わるのに響きは繋がったまま進む。
14174
+ // J-POPのサビ後半やAメロの定番。
14175
+ ["Am", "AmM7", "Am7", "Am6"],
14176
+ // ラ→ソ#→ソ→ファ# の下行
14177
+ ["C", "CM7", "C7", "F"],
14178
+ // ド→シ→シb→ラ
14179
+ ["Am", "AmM7", "Am7", "D7"],
14180
+ // クリシェからドリアンのIVへ
14181
+ ["F", "Fm", "CM7", "Am7"],
14182
+ // サブドミナントマイナーのクリシェ
14183
+ // --- ペダルポイント(ベースを保続する) ---
14184
+ // ベースだけ動かさずに上の和音を変える。緊張感が出る。分数和音のルートを
14185
+ // {@link chordTones} が先頭で返すので、ベースは自然に保続音になる。
14186
+ ["C", "Am/C", "F/C", "G/C"],
14187
+ ["Am", "Em/A", "F/A", "G/A"],
13789
14188
  // --- 借用和音(同主短調・ミクソリディアンから借りる) ---
13790
14189
  // **調の外の和音を進行そのものに持たせる。** ここまでの候補は全部ハ長調/イ短調の
13791
14190
  // ダイアトニックで、セカンダリドミナント以外に調の外の音が出てこなかった。
@@ -13842,10 +14241,34 @@ var SECTION_A2_DERIVATIONS = [
13842
14241
  var SECTION_A3_DERIVATIONS = [
13843
14242
  (a, t) => [a[0], a[1], "G7", t],
13844
14243
  (a, t) => [a[0], "Dm7", "G7", t],
13845
- (a, t) => [a[0], "F", "G7", t],
13846
14244
  (a, t) => [a[0], a[1], "Em7", t],
13847
- (a, t) => [a[0], "FM7", "G7", t]
14245
+ // アーメン終止(IV→I)
14246
+ (a, t) => [a[0], "G7", "F", t],
14247
+ (a, t) => [a[0], "Dm7", "FM7", t],
14248
+ // サブドミナントマイナー終止(IVm→I)
14249
+ (a, t) => [a[0], "G7", "Fm", t],
14250
+ (a, t) => [a[0], "F", "Fm", t]
13848
14251
  ];
14252
+ var SECTION_DECEPTIVE_DERIVATIONS = [
14253
+ (a, t) => [a[0], a[1], "G7", t === "Am" ? "F" : "Am"],
14254
+ (a, t) => [a[0], "Dm7", "G7", t === "Am" ? "FM7" : "Am7"],
14255
+ (a, t) => [a[0], "F", "G7", t === "Am" ? "F" : "Am7"]
14256
+ ];
14257
+ var MODAL_BORROW = {
14258
+ F: "Fm",
14259
+ // IV → IVm(サブドミナントマイナー。いちばん定番)
14260
+ FM7: "Fm7",
14261
+ Am: "Ab",
14262
+ // VIm → ♭VI
14263
+ Am7: "Ab",
14264
+ G: "Bb",
14265
+ // V → ♭VII(ミクソリディアン)
14266
+ G7: "Bb",
14267
+ Em7: "Eb",
14268
+ // IIIm → ♭III
14269
+ Dm7: "Dm7-5"
14270
+ // IIm7 → IIm7♭5(エオリアン)
14271
+ };
13849
14272
  var CHORD_PATTERNS = [
13850
14273
  "block",
13851
14274
  "arpeggio",
@@ -13853,6 +14276,7 @@ var CHORD_PATTERNS = [
13853
14276
  "yatsume",
13854
14277
  "alternating"
13855
14278
  ];
14279
+ var chordPatternPool = (bpm) => bpm <= 130 ? [...CHORD_PATTERNS, "arpeggio-fast"] : CHORD_PATTERNS;
13856
14280
  var CHORD_TONE_WEIGHT = {
13857
14281
  0: 3,
13858
14282
  // ルート
@@ -14030,6 +14454,54 @@ var RHYTHM_CELLS = [
14030
14454
  density: "dense"
14031
14455
  }
14032
14456
  ];
14457
+ var PHRASE_END_CELLS = [
14458
+ // 白玉で受けてから息を継ぐ(いちばん歌らしい終わり方)。
14459
+ { value: [HALF, QUARTER, -QUARTER], density: "sparse" },
14460
+ { value: [DOT_QUARTER, EIGHTH, HALF], density: "sparse" },
14461
+ { value: [QUARTER, DOT_QUARTER, EIGHTH, -QUARTER], density: "sparse" },
14462
+ // 2音で降りてから息を継ぐ。
14463
+ { value: [QUARTER, QUARTER, -HALF], density: "sparse" },
14464
+ { value: [DOT_QUARTER, EIGHTH, -HALF], density: "sparse" },
14465
+ { value: [HALF, -QUARTER, QUARTER], density: "sparse" },
14466
+ // 3音で言い切ってから息を継ぐ。
14467
+ { value: [EIGHTH, EIGHTH, QUARTER, -HALF], density: "sparse" },
14468
+ { value: [QUARTER, EIGHTH, EIGHTH, -HALF], density: "sparse" },
14469
+ { value: [QUARTER, QUARTER, QUARTER, -QUARTER], density: "sparse" },
14470
+ { value: [HALF, EIGHTH, EIGHTH, -QUARTER], density: "sparse" },
14471
+ // 4音で言い切ってから息を継ぐ。周りが7音前後の曲では、2音まで落とすと崖になる。
14472
+ { value: [EIGHTH, EIGHTH, EIGHTH, EIGHTH, -HALF], density: "sparse" },
14473
+ { value: [QUARTER, EIGHTH, EIGHTH, QUARTER, -QUARTER], density: "sparse" },
14474
+ { value: [EIGHTH, EIGHTH, QUARTER, QUARTER, -QUARTER], density: "sparse" }
14475
+ ];
14476
+ var MID_BREATH_CELLS = [
14477
+ { value: [QUARTER, EIGHTH, EIGHTH, QUARTER, -QUARTER], density: "medium" },
14478
+ { value: [EIGHTH, EIGHTH, QUARTER, QUARTER, -QUARTER], density: "medium" },
14479
+ { value: [DOT_QUARTER, EIGHTH, QUARTER, QUARTER], density: "medium" },
14480
+ { value: [QUARTER, QUARTER, EIGHTH, EIGHTH, -QUARTER], density: "medium" },
14481
+ { value: [EIGHTH, EIGHTH, EIGHTH, EIGHTH, HALF], density: "medium" },
14482
+ { value: [QUARTER, EIGHTH, EIGHTH, DOT_QUARTER, EIGHTH], density: "medium" }
14483
+ ];
14484
+ var CELL_GRID = BASE_STEPS_PER_BAR / 16;
14485
+ var onsetKeyOf = (value) => {
14486
+ const on = [];
14487
+ let at = 0;
14488
+ for (const v of value) {
14489
+ if (v > 0) on.push(Math.round(at / CELL_GRID));
14490
+ at += Math.abs(v);
14491
+ }
14492
+ return on.join(",");
14493
+ };
14494
+ var UNSEEN_CELL_WEIGHT = 0.5;
14495
+ var cellEntryCount = /* @__PURE__ */ new Map();
14496
+ var cellWeightCache = /* @__PURE__ */ new Map();
14497
+ var cellWeight = (c) => {
14498
+ const cached = cellWeightCache.get(c.value);
14499
+ if (cached !== void 0) return cached;
14500
+ const key = onsetKeyOf(c.value);
14501
+ const w = (CORPUS_CELL_WEIGHTS[key] ?? UNSEEN_CELL_WEIGHT) / Math.max(1, cellEntryCount.get(key) ?? 1);
14502
+ cellWeightCache.set(c.value, w);
14503
+ return w;
14504
+ };
14033
14505
  var HALF_BAR_FIGURES = [
14034
14506
  [EIGHTH, EIGHTH, EIGHTH, EIGHTH],
14035
14507
  // タタタタ
@@ -14062,6 +14534,14 @@ var HALF_BAR_FIGURES = [
14062
14534
  // 2拍目裏からのアウフタクト(タタ)
14063
14535
  [QUARTER, -EIGHTH, EIGHTH],
14064
14536
  // ター・休タ(8分裏弱起)
14537
+ // **3+3+2(トレシーヨ)。** 参考曲91本で最も多かった「語彙に無い形」で、
14538
+ // 174小節ぶんの穴が空いていた(`scripts/compare-vocabulary.ts` の①)。
14539
+ // 付点8分が `[DOT_EIGHTH, SIXTEENTH]` の対でしか入っておらず、
14540
+ // 付点8分を2つ並べる形が作れなかった。界隈曲・ボカロの推進力の中心。
14541
+ [DOT_EIGHTH, DOT_EIGHTH, EIGHTH],
14542
+ [DOT_EIGHTH, DOT_EIGHTH, EIGHTH],
14543
+ // 4分音符が全部8分裏に来る形(食い)。参考曲で67小節ぶん取りこぼしていた。
14544
+ [-EIGHTH, QUARTER, EIGHTH],
14065
14545
  // 付点8分+16分(タッカ)。歌モノで非常に多用される跳ね・推進力の型。
14066
14546
  [DOT_EIGHTH, SIXTEENTH, EIGHTH, EIGHTH],
14067
14547
  [EIGHTH, EIGHTH, DOT_EIGHTH, SIXTEENTH],
@@ -14227,9 +14707,21 @@ var MOTIF_CELLS = [
14227
14707
  ...buildMotifCells(),
14228
14708
  ...HAND_MOTIF_CELLS
14229
14709
  ];
14710
+ for (const c of [...MOTIF_CELLS, ...RHYTHM_CELLS]) {
14711
+ const key = onsetKeyOf(c.value);
14712
+ cellEntryCount.set(key, (cellEntryCount.get(key) ?? 0) + 1);
14713
+ }
14230
14714
  var groovyCells = (cells, groove, rnd) => {
14231
14715
  const hasSixteenth = (c) => c.value.some((v) => Math.abs(v) <= SIXTEENTH);
14232
- if (groove === "eighth") return cells.filter((c) => !hasSixteenth(c));
14716
+ if (groove === "eighth") {
14717
+ const plain = cells.filter((c) => !hasSixteenth(c));
14718
+ const spiced = cells.filter(hasSixteenth);
14719
+ if (spiced.length === 0) return plain;
14720
+ const out = [...plain];
14721
+ const take = Math.max(1, Math.round(plain.length * 0.1));
14722
+ for (let i2 = 0; i2 < take; i2++) out.push(pick(spiced, rnd));
14723
+ return out;
14724
+ }
14233
14725
  const withSixteenth = cells.filter(hasSixteenth);
14234
14726
  return withSixteenth.length > 0 && rnd() < 0.75 ? withSixteenth : cells;
14235
14727
  };
@@ -14350,8 +14842,35 @@ var nearestChordTone = (targetSemi, tones, minWeight, preferColor = false) => {
14350
14842
  return best;
14351
14843
  };
14352
14844
  var MELODY_LOW = 60;
14353
- var MELODY_HIGH = 84;
14845
+ var MELODY_HIGH = 81;
14354
14846
  var MELODY_CENTER = (MELODY_LOW + MELODY_HIGH) / 2;
14847
+ var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) => {
14848
+ const lo = Math.max(-12, Math.min(offset, 0) - 7);
14849
+ const hi = Math.min(12, Math.max(offset, 0) + 7);
14850
+ let best = null;
14851
+ let bestCost = Number.POSITIVE_INFINITY;
14852
+ for (const tone of tones) {
14853
+ const pc = pitchClass(tone.semi);
14854
+ for (let oct = 0; oct <= 10; oct++) {
14855
+ const semi = pc + oct * 12;
14856
+ const delta = semi - melodySemi;
14857
+ if (delta > hi || delta < lo) continue;
14858
+ const move = prevHarmony === null ? 0 : Math.abs(semi - prevHarmony);
14859
+ const stay = parallel ? move * 0.06 : move <= 2 ? move * 0.04 : 0.1 + (move - 2) * 0.9;
14860
+ let clash = 0;
14861
+ if (against !== null) {
14862
+ const g = Math.abs(semi - against) % 12;
14863
+ clash = g === 3 || g === 4 || g === 8 || g === 9 || g === 7 ? 0 : g === 0 ? 0.5 : 1.2;
14864
+ }
14865
+ const cost = stay + clash + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
14866
+ if (cost < bestCost) {
14867
+ bestCost = cost;
14868
+ best = { semi, fifth: tone.fifth };
14869
+ }
14870
+ }
14871
+ }
14872
+ return best ?? nearestChordTone(melodySemi + offset, tones, 2);
14873
+ };
14355
14874
  var SUBMELODY_LOW = 55;
14356
14875
  var SUBMELODY_HIGH = 74;
14357
14876
  var BASS_LOW = 33;
@@ -14533,9 +15052,12 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14533
15052
  }
14534
15053
  return out;
14535
15054
  };
14536
- var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic) => {
15055
+ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, preferShift) => {
14537
15056
  let best = degrees;
15057
+ let bestShift = 0;
14538
15058
  let bestScore = Number.NEGATIVE_INFINITY;
15059
+ let preferScore = Number.NEGATIVE_INFINITY;
15060
+ let preferMoved = null;
14539
15061
  for (let shift = -3; shift <= 3; shift++) {
14540
15062
  const moved = degrees.map(
14541
15063
  (d) => pentatonic ? pentaToDegree(degreeToPenta(d) + shift) : d + shift
@@ -14557,12 +15079,19 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic) => {
14557
15079
  MELODY_HIGH
14558
15080
  );
14559
15081
  score -= Math.max(0, Math.abs(head - prevSemi) - MAX_BAR_LEAP_SEMITONES);
15082
+ if (shift === preferShift) {
15083
+ preferScore = score;
15084
+ preferMoved = moved;
15085
+ }
14560
15086
  if (score > bestScore) {
14561
15087
  bestScore = score;
15088
+ bestShift = shift;
14562
15089
  best = moved;
14563
15090
  }
14564
15091
  }
14565
- return best;
15092
+ if (preferMoved !== null && preferScore >= bestScore - 3)
15093
+ return { degrees: preferMoved, shift: preferShift };
15094
+ return { degrees: best, shift: bestShift };
14566
15095
  };
14567
15096
  var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14568
15097
  const out = [];
@@ -14735,9 +15264,24 @@ var draw = (options, resolvedKey, rnd) => {
14735
15264
  options.template
14736
15265
  );
14737
15266
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
14738
- if (rnd() < 0.35) {
15267
+ const relativeKinds = /* @__PURE__ */ new Set();
15268
+ let relativeShift = 0;
15269
+ if (rnd() < 0.25) {
15270
+ for (const kind of pick(
15271
+ [["prechorus"], ["bridge"], ["prechorus", "bridge"]],
15272
+ rnd
15273
+ ))
15274
+ relativeKinds.add(kind);
15275
+ if (rnd() < 0.33) relativeShift = resolvedKey.mode === "minor" ? -3 : 3;
15276
+ }
15277
+ const floating = rnd() < 0.12;
15278
+ if (relativeKinds.size > 0 && relativeShift !== 0) {
15279
+ for (const s of sectionPlan)
15280
+ if (relativeKinds.has(s.kind)) s.keyShift = relativeShift;
15281
+ }
15282
+ if (relativeKinds.size === 0 && !floating && rnd() < 0.32) {
14739
15283
  const modType = pick(
14740
- ["chorus_up", "chorus_up", "prechorus_down"],
15284
+ ["chorus_up", "chorus_up", "color", "dominant", "subdominant"],
14741
15285
  rnd
14742
15286
  );
14743
15287
  if (modType === "chorus_up") {
@@ -14746,15 +15290,83 @@ var draw = (options, resolvedKey, rnd) => {
14746
15290
  for (let i2 = 0; i2 < sectionPlan.length; i2++) {
14747
15291
  if (sectionPlan[i2].kind === "chorus") lastChorusIdx = i2;
14748
15292
  }
14749
- if (lastChorusIdx >= 0) {
14750
- for (let i2 = lastChorusIdx; i2 < sectionPlan.length; i2++) {
15293
+ if (lastChorusIdx >= 0)
15294
+ for (let i2 = lastChorusIdx; i2 < sectionPlan.length; i2++)
14751
15295
  sectionPlan[i2].keyShift = shift;
14752
- }
14753
- }
14754
15296
  } else {
14755
- const shift = pick([-2, 3], rnd);
14756
- for (const s of sectionPlan) {
14757
- if (s.kind === "prechorus") s.keyShift = shift;
15297
+ const shift = modType === "dominant" ? 7 : modType === "subdominant" ? 5 : pick([-2, 3], rnd);
15298
+ const kinds = pick(
15299
+ [["prechorus"], ["bridge"], ["prechorus", "bridge"]],
15300
+ rnd
15301
+ );
15302
+ for (const s of sectionPlan)
15303
+ if (kinds.includes(s.kind)) s.keyShift = shift;
15304
+ }
15305
+ }
15306
+ const harmonyFrom = pick(
15307
+ ["chorus", "chorus", "prechorus"],
15308
+ rnd
15309
+ );
15310
+ const harmonyParallel = rnd() < 0.45;
15311
+ const harmonyCoverage = 0.7 + rnd() * 0.3;
15312
+ const useHarmony2 = rnd() < 0.3;
15313
+ const useOctaveLayer = !useHarmony2 && rnd() < 0.25;
15314
+ const octaveCoverage = 0.3 + rnd() * 0.3;
15315
+ const harmonyOffset = useHarmony2 ? (
15316
+ // 3声のときは1声目を下〜ユニゾン側へ寄せ、2声目を上へ回して主旋律を挟む
15317
+ // (参考: チョウチン少女 ch12/ch11/ch13)。
15318
+ pick([-5, -3, 0, 0, 3], rnd)
15319
+ ) : harmonyParallel ? pick([3, 4, 5, 9, 7, 0, -5], rnd) : pick([3, 4, 5, 0, -5, -4, -7], rnd);
15320
+ let prevHarmony = null;
15321
+ let prevHarmony2 = null;
15322
+ const harmony2Offset = harmonyOffset + pick([9, 9, 9, 8, 7], rnd);
15323
+ const harmonyKinds = harmonyFrom === "prechorus" ? ["prechorus", "chorus", "bridge"] : ["chorus"];
15324
+ const duetStyle = pick(
15325
+ [
15326
+ "none",
15327
+ "none",
15328
+ "none",
15329
+ "none",
15330
+ "none",
15331
+ "section",
15332
+ "phrase",
15333
+ "chorus",
15334
+ "verse"
15335
+ ],
15336
+ rnd
15337
+ );
15338
+ const duetOwner = new Array(totalBars).fill(false);
15339
+ if (duetStyle !== "none") {
15340
+ let melodySection = 0;
15341
+ for (const section of sectionPlan) {
15342
+ if (!section.spec.melody) continue;
15343
+ const isChorus = section.kind === "chorus" || section.kind === "drop_chorus";
15344
+ const isVerse = section.kind === "verse";
15345
+ const takeSection = melodySection % 2 === 1;
15346
+ melodySection++;
15347
+ const alternate = duetStyle === "phrase" ? !isChorus : duetStyle === "chorus" ? isChorus : duetStyle === "verse" ? isVerse : false;
15348
+ if (!alternate && !(duetStyle === "section" && takeSection && !isChorus))
15349
+ continue;
15350
+ for (let b = section.startBar; b < section.startBar + section.bars; b++) {
15351
+ if (alternate) {
15352
+ if (Math.floor((b - section.startBar) / 2) % 2 === 1)
15353
+ duetOwner[b] = true;
15354
+ } else duetOwner[b] = true;
15355
+ }
15356
+ }
15357
+ }
15358
+ const duetSpans = [];
15359
+ {
15360
+ let from = -1;
15361
+ for (let b = 0; b <= totalBars; b++) {
15362
+ const owned = b < totalBars && duetOwner[b];
15363
+ if (owned && from < 0) from = b;
15364
+ if (!owned && from >= 0) {
15365
+ const lead = from === 0 ? 0 : scaleStep(
15366
+ pick([0, 0, EIGHTH, EIGHTH, QUARTER, DOT_QUARTER], rnd)
15367
+ );
15368
+ duetSpans.push([from * stepsPerBar - lead, b * stepsPerBar]);
15369
+ from = -1;
14758
15370
  }
14759
15371
  }
14760
15372
  }
@@ -14765,41 +15377,114 @@ var draw = (options, resolvedKey, rnd) => {
14765
15377
  }
14766
15378
  }
14767
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;
14768
- const progA = pick(progAPool, rnd);
15380
+ const withoutTonic = (pool, root) => {
15381
+ const isTonic = (c) => root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15382
+ const out = pool.filter((p) => !p.some(isTonic));
15383
+ return out.length > 0 ? out : pool;
15384
+ };
15385
+ const homeRoot = resolvedKey.mode === "minor" ? "Am" : "C";
15386
+ const progA = pick(
15387
+ floating ? withoutTonic(progAPool, homeRoot) : progAPool,
15388
+ rnd
15389
+ );
14769
15390
  const progBPool = SECTION_B_PROGRESSIONS.filter(
14770
15391
  (p) => p.join("|") !== progA.join("|")
14771
15392
  );
14772
- const progB = pick(progBPool, rnd);
15393
+ const progB = pick(
15394
+ floating ? withoutTonic(progBPool, homeRoot) : progBPool,
15395
+ rnd
15396
+ );
15397
+ const progRelativePool = SECTION_A_PROGRESSIONS.filter(
15398
+ (p) => resolvedKey.mode === "minor" ? !p[0].startsWith("Am") : p[0].startsWith("Am")
15399
+ );
15400
+ const progRelative = pick(
15401
+ floating ? withoutTonic(progRelativePool, homeRoot) : progRelativePool,
15402
+ rnd
15403
+ );
15404
+ const progCPool = SECTION_C_PROGRESSIONS.filter(
15405
+ (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
15406
+ );
14773
15407
  const progC = pick(
14774
- SECTION_C_PROGRESSIONS.filter(
14775
- (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
14776
- ),
15408
+ floating ? withoutTonic(progCPool, homeRoot) : progCPool,
14777
15409
  rnd
14778
15410
  );
14779
15411
  const tonic = progA[0].startsWith("Am") ? "Am" : "C";
14780
15412
  const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
14781
15413
  const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15414
+ const progDeceptive = pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15415
+ let lastChorusBar = -1;
15416
+ for (const section of sectionPlan)
15417
+ if (section.kind === "chorus" || section.kind === "outro")
15418
+ lastChorusBar = section.startBar;
14782
15419
  const progression = [];
14783
15420
  for (const section of sectionPlan) {
14784
- const base = section.spec.progression === "c" ? progC : section.spec.progression === "b" ? progB : progA;
15421
+ const base = relativeKinds.has(section.kind) ? progRelative : section.spec.progression === "c" ? progC : section.spec.progression === "b" ? progB : progA;
14785
15422
  for (let i2 = 0; i2 < section.bars; i2 += 4) {
14786
15423
  const isLastPhrase = i2 + 4 >= section.bars;
14787
15424
  if (!isLastPhrase) {
14788
15425
  progression.push(...base);
14789
15426
  continue;
14790
15427
  }
14791
- if (section.kind === "prechorus") progression.push(...progHalf);
15428
+ if (relativeKinds.has(section.kind)) progression.push(...base);
15429
+ else if (section.kind === "prechorus") progression.push(...progHalf);
14792
15430
  else if (section.kind === "chorus" || section.kind === "outro")
14793
- progression.push(...progFull);
15431
+ progression.push(
15432
+ ...floating ? progHalf : section.startBar === lastChorusBar ? progFull : progDeceptive
15433
+ );
14794
15434
  else progression.push(...base);
14795
15435
  }
14796
15436
  }
14797
15437
  progression.length = totalBars;
15438
+ const PIVOT_CHORDS = {
15439
+ 7: ["C", "Em7", "G", "Am"],
15440
+ // 属調(ト長調)と共通
15441
+ 5: ["C", "Dm7", "F", "Am"]
15442
+ // 下属調(ヘ長調)と共通
15443
+ };
15444
+ for (let bar = 1; bar < totalBars; bar++) {
15445
+ const shift = barKeyShift[bar];
15446
+ const prev = barKeyShift[bar - 1];
15447
+ if (shift === prev) continue;
15448
+ const distance = fifthsDistance(shift - prev);
15449
+ if (distance >= 4) continue;
15450
+ const prevSec = sectionAt(sectionPlan, bar - 1);
15451
+ if (prevSec.kind === "prechorus" || prevSec.kind === "chorus" || prevSec.kind === "outro")
15452
+ continue;
15453
+ if (rnd() < 0.35) continue;
15454
+ const pivots = PIVOT_CHORDS[((shift - prev) % 12 + 12) % 12];
15455
+ if (distance <= 1 && pivots && rnd() < 0.6) {
15456
+ progression[bar - 1] = pick(pivots, rnd);
15457
+ } else {
15458
+ progression[bar - 1] = transposeChordName("G7", shift - prev);
15459
+ }
15460
+ }
15461
+ if (rnd() < 0.3) {
15462
+ const tonicNames = tonic === "Am" ? ["Am", "Am7"] : ["C", "CM7"];
15463
+ const spots = [];
15464
+ for (let bar = 0; bar + 1 < totalBars; bar++) {
15465
+ if (barKeyShift[bar] !== barKeyShift[bar + 1]) continue;
15466
+ if (!MODAL_BORROW[progression[bar]]) continue;
15467
+ if (!tonicNames.includes(progression[bar + 1])) continue;
15468
+ spots.push(bar);
15469
+ }
15470
+ if (spots.length > 0) {
15471
+ const at = pick(spots, rnd);
15472
+ progression[at] = MODAL_BORROW[progression[at]];
15473
+ }
15474
+ }
14798
15475
  const chordProgression = progression.map((chord, bar) => transposeChordName(chord, barKeyShift[bar])).join("|");
14799
- const chordPattern = pick(CHORD_PATTERNS, rnd);
14800
15476
  const rootShift = resolvedKey.rootShift;
14801
15477
  const bpm = pick(BPM_CHOICES, rnd);
15478
+ const chordPattern = pick(chordPatternPool(bpm), rnd);
14802
15479
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" || kind === "drop_chorus" ? "b" : kind === "prechorus" ? "a2" : kind === "bridge" ? "c" : "a";
15480
+ const landingOf = (section) => {
15481
+ 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;
15486
+ return landing;
15487
+ };
14803
15488
  const units2 = [];
14804
15489
  for (const section of sectionPlan) {
14805
15490
  const unitCount = Math.max(1, Math.round(section.bars / 2));
@@ -14817,22 +15502,28 @@ var draw = (options, resolvedKey, rnd) => {
14817
15502
  const isLast = u === unitCount - 1;
14818
15503
  if (u % 2 === 0) {
14819
15504
  units2.push({
14820
- role: section.kind === "chorus" ? "climax" : src === "a2" || u > 0 ? "sequence" : "motif",
15505
+ role: section.kind === "bridge" ? "step" : section.kind === "chorus" ? "climax" : src === "a2" || u > 0 ? "sequence" : "motif",
14821
15506
  source: src,
14822
15507
  landing: null,
14823
15508
  section
14824
15509
  });
14825
15510
  } else {
15511
+ const landing = landingOf(section);
14826
15512
  units2.push({
14827
- role: isLast && section.spec.landing === 0 ? "cadence" : "answer",
15513
+ role: isLast && landing === 0 ? "cadence" : "answer",
14828
15514
  source: "answer",
14829
- landing: isLast ? section.spec.landing : null,
15515
+ landing: isLast ? landing : null,
14830
15516
  section
14831
15517
  });
14832
15518
  }
14833
15519
  }
14834
15520
  }
14835
15521
  const barRoles = units2.flatMap((u) => [u.role, u.role]);
15522
+ for (const section of sectionPlan) {
15523
+ if (section.kind !== "prechorus") continue;
15524
+ const last = section.startBar + section.bars - 1;
15525
+ if (last < barRoles.length) barRoles[last] = "run";
15526
+ }
14836
15527
  const barInUnit = (bar) => bar % 2;
14837
15528
  const unitOf = (bar) => Math.floor(bar / 2);
14838
15529
  const restatementOf = (bar) => {
@@ -14863,8 +15554,9 @@ var draw = (options, resolvedKey, rnd) => {
14863
15554
  groove: pick(["eighth", "sixteenth"], rnd),
14864
15555
  arcPeriod: pick([4, 8, 8, 16], rnd),
14865
15556
  arcPhase: pick([0, 1, 2], rnd),
14866
- arcAmp: 3 + rnd() * 4,
14867
- octaveAffinity: 0.14 + rnd() * 0.2,
15557
+ arcAmp: 2 + rnd() * 3,
15558
+ // オクターブ跳躍は参考曲では音程の1.0%しかない。上げすぎると音域が広がる。
15559
+ octaveAffinity: 0.06 + rnd() * 0.12,
14868
15560
  pentatonicMotif: rnd() < 0.55,
14869
15561
  runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
14870
15562
  stepShape: pick(
@@ -14876,10 +15568,14 @@ var draw = (options, resolvedKey, rnd) => {
14876
15568
  ["descend", "five-three-one", "leap-up", "hold-tonic"],
14877
15569
  rnd
14878
15570
  ),
14879
- // 人間の曲の跳躍率は p25〜p75 で 0.40〜0.55。上限が低いとその帯へ届かない。
14880
- leapAffinity: 0.08 + rnd() * 0.3,
15571
+ // 参考曲の跳躍率は p25〜p75 で 0.28〜0.41、最大跳躍の中央値は9半音。
15572
+ // 2声を交互に書いたチャンネルを主旋律と誤検出していた頃は 0.31〜0.65 に
15573
+ // 見えており、それに合わせて上限を高く取っていた。
15574
+ leapAffinity: 0.05 + rnd() * 0.2,
14881
15575
  // 界隈曲らしさ:調の外の音(クロマチック)や微小な逸脱を積極的に許容する。
14882
- chromaticAffinity: rnd() < 0.1 ? 0 : 0.3 + rnd() * 0.6,
15576
+ // 刻みを細かくすると経過音の置き場所が増えるので、同じ係数でも変化音は増える。
15577
+ // 参考曲の1.75倍まで伸びていたぶんを引く。
15578
+ chromaticAffinity: rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33,
14883
15579
  barHeadWeight: rnd() < 0.5 ? 3 : 2,
14884
15580
  bassStyle: pick(
14885
15581
  [
@@ -14906,7 +15602,7 @@ var draw = (options, resolvedKey, rnd) => {
14906
15602
  subInterval: pick([3, 4, 8, 9], rnd)
14907
15603
  };
14908
15604
  const motifPool = groovyCells(MOTIF_CELLS, style.groove, rnd);
14909
- const targetNotesPerBar = 4 + rnd() * 4;
15605
+ const targetNotesPerBar = 4.5 + rnd() * 4.5;
14910
15606
  const targetRestRatio = 0.02 + rnd() * 0.1;
14911
15607
  const cellNotes = (c) => c.value.filter((v) => v > 0).length;
14912
15608
  const cellRest = (c) => {
@@ -14919,17 +15615,27 @@ var draw = (options, resolvedKey, rnd) => {
14919
15615
  return total === 0 ? 0 : rest / total;
14920
15616
  };
14921
15617
  const cellDistance = (c, densityMul = 1) => Math.abs(cellNotes(c) - targetNotesPerBar * densityMul) + Math.abs(cellRest(c) - targetRestRatio / Math.max(0.5, densityMul)) * 8;
15618
+ const weightedPick = (pool) => {
15619
+ let total = 0;
15620
+ for (const c of pool) total += cellWeight(c);
15621
+ let r = rnd() * total;
15622
+ for (const c of pool) {
15623
+ r -= cellWeight(c);
15624
+ if (r <= 0) return c;
15625
+ }
15626
+ return pool[pool.length - 1];
15627
+ };
14922
15628
  const pickCell = (pool, densityMul = 1) => {
14923
- let best = pick(pool, rnd);
15629
+ let best = weightedPick(pool);
14924
15630
  for (let i2 = 0; i2 < 2; i2++) {
14925
- const c = pick(pool, rnd);
15631
+ const c = weightedPick(pool);
14926
15632
  if (cellDistance(c, densityMul) < cellDistance(best, densityMul))
14927
15633
  best = c;
14928
15634
  }
14929
15635
  return best;
14930
15636
  };
14931
15637
  const motifCell = pickCell(motifPool, 0.85);
14932
- const motifCell2 = rnd() < 0.45 ? motifCell : pickCell(
15638
+ const motifCell2 = rnd() < 0.72 ? motifCell : pickCell(
14933
15639
  motifPool.filter((c) => c !== motifCell),
14934
15640
  0.85
14935
15641
  );
@@ -14947,7 +15653,7 @@ var draw = (options, resolvedKey, rnd) => {
14947
15653
  ),
14948
15654
  1.1
14949
15655
  );
14950
- const motifA22 = rnd() < 0.45 ? motifA2 : pickCell(
15656
+ const motifA22 = rnd() < 0.72 ? motifA2 : pickCell(
14951
15657
  motifPool.filter((c) => c !== motifA2),
14952
15658
  1.1
14953
15659
  );
@@ -14957,7 +15663,7 @@ var draw = (options, resolvedKey, rnd) => {
14957
15663
  ),
14958
15664
  0.9
14959
15665
  );
14960
- const motifC2 = rnd() < 0.45 ? motifC : pickCell(
15666
+ const motifC2 = rnd() < 0.72 ? motifC : pickCell(
14961
15667
  motifPool.filter((c) => c !== motifC),
14962
15668
  0.9
14963
15669
  );
@@ -14996,12 +15702,24 @@ var draw = (options, resolvedKey, rnd) => {
14996
15702
  ),
14997
15703
  rnd
14998
15704
  ).value;
14999
- const breathCell = pick(
15000
- RHYTHM_CELLS.filter(
15001
- (c) => c.density === "sparse" && c.value.some((v) => v < 0)
15002
- ),
15003
- rnd
15004
- );
15705
+ const breathBySection = /* @__PURE__ */ new Map();
15706
+ {
15707
+ let prev = null;
15708
+ for (const section of sectionPlan) {
15709
+ if (!section.spec.melody) continue;
15710
+ const pool = PHRASE_END_CELLS.filter((c) => c !== prev);
15711
+ const cell = pick(pool.length > 0 ? pool : PHRASE_END_CELLS, rnd);
15712
+ breathBySection.set(section.startBar, cell);
15713
+ prev = cell;
15714
+ }
15715
+ }
15716
+ const midBreathCell = pick(MID_BREATH_CELLS, rnd);
15717
+ const breathBars = /* @__PURE__ */ new Set();
15718
+ const phraseEndBars = /* @__PURE__ */ new Set();
15719
+ for (const section of sectionPlan) {
15720
+ if (!section.spec.melody) continue;
15721
+ phraseEndBars.add(section.startBar + section.bars - 1);
15722
+ }
15005
15723
  const barRhythms = [];
15006
15724
  for (let bar = 0; bar < totalBars; bar++) {
15007
15725
  const u = unitOf(bar);
@@ -15009,6 +15727,7 @@ var draw = (options, resolvedKey, rnd) => {
15009
15727
  const source = restatementOf(bar);
15010
15728
  if (source !== null) {
15011
15729
  barRhythms.push(barRhythms[source]);
15730
+ if (breathBars.has(source)) breathBars.add(bar);
15012
15731
  continue;
15013
15732
  }
15014
15733
  const isB = units2[u].source === "b";
@@ -15026,7 +15745,14 @@ var draw = (options, resolvedKey, rnd) => {
15026
15745
  const prevSource = units2[u - 1]?.source;
15027
15746
  const [head, tail] = prevSource === "b" ? [motifB, motifB2] : prevSource === "a2" ? [motifA2, motifA22] : [motifCell, motifCell2];
15028
15747
  const answerVar = prevSource === "b" ? motifBVar : motifVar;
15029
- cell = half === 0 ? rnd() < 0.4 ? answerVar : head : isPeriodEnd ? breathCell : rnd() < 0.35 ? answerVar : tail;
15748
+ if (half === 0) {
15749
+ cell = rnd() < 0.15 ? answerVar : head;
15750
+ } else if (isPeriodEnd) {
15751
+ cell = breathBySection.get(units2[u].section.startBar) ?? tail;
15752
+ breathBars.add(bar);
15753
+ } else {
15754
+ cell = rnd() < 0.28 ? midBreathCell : rnd() < 0.12 ? answerVar : tail;
15755
+ }
15030
15756
  } else {
15031
15757
  const [head, tail] = pair();
15032
15758
  cell = half === 0 ? head : tail;
@@ -15047,10 +15773,12 @@ var draw = (options, resolvedKey, rnd) => {
15047
15773
  motifContour.push(baseDegree + archetype[idx]);
15048
15774
  }
15049
15775
  const plannedDegrees = new Array(totalBars).fill(null);
15776
+ const motifShiftMemo = /* @__PURE__ */ new Map();
15050
15777
  const melody = [];
15051
15778
  const submelody = [];
15052
15779
  const bass = [];
15053
15780
  const harmony = [];
15781
+ const harmony2 = [];
15054
15782
  const pad = [];
15055
15783
  const melodyDurations = [];
15056
15784
  const barTension = new Array(totalBars).fill(0);
@@ -15142,34 +15870,36 @@ var draw = (options, resolvedKey, rnd) => {
15142
15870
  if (landing !== null && barInUnit(bar) === 1) landOn(degrees, landing);
15143
15871
  plannedDegrees[bar] = [...degrees];
15144
15872
  const isMotifBar = source !== null || role === "motif" || role === "sequence" || role === "climax" || role === "answer";
15145
- const pitches = shapeBar(
15146
- isMotifBar ? fitMotif(
15873
+ let fitted = degrees;
15874
+ if (isMotifBar) {
15875
+ const shiftKey = degrees.join(",");
15876
+ const r = fitMotif(
15147
15877
  degrees,
15148
15878
  slots,
15149
15879
  tones,
15150
15880
  prevSemi,
15151
15881
  quarterSteps,
15152
- style.pentatonicMotif
15153
- ) : degrees,
15154
- slots,
15155
- tones,
15156
- prevSemi,
15157
- {
15158
- allowLeap: role === "climax",
15159
- allowArpeggio: role === "climax" || role === "run" && style.runShape === "broken" || role === "cadence" && style.cadenceShape !== "descend",
15160
- quarterSteps,
15161
- // モチーフの小節はオクターブ移動を入れない。モチーフは輪郭が命なので、
15162
- // 後から音を1つ跳ばすと「同じフレーズが返ってきた」と分からなくなる。
15163
- // モチーフ側は {@link MOTIF_ARCHETYPES} が自前でオクターブを持つ。
15164
- // モチーフ・セクエンツ・サビの小節はオクターブ移動を入れない
15165
- // (輪郭が命なので、後から音を1つ跳ばすと同じフレーズと分からなくなる)。
15166
- // 答えの小節は輪郭を借りているだけなので許す。
15167
- octaveAffinity: role === "motif" || role === "sequence" || role === "climax" ? 0 : style.octaveAffinity,
15168
- chromaticAffinity: style.chromaticAffinity,
15169
- rnd,
15170
- preserveContour: isMotifBar
15171
- }
15172
- );
15882
+ style.pentatonicMotif,
15883
+ motifShiftMemo.get(shiftKey) ?? null
15884
+ );
15885
+ fitted = r.degrees;
15886
+ motifShiftMemo.set(shiftKey, r.shift);
15887
+ }
15888
+ const pitches = shapeBar(fitted, slots, tones, prevSemi, {
15889
+ allowLeap: role === "climax",
15890
+ allowArpeggio: role === "climax" || role === "run" && style.runShape === "broken" || role === "cadence" && style.cadenceShape !== "descend",
15891
+ quarterSteps,
15892
+ // モチーフの小節はオクターブ移動を入れない。モチーフは輪郭が命なので、
15893
+ // 後から音を1つ跳ばすと「同じフレーズが返ってきた」と分からなくなる。
15894
+ // モチーフ側は {@link MOTIF_ARCHETYPES} が自前でオクターブを持つ。
15895
+ // モチーフ・セクエンツ・サビの小節はオクターブ移動を入れない
15896
+ // (輪郭が命なので、後から音を1つ跳ばすと同じフレーズと分からなくなる)。
15897
+ // 答えの小節は輪郭を借りているだけなので許す。
15898
+ octaveAffinity: role === "motif" || role === "sequence" || role === "climax" ? 0 : style.octaveAffinity,
15899
+ chromaticAffinity: style.chromaticAffinity,
15900
+ rnd,
15901
+ preserveContour: isMotifBar
15902
+ });
15173
15903
  if (landing !== null && barInUnit(bar) === 1) landPitch(pitches, landing);
15174
15904
  const fifths = pitches.map(diatonicFifth);
15175
15905
  applyChromatic(pitches, fifths, slots, tones, {
@@ -15294,23 +16024,50 @@ var draw = (options, resolvedKey, rnd) => {
15294
16024
  pushSub(scaleStep(QUARTER), scaleStep(DOT_HALF), subSemi);
15295
16025
  }
15296
16026
  const barSec = sectionAt(sectionPlan, bar);
15297
- if (!silent && (barSec.kind === "chorus" || barSec.kind === "drop_chorus")) {
15298
- for (let i2 = 0; i2 < slots.length; i2++) {
15299
- const hSemi = pitches[i2] + 3;
15300
- const hTone = nearestChordTone(hSemi, tones, 2);
15301
- const hClamped = clampSemi(hTone.semi, MELODY_LOW, MELODY_HIGH);
15302
- const k = barKeyShift[bar];
15303
- const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15304
- harmony.push({
15305
- startStep: barStart + slots[i2].at,
15306
- pitchUnits: spelledToUnits(
15307
- hClamped + k,
15308
- hTone.fifth + fifthShift,
15309
- edo
15310
- ),
15311
- durationSteps: slots[i2].value,
15312
- velocity: slots[i2].at === 0 ? 82 : 76
15313
- });
16027
+ if (!silent && harmonyKinds.includes(barSec.kind)) {
16028
+ const k = barKeyShift[bar];
16029
+ const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
16030
+ const writeHarmony = (out, prev, offset, against) => {
16031
+ let last = prev;
16032
+ const written = [];
16033
+ for (let i2 = 0; i2 < slots.length; i2++) {
16034
+ const hTone = harmonyPitch(
16035
+ pitches[i2],
16036
+ tones,
16037
+ last,
16038
+ offset,
16039
+ harmonyParallel,
16040
+ against?.[i2] ?? null
16041
+ );
16042
+ let hClamped = clampSemi(
16043
+ hTone.semi,
16044
+ MELODY_LOW - 12,
16045
+ MELODY_HIGH + 7
16046
+ );
16047
+ while (hClamped - pitches[i2] > 12) hClamped -= 12;
16048
+ while (hClamped - pitches[i2] < -12) hClamped += 12;
16049
+ last = hClamped;
16050
+ written.push(hClamped);
16051
+ const keep = slots[i2].isStrong || slots[i2].value >= quarterSteps || rnd() < harmonyCoverage;
16052
+ if (!keep) continue;
16053
+ out.push({
16054
+ startStep: barStart + slots[i2].at,
16055
+ pitchUnits: spelledToUnits(
16056
+ hClamped + k,
16057
+ hTone.fifth + fifthShift,
16058
+ edo
16059
+ ),
16060
+ durationSteps: slots[i2].value,
16061
+ velocity: slots[i2].at === 0 ? 82 : 76
16062
+ });
16063
+ }
16064
+ return written;
16065
+ };
16066
+ const h1 = writeHarmony(harmony, prevHarmony, harmonyOffset, null);
16067
+ prevHarmony = h1[h1.length - 1] ?? prevHarmony;
16068
+ if (useHarmony2) {
16069
+ const h2 = writeHarmony(harmony2, prevHarmony2, harmony2Offset, h1);
16070
+ prevHarmony2 = h2[h2.length - 1] ?? prevHarmony2;
15314
16071
  }
15315
16072
  }
15316
16073
  if (barSec.kind === "prechorus" || barSec.kind === "chorus" || barSec.kind === "bridge" || barSec.kind === "drop_chorus") {
@@ -15442,6 +16199,7 @@ var draw = (options, resolvedKey, rnd) => {
15442
16199
  for (const n of barBass) bass.push(n);
15443
16200
  }
15444
16201
  const eighthSteps = scaleStep(EIGHTH);
16202
+ const tieMemo = /* @__PURE__ */ new Map();
15445
16203
  for (let i2 = 0; i2 < melody.length - 1; i2++) {
15446
16204
  const cur = melody[i2];
15447
16205
  const nxt = melody[i2 + 1];
@@ -15455,16 +16213,28 @@ var draw = (options, resolvedKey, rnd) => {
15455
16213
  const curSec = sectionAt(sectionPlan, barIdx - 1);
15456
16214
  const nextSec = sectionAt(sectionPlan, barIdx);
15457
16215
  if (!nextSec.spec.melody || curSec !== nextSec) continue;
16216
+ if (breathBars.has(barIdx)) continue;
15458
16217
  const curSemi = Math.round(cur.pitchUnits / UNITS_PER_SEMITONE);
15459
16218
  const nxtSemi = Math.round(nxt.pitchUnits / UNITS_PER_SEMITONE);
15460
16219
  if (Math.abs(nxtSemi - curSemi) > 3) continue;
16220
+ const after = melody[i2 + 2];
16221
+ if (after && Math.floor(after.startStep / stepsPerBar) === barIdx) {
16222
+ const afterSemi = Math.round(after.pitchUnits / UNITS_PER_SEMITONE) - (barKeyShift[barIdx] ?? 0);
16223
+ if (!DIATONIC_PCS.has(pitchClass(afterSemi))) continue;
16224
+ }
15461
16225
  const nextRole = barRoles[barIdx];
15462
- let tieProb = 0.4;
15463
- if (nextRole === "motif") tieProb = 0.05;
15464
- else if (nextRole === "sequence") tieProb = 0.75;
15465
- else if (nextRole === "climax") tieProb = 0.9;
16226
+ let tieProb = 0.3;
16227
+ if (nextRole === "motif") tieProb = 0.1;
16228
+ else if (nextRole === "sequence") tieProb = 0.35;
16229
+ else if (nextRole === "climax") tieProb = 0.45;
15466
16230
  else if (nextRole === "cadence") tieProb = 0.1;
15467
- if (rnd() < tieProb) {
16231
+ const tieKey = `${barRhythms[barIdx].join(",")}|${barInUnit(barIdx)}`;
16232
+ let tie = tieMemo.get(tieKey);
16233
+ if (tie === void 0) {
16234
+ tie = rnd() < tieProb;
16235
+ tieMemo.set(tieKey, tie);
16236
+ }
16237
+ if (tie) {
15468
16238
  cur.durationSteps += nxt.durationSteps;
15469
16239
  melody.splice(i2 + 1, 1);
15470
16240
  if (i2 < melodyDurations.length - 1) {
@@ -15480,15 +16250,32 @@ var draw = (options, resolvedKey, rnd) => {
15480
16250
  i2--;
15481
16251
  }
15482
16252
  }
16253
+ const splitMemo = /* @__PURE__ */ new Map();
16254
+ let splitLastBar = -1;
16255
+ let splitIdxInBar = 0;
15483
16256
  for (let i2 = 0; i2 < melody.length; i2++) {
15484
16257
  const barIdx = Math.floor(melody[i2].startStep / stepsPerBar);
16258
+ if (barIdx !== splitLastBar) {
16259
+ splitLastBar = barIdx;
16260
+ splitIdxInBar = 0;
16261
+ } else splitIdxInBar++;
15485
16262
  if (barIdx >= totalBars) continue;
16263
+ if (phraseEndBars.has(barIdx)) continue;
15486
16264
  const role = barRoles[barIdx];
15487
- let splitProb = 0.15;
16265
+ let splitProb = 0.08;
15488
16266
  if (role === "motif" || role === "sequence") splitProb = 0;
15489
- else if (role === "climax") splitProb = 0.6;
16267
+ else if (role === "climax") splitProb = 0.3;
15490
16268
  else if (role === "cadence") splitProb = 0;
15491
- if (rnd() < splitProb && melody[i2].durationSteps === quarterSteps) {
16269
+ let doSplit = false;
16270
+ if (melody[i2].durationSteps === quarterSteps) {
16271
+ const key = `${barRhythms[barIdx].join(",")}|${barInUnit(barIdx)}|${role}|${splitIdxInBar}`;
16272
+ const memo = splitMemo.get(key);
16273
+ if (memo === void 0) {
16274
+ doSplit = rnd() < splitProb;
16275
+ splitMemo.set(key, doSplit);
16276
+ } else doSplit = memo;
16277
+ }
16278
+ if (doSplit) {
15492
16279
  const half = scaleStep(EIGHTH);
15493
16280
  const newNote = {
15494
16281
  ...melody[i2],
@@ -15502,6 +16289,7 @@ var draw = (options, resolvedKey, rnd) => {
15502
16289
  melodyDurations.splice(i2 + 1, 0, half);
15503
16290
  }
15504
16291
  i2++;
16292
+ splitIdxInBar++;
15505
16293
  }
15506
16294
  }
15507
16295
  const range = (notes) => {
@@ -15511,8 +16299,11 @@ var draw = (options, resolvedKey, rnd) => {
15511
16299
  };
15512
16300
  const shiftUnits = semitonesToUnits(rootShift, edo);
15513
16301
  if (shiftUnits !== 0)
15514
- for (const list of [melody, submelody, bass, harmony, pad])
16302
+ for (const list of [melody, submelody, bass, harmony, harmony2, pad])
15515
16303
  for (const n of list) n.pitchUnits = n.pitchUnits + shiftUnits;
16304
+ const octave = useOctaveLayer ? melody.filter(
16305
+ (n) => n.durationSteps >= quarterSteps || rnd() < octaveCoverage
16306
+ ).map((n) => ({ ...n, velocity: Math.max(40, n.velocity - 26) })) : [];
15516
16307
  return {
15517
16308
  chordProgression,
15518
16309
  chordPattern,
@@ -15523,10 +16314,20 @@ var draw = (options, resolvedKey, rnd) => {
15523
16314
  bpm,
15524
16315
  sections: sectionPlan,
15525
16316
  bars: totalBars,
16317
+ vocal: {
16318
+ duetSpans,
16319
+ duetStyle,
16320
+ harmonyKinds,
16321
+ harmony2: useHarmony2,
16322
+ octaveLayer: useOctaveLayer
16323
+ },
16324
+ tonal: { relativeKinds: [...relativeKinds], relativeShift, floating },
15526
16325
  melody,
15527
16326
  submelody,
15528
16327
  bass,
15529
16328
  harmony,
16329
+ harmony2,
16330
+ octave,
15530
16331
  pad,
15531
16332
  melodyDurations,
15532
16333
  restSteps,
@@ -15598,6 +16399,8 @@ var evaluate = (d, recent) => {
15598
16399
  melodyRange: atc("melodyRange", d.melodyRange),
15599
16400
  notesPerBar: atc("notesPerBar", density.notesPerBar),
15600
16401
  shortNoteRatio: atc("shortNoteRatio", density.shortNoteRatio),
16402
+ barDensityCv: atc("barDensityCv", density.barDensityCv),
16403
+ densityCliff: atc("densityCliff", density.densityCliff),
15601
16404
  stepRatio: atc("stepRatio", d.stepRatio),
15602
16405
  chromaticRatio: atc("chromaticRatio", d.chromaticRatio),
15603
16406
  sim1: atc("sim1", structure.sim1),
@@ -15605,12 +16408,20 @@ var evaluate = (d, recent) => {
15605
16408
  sim4: atc("sim4", structure.sim4),
15606
16409
  sim8: atc("sim8", structure.sim8),
15607
16410
  phraseBreath: atc("phraseBreath", structure.phraseBreath),
16411
+ turnRatio: atc("turnRatio", structure.turnRatio),
15608
16412
  climaxPosition: atc("climaxPosition", structure.climaxPosition),
15609
16413
  climaxPeaks: at(peakBand, structure.climaxPeaks),
15610
16414
  complementarity: at(HAND_BANDS.complementarity, structure.complementarity),
15611
16415
  subDensity: at(HAND_BANDS.subDensity, d.submelody.length / d.bars),
16416
+ // サブメロにも同じ崖の物差しを当てる。帯はメロディから採ったものを流用する。
16417
+ subDensityCliff: atc(
16418
+ "densityCliff",
16419
+ densityFeatures(fromMelody(toMetricNotes(d.submelody)), opts).densityCliff
16420
+ ),
15612
16421
  tensionRise: tension.rise,
15613
- tensionResolve: tension.resolve,
16422
+ // **浮遊感の曲に「終止で解決しろ」は要求しない。** 解決しないことが狙いなので、
16423
+ // この項目で減点すると候補40本の選抜で必ず負けて、狙って引いた曲が出てこない。
16424
+ tensionResolve: d.tonal.floating ? 1 : tension.resolve,
15614
16425
  novelty
15615
16426
  };
15616
16427
  let weighted = 0;
@@ -15673,10 +16484,14 @@ var composeSong = (options) => {
15673
16484
  bpm: d.bpm,
15674
16485
  sections: d.sections,
15675
16486
  bars: d.bars,
16487
+ vocal: d.vocal,
16488
+ tonal: d.tonal,
15676
16489
  melody: d.melody,
15677
16490
  submelody: d.submelody,
15678
16491
  bass: d.bass,
15679
16492
  harmony: d.harmony,
16493
+ harmony2: d.harmony2,
16494
+ octave: d.octave,
15680
16495
  pad: d.pad,
15681
16496
  stats: { ...stats, attempts: attempt, rejected }
15682
16497
  };
@@ -15691,7 +16506,10 @@ var composeSong = (options) => {
15691
16506
  var pickBuiltinDrum = (song, rnd) => {
15692
16507
  const eighth = BASE_STEPS_PER_BAR / 8;
15693
16508
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
15694
- const pool = song.bpm >= 150 ? ["4beat", "dance", "16beat", "disco"] : short >= 0.6 ? ["16beat", "dance", "4beat", "disco"] : song.bpm <= 110 && short < 0.4 ? ["bossa", "8beat"] : ["8beat", "4beat", "16beat"];
16509
+ const dotted = song.melody.filter(
16510
+ (n) => n.durationSteps === Math.round(BASE_STEPS_PER_BAR * 3 / 16)
16511
+ ).length / Math.max(1, song.melody.length);
16512
+ 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"];
15695
16513
  return pick(pool, rnd);
15696
16514
  };
15697
16515
  var pickBuiltinInstrument = (song, rnd) => {
@@ -15705,7 +16523,7 @@ var pickBuiltinInstrument = (song, rnd) => {
15705
16523
  pool = song.bpm >= 150 ? ["cyber_punk", "synth_pop", "retro_game"] : ["synth_pop", "retro_game", "rock", "piano"];
15706
16524
  } else if (song.drum === "bossa") {
15707
16525
  pool = ["jazz_night", "acoustic", "piano"];
15708
- } else if (song.bpm <= 105 && short < 0.4) {
16526
+ } else if (song.bpm <= 115) {
15709
16527
  pool = ["ambient_cloud", "acoustic", "orchestra", "piano", "fantasy_rpg"];
15710
16528
  } else if (song.bpm >= 145) {
15711
16529
  pool = ["rock", "synth_pop", "cyber_punk", "retro_game", "piano"];
@@ -15761,6 +16579,23 @@ var LYRIC_WORDS = [
15761
16579
  "\u305B\u304B\u3044",
15762
16580
  "\u304D\u305B\u3064"
15763
16581
  ];
16582
+ var alignLyrics = (source, lyrics, target, options) => {
16583
+ const sorted = [...source].sort((a, b) => a.startStep - b.startStep);
16584
+ const kana = [...lyrics].filter((c) => c !== "\u3001");
16585
+ const at = /* @__PURE__ */ new Map();
16586
+ for (let i2 = 0; i2 < sorted.length && i2 < kana.length; i2++)
16587
+ at.set(sorted[i2].startStep, kana[i2]);
16588
+ const { stepsPerBar } = options;
16589
+ const tgt = [...target].sort((a, b) => a.startStep - b.startStep);
16590
+ const out = [];
16591
+ for (let i2 = 0; i2 < tgt.length; i2++) {
16592
+ out.push(at.get(tgt[i2].startStep) ?? "\u30FC");
16593
+ const next = tgt[i2 + 1];
16594
+ if (next && Math.floor(tgt[i2].startStep / (stepsPerBar * 4)) !== Math.floor(next.startStep / (stepsPerBar * 4)))
16595
+ out.push("\u3001");
16596
+ }
16597
+ return out.join("");
16598
+ };
15764
16599
  var composeLyrics = (melody, options) => {
15765
16600
  const rnd = options.random ?? Math.random;
15766
16601
  const { stepsPerBar } = options;
@@ -17055,56 +17890,95 @@ var exportMIDI = (options) => {
17055
17890
  const div = 480;
17056
17891
  const tickPerStep = div / STEPS_PER_BEAT3;
17057
17892
  const midiTracks = [];
17058
- const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
17059
- const BEND_RANGE_SEMITONES = 2;
17060
17893
  const bendKey = (cents) => Math.round(cents * 100);
17061
- const bendUse = /* @__PURE__ */ new Map();
17062
- for (const track of tracks) {
17063
- for (const n of track.notes) {
17894
+ const hasMicrotones = tracks.some(
17895
+ (track) => track.notes.some((n) => {
17064
17896
  const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
17065
- const k = bendKey(detuneCents);
17066
- bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
17067
- }
17068
- }
17069
- const bendChannel = /* @__PURE__ */ new Map();
17070
- const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
17071
- for (const [k] of ordered) {
17072
- if (bendChannel.size >= NOTE_CHANNELS.length) break;
17073
- bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
17074
- }
17075
- tracks.forEach((track) => {
17076
- if (track.notes.length === 0) return;
17077
- const events = [];
17078
- for (const n of track.notes) {
17079
- const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
17080
- const k = bendKey(detuneCents);
17081
- const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
17082
- const note = Math.max(0, Math.min(127, midi));
17083
- const startTick = Math.round(n.startStep * tickPerStep);
17084
- const endTick = Math.round(
17085
- (n.startStep + (n.durationSteps || 1)) * tickPerStep
17086
- );
17087
- const vel = Math.round(
17088
- (n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
17089
- );
17090
- events.push({ t: startTick, m: [144 | channel, note, vel] });
17091
- events.push({ t: endTick, m: [144 | channel, note, 0] });
17092
- }
17093
- events.sort((a, b) => a.t - b.t);
17094
- midiTracks.push(events);
17095
- });
17897
+ return bendKey(detuneCents) !== 0;
17898
+ })
17899
+ );
17096
17900
  const bendSetup = [];
17097
- for (const [k, channel] of bendChannel) {
17098
- const cents = k / 100;
17099
- bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
17100
- bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
17101
- bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
17102
- bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
17103
- bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
17104
- bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
17105
- const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
17106
- const v = Math.max(0, Math.min(16383, raw));
17107
- bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
17901
+ if (hasMicrotones) {
17902
+ const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
17903
+ const BEND_RANGE_SEMITONES = 2;
17904
+ const bendUse = /* @__PURE__ */ new Map();
17905
+ for (const track of tracks) {
17906
+ for (const n of track.notes) {
17907
+ const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
17908
+ const k = bendKey(detuneCents);
17909
+ bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
17910
+ }
17911
+ }
17912
+ const bendChannel = /* @__PURE__ */ new Map();
17913
+ const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
17914
+ for (const [k] of ordered) {
17915
+ if (bendChannel.size >= NOTE_CHANNELS.length) break;
17916
+ bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
17917
+ }
17918
+ tracks.forEach((track) => {
17919
+ if (track.notes.length === 0) return;
17920
+ const events = [];
17921
+ for (const n of track.notes) {
17922
+ const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
17923
+ const k = bendKey(detuneCents);
17924
+ const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
17925
+ const note = Math.max(0, Math.min(127, midi));
17926
+ const startTick = Math.round(n.startStep * tickPerStep);
17927
+ const endTick = Math.round(
17928
+ (n.startStep + (n.durationSteps || 1)) * tickPerStep
17929
+ );
17930
+ const vel = Math.round(
17931
+ (n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
17932
+ );
17933
+ events.push({ t: startTick, m: [144 | channel, note, vel] });
17934
+ events.push({ t: endTick, m: [144 | channel, note, 0] });
17935
+ }
17936
+ events.sort((a, b) => a.t - b.t);
17937
+ midiTracks.push(events);
17938
+ });
17939
+ for (const [k, channel] of bendChannel) {
17940
+ const cents = k / 100;
17941
+ bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
17942
+ bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
17943
+ bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
17944
+ bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
17945
+ bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
17946
+ bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
17947
+ const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
17948
+ const v = Math.max(0, Math.min(16383, raw));
17949
+ bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
17950
+ }
17951
+ } else {
17952
+ tracks.forEach((track, trIdx) => {
17953
+ if (track.notes.length === 0) return;
17954
+ const channel = trIdx < 9 ? trIdx : trIdx + 1 & 15;
17955
+ const events = [];
17956
+ if (track.program !== void 0 && track.program >= 0 && track.program <= 127) {
17957
+ events.push({ t: 0, m: [192 | channel, track.program] });
17958
+ }
17959
+ for (const n of track.notes) {
17960
+ const { midi } = unitsToMidiDetune(n.pitchUnits);
17961
+ const note = Math.max(0, Math.min(127, midi));
17962
+ const startTick = Math.round(n.startStep * tickPerStep);
17963
+ const endTick = Math.round(
17964
+ (n.startStep + (n.durationSteps || 1)) * tickPerStep
17965
+ );
17966
+ const vel = Math.round(
17967
+ (n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
17968
+ );
17969
+ events.push({ t: startTick, m: [144 | channel, note, vel] });
17970
+ events.push({ t: endTick, m: [144 | channel, note, 0] });
17971
+ }
17972
+ events.sort((a, b) => {
17973
+ if (a.t !== b.t) return a.t - b.t;
17974
+ const typeA = a.m[0] & 240;
17975
+ const typeB = b.m[0] & 240;
17976
+ if (typeA === 192 && typeB !== 192) return -1;
17977
+ if (typeB === 192 && typeA !== 192) return 1;
17978
+ return 0;
17979
+ });
17980
+ midiTracks.push(events);
17981
+ });
17108
17982
  }
17109
17983
  const maxStep = Math.max(
17110
17984
  ...tracks.filter((t) => t.notes.length > 0).map(
@@ -18329,6 +19203,28 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
18329
19203
  };
18330
19204
  };
18331
19205
 
19206
+ // src/track1-state.ts
19207
+ var TRACK1_STORAGE_KEY = "dtm-track1:settings";
19208
+ var readTrack1Settings = () => {
19209
+ try {
19210
+ if (typeof localStorage === "undefined" || !localStorage) return null;
19211
+ const raw = localStorage.getItem(TRACK1_STORAGE_KEY);
19212
+ if (!raw) return null;
19213
+ const parsed = JSON.parse(raw);
19214
+ if (!parsed || typeof parsed !== "object") return null;
19215
+ return parsed;
19216
+ } catch (_) {
19217
+ return null;
19218
+ }
19219
+ };
19220
+ var writeTrack1Settings = (settings) => {
19221
+ try {
19222
+ if (typeof localStorage === "undefined" || !localStorage) return;
19223
+ localStorage.setItem(TRACK1_STORAGE_KEY, JSON.stringify(settings));
19224
+ } catch (_) {
19225
+ }
19226
+ };
19227
+
18332
19228
  // src/daw.ts
18333
19229
  var CHORD_INFO_HTML2 = `
18334
19230
  <div class="dtm-modal-body-content">
@@ -18661,8 +19557,16 @@ var COMPOSE_INFO_HTML = `
18661
19557
  <li><strong>\u97F3\u306E\u5206\u5E03</strong>\u2014\u2014\u97F3\u4FA1\u306E\u3070\u3089\u3064\u304D\u30FB\u4F11\u7B26\u306E\u5272\u5408\u30FB\u8DF3\u8E8D\u306E\u5927\u304D\u3055\u3068\u6BD4\u7387\u30FB\u4F7F\u3063\u3066\u3044\u308B\u97F3\u57DF\u3002</li>
18662
19558
  </ul>
18663
19559
  <p>\u5404\u9805\u76EE\u306E\u5408\u683C\u30E9\u30A4\u30F3\u306F\u52D8\u3067\u6C7A\u3081\u305F\u3082\u306E\u3067\u306F\u306A\u304F\u3001<strong>\u4EBA\u9593\u304C\u66F8\u3044\u305FMIDI\u3092\u5B9F\u969B\u306B\u6E2C\u3063\u3066\u3001\u305D\u306E\u5206\u5E03\u306E\u771F\u3093\u4E2D\u3042\u305F\u308A\u3092\u6E80\u70B9\u306B\u3057\u3066\u3044\u307E\u3059</strong>\u3002\u305F\u3068\u3048\u3070\u4E3B\u65CB\u5F8B\u306E\u4F11\u7B26\u306F\u3001\u4EBA\u304C\u66F8\u3044\u305F\u66F2\u3067\u306F\u5168\u4F53\u306E15\u301C43%\u3092\u5360\u3081\u3066\u3044\u307E\u3057\u305F\u3002</p>
19560
+ <h4>\u6B4C\u5165\u308A\u4F5C\u66F2\u306E\u30DC\u30FC\u30AB\u30EB</h4>
19561
+ <p>\u4E3B\u65CB\u5F8B\u306B\u6B4C\u8A5E\u3092\u4ED8\u3051\u3066\u6B4C\u308F\u305B\u307E\u3059\u3002\u6B4C\u8A5E\u306B\u610F\u5473\u306F\u3042\u308A\u307E\u305B\u3093\uFF08\u30E1\u30ED\u30C7\u30A3\u306B\u6B63\u3057\u304F\u4E57\u308B\u3001\u767A\u97F3\u3067\u304D\u308B\u97F3\u306E\u4E26\u3073\u3060\u3051\u3092\u4F5C\u3063\u3066\u3044\u307E\u3059\uFF09\u3002<strong>\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\uFF0815\u30C8\u30E9\u30C3\u30AF\uFF09\u3067\u306F\u3001\u3055\u3089\u306B\u30DC\u30FC\u30AB\u30EB\u30A2\u30EC\u30F3\u30B8\u307E\u3067\u5C55\u958B\u3057\u307E\u3059\u3002</strong>\u3069\u3053\u3067\u4F55\u3092\u3059\u308B\u304B\u306F\u66F2\u3054\u3068\u306B\u81EA\u52D5\u3067\u6C7A\u307E\u308B\u306E\u3067\u3001\u8A2D\u5B9A\u9805\u76EE\u306F\u3042\u308A\u307E\u305B\u3093\u3002</p>
19562
+ <ul>
19563
+ <li><strong>\u30CF\u30E2\u30EA</strong> \u2014 \u30B5\u30D3\u304B\u3089\u3001\u66F2\u306B\u3088\u3063\u3066\u306FB\u30E1\u30ED\u304B\u3089\u5165\u308A\u307E\u3059\u3002<strong>\u4E3B\u65CB\u5F8B\u3068\u540C\u3058\u58F0\u30FB\u540C\u3058\u6B4C\u8A5E\u3067\u9055\u3046\u9AD8\u3055\u3092\u91CD\u306D\u308B</strong>\u306E\u3067\u3001\u305D\u306E\u30D1\u30FC\u30C8\u304B\u3089\u58F0\u304C\u539A\u304F\u306A\u3063\u3066\u8C6A\u83EF\u306B\u805E\u3053\u3048\u307E\u3059\u30023\u5EA6\u304B6\u5EA6\u304C\u57FA\u672C\u3067\u3001\u6D6E\u3044\u3066\u805E\u3053\u3048\u308B\u5B8C\u51685\u5EA6\u306F\u907F\u3051\u307E\u3059\u3002\u4E3B\u65CB\u5F8B\u304C\u30C6\u30F3\u30B7\u30E7\u30F3\u97F3\uFF089th\u30FB11th \u306A\u3069\uFF09\u306B\u4E57\u3063\u3066\u3044\u308B\u3068\u3053\u308D\u3060\u30514\u5EA6\u3067\u5F53\u3066\u307E\u3059\u2014\u2014\u305D\u3053\u3067\u306F3\u5EA6\u30826\u5EA6\u3082\u548C\u97F3\u306E\u69CB\u6210\u97F3\u3078\u5C4A\u304B\u306A\u3044\u305F\u3081\u3067\u3059\u3002\u843D\u3061\u30B5\u30D3\u306F\u58F0\u3060\u3051\u3092\u805E\u304B\u305B\u308B\u5834\u6240\u306A\u306E\u3067\u5916\u3057\u307E\u3059\u3002</li>
19564
+ <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
+ </ul>
19566
+ <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>
18664
19567
  <h4>\u305D\u306E\u307B\u304B</h4>
18665
19568
  <ul>
19569
+ <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>
18666
19570
  <li>\u8ABF\u306F\u30CF\u9577\u8ABF\uFF08\u307E\u305F\u306F\u30A4\u77ED\u8ABF\uFF09\u3067\u56FA\u5B9A\u3067\u3059\u3002\u5225\u306E\u8ABF\u306B\u3057\u305F\u3044\u3068\u304D\u306F\u3001\u3053\u306E\u4E0B\u306E\u300C\u79FB\u8ABF\u300D\u3067\u52D5\u304B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</li>
18667
19571
  <li>31\u5E73\u5747\u5F8B\u306E\u66F2\u3067\u3082\u4F7F\u3048\u307E\u3059\u3002\u548C\u97F3\u30FB\u30E1\u30ED\u30C7\u30A3\u3068\u308231\u5E73\u5747\u5F8B\u306E\u683C\u5B50\u306B\u4E57\u305B\u3066\u751F\u6210\u3057\u307E\u3059\u3002</li>
18668
19572
  <li><strong>\u30C9\u30E9\u30E0\u3082\u66F2\u306B\u5408\u308F\u305B\u3066\u7D44\u307F\u7ACB\u3066\u307E\u3059</strong>\u3002\u30C6\u30F3\u30DD\u3068\u30E1\u30ED\u30C7\u30A3\u306E\u523B\u307F\u304B\u30898\u30D3\u30FC\u30C8\uFF0F16\u30D3\u30FC\u30C8\uFF0F4\u3064\u6253\u3061\uFF0F\u30B7\u30E3\u30C3\u30D5\u30EB\uFF0F\u30D0\u30E9\u30FC\u30C9\uFF0F\u30ED\u30C3\u30AF\u306E\u3069\u308C\u304B\u3092\u9078\u3073\u3001A\u30FBA'\u30FB\u30B5\u30D3\u30FBA'' \u3067\u523B\u307F\u306E\u5F37\u3055\u3092\u5909\u3048\u30014\u5C0F\u7BC0\u3054\u3068\u306B\u30D5\u30A3\u30EB\u3001\u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u982D\u306B\u30AF\u30E9\u30C3\u30B7\u30E5\u3092\u7F6E\u304D\u307E\u3059\u3002\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u300C\u30C9\u30E9\u30E0\u8A2D\u5B9A\u300D\u304B\u3089\u4ED6\u306E\u30EA\u30BA\u30E0\u3078\u5207\u308A\u66FF\u3048\u3089\u308C\u307E\u3059\u3002</li>
@@ -18937,7 +19841,14 @@ var ADVANCED_COMPOSE_LAYOUT = [
18937
19841
  { index: 7, part: "block", octave: 0, volume: 62 },
18938
19842
  { index: 8, part: "arpeggio", octave: 0, volume: 54 },
18939
19843
  { index: 9, part: "offbeat", octave: 0, volume: 50 },
18940
- { index: 10, part: "uwamono", octave: 1, volume: 56 }
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 }
18941
19852
  ];
18942
19853
  var LYRIC_MODEL_CATEGORIES = [
18943
19854
  {
@@ -19217,6 +20128,7 @@ var mountDAW = (target, options = {}) => {
19217
20128
  let currentDrumPattern = refs.drumSelect.value;
19218
20129
  const recentComposeFingerprints = [];
19219
20130
  let autoComposeVocal = null;
20131
+ const autoComposeVocalTracks = /* @__PURE__ */ new Map();
19220
20132
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
19221
20133
  refs.drumFontSelect.value = currentDrumFont;
19222
20134
  const applyDrumPatternFont = (name) => {
@@ -19293,7 +20205,38 @@ var mountDAW = (target, options = {}) => {
19293
20205
  let trackStates = [];
19294
20206
  let suppressPatch = false;
19295
20207
  let lyricsDebounceTimer = null;
20208
+ const persistTrack1 = (t) => {
20209
+ if (trackStates.indexOf(t) !== 0) return;
20210
+ const settings = {
20211
+ volume: t.volume,
20212
+ trackInstrument: t.trackInstrument,
20213
+ trackOctave: t.trackOctave,
20214
+ trackOctaveUnison: t.trackOctaveUnison,
20215
+ trackCompression: t.trackCompression,
20216
+ trackWidth: t.trackWidth,
20217
+ trackReverbSend: t.trackReverbSend,
20218
+ trackEqLow: t.trackEqLow,
20219
+ trackEqMid: t.trackEqMid,
20220
+ trackEqHigh: t.trackEqHigh,
20221
+ trackPan: t.trackPan,
20222
+ trackDelaySend: t.trackDelaySend,
20223
+ lyricModel: t.lyricModel,
20224
+ vocalVolume: t.vocalVolume,
20225
+ vocalGate: t.vocalGate,
20226
+ vocalPan: t.vocalPan,
20227
+ vocalOctave: t.vocalOctave,
20228
+ vocalVibrato: t.vocalVibrato,
20229
+ vocalReverb: t.vocalReverb,
20230
+ vocalDelay: t.vocalDelay,
20231
+ vocalGender: t.vocalGender,
20232
+ vocalBreathiness: t.vocalBreathiness,
20233
+ vocalTension: t.vocalTension,
20234
+ vocalOctaveUnison: t.vocalOctaveUnison
20235
+ };
20236
+ writeTrack1Settings(settings);
20237
+ };
19296
20238
  const fireLyricsChange = (t) => {
20239
+ persistTrack1(t);
19297
20240
  if (!options.onLyricsChange) return;
19298
20241
  const trackId = t.config.id;
19299
20242
  const data = {
@@ -19319,9 +20262,11 @@ var mountDAW = (target, options = {}) => {
19319
20262
  };
19320
20263
  const hiddenTracks = /* @__PURE__ */ new Set();
19321
20264
  const audioMutedTracks = /* @__PURE__ */ new Set();
20265
+ const savedTrack1 = readTrack1Settings();
19322
20266
  const createTrackStates = () => {
19323
- trackStates = trackConfigs.map((config) => {
20267
+ trackStates = trackConfigs.map((config, index) => {
19324
20268
  let prevNotes = [];
20269
+ const t1 = index === 0 ? savedTrack1 : null;
19325
20270
  return {
19326
20271
  config,
19327
20272
  core: new MMLCore(
@@ -19366,35 +20311,35 @@ var mountDAW = (target, options = {}) => {
19366
20311
  config.volume,
19367
20312
  () => renderConfig
19368
20313
  ),
19369
- volume: config.volume,
20314
+ volume: t1?.volume ?? config.volume,
19370
20315
  savedChordInput: "",
19371
20316
  savedChordPattern: "block",
19372
20317
  savedChordRoot: 0,
19373
20318
  lyrics: "",
19374
- lyricModel: "",
20319
+ lyricModel: t1?.lyricModel ?? "",
19375
20320
  // 既定は「なし」(歌わない)
19376
- vocalVolume: DEFAULT_VOCAL_VOLUME,
19377
- vocalGate: 100,
19378
- vocalPan: 64,
19379
- trackOctave: 0,
19380
- trackOctaveUnison: "none",
19381
- vocalOctave: 0,
19382
- vocalVibrato: false,
19383
- vocalReverb: 0,
19384
- vocalDelay: 0,
19385
- vocalGender: 50,
19386
- vocalBreathiness: 50,
19387
- vocalTension: 50,
19388
- vocalOctaveUnison: "none",
19389
- trackInstrument: "",
19390
- trackCompression: 0,
19391
- trackWidth: 100,
19392
- trackReverbSend: 0,
19393
- trackEqLow: 0,
19394
- trackEqMid: 0,
19395
- trackEqHigh: 0,
19396
- trackPan: 64,
19397
- trackDelaySend: 0
20321
+ vocalVolume: t1?.vocalVolume ?? DEFAULT_VOCAL_VOLUME,
20322
+ vocalGate: t1?.vocalGate ?? 100,
20323
+ vocalPan: t1?.vocalPan ?? 64,
20324
+ trackOctave: t1?.trackOctave ?? 0,
20325
+ trackOctaveUnison: t1?.trackOctaveUnison ?? "none",
20326
+ vocalOctave: t1?.vocalOctave ?? 0,
20327
+ vocalVibrato: t1?.vocalVibrato ?? false,
20328
+ vocalReverb: t1?.vocalReverb ?? 0,
20329
+ vocalDelay: t1?.vocalDelay ?? 0,
20330
+ vocalGender: t1?.vocalGender ?? 50,
20331
+ vocalBreathiness: t1?.vocalBreathiness ?? 50,
20332
+ vocalTension: t1?.vocalTension ?? 50,
20333
+ vocalOctaveUnison: t1?.vocalOctaveUnison ?? "none",
20334
+ trackInstrument: t1?.trackInstrument ?? "",
20335
+ trackCompression: t1?.trackCompression ?? 0,
20336
+ trackWidth: t1?.trackWidth ?? 100,
20337
+ trackReverbSend: t1?.trackReverbSend ?? 0,
20338
+ trackEqLow: t1?.trackEqLow ?? 0,
20339
+ trackEqMid: t1?.trackEqMid ?? 0,
20340
+ trackEqHigh: t1?.trackEqHigh ?? 0,
20341
+ trackPan: t1?.trackPan ?? 64,
20342
+ trackDelaySend: t1?.trackDelaySend ?? 0
19398
20343
  };
19399
20344
  });
19400
20345
  };
@@ -20562,6 +21507,7 @@ var mountDAW = (target, options = {}) => {
20562
21507
  active.volume = Number.parseInt(volInput.value, 10);
20563
21508
  active.core.setVolume(active.volume);
20564
21509
  volLabel.textContent = String(active.volume);
21510
+ persistTrack1(active);
20565
21511
  });
20566
21512
  const syncVelocityDisabled = () => {
20567
21513
  volInput.disabled = !!active.lyricModel;
@@ -20600,16 +21546,19 @@ var mountDAW = (target, options = {}) => {
20600
21546
  active.trackEqLow = Number.parseInt(trackEqLowInput.value, 10);
20601
21547
  trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
20602
21548
  options.onTrackEqLowChange?.(active.config.id, active.trackEqLow);
21549
+ persistTrack1(active);
20603
21550
  });
20604
21551
  trackEqMidInput.addEventListener("input", () => {
20605
21552
  active.trackEqMid = Number.parseInt(trackEqMidInput.value, 10);
20606
21553
  trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
20607
21554
  options.onTrackEqMidChange?.(active.config.id, active.trackEqMid);
21555
+ persistTrack1(active);
20608
21556
  });
20609
21557
  trackEqHighInput.addEventListener("input", () => {
20610
21558
  active.trackEqHigh = Number.parseInt(trackEqHighInput.value, 10);
20611
21559
  trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
20612
21560
  options.onTrackEqHighChange?.(active.config.id, active.trackEqHigh);
21561
+ persistTrack1(active);
20613
21562
  });
20614
21563
  trackEqInfo.addEventListener("click", () => {
20615
21564
  showModal("EQ\uFF08\u30A4\u30B3\u30E9\u30A4\u30B6\u30FC\uFF09\u306E\u89E3\u8AAC", TRACK_EQ_INFO_HTML);
@@ -20643,11 +21592,13 @@ var mountDAW = (target, options = {}) => {
20643
21592
  active.config.id,
20644
21593
  active.trackCompression
20645
21594
  );
21595
+ persistTrack1(active);
20646
21596
  });
20647
21597
  trackWidthInput.addEventListener("input", () => {
20648
21598
  active.trackWidth = Number.parseInt(trackWidthInput.value, 10);
20649
21599
  trackWidthLabel.textContent = `${active.trackWidth}%`;
20650
21600
  options.onTrackWidthChange?.(active.config.id, active.trackWidth);
21601
+ persistTrack1(active);
20651
21602
  });
20652
21603
  trackCompInfo.addEventListener("click", () => {
20653
21604
  showModal("\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC", TRACK_COMPRESSION_INFO_HTML);
@@ -20671,6 +21622,7 @@ var mountDAW = (target, options = {}) => {
20671
21622
  active.trackPan = Number.parseInt(trackPanInput.value, 10);
20672
21623
  trackPanLabel.textContent = fmtTrackPan(active.trackPan);
20673
21624
  options.onTrackPanChange?.(active.config.id, active.trackPan);
21625
+ persistTrack1(active);
20674
21626
  });
20675
21627
  trackPanInfo.addEventListener("click", () => {
20676
21628
  showModal("\u5B9A\u4F4D\u306E\u89E3\u8AAC", TRACK_PAN_INFO_HTML);
@@ -20693,6 +21645,7 @@ var mountDAW = (target, options = {}) => {
20693
21645
  active.config.id,
20694
21646
  active.trackReverbSend
20695
21647
  );
21648
+ persistTrack1(active);
20696
21649
  });
20697
21650
  trackReverbSendInfo.addEventListener("click", () => {
20698
21651
  showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", TRACK_REVERBSEND_INFO_HTML);
@@ -20712,6 +21665,7 @@ var mountDAW = (target, options = {}) => {
20712
21665
  active.trackDelaySend = Number.parseInt(trackDelaySendInput.value, 10);
20713
21666
  trackDelaySendLabel.textContent = `${active.trackDelaySend}%`;
20714
21667
  options.onTrackDelaySendChange?.(active.config.id, active.trackDelaySend);
21668
+ persistTrack1(active);
20715
21669
  });
20716
21670
  trackDelaySendInfo.addEventListener("click", () => {
20717
21671
  showModal("\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC", TRACK_DELAYSEND_INFO_HTML);
@@ -20766,6 +21720,7 @@ var mountDAW = (target, options = {}) => {
20766
21720
  active.trackInstrument = instSel.value;
20767
21721
  const trackIndex = trackStates.indexOf(active);
20768
21722
  options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
21723
+ persistTrack1(active);
20769
21724
  });
20770
21725
  refs.trackBody.querySelector('[data-dtm="track-vol-row"]').prepend(instLabel, instSel);
20771
21726
  const octaveRow = refs.trackBody.querySelector(
@@ -20781,6 +21736,7 @@ var mountDAW = (target, options = {}) => {
20781
21736
  syncOctaveVisibility();
20782
21737
  octaveSel.addEventListener("change", () => {
20783
21738
  active.trackOctave = Number.parseInt(octaveSel.value, 10) || 0;
21739
+ persistTrack1(active);
20784
21740
  });
20785
21741
  const unisonSel = refs.trackBody.querySelector(
20786
21742
  '[data-dtm="track-octave-unison"]'
@@ -20788,6 +21744,7 @@ var mountDAW = (target, options = {}) => {
20788
21744
  unisonSel.value = active.trackOctaveUnison;
20789
21745
  unisonSel.addEventListener("change", () => {
20790
21746
  active.trackOctaveUnison = unisonSel.value;
21747
+ persistTrack1(active);
20791
21748
  });
20792
21749
  if (isAdvanced || active.config.id !== "chord") {
20793
21750
  const lyricDiv = document.createElement("div");
@@ -21939,16 +22896,32 @@ var mountDAW = (target, options = {}) => {
21939
22896
  updateTrackPanel();
21940
22897
  updateUndoRedo();
21941
22898
  };
21942
- const exportMIDI2 = () => exportMIDI({
21943
- tracks: trackStates.map((t) => ({
21944
- notes: playableNotes(t),
21945
- volume: t.volume
21946
- })),
21947
- getDrumPattern: (currentBar) => resolveDrumPattern(currentDrumPattern, drumPatterns, currentBar),
21948
- drumVolume,
21949
- bpm,
21950
- stepsPerBar: renderConfig.stepsPerBar
21951
- });
22899
+ const exportMIDI2 = () => {
22900
+ const autoPreset = INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano;
22901
+ return exportMIDI({
22902
+ tracks: trackStates.map((t) => {
22903
+ let program;
22904
+ if (t.trackInstrument) {
22905
+ const p = programOfInstrumentName(t.trackInstrument);
22906
+ if (p !== null) program = p;
22907
+ } else if (!t.lyricModel) {
22908
+ const role = DECLARED_ROLE[t.config.id] ?? "melody";
22909
+ const instName = autoPreset[role] ?? autoPreset.melody;
22910
+ const p = programOfInstrumentName(instName);
22911
+ if (p !== null) program = p;
22912
+ }
22913
+ return {
22914
+ notes: playableNotes(t),
22915
+ volume: t.volume,
22916
+ program
22917
+ };
22918
+ }),
22919
+ getDrumPattern: (currentBar) => resolveDrumPattern(currentDrumPattern, drumPatterns, currentBar),
22920
+ drumVolume,
22921
+ bpm,
22922
+ stepsPerBar: renderConfig.stepsPerBar
22923
+ });
22924
+ };
21952
22925
  const setBpm = (value) => {
21953
22926
  bpm = value;
21954
22927
  refs.bpmInput.value = String(value);
@@ -22571,7 +23544,7 @@ var mountDAW = (target, options = {}) => {
22571
23544
  for (const layer of ADVANCED_COMPOSE_LAYOUT) {
22572
23545
  const track = trackStates[layer.index];
22573
23546
  if (!track) continue;
22574
- const notes = 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({
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({
22575
23548
  edo: renderConfig.edo,
22576
23549
  chordStr: song.chordProgression,
22577
23550
  patternType: "arpeggio",
@@ -22629,18 +23602,95 @@ var mountDAW = (target, options = {}) => {
22629
23602
  options.onInstrumentChange?.(song.instrument);
22630
23603
  }
22631
23604
  if (withVocal) {
23605
+ autoComposeVocalTracks.clear();
22632
23606
  const melodyTrack = isAdvanced ? trackStates[0] : trackStates.find((t) => t.config.id === "melody");
22633
23607
  if (melodyTrack) {
22634
23608
  const current = melodyTrack.lyricModel.trim();
22635
23609
  if (!current || current === autoComposeVocal) {
23610
+ if (!current && melodyTrack.vocalOctave === 0) {
23611
+ melodyTrack.vocalOctave = -1;
23612
+ }
22636
23613
  melodyTrack.lyricModel = pickComposeVocal(autoComposeVocal);
22637
23614
  autoComposeVocal = melodyTrack.lyricModel;
23615
+ autoComposeVocalTracks.set(melodyTrack, melodyTrack.lyricModel);
22638
23616
  }
22639
23617
  melodyTrack.lyrics = composeLyrics(song.melody, {
22640
23618
  stepsPerBar: renderConfig.stepsPerBar
22641
23619
  });
22642
23620
  fireLyricsChange(melodyTrack);
22643
23621
  }
23622
+ if (isAdvanced && melodyTrack) {
23623
+ const stepsPerBar = renderConfig.stepsPerBar;
23624
+ const spans = song.vocal.duetSpans;
23625
+ const inDuet = (n) => spans.some(([a, b]) => n.startStep >= a && n.startStep < b);
23626
+ const sing = (track, lyrics, voice) => {
23627
+ if (!track) return;
23628
+ track.lyricModel = voice;
23629
+ autoComposeVocalTracks.set(track, voice);
23630
+ if (track.vocalOctave === 0) track.vocalOctave = -1;
23631
+ track.lyrics = lyrics;
23632
+ fireLyricsChange(track);
23633
+ };
23634
+ const partnerVoice = pickComposeVocal(melodyTrack.lyricModel);
23635
+ const duetting = spans.length > 0;
23636
+ if (duetting) {
23637
+ const mine = song.melody.filter((n) => !inDuet(n));
23638
+ const yours = song.melody.filter((n) => inDuet(n));
23639
+ writeTrackAt(0, mine);
23640
+ writeTrackAt(11, yours);
23641
+ const full = melodyTrack.lyrics;
23642
+ sing(
23643
+ melodyTrack,
23644
+ alignLyrics(song.melody, full, mine, { stepsPerBar }),
23645
+ melodyTrack.lyricModel
23646
+ );
23647
+ sing(
23648
+ trackStates[11],
23649
+ alignLyrics(song.melody, full, yours, { stepsPerBar }),
23650
+ partnerVoice
23651
+ );
23652
+ }
23653
+ const harmonyTrack = trackStates[2];
23654
+ if (song.harmony.length > 0 && harmonyTrack) {
23655
+ sing(
23656
+ harmonyTrack,
23657
+ alignLyrics(song.melody, melodyTrack.lyrics, song.harmony, {
23658
+ stepsPerBar
23659
+ }),
23660
+ duetting ? partnerVoice : melodyTrack.lyricModel
23661
+ );
23662
+ }
23663
+ if (!duetting) {
23664
+ if (song.harmony2.length > 0)
23665
+ sing(
23666
+ trackStates[12],
23667
+ alignLyrics(song.melody, melodyTrack.lyrics, song.harmony2, {
23668
+ stepsPerBar
23669
+ }),
23670
+ melodyTrack.lyricModel
23671
+ );
23672
+ const octaveTrack = trackStates[13];
23673
+ if (song.octave.length > 0 && octaveTrack) {
23674
+ sing(
23675
+ octaveTrack,
23676
+ alignLyrics(song.melody, melodyTrack.lyrics, song.octave, {
23677
+ stepsPerBar
23678
+ }),
23679
+ melodyTrack.lyricModel
23680
+ );
23681
+ octaveTrack.vocalOctave = -2;
23682
+ }
23683
+ }
23684
+ }
23685
+ } else {
23686
+ for (const [track, voice] of autoComposeVocalTracks) {
23687
+ if (track.lyricModel === voice) {
23688
+ track.lyrics = "";
23689
+ track.lyricModel = "";
23690
+ fireLyricsChange(track);
23691
+ }
23692
+ }
23693
+ autoComposeVocalTracks.clear();
22644
23694
  }
22645
23695
  applyAutoMastering();
22646
23696
  if (refs.composeKeyHint) {
@@ -23777,7 +24827,10 @@ var mountDAW = (target, options = {}) => {
23777
24827
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
23778
24828
  t.vocalTension = data.vocalTension ?? 50;
23779
24829
  t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
23780
- if (t.config.id === activeTrackId) redrawAll();
24830
+ if (t.config.id === activeTrackId) {
24831
+ updateTrackPanel();
24832
+ redrawAll();
24833
+ }
23781
24834
  },
23782
24835
  applyTrackInstrument: (trackIndex, instrumentName) => {
23783
24836
  const t = trackStates[trackIndex];
@@ -26187,6 +27240,7 @@ var createDtmStudio = async (options = {}) => {
26187
27240
  playNote,
26188
27241
  playPlacements,
26189
27242
  playSingingMML,
27243
+ programOfInstrumentName,
26190
27244
  readGlobalBool,
26191
27245
  readGlobalNumber,
26192
27246
  readGlobalSetting,