@onjmin/dtm 0.1.83 → 0.1.85

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
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  PREWARM_NOTES: () => PREWARM_NOTES,
47
47
  TRACKS_ADVANCED: () => TRACKS_ADVANCED,
48
48
  TRACKS_SIMPLE: () => TRACKS_SIMPLE,
49
+ VIBRATO_MIN_SEC: () => VIBRATO_MIN_SEC,
49
50
  VOICE_IMAGES: () => VOICE_IMAGES,
50
51
  VOICE_IMAGE_KEY: () => VOICE_IMAGE_KEY,
51
52
  analyzeMidiTracks: () => analyzeMidiTracks,
@@ -348,8 +349,8 @@ var Input = class _Input {
348
349
  }
349
350
  return str.length ? Number(str) : null;
350
351
  }
351
- slice(i) {
352
- return this.str.slice(this.idx, this.idx + i);
352
+ slice(i2) {
353
+ return this.str.slice(this.idx, this.idx + i2);
353
354
  }
354
355
  };
355
356
  var Output = class {
@@ -385,8 +386,8 @@ var Matcher = class {
385
386
  else this._set(key, value);
386
387
  }
387
388
  parse(input) {
388
- for (const i of this.lengths) {
389
- const s = input.slice(i);
389
+ for (const i2 of this.lengths) {
390
+ const s = input.slice(i2);
390
391
  if (this.map.has(s)) {
391
392
  input.idx += s.length;
392
393
  return this.map.get(s);
@@ -440,7 +441,7 @@ var parseFormula = (input, output = new Output(), nest = 0) => {
440
441
  if (o.isChord) {
441
442
  output.value = [...o.value].concat(v);
442
443
  } else {
443
- const a = v.sort((x, y) => x - y);
444
+ const a = v.sort((x2, y) => x2 - y);
444
445
  const pitch = (o.pitch + 3) % 12 - 3;
445
446
  if (a[0] < pitch) {
446
447
  while (a[0] < pitch) a.push(a.shift() + 12);
@@ -477,16 +478,16 @@ halfMatcher.set("+", 1);
477
478
  halfMatcher.set("-", -1);
478
479
  var parseHalf = (input, isPitch = false) => (isPitch ? halfMatcherStrict : halfMatcher).parse(input);
479
480
  var idx2pitch = [0, 2, 4, 5, 7, 9, 11];
480
- for (const i of [...idx2pitch.keys()]) idx2pitch.push(idx2pitch[i] + 12);
481
+ for (const i2 of [...idx2pitch.keys()]) idx2pitch.push(idx2pitch[i2] + 12);
481
482
  var deg2pitch = (deg) => idx2pitch[deg - 1];
482
483
  var pitchMatcher = new Matcher();
483
- for (const [i, v] of [..."CDEFGAB"].entries())
484
- pitchMatcher.set(v, idx2pitch[i]);
484
+ for (const [i2, v] of [..."CDEFGAB"].entries())
485
+ pitchMatcher.set(v, idx2pitch[i2]);
485
486
  var parsePitch = (input, output) => {
486
487
  const pitch = pitchMatcher.parse(input);
487
488
  if (pitch === null) err(input, "Not found pitch");
488
489
  output.pitch = pitch;
489
- for (let i = 0; i < 2; i++) {
490
+ for (let i2 = 0; i2 < 2; i2++) {
490
491
  const half = parseHalf(input, true);
491
492
  if (half === null) break;
492
493
  output.pitch += half;
@@ -692,11 +693,11 @@ var pearson = (a, b) => {
692
693
  let num = 0;
693
694
  let da = 0;
694
695
  let db = 0;
695
- for (let i = 0; i < a.length; i++) {
696
- const x = a[i] - ma;
697
- const y = b[i] - mb;
698
- num += x * y;
699
- da += x * x;
696
+ for (let i2 = 0; i2 < a.length; i2++) {
697
+ const x2 = a[i2] - ma;
698
+ const y = b[i2] - mb;
699
+ num += x2 * y;
700
+ da += x2 * x2;
700
701
  db += y * y;
701
702
  }
702
703
  const den = Math.sqrt(da * db);
@@ -772,19 +773,19 @@ var coalesce = (segments) => {
772
773
  var mergeShortSegments = (segments, min) => {
773
774
  if (min <= 0) return segments;
774
775
  const result = segments.map((s) => ({ ...s }));
775
- let i = 0;
776
- while (i < result.length && result.length > 1) {
777
- if (result[i].duration >= min) {
778
- i++;
776
+ let i2 = 0;
777
+ while (i2 < result.length && result.length > 1) {
778
+ if (result[i2].duration >= min) {
779
+ i2++;
779
780
  continue;
780
781
  }
781
- if (i > 0) {
782
- result[i - 1].duration += result[i].duration;
783
- result.splice(i, 1);
782
+ if (i2 > 0) {
783
+ result[i2 - 1].duration += result[i2].duration;
784
+ result.splice(i2, 1);
784
785
  } else {
785
- result[i + 1].when = result[i].when;
786
- result[i + 1].duration += result[i].duration;
787
- result.splice(i, 1);
786
+ result[i2 + 1].when = result[i2].when;
787
+ result[i2 + 1].duration += result[i2].duration;
788
+ result.splice(i2, 1);
788
789
  }
789
790
  }
790
791
  return coalesce(result);
@@ -1003,19 +1004,19 @@ var viterbi = (emissions, changePenalty) => {
1003
1004
  const curr = new Array(N).fill(0);
1004
1005
  const em = emissions[t];
1005
1006
  const switchVal = bestPrevVal - changePenalty;
1006
- for (let i = 0; i < N; i++) {
1007
- if (prev[i] >= switchVal) {
1008
- curr[i] = em[i] + prev[i];
1009
- back[t][i] = i;
1007
+ for (let i2 = 0; i2 < N; i2++) {
1008
+ if (prev[i2] >= switchVal) {
1009
+ curr[i2] = em[i2] + prev[i2];
1010
+ back[t][i2] = i2;
1010
1011
  } else {
1011
- curr[i] = em[i] + switchVal;
1012
- back[t][i] = bestPrevIdx;
1012
+ curr[i2] = em[i2] + switchVal;
1013
+ back[t][i2] = bestPrevIdx;
1013
1014
  }
1014
1015
  }
1015
1016
  prev = curr;
1016
1017
  }
1017
1018
  let bestIdx = 0;
1018
- for (let i = 1; i < N; i++) if (prev[i] > prev[bestIdx]) bestIdx = i;
1019
+ for (let i2 = 1; i2 < N; i2++) if (prev[i2] > prev[bestIdx]) bestIdx = i2;
1019
1020
  const path = new Array(T).fill(0);
1020
1021
  path[T - 1] = bestIdx;
1021
1022
  for (let t = T - 1; t > 0; t--) path[t - 1] = back[t][path[t]];
@@ -1112,20 +1113,20 @@ var parseChords = (str, bpm = 120) => {
1112
1113
  if (!str2.length) continue;
1113
1114
  const when = idx++ * secBar;
1114
1115
  const a = [];
1115
- for (let i = 0; i < str2.length; i++) {
1116
- const char = str2[i];
1117
- const prev = str2[i - 1];
1118
- const prev2 = str2.slice(i - 2, i);
1116
+ for (let i2 = 0; i2 < str2.length; i2++) {
1117
+ const char = str2[i2];
1118
+ const prev = str2[i2 - 1];
1119
+ const prev2 = str2.slice(i2 - 2, i2);
1119
1120
  if (!frontChars.has(char)) continue;
1120
1121
  if (prev === "/" || prev2 === "on") continue;
1121
1122
  if (prev2 === "N." && char === "C") continue;
1122
- a.push(i);
1123
+ a.push(i2);
1123
1124
  }
1124
1125
  if (!a.length) continue;
1125
1126
  const divide = 2 ** Math.ceil(Math.log2(a.length));
1126
1127
  const unitTime = secBar / divide;
1127
- for (const [i, v] of a.entries()) {
1128
- const s = str2.slice(v, i === a.length - 1 ? str2.length : a[i + 1]).replace(/\s+/g, "");
1128
+ for (const [i2, v] of a.entries()) {
1129
+ const s = str2.slice(v, i2 === a.length - 1 ? str2.length : a[i2 + 1]).replace(/\s+/g, "");
1129
1130
  const c = s[0];
1130
1131
  if (c === "_" || c === "N") {
1131
1132
  last = null;
@@ -1135,7 +1136,7 @@ var parseChords = (str, bpm = 120) => {
1135
1136
  if (last) last.duration += unitTime;
1136
1137
  continue;
1137
1138
  }
1138
- const _when = when + i * unitTime;
1139
+ const _when = when + i2 * unitTime;
1139
1140
  if (c === "%") {
1140
1141
  if (last === null) continue;
1141
1142
  const base = last;
@@ -1300,19 +1301,19 @@ var resolveDrumPattern = (name, dict, currentBar) => {
1300
1301
  if (Array.isArray(patternObj) && patternObj.length > 0 && "ranges" in patternObj[0]) {
1301
1302
  const songDef = patternObj;
1302
1303
  const instructions = songDef.filter(
1303
- (i) => i.ranges.some(([s, e]) => currentBar >= s && currentBar <= e)
1304
+ (i2) => i2.ranges.some(([s, e]) => currentBar >= s && currentBar <= e)
1304
1305
  );
1305
1306
  if (instructions.length > 0) {
1306
- return instructions.flatMap((i) => {
1307
- const maxStep = Math.max(...i.pattern.map((p) => p.step), 0);
1308
- const loopLengthBars = i.patternBars ?? Math.max(1, Math.ceil((maxStep + 1) / 192));
1309
- const range = i.ranges.find(
1307
+ return instructions.flatMap((i2) => {
1308
+ const maxStep = Math.max(...i2.pattern.map((p) => p.step), 0);
1309
+ const loopLengthBars = i2.patternBars ?? Math.max(1, Math.ceil((maxStep + 1) / 192));
1310
+ const range = i2.ranges.find(
1310
1311
  ([s, e]) => currentBar >= s && currentBar <= e
1311
1312
  );
1312
1313
  const startBar = range ? range[0] : 1;
1313
1314
  const barInLoop = (currentBar - startBar) % loopLengthBars;
1314
1315
  const stepOffset = barInLoop * 192;
1315
- return i.pattern.filter((p) => p.step >= stepOffset && p.step < stepOffset + 192).map((p) => ({ ...p, step: p.step - stepOffset }));
1316
+ return i2.pattern.filter((p) => p.step >= stepOffset && p.step < stepOffset + 192).map((p) => ({ ...p, step: p.step - stepOffset }));
1316
1317
  });
1317
1318
  }
1318
1319
  }
@@ -1455,8 +1456,8 @@ var getDrumPatternKeys = (name, dict) => {
1455
1456
  const keys = /* @__PURE__ */ new Set();
1456
1457
  if (Array.isArray(patternObj) && patternObj.length > 0 && "ranges" in patternObj[0]) {
1457
1458
  const songDef = patternObj;
1458
- for (const i of songDef) {
1459
- for (const p of i.pattern) keys.add(p.pitch);
1459
+ for (const i2 of songDef) {
1460
+ for (const p of i2.pattern) keys.add(p.pitch);
1460
1461
  }
1461
1462
  } else if (Array.isArray(patternObj)) {
1462
1463
  for (const p of patternObj) keys.add(p.pitch);
@@ -1513,21 +1514,21 @@ var buildChordPlacements = (options) => {
1513
1514
  }
1514
1515
  } else if (patternType === "arpeggio") {
1515
1516
  const arpInterval = Math.floor(noteLength / notes.length);
1516
- notes.forEach((noteOffset, i) => {
1517
+ notes.forEach((noteOffset, i2) => {
1517
1518
  placements.push({
1518
- startStep: chord.whenStep + i * arpInterval,
1519
+ startStep: chord.whenStep + i2 * arpInterval,
1519
1520
  pitch: C3 + noteOffset + offset,
1520
- durationSteps: noteLength - i * arpInterval,
1521
+ durationSteps: noteLength - i2 * arpInterval,
1521
1522
  velocity: 100
1522
1523
  });
1523
1524
  });
1524
1525
  } else if (patternType === "arpeggio-fast") {
1525
1526
  const arpInterval = 6;
1526
- notes.forEach((noteOffset, i) => {
1527
+ notes.forEach((noteOffset, i2) => {
1527
1528
  placements.push({
1528
- startStep: chord.whenStep + i * arpInterval,
1529
+ startStep: chord.whenStep + i2 * arpInterval,
1529
1530
  pitch: C3 + noteOffset + offset,
1530
- durationSteps: Math.max(12, noteLength - i * arpInterval),
1531
+ durationSteps: Math.max(12, noteLength - i2 * arpInterval),
1531
1532
  velocity: 100
1532
1533
  });
1533
1534
  });
@@ -1567,8 +1568,8 @@ var buildChordPlacements = (options) => {
1567
1568
  }
1568
1569
  }
1569
1570
  } else if (patternType === "alternating") {
1570
- notes.forEach((noteOffset, i) => {
1571
- const stepOffset = i * Math.floor(stepsPerBar / 4);
1571
+ notes.forEach((noteOffset, i2) => {
1572
+ const stepOffset = i2 * Math.floor(stepsPerBar / 4);
1572
1573
  placements.push({
1573
1574
  startStep: chord.whenStep + stepOffset,
1574
1575
  pitch: C3 + noteOffset + offset,
@@ -1590,8 +1591,8 @@ var buildChordPlacements = (options) => {
1590
1591
  }
1591
1592
  if (notes.length === 0) return;
1592
1593
  const startStep = barIndex * chordLength;
1593
- notes.forEach((noteOffset, i) => {
1594
- const stepOffset = i * 3;
1594
+ notes.forEach((noteOffset, i2) => {
1595
+ const stepOffset = i2 * 3;
1595
1596
  placements.push({
1596
1597
  startStep: startStep + stepOffset,
1597
1598
  pitch: C3 + noteOffset + offset,
@@ -1604,7 +1605,136 @@ var buildChordPlacements = (options) => {
1604
1605
  return placements;
1605
1606
  };
1606
1607
 
1607
- // node_modules/.pnpm/@onjmin+koe@1.0.4/node_modules/@onjmin/koe/dist/index.js
1608
+ // node_modules/.pnpm/@onjmin+koe@1.0.5/node_modules/@onjmin/koe/dist/index.js
1609
+ var u8 = Uint8Array;
1610
+ var u16 = Uint16Array;
1611
+ var i32 = Int32Array;
1612
+ var fleb = new u8([
1613
+ 0,
1614
+ 0,
1615
+ 0,
1616
+ 0,
1617
+ 0,
1618
+ 0,
1619
+ 0,
1620
+ 0,
1621
+ 1,
1622
+ 1,
1623
+ 1,
1624
+ 1,
1625
+ 2,
1626
+ 2,
1627
+ 2,
1628
+ 2,
1629
+ 3,
1630
+ 3,
1631
+ 3,
1632
+ 3,
1633
+ 4,
1634
+ 4,
1635
+ 4,
1636
+ 4,
1637
+ 5,
1638
+ 5,
1639
+ 5,
1640
+ 5,
1641
+ 0,
1642
+ /* unused */
1643
+ 0,
1644
+ 0,
1645
+ /* impossible */
1646
+ 0
1647
+ ]);
1648
+ var fdeb = new u8([
1649
+ 0,
1650
+ 0,
1651
+ 0,
1652
+ 0,
1653
+ 1,
1654
+ 1,
1655
+ 2,
1656
+ 2,
1657
+ 3,
1658
+ 3,
1659
+ 4,
1660
+ 4,
1661
+ 5,
1662
+ 5,
1663
+ 6,
1664
+ 6,
1665
+ 7,
1666
+ 7,
1667
+ 8,
1668
+ 8,
1669
+ 9,
1670
+ 9,
1671
+ 10,
1672
+ 10,
1673
+ 11,
1674
+ 11,
1675
+ 12,
1676
+ 12,
1677
+ 13,
1678
+ 13,
1679
+ /* unused */
1680
+ 0,
1681
+ 0
1682
+ ]);
1683
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
1684
+ var freb = function(eb, start) {
1685
+ var b = new u16(31);
1686
+ for (var i2 = 0; i2 < 31; ++i2) {
1687
+ b[i2] = start += 1 << eb[i2 - 1];
1688
+ }
1689
+ var r = new i32(b[30]);
1690
+ for (var i2 = 1; i2 < 30; ++i2) {
1691
+ for (var j = b[i2]; j < b[i2 + 1]; ++j) {
1692
+ r[j] = j - b[i2] << 5 | i2;
1693
+ }
1694
+ }
1695
+ return { b, r };
1696
+ };
1697
+ var _a = freb(fleb, 2);
1698
+ var fl = _a.b;
1699
+ var revfl = _a.r;
1700
+ fl[28] = 258, revfl[258] = 28;
1701
+ var _b = freb(fdeb, 0);
1702
+ var fd = _b.b;
1703
+ var revfd = _b.r;
1704
+ var rev = new u16(32768);
1705
+ for (i = 0; i < 32768; ++i) {
1706
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
1707
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
1708
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
1709
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
1710
+ }
1711
+ var x;
1712
+ var i;
1713
+ var flt = new u8(288);
1714
+ for (i = 0; i < 144; ++i)
1715
+ flt[i] = 8;
1716
+ var i;
1717
+ for (i = 144; i < 256; ++i)
1718
+ flt[i] = 9;
1719
+ var i;
1720
+ for (i = 256; i < 280; ++i)
1721
+ flt[i] = 7;
1722
+ var i;
1723
+ for (i = 280; i < 288; ++i)
1724
+ flt[i] = 8;
1725
+ var i;
1726
+ var fdt = new u8(32);
1727
+ for (i = 0; i < 32; ++i)
1728
+ fdt[i] = 5;
1729
+ var i;
1730
+ var et = /* @__PURE__ */ new u8(0);
1731
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
1732
+ var tds = 0;
1733
+ try {
1734
+ td.decode(et, { stream: true });
1735
+ tds = 1;
1736
+ } catch (e) {
1737
+ }
1608
1738
  var MAGIC = 1263486208;
1609
1739
  function parseKoeHeader(headerBytes) {
1610
1740
  const view = new DataView(headerBytes);
@@ -1770,7 +1900,7 @@ var VoiceBank = class _VoiceBank {
1770
1900
  if (!buf) return null;
1771
1901
  const int16 = new Int16Array(buf, 0, Math.floor(buf.byteLength / 2));
1772
1902
  const f64 = new Float64Array(int16.length);
1773
- for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;
1903
+ for (let i2 = 0; i2 < int16.length; i2++) f64[i2] = int16[i2] / 32768;
1774
1904
  return f64;
1775
1905
  }
1776
1906
  };
@@ -1778,6 +1908,7 @@ var WORLDLINE_SAMPLE_RATE = 48e3;
1778
1908
  var MIN_WORLDLINE_SAMPLES = 4096;
1779
1909
  var SYNTH_REQ_SIZE = 120;
1780
1910
  var WL_FRAME_MS = 10;
1911
+ var sampleCurve = (input, curve, totalMs) => typeof curve === "function" ? curve(input, totalMs) : curve;
1781
1912
  var samplesToMs = (samples) => samples / WORLDLINE_SAMPLE_RATE * 1e3;
1782
1913
  function leadInFromEntry(entry) {
1783
1914
  return {
@@ -1863,11 +1994,23 @@ var Worldline = class _Worldline {
1863
1994
  * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
1864
1995
  */
1865
1996
  renderNote(params) {
1866
- const { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;
1997
+ const {
1998
+ pcm,
1999
+ pitch,
2000
+ durationMs,
2001
+ preMs,
2002
+ consonantMs,
2003
+ tempo = 120,
2004
+ gender = 0.5,
2005
+ tension = 0.5,
2006
+ breathiness = 0.5,
2007
+ voicing = 1
2008
+ } = params;
1867
2009
  if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
1868
2010
  const WL = this.wasm;
1869
2011
  const FS = WORLDLINE_SAMPLE_RATE;
1870
- const midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));
2012
+ const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
2013
+ const midiNote = Math.round(69 + 12 * Math.log2(basePitch / 440));
1871
2014
  const posMs = 0;
1872
2015
  const reqLen = preMs + durationMs;
1873
2016
  const cutMs = WL_FRAME_MS * 2;
@@ -1913,11 +2056,19 @@ var Worldline = class _Worldline {
1913
2056
  WL._free(reqPtr);
1914
2057
  const totalMs = posMs + reqLen + WL_FRAME_MS * 2;
1915
2058
  const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
1916
- const f0Arr = new Float64Array(nFrames).fill(pitch);
1917
- const gArr = new Float64Array(nFrames).fill(0.5);
1918
- const tArr = new Float64Array(nFrames).fill(0.5);
1919
- const bArr = new Float64Array(nFrames).fill(0.5);
1920
- const vArr = new Float64Array(nFrames).fill(1);
2059
+ const f0Arr = new Float64Array(nFrames);
2060
+ const gArr = new Float64Array(nFrames);
2061
+ const tArr = new Float64Array(nFrames);
2062
+ const bArr = new Float64Array(nFrames);
2063
+ const vArr = new Float64Array(nFrames);
2064
+ for (let i2 = 0; i2 < nFrames; i2++) {
2065
+ const tMs = i2 * WL_FRAME_MS;
2066
+ f0Arr[i2] = sampleCurve(tMs, pitch, totalMs);
2067
+ gArr[i2] = sampleCurve(tMs, gender, totalMs);
2068
+ tArr[i2] = sampleCurve(tMs, tension, totalMs);
2069
+ bArr[i2] = sampleCurve(tMs, breathiness, totalMs);
2070
+ vArr[i2] = sampleCurve(tMs, voicing, totalMs);
2071
+ }
1921
2072
  const f0Ptr = WL._malloc(nFrames * 8);
1922
2073
  const gPtr = WL._malloc(nFrames * 8);
1923
2074
  const tPtr = WL._malloc(nFrames * 8);
@@ -1977,6 +2128,19 @@ var DEFAULT_PLAYBACK_VELOCITY = 127;
1977
2128
  var DEFAULT_STEPS_PER_BAR = 192;
1978
2129
  var MML_END_MARKER = "#end;";
1979
2130
 
2131
+ // src/vibrato.ts
2132
+ var VIBRATO_MIN_SEC = 0.35;
2133
+ var VIBRATO_RATE_HZ = 5.5;
2134
+ var VIBRATO_DEPTH_CENTS = 35;
2135
+ var VIBRATO_FADE_MS = 150;
2136
+ var vibratoPitchCurve = (baseHz, preMs) => (tMs) => {
2137
+ const sinceOnset = tMs - preMs;
2138
+ if (sinceOnset <= 0) return baseHz;
2139
+ const depth = VIBRATO_DEPTH_CENTS * Math.min(1, sinceOnset / VIBRATO_FADE_MS);
2140
+ const cents = depth * Math.sin(2 * Math.PI * VIBRATO_RATE_HZ * sinceOnset / 1e3);
2141
+ return baseHz * 2 ** (cents / 1200);
2142
+ };
2143
+
1980
2144
  // src/lyrics.ts
1981
2145
  var kanaTable = {
1982
2146
  \u3042: ["", "a"],
@@ -2141,8 +2305,8 @@ var vocalVolumeToGain = (v) => {
2141
2305
  var parseLyrics = (mml) => {
2142
2306
  const tracks = /* @__PURE__ */ new Map();
2143
2307
  const segments = splitSegments(mml);
2144
- for (let i = 0; i < segments.length; i++) {
2145
- const m = segments[i].match(LYRIC_LINE);
2308
+ for (let i2 = 0; i2 < segments.length; i2++) {
2309
+ const m = segments[i2].match(LYRIC_LINE);
2146
2310
  if (!m) continue;
2147
2311
  const trackId = Number.parseInt(m[1], 10);
2148
2312
  let rest = m[2].trim();
@@ -2150,8 +2314,12 @@ var parseLyrics = (mml) => {
2150
2314
  let gate = 100;
2151
2315
  let pan = 64;
2152
2316
  let octave = 0;
2317
+ let vibrato = false;
2318
+ let reverb = 0;
2319
+ let gender = 50;
2320
+ let breathiness = 50;
2153
2321
  const modelMatch = rest.match(
2154
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpo]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2322
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2155
2323
  );
2156
2324
  let model = "";
2157
2325
  const metaTokens = [];
@@ -2192,11 +2360,39 @@ var parseLyrics = (mml) => {
2192
2360
  rest = rest.substring(oMatch[0].length).trim();
2193
2361
  continue;
2194
2362
  }
2363
+ const bMatch = rest.match(/^b([01])/i);
2364
+ if (bMatch) {
2365
+ vibrato = bMatch[1] === "1";
2366
+ metaTokens.push(bMatch[0]);
2367
+ rest = rest.substring(bMatch[0].length).trim();
2368
+ continue;
2369
+ }
2370
+ const rMatch = rest.match(/^r(\d+)/i);
2371
+ if (rMatch) {
2372
+ reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2373
+ metaTokens.push(rMatch[0]);
2374
+ rest = rest.substring(rMatch[0].length).trim();
2375
+ continue;
2376
+ }
2377
+ const gMatch = rest.match(/^g(\d+)/i);
2378
+ if (gMatch) {
2379
+ gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2380
+ metaTokens.push(gMatch[0]);
2381
+ rest = rest.substring(gMatch[0].length).trim();
2382
+ continue;
2383
+ }
2384
+ const hMatch = rest.match(/^h(\d+)/i);
2385
+ if (hMatch) {
2386
+ breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2387
+ metaTokens.push(hMatch[0]);
2388
+ rest = rest.substring(hMatch[0].length).trim();
2389
+ continue;
2390
+ }
2195
2391
  break;
2196
2392
  }
2197
2393
  const lyricLines = [rest];
2198
- while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
2199
- lyricLines.push(segments[++i]);
2394
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1])) {
2395
+ lyricLines.push(segments[++i2]);
2200
2396
  }
2201
2397
  const { syllables, lineBreaks } = normalizeLyricLines(lyricLines);
2202
2398
  tracks.set(trackId, {
@@ -2206,6 +2402,10 @@ var parseLyrics = (mml) => {
2206
2402
  gate,
2207
2403
  pan,
2208
2404
  octave,
2405
+ vibrato,
2406
+ reverb,
2407
+ gender,
2408
+ breathiness,
2209
2409
  syllables,
2210
2410
  metaText: metaTokens.join(" "),
2211
2411
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2216,13 +2416,13 @@ var parseLyrics = (mml) => {
2216
2416
  var stripLyrics = (mml) => {
2217
2417
  const segments = splitSegments(mml);
2218
2418
  const kept = [];
2219
- for (let i = 0; i < segments.length; i++) {
2220
- if (LYRIC_LINE.test(segments[i])) {
2221
- while (i + 1 < segments.length && isLyricContinuation(segments[i + 1]))
2222
- i++;
2419
+ for (let i2 = 0; i2 < segments.length; i2++) {
2420
+ if (LYRIC_LINE.test(segments[i2])) {
2421
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1]))
2422
+ i2++;
2223
2423
  continue;
2224
2424
  }
2225
- kept.push(segments[i]);
2425
+ kept.push(segments[i2]);
2226
2426
  }
2227
2427
  return kept.join("\n");
2228
2428
  };
@@ -2291,7 +2491,7 @@ var FORMANTS = {
2291
2491
  N: [250, 1e3]
2292
2492
  };
2293
2493
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2294
- var createKlattVoice = (ctx, destination) => {
2494
+ var createKlattVoice = (ctx, destination, reverbBus) => {
2295
2495
  const active = /* @__PURE__ */ new Set();
2296
2496
  const voice = (syllable, e) => {
2297
2497
  const t0 = ctx.currentTime + e.when;
@@ -2301,14 +2501,21 @@ var createKlattVoice = (ctx, destination) => {
2301
2501
  const attack = 0.02;
2302
2502
  const release = 0.06;
2303
2503
  const sustainEnd = t0 + Math.max(attack + 0.02, e.duration);
2504
+ const dest = e.destination ?? destination;
2304
2505
  let panner = null;
2305
- let out = destination;
2506
+ let out = dest;
2306
2507
  if (typeof ctx.createStereoPanner === "function") {
2307
2508
  panner = ctx.createStereoPanner();
2308
2509
  panner.pan.value = Math.max(-1, Math.min(1, e.pan ?? 0));
2309
- panner.connect(destination);
2510
+ panner.connect(dest);
2310
2511
  out = panner;
2311
2512
  }
2513
+ let sendGain = null;
2514
+ if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2515
+ sendGain = ctx.createGain();
2516
+ sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2517
+ panner.connect(sendGain).connect(reverbBus);
2518
+ }
2312
2519
  const osc = ctx.createOscillator();
2313
2520
  osc.type = "sawtooth";
2314
2521
  osc.frequency.value = midiToFreq(e.pitch);
@@ -2337,7 +2544,7 @@ var createKlattVoice = (ctx, destination) => {
2337
2544
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
2338
2545
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
2339
2546
  const data = buffer.getChannelData(0);
2340
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
2547
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
2341
2548
  const src = ctx.createBufferSource();
2342
2549
  src.buffer = buffer;
2343
2550
  const hp = ctx.createBiquadFilter();
@@ -2364,6 +2571,7 @@ var createKlattVoice = (ctx, destination) => {
2364
2571
  active.delete(osc);
2365
2572
  osc.disconnect();
2366
2573
  panner?.disconnect();
2574
+ sendGain?.disconnect();
2367
2575
  };
2368
2576
  };
2369
2577
  voice.stopAll = () => {
@@ -2382,6 +2590,7 @@ var KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
2382
2590
  var KOE_VOICEBANKS = {
2383
2591
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
2384
2592
  rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
2593
+ rino121: "\u6625\u97F3\u30EA\u30CEver.1.1(226).koe",
2385
2594
  roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
2386
2595
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
2387
2596
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
@@ -2390,11 +2599,13 @@ var KOE_VOICEBANKS = {
2390
2599
  rei: "\u8DB3\u7ACB\u30EC\u30A4ver3.5.0.koe",
2391
2600
  mgroid: "MGRoid_\u539F\u97F3\u8A2D\u5B9A\u6E08\u307F.koe",
2392
2601
  motroid: "MOTRoid\u5B8C\u5168\u7248V2.koe",
2393
- nynroid: "NYNRoidver1.4.koe"
2602
+ nynroid: "NYNRoidver1.4.koe",
2603
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3\u97F3\u6E90.koe"
2394
2604
  };
2395
2605
  var KOE_VOICEBANK_LABELS = {
2396
2606
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
2397
2607
  rino: "\u6625\u97F3\u30EA\u30CE",
2608
+ rino121: "\u6625\u97F3\u30EA\u30CEv1.2.1",
2398
2609
  roze: "\u675F\u97F3\u30ED\u30BC",
2399
2610
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
2400
2611
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
@@ -2403,12 +2614,14 @@ var KOE_VOICEBANK_LABELS = {
2403
2614
  rei: "\u8DB3\u7ACB\u30EC\u30A4",
2404
2615
  mgroid: "MGRoid",
2405
2616
  motroid: "MOTRoid",
2406
- nynroid: "NYNRoid"
2617
+ nynroid: "NYNRoid",
2618
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3"
2407
2619
  };
2408
2620
  var VOICE_IMAGE_KEY = {
2409
2621
  klatt: "puyuyu",
2410
2622
  tsukuyomi: "tsukuyomi",
2411
2623
  rino: "rino",
2624
+ rino121: "rino",
2412
2625
  roze: "roze",
2413
2626
  ruko_male: "ruko",
2414
2627
  ruko_female: "ruko",
@@ -2417,11 +2630,13 @@ var VOICE_IMAGE_KEY = {
2417
2630
  rei: "rei",
2418
2631
  mgroid: "MGRoid",
2419
2632
  motroid: "MOTRoid",
2420
- nynroid: "NYNRoid"
2633
+ nynroid: "NYNRoid",
2634
+ uc: "uc"
2421
2635
  };
2422
2636
  var KOE_VOICEBANK_TERMS = {
2423
2637
  tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
2424
2638
  rino: "https://hatenakun1.github.io/halunelino/",
2639
+ rino121: "https://harunerino.vercel.app/",
2425
2640
  roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
2426
2641
  ruko_male: "https://long-sleeper.net/index.php?id=22",
2427
2642
  ruko_female: "https://long-sleeper.net/index.php?id=22",
@@ -2430,7 +2645,8 @@ var KOE_VOICEBANK_TERMS = {
2430
2645
  rei: "https://mechanicalgirl.jp/guidelines/",
2431
2646
  mgroid: "https://x.com/nisusansu/status/1048825378188353536",
2432
2647
  motroid: "https://www.nicovideo.jp/watch/sm40031282",
2433
- nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs"
2648
+ nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs",
2649
+ uc: "https://chi9nekiriko.wixsite.com/home/%E5%88%A9%E7%94%A8%E8%A6%8F%E7%B4%84"
2434
2650
  };
2435
2651
  var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
2436
2652
  var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
@@ -2519,7 +2735,7 @@ var createLocalBackend = async (options) => {
2519
2735
  }
2520
2736
  return p;
2521
2737
  };
2522
- const renderAlias = async (alias, pitch, durationMs) => {
2738
+ const renderAlias = async (alias, pitch, durationMs, vibrato, expr) => {
2523
2739
  const pcm = await getPcm(alias);
2524
2740
  if (!pcm || pcm.length === 0) return null;
2525
2741
  const entry = bank.manifest.phonemes[alias];
@@ -2528,9 +2744,11 @@ var createLocalBackend = async (options) => {
2528
2744
  if (worldline) {
2529
2745
  const audio = worldline.renderNote({
2530
2746
  pcm,
2531
- pitch: targetHz,
2747
+ pitch: vibrato ? vibratoPitchCurve(targetHz, lead.preMs) : targetHz,
2532
2748
  durationMs,
2533
- ...lead
2749
+ ...lead,
2750
+ gender: expr?.gender,
2751
+ breathiness: expr?.breathiness
2534
2752
  });
2535
2753
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
2536
2754
  }
@@ -2595,7 +2813,7 @@ var createWorkerBackend = async (workerUrl, options) => {
2595
2813
  });
2596
2814
  onReady = null;
2597
2815
  onFail = null;
2598
- const renderAlias = (alias, pitch, durationMs) => new Promise((resolve) => {
2816
+ const renderAlias = (alias, pitch, durationMs, vibrato, expr) => new Promise((resolve) => {
2599
2817
  const id = ++reqId;
2600
2818
  pending.set(
2601
2819
  id,
@@ -2608,7 +2826,10 @@ var createWorkerBackend = async (workerUrl, options) => {
2608
2826
  id,
2609
2827
  alias,
2610
2828
  pitch,
2611
- durationMs
2829
+ durationMs,
2830
+ vibrato,
2831
+ gender: expr?.gender,
2832
+ breathiness: expr?.breathiness
2612
2833
  });
2613
2834
  });
2614
2835
  return {
@@ -2637,15 +2858,21 @@ var createKoeVoice = async (ctx, destination, options) => {
2637
2858
  const inflight = /* @__PURE__ */ new Map();
2638
2859
  const active = /* @__PURE__ */ new Set();
2639
2860
  let prevVowel = "";
2640
- const keyOf = (alias, pitch, durationMs) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}`;
2641
- const renderInto = (alias, pitch, durationMs) => {
2642
- const key = keyOf(alias, pitch, durationMs);
2861
+ const keyOf = (alias, pitch, durationMs, vibrato, expr) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}${expr?.gender !== void 0 ? `|g${Math.round(expr.gender * 100)}` : ""}${expr?.breathiness !== void 0 ? `|h${Math.round(expr.breathiness * 100)}` : ""}`;
2862
+ const renderInto = (alias, pitch, durationMs, vibrato, expr) => {
2863
+ const key = keyOf(alias, pitch, durationMs, vibrato, expr);
2643
2864
  const existing = renderCache.get(key);
2644
2865
  if (existing !== void 0) return Promise.resolve(existing);
2645
2866
  const flying = inflight.get(key);
2646
2867
  if (flying) return flying;
2647
2868
  const p = (async () => {
2648
- const out = await backend.renderAlias(alias, pitch, durationMs);
2869
+ const out = await backend.renderAlias(
2870
+ alias,
2871
+ pitch,
2872
+ durationMs,
2873
+ vibrato,
2874
+ expr
2875
+ );
2649
2876
  let rendered = null;
2650
2877
  if (out) {
2651
2878
  const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
@@ -2660,15 +2887,22 @@ var createKoeVoice = async (ctx, destination, options) => {
2660
2887
  return p;
2661
2888
  };
2662
2889
  const LEADCAP_S = 0.09;
2663
- const schedule = (r, t0, peak, pan) => {
2664
- let out = destination;
2890
+ const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2891
+ const dest = destOverride ?? destination;
2892
+ let out = dest;
2665
2893
  let panner = null;
2666
2894
  if (typeof ctx.createStereoPanner === "function") {
2667
2895
  panner = ctx.createStereoPanner();
2668
2896
  panner.pan.value = Math.max(-1, Math.min(1, pan));
2669
- panner.connect(destination);
2897
+ panner.connect(dest);
2670
2898
  out = panner;
2671
2899
  }
2900
+ let sendGain = null;
2901
+ if (options.reverbBus && reverbSend > 0 && panner) {
2902
+ sendGain = ctx.createGain();
2903
+ sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2904
+ panner.connect(sendGain).connect(options.reverbBus);
2905
+ }
2672
2906
  const src = ctx.createBufferSource();
2673
2907
  src.buffer = r.audio;
2674
2908
  src.playbackRate.value = r.rate;
@@ -2694,6 +2928,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2694
2928
  src.disconnect();
2695
2929
  env.disconnect();
2696
2930
  panner?.disconnect();
2931
+ sendGain?.disconnect();
2697
2932
  };
2698
2933
  };
2699
2934
  const model = (syllable, e) => {
@@ -2712,10 +2947,10 @@ var createKoeVoice = async (ctx, destination, options) => {
2712
2947
  const pan = e.pan ?? 0;
2713
2948
  const durationMs = Math.max(60, e.duration * 1e3);
2714
2949
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2715
- if (r) schedule(r, t0, peak, pan);
2950
+ if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2716
2951
  });
2717
2952
  };
2718
- model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs) => {
2953
+ model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
2719
2954
  if (syllable.consonant === "Q" || syllable.vowel === "") return null;
2720
2955
  const alias = resolveKoeAlias(
2721
2956
  backend.hasAlias,
@@ -2726,12 +2961,13 @@ var createKoeVoice = async (ctx, destination, options) => {
2726
2961
  );
2727
2962
  if (!alias) return null;
2728
2963
  const dMs = Math.max(60, durationMs);
2729
- const r = await renderInto(alias, pitch, dMs);
2730
- return r ? keyOf(alias, pitch, dMs) : null;
2964
+ const vib = !!vibrato && dMs / 1e3 >= VIBRATO_MIN_SEC;
2965
+ const r = await renderInto(alias, pitch, dMs, vib, expr);
2966
+ return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2731
2967
  };
2732
- model.scheduleCached = (key, t0, peak, pan) => {
2968
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2733
2969
  const r = renderCache.get(key);
2734
- if (r) schedule(r, t0, peak, pan);
2970
+ if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2735
2971
  };
2736
2972
  model.stopAll = () => {
2737
2973
  for (const src of active) {
@@ -2762,7 +2998,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2762
2998
  const masterGain = ctx.createGain();
2763
2999
  masterGain.connect(destination);
2764
3000
  const loaded = /* @__PURE__ */ new Map([
2765
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain)]
3001
+ [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
2766
3002
  ]);
2767
3003
  const loading = /* @__PURE__ */ new Map();
2768
3004
  const load2 = (model) => {
@@ -2782,7 +3018,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2782
3018
  koe,
2783
3019
  worldlineScriptUrl: options.worldlineScriptUrl,
2784
3020
  lightweight: options.lightweight,
2785
- voiceWorkerUrl: options.voiceWorkerUrl
3021
+ voiceWorkerUrl: options.voiceWorkerUrl,
3022
+ reverbBus: options.reverbBus
2786
3023
  })
2787
3024
  ))().then((v) => {
2788
3025
  loaded.set(m, v);
@@ -2824,10 +3061,14 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2824
3061
  const m = loaded.get(track.model.toLowerCase());
2825
3062
  if (!m?.renderToCache) continue;
2826
3063
  let n = 0;
3064
+ const expr = {
3065
+ gender: track.gender,
3066
+ breathiness: track.breathiness
3067
+ };
2827
3068
  forEachSungNote(track, (note, prevVowel) => {
2828
3069
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2829
3070
  n++;
2830
- tasks.push({ model: m, note, prevVowel });
3071
+ tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
2831
3072
  });
2832
3073
  }
2833
3074
  const total = tasks.length;
@@ -2842,7 +3083,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2842
3083
  task.note.syllable,
2843
3084
  task.prevVowel,
2844
3085
  task.note.pitch,
2845
- task.note.durationSec * 1e3
3086
+ task.note.durationSec * 1e3,
3087
+ task.vibrato,
3088
+ task.expr
2846
3089
  ) ?? Promise.resolve(null));
2847
3090
  done++;
2848
3091
  onProgress?.(done, total);
@@ -2887,13 +3130,22 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2887
3130
  note.syllable,
2888
3131
  prevVowel,
2889
3132
  note.pitch,
2890
- note.durationSec * 1e3
3133
+ note.durationSec * 1e3,
3134
+ track.vibrato,
3135
+ { gender: track.gender, breathiness: track.breathiness }
2891
3136
  );
2892
3137
  if (session !== streamSession) return;
2893
3138
  if (key) {
2894
3139
  const delay = ctx.currentTime - t0;
2895
3140
  if (delay < 0.05) {
2896
- scheduleCached(key, t0, peak, track.pan);
3141
+ scheduleCached(
3142
+ key,
3143
+ t0,
3144
+ peak,
3145
+ track.pan,
3146
+ track.reverbSend,
3147
+ options.getTrackDestination?.(track.id ?? "")
3148
+ );
2897
3149
  opts?.onScheduled?.(track, note, t0);
2898
3150
  } else {
2899
3151
  console.warn(
@@ -2906,13 +3158,15 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2906
3158
  } else {
2907
3159
  const when = t0 - ctx.currentTime;
2908
3160
  model(note.syllable, {
2909
- trackId: "",
3161
+ trackId: track.id ?? "",
2910
3162
  pitch: note.pitch,
2911
3163
  velocity: 100,
2912
3164
  volume: peak,
2913
3165
  when,
2914
3166
  duration: note.durationSec,
2915
- pan: track.pan
3167
+ pan: track.pan,
3168
+ reverbSend: track.reverbSend,
3169
+ destination: options.getTrackDestination?.(track.id ?? "")
2916
3170
  });
2917
3171
  opts?.onScheduled?.(track, note, t0);
2918
3172
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -2971,8 +3225,10 @@ var PITCH_MAP = {
2971
3225
  b: 11
2972
3226
  };
2973
3227
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2974
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|mode)=([\w-]+)/gi;
3228
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
2975
3229
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3230
+ var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3231
+ var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
2976
3232
  var parseMmlMeta = (mml) => {
2977
3233
  const meta = {};
2978
3234
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -2986,6 +3242,9 @@ var parseMmlMeta = (mml) => {
2986
3242
  } else if (key === "drumvolume") {
2987
3243
  const dv = Number.parseInt(m[2], 10);
2988
3244
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3245
+ } else if (key === "reverb") {
3246
+ const rv = Number.parseInt(m[2], 10);
3247
+ if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
2989
3248
  } else if (key === "mode") {
2990
3249
  if (m[2] === "simple" || m[2] === "advanced") {
2991
3250
  meta.mode = m[2];
@@ -3000,9 +3259,25 @@ var parseMmlMeta = (mml) => {
3000
3259
  meta.trackInstruments[idx] = name;
3001
3260
  }
3002
3261
  }
3262
+ for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3263
+ const idx = Number.parseInt(m[1], 10);
3264
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3265
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3266
+ meta.trackCompression ??= {};
3267
+ meta.trackCompression[idx] = val;
3268
+ }
3269
+ }
3270
+ for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3271
+ const idx = Number.parseInt(m[1], 10);
3272
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3273
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3274
+ meta.trackWidth ??= {};
3275
+ meta.trackWidth[idx] = val;
3276
+ }
3277
+ }
3003
3278
  return meta;
3004
3279
  };
3005
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "");
3280
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3006
3281
  var formatMmlMeta = (meta, space = "") => {
3007
3282
  const parts = [];
3008
3283
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3011,12 +3286,24 @@ var formatMmlMeta = (meta, space = "") => {
3011
3286
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
3012
3287
  if (meta.drumVolume !== void 0)
3013
3288
  parts.push(`#drumvolume=${meta.drumVolume}`);
3289
+ if (meta.reverb !== void 0 && meta.reverb !== 0)
3290
+ parts.push(`#reverb=${meta.reverb}`);
3014
3291
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3015
3292
  if (meta.trackInstruments) {
3016
3293
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
3017
3294
  if (name) parts.push(`#t${idx}inst=${name}`);
3018
3295
  }
3019
3296
  }
3297
+ if (meta.trackCompression) {
3298
+ for (const [idx, val] of Object.entries(meta.trackCompression)) {
3299
+ if (val !== 0) parts.push(`#t${idx}comp=${val}`);
3300
+ }
3301
+ }
3302
+ if (meta.trackWidth) {
3303
+ for (const [idx, val] of Object.entries(meta.trackWidth)) {
3304
+ if (val !== 100) parts.push(`#t${idx}width=${val}`);
3305
+ }
3306
+ }
3020
3307
  return parts.join(space);
3021
3308
  };
3022
3309
  var parseMML = (mml, options = {}) => {
@@ -5773,7 +6060,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
5773
6060
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
5774
6061
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
5775
6062
  const data = buffer.getChannelData(0);
5776
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
6063
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
5777
6064
  const src = ctx.createBufferSource();
5778
6065
  src.buffer = buffer;
5779
6066
  const filter = ctx.createBiquadFilter();
@@ -6758,6 +7045,23 @@ var DAW_CSS = `
6758
7045
  @keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
6759
7046
  .dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
6760
7047
 
7048
+ /* \u2500\u2500\u2500 \u97F3\u5272\u308C\u691C\u77E5\u30D0\u30C3\u30B8 \u2500\u2500\u2500 \u30AF\u30EA\u30C3\u30D7\u767A\u751F\u4E2D\u3060\u3051\u8868\u793A\u3055\u308C\u308B\u8B66\u544A\u30DC\u30BF\u30F3\u3002\u30AF\u30EA\u30C3\u30AF\u3067\u6D88\u305B\u308B\u3002 */
7049
+ .dtm-clip-badge {
7050
+ flex: 0 0 auto;
7051
+ min-height: 24px;
7052
+ padding: 0 8px;
7053
+ border: 2px solid var(--c-black);
7054
+ background: var(--dtm-danger);
7055
+ color: var(--c-white);
7056
+ font-family: var(--dtm-font);
7057
+ font-size: 10px;
7058
+ letter-spacing: .1em;
7059
+ cursor: pointer;
7060
+ box-shadow: 0 0 0 2px var(--dtm-danger), 2px 2px 0 var(--c-black);
7061
+ animation: dtm-blink .5s steps(1) infinite;
7062
+ }
7063
+ .dtm-clip-badge:active { transform: translate(2px,2px); box-shadow: none; }
7064
+
6761
7065
  /* \u2500\u2500\u2500 \u30A4\u30F3\u30D5\u30A9\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
6762
7066
  .dtm-infobtn {
6763
7067
  display: inline-flex;
@@ -7033,6 +7337,37 @@ var DAW_CSS = `
7033
7337
  object-fit: cover;
7034
7338
  image-rendering: pixelated;
7035
7339
  }
7340
+ /* \u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u8A73\u7D30\u8A2D\u5B9A\u300D\u2014 \u5E38\u7528\u3057\u306A\u3044\u30D1\u30E9\u30E1\u30FC\u30BF\uFF08\u30AA\u30AF\u30BF\u30FC\u30D6/\u5B9A\u4F4D/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/
7341
+ \u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30D3\u30D6\u30E9\u30FC\u30C8\uFF09\u3092\u7573\u3093\u3067\u304A\u304F\u8EFD\u91CF\u306A details\u3002dtm-panel \u307B\u3069
7342
+ \u4EF0\u3005\u3057\u304F\u305B\u305A\u3001\u63A7\u3048\u3081\u306A\u4ED5\u5207\u308A\u7DDA\uFF0B\u5C0F\u3055\u3044\u25B6\u30DE\u30FC\u30AB\u30FC\u3060\u3051\u4ED8\u3051\u308B\u3002 */
7343
+ .dtm-advanced {
7344
+ border-top: 1px dashed var(--dtm-border2);
7345
+ padding-top: 6px;
7346
+ display: flex;
7347
+ flex-direction: column;
7348
+ gap: 6px;
7349
+ }
7350
+ .dtm-advanced > summary {
7351
+ list-style: none;
7352
+ cursor: pointer;
7353
+ font-size: 11px;
7354
+ letter-spacing: .08em;
7355
+ color: var(--dtm-text);
7356
+ display: flex;
7357
+ align-items: center;
7358
+ gap: 6px;
7359
+ min-height: 28px;
7360
+ padding: 0 8px;
7361
+ border: 2px solid var(--dtm-border2);
7362
+ background: var(--dtm-deep);
7363
+ box-shadow: 2px 2px 0 var(--c-black);
7364
+ width: fit-content;
7365
+ }
7366
+ .dtm-advanced > summary:active { transform: translate(2px,2px); box-shadow: none; }
7367
+ .dtm-advanced > summary::-webkit-details-marker { display: none; }
7368
+ .dtm-advanced > summary::before { content: "\u25B6"; font-size: 9px; color: var(--dtm-accent); }
7369
+ .dtm-advanced[open] > summary::before { content: "\u25BC"; }
7370
+ .dtm-advanced[open] > summary { border-color: var(--dtm-primary); color: var(--dtm-primary); }
7036
7371
 
7037
7372
  /* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
7038
7373
  @media (min-width: 768px) {
@@ -7694,6 +8029,9 @@ var teto_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAA
7694
8029
  // assets/tsukuyomi.png
7695
8030
  var tsukuyomi_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB3UlEQVR42tWXPUsDQRCGnwuijYIoFqKmEDSCxIgIgqnsogbsrC3ERiwN4g8Ikk6sFH+BnYofvcTKxoigBCzEYGGjWIiCOYvNBHe5TdQUt051czc3e++zO7tzHnUst7vn04BlFue9Ws8jhGxN9ZTPzU43NkIlj41E6AQ8m/JYfByAoWgXAPtHJzRCRN43SbizBkzlv1X2VzLuVoHYzf0TAMnys+aLCTGThMTJGnKfQOpyWF1cvgHQsh4FYPDzRd3vmQHgoPShJbi9uqhJyPk1UK3Jwsq1/115rLst8AX/4UFP0NsbGHf7+Kr579l7zR/ZGvbcrAJR7vvqEOwcTdZMYIuTPCYJUS7Ewydgzn2Bay2gdWzqR4nMuMPlhcC5l/HcWQOnCaU4lVU3xI9tnwFQnOgDoDmdDs5QKmlxYhdGmBA2ibhTBfKFHCkCm00dAKymJn+USAidHx8DkI+0q7PBUC7j5BJ7jlSB7RyXziVT8fM7Oz7AeLmsz3VEaWi7UvtAcmvJC+qmq4T/XT8gdTsw2692to07AF7jeU35wFq/7COqzhdVnVOnu3aHgNm1VpVXlImJX9zQffO5kDg1dlb3CNj6dZsVK2vAVP7Xf8bwCfxWuc23Eanmt/xlh07gC6oBviFE8rZHAAAAAElFTkSuQmCC";
7696
8031
 
8032
+ // assets/uc.png
8033
+ var uc_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB/ElEQVR42s2WPUhbURTHfxHniEEIVdvFmDaTiIJjBpcuGQrFr2JDaoeCkxRdAg6WVPAD20FwqEoUQUs7hdAuDaSbYNpGF6VxiaLooCCdREyHlxs4z/deEhG8Zwn/m8d57/+759x7XEAB53BxuyhU8lAN9xyusLfe8UvTrT4ALhr3q0ocSjsDiJ+c60Gg9q6dq1jYbATgTdeR3jVQW63z5t6nABxufBd6syMLQFemDYDA0jEA829nAPg8O6ApAVWNYW99RU7tQjkvkcDQw9kpAILa14B579203iqhmURqv1gDDIj+Vyesfl1Qbq/NNRFJ7gDwsblBPthh/AxuvQDgzORc33PgImv0r7vtgeV6icTkFQDvCBgL26ZEr04r+oD7J6D6P21DoqR3s0LnIntC+5YfC900ViT5xOiKkPeP6oKC3l1A6p+QblqE/tV/aZnoBpG09QsVcUVCHwLBvznxRyJotGsu+FCsj0zkLRPNjT8qOj9wrB39ZsJEIiGGt/Bq2HqPy5CwI2CeDZ9/WjHWQyFNCJjn9+TUN3GGr3auAXAdME6+TCYjEtTlv8rqf/Ze6C+vXwrnJTLaEfD0eBxvRXUL+vN+x4TRaFTon2sfAJhe/6H5bThU0w7A4vXvqmZBO+fmGO3rViQ0vwvsSMQH4wDEYrGKnKu9134e+A/zObZWSEHtdQAAAABJRU5ErkJggg==";
8034
+
7697
8035
  // src/voice-images.ts
7698
8036
  var VOICE_IMAGES = {
7699
8037
  puyuyu: puyuyu_default,
@@ -7706,7 +8044,8 @@ var VOICE_IMAGES = {
7706
8044
  rei: rei_default,
7707
8045
  MGRoid: MGRoid_default,
7708
8046
  MOTRoid: MOTRoid_default,
7709
- NYNRoid: NYNRoid_default
8047
+ NYNRoid: NYNRoid_default,
8048
+ uc: uc_default
7710
8049
  };
7711
8050
  var FALLBACK_VOCAL_ICON = Chip_default;
7712
8051
 
@@ -7768,8 +8107,8 @@ var copyToClipboard = async (doc, text) => {
7768
8107
  };
7769
8108
  var toBase64Url = (bytes) => {
7770
8109
  let bin = "";
7771
- for (let i = 0; i < bytes.length; i++) {
7772
- bin += String.fromCharCode(bytes[i]);
8110
+ for (let i2 = 0; i2 < bytes.length; i2++) {
8111
+ bin += String.fromCharCode(bytes[i2]);
7773
8112
  }
7774
8113
  return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
7775
8114
  };
@@ -7782,8 +8121,8 @@ var SHIFT_KATAKANA = 255;
7782
8121
  var VALUE_PROLONGED = 223;
7783
8122
  var customEncode = (str) => {
7784
8123
  const bytes = [];
7785
- for (let i = 0; i < str.length; i++) {
7786
- const code = str.charCodeAt(i);
8124
+ for (let i2 = 0; i2 < str.length; i2++) {
8125
+ const code = str.charCodeAt(i2);
7787
8126
  if (code === 32) {
7788
8127
  continue;
7789
8128
  }
@@ -7825,37 +8164,37 @@ var fromBase64Url = (s) => {
7825
8164
  }
7826
8165
  const bin = atob(normalized);
7827
8166
  const bytes = new Uint8Array(bin.length);
7828
- for (let i = 0; i < bin.length; i++) {
7829
- bytes[i] = bin.charCodeAt(i);
8167
+ for (let i2 = 0; i2 < bin.length; i2++) {
8168
+ bytes[i2] = bin.charCodeAt(i2);
7830
8169
  }
7831
8170
  return bytes;
7832
8171
  };
7833
8172
  var customDecode = (bytes) => {
7834
8173
  let str = "";
7835
- let i = 0;
7836
- while (i < bytes.length) {
7837
- const byte = bytes[i];
8174
+ let i2 = 0;
8175
+ while (i2 < bytes.length) {
8176
+ const byte = bytes[i2];
7838
8177
  if (byte <= 127) {
7839
8178
  str += String.fromCharCode(byte);
7840
- i++;
8179
+ i2++;
7841
8180
  } else if (byte === VALUE_PROLONGED) {
7842
8181
  str += String.fromCharCode(PROLONGED_MARK);
7843
- i++;
8182
+ i2++;
7844
8183
  } else if (byte >= 128 && byte <= 222) {
7845
8184
  str += String.fromCharCode(HIRAGANA_START + (byte - 128));
7846
- i++;
8185
+ i2++;
7847
8186
  } else if (byte === SHIFT_KATAKANA) {
7848
- if (i + 1 < bytes.length) {
7849
- const nextByte = bytes[i + 1];
8187
+ if (i2 + 1 < bytes.length) {
8188
+ const nextByte = bytes[i2 + 1];
7850
8189
  if (nextByte >= 128 && nextByte <= 222) {
7851
8190
  str += String.fromCharCode(HIRAGANA_START + 96 + (nextByte - 128));
7852
8191
  }
7853
- i += 2;
8192
+ i2 += 2;
7854
8193
  } else {
7855
- i++;
8194
+ i2++;
7856
8195
  }
7857
8196
  } else {
7858
- i++;
8197
+ i2++;
7859
8198
  }
7860
8199
  }
7861
8200
  return str;
@@ -8420,7 +8759,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8420
8759
  const beatRow = doc.createElement("div");
8421
8760
  beatRow.className = "dtm-player-beat-row";
8422
8761
  const beatDots = [];
8423
- for (let i = 0; i < 4; i++) {
8762
+ for (let i2 = 0; i2 < 4; i2++) {
8424
8763
  const d = doc.createElement("span");
8425
8764
  d.className = "dtm-player-beat-dot";
8426
8765
  beatRow.appendChild(d);
@@ -8524,9 +8863,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8524
8863
  lane.appendChild(metaEl);
8525
8864
  }
8526
8865
  const count = Math.min(notes.length, lyricTrack.syllables.length);
8527
- for (let i = 0; i < count; i++) {
8528
- const note = notes[i];
8529
- if (breaks.has(i)) {
8866
+ for (let i2 = 0; i2 < count; i2++) {
8867
+ const note = notes[i2];
8868
+ if (breaks.has(i2)) {
8530
8869
  const br = doc.createElement("span");
8531
8870
  br.className = "dtm-tk dtm-tk--break";
8532
8871
  br.textContent = "\\n";
@@ -8534,7 +8873,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8534
8873
  }
8535
8874
  const span = doc.createElement("span");
8536
8875
  span.className = "dtm-tk dtm-tk--lyric";
8537
- span.textContent = lyricTrack.syllables[i].kana;
8876
+ span.textContent = lyricTrack.syllables[i2].kana;
8538
8877
  lane.appendChild(span);
8539
8878
  laneTokens.push({
8540
8879
  el: span,
@@ -8687,8 +9026,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8687
9026
  const intStep = Math.floor(step);
8688
9027
  playStep = intStep;
8689
9028
  const beatIndex = Math.floor(step / STEPS_PER_BEAT2) % 4;
8690
- for (let i = 0; i < 4; i++)
8691
- beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
9029
+ for (let i2 = 0; i2 < 4; i2++)
9030
+ beatDots[i2].classList.toggle("dtm-player-beat-dot--on", i2 === beatIndex);
8692
9031
  barEl.textContent = String(Math.floor(step / STEPS_PER_BAR2) + 1);
8693
9032
  if (!isSeeking) {
8694
9033
  seekInput.value = String(Math.min(maxStep, Math.max(0, intStep)));
@@ -8783,11 +9122,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8783
9122
  const semis = (lt.octave ?? 0) * 12;
8784
9123
  const count = Math.min(sorted.length, lt.syllables.length);
8785
9124
  const notes = [];
8786
- for (let i = 0; i < count; i++) {
8787
- const n = sorted[i];
9125
+ for (let i2 = 0; i2 < count; i2++) {
9126
+ const n = sorted[i2];
8788
9127
  if (n.startStep < fromStep) continue;
8789
9128
  notes.push({
8790
- syllable: lt.syllables[i],
9129
+ syllable: lt.syllables[i2],
8791
9130
  pitch: n.pitch + semis,
8792
9131
  startSec: (n.startStep - fromStep) * secondsPerStep,
8793
9132
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -8798,6 +9137,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8798
9137
  model: lt.model,
8799
9138
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
8800
9139
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9140
+ vibrato: lt.vibrato,
9141
+ reverbSend: (lt.reverb ?? 0) / 100,
9142
+ gender: (lt.gender ?? 50) / 100,
9143
+ breathiness: (lt.breathiness ?? 50) / 100,
8801
9144
  notes
8802
9145
  };
8803
9146
  });
@@ -9057,13 +9400,13 @@ var findZone = (ctx, fontName, zones, pitchs = []) => {
9057
9400
  for (const zone of zones) {
9058
9401
  const low = zone.keyRangeLow | 0, high = zone.keyRangeHigh | 0;
9059
9402
  if (low > high) continue;
9060
- for (let i = low; i <= high; i++) pitchs.push(i);
9403
+ for (let i2 = low; i2 <= high; i2++) pitchs.push(i2);
9061
9404
  }
9062
9405
  const set = new Set(pitchs);
9063
9406
  const map = new Map(pitchs.map((v) => [v, zones[0]]));
9064
- for (let i = zones.length - 1; i >= 0; i--)
9407
+ for (let i2 = zones.length - 1; i2 >= 0; i2--)
9065
9408
  for (const v of set) {
9066
- const zone = zones[i];
9409
+ const zone = zones[i2];
9067
9410
  if (v < zone.keyRangeLow || v > zone.keyRangeHigh) continue;
9068
9411
  set.delete(v);
9069
9412
  map.set(v, { ...zone });
@@ -9093,13 +9436,13 @@ var adjustZone = async (ctx, fontName, zone) => {
9093
9436
  const decoded = atob(zone.sample);
9094
9437
  zone.buffer = ctx.createBuffer(1, decoded.length / 2, zone.sampleRate);
9095
9438
  const a = zone.buffer.getChannelData(0);
9096
- for (let i = 0; i < decoded.length / 2; i++) {
9097
- let b1 = decoded.charCodeAt(i * 2), b2 = decoded.charCodeAt(i * 2 + 1);
9439
+ for (let i2 = 0; i2 < decoded.length / 2; i2++) {
9440
+ let b1 = decoded.charCodeAt(i2 * 2), b2 = decoded.charCodeAt(i2 * 2 + 1);
9098
9441
  if (b1 < 0) b1 = 256 + b1;
9099
9442
  if (b2 < 0) b2 = 256 + b2;
9100
9443
  let n = b2 * 256 + b1;
9101
9444
  if (n >= 65536 / 2) n = n - 65536;
9102
- a[i] = n / 65536;
9445
+ a[i2] = n / 65536;
9103
9446
  }
9104
9447
  } else if (zone.file) {
9105
9448
  const bytes = Uint8Array.from(atob(zone.file), (c) => c.charCodeAt(0));
@@ -9139,10 +9482,10 @@ var adjustZone = async (ctx, fontName, zone) => {
9139
9482
  let loopPeak = 0;
9140
9483
  if (oldBuf.numberOfChannels > 0) {
9141
9484
  const ch0 = oldBuf.getChannelData(0);
9142
- for (let i = 0; i < ch0.length; i++) {
9143
- const abs = Math.abs(ch0[i]);
9485
+ for (let i2 = 0; i2 < ch0.length; i2++) {
9486
+ const abs = Math.abs(ch0[i2]);
9144
9487
  if (abs > totalPeak) totalPeak = abs;
9145
- if (i >= loopStartFrame && i < loopEndFrame) {
9488
+ if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9146
9489
  if (abs > loopPeak) loopPeak = abs;
9147
9490
  }
9148
9491
  }
@@ -9161,16 +9504,16 @@ var adjustZone = async (ctx, fontName, zone) => {
9161
9504
  for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9162
9505
  const oldData = oldBuf.getChannelData(ch);
9163
9506
  const newData = newBuf.getChannelData(ch);
9164
- for (let i = 0; i < attackLength; i++) {
9165
- const ratio = attackLength > 1 ? i / (attackLength - 1) : 0;
9507
+ for (let i2 = 0; i2 < attackLength; i2++) {
9508
+ const ratio = attackLength > 1 ? i2 / (attackLength - 1) : 0;
9166
9509
  const m = 1 + (gainMultiplier - 1) * ratio;
9167
- newData[i] = oldData[i] * m;
9510
+ newData[i2] = oldData[i2] * m;
9168
9511
  }
9169
9512
  let offset = attackLength;
9170
9513
  const loopData = oldData.subarray(loopStartFrame, loopEndFrame);
9171
9514
  const normalizedLoopData = new Float32Array(loopLengthFrame);
9172
- for (let i = 0; i < loopLengthFrame; i++) {
9173
- normalizedLoopData[i] = loopData[i] * gainMultiplier;
9515
+ for (let i2 = 0; i2 < loopLengthFrame; i2++) {
9516
+ normalizedLoopData[i2] = loopData[i2] * gainMultiplier;
9174
9517
  }
9175
9518
  for (let r = 0; r < repeatCount; r++) {
9176
9519
  newData.set(normalizedLoopData, offset);
@@ -9180,8 +9523,8 @@ var adjustZone = async (ctx, fontName, zone) => {
9180
9523
  const releaseData = oldData.subarray(loopEndFrame);
9181
9524
  if (gainMultiplier !== 1) {
9182
9525
  const normalizedRelease = new Float32Array(releaseLength);
9183
- for (let i = 0; i < releaseLength; i++) {
9184
- normalizedRelease[i] = releaseData[i] * gainMultiplier;
9526
+ for (let i2 = 0; i2 < releaseLength; i2++) {
9527
+ normalizedRelease[i2] = releaseData[i2] * gainMultiplier;
9185
9528
  }
9186
9529
  newData.set(normalizedRelease, offset);
9187
9530
  } else {
@@ -9377,10 +9720,10 @@ var buildDisplayLines = (chords, eventsCount) => {
9377
9720
  if (segments.length === 0) continue;
9378
9721
  const hasMultiSeg = segments.length > 1;
9379
9722
  const parts = [];
9380
- for (let i = 0; i < segments.length; i++) {
9381
- if (hasMultiSeg && i > 0)
9723
+ for (let i2 = 0; i2 < segments.length; i2++) {
9724
+ if (hasMultiSeg && i2 > 0)
9382
9725
  parts.push({ text: "|", isChord: false, eventIdx: -1 });
9383
- const bar = segments[i].trim();
9726
+ const bar = segments[i2].trim();
9384
9727
  if (!bar) continue;
9385
9728
  const splitAt = [];
9386
9729
  for (let j = 0; j < bar.length; j++) {
@@ -10499,6 +10842,7 @@ var buildUI = (target, options) => {
10499
10842
  <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
10500
10843
  <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
10501
10844
  <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
10845
+ <button class="dtm-clip-badge dtm-hidden" data-dtm="clip-badge" title="\u97F3\u5272\u308C\u691C\u77E5\uFF08\u30AF\u30EA\u30C3\u30AF\u3067\u6D88\u3059\uFF09">CLIP</button>
10502
10846
  <span class="dtm-grow"></span>
10503
10847
  <span class="dtm-label">BPM</span>
10504
10848
  <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
@@ -10536,13 +10880,8 @@ var buildUI = (target, options) => {
10536
10880
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
10537
10881
 
10538
10882
  <details class="dtm-panel" open>
10539
- <summary>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10883
+ <summary>\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10540
10884
  <div class="dtm-panel-body">
10541
- <div class="dtm-row">
10542
- <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10543
- <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10544
- <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10545
- </div>
10546
10885
  <div class="dtm-track-body" data-dtm="track-body"></div>
10547
10886
  </div>
10548
10887
  </details>
@@ -10575,6 +10914,29 @@ var buildUI = (target, options) => {
10575
10914
  </div>
10576
10915
  </details>
10577
10916
 
10917
+ <details class="dtm-panel" open>
10918
+ <summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10919
+ <div class="dtm-panel-body">
10920
+ <div data-dtm="preset-select-slot"></div>
10921
+ <div class="dtm-row">
10922
+ <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10923
+ <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10924
+ <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10925
+ </div>
10926
+ <div class="dtm-row">
10927
+ <button class="dtm-btn dtm-btn--ghost dtm-btn--xs" data-dtm="auto-master" title="\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\u30FB\u30EA\u30D0\u30FC\u30D6\u3092\u5546\u696D\u66F2\u5BC4\u308A\u306E\u5024\u306B\u4E00\u62EC\u8A2D\u5B9A\u3057\u307E\u3059">\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0</button>
10928
+ <button class="dtm-infobtn" data-dtm="auto-master-info" title="\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10929
+ <span class="dtm-grow"></span>
10930
+ </div>
10931
+ <div class="dtm-row">
10932
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10933
+ <input type="range" class="dtm-range dtm-grow" data-dtm="reverb-amount" value="0" min="0" max="100" aria-label="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\uFF08\u5168\u30C8\u30E9\u30C3\u30AF\u3078\u4E00\u5F8B\u306B\u639B\u304B\u308B\u6B8B\u97FF\uFF09">
10934
+ <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10935
+ <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10936
+ </div>
10937
+ </div>
10938
+ </details>
10939
+
10578
10940
  <details class="dtm-panel">
10579
10941
  <summary>\u30C9\u30E9\u30E0\u8A2D\u5B9A</summary>
10580
10942
  <div class="dtm-panel-body">
@@ -10740,6 +11102,7 @@ var buildUI = (target, options) => {
10740
11102
  prevBarBtn: sel("prev-bar"),
10741
11103
  nextBarBtn: sel("next-bar"),
10742
11104
  soloCheckbox: sel("solo"),
11105
+ clipBadge: sel("clip-badge"),
10743
11106
  toolPen: sel("tool-pen"),
10744
11107
  toolSelect: sel("tool-select"),
10745
11108
  toolEraser: sel("tool-eraser"),
@@ -10766,6 +11129,11 @@ var buildUI = (target, options) => {
10766
11129
  hScrollThumb: sel("hscroll-thumb"),
10767
11130
  masterVolume: sel("master-volume"),
10768
11131
  masterVolumeLabel: sel("master-volume-label"),
11132
+ reverbAmount: sel("reverb-amount"),
11133
+ reverbAmountLabel: sel("reverb-amount-label"),
11134
+ reverbAmountInfoBtn: sel("reverb-amount-info"),
11135
+ autoMasterBtn: sel("auto-master"),
11136
+ autoMasterInfoBtn: sel("auto-master-info"),
10769
11137
  trackTabs: sel("track-tabs"),
10770
11138
  trackBody: sel("track-body"),
10771
11139
  drumSelect: sel("drum-select"),
@@ -10934,16 +11302,16 @@ var generateRandomPattern = (core, options) => {
10934
11302
  const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
10935
11303
  const rootOffset = Math.floor(Math.random() * 12);
10936
11304
  const availablePitches = [];
10937
- for (let i = 0; i < 12; i++) {
10938
- const noteInOctave = (i - rootOffset + 12) % 12;
10939
- if (scale.includes(noteInOctave)) availablePitches.push(basePitch + i);
11305
+ for (let i2 = 0; i2 < 12; i2++) {
11306
+ const noteInOctave = (i2 - rootOffset + 12) % 12;
11307
+ if (scale.includes(noteInOctave)) availablePitches.push(basePitch + i2);
10940
11308
  }
10941
11309
  core.beginBatch();
10942
11310
  for (let bar = 0; bar < numBars; bar++) {
10943
11311
  const barStart = startStep + bar * stepsPerBar;
10944
11312
  const numNotes = Math.floor(Math.random() * 4) + 2;
10945
11313
  const occupied = /* @__PURE__ */ new Set();
10946
- for (let i = 0; i < numNotes; i++) {
11314
+ for (let i2 = 0; i2 < numNotes; i2++) {
10947
11315
  const stepInRange = Math.floor(Math.random() * (stepsPerBar / noteLength)) * noteLength;
10948
11316
  const step = barStart + stepInRange;
10949
11317
  if (occupied.has(step)) continue;
@@ -11044,10 +11412,10 @@ var STEPS_PER_BEAT3 = 48;
11044
11412
  var analyzeMidiTracks = (midi) => {
11045
11413
  const { tracks } = midi;
11046
11414
  const result = [];
11047
- for (let i = 0; i < tracks.length; i++) {
11415
+ for (let i2 = 0; i2 < tracks.length; i2++) {
11048
11416
  const notes = [];
11049
11417
  let currentTime = 0;
11050
- for (const event of tracks[i]) {
11418
+ for (const event of tracks[i2]) {
11051
11419
  currentTime += event.delta;
11052
11420
  if (event.noteOn && event.noteOn.velocity > 0) {
11053
11421
  notes.push({
@@ -11070,8 +11438,8 @@ var analyzeMidiTracks = (midi) => {
11070
11438
  const editableNotes = validNotes.filter((n) => n.channel !== 9);
11071
11439
  if (validNotes.length > 0 && editableNotes.length === 0) continue;
11072
11440
  result.push({
11073
- index: i,
11074
- name: `Ch${i + 1}`,
11441
+ index: i2,
11442
+ name: `Ch${i2 + 1}`,
11075
11443
  noteCount: editableNotes.length,
11076
11444
  selected: editableNotes.length > 0
11077
11445
  });
@@ -11118,8 +11486,8 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11118
11486
  const pitch = noteOff.noteNumber;
11119
11487
  const channel = event.channel ?? 0;
11120
11488
  if (channelNotes[channel]) {
11121
- for (let i = channelNotes[channel].length - 1; i >= 0; i--) {
11122
- const note = channelNotes[channel][i];
11489
+ for (let i2 = channelNotes[channel].length - 1; i2 >= 0; i2--) {
11490
+ const note = channelNotes[channel][i2];
11123
11491
  if (note.pitch === pitch && note.end === null) {
11124
11492
  note.end = currentTime;
11125
11493
  break;
@@ -11149,10 +11517,10 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11149
11517
  const avgPitch = validNotes.reduce((sum, n) => sum + n.pitch, 0) / validNotes.length;
11150
11518
  let maxSimultaneous = 0;
11151
11519
  const sortedNotes = [...validNotes].sort((a, b) => a.start - b.start);
11152
- for (let i = 0; i < sortedNotes.length; i++) {
11520
+ for (let i2 = 0; i2 < sortedNotes.length; i2++) {
11153
11521
  let simultaneous = 1;
11154
- for (let j = i + 1; j < sortedNotes.length; j++) {
11155
- if (sortedNotes[j].start < sortedNotes[i].end) {
11522
+ for (let j = i2 + 1; j < sortedNotes.length; j++) {
11523
+ if (sortedNotes[j].start < sortedNotes[i2].end) {
11156
11524
  simultaneous++;
11157
11525
  }
11158
11526
  }
@@ -11163,14 +11531,14 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11163
11531
  const blocks = [];
11164
11532
  let blockStart = sortedNotes[0].start;
11165
11533
  let blockEnd = sortedNotes[0].end;
11166
- for (let i = 1; i < sortedNotes.length; i++) {
11167
- const gap = sortedNotes[i].start - sortedNotes[i - 1].end;
11534
+ for (let i2 = 1; i2 < sortedNotes.length; i2++) {
11535
+ const gap = sortedNotes[i2].start - sortedNotes[i2 - 1].end;
11168
11536
  if (gap >= ticksPerBar) {
11169
11537
  blocks.push({ start: blockStart, end: blockEnd });
11170
- blockStart = sortedNotes[i].start;
11171
- blockEnd = sortedNotes[i].end;
11538
+ blockStart = sortedNotes[i2].start;
11539
+ blockEnd = sortedNotes[i2].end;
11172
11540
  } else {
11173
- blockEnd = sortedNotes[i].end;
11541
+ blockEnd = sortedNotes[i2].end;
11174
11542
  }
11175
11543
  }
11176
11544
  blocks.push({ start: blockStart, end: blockEnd });
@@ -11284,9 +11652,9 @@ var extractMidiPlacementsByTrack = (midi, selectedIndices, trackIds) => {
11284
11652
  const noteOff = event.noteOff || event.noteOn;
11285
11653
  if (noteOff) {
11286
11654
  const pitch = noteOff.noteNumber;
11287
- for (let i = active.length - 1; i >= 0; i--) {
11288
- if (active[i].pitch === pitch && active[i].end === null) {
11289
- active[i].end = currentTime;
11655
+ for (let i2 = active.length - 1; i2 >= 0; i2--) {
11656
+ if (active[i2].pitch === pitch && active[i2].end === null) {
11657
+ active[i2].end = currentTime;
11290
11658
  break;
11291
11659
  }
11292
11660
  }
@@ -11451,17 +11819,17 @@ var extractDrumPatternFromNotes = (rawNotes, drumFont = "FluidR3_GM_sf2_file:0")
11451
11819
  let prev = bList[0];
11452
11820
  const parts = key.split("_");
11453
11821
  const noteObj = { step: +parts[0], pitch: +parts[1], velocity: +parts[2] };
11454
- for (let i = 1; i <= bList.length; i++) {
11455
- if (i === bList.length || bList[i] !== prev + 1) {
11822
+ for (let i2 = 1; i2 <= bList.length; i2++) {
11823
+ if (i2 === bList.length || bList[i2] !== prev + 1) {
11456
11824
  const rKey = `${start}-${prev}`;
11457
11825
  if (!rangeMap[rKey]) rangeMap[rKey] = [];
11458
11826
  rangeMap[rKey].push(noteObj);
11459
- if (i < bList.length) {
11460
- start = bList[i];
11461
- prev = bList[i];
11827
+ if (i2 < bList.length) {
11828
+ start = bList[i2];
11829
+ prev = bList[i2];
11462
11830
  }
11463
11831
  } else {
11464
- prev = bList[i];
11832
+ prev = bList[i2];
11465
11833
  }
11466
11834
  }
11467
11835
  }
@@ -11838,8 +12206,8 @@ var drawHeader = () => {
11838
12206
  (g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
11839
12207
  );
11840
12208
  for (let bar = startBar; bar <= endBar + 1; bar++) {
11841
- const x = bar * stepsPerBar * stepWidth;
11842
- const screenX = x;
12209
+ const x2 = bar * stepsPerBar * stepWidth;
12210
+ const screenX = x2;
11843
12211
  g_header_ctx.beginPath();
11844
12212
  g_header_ctx.moveTo(screenX, 0);
11845
12213
  g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
@@ -11886,11 +12254,11 @@ var drawGrid = (noteLengthSteps = 1) => {
11886
12254
  const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
11887
12255
  const endX = g_draw_offset_x + g_grid_canvas.width;
11888
12256
  const lineStep = stepWidth * gridStep;
11889
- for (let x = startX; x <= endX; x += lineStep) {
11890
- const step = x / stepWidth;
12257
+ for (let x2 = startX; x2 <= endX; x2 += lineStep) {
12258
+ const step = x2 / stepWidth;
11891
12259
  const isBarLine = step % stepsPerBar === 0;
11892
12260
  const isNoteLine = step % gridStep === 0;
11893
- const screenX = x - g_draw_offset_x;
12261
+ const screenX = x2 - g_draw_offset_x;
11894
12262
  g_grid_ctx.beginPath();
11895
12263
  g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
11896
12264
  g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
@@ -11952,26 +12320,26 @@ var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
11952
12320
  var getXY = (e) => {
11953
12321
  const { clientX, clientY } = e;
11954
12322
  const rect = g_grid_canvas.getBoundingClientRect();
11955
- const x = Math.floor(clientX - rect.left);
12323
+ const x2 = Math.floor(clientX - rect.left);
11956
12324
  const y = Math.floor(clientY - rect.top);
11957
- return [x, y, e.buttons];
12325
+ return [x2, y, e.buttons];
11958
12326
  };
11959
12327
  var getGridPosition = (e) => {
11960
- const [x, y] = getXY(e);
12328
+ const [x2, y] = getXY(e);
11961
12329
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11962
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12330
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11963
12331
  const absoluteY = y + g_draw_offset_y;
11964
12332
  const yIndex = Math.floor(absoluteY / keyHeight);
11965
12333
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
11966
- return { step, pitch, x, y };
12334
+ return { step, pitch, x: x2, y };
11967
12335
  };
11968
12336
  var onClick = (callback) => {
11969
12337
  g_grid_canvas.addEventListener(
11970
12338
  "click",
11971
12339
  (e) => {
11972
- const [x, y] = getXY(e);
12340
+ const [x2, y] = getXY(e);
11973
12341
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11974
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12342
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11975
12343
  const absoluteY = y + g_draw_offset_y;
11976
12344
  const yIndex = Math.floor(absoluteY / keyHeight);
11977
12345
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
@@ -11983,8 +12351,8 @@ var onClick = (callback) => {
11983
12351
  );
11984
12352
  g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
11985
12353
  };
11986
- var setDrawOffset = (x, y) => {
11987
- g_draw_offset_x = x;
12354
+ var setDrawOffset = (x2, y) => {
12355
+ g_draw_offset_x = x2;
11988
12356
  g_draw_offset_y = y;
11989
12357
  drawKeyboard();
11990
12358
  drawHeader();
@@ -12299,12 +12667,12 @@ var MMLCore = class _MMLCore {
12299
12667
  currentCursor += steps;
12300
12668
  }
12301
12669
  };
12302
- for (let i = 0; i < sortedSteps.length; i++) {
12303
- const startStep = sortedSteps[i];
12670
+ for (let i2 = 0; i2 < sortedSteps.length; i2++) {
12671
+ const startStep = sortedSteps[i2];
12304
12672
  const notes = notesByStep.get(startStep);
12305
12673
  if (!notes) continue;
12306
12674
  fillRests(startStep);
12307
- const nextStart = sortedSteps[i + 1] ?? endStep;
12675
+ const nextStart = sortedSteps[i2 + 1] ?? endStep;
12308
12676
  const physicsLimit = nextStart - currentCursor;
12309
12677
  if (physicsLimit < MIN_STEP) {
12310
12678
  continue;
@@ -12369,10 +12737,10 @@ var decomposeToMonophonic = (notes) => {
12369
12737
  for (const note of sorted) {
12370
12738
  let assigned = -1;
12371
12739
  let minEnd = Infinity;
12372
- for (let i = 0; i < tracks.length; i++) {
12373
- if (trackEnds[i] <= note.startStep && trackEnds[i] < minEnd) {
12374
- minEnd = trackEnds[i];
12375
- assigned = i;
12740
+ for (let i2 = 0; i2 < tracks.length; i2++) {
12741
+ if (trackEnds[i2] <= note.startStep && trackEnds[i2] < minEnd) {
12742
+ minEnd = trackEnds[i2];
12743
+ assigned = i2;
12376
12744
  }
12377
12745
  }
12378
12746
  if (assigned === -1) {
@@ -12428,6 +12796,89 @@ var CHORD_INFO_HTML2 = `
12428
12796
  </ul>
12429
12797
  </div>
12430
12798
  `;
12799
+ var VIBRATO_INFO_HTML = `
12800
+ <div class="dtm-modal-body-content">
12801
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12802
+ <p>ON\u306B\u3059\u308B\u3068\u3001\u4E00\u5B9A\u306E\u9577\u3055\uFF08\u7D040.35\u79D2\uFF09\u4EE5\u4E0A\u306E\u97F3\u7B26\uFF08\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\uFF09\u306B\u3060\u3051\u3001\u81EA\u52D5\u3067\u30D4\u30C3\u30C1\u304C\u5C0F\u523B\u307F\u306B\u63FA\u308C\u308B\u6B4C\u5531\u8868\u73FE\uFF08\u30D3\u30D6\u30E9\u30FC\u30C8\uFF09\u304C\u639B\u304B\u308A\u307E\u3059\u3002</p>
12803
+ <h4>\u77ED\u3044\u97F3\u7B26\u306B\u639B\u304B\u3089\u306A\u3044\u7406\u7531</h4>
12804
+ <p>1\u5468\u671F\u3082\u63FA\u308C\u304D\u3089\u306A\u3044\u3046\u3061\u306B\u6B21\u306E\u97F3\u3078\u79FB\u3063\u3066\u3057\u307E\u3044\u3001\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u3044\u3046\u3088\u308A\u5358\u306A\u308B\u97F3\u7A0B\u306E\u30D6\u30EC\u3068\u3057\u3066\u4E0D\u81EA\u7136\u306B\u805E\u3053\u3048\u308B\u305F\u3081\u3067\u3059\u3002</p>
12805
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12806
+ <p>\u30D4\u30C3\u30C1\u3092\u63FA\u3089\u3059\u52B9\u679C\u306A\u306E\u3067\u3001\u58F0\u8CEA\u305D\u306E\u3082\u306E\u3092\u5909\u3048\u308B\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306F\u72EC\u7ACB\u3057\u3066\u7D44\u307F\u5408\u308F\u305B\u3089\u308C\u307E\u3059\u3002\u901F\u3055\u30FB\u6DF1\u3055\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\uFF08\u6B4C\u3068\u3057\u3066\u7834\u7DBB\u3057\u306B\u304F\u3044\u63A7\u3048\u3081\u306A\u91CF\u306B\u56FA\u5B9A\uFF09\u3002\u66F2\u3084\u7B87\u6240\u3054\u3068\u306B\u639B\u3051\u305F\u3044/\u639B\u3051\u305F\u304F\u306A\u3044\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u6B4C\u8A5E\u3092\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002</p>
12807
+ </div>
12808
+ `;
12809
+ var GENDER_INFO_HTML = `
12810
+ <div class="dtm-modal-body-content">
12811
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12812
+ <p>\u30D4\u30C3\u30C1\uFF08\u97F3\u306E\u9AD8\u3055\uFF09\u306F\u305D\u306E\u307E\u307E\u306B\u3001\u58F0\u306E\u592A\u3055/\u7D30\u3055\uFF08\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8\uFF1D\u58F0\u9053\u306E\u5171\u9CF4\u3001\u5E74\u9F62\u30FB\u6027\u5225\u611F\u306E\u5370\u8C61\uFF09\u3060\u3051\u3092\u52D5\u304B\u3057\u307E\u3059\u300250\u304C\u7121\u5909\u5316\u300150\u672A\u6E80\u3067\u4F4E\u3081/\u592A\u3081\uFF08\u5927\u4EBA\u3073\u308B\uFF09\u300150\u8D85\u3067\u9AD8\u3081/\u7D30\u3081\uFF08\u82E5\u304F/\u660E\u308B\u304F\uFF09\u306B\u5BC4\u308A\u307E\u3059\u3002</p>
12813
+ <h4>\u30AA\u30AF\u30BF\u30FC\u30D6\u30B7\u30D5\u30C8\u3068\u306E\u9055\u3044</h4>
12814
+ <p>\u30AA\u30AF\u30BF\u30FC\u30D6\u306F\u97F3\u7A0B\u305D\u306E\u3082\u306E\u3092\u4E0A\u4E0B\u3055\u305B\u307E\u3059\u304C\u3001\u30B8\u30A7\u30F3\u30C0\u30FC\u306F\u97F3\u7A0B\u3092\u5909\u3048\u305A\u306B\u58F0\u8272\u3060\u3051\u3092\u52D5\u304B\u3057\u307E\u3059\u3002\u300C\u9AD8\u3044\u58F0\u306E\u307E\u307E\u5927\u4EBA\u3073\u3055\u305B\u308B\u300D\u300C\u4F4E\u3044\u58F0\u306E\u307E\u307E\u82E5\u3005\u3057\u304F\u3059\u308B\u300D\u3068\u3044\u3063\u305F\u3001\u97F3\u7A0B\u3068\u58F0\u8CEA\u3092\u5225\u3005\u306B\u8ABF\u6574\u3057\u305F\u3044\u3068\u304D\u306B\u4F7F\u3044\u307E\u3059\u3002</p>
12815
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12816
+ <p>\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u7D44\u307F\u5408\u308F\u305B\u3066\u58F0\u306E\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3092\u4F5C\u308A\u307E\u3059\uFF08\u4F8B: \u4F4E\u3081+\u606F\u591A\u3081\u3067\u6E0B\u3044/\u5927\u4EBA\u3063\u307D\u3044\u5370\u8C61\u3001\u9AD8\u3081+\u606F\u5C11\u306A\u3081\u3067\u5143\u6C17/\u82E5\u3005\u3057\u3044\u5370\u8C61\uFF09\u3002koe\u97F3\u6E90\uFF08UTAU\u7531\u6765\u306E.koe\u97F3\u6E90\uFF09\u9650\u5B9A\u306E\u52B9\u679C\u3067\u3001klatt\uFF08\u5185\u8535\u306E\u7C21\u6613\u5408\u6210\uFF09\u3067\u306F\u5909\u5316\u3057\u307E\u305B\u3093\u3002</p>
12817
+ </div>
12818
+ `;
12819
+ var BREATHINESS_INFO_HTML = `
12820
+ <div class="dtm-modal-body-content">
12821
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12822
+ <p>\u606F\u6210\u5206\u306E\u91CF\u3067\u3059\u300250\u304C\u7121\u5909\u5316\u3001\u5927\u304D\u3044\u307B\u3069\u606F\u3063\u307D\u304F\uFF08\u3055\u3055\u3084\u304D\u5BC4\u308A\uFF09\u3001\u5C0F\u3055\u3044\u307B\u3069\u82AF\u306E\u3042\u308B\u58F0\u306B\u306A\u308A\u307E\u3059\u3002</p>
12823
+ <h4>\u4E0A\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12824
+ <p>\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\u3067\u97F3\u7A0B\u611F\u304C\u8584\u308C\u3066\u805E\u3053\u3048\u307E\u3059\uFF08\u606F\u306E\u97F3\u304C\u30D4\u30C3\u30C1\u611F\u3092\u96A0\u3059\u305F\u3081\uFF09\u3002\u56C1\u304F\u3088\u3046\u306A\u30D0\u30E9\u30FC\u30C9\u8868\u73FE\u306B\u306F\u52B9\u679C\u7684\u3067\u3059\u304C\u3001\u4E0A\u3052\u3059\u304E\u308B\u3068\u30E1\u30ED\u30C7\u30A3\u304C\u4F1D\u308F\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002</p>
12825
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12826
+ <p>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u7D44\u307F\u5408\u308F\u305B\u308B\u3068\u3001\u63FA\u308C\u306A\u304C\u3089\u606F\u3063\u307D\u3044\u3001\u3088\u308A\u30A8\u30E2\u30FC\u30B7\u30E7\u30CA\u30EB\u306A\u8868\u73FE\u306B\u306A\u308A\u3084\u3059\u3044\u3067\u3059\u3002koe\u97F3\u6E90\uFF08UTAU\u7531\u6765\u306E.koe\u97F3\u6E90\uFF09\u9650\u5B9A\u306E\u52B9\u679C\u3067\u3001klatt\uFF08\u5185\u8535\u306E\u7C21\u6613\u5408\u6210\uFF09\u3067\u306F\u5909\u5316\u3057\u307E\u305B\u3093\u3002</p>
12827
+ </div>
12828
+ `;
12829
+ var LYRIC_REVERB_INFO_HTML = `
12830
+ <div class="dtm-modal-body-content">
12831
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12832
+ <p>\u3053\u306E\u30DC\u30FC\u30AB\u30EB\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u3001\u66F2\u5168\u4F53\u306B\u639B\u304B\u308B\u300C\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u300D\u3078\u3069\u308C\u3060\u3051\u97F3\u3092\u9001\u308B\u304B\u3092\u6C7A\u3081\u307E\u3059\uFF080\u3067\u9001\u3089\u306A\u3044\uFF1D\u30C9\u30E9\u30A4\u3001100\u3067\u76EE\u4E00\u676F\u9001\u308B\uFF09\u3002</p>
12833
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12834
+ <p>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u30D1\u30CD\u30EB\u306B\u3042\u308B\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u304C0%\u3060\u3068\u3001\u3053\u3053\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u300C\u9001\u308A\u91CF\uFF08\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304C\u3069\u308C\u3060\u3051\u63D0\u4F9B\u3059\u308B\u304B\uFF09\u300D\u3068\u300C\u30DE\u30B9\u30BF\u306E\u6B8B\u97FF\u8A2D\u5B9A\uFF08\u5B9F\u969B\u306B\u3069\u3093\u306A\u97FF\u304D\u304C\u639B\u304B\u308B\u304B\uFF09\u300D\u306E\u4E8C\u6BB5\u69CB\u3048\u306B\u306A\u3063\u3066\u3044\u308B\u305F\u3081\u3067\u3059\u3002\u4E21\u65B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12835
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12836
+ <p>\u6B8B\u97FF\u3067\u97F3\u304C\u6EF2\u307F\u3001\u6B4C\u8A5E\u304C\u805E\u304D\u53D6\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002\u30C9\u30E9\u30A4\u306A\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u8FD1\u3044\u300D\u300C\u524D\u306B\u51FA\u308B\u300D\u5370\u8C61\u3001\u30EA\u30D0\u30FC\u30D6\u305F\u3063\u3077\u308A\u306E\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u5965\u884C\u304D\u304C\u3042\u308B\u300D\u300C\u5E7B\u60F3\u7684\u300D\u306A\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u3002\u8868\u73FE\u3057\u305F\u3044\u8DDD\u96E2\u611F\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12837
+ </div>
12838
+ `;
12839
+ var MASTER_REVERB_INFO_HTML = `
12840
+ <div class="dtm-modal-body-content">
12841
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12842
+ <p>\u66F2\u5168\u4F53\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308B\u6B8B\u97FF\uFF08\u90E8\u5C4B\u9CF4\u308A\u30FB\u7A7A\u9593\u306E\u97FF\u304D\uFF09\u3067\u3059\u30020%\u3067\u5B8C\u5168\u306B\u30C9\u30E9\u30A4\uFF08\u97FF\u304D\u306A\u3057\uFF09\u3001\u4E0A\u3052\u308B\u307B\u3069\u5E83\u3044\u7A7A\u9593\u3067\u9CF4\u3063\u3066\u3044\u308B\u3088\u3046\u306A\u5965\u884C\u304D\u304C\u51FA\u307E\u3059\u3002</p>
12843
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12844
+ <p>\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u305D\u308C\u305E\u308C\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u3044\u3046\u500B\u5225\u306E\u3064\u307E\u307F\u304C\u3042\u308A\u3001\u305D\u3061\u3089\u304C0%\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3001\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002\u300C\u30DE\u30B9\u30BF\uFF1D\u6B8B\u97FF\u306E\u8CEA\u3068\u91CF\u305D\u306E\u3082\u306E\u300D\u300C\u30C8\u30E9\u30C3\u30AF\u9001\u308A\uFF1D\u305D\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u3069\u308C\u3060\u3051\u6DF7\u305C\u308B\u304B\u300D\u3068\u3044\u3046\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u73FE\u72B6\u30BB\u30F3\u30C9\u6A5F\u80FD\u304C\u7121\u3044\u305F\u3081\u3001\u5E38\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308A\u307E\u3059\u3002</p>
12845
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12846
+ <p>\u30DF\u30C3\u30AF\u30B9\u5168\u4F53\u306E\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3001\u9060\u304F\u30FB\u3053\u3082\u3063\u305F\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u300210\u301C30%\u7A0B\u5EA6\u3092\u51FA\u767A\u70B9\u306B\u3001\u66F2\u306E\u30B8\u30E3\u30F3\u30EB\u3084\u7A7A\u9593\u306E\u5E83\u3055\u306E\u30A4\u30E1\u30FC\u30B8\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3059\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
12847
+ </div>
12848
+ `;
12849
+ var TRACK_COMPRESSION_INFO_HTML = `
12850
+ <div class="dtm-modal-body-content">
12851
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12852
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306B\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u3092\u639B\u3051\u3001\u97F3\u91CF\u306E\u5927\u5C0F\u5DEE\u3092\u7E2E\u3081\u3066\u300C\u524D\u306B\u51FA\u308B\u300D\u300C\u805E\u3053\u3048\u3084\u3059\u3044\u300D\u97F3\u306B\u3057\u307E\u3059\u3002\u5E02\u8CA9\u66F2\u306E\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3084\u30DF\u30C3\u30AF\u30B9\u3067\u5B9A\u756A\u306E\u51E6\u7406\u3067\u3059\u30020\u3067\u7121\u5727\u7E2E\u3001100\u306B\u8FD1\u3065\u304F\u307B\u3069\u5F37\u304F\u5727\u7E2E\u3055\u308C\u307E\u3059\u3002</p>
12853
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12854
+ <p>\u5F37\u5F31\u306E\u8868\u60C5\uFF08\u30C0\u30A4\u30CA\u30DF\u30AF\u30B9\uFF09\u304C\u5931\u308F\u308C\u3066\u5358\u8ABF\u306B\u805E\u3053\u3048\u307E\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u3084\u30EA\u30FC\u30C9\u697D\u5668\u306F\u63A7\u3048\u3081\uFF0820\u301C40\u7A0B\u5EA6\uFF09\u3001\u30C9\u30E9\u30E0\u3084\u30D9\u30FC\u30B9\u306F\u3084\u3084\u5F37\u3081\u3001\u304C\u4E00\u822C\u7684\u306A\u76EE\u5B89\u3067\u3059\u3002</p>
12855
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12856
+ <p>\u30DE\u30B9\u30BF\u306E\u300C\u5B89\u5168\u30EA\u30DF\u30C3\u30BF\u30FC\u300D\uFF08\u5E38\u6642ON\u3001[reverb]\u30D1\u30CD\u30EB\u306E\u5916\u5074\u3067\u81EA\u52D5\u7684\u306B\u50CD\u304F\u4FDD\u967A\uFF09\u3068\u306F\u5225\u7269\u3067\u3059\u3002\u3042\u3061\u3089\u306F\u97F3\u5272\u308C\u3092\u7269\u7406\u7684\u306B\u9632\u3050\u305F\u3081\u306E\u6700\u7D42\u9632\u885B\u30E9\u30A4\u30F3\u3067\u3001\u5E38\u306B\u63A7\u3048\u3081\u306B\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u3053\u3061\u3089\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u306F\u97F3\u4F5C\u308A\uFF08\u8868\u73FE\uFF09\u306E\u305F\u3081\u306E\u51E6\u7406\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3054\u3068\u306B\u597D\u304D\u306A\u3060\u3051\u5F37\u304F/\u5F31\u304F\u639B\u3051\u3089\u308C\u307E\u3059\u3002\u300C\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u300D\u306F\u5168\u30C8\u30E9\u30C3\u30AF\u4E00\u5F8B35%\u3092\u5F53\u3066\u308B\u3060\u3051\u306A\u306E\u3067\u3001\u30DC\u30FC\u30AB\u30EB\u306A\u3069\u76EE\u7ACB\u305F\u305B\u305F\u3044\u30D1\u30FC\u30C8\u306F\u5F8C\u3067\u500B\u5225\u306B\u4E0B\u3052\u308B\u3068\u8F2A\u90ED\u304C\u51FA\u3084\u3059\u304F\u306A\u308A\u307E\u3059\u3002</p>
12857
+ </div>
12858
+ `;
12859
+ var TRACK_WIDTH_INFO_HTML = `
12860
+ <div class="dtm-modal-body-content">
12861
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12862
+ <p>\u5DE6\u53F3\u306E\u5E83\u304C\u308A\u3092\u8ABF\u6574\u3057\u307E\u3059\u3002100\u304C\u539F\u97F3\u306E\u307E\u307E\u30010\u3067\u5B8C\u5168\u30E2\u30CE\u30E9\u30EB\uFF08\u5DE6\u53F3\u304C\u540C\u3058\u97F3\uFF09\u3001100\u3092\u8D85\u3048\u308B\u3068\u5DE6\u53F3\u306E\u9055\u3044\u304C\u8A87\u5F35\u3055\u308C\u3066\u5E83\u304F\u805E\u3053\u3048\u307E\u3059\u3002</p>
12863
+ <h4>\u300C\u5B9A\u4F4D\uFF08\u30D1\u30F3\uFF09\u300D\u3068\u306E\u9055\u3044</h4>
12864
+ <p>\u5B9A\u4F4D\u306F\u300C\u97F3\u3092\u3069\u3053\u306B\u7F6E\u304F\u304B\u300D\uFF08\u5DE6\u5BC4\u308A/\u4E2D\u592E/\u53F3\u5BC4\u308A\uFF09\u3001\u30B9\u30C6\u30EC\u30AA\u5E45\u306F\u300C\u305D\u306E\u97F3\u81EA\u4F53\u304C\u3069\u308C\u3060\u3051\u5E83\u304C\u3063\u3066\u805E\u3053\u3048\u308B\u304B\u300D\u3067\u3001\u5F79\u5272\u304C\u7570\u306A\u308A\u307E\u3059\u3002\u4E21\u65B9\u3092\u5F37\u304F\u4F7F\u3046\u3068\u5B9A\u4F4D\u304C\u307C\u3084\u3051\u3066\u66D6\u6627\u306B\u306A\u308A\u304C\u3061\u3067\u3059\u3002</p>
12865
+ <h4>\u5E83\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12866
+ <p>\u30B9\u30DE\u30DB\u306E\u30B9\u30D4\u30FC\u30AB\u30FC1\u500B\u306A\u3069\u3001\u30E2\u30CE\u30E9\u30EB\u306B\u8FD1\u3044\u74B0\u5883\u3067\u518D\u751F\u3059\u308B\u3068\u97F3\u304C\u8584\u304F/\u4F4D\u76F8\u304C\u4E71\u308C\u3066\u805E\u3053\u3048\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\uFF08\u5DE6\u53F3\u306E\u5DEE\u5206\u3092\u8A87\u5F35\u3057\u3066\u3044\u308B\u305F\u3081\u3001\u8DB3\u3057\u5408\u308F\u305B\u308B\u3068\u6253\u3061\u6D88\u3057\u5408\u3046\u6210\u5206\u304C\u5897\u3048\u308B\uFF09\u3002\u4E3B\u65CB\u5F8B\u3084\u30DC\u30FC\u30AB\u30EB\u306F\u4E2D\u592E\u4ED8\u8FD1\u3067\u72ED\u3081\u3001\u30D1\u30C3\u30C9\u3084\u30B7\u30F3\u30BB\u306E\u88C5\u98FE\u30D1\u30FC\u30C8\u306F\u5E83\u3052\u308B\u3001\u3068\u3044\u3046\u306E\u304C\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3067\u3059\u3002</p>
12867
+ </div>
12868
+ `;
12869
+ var AUTO_MASTER_INFO_HTML = `
12870
+ <div class="dtm-modal-body-content">
12871
+ <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12872
+ <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002</p>
12873
+ <ul>
12874
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12875
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
12876
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12877
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u3001\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%</li>
12878
+ </ul>
12879
+ <p style="margin-top:4px;"><small>\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
12880
+ </div>
12881
+ `;
12431
12882
  var MIDI_INFO_HTML = `
12432
12883
  <div class="dtm-modal-body-content">
12433
12884
  <h4>1. MIDI\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -12631,7 +13082,7 @@ var LYRIC_MODEL_CATEGORIES = [
12631
13082
  },
12632
13083
  {
12633
13084
  label: "\u304A\u3093J",
12634
- models: ["roze", "shiyo", "rino"]
13085
+ models: ["roze", "shiyo", "rino", "rino121", "uc"]
12635
13086
  },
12636
13087
  {
12637
13088
  label: "\u4E00\u822C",
@@ -12699,6 +13150,8 @@ var mountDAW = (target, options = {}) => {
12699
13150
  });
12700
13151
  refs.masterVolume.value = String(options.masterVolume ?? 50);
12701
13152
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
13153
+ refs.reverbAmount.value = String(options.reverbAmount ?? 0);
13154
+ refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
12702
13155
  refs.drumVolume.value = String(options.drumVolume ?? 80);
12703
13156
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
12704
13157
  const renderConfig = {
@@ -12714,6 +13167,9 @@ var mountDAW = (target, options = {}) => {
12714
13167
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
12715
13168
  let masterVolume = options.masterVolume ?? 50;
12716
13169
  options.singingVoices?.setVolume(masterVolume / 100);
13170
+ let reverbAmount = options.reverbAmount ?? 0;
13171
+ let unsubscribeClip;
13172
+ options.onReverbChange?.(reverbAmount);
12717
13173
  let drumVolume = options.drumVolume ?? 80;
12718
13174
  let currentDrumPattern = refs.drumSelect.value;
12719
13175
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -12791,7 +13247,11 @@ var mountDAW = (target, options = {}) => {
12791
13247
  vocalVolume: t.vocalVolume,
12792
13248
  vocalGate: t.vocalGate,
12793
13249
  vocalPan: t.vocalPan,
12794
- vocalOctave: t.vocalOctave
13250
+ vocalOctave: t.vocalOctave,
13251
+ vocalVibrato: t.vocalVibrato,
13252
+ vocalReverb: t.vocalReverb,
13253
+ vocalGender: t.vocalGender,
13254
+ vocalBreathiness: t.vocalBreathiness
12795
13255
  };
12796
13256
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
12797
13257
  lyricsDebounceTimer = setTimeout(() => {
@@ -12851,25 +13311,35 @@ var mountDAW = (target, options = {}) => {
12851
13311
  vocalGate: 100,
12852
13312
  vocalPan: 64,
12853
13313
  vocalOctave: 0,
12854
- trackInstrument: ""
13314
+ vocalVibrato: false,
13315
+ vocalReverb: 0,
13316
+ vocalGender: 50,
13317
+ vocalBreathiness: 50,
13318
+ trackInstrument: "",
13319
+ trackCompression: 0,
13320
+ trackWidth: 100
12855
13321
  };
12856
13322
  });
12857
13323
  };
12858
13324
  const buildLyricsMap = () => {
12859
13325
  const map = /* @__PURE__ */ new Map();
12860
- trackStates.forEach((t, i) => {
13326
+ trackStates.forEach((t, i2) => {
12861
13327
  const model = t.lyricModel.trim();
12862
13328
  const text = t.lyrics.trim();
12863
13329
  if (!model || !text) return;
12864
13330
  const syllables = normalizeLyrics(text);
12865
13331
  if (syllables.length === 0) return;
12866
- map.set(i, {
12867
- trackId: i,
13332
+ map.set(i2, {
13333
+ trackId: i2,
12868
13334
  model: model.toLowerCase(),
12869
13335
  volume: t.vocalVolume,
12870
13336
  gate: t.vocalGate,
12871
13337
  pan: t.vocalPan,
12872
13338
  octave: t.vocalOctave,
13339
+ vibrato: t.vocalVibrato,
13340
+ reverb: t.vocalReverb,
13341
+ gender: t.vocalGender,
13342
+ breathiness: t.vocalBreathiness,
12873
13343
  syllables
12874
13344
  });
12875
13345
  });
@@ -12909,15 +13379,15 @@ var mountDAW = (target, options = {}) => {
12909
13379
  const ctx = getGridContext();
12910
13380
  const canvas = getGridCanvas();
12911
13381
  if (!ctx) return;
12912
- const x = playStartStep * renderConfig.stepWidth - currentOffsetX;
12913
- if (x < -10 || x > canvas.width + 10) return;
13382
+ const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
13383
+ if (x2 < -10 || x2 > canvas.width + 10) return;
12914
13384
  ctx.save();
12915
13385
  ctx.strokeStyle = "#ffec27";
12916
13386
  ctx.lineWidth = 2;
12917
13387
  ctx.setLineDash([4, 4]);
12918
13388
  ctx.beginPath();
12919
- ctx.moveTo(x, 0);
12920
- ctx.lineTo(x, canvas.height);
13389
+ ctx.moveTo(x2, 0);
13390
+ ctx.lineTo(x2, canvas.height);
12921
13391
  ctx.stroke();
12922
13392
  ctx.restore();
12923
13393
  };
@@ -12925,14 +13395,14 @@ var mountDAW = (target, options = {}) => {
12925
13395
  const ctx = getGridContext();
12926
13396
  const canvas = getGridCanvas();
12927
13397
  if (!ctx) return;
12928
- const x = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
12929
- if (x < 0 || x > canvas.width) return;
13398
+ const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
13399
+ if (x2 < 0 || x2 > canvas.width) return;
12930
13400
  ctx.save();
12931
13401
  ctx.strokeStyle = "#ff004d";
12932
13402
  ctx.lineWidth = 2;
12933
13403
  ctx.beginPath();
12934
- ctx.moveTo(x, 0);
12935
- ctx.lineTo(x, canvas.height);
13404
+ ctx.moveTo(x2, 0);
13405
+ ctx.lineTo(x2, canvas.height);
12936
13406
  ctx.stroke();
12937
13407
  ctx.restore();
12938
13408
  };
@@ -13060,8 +13530,8 @@ var mountDAW = (target, options = {}) => {
13060
13530
  if (maxOffsetX <= 0) return;
13061
13531
  const rect = refs.hScroll.getBoundingClientRect();
13062
13532
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13063
- const x = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13064
- const ratio = x / (rect.width - thumbW);
13533
+ const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13534
+ const ratio = x2 / (rect.width - thumbW);
13065
13535
  currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
13066
13536
  setDrawOffset(currentOffsetX, currentOffsetY);
13067
13537
  redrawAll();
@@ -13088,13 +13558,60 @@ var mountDAW = (target, options = {}) => {
13088
13558
  let selectionStart = null;
13089
13559
  let selectedOriginal = [];
13090
13560
  let lastMultiPreviewPitch = null;
13561
+ const AUTO_SCROLL_MARGIN = 30;
13562
+ const AUTO_SCROLL_MAX_SPEED = 22;
13563
+ let autoScrollRAF = null;
13564
+ let lastMoveEvent = null;
13565
+ const computeEdgeSpeed = (pos, size) => {
13566
+ if (pos < AUTO_SCROLL_MARGIN) {
13567
+ const t = (AUTO_SCROLL_MARGIN - pos) / AUTO_SCROLL_MARGIN;
13568
+ return -Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13569
+ }
13570
+ if (pos > size - AUTO_SCROLL_MARGIN) {
13571
+ const t = (pos - (size - AUTO_SCROLL_MARGIN)) / AUTO_SCROLL_MARGIN;
13572
+ return Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13573
+ }
13574
+ return 0;
13575
+ };
13576
+ const stopAutoScroll = () => {
13577
+ if (autoScrollRAF !== null) {
13578
+ cancelAnimationFrame(autoScrollRAF);
13579
+ autoScrollRAF = null;
13580
+ }
13581
+ lastMoveEvent = null;
13582
+ };
13583
+ const autoScrollTick = () => {
13584
+ autoScrollRAF = null;
13585
+ if (!isSelecting || !lastMoveEvent) return;
13586
+ const canvas = getGridCanvas();
13587
+ const { x: x2, y } = getGridPosition(lastMoveEvent);
13588
+ const dx = computeEdgeSpeed(x2, canvas.width);
13589
+ const dy = computeEdgeSpeed(y, canvas.height);
13590
+ if (dx !== 0 || dy !== 0) {
13591
+ const maxOffsetX = getMaxOffsetX();
13592
+ const maxOffsetY = getMaxOffsetY();
13593
+ currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13594
+ currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13595
+ setDrawOffset(currentOffsetX, currentOffsetY);
13596
+ onPointerMove(lastMoveEvent);
13597
+ }
13598
+ if (isSelecting) {
13599
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13600
+ }
13601
+ };
13602
+ const ensureAutoScroll = (event) => {
13603
+ lastMoveEvent = event;
13604
+ if (autoScrollRAF === null) {
13605
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13606
+ }
13607
+ };
13091
13608
  const playPreview = (pitch) => {
13092
13609
  if (isLoading) return;
13093
13610
  options.onResumeAudio?.();
13094
13611
  const active = getActive();
13095
13612
  dispatchNote(active.config.id, pitch, active.volume, 100, 0, 0.5);
13096
13613
  };
13097
- const findActiveNoteAt = (x, y, margin = 0) => {
13614
+ const findActiveNoteAt = (x2, y, margin = 0) => {
13098
13615
  const active = getActive();
13099
13616
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = renderConfig;
13100
13617
  const offset = getDrawOffset();
@@ -13105,7 +13622,7 @@ var mountDAW = (target, options = {}) => {
13105
13622
  const w = note.durationSteps * stepWidth;
13106
13623
  const renderX = logicalX - offset.x;
13107
13624
  const renderY = logicalY - offset.y;
13108
- if (x >= renderX - margin && x <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
13625
+ if (x2 >= renderX - margin && x2 <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
13109
13626
  return note;
13110
13627
  }
13111
13628
  return null;
@@ -13124,17 +13641,17 @@ var mountDAW = (target, options = {}) => {
13124
13641
  const onGridPointerDown = (event) => {
13125
13642
  event.preventDefault();
13126
13643
  options.onResumeAudio?.();
13127
- const { x, y, step, pitch } = getGridPosition(event);
13644
+ const { x: x2, y, step, pitch } = getGridPosition(event);
13128
13645
  const active = getActive();
13129
13646
  if (activeToolMode === "eraser") {
13130
13647
  if (isActiveLocked()) return;
13131
- const note = findActiveNoteAt(x, y);
13648
+ const note = findActiveNoteAt(x2, y);
13132
13649
  if (note) active.core.deleteNoteById(note.id);
13133
13650
  return;
13134
13651
  }
13135
13652
  if (activeToolMode === "select") {
13136
13653
  if (selectedNotes.length > 0) {
13137
- const clicked2 = findActiveNoteAt(x, y);
13654
+ const clicked2 = findActiveNoteAt(x2, y);
13138
13655
  if (clicked2 && selectedNotes.some((n) => n.id === clicked2.id)) {
13139
13656
  selectedOriginal = selectedNotes.map((n) => ({
13140
13657
  id: n.id,
@@ -13143,7 +13660,7 @@ var mountDAW = (target, options = {}) => {
13143
13660
  }));
13144
13661
  isSelecting = true;
13145
13662
  dragMode = "move";
13146
- selectionStart = { x, y, step, pitch };
13663
+ selectionStart = { x: x2, y, step, pitch };
13147
13664
  hasDragged = false;
13148
13665
  lastMultiPreviewPitch = null;
13149
13666
  return;
@@ -13151,7 +13668,7 @@ var mountDAW = (target, options = {}) => {
13151
13668
  selectedNotes = [];
13152
13669
  selectionRect = null;
13153
13670
  }
13154
- const clicked = findActiveNoteAt(x, y);
13671
+ const clicked = findActiveNoteAt(x2, y);
13155
13672
  if (clicked) {
13156
13673
  selectedNotes = [clicked];
13157
13674
  selectedOriginal = [
@@ -13169,19 +13686,19 @@ var mountDAW = (target, options = {}) => {
13169
13686
  isSelecting = true;
13170
13687
  dragMode = "rect";
13171
13688
  }
13172
- selectionStart = { x, y, step, pitch };
13689
+ selectionStart = { x: x2, y, step, pitch };
13173
13690
  hasDragged = false;
13174
13691
  return;
13175
13692
  }
13176
13693
  hasDragged = false;
13177
- const existing = findActiveNoteAt(x, y, TOUCH_HIT_MARGIN);
13694
+ const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
13178
13695
  if (existing) {
13179
13696
  playPreview(existing.pitch);
13180
13697
  const { stepWidth } = renderConfig;
13181
13698
  const offset = getDrawOffset();
13182
13699
  const renderX = existing.startStep * stepWidth - offset.x;
13183
13700
  const w = existing.durationSteps * stepWidth;
13184
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w) {
13701
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
13185
13702
  dragState = {
13186
13703
  noteId: existing.id,
13187
13704
  mode: "resize",
@@ -13260,12 +13777,13 @@ var mountDAW = (target, options = {}) => {
13260
13777
  return;
13261
13778
  }
13262
13779
  if (activeToolMode === "select" && isSelecting && selectionStart) {
13263
- const { x, y, step, pitch } = getGridPosition(event);
13780
+ ensureAutoScroll(event);
13781
+ const { x: x2, y, step, pitch } = getGridPosition(event);
13264
13782
  if (dragMode === "rect") {
13265
13783
  const rect = {
13266
- x: Math.min(x, selectionStart.x),
13784
+ x: Math.min(x2, selectionStart.x),
13267
13785
  y: Math.min(y, selectionStart.y),
13268
- width: Math.abs(x - selectionStart.x),
13786
+ width: Math.abs(x2 - selectionStart.x),
13269
13787
  height: Math.abs(y - selectionStart.y)
13270
13788
  };
13271
13789
  selectionRect = rect;
@@ -13278,7 +13796,7 @@ var mountDAW = (target, options = {}) => {
13278
13796
  const nx = logicalX - offset.x;
13279
13797
  const ny = logicalY - offset.y;
13280
13798
  const nw = note.durationSteps * stepWidth;
13281
- return rect.x < nx + nw && rect.x + rect.width > nx && rect.y < ny + keyHeight && rect.y + rect.height > ny;
13799
+ return nx >= rect.x && nx + nw <= rect.x + rect.width && ny >= rect.y && ny + keyHeight <= rect.y + rect.height;
13282
13800
  });
13283
13801
  redrawAll();
13284
13802
  } else {
@@ -13335,8 +13853,11 @@ var mountDAW = (target, options = {}) => {
13335
13853
  selectionStart = null;
13336
13854
  hasDragged = false;
13337
13855
  lastMultiPreviewPitch = null;
13338
- selectionRect = null;
13856
+ if (dragMode !== "rect" || selectedNotes.length === 0) {
13857
+ selectionRect = null;
13858
+ }
13339
13859
  selectedOriginal = [];
13860
+ stopAutoScroll();
13340
13861
  redrawAll();
13341
13862
  }
13342
13863
  };
@@ -13484,8 +14005,8 @@ var mountDAW = (target, options = {}) => {
13484
14005
  headerCanvas.addEventListener("click", (event) => {
13485
14006
  if (playbackState === "playing") return;
13486
14007
  const rect = headerCanvas.getBoundingClientRect();
13487
- const x = event.clientX - rect.left;
13488
- const step = Math.floor((x + currentOffsetX) / renderConfig.stepWidth);
14008
+ const x2 = event.clientX - rect.left;
14009
+ const step = Math.floor((x2 + currentOffsetX) / renderConfig.stepWidth);
13489
14010
  playStartStep = Math.max(
13490
14011
  0,
13491
14012
  Math.floor(step / snapGridSteps) * snapGridSteps
@@ -13606,11 +14127,11 @@ var mountDAW = (target, options = {}) => {
13606
14127
  const semis = (lt.octave ?? 0) * 12;
13607
14128
  const count = Math.min(sorted.length, lt.syllables.length);
13608
14129
  const notes = [];
13609
- for (let i = 0; i < count; i++) {
13610
- const n = sorted[i];
14130
+ for (let i2 = 0; i2 < count; i2++) {
14131
+ const n = sorted[i2];
13611
14132
  if (n.startStep < fromStep) continue;
13612
14133
  notes.push({
13613
- syllable: lt.syllables[i],
14134
+ syllable: lt.syllables[i2],
13614
14135
  pitch: n.pitch + semis,
13615
14136
  startSec: (n.startStep - fromStep) * secondsPerStep,
13616
14137
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -13621,6 +14142,10 @@ var mountDAW = (target, options = {}) => {
13621
14142
  model: lt.model,
13622
14143
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
13623
14144
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
14145
+ vibrato: lt.vibrato,
14146
+ reverbSend: (lt.reverb ?? 0) / 100,
14147
+ gender: (lt.gender ?? 50) / 100,
14148
+ breathiness: (lt.breathiness ?? 50) / 100,
13624
14149
  notes
13625
14150
  };
13626
14151
  }) : [];
@@ -13709,13 +14234,13 @@ var mountDAW = (target, options = {}) => {
13709
14234
  const updateTrackPanel = () => {
13710
14235
  refs.trackTabs.innerHTML = "";
13711
14236
  trackPillEls.clear();
13712
- for (const [i, t] of trackStates.entries()) {
14237
+ for (const [i2, t] of trackStates.entries()) {
13713
14238
  const [r, g, b] = t.config.color;
13714
14239
  const btn = document.createElement("button");
13715
14240
  btn.className = `dtm-pill ${t.config.id === activeTrackId ? "dtm-pill--active" : ""}`;
13716
14241
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
13717
14242
  btn.title = t.config.name;
13718
- btn.textContent = String(i + 1);
14243
+ btn.textContent = String(i2 + 1);
13719
14244
  btn.addEventListener("click", () => switchTrack(t.config.id));
13720
14245
  refs.trackTabs.appendChild(btn);
13721
14246
  trackPillEls.set(t.config.id, btn);
@@ -13726,7 +14251,22 @@ var mountDAW = (target, options = {}) => {
13726
14251
  <span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
13727
14252
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
13728
14253
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
13729
- </div>`;
14254
+ </div>
14255
+ <details class="dtm-advanced" data-dtm="track-fx-advanced">
14256
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14257
+ <div class="dtm-row">
14258
+ <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14259
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-comp" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u91CF\uFF08\u97F3\u5727\u5F37\u5316\uFF09">
14260
+ <span class="dtm-label" data-dtm="track-comp-label"></span>
14261
+ <button class="dtm-infobtn" data-dtm="track-comp-info" title="\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14262
+ </div>
14263
+ <div class="dtm-row">
14264
+ <span class="dtm-label">\u30B9\u30C6\u30EC\u30AA\u5E45</span>
14265
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-width" min="0" max="200" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45\uFF08100=\u539F\u97F3\u30010=\u30E2\u30CE\u30E9\u30EB\uFF09">
14266
+ <span class="dtm-label" data-dtm="track-width-label"></span>
14267
+ <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14268
+ </div>
14269
+ </details>`;
13730
14270
  const volInput = refs.trackBody.querySelector(
13731
14271
  '[data-dtm="track-vol"]'
13732
14272
  );
@@ -13743,6 +14283,47 @@ var mountDAW = (target, options = {}) => {
13743
14283
  volInput.title = active.lyricModel ? "\u6B4C\u8A5E\u30E2\u30FC\u30C9\u306E\u3068\u304D\u306F\u30D9\u30ED\u30B7\u30C6\u30A3\u304C\u518D\u751F\u306B\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF08\u58F0\u91CF\u3067\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
13744
14284
  };
13745
14285
  syncVelocityDisabled();
14286
+ const trackCompInput = refs.trackBody.querySelector(
14287
+ '[data-dtm="track-comp"]'
14288
+ );
14289
+ const trackCompLabel = refs.trackBody.querySelector(
14290
+ '[data-dtm="track-comp-label"]'
14291
+ );
14292
+ const trackWidthInput = refs.trackBody.querySelector(
14293
+ '[data-dtm="track-width"]'
14294
+ );
14295
+ const trackWidthLabel = refs.trackBody.querySelector(
14296
+ '[data-dtm="track-width-label"]'
14297
+ );
14298
+ const trackCompInfo = refs.trackBody.querySelector(
14299
+ '[data-dtm="track-comp-info"]'
14300
+ );
14301
+ const trackWidthInfo = refs.trackBody.querySelector(
14302
+ '[data-dtm="track-width-info"]'
14303
+ );
14304
+ trackCompInput.value = String(active.trackCompression);
14305
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14306
+ trackWidthInput.value = String(active.trackWidth);
14307
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14308
+ trackCompInput.addEventListener("input", () => {
14309
+ active.trackCompression = Number.parseInt(trackCompInput.value, 10);
14310
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14311
+ options.onTrackCompressionChange?.(
14312
+ active.config.id,
14313
+ active.trackCompression
14314
+ );
14315
+ });
14316
+ trackWidthInput.addEventListener("input", () => {
14317
+ active.trackWidth = Number.parseInt(trackWidthInput.value, 10);
14318
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14319
+ options.onTrackWidthChange?.(active.config.id, active.trackWidth);
14320
+ });
14321
+ trackCompInfo.addEventListener("click", () => {
14322
+ showModal("\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC", TRACK_COMPRESSION_INFO_HTML);
14323
+ });
14324
+ trackWidthInfo.addEventListener("click", () => {
14325
+ showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14326
+ });
13746
14327
  const instRow = document.createElement("div");
13747
14328
  instRow.className = "dtm-row";
13748
14329
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -13806,13 +14387,6 @@ var mountDAW = (target, options = {}) => {
13806
14387
  <span class="dtm-label">\u266A UTAU</span>
13807
14388
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
13808
14389
  <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
13809
- <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
13810
- <option value="2">+2 oct</option>
13811
- <option value="1">+1 oct</option>
13812
- <option value="0">\xB10 oct</option>
13813
- <option value="-1">-1 oct</option>
13814
- <option value="-2">-2 oct</option>
13815
- </select>
13816
14390
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
13817
14391
  </div>
13818
14392
  <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
@@ -13838,11 +14412,48 @@ var mountDAW = (target, options = {}) => {
13838
14412
  <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A200\uFF09">
13839
14413
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
13840
14414
  </div>
13841
- <div class="dtm-row">
13842
- <span class="dtm-label">\u5B9A\u4F4D</span>
13843
- <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-pan" min="0" max="127" aria-label="\u6B4C\u5531\u306E\u30B9\u30C6\u30EC\u30AA\u5B9A\u4F4D\uFF08\u5DE6\u53F3\uFF09">
13844
- <span class="dtm-label" data-dtm="lyric-pan-label"></span>
13845
- </div>
14415
+ <details class="dtm-advanced" data-dtm="lyric-advanced">
14416
+ <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14417
+ <div class="dtm-row">
14418
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
14419
+ <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
14420
+ <option value="2">+2 oct</option>
14421
+ <option value="1">+1 oct</option>
14422
+ <option value="0">\xB10 oct</option>
14423
+ <option value="-1">-1 oct</option>
14424
+ <option value="-2">-2 oct</option>
14425
+ </select>
14426
+ </div>
14427
+ <div class="dtm-row">
14428
+ <span class="dtm-label">\u5B9A\u4F4D</span>
14429
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-pan" min="0" max="127" aria-label="\u6B4C\u5531\u306E\u30B9\u30C6\u30EC\u30AA\u5B9A\u4F4D\uFF08\u5DE6\u53F3\uFF09">
14430
+ <span class="dtm-label" data-dtm="lyric-pan-label"></span>
14431
+ </div>
14432
+ <div class="dtm-row">
14433
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14434
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-reverb" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u9001\u308B\u91CF\uFF08\u30DE\u30B9\u30BF\u306E\u30EA\u30D0\u30FC\u30D6\u3064\u307E\u307F\u304C0%\u3060\u3068\u7121\u97F3\uFF09">
14435
+ <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14436
+ <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14437
+ </div>
14438
+ <div class="dtm-row">
14439
+ <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14440
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-gender" min="0" max="100" aria-label="\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC\u30D5\u30A1\u30AF\u30BF\u30FC\uFF08koe\u97F3\u6E90\u9650\u5B9A\uFF09">
14441
+ <span class="dtm-label" data-dtm="lyric-gender-label"></span>
14442
+ <button class="dtm-infobtn" data-dtm="lyric-gender-info" title="\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14443
+ </div>
14444
+ <div class="dtm-row">
14445
+ <span class="dtm-label">\u30D6\u30EC\u30B7\u30CD\u30B9</span>
14446
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-breathiness" min="0" max="100" aria-label="\u30D6\u30EC\u30B7\u30CD\u30B9\uFF08\u606F\u6210\u5206\u3001koe\u97F3\u6E90\u9650\u5B9A\uFF09">
14447
+ <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
14448
+ <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14449
+ </div>
14450
+ <div class="dtm-row">
14451
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14452
+ <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
14453
+ </span>
14454
+ <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14455
+ </div>
14456
+ </details>
13846
14457
  <textarea class="dtm-textarea" data-dtm="lyric-input" rows="2" placeholder="\u3072\u3089\u304C\u306A\u30FB\u30AB\u30BF\u30AB\u30CA\u3067\u6B4C\u8A5E\uFF08\u4F8B: \u3069\u308C\u307F\u3075\u3041\u305D\u3089\u3057\u3069\uFF09"></textarea>
13847
14458
  </div>`;
13848
14459
  refs.trackBody.appendChild(lyricDiv);
@@ -13876,6 +14487,51 @@ var mountDAW = (target, options = {}) => {
13876
14487
  const lyricPanLabel = lyricDiv.querySelector(
13877
14488
  '[data-dtm="lyric-pan-label"]'
13878
14489
  );
14490
+ const lyricReverb = lyricDiv.querySelector(
14491
+ '[data-dtm="lyric-reverb"]'
14492
+ );
14493
+ const lyricReverbLabel = lyricDiv.querySelector(
14494
+ '[data-dtm="lyric-reverb-label"]'
14495
+ );
14496
+ const lyricReverbInfo = lyricDiv.querySelector(
14497
+ '[data-dtm="lyric-reverb-info"]'
14498
+ );
14499
+ lyricReverbInfo.addEventListener("click", () => {
14500
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14501
+ });
14502
+ const lyricGender = lyricDiv.querySelector(
14503
+ '[data-dtm="lyric-gender"]'
14504
+ );
14505
+ const lyricGenderLabel = lyricDiv.querySelector(
14506
+ '[data-dtm="lyric-gender-label"]'
14507
+ );
14508
+ const lyricGenderInfo = lyricDiv.querySelector(
14509
+ '[data-dtm="lyric-gender-info"]'
14510
+ );
14511
+ lyricGenderInfo.addEventListener("click", () => {
14512
+ showModal("\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC", GENDER_INFO_HTML);
14513
+ });
14514
+ const lyricBreathiness = lyricDiv.querySelector(
14515
+ '[data-dtm="lyric-breathiness"]'
14516
+ );
14517
+ const lyricBreathinessLabel = lyricDiv.querySelector(
14518
+ '[data-dtm="lyric-breathiness-label"]'
14519
+ );
14520
+ const lyricBreathinessInfo = lyricDiv.querySelector(
14521
+ '[data-dtm="lyric-breathiness-info"]'
14522
+ );
14523
+ lyricBreathinessInfo.addEventListener("click", () => {
14524
+ showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
14525
+ });
14526
+ const lyricVibrato = lyricDiv.querySelector(
14527
+ '[data-dtm="lyric-vibrato"]'
14528
+ );
14529
+ const lyricVibratoInfo = lyricDiv.querySelector(
14530
+ '[data-dtm="lyric-vibrato-info"]'
14531
+ );
14532
+ lyricVibratoInfo.addEventListener("click", () => {
14533
+ showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14534
+ });
13879
14535
  const lyricTerms = lyricDiv.querySelector(
13880
14536
  '[data-dtm="lyric-terms"]'
13881
14537
  );
@@ -13937,6 +14593,13 @@ var mountDAW = (target, options = {}) => {
13937
14593
  lyricVolLabel.textContent = String(active.vocalVolume);
13938
14594
  lyricPan.value = String(active.vocalPan);
13939
14595
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14596
+ lyricReverb.value = String(active.vocalReverb);
14597
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14598
+ lyricGender.value = String(active.vocalGender);
14599
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14600
+ lyricBreathiness.value = String(active.vocalBreathiness);
14601
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14602
+ lyricVibrato.checked = active.vocalVibrato;
13940
14603
  const updateLyricCount = () => {
13941
14604
  const n = normalizeLyrics(lyricInput.value).length;
13942
14605
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -13986,7 +14649,6 @@ var mountDAW = (target, options = {}) => {
13986
14649
  };
13987
14650
  const syncLyricVisibility = () => {
13988
14651
  lyricBody.style.display = active.lyricModel ? "" : "none";
13989
- lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
13990
14652
  updateLyricCount();
13991
14653
  syncLyricTerms();
13992
14654
  syncLyricIcon();
@@ -14055,6 +14717,10 @@ var mountDAW = (target, options = {}) => {
14055
14717
  active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
14056
14718
  fireLyricsChange(active);
14057
14719
  });
14720
+ lyricVibrato.addEventListener("change", () => {
14721
+ active.vocalVibrato = lyricVibrato.checked;
14722
+ fireLyricsChange(active);
14723
+ });
14058
14724
  lyricInput.addEventListener("input", () => {
14059
14725
  active.lyrics = lyricInput.value;
14060
14726
  updateLyricCount();
@@ -14078,6 +14744,21 @@ var mountDAW = (target, options = {}) => {
14078
14744
  lyricPanLabel.textContent = fmtPan(64);
14079
14745
  fireLyricsChange(active);
14080
14746
  });
14747
+ lyricReverb.addEventListener("input", () => {
14748
+ active.vocalReverb = Number.parseInt(lyricReverb.value, 10);
14749
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14750
+ fireLyricsChange(active);
14751
+ });
14752
+ lyricGender.addEventListener("input", () => {
14753
+ active.vocalGender = Number.parseInt(lyricGender.value, 10);
14754
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14755
+ fireLyricsChange(active);
14756
+ });
14757
+ lyricBreathiness.addEventListener("input", () => {
14758
+ active.vocalBreathiness = Number.parseInt(lyricBreathiness.value, 10);
14759
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14760
+ fireLyricsChange(active);
14761
+ });
14081
14762
  }
14082
14763
  if (active.config.id === "chord" && showChord) {
14083
14764
  const div = document.createElement("div");
@@ -14161,18 +14842,28 @@ var mountDAW = (target, options = {}) => {
14161
14842
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
14162
14843
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
14163
14844
  const trackInstrumentsForMeta = {};
14164
- trackStates.forEach((t, i) => {
14165
- if (t.trackInstrument) trackInstrumentsForMeta[i] = t.trackInstrument;
14845
+ const trackCompressionForMeta = {};
14846
+ const trackWidthForMeta = {};
14847
+ trackStates.forEach((t, i2) => {
14848
+ if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14849
+ if (t.trackCompression !== 0)
14850
+ trackCompressionForMeta[i2] = t.trackCompression;
14851
+ if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
14166
14852
  });
14167
14853
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14854
+ const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14855
+ const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
14168
14856
  const metaLineFull = formatMmlMeta(
14169
14857
  {
14170
14858
  instrument: currentInstrument || void 0,
14171
14859
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14172
14860
  volume: masterVolume,
14173
14861
  drumVolume,
14862
+ reverb: reverbAmount,
14174
14863
  mode,
14175
- trackInstruments: trackInstMeta
14864
+ trackInstruments: trackInstMeta,
14865
+ trackCompression: trackCompMeta,
14866
+ trackWidth: trackWidthMeta
14176
14867
  },
14177
14868
  " "
14178
14869
  );
@@ -14182,8 +14873,11 @@ var mountDAW = (target, options = {}) => {
14182
14873
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14183
14874
  volume: masterVolume,
14184
14875
  drumVolume,
14876
+ reverb: reverbAmount,
14185
14877
  mode,
14186
- trackInstruments: trackInstMeta
14878
+ trackInstruments: trackInstMeta,
14879
+ trackCompression: trackCompMeta,
14880
+ trackWidth: trackWidthMeta
14187
14881
  },
14188
14882
  ""
14189
14883
  );
@@ -14197,10 +14891,10 @@ var mountDAW = (target, options = {}) => {
14197
14891
  const monoTracks = decomposeToMonophonic(allNotes);
14198
14892
  const refCore = trackStates[0].core;
14199
14893
  const decomposedFull = monoTracks.map(
14200
- (notes, i) => `@${i} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14894
+ (notes, i2) => `@${i2} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14201
14895
  );
14202
14896
  const decomposedMini = monoTracks.map(
14203
- (notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14897
+ (notes, i2) => `@${i2}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14204
14898
  );
14205
14899
  const full2 = [metaLineFull, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
14206
14900
  const minified2 = [metaLineMini, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
@@ -14214,34 +14908,42 @@ var mountDAW = (target, options = {}) => {
14214
14908
  }
14215
14909
  const trackLines = [];
14216
14910
  const trackLinesMini = [];
14217
- trackStates.forEach((t, i) => {
14911
+ trackStates.forEach((t, i2) => {
14218
14912
  const notes = clipNotes(t.core.getNotes());
14219
14913
  if (notes.length > 0) {
14220
14914
  const mml = t.core.getMMLFromNotes(notes, bpm, t.volume).trim();
14221
- trackLines.push(`@${i} ${mml}`);
14222
- trackLinesMini.push(`@${i}${mml.replace(/\s+/g, "")}`);
14915
+ trackLines.push(`@${i2} ${mml}`);
14916
+ trackLinesMini.push(`@${i2}${mml.replace(/\s+/g, "")}`);
14223
14917
  }
14224
14918
  });
14225
- const lyricLines = trackStates.map((t, i) => ({
14226
- i,
14919
+ const lyricLines = trackStates.map((t, i2) => ({
14920
+ i: i2,
14227
14921
  notes: clipNotes(t.core.getNotes()),
14228
14922
  text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
14229
14923
  model: t.lyricModel.trim(),
14230
14924
  vol: t.vocalVolume,
14231
14925
  gate: t.vocalGate,
14232
14926
  pan: t.vocalPan,
14233
- oct: t.vocalOctave
14927
+ oct: t.vocalOctave,
14928
+ vib: t.vocalVibrato,
14929
+ rev: t.vocalReverb,
14930
+ gen: t.vocalGender,
14931
+ bre: t.vocalBreathiness
14234
14932
  })).filter(
14235
- (x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
14236
- ).map((x) => {
14933
+ (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14934
+ ).map((x2) => {
14237
14935
  const params = [
14238
- x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
14239
- x.gate === 100 ? "" : `q${x.gate}`,
14240
- x.pan === 64 ? "" : `p${x.pan}`,
14241
- x.oct === 0 ? "" : `o${x.oct}`
14936
+ x2.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x2.vol}`,
14937
+ x2.gate === 100 ? "" : `q${x2.gate}`,
14938
+ x2.pan === 64 ? "" : `p${x2.pan}`,
14939
+ x2.oct === 0 ? "" : `o${x2.oct}`,
14940
+ x2.vib ? "b1" : "",
14941
+ x2.rev === 0 ? "" : `r${x2.rev}`,
14942
+ x2.gen === 50 ? "" : `g${x2.gen}`,
14943
+ x2.bre === 50 ? "" : `h${x2.bre}`
14242
14944
  ].filter((s) => s.length > 0).join(" ");
14243
- const head = params ? `${x.model} ${params}` : x.model;
14244
- return `@@${x.i} ${head} ${x.text}`;
14945
+ const head = params ? `${x2.model} ${params}` : x2.model;
14946
+ return `@@${x2.i} ${head} ${x2.text}`;
14245
14947
  });
14246
14948
  const customVocalDecls = [];
14247
14949
  for (const [key, def] of customVocalsMap) {
@@ -14378,18 +15080,37 @@ var mountDAW = (target, options = {}) => {
14378
15080
  refs.drumVolume.value = String(meta.drumVolume);
14379
15081
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
14380
15082
  }
15083
+ if (meta.reverb !== void 0) {
15084
+ reverbAmount = meta.reverb;
15085
+ refs.reverbAmount.value = String(meta.reverb);
15086
+ refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
15087
+ options.onReverbChange?.(meta.reverb);
15088
+ }
14381
15089
  }
14382
- trackStates.forEach((t, i) => {
14383
- if (applyActiveOnly && i !== activeTrackIndex) return;
14384
- const name = normalizeInstrumentName(meta.trackInstruments?.[i] ?? "");
15090
+ trackStates.forEach((t, i2) => {
15091
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
15092
+ const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
14385
15093
  if (t.trackInstrument !== name) {
14386
15094
  t.trackInstrument = name;
14387
- options.onTrackInstrumentChange?.(i, name);
15095
+ options.onTrackInstrumentChange?.(i2, name);
15096
+ }
15097
+ });
15098
+ trackStates.forEach((t, i2) => {
15099
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
15100
+ const comp = meta.trackCompression?.[i2] ?? 0;
15101
+ if (t.trackCompression !== comp) {
15102
+ t.trackCompression = comp;
15103
+ options.onTrackCompressionChange?.(t.config.id, comp);
15104
+ }
15105
+ const width = meta.trackWidth?.[i2] ?? 100;
15106
+ if (t.trackWidth !== width) {
15107
+ t.trackWidth = width;
15108
+ options.onTrackWidthChange?.(t.config.id, width);
14388
15109
  }
14389
15110
  });
14390
- trackStates.forEach((t, i) => {
14391
- if (applyActiveOnly && i !== activeTrackIndex) return;
14392
- const v = trackVelocity.get(i);
15111
+ trackStates.forEach((t, i2) => {
15112
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
15113
+ const v = trackVelocity.get(i2);
14393
15114
  if (v !== void 0 && v !== t.volume) {
14394
15115
  t.volume = v;
14395
15116
  t.core.setVolume(v);
@@ -14403,6 +15124,10 @@ var mountDAW = (target, options = {}) => {
14403
15124
  active.vocalGate = 100;
14404
15125
  active.vocalPan = 64;
14405
15126
  active.vocalOctave = 0;
15127
+ active.vocalVibrato = false;
15128
+ active.vocalReverb = 0;
15129
+ active.vocalGender = 50;
15130
+ active.vocalBreathiness = 50;
14406
15131
  } else {
14407
15132
  for (const t of trackStates) {
14408
15133
  t.lyrics = "";
@@ -14411,6 +15136,10 @@ var mountDAW = (target, options = {}) => {
14411
15136
  t.vocalGate = 100;
14412
15137
  t.vocalPan = 64;
14413
15138
  t.vocalOctave = 0;
15139
+ t.vocalVibrato = false;
15140
+ t.vocalReverb = 0;
15141
+ t.vocalGender = 50;
15142
+ t.vocalBreathiness = 50;
14414
15143
  }
14415
15144
  }
14416
15145
  lyrics?.forEach((lt) => {
@@ -14423,6 +15152,10 @@ var mountDAW = (target, options = {}) => {
14423
15152
  t.vocalGate = lt.gate;
14424
15153
  t.vocalPan = lt.pan;
14425
15154
  t.vocalOctave = lt.octave ?? 0;
15155
+ t.vocalVibrato = lt.vibrato ?? false;
15156
+ t.vocalReverb = lt.reverb ?? 0;
15157
+ t.vocalGender = lt.gender ?? 50;
15158
+ t.vocalBreathiness = lt.breathiness ?? 50;
14426
15159
  });
14427
15160
  for (const p of placements) {
14428
15161
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -14501,6 +15234,10 @@ var mountDAW = (target, options = {}) => {
14501
15234
  active.vocalGate = 100;
14502
15235
  active.vocalPan = 64;
14503
15236
  active.vocalOctave = 0;
15237
+ active.vocalVibrato = false;
15238
+ active.vocalReverb = 0;
15239
+ active.vocalGender = 50;
15240
+ active.vocalBreathiness = 50;
14504
15241
  } else {
14505
15242
  clearAll();
14506
15243
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -14511,6 +15248,10 @@ var mountDAW = (target, options = {}) => {
14511
15248
  t.vocalGate = 100;
14512
15249
  t.vocalPan = 64;
14513
15250
  t.vocalOctave = 0;
15251
+ t.vocalVibrato = false;
15252
+ t.vocalReverb = 0;
15253
+ t.vocalGender = 50;
15254
+ t.vocalBreathiness = 50;
14514
15255
  }
14515
15256
  }
14516
15257
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -14645,6 +15386,12 @@ var mountDAW = (target, options = {}) => {
14645
15386
  isSolo = refs.soloCheckbox.checked;
14646
15387
  redrawAll();
14647
15388
  });
15389
+ unsubscribeClip = options.clipMeter?.onClipChange((clipping) => {
15390
+ refs.clipBadge.classList.toggle("dtm-hidden", !clipping);
15391
+ });
15392
+ refs.clipBadge.addEventListener("click", () => {
15393
+ options.clipMeter?.reset();
15394
+ });
14648
15395
  refs.toolPen.addEventListener("click", () => setToolMode("pen"));
14649
15396
  refs.toolSelect.addEventListener("click", () => setToolMode("select"));
14650
15397
  refs.toolEraser.addEventListener("click", () => setToolMode("eraser"));
@@ -14720,6 +15467,35 @@ var mountDAW = (target, options = {}) => {
14720
15467
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
14721
15468
  options.singingVoices?.setVolume(masterVolume / 100);
14722
15469
  });
15470
+ refs.reverbAmount.addEventListener("input", () => {
15471
+ reverbAmount = Number.parseInt(refs.reverbAmount.value, 10) || 0;
15472
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
15473
+ options.onReverbChange?.(reverbAmount);
15474
+ });
15475
+ refs.reverbAmountInfoBtn.addEventListener("click", () => {
15476
+ showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15477
+ });
15478
+ refs.autoMasterInfoBtn.addEventListener("click", () => {
15479
+ showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15480
+ });
15481
+ refs.autoMasterBtn.addEventListener("click", () => {
15482
+ reverbAmount = 20;
15483
+ refs.reverbAmount.value = "20";
15484
+ refs.reverbAmountLabel.textContent = "20%";
15485
+ options.onReverbChange?.(20);
15486
+ for (const t of trackStates) {
15487
+ t.trackCompression = 35;
15488
+ t.trackWidth = 115;
15489
+ options.onTrackCompressionChange?.(t.config.id, 35);
15490
+ options.onTrackWidthChange?.(t.config.id, 115);
15491
+ if (t.lyricModel) {
15492
+ t.vocalVibrato = true;
15493
+ t.vocalReverb = 25;
15494
+ fireLyricsChange(t);
15495
+ }
15496
+ }
15497
+ updateTrackPanel();
15498
+ });
14723
15499
  refs.drumSelect.addEventListener("change", () => {
14724
15500
  currentDrumPattern = refs.drumSelect.value;
14725
15501
  options.onDrumChange?.(currentDrumPattern);
@@ -15000,7 +15776,7 @@ var mountDAW = (target, options = {}) => {
15000
15776
  console.error(e);
15001
15777
  }
15002
15778
  refs.midiTrackSelection.innerHTML = `<span class="dtm-label">\u30C8\u30E9\u30C3\u30AF</span>`;
15003
- detectedTracks.forEach((t, i) => {
15779
+ detectedTracks.forEach((t, i2) => {
15004
15780
  const btn = document.createElement("button");
15005
15781
  btn.className = `dtm-btn ${t.selected ? "dtm-btn--primary" : "dtm-btn--ghost"}`;
15006
15782
  btn.dataset.selected = String(t.selected);
@@ -15012,7 +15788,7 @@ var mountDAW = (target, options = {}) => {
15012
15788
  btn.classList.toggle("dtm-btn--ghost", !on);
15013
15789
  });
15014
15790
  refs.midiTrackSelection.appendChild(btn);
15015
- if (i === 0) refs.midiTrackSelection.dataset.ready = "1";
15791
+ if (i2 === 0) refs.midiTrackSelection.dataset.ready = "1";
15016
15792
  });
15017
15793
  refs.midiTrackSelection.classList.remove("dtm-hidden");
15018
15794
  refs.overlay.hidden = true;
@@ -15022,9 +15798,9 @@ var mountDAW = (target, options = {}) => {
15022
15798
  if (!pendingMidi) return;
15023
15799
  const selected = [];
15024
15800
  const btns = refs.midiTrackSelection.querySelectorAll("button");
15025
- btns.forEach((b, i) => {
15801
+ btns.forEach((b, i2) => {
15026
15802
  if (b.dataset.selected === "true")
15027
- selected.push(detectedTracks[i].index);
15803
+ selected.push(detectedTracks[i2].index);
15028
15804
  });
15029
15805
  if (selected.length === 0) return;
15030
15806
  if (!isAdvanced && options.onRequestAdvancedMode && selected.length > trackStates.length) {
@@ -15058,7 +15834,7 @@ var mountDAW = (target, options = {}) => {
15058
15834
  const refCore = trackStates[0].core;
15059
15835
  const tempo = Math.round(parsedBpm) || bpm;
15060
15836
  const lines = [];
15061
- let i = 0;
15837
+ let i2 = 0;
15062
15838
  for (const notes of byTrack.values()) {
15063
15839
  const asNotes = notes.map((p) => ({
15064
15840
  id: 0,
@@ -15068,8 +15844,8 @@ var mountDAW = (target, options = {}) => {
15068
15844
  velocity: p.velocity
15069
15845
  }));
15070
15846
  const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
15071
- lines.push(`@${i} ${mml}`);
15072
- i++;
15847
+ lines.push(`@${i2} ${mml}`);
15848
+ i2++;
15073
15849
  }
15074
15850
  return [...lines, MML_END_MARKER].join(";\n");
15075
15851
  };
@@ -15684,6 +16460,12 @@ var mountDAW = (target, options = {}) => {
15684
16460
  refs.drumVolume.value = String(drumVolume);
15685
16461
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
15686
16462
  },
16463
+ setReverbAmount: (amount) => {
16464
+ reverbAmount = clamp3(amount, 0, 100);
16465
+ refs.reverbAmount.value = String(reverbAmount);
16466
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16467
+ options.onReverbChange?.(reverbAmount);
16468
+ },
15687
16469
  applyPatch: (trackId, added, removed) => {
15688
16470
  const track = trackStates.find((t) => t.config.id === trackId);
15689
16471
  if (!track) return;
@@ -15723,6 +16505,10 @@ var mountDAW = (target, options = {}) => {
15723
16505
  t.vocalGate = data.vocalGate;
15724
16506
  t.vocalPan = data.vocalPan;
15725
16507
  t.vocalOctave = data.vocalOctave;
16508
+ t.vocalVibrato = data.vocalVibrato ?? false;
16509
+ t.vocalReverb = data.vocalReverb ?? 0;
16510
+ t.vocalGender = data.vocalGender ?? 50;
16511
+ t.vocalBreathiness = data.vocalBreathiness ?? 50;
15726
16512
  },
15727
16513
  applyTrackInstrument: (trackIndex, instrumentName) => {
15728
16514
  const t = trackStates[trackIndex];
@@ -15733,21 +16519,22 @@ var mountDAW = (target, options = {}) => {
15733
16519
  },
15734
16520
  noteToCanvas: (step, pitch) => {
15735
16521
  const canvas = getGridCanvas();
15736
- const x = step * renderConfig.stepWidth - currentOffsetX;
16522
+ const x2 = step * renderConfig.stepWidth - currentOffsetX;
15737
16523
  const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
15738
- const onScreen = x >= 0 && x <= canvas.width && y >= 0 && y <= canvas.height;
16524
+ const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
15739
16525
  let side = null;
15740
16526
  if (!onScreen) {
15741
- if (x < 0) side = "left";
15742
- else if (x > canvas.width) side = "right";
16527
+ if (x2 < 0) side = "left";
16528
+ else if (x2 > canvas.width) side = "right";
15743
16529
  else if (y < 0) side = "top";
15744
16530
  else side = "bottom";
15745
16531
  }
15746
- return { x, y, onScreen, side };
16532
+ return { x: x2, y, onScreen, side };
15747
16533
  },
15748
16534
  destroy: () => {
15749
16535
  sequencer.stop();
15750
16536
  options.singingVoices?.stopStream();
16537
+ unsubscribeClip?.();
15751
16538
  resizeObserver.disconnect();
15752
16539
  document.removeEventListener("pointermove", onPointerMove);
15753
16540
  document.removeEventListener("pointerup", onPointerUp);
@@ -15823,11 +16610,11 @@ var playSingingMML = async (mml, options = {}) => {
15823
16610
  const semis = (lt.octave ?? 0) * 12;
15824
16611
  const count = Math.min(sorted.length, lt.syllables.length);
15825
16612
  const notes = [];
15826
- for (let i = 0; i < count; i++) {
15827
- const n = sorted[i];
16613
+ for (let i2 = 0; i2 < count; i2++) {
16614
+ const n = sorted[i2];
15828
16615
  if (n.startStep < fromStep) continue;
15829
16616
  notes.push({
15830
- syllable: lt.syllables[i],
16617
+ syllable: lt.syllables[i2],
15831
16618
  pitch: n.pitch + semis,
15832
16619
  startSec: (n.startStep - fromStep) * secondsPerStep,
15833
16620
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -15838,6 +16625,10 @@ var playSingingMML = async (mml, options = {}) => {
15838
16625
  model: lt.model,
15839
16626
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
15840
16627
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16628
+ vibrato: lt.vibrato,
16629
+ reverbSend: (lt.reverb ?? 0) / 100,
16630
+ gender: (lt.gender ?? 50) / 100,
16631
+ breathiness: (lt.breathiness ?? 50) / 100,
15841
16632
  notes
15842
16633
  };
15843
16634
  });
@@ -16052,7 +16843,7 @@ var createPianoRoll = (options, handlers) => {
16052
16843
  let dragState = null;
16053
16844
  let hasDragged = false;
16054
16845
  let lastPreviewPitch = null;
16055
- const findNoteAtPosition = (x, y) => {
16846
+ const findNoteAtPosition = (x2, y) => {
16056
16847
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
16057
16848
  const offset = getDrawOffset();
16058
16849
  for (const note of core.getNotes()) {
@@ -16063,7 +16854,7 @@ var createPianoRoll = (options, handlers) => {
16063
16854
  const h = keyHeight;
16064
16855
  const renderX = logicalX - offset.x;
16065
16856
  const renderY = logicalY - offset.y;
16066
- if (x >= renderX && x <= renderX + w && y >= renderY && y <= renderY + h) {
16857
+ if (x2 >= renderX && x2 <= renderX + w && y >= renderY && y <= renderY + h) {
16067
16858
  return note;
16068
16859
  }
16069
16860
  }
@@ -16071,10 +16862,10 @@ var createPianoRoll = (options, handlers) => {
16071
16862
  };
16072
16863
  const handlePointerMove = (e) => {
16073
16864
  if (core.getToolMode() === "select" && isSelecting && selectionStart) {
16074
- const { x, y } = getGridPosition(e);
16075
- const minX = Math.min(x, selectionStart.x);
16865
+ const { x: x2, y } = getGridPosition(e);
16866
+ const minX = Math.min(x2, selectionStart.x);
16076
16867
  const minY = Math.min(y, selectionStart.y);
16077
- const width2 = Math.abs(x - selectionStart.x);
16868
+ const width2 = Math.abs(x2 - selectionStart.x);
16078
16869
  const height2 = Math.abs(y - selectionStart.y);
16079
16870
  selectionRect = { x: minX, y: minY, width: width2, height: height2 };
16080
16871
  selectedNotes = getNotesInRect(selectionRect);
@@ -16132,10 +16923,10 @@ var createPianoRoll = (options, handlers) => {
16132
16923
  }
16133
16924
  };
16134
16925
  gridCanvas.addEventListener("pointerdown", (e) => {
16135
- const { x, y, step, pitch } = getGridPosition(e);
16926
+ const { x: x2, y, step, pitch } = getGridPosition(e);
16136
16927
  const currentMode = core.getToolMode();
16137
16928
  if (currentMode === "select") {
16138
- const clickedNote = findNoteAtPosition(x, y);
16929
+ const clickedNote = findNoteAtPosition(x2, y);
16139
16930
  if (selectionRect && clickedNote) {
16140
16931
  const notesInRect = getNotesInRect(selectionRect);
16141
16932
  if (notesInRect.some((n) => n.id === clickedNote.id)) {
@@ -16156,10 +16947,10 @@ var createPianoRoll = (options, handlers) => {
16156
16947
  selectedNotes = [];
16157
16948
  selectionRect = null;
16158
16949
  isSelecting = true;
16159
- selectionStart = { x, y, step, pitch };
16950
+ selectionStart = { x: x2, y, step, pitch };
16160
16951
  return;
16161
16952
  }
16162
- const note = findNoteAtPosition(x, y);
16953
+ const note = findNoteAtPosition(x2, y);
16163
16954
  if (!note) return;
16164
16955
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
16165
16956
  const offset = getDrawOffset();
@@ -16169,7 +16960,7 @@ var createPianoRoll = (options, handlers) => {
16169
16960
  const renderX = logicalX - offset.x;
16170
16961
  const renderY = logicalY - offset.y;
16171
16962
  const w = note.durationSteps * stepWidth;
16172
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16963
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16173
16964
  dragState = {
16174
16965
  noteId: note.id,
16175
16966
  mode: "resize",
@@ -16445,6 +17236,166 @@ var isSupported = midiJsonParser.isSupported;
16445
17236
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
16446
17237
  URL.revokeObjectURL(url);
16447
17238
 
17239
+ // src/channel-strip.ts
17240
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17241
+ var compressionParams = (amount) => {
17242
+ const t = clamp4(amount, 0, 100) / 100;
17243
+ return {
17244
+ threshold: 0 + (-24 - 0) * t,
17245
+ ratio: 1 + (12 - 1) * t,
17246
+ knee: 0 + (6 - 0) * t,
17247
+ attack: 0.02 + (3e-3 - 0.02) * t,
17248
+ release: 0.25 + (0.15 - 0.25) * t
17249
+ };
17250
+ };
17251
+ var createChannelStrip = (ctx, destination, options = {}) => {
17252
+ const input = ctx.createGain();
17253
+ const compressor = ctx.createDynamicsCompressor();
17254
+ const applyCompression = (amount) => {
17255
+ const p = compressionParams(amount);
17256
+ const now = ctx.currentTime;
17257
+ compressor.threshold.setValueAtTime(p.threshold, now);
17258
+ compressor.ratio.setValueAtTime(p.ratio, now);
17259
+ compressor.knee.setValueAtTime(p.knee, now);
17260
+ compressor.attack.setValueAtTime(p.attack, now);
17261
+ compressor.release.setValueAtTime(p.release, now);
17262
+ };
17263
+ applyCompression(options.compression ?? 0);
17264
+ const splitter = ctx.createChannelSplitter(2);
17265
+ const mid = ctx.createGain();
17266
+ mid.gain.value = 0.5;
17267
+ splitter.connect(mid, 0);
17268
+ splitter.connect(mid, 1);
17269
+ const sideSum = ctx.createGain();
17270
+ sideSum.gain.value = 1;
17271
+ const sideL = ctx.createGain();
17272
+ sideL.gain.value = 0.5;
17273
+ const sideR = ctx.createGain();
17274
+ sideR.gain.value = -0.5;
17275
+ splitter.connect(sideL, 0);
17276
+ splitter.connect(sideR, 1);
17277
+ sideL.connect(sideSum);
17278
+ sideR.connect(sideSum);
17279
+ const widthGain = ctx.createGain();
17280
+ sideSum.connect(widthGain);
17281
+ const widthInv = ctx.createGain();
17282
+ widthInv.gain.value = -1;
17283
+ widthGain.connect(widthInv);
17284
+ const outL = ctx.createGain();
17285
+ mid.connect(outL);
17286
+ widthGain.connect(outL);
17287
+ const outR = ctx.createGain();
17288
+ mid.connect(outR);
17289
+ widthInv.connect(outR);
17290
+ const merger = ctx.createChannelMerger(2);
17291
+ outL.connect(merger, 0, 0);
17292
+ outR.connect(merger, 0, 1);
17293
+ const setWidth = (width) => {
17294
+ widthGain.gain.setTargetAtTime(
17295
+ clamp4(width, 0, 200) / 100,
17296
+ ctx.currentTime,
17297
+ 0.02
17298
+ );
17299
+ };
17300
+ setWidth(options.width ?? 100);
17301
+ input.connect(compressor);
17302
+ compressor.connect(splitter);
17303
+ merger.connect(destination);
17304
+ return {
17305
+ input,
17306
+ setCompression: applyCompression,
17307
+ setWidth,
17308
+ dispose: () => {
17309
+ input.disconnect();
17310
+ compressor.disconnect();
17311
+ splitter.disconnect();
17312
+ mid.disconnect();
17313
+ sideSum.disconnect();
17314
+ sideL.disconnect();
17315
+ sideR.disconnect();
17316
+ widthGain.disconnect();
17317
+ widthInv.disconnect();
17318
+ outL.disconnect();
17319
+ outR.disconnect();
17320
+ merger.disconnect();
17321
+ }
17322
+ };
17323
+ };
17324
+
17325
+ // src/clip-meter.ts
17326
+ var createClipMeter = (ctx, source, options = {}) => {
17327
+ const threshold = options.threshold ?? 0.98;
17328
+ const holdMs = options.holdMs ?? 800;
17329
+ const analyser = ctx.createAnalyser();
17330
+ analyser.fftSize = 512;
17331
+ source.connect(analyser);
17332
+ const buf = new Float32Array(analyser.fftSize);
17333
+ let clipping = false;
17334
+ let clipUntil = 0;
17335
+ let peakLevel = 0;
17336
+ const listeners = /* @__PURE__ */ new Set();
17337
+ let rafId = null;
17338
+ const tick = () => {
17339
+ analyser.getFloatTimeDomainData(buf);
17340
+ let peak = 0;
17341
+ let clipped = false;
17342
+ for (let i2 = 0; i2 < buf.length; i2++) {
17343
+ const v = Math.abs(buf[i2]);
17344
+ if (v > peak) peak = v;
17345
+ if (v >= threshold) clipped = true;
17346
+ }
17347
+ peakLevel = peak;
17348
+ const now = performance.now();
17349
+ if (clipped) clipUntil = now + holdMs;
17350
+ const nextClipping = now < clipUntil;
17351
+ if (nextClipping !== clipping) {
17352
+ clipping = nextClipping;
17353
+ for (const cb of listeners) cb(clipping);
17354
+ }
17355
+ rafId = requestAnimationFrame(tick);
17356
+ };
17357
+ rafId = requestAnimationFrame(tick);
17358
+ return {
17359
+ onClipChange: (cb) => {
17360
+ listeners.add(cb);
17361
+ return () => listeners.delete(cb);
17362
+ },
17363
+ getPeakLevel: () => peakLevel,
17364
+ reset: () => {
17365
+ clipUntil = 0;
17366
+ if (clipping) {
17367
+ clipping = false;
17368
+ for (const cb of listeners) cb(false);
17369
+ }
17370
+ },
17371
+ dispose: () => {
17372
+ if (rafId !== null) cancelAnimationFrame(rafId);
17373
+ rafId = null;
17374
+ analyser.disconnect();
17375
+ listeners.clear();
17376
+ }
17377
+ };
17378
+ };
17379
+
17380
+ // src/reverb.ts
17381
+ var IMPULSE_DURATION_SEC = 2.2;
17382
+ var IMPULSE_DECAY = 2.5;
17383
+ var createReverbImpulse = (ctx) => {
17384
+ const rate = ctx.sampleRate;
17385
+ const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
17386
+ const impulse = ctx.createBuffer(2, length, rate);
17387
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
17388
+ const data = impulse.getChannelData(ch);
17389
+ for (let i2 = 0; i2 < length; i2++) {
17390
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
17391
+ data[i2] = (Math.random() * 2 - 1) * envelope;
17392
+ }
17393
+ }
17394
+ return impulse;
17395
+ };
17396
+ var REVERB_MAX_WET = 0.6;
17397
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
17398
+
16448
17399
  // src/sf/SoundFont_drum.ts
16449
17400
  var touch = (map, key, ctor) => {
16450
17401
  if (!map.has(key)) map.set(key, new ctor());
@@ -16597,10 +17548,44 @@ var createDtmStudio = async (options = {}) => {
16597
17548
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
16598
17549
  const masterGain = audioCtx.createGain();
16599
17550
  masterGain.gain.value = options.masterVolume ?? 1;
16600
- masterGain.connect(audioCtx.destination);
16601
17551
  const drumGain = audioCtx.createGain();
16602
17552
  drumGain.gain.value = options.drumVolume ?? 1;
16603
17553
  drumGain.connect(masterGain);
17554
+ const reverbConvolver = audioCtx.createConvolver();
17555
+ reverbConvolver.buffer = createReverbImpulse(audioCtx);
17556
+ reverbConvolver.normalize = true;
17557
+ const reverbWetGain = audioCtx.createGain();
17558
+ reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17559
+ masterGain.connect(reverbConvolver);
17560
+ reverbConvolver.connect(reverbWetGain);
17561
+ const setReverbAmount = (amount) => {
17562
+ reverbWetGain.gain.setTargetAtTime(
17563
+ reverbAmountToGain(amount),
17564
+ audioCtx.currentTime,
17565
+ 0.02
17566
+ );
17567
+ };
17568
+ const finalMix = audioCtx.createGain();
17569
+ masterGain.connect(finalMix);
17570
+ reverbWetGain.connect(finalMix);
17571
+ const safetyLimiter = audioCtx.createDynamicsCompressor();
17572
+ safetyLimiter.threshold.value = -1;
17573
+ safetyLimiter.knee.value = 0;
17574
+ safetyLimiter.ratio.value = 20;
17575
+ safetyLimiter.attack.value = 1e-3;
17576
+ safetyLimiter.release.value = 0.1;
17577
+ finalMix.connect(safetyLimiter);
17578
+ safetyLimiter.connect(options.destination ?? audioCtx.destination);
17579
+ const clipMeter = createClipMeter(audioCtx, finalMix);
17580
+ const channelStrips = /* @__PURE__ */ new Map();
17581
+ const getChannelStrip = (trackId) => {
17582
+ let strip = channelStrips.get(trackId);
17583
+ if (!strip) {
17584
+ strip = createChannelStrip(audioCtx, masterGain);
17585
+ channelStrips.set(trackId, strip);
17586
+ }
17587
+ return strip;
17588
+ };
16604
17589
  const resumeAudio = () => {
16605
17590
  if (audioCtx.state === "closed") return Promise.resolve();
16606
17591
  return audioCtx.resume();
@@ -16631,7 +17616,14 @@ var createDtmStudio = async (options = {}) => {
16631
17616
  const singingVoices = createSingingVoices(audioCtx, masterGain, {
16632
17617
  voiceWorkerUrl,
16633
17618
  voicebanks,
16634
- worldlineScriptUrl: options.worldlineScriptUrl
17619
+ worldlineScriptUrl: options.worldlineScriptUrl,
17620
+ // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17621
+ // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17622
+ reverbBus: reverbConvolver,
17623
+ // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17624
+ // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17625
+ // 同じ trackId を指していれば同じ処理が掛かる。
17626
+ getTrackDestination: (trackId) => getChannelStrip(trackId).input
16635
17627
  });
16636
17628
  const listReady = new Promise((resolve) => {
16637
17629
  sfList.init();
@@ -16861,7 +17853,7 @@ var createDtmStudio = async (options = {}) => {
16861
17853
  if (!sfInst) return;
16862
17854
  sfInst.play({
16863
17855
  ctx: audioCtx,
16864
- destination: masterGain,
17856
+ destination: getChannelStrip(e.trackId).input,
16865
17857
  pitch: e.pitch,
16866
17858
  volume: e.volume,
16867
17859
  when: e.when,
@@ -16906,6 +17898,11 @@ var createDtmStudio = async (options = {}) => {
16906
17898
  void handleTrackInstrumentChange(idx, name);
16907
17899
  externalOnTrackInstrumentChange?.(idx, name);
16908
17900
  },
17901
+ reverbAmount: options.reverbAmount,
17902
+ onReverbChange: setReverbAmount,
17903
+ onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17904
+ onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
17905
+ clipMeter,
16909
17906
  ...dawOverrides,
16910
17907
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
16911
17908
  onDrumChange: (name) => {
@@ -16932,17 +17929,22 @@ var createDtmStudio = async (options = {}) => {
16932
17929
  mountedEditors.push(daw);
16933
17930
  const wantPresetUI = presetUI ?? features.presetUI;
16934
17931
  const rollEl = target.querySelector('[data-dtm="roll"]');
17932
+ const presetSlot = target.querySelector(
17933
+ '[data-dtm="preset-select-slot"]'
17934
+ );
16935
17935
  if (wantPresetUI) {
16936
17936
  editorPresetSelects.get(target)?.destroy();
16937
- presetSelect = mountPresetSelect(target, {
17937
+ presetSelect = mountPresetSelect(presetSlot ?? target, {
16938
17938
  getDaw: () => daw,
16939
17939
  getTrackIds: () => trackIds,
16940
17940
  value: initialPreset,
16941
17941
  loadingTarget: rollEl ?? target,
16942
- position: "prepend",
16943
- // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
17942
+ position: presetSlot ? "append" : "prepend",
17943
+ // 楽器変更時、このエディタの発音解決が使うプリセットも追従させ、
17944
+ // 外部の onInstrumentChange(呼び出し側の永続化フック等)へも通知する。
17945
+ // #inst= 付きMML読込時の通知経路(handleInstrumentChange)と揃える。
16944
17946
  onChange: (key) => {
16945
- editorPreset = key;
17947
+ handleInstrumentChange(key);
16946
17948
  }
16947
17949
  });
16948
17950
  editorPresetSelects.set(target, presetSelect);
@@ -16963,8 +17965,8 @@ var createDtmStudio = async (options = {}) => {
16963
17965
  presetSelect?.destroy();
16964
17966
  if (editorPresetSelects.get(target) === presetSelect)
16965
17967
  editorPresetSelects.delete(target);
16966
- const i = mountedEditors.indexOf(daw);
16967
- if (i >= 0) mountedEditors.splice(i, 1);
17968
+ const i2 = mountedEditors.indexOf(daw);
17969
+ if (i2 >= 0) mountedEditors.splice(i2, 1);
16968
17970
  };
16969
17971
  return {
16970
17972
  ...daw,
@@ -17067,8 +18069,8 @@ var createDtmStudio = async (options = {}) => {
17067
18069
  destroy: () => {
17068
18070
  doUnmount();
17069
18071
  wrapper.remove();
17070
- const i = mountedModeSwitches.indexOf(instance);
17071
- if (i >= 0) mountedModeSwitches.splice(i, 1);
18072
+ const i2 = mountedModeSwitches.indexOf(instance);
18073
+ if (i2 >= 0) mountedModeSwitches.splice(i2, 1);
17072
18074
  }
17073
18075
  };
17074
18076
  mountedModeSwitches.push(instance);
@@ -17125,7 +18127,7 @@ var createDtmStudio = async (options = {}) => {
17125
18127
  if (!sfInst) return;
17126
18128
  sfInst.play({
17127
18129
  ctx: audioCtx,
17128
- destination: masterGain,
18130
+ destination: getChannelStrip(e.trackId).input,
17129
18131
  pitch: e.pitch,
17130
18132
  volume: e.volume,
17131
18133
  when: e.when,
@@ -17155,8 +18157,8 @@ var createDtmStudio = async (options = {}) => {
17155
18157
  mountedPlayers.push(player);
17156
18158
  const destroy = () => {
17157
18159
  player.destroy();
17158
- const i = mountedPlayers.indexOf(player);
17159
- if (i >= 0) mountedPlayers.splice(i, 1);
18160
+ const i2 = mountedPlayers.indexOf(player);
18161
+ if (i2 >= 0) mountedPlayers.splice(i2, 1);
17160
18162
  };
17161
18163
  return { ...player, destroy };
17162
18164
  };
@@ -17211,7 +18213,7 @@ var createDtmStudio = async (options = {}) => {
17211
18213
  if (!sfInst) return;
17212
18214
  sfInst.play({
17213
18215
  ctx: audioCtx,
17214
- destination: masterGain,
18216
+ destination: getChannelStrip(e.trackId).input,
17215
18217
  pitch: e.pitch,
17216
18218
  volume: e.volume,
17217
18219
  when: e.when,
@@ -17290,7 +18292,7 @@ var createDtmStudio = async (options = {}) => {
17290
18292
  if (!sfInst) return;
17291
18293
  sfInst.play({
17292
18294
  ctx: audioCtx,
17293
- destination: masterGain,
18295
+ destination: getChannelStrip(e.trackId).input,
17294
18296
  pitch: e.pitch,
17295
18297
  volume: e.volume,
17296
18298
  when: e.when,
@@ -17329,7 +18331,7 @@ var createDtmStudio = async (options = {}) => {
17329
18331
  if (!sfInst) return;
17330
18332
  sfInst.play({
17331
18333
  ctx: audioCtx,
17332
- destination: masterGain,
18334
+ destination: getChannelStrip(e.trackId).input,
17333
18335
  pitch: e.pitch,
17334
18336
  volume: e.volume,
17335
18337
  when: e.when,
@@ -17385,7 +18387,7 @@ var createDtmStudio = async (options = {}) => {
17385
18387
  if (!sfInst) return;
17386
18388
  sfInst.play({
17387
18389
  ctx: audioCtx,
17388
- destination: masterGain,
18390
+ destination: getChannelStrip("chord").input,
17389
18391
  pitch: e.pitch,
17390
18392
  volume: e.volume,
17391
18393
  when: e.when,
@@ -17417,8 +18419,8 @@ var createDtmStudio = async (options = {}) => {
17417
18419
  mountedChordPlayers.push(player);
17418
18420
  const destroy = () => {
17419
18421
  player.destroy();
17420
- const i = mountedChordPlayers.indexOf(player);
17421
- if (i >= 0) mountedChordPlayers.splice(i, 1);
18422
+ const i2 = mountedChordPlayers.indexOf(player);
18423
+ if (i2 >= 0) mountedChordPlayers.splice(i2, 1);
17422
18424
  };
17423
18425
  return {
17424
18426
  ...player,
@@ -17435,6 +18437,9 @@ var createDtmStudio = async (options = {}) => {
17435
18437
  mountedPlayers.length = 0;
17436
18438
  mountedChordPlayers.length = 0;
17437
18439
  mountedEditors.length = 0;
18440
+ clipMeter.dispose();
18441
+ for (const strip of channelStrips.values()) strip.dispose();
18442
+ channelStrips.clear();
17438
18443
  void audioCtx.close();
17439
18444
  };
17440
18445
  const setMasterVolume = (volume) => {
@@ -17473,6 +18478,7 @@ var createDtmStudio = async (options = {}) => {
17473
18478
  mountModeSwitch,
17474
18479
  setMasterVolume,
17475
18480
  setVolume: setMasterVolume,
18481
+ setReverbAmount,
17476
18482
  dispose
17477
18483
  };
17478
18484
  };
@@ -17504,6 +18510,7 @@ var createDtmStudio = async (options = {}) => {
17504
18510
  PREWARM_NOTES,
17505
18511
  TRACKS_ADVANCED,
17506
18512
  TRACKS_SIMPLE,
18513
+ VIBRATO_MIN_SEC,
17507
18514
  VOICE_IMAGES,
17508
18515
  VOICE_IMAGE_KEY,
17509
18516
  analyzeMidiTracks,