@onjmin/dtm 0.1.83 → 0.1.84

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,10 @@ 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;
2153
2319
  const modelMatch = rest.match(
2154
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpo]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2320
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobr]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2155
2321
  );
2156
2322
  let model = "";
2157
2323
  const metaTokens = [];
@@ -2192,11 +2358,25 @@ var parseLyrics = (mml) => {
2192
2358
  rest = rest.substring(oMatch[0].length).trim();
2193
2359
  continue;
2194
2360
  }
2361
+ const bMatch = rest.match(/^b([01])/i);
2362
+ if (bMatch) {
2363
+ vibrato = bMatch[1] === "1";
2364
+ metaTokens.push(bMatch[0]);
2365
+ rest = rest.substring(bMatch[0].length).trim();
2366
+ continue;
2367
+ }
2368
+ const rMatch = rest.match(/^r(\d+)/i);
2369
+ if (rMatch) {
2370
+ reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2371
+ metaTokens.push(rMatch[0]);
2372
+ rest = rest.substring(rMatch[0].length).trim();
2373
+ continue;
2374
+ }
2195
2375
  break;
2196
2376
  }
2197
2377
  const lyricLines = [rest];
2198
- while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
2199
- lyricLines.push(segments[++i]);
2378
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1])) {
2379
+ lyricLines.push(segments[++i2]);
2200
2380
  }
2201
2381
  const { syllables, lineBreaks } = normalizeLyricLines(lyricLines);
2202
2382
  tracks.set(trackId, {
@@ -2206,6 +2386,8 @@ var parseLyrics = (mml) => {
2206
2386
  gate,
2207
2387
  pan,
2208
2388
  octave,
2389
+ vibrato,
2390
+ reverb,
2209
2391
  syllables,
2210
2392
  metaText: metaTokens.join(" "),
2211
2393
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2216,13 +2398,13 @@ var parseLyrics = (mml) => {
2216
2398
  var stripLyrics = (mml) => {
2217
2399
  const segments = splitSegments(mml);
2218
2400
  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++;
2401
+ for (let i2 = 0; i2 < segments.length; i2++) {
2402
+ if (LYRIC_LINE.test(segments[i2])) {
2403
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1]))
2404
+ i2++;
2223
2405
  continue;
2224
2406
  }
2225
- kept.push(segments[i]);
2407
+ kept.push(segments[i2]);
2226
2408
  }
2227
2409
  return kept.join("\n");
2228
2410
  };
@@ -2291,7 +2473,7 @@ var FORMANTS = {
2291
2473
  N: [250, 1e3]
2292
2474
  };
2293
2475
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2294
- var createKlattVoice = (ctx, destination) => {
2476
+ var createKlattVoice = (ctx, destination, reverbBus) => {
2295
2477
  const active = /* @__PURE__ */ new Set();
2296
2478
  const voice = (syllable, e) => {
2297
2479
  const t0 = ctx.currentTime + e.when;
@@ -2309,6 +2491,12 @@ var createKlattVoice = (ctx, destination) => {
2309
2491
  panner.connect(destination);
2310
2492
  out = panner;
2311
2493
  }
2494
+ let sendGain = null;
2495
+ if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2496
+ sendGain = ctx.createGain();
2497
+ sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2498
+ panner.connect(sendGain).connect(reverbBus);
2499
+ }
2312
2500
  const osc = ctx.createOscillator();
2313
2501
  osc.type = "sawtooth";
2314
2502
  osc.frequency.value = midiToFreq(e.pitch);
@@ -2337,7 +2525,7 @@ var createKlattVoice = (ctx, destination) => {
2337
2525
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
2338
2526
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
2339
2527
  const data = buffer.getChannelData(0);
2340
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
2528
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
2341
2529
  const src = ctx.createBufferSource();
2342
2530
  src.buffer = buffer;
2343
2531
  const hp = ctx.createBiquadFilter();
@@ -2364,6 +2552,7 @@ var createKlattVoice = (ctx, destination) => {
2364
2552
  active.delete(osc);
2365
2553
  osc.disconnect();
2366
2554
  panner?.disconnect();
2555
+ sendGain?.disconnect();
2367
2556
  };
2368
2557
  };
2369
2558
  voice.stopAll = () => {
@@ -2382,6 +2571,7 @@ var KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
2382
2571
  var KOE_VOICEBANKS = {
2383
2572
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
2384
2573
  rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
2574
+ rino121: "\u6625\u97F3\u30EA\u30CEver.1.1(226).koe",
2385
2575
  roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
2386
2576
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
2387
2577
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
@@ -2390,11 +2580,13 @@ var KOE_VOICEBANKS = {
2390
2580
  rei: "\u8DB3\u7ACB\u30EC\u30A4ver3.5.0.koe",
2391
2581
  mgroid: "MGRoid_\u539F\u97F3\u8A2D\u5B9A\u6E08\u307F.koe",
2392
2582
  motroid: "MOTRoid\u5B8C\u5168\u7248V2.koe",
2393
- nynroid: "NYNRoidver1.4.koe"
2583
+ nynroid: "NYNRoidver1.4.koe",
2584
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3\u97F3\u6E90.koe"
2394
2585
  };
2395
2586
  var KOE_VOICEBANK_LABELS = {
2396
2587
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
2397
2588
  rino: "\u6625\u97F3\u30EA\u30CE",
2589
+ rino121: "\u6625\u97F3\u30EA\u30CEv1.2.1",
2398
2590
  roze: "\u675F\u97F3\u30ED\u30BC",
2399
2591
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
2400
2592
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
@@ -2403,12 +2595,14 @@ var KOE_VOICEBANK_LABELS = {
2403
2595
  rei: "\u8DB3\u7ACB\u30EC\u30A4",
2404
2596
  mgroid: "MGRoid",
2405
2597
  motroid: "MOTRoid",
2406
- nynroid: "NYNRoid"
2598
+ nynroid: "NYNRoid",
2599
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3"
2407
2600
  };
2408
2601
  var VOICE_IMAGE_KEY = {
2409
2602
  klatt: "puyuyu",
2410
2603
  tsukuyomi: "tsukuyomi",
2411
2604
  rino: "rino",
2605
+ rino121: "rino",
2412
2606
  roze: "roze",
2413
2607
  ruko_male: "ruko",
2414
2608
  ruko_female: "ruko",
@@ -2417,11 +2611,13 @@ var VOICE_IMAGE_KEY = {
2417
2611
  rei: "rei",
2418
2612
  mgroid: "MGRoid",
2419
2613
  motroid: "MOTRoid",
2420
- nynroid: "NYNRoid"
2614
+ nynroid: "NYNRoid",
2615
+ uc: "uc"
2421
2616
  };
2422
2617
  var KOE_VOICEBANK_TERMS = {
2423
2618
  tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
2424
2619
  rino: "https://hatenakun1.github.io/halunelino/",
2620
+ rino121: "https://harunerino.vercel.app/",
2425
2621
  roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
2426
2622
  ruko_male: "https://long-sleeper.net/index.php?id=22",
2427
2623
  ruko_female: "https://long-sleeper.net/index.php?id=22",
@@ -2430,7 +2626,8 @@ var KOE_VOICEBANK_TERMS = {
2430
2626
  rei: "https://mechanicalgirl.jp/guidelines/",
2431
2627
  mgroid: "https://x.com/nisusansu/status/1048825378188353536",
2432
2628
  motroid: "https://www.nicovideo.jp/watch/sm40031282",
2433
- nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs"
2629
+ nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs",
2630
+ uc: "https://chi9nekiriko.wixsite.com/home/%E5%88%A9%E7%94%A8%E8%A6%8F%E7%B4%84"
2434
2631
  };
2435
2632
  var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
2436
2633
  var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
@@ -2519,7 +2716,7 @@ var createLocalBackend = async (options) => {
2519
2716
  }
2520
2717
  return p;
2521
2718
  };
2522
- const renderAlias = async (alias, pitch, durationMs) => {
2719
+ const renderAlias = async (alias, pitch, durationMs, vibrato) => {
2523
2720
  const pcm = await getPcm(alias);
2524
2721
  if (!pcm || pcm.length === 0) return null;
2525
2722
  const entry = bank.manifest.phonemes[alias];
@@ -2528,7 +2725,7 @@ var createLocalBackend = async (options) => {
2528
2725
  if (worldline) {
2529
2726
  const audio = worldline.renderNote({
2530
2727
  pcm,
2531
- pitch: targetHz,
2728
+ pitch: vibrato ? vibratoPitchCurve(targetHz, lead.preMs) : targetHz,
2532
2729
  durationMs,
2533
2730
  ...lead
2534
2731
  });
@@ -2595,7 +2792,7 @@ var createWorkerBackend = async (workerUrl, options) => {
2595
2792
  });
2596
2793
  onReady = null;
2597
2794
  onFail = null;
2598
- const renderAlias = (alias, pitch, durationMs) => new Promise((resolve) => {
2795
+ const renderAlias = (alias, pitch, durationMs, vibrato) => new Promise((resolve) => {
2599
2796
  const id = ++reqId;
2600
2797
  pending.set(
2601
2798
  id,
@@ -2608,7 +2805,8 @@ var createWorkerBackend = async (workerUrl, options) => {
2608
2805
  id,
2609
2806
  alias,
2610
2807
  pitch,
2611
- durationMs
2808
+ durationMs,
2809
+ vibrato
2612
2810
  });
2613
2811
  });
2614
2812
  return {
@@ -2637,15 +2835,15 @@ var createKoeVoice = async (ctx, destination, options) => {
2637
2835
  const inflight = /* @__PURE__ */ new Map();
2638
2836
  const active = /* @__PURE__ */ new Set();
2639
2837
  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);
2838
+ const keyOf = (alias, pitch, durationMs, vibrato) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}`;
2839
+ const renderInto = (alias, pitch, durationMs, vibrato) => {
2840
+ const key = keyOf(alias, pitch, durationMs, vibrato);
2643
2841
  const existing = renderCache.get(key);
2644
2842
  if (existing !== void 0) return Promise.resolve(existing);
2645
2843
  const flying = inflight.get(key);
2646
2844
  if (flying) return flying;
2647
2845
  const p = (async () => {
2648
- const out = await backend.renderAlias(alias, pitch, durationMs);
2846
+ const out = await backend.renderAlias(alias, pitch, durationMs, vibrato);
2649
2847
  let rendered = null;
2650
2848
  if (out) {
2651
2849
  const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
@@ -2660,7 +2858,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2660
2858
  return p;
2661
2859
  };
2662
2860
  const LEADCAP_S = 0.09;
2663
- const schedule = (r, t0, peak, pan) => {
2861
+ const schedule = (r, t0, peak, pan, reverbSend = 0) => {
2664
2862
  let out = destination;
2665
2863
  let panner = null;
2666
2864
  if (typeof ctx.createStereoPanner === "function") {
@@ -2669,6 +2867,12 @@ var createKoeVoice = async (ctx, destination, options) => {
2669
2867
  panner.connect(destination);
2670
2868
  out = panner;
2671
2869
  }
2870
+ let sendGain = null;
2871
+ if (options.reverbBus && reverbSend > 0 && panner) {
2872
+ sendGain = ctx.createGain();
2873
+ sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2874
+ panner.connect(sendGain).connect(options.reverbBus);
2875
+ }
2672
2876
  const src = ctx.createBufferSource();
2673
2877
  src.buffer = r.audio;
2674
2878
  src.playbackRate.value = r.rate;
@@ -2694,6 +2898,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2694
2898
  src.disconnect();
2695
2899
  env.disconnect();
2696
2900
  panner?.disconnect();
2901
+ sendGain?.disconnect();
2697
2902
  };
2698
2903
  };
2699
2904
  const model = (syllable, e) => {
@@ -2712,10 +2917,10 @@ var createKoeVoice = async (ctx, destination, options) => {
2712
2917
  const pan = e.pan ?? 0;
2713
2918
  const durationMs = Math.max(60, e.duration * 1e3);
2714
2919
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2715
- if (r) schedule(r, t0, peak, pan);
2920
+ if (r) schedule(r, t0, peak, pan, e.reverbSend);
2716
2921
  });
2717
2922
  };
2718
- model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs) => {
2923
+ model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato) => {
2719
2924
  if (syllable.consonant === "Q" || syllable.vowel === "") return null;
2720
2925
  const alias = resolveKoeAlias(
2721
2926
  backend.hasAlias,
@@ -2726,12 +2931,13 @@ var createKoeVoice = async (ctx, destination, options) => {
2726
2931
  );
2727
2932
  if (!alias) return null;
2728
2933
  const dMs = Math.max(60, durationMs);
2729
- const r = await renderInto(alias, pitch, dMs);
2730
- return r ? keyOf(alias, pitch, dMs) : null;
2934
+ const vib = !!vibrato && dMs / 1e3 >= VIBRATO_MIN_SEC;
2935
+ const r = await renderInto(alias, pitch, dMs, vib);
2936
+ return r ? keyOf(alias, pitch, dMs, vib) : null;
2731
2937
  };
2732
- model.scheduleCached = (key, t0, peak, pan) => {
2938
+ model.scheduleCached = (key, t0, peak, pan, reverbSend) => {
2733
2939
  const r = renderCache.get(key);
2734
- if (r) schedule(r, t0, peak, pan);
2940
+ if (r) schedule(r, t0, peak, pan, reverbSend);
2735
2941
  };
2736
2942
  model.stopAll = () => {
2737
2943
  for (const src of active) {
@@ -2762,7 +2968,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2762
2968
  const masterGain = ctx.createGain();
2763
2969
  masterGain.connect(destination);
2764
2970
  const loaded = /* @__PURE__ */ new Map([
2765
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain)]
2971
+ [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
2766
2972
  ]);
2767
2973
  const loading = /* @__PURE__ */ new Map();
2768
2974
  const load2 = (model) => {
@@ -2782,7 +2988,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2782
2988
  koe,
2783
2989
  worldlineScriptUrl: options.worldlineScriptUrl,
2784
2990
  lightweight: options.lightweight,
2785
- voiceWorkerUrl: options.voiceWorkerUrl
2991
+ voiceWorkerUrl: options.voiceWorkerUrl,
2992
+ reverbBus: options.reverbBus
2786
2993
  })
2787
2994
  ))().then((v) => {
2788
2995
  loaded.set(m, v);
@@ -2827,7 +3034,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2827
3034
  forEachSungNote(track, (note, prevVowel) => {
2828
3035
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2829
3036
  n++;
2830
- tasks.push({ model: m, note, prevVowel });
3037
+ tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato });
2831
3038
  });
2832
3039
  }
2833
3040
  const total = tasks.length;
@@ -2842,7 +3049,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2842
3049
  task.note.syllable,
2843
3050
  task.prevVowel,
2844
3051
  task.note.pitch,
2845
- task.note.durationSec * 1e3
3052
+ task.note.durationSec * 1e3,
3053
+ task.vibrato
2846
3054
  ) ?? Promise.resolve(null));
2847
3055
  done++;
2848
3056
  onProgress?.(done, total);
@@ -2887,13 +3095,14 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2887
3095
  note.syllable,
2888
3096
  prevVowel,
2889
3097
  note.pitch,
2890
- note.durationSec * 1e3
3098
+ note.durationSec * 1e3,
3099
+ track.vibrato
2891
3100
  );
2892
3101
  if (session !== streamSession) return;
2893
3102
  if (key) {
2894
3103
  const delay = ctx.currentTime - t0;
2895
3104
  if (delay < 0.05) {
2896
- scheduleCached(key, t0, peak, track.pan);
3105
+ scheduleCached(key, t0, peak, track.pan, track.reverbSend);
2897
3106
  opts?.onScheduled?.(track, note, t0);
2898
3107
  } else {
2899
3108
  console.warn(
@@ -2912,7 +3121,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2912
3121
  volume: peak,
2913
3122
  when,
2914
3123
  duration: note.durationSec,
2915
- pan: track.pan
3124
+ pan: track.pan,
3125
+ reverbSend: track.reverbSend
2916
3126
  });
2917
3127
  opts?.onScheduled?.(track, note, t0);
2918
3128
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -5773,7 +5983,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
5773
5983
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
5774
5984
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
5775
5985
  const data = buffer.getChannelData(0);
5776
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
5986
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
5777
5987
  const src = ctx.createBufferSource();
5778
5988
  src.buffer = buffer;
5779
5989
  const filter = ctx.createBiquadFilter();
@@ -7694,6 +7904,9 @@ var teto_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAA
7694
7904
  // assets/tsukuyomi.png
7695
7905
  var tsukuyomi_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB3UlEQVR42tWXPUsDQRCGnwuijYIoFqKmEDSCxIgIgqnsogbsrC3ERiwN4g8Ikk6sFH+BnYofvcTKxoigBCzEYGGjWIiCOYvNBHe5TdQUt051czc3e++zO7tzHnUst7vn04BlFue9Ws8jhGxN9ZTPzU43NkIlj41E6AQ8m/JYfByAoWgXAPtHJzRCRN43SbizBkzlv1X2VzLuVoHYzf0TAMnys+aLCTGThMTJGnKfQOpyWF1cvgHQsh4FYPDzRd3vmQHgoPShJbi9uqhJyPk1UK3Jwsq1/115rLst8AX/4UFP0NsbGHf7+Kr579l7zR/ZGvbcrAJR7vvqEOwcTdZMYIuTPCYJUS7Ewydgzn2Bay2gdWzqR4nMuMPlhcC5l/HcWQOnCaU4lVU3xI9tnwFQnOgDoDmdDs5QKmlxYhdGmBA2ibhTBfKFHCkCm00dAKymJn+USAidHx8DkI+0q7PBUC7j5BJ7jlSB7RyXziVT8fM7Oz7AeLmsz3VEaWi7UvtAcmvJC+qmq4T/XT8gdTsw2692to07AF7jeU35wFq/7COqzhdVnVOnu3aHgNm1VpVXlImJX9zQffO5kDg1dlb3CNj6dZsVK2vAVP7Xf8bwCfxWuc23Eanmt/xlh07gC6oBviFE8rZHAAAAAElFTkSuQmCC";
7696
7906
 
7907
+ // assets/uc.png
7908
+ 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==";
7909
+
7697
7910
  // src/voice-images.ts
7698
7911
  var VOICE_IMAGES = {
7699
7912
  puyuyu: puyuyu_default,
@@ -7706,7 +7919,8 @@ var VOICE_IMAGES = {
7706
7919
  rei: rei_default,
7707
7920
  MGRoid: MGRoid_default,
7708
7921
  MOTRoid: MOTRoid_default,
7709
- NYNRoid: NYNRoid_default
7922
+ NYNRoid: NYNRoid_default,
7923
+ uc: uc_default
7710
7924
  };
7711
7925
  var FALLBACK_VOCAL_ICON = Chip_default;
7712
7926
 
@@ -7768,8 +7982,8 @@ var copyToClipboard = async (doc, text) => {
7768
7982
  };
7769
7983
  var toBase64Url = (bytes) => {
7770
7984
  let bin = "";
7771
- for (let i = 0; i < bytes.length; i++) {
7772
- bin += String.fromCharCode(bytes[i]);
7985
+ for (let i2 = 0; i2 < bytes.length; i2++) {
7986
+ bin += String.fromCharCode(bytes[i2]);
7773
7987
  }
7774
7988
  return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
7775
7989
  };
@@ -7782,8 +7996,8 @@ var SHIFT_KATAKANA = 255;
7782
7996
  var VALUE_PROLONGED = 223;
7783
7997
  var customEncode = (str) => {
7784
7998
  const bytes = [];
7785
- for (let i = 0; i < str.length; i++) {
7786
- const code = str.charCodeAt(i);
7999
+ for (let i2 = 0; i2 < str.length; i2++) {
8000
+ const code = str.charCodeAt(i2);
7787
8001
  if (code === 32) {
7788
8002
  continue;
7789
8003
  }
@@ -7825,37 +8039,37 @@ var fromBase64Url = (s) => {
7825
8039
  }
7826
8040
  const bin = atob(normalized);
7827
8041
  const bytes = new Uint8Array(bin.length);
7828
- for (let i = 0; i < bin.length; i++) {
7829
- bytes[i] = bin.charCodeAt(i);
8042
+ for (let i2 = 0; i2 < bin.length; i2++) {
8043
+ bytes[i2] = bin.charCodeAt(i2);
7830
8044
  }
7831
8045
  return bytes;
7832
8046
  };
7833
8047
  var customDecode = (bytes) => {
7834
8048
  let str = "";
7835
- let i = 0;
7836
- while (i < bytes.length) {
7837
- const byte = bytes[i];
8049
+ let i2 = 0;
8050
+ while (i2 < bytes.length) {
8051
+ const byte = bytes[i2];
7838
8052
  if (byte <= 127) {
7839
8053
  str += String.fromCharCode(byte);
7840
- i++;
8054
+ i2++;
7841
8055
  } else if (byte === VALUE_PROLONGED) {
7842
8056
  str += String.fromCharCode(PROLONGED_MARK);
7843
- i++;
8057
+ i2++;
7844
8058
  } else if (byte >= 128 && byte <= 222) {
7845
8059
  str += String.fromCharCode(HIRAGANA_START + (byte - 128));
7846
- i++;
8060
+ i2++;
7847
8061
  } else if (byte === SHIFT_KATAKANA) {
7848
- if (i + 1 < bytes.length) {
7849
- const nextByte = bytes[i + 1];
8062
+ if (i2 + 1 < bytes.length) {
8063
+ const nextByte = bytes[i2 + 1];
7850
8064
  if (nextByte >= 128 && nextByte <= 222) {
7851
8065
  str += String.fromCharCode(HIRAGANA_START + 96 + (nextByte - 128));
7852
8066
  }
7853
- i += 2;
8067
+ i2 += 2;
7854
8068
  } else {
7855
- i++;
8069
+ i2++;
7856
8070
  }
7857
8071
  } else {
7858
- i++;
8072
+ i2++;
7859
8073
  }
7860
8074
  }
7861
8075
  return str;
@@ -8420,7 +8634,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8420
8634
  const beatRow = doc.createElement("div");
8421
8635
  beatRow.className = "dtm-player-beat-row";
8422
8636
  const beatDots = [];
8423
- for (let i = 0; i < 4; i++) {
8637
+ for (let i2 = 0; i2 < 4; i2++) {
8424
8638
  const d = doc.createElement("span");
8425
8639
  d.className = "dtm-player-beat-dot";
8426
8640
  beatRow.appendChild(d);
@@ -8524,9 +8738,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8524
8738
  lane.appendChild(metaEl);
8525
8739
  }
8526
8740
  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)) {
8741
+ for (let i2 = 0; i2 < count; i2++) {
8742
+ const note = notes[i2];
8743
+ if (breaks.has(i2)) {
8530
8744
  const br = doc.createElement("span");
8531
8745
  br.className = "dtm-tk dtm-tk--break";
8532
8746
  br.textContent = "\\n";
@@ -8534,7 +8748,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8534
8748
  }
8535
8749
  const span = doc.createElement("span");
8536
8750
  span.className = "dtm-tk dtm-tk--lyric";
8537
- span.textContent = lyricTrack.syllables[i].kana;
8751
+ span.textContent = lyricTrack.syllables[i2].kana;
8538
8752
  lane.appendChild(span);
8539
8753
  laneTokens.push({
8540
8754
  el: span,
@@ -8687,8 +8901,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8687
8901
  const intStep = Math.floor(step);
8688
8902
  playStep = intStep;
8689
8903
  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);
8904
+ for (let i2 = 0; i2 < 4; i2++)
8905
+ beatDots[i2].classList.toggle("dtm-player-beat-dot--on", i2 === beatIndex);
8692
8906
  barEl.textContent = String(Math.floor(step / STEPS_PER_BAR2) + 1);
8693
8907
  if (!isSeeking) {
8694
8908
  seekInput.value = String(Math.min(maxStep, Math.max(0, intStep)));
@@ -8783,11 +8997,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8783
8997
  const semis = (lt.octave ?? 0) * 12;
8784
8998
  const count = Math.min(sorted.length, lt.syllables.length);
8785
8999
  const notes = [];
8786
- for (let i = 0; i < count; i++) {
8787
- const n = sorted[i];
9000
+ for (let i2 = 0; i2 < count; i2++) {
9001
+ const n = sorted[i2];
8788
9002
  if (n.startStep < fromStep) continue;
8789
9003
  notes.push({
8790
- syllable: lt.syllables[i],
9004
+ syllable: lt.syllables[i2],
8791
9005
  pitch: n.pitch + semis,
8792
9006
  startSec: (n.startStep - fromStep) * secondsPerStep,
8793
9007
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -8798,6 +9012,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8798
9012
  model: lt.model,
8799
9013
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
8800
9014
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9015
+ vibrato: lt.vibrato,
9016
+ reverbSend: (lt.reverb ?? 0) / 100,
8801
9017
  notes
8802
9018
  };
8803
9019
  });
@@ -9057,13 +9273,13 @@ var findZone = (ctx, fontName, zones, pitchs = []) => {
9057
9273
  for (const zone of zones) {
9058
9274
  const low = zone.keyRangeLow | 0, high = zone.keyRangeHigh | 0;
9059
9275
  if (low > high) continue;
9060
- for (let i = low; i <= high; i++) pitchs.push(i);
9276
+ for (let i2 = low; i2 <= high; i2++) pitchs.push(i2);
9061
9277
  }
9062
9278
  const set = new Set(pitchs);
9063
9279
  const map = new Map(pitchs.map((v) => [v, zones[0]]));
9064
- for (let i = zones.length - 1; i >= 0; i--)
9280
+ for (let i2 = zones.length - 1; i2 >= 0; i2--)
9065
9281
  for (const v of set) {
9066
- const zone = zones[i];
9282
+ const zone = zones[i2];
9067
9283
  if (v < zone.keyRangeLow || v > zone.keyRangeHigh) continue;
9068
9284
  set.delete(v);
9069
9285
  map.set(v, { ...zone });
@@ -9093,13 +9309,13 @@ var adjustZone = async (ctx, fontName, zone) => {
9093
9309
  const decoded = atob(zone.sample);
9094
9310
  zone.buffer = ctx.createBuffer(1, decoded.length / 2, zone.sampleRate);
9095
9311
  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);
9312
+ for (let i2 = 0; i2 < decoded.length / 2; i2++) {
9313
+ let b1 = decoded.charCodeAt(i2 * 2), b2 = decoded.charCodeAt(i2 * 2 + 1);
9098
9314
  if (b1 < 0) b1 = 256 + b1;
9099
9315
  if (b2 < 0) b2 = 256 + b2;
9100
9316
  let n = b2 * 256 + b1;
9101
9317
  if (n >= 65536 / 2) n = n - 65536;
9102
- a[i] = n / 65536;
9318
+ a[i2] = n / 65536;
9103
9319
  }
9104
9320
  } else if (zone.file) {
9105
9321
  const bytes = Uint8Array.from(atob(zone.file), (c) => c.charCodeAt(0));
@@ -9139,10 +9355,10 @@ var adjustZone = async (ctx, fontName, zone) => {
9139
9355
  let loopPeak = 0;
9140
9356
  if (oldBuf.numberOfChannels > 0) {
9141
9357
  const ch0 = oldBuf.getChannelData(0);
9142
- for (let i = 0; i < ch0.length; i++) {
9143
- const abs = Math.abs(ch0[i]);
9358
+ for (let i2 = 0; i2 < ch0.length; i2++) {
9359
+ const abs = Math.abs(ch0[i2]);
9144
9360
  if (abs > totalPeak) totalPeak = abs;
9145
- if (i >= loopStartFrame && i < loopEndFrame) {
9361
+ if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9146
9362
  if (abs > loopPeak) loopPeak = abs;
9147
9363
  }
9148
9364
  }
@@ -9161,16 +9377,16 @@ var adjustZone = async (ctx, fontName, zone) => {
9161
9377
  for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9162
9378
  const oldData = oldBuf.getChannelData(ch);
9163
9379
  const newData = newBuf.getChannelData(ch);
9164
- for (let i = 0; i < attackLength; i++) {
9165
- const ratio = attackLength > 1 ? i / (attackLength - 1) : 0;
9380
+ for (let i2 = 0; i2 < attackLength; i2++) {
9381
+ const ratio = attackLength > 1 ? i2 / (attackLength - 1) : 0;
9166
9382
  const m = 1 + (gainMultiplier - 1) * ratio;
9167
- newData[i] = oldData[i] * m;
9383
+ newData[i2] = oldData[i2] * m;
9168
9384
  }
9169
9385
  let offset = attackLength;
9170
9386
  const loopData = oldData.subarray(loopStartFrame, loopEndFrame);
9171
9387
  const normalizedLoopData = new Float32Array(loopLengthFrame);
9172
- for (let i = 0; i < loopLengthFrame; i++) {
9173
- normalizedLoopData[i] = loopData[i] * gainMultiplier;
9388
+ for (let i2 = 0; i2 < loopLengthFrame; i2++) {
9389
+ normalizedLoopData[i2] = loopData[i2] * gainMultiplier;
9174
9390
  }
9175
9391
  for (let r = 0; r < repeatCount; r++) {
9176
9392
  newData.set(normalizedLoopData, offset);
@@ -9180,8 +9396,8 @@ var adjustZone = async (ctx, fontName, zone) => {
9180
9396
  const releaseData = oldData.subarray(loopEndFrame);
9181
9397
  if (gainMultiplier !== 1) {
9182
9398
  const normalizedRelease = new Float32Array(releaseLength);
9183
- for (let i = 0; i < releaseLength; i++) {
9184
- normalizedRelease[i] = releaseData[i] * gainMultiplier;
9399
+ for (let i2 = 0; i2 < releaseLength; i2++) {
9400
+ normalizedRelease[i2] = releaseData[i2] * gainMultiplier;
9185
9401
  }
9186
9402
  newData.set(normalizedRelease, offset);
9187
9403
  } else {
@@ -9377,10 +9593,10 @@ var buildDisplayLines = (chords, eventsCount) => {
9377
9593
  if (segments.length === 0) continue;
9378
9594
  const hasMultiSeg = segments.length > 1;
9379
9595
  const parts = [];
9380
- for (let i = 0; i < segments.length; i++) {
9381
- if (hasMultiSeg && i > 0)
9596
+ for (let i2 = 0; i2 < segments.length; i2++) {
9597
+ if (hasMultiSeg && i2 > 0)
9382
9598
  parts.push({ text: "|", isChord: false, eventIdx: -1 });
9383
- const bar = segments[i].trim();
9599
+ const bar = segments[i2].trim();
9384
9600
  if (!bar) continue;
9385
9601
  const splitAt = [];
9386
9602
  for (let j = 0; j < bar.length; j++) {
@@ -10543,6 +10759,11 @@ var buildUI = (target, options) => {
10543
10759
  <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10544
10760
  <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10545
10761
  </div>
10762
+ <div class="dtm-row">
10763
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10764
+ <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" title="\u30EA\u30D0\u30FC\u30D6">
10765
+ <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10766
+ </div>
10546
10767
  <div class="dtm-track-body" data-dtm="track-body"></div>
10547
10768
  </div>
10548
10769
  </details>
@@ -10766,6 +10987,8 @@ var buildUI = (target, options) => {
10766
10987
  hScrollThumb: sel("hscroll-thumb"),
10767
10988
  masterVolume: sel("master-volume"),
10768
10989
  masterVolumeLabel: sel("master-volume-label"),
10990
+ reverbAmount: sel("reverb-amount"),
10991
+ reverbAmountLabel: sel("reverb-amount-label"),
10769
10992
  trackTabs: sel("track-tabs"),
10770
10993
  trackBody: sel("track-body"),
10771
10994
  drumSelect: sel("drum-select"),
@@ -10934,16 +11157,16 @@ var generateRandomPattern = (core, options) => {
10934
11157
  const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
10935
11158
  const rootOffset = Math.floor(Math.random() * 12);
10936
11159
  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);
11160
+ for (let i2 = 0; i2 < 12; i2++) {
11161
+ const noteInOctave = (i2 - rootOffset + 12) % 12;
11162
+ if (scale.includes(noteInOctave)) availablePitches.push(basePitch + i2);
10940
11163
  }
10941
11164
  core.beginBatch();
10942
11165
  for (let bar = 0; bar < numBars; bar++) {
10943
11166
  const barStart = startStep + bar * stepsPerBar;
10944
11167
  const numNotes = Math.floor(Math.random() * 4) + 2;
10945
11168
  const occupied = /* @__PURE__ */ new Set();
10946
- for (let i = 0; i < numNotes; i++) {
11169
+ for (let i2 = 0; i2 < numNotes; i2++) {
10947
11170
  const stepInRange = Math.floor(Math.random() * (stepsPerBar / noteLength)) * noteLength;
10948
11171
  const step = barStart + stepInRange;
10949
11172
  if (occupied.has(step)) continue;
@@ -11044,10 +11267,10 @@ var STEPS_PER_BEAT3 = 48;
11044
11267
  var analyzeMidiTracks = (midi) => {
11045
11268
  const { tracks } = midi;
11046
11269
  const result = [];
11047
- for (let i = 0; i < tracks.length; i++) {
11270
+ for (let i2 = 0; i2 < tracks.length; i2++) {
11048
11271
  const notes = [];
11049
11272
  let currentTime = 0;
11050
- for (const event of tracks[i]) {
11273
+ for (const event of tracks[i2]) {
11051
11274
  currentTime += event.delta;
11052
11275
  if (event.noteOn && event.noteOn.velocity > 0) {
11053
11276
  notes.push({
@@ -11070,8 +11293,8 @@ var analyzeMidiTracks = (midi) => {
11070
11293
  const editableNotes = validNotes.filter((n) => n.channel !== 9);
11071
11294
  if (validNotes.length > 0 && editableNotes.length === 0) continue;
11072
11295
  result.push({
11073
- index: i,
11074
- name: `Ch${i + 1}`,
11296
+ index: i2,
11297
+ name: `Ch${i2 + 1}`,
11075
11298
  noteCount: editableNotes.length,
11076
11299
  selected: editableNotes.length > 0
11077
11300
  });
@@ -11118,8 +11341,8 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11118
11341
  const pitch = noteOff.noteNumber;
11119
11342
  const channel = event.channel ?? 0;
11120
11343
  if (channelNotes[channel]) {
11121
- for (let i = channelNotes[channel].length - 1; i >= 0; i--) {
11122
- const note = channelNotes[channel][i];
11344
+ for (let i2 = channelNotes[channel].length - 1; i2 >= 0; i2--) {
11345
+ const note = channelNotes[channel][i2];
11123
11346
  if (note.pitch === pitch && note.end === null) {
11124
11347
  note.end = currentTime;
11125
11348
  break;
@@ -11149,10 +11372,10 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11149
11372
  const avgPitch = validNotes.reduce((sum, n) => sum + n.pitch, 0) / validNotes.length;
11150
11373
  let maxSimultaneous = 0;
11151
11374
  const sortedNotes = [...validNotes].sort((a, b) => a.start - b.start);
11152
- for (let i = 0; i < sortedNotes.length; i++) {
11375
+ for (let i2 = 0; i2 < sortedNotes.length; i2++) {
11153
11376
  let simultaneous = 1;
11154
- for (let j = i + 1; j < sortedNotes.length; j++) {
11155
- if (sortedNotes[j].start < sortedNotes[i].end) {
11377
+ for (let j = i2 + 1; j < sortedNotes.length; j++) {
11378
+ if (sortedNotes[j].start < sortedNotes[i2].end) {
11156
11379
  simultaneous++;
11157
11380
  }
11158
11381
  }
@@ -11163,14 +11386,14 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11163
11386
  const blocks = [];
11164
11387
  let blockStart = sortedNotes[0].start;
11165
11388
  let blockEnd = sortedNotes[0].end;
11166
- for (let i = 1; i < sortedNotes.length; i++) {
11167
- const gap = sortedNotes[i].start - sortedNotes[i - 1].end;
11389
+ for (let i2 = 1; i2 < sortedNotes.length; i2++) {
11390
+ const gap = sortedNotes[i2].start - sortedNotes[i2 - 1].end;
11168
11391
  if (gap >= ticksPerBar) {
11169
11392
  blocks.push({ start: blockStart, end: blockEnd });
11170
- blockStart = sortedNotes[i].start;
11171
- blockEnd = sortedNotes[i].end;
11393
+ blockStart = sortedNotes[i2].start;
11394
+ blockEnd = sortedNotes[i2].end;
11172
11395
  } else {
11173
- blockEnd = sortedNotes[i].end;
11396
+ blockEnd = sortedNotes[i2].end;
11174
11397
  }
11175
11398
  }
11176
11399
  blocks.push({ start: blockStart, end: blockEnd });
@@ -11284,9 +11507,9 @@ var extractMidiPlacementsByTrack = (midi, selectedIndices, trackIds) => {
11284
11507
  const noteOff = event.noteOff || event.noteOn;
11285
11508
  if (noteOff) {
11286
11509
  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;
11510
+ for (let i2 = active.length - 1; i2 >= 0; i2--) {
11511
+ if (active[i2].pitch === pitch && active[i2].end === null) {
11512
+ active[i2].end = currentTime;
11290
11513
  break;
11291
11514
  }
11292
11515
  }
@@ -11451,17 +11674,17 @@ var extractDrumPatternFromNotes = (rawNotes, drumFont = "FluidR3_GM_sf2_file:0")
11451
11674
  let prev = bList[0];
11452
11675
  const parts = key.split("_");
11453
11676
  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) {
11677
+ for (let i2 = 1; i2 <= bList.length; i2++) {
11678
+ if (i2 === bList.length || bList[i2] !== prev + 1) {
11456
11679
  const rKey = `${start}-${prev}`;
11457
11680
  if (!rangeMap[rKey]) rangeMap[rKey] = [];
11458
11681
  rangeMap[rKey].push(noteObj);
11459
- if (i < bList.length) {
11460
- start = bList[i];
11461
- prev = bList[i];
11682
+ if (i2 < bList.length) {
11683
+ start = bList[i2];
11684
+ prev = bList[i2];
11462
11685
  }
11463
11686
  } else {
11464
- prev = bList[i];
11687
+ prev = bList[i2];
11465
11688
  }
11466
11689
  }
11467
11690
  }
@@ -11838,8 +12061,8 @@ var drawHeader = () => {
11838
12061
  (g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
11839
12062
  );
11840
12063
  for (let bar = startBar; bar <= endBar + 1; bar++) {
11841
- const x = bar * stepsPerBar * stepWidth;
11842
- const screenX = x;
12064
+ const x2 = bar * stepsPerBar * stepWidth;
12065
+ const screenX = x2;
11843
12066
  g_header_ctx.beginPath();
11844
12067
  g_header_ctx.moveTo(screenX, 0);
11845
12068
  g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
@@ -11886,11 +12109,11 @@ var drawGrid = (noteLengthSteps = 1) => {
11886
12109
  const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
11887
12110
  const endX = g_draw_offset_x + g_grid_canvas.width;
11888
12111
  const lineStep = stepWidth * gridStep;
11889
- for (let x = startX; x <= endX; x += lineStep) {
11890
- const step = x / stepWidth;
12112
+ for (let x2 = startX; x2 <= endX; x2 += lineStep) {
12113
+ const step = x2 / stepWidth;
11891
12114
  const isBarLine = step % stepsPerBar === 0;
11892
12115
  const isNoteLine = step % gridStep === 0;
11893
- const screenX = x - g_draw_offset_x;
12116
+ const screenX = x2 - g_draw_offset_x;
11894
12117
  g_grid_ctx.beginPath();
11895
12118
  g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
11896
12119
  g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
@@ -11952,26 +12175,26 @@ var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
11952
12175
  var getXY = (e) => {
11953
12176
  const { clientX, clientY } = e;
11954
12177
  const rect = g_grid_canvas.getBoundingClientRect();
11955
- const x = Math.floor(clientX - rect.left);
12178
+ const x2 = Math.floor(clientX - rect.left);
11956
12179
  const y = Math.floor(clientY - rect.top);
11957
- return [x, y, e.buttons];
12180
+ return [x2, y, e.buttons];
11958
12181
  };
11959
12182
  var getGridPosition = (e) => {
11960
- const [x, y] = getXY(e);
12183
+ const [x2, y] = getXY(e);
11961
12184
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11962
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12185
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11963
12186
  const absoluteY = y + g_draw_offset_y;
11964
12187
  const yIndex = Math.floor(absoluteY / keyHeight);
11965
12188
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
11966
- return { step, pitch, x, y };
12189
+ return { step, pitch, x: x2, y };
11967
12190
  };
11968
12191
  var onClick = (callback) => {
11969
12192
  g_grid_canvas.addEventListener(
11970
12193
  "click",
11971
12194
  (e) => {
11972
- const [x, y] = getXY(e);
12195
+ const [x2, y] = getXY(e);
11973
12196
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11974
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12197
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11975
12198
  const absoluteY = y + g_draw_offset_y;
11976
12199
  const yIndex = Math.floor(absoluteY / keyHeight);
11977
12200
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
@@ -11983,8 +12206,8 @@ var onClick = (callback) => {
11983
12206
  );
11984
12207
  g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
11985
12208
  };
11986
- var setDrawOffset = (x, y) => {
11987
- g_draw_offset_x = x;
12209
+ var setDrawOffset = (x2, y) => {
12210
+ g_draw_offset_x = x2;
11988
12211
  g_draw_offset_y = y;
11989
12212
  drawKeyboard();
11990
12213
  drawHeader();
@@ -12299,12 +12522,12 @@ var MMLCore = class _MMLCore {
12299
12522
  currentCursor += steps;
12300
12523
  }
12301
12524
  };
12302
- for (let i = 0; i < sortedSteps.length; i++) {
12303
- const startStep = sortedSteps[i];
12525
+ for (let i2 = 0; i2 < sortedSteps.length; i2++) {
12526
+ const startStep = sortedSteps[i2];
12304
12527
  const notes = notesByStep.get(startStep);
12305
12528
  if (!notes) continue;
12306
12529
  fillRests(startStep);
12307
- const nextStart = sortedSteps[i + 1] ?? endStep;
12530
+ const nextStart = sortedSteps[i2 + 1] ?? endStep;
12308
12531
  const physicsLimit = nextStart - currentCursor;
12309
12532
  if (physicsLimit < MIN_STEP) {
12310
12533
  continue;
@@ -12369,10 +12592,10 @@ var decomposeToMonophonic = (notes) => {
12369
12592
  for (const note of sorted) {
12370
12593
  let assigned = -1;
12371
12594
  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;
12595
+ for (let i2 = 0; i2 < tracks.length; i2++) {
12596
+ if (trackEnds[i2] <= note.startStep && trackEnds[i2] < minEnd) {
12597
+ minEnd = trackEnds[i2];
12598
+ assigned = i2;
12376
12599
  }
12377
12600
  }
12378
12601
  if (assigned === -1) {
@@ -12428,6 +12651,14 @@ var CHORD_INFO_HTML2 = `
12428
12651
  </ul>
12429
12652
  </div>
12430
12653
  `;
12654
+ var VIBRATO_INFO_HTML = `
12655
+ <div class="dtm-modal-body-content">
12656
+ <h4>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u306F</h4>
12657
+ <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>
12658
+ <p style="margin-top:4px;"><small>\u77ED\u3044\u97F3\u7B26\u306B\u306F\u639B\u304B\u308A\u307E\u305B\u3093\u30021\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</small></p>
12659
+ <p style="margin-top:4px;"><small>\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\u3057\u3066\u3044\u307E\u3059\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</small></p>
12660
+ </div>
12661
+ `;
12431
12662
  var MIDI_INFO_HTML = `
12432
12663
  <div class="dtm-modal-body-content">
12433
12664
  <h4>1. MIDI\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -12631,7 +12862,7 @@ var LYRIC_MODEL_CATEGORIES = [
12631
12862
  },
12632
12863
  {
12633
12864
  label: "\u304A\u3093J",
12634
- models: ["roze", "shiyo", "rino"]
12865
+ models: ["roze", "shiyo", "rino", "rino121", "uc"]
12635
12866
  },
12636
12867
  {
12637
12868
  label: "\u4E00\u822C",
@@ -12699,6 +12930,8 @@ var mountDAW = (target, options = {}) => {
12699
12930
  });
12700
12931
  refs.masterVolume.value = String(options.masterVolume ?? 50);
12701
12932
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
12933
+ refs.reverbAmount.value = String(options.reverbAmount ?? 0);
12934
+ refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
12702
12935
  refs.drumVolume.value = String(options.drumVolume ?? 80);
12703
12936
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
12704
12937
  const renderConfig = {
@@ -12714,6 +12947,8 @@ var mountDAW = (target, options = {}) => {
12714
12947
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
12715
12948
  let masterVolume = options.masterVolume ?? 50;
12716
12949
  options.singingVoices?.setVolume(masterVolume / 100);
12950
+ let reverbAmount = options.reverbAmount ?? 0;
12951
+ options.onReverbChange?.(reverbAmount);
12717
12952
  let drumVolume = options.drumVolume ?? 80;
12718
12953
  let currentDrumPattern = refs.drumSelect.value;
12719
12954
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -12791,7 +13026,9 @@ var mountDAW = (target, options = {}) => {
12791
13026
  vocalVolume: t.vocalVolume,
12792
13027
  vocalGate: t.vocalGate,
12793
13028
  vocalPan: t.vocalPan,
12794
- vocalOctave: t.vocalOctave
13029
+ vocalOctave: t.vocalOctave,
13030
+ vocalVibrato: t.vocalVibrato,
13031
+ vocalReverb: t.vocalReverb
12795
13032
  };
12796
13033
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
12797
13034
  lyricsDebounceTimer = setTimeout(() => {
@@ -12851,25 +13088,29 @@ var mountDAW = (target, options = {}) => {
12851
13088
  vocalGate: 100,
12852
13089
  vocalPan: 64,
12853
13090
  vocalOctave: 0,
13091
+ vocalVibrato: false,
13092
+ vocalReverb: 0,
12854
13093
  trackInstrument: ""
12855
13094
  };
12856
13095
  });
12857
13096
  };
12858
13097
  const buildLyricsMap = () => {
12859
13098
  const map = /* @__PURE__ */ new Map();
12860
- trackStates.forEach((t, i) => {
13099
+ trackStates.forEach((t, i2) => {
12861
13100
  const model = t.lyricModel.trim();
12862
13101
  const text = t.lyrics.trim();
12863
13102
  if (!model || !text) return;
12864
13103
  const syllables = normalizeLyrics(text);
12865
13104
  if (syllables.length === 0) return;
12866
- map.set(i, {
12867
- trackId: i,
13105
+ map.set(i2, {
13106
+ trackId: i2,
12868
13107
  model: model.toLowerCase(),
12869
13108
  volume: t.vocalVolume,
12870
13109
  gate: t.vocalGate,
12871
13110
  pan: t.vocalPan,
12872
13111
  octave: t.vocalOctave,
13112
+ vibrato: t.vocalVibrato,
13113
+ reverb: t.vocalReverb,
12873
13114
  syllables
12874
13115
  });
12875
13116
  });
@@ -12909,15 +13150,15 @@ var mountDAW = (target, options = {}) => {
12909
13150
  const ctx = getGridContext();
12910
13151
  const canvas = getGridCanvas();
12911
13152
  if (!ctx) return;
12912
- const x = playStartStep * renderConfig.stepWidth - currentOffsetX;
12913
- if (x < -10 || x > canvas.width + 10) return;
13153
+ const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
13154
+ if (x2 < -10 || x2 > canvas.width + 10) return;
12914
13155
  ctx.save();
12915
13156
  ctx.strokeStyle = "#ffec27";
12916
13157
  ctx.lineWidth = 2;
12917
13158
  ctx.setLineDash([4, 4]);
12918
13159
  ctx.beginPath();
12919
- ctx.moveTo(x, 0);
12920
- ctx.lineTo(x, canvas.height);
13160
+ ctx.moveTo(x2, 0);
13161
+ ctx.lineTo(x2, canvas.height);
12921
13162
  ctx.stroke();
12922
13163
  ctx.restore();
12923
13164
  };
@@ -12925,14 +13166,14 @@ var mountDAW = (target, options = {}) => {
12925
13166
  const ctx = getGridContext();
12926
13167
  const canvas = getGridCanvas();
12927
13168
  if (!ctx) return;
12928
- const x = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
12929
- if (x < 0 || x > canvas.width) return;
13169
+ const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
13170
+ if (x2 < 0 || x2 > canvas.width) return;
12930
13171
  ctx.save();
12931
13172
  ctx.strokeStyle = "#ff004d";
12932
13173
  ctx.lineWidth = 2;
12933
13174
  ctx.beginPath();
12934
- ctx.moveTo(x, 0);
12935
- ctx.lineTo(x, canvas.height);
13175
+ ctx.moveTo(x2, 0);
13176
+ ctx.lineTo(x2, canvas.height);
12936
13177
  ctx.stroke();
12937
13178
  ctx.restore();
12938
13179
  };
@@ -13060,8 +13301,8 @@ var mountDAW = (target, options = {}) => {
13060
13301
  if (maxOffsetX <= 0) return;
13061
13302
  const rect = refs.hScroll.getBoundingClientRect();
13062
13303
  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);
13304
+ const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13305
+ const ratio = x2 / (rect.width - thumbW);
13065
13306
  currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
13066
13307
  setDrawOffset(currentOffsetX, currentOffsetY);
13067
13308
  redrawAll();
@@ -13088,13 +13329,60 @@ var mountDAW = (target, options = {}) => {
13088
13329
  let selectionStart = null;
13089
13330
  let selectedOriginal = [];
13090
13331
  let lastMultiPreviewPitch = null;
13332
+ const AUTO_SCROLL_MARGIN = 30;
13333
+ const AUTO_SCROLL_MAX_SPEED = 22;
13334
+ let autoScrollRAF = null;
13335
+ let lastMoveEvent = null;
13336
+ const computeEdgeSpeed = (pos, size) => {
13337
+ if (pos < AUTO_SCROLL_MARGIN) {
13338
+ const t = (AUTO_SCROLL_MARGIN - pos) / AUTO_SCROLL_MARGIN;
13339
+ return -Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13340
+ }
13341
+ if (pos > size - AUTO_SCROLL_MARGIN) {
13342
+ const t = (pos - (size - AUTO_SCROLL_MARGIN)) / AUTO_SCROLL_MARGIN;
13343
+ return Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13344
+ }
13345
+ return 0;
13346
+ };
13347
+ const stopAutoScroll = () => {
13348
+ if (autoScrollRAF !== null) {
13349
+ cancelAnimationFrame(autoScrollRAF);
13350
+ autoScrollRAF = null;
13351
+ }
13352
+ lastMoveEvent = null;
13353
+ };
13354
+ const autoScrollTick = () => {
13355
+ autoScrollRAF = null;
13356
+ if (!isSelecting || !lastMoveEvent) return;
13357
+ const canvas = getGridCanvas();
13358
+ const { x: x2, y } = getGridPosition(lastMoveEvent);
13359
+ const dx = computeEdgeSpeed(x2, canvas.width);
13360
+ const dy = computeEdgeSpeed(y, canvas.height);
13361
+ if (dx !== 0 || dy !== 0) {
13362
+ const maxOffsetX = getMaxOffsetX();
13363
+ const maxOffsetY = getMaxOffsetY();
13364
+ currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13365
+ currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13366
+ setDrawOffset(currentOffsetX, currentOffsetY);
13367
+ onPointerMove(lastMoveEvent);
13368
+ }
13369
+ if (isSelecting) {
13370
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13371
+ }
13372
+ };
13373
+ const ensureAutoScroll = (event) => {
13374
+ lastMoveEvent = event;
13375
+ if (autoScrollRAF === null) {
13376
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13377
+ }
13378
+ };
13091
13379
  const playPreview = (pitch) => {
13092
13380
  if (isLoading) return;
13093
13381
  options.onResumeAudio?.();
13094
13382
  const active = getActive();
13095
13383
  dispatchNote(active.config.id, pitch, active.volume, 100, 0, 0.5);
13096
13384
  };
13097
- const findActiveNoteAt = (x, y, margin = 0) => {
13385
+ const findActiveNoteAt = (x2, y, margin = 0) => {
13098
13386
  const active = getActive();
13099
13387
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = renderConfig;
13100
13388
  const offset = getDrawOffset();
@@ -13105,7 +13393,7 @@ var mountDAW = (target, options = {}) => {
13105
13393
  const w = note.durationSteps * stepWidth;
13106
13394
  const renderX = logicalX - offset.x;
13107
13395
  const renderY = logicalY - offset.y;
13108
- if (x >= renderX - margin && x <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
13396
+ if (x2 >= renderX - margin && x2 <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
13109
13397
  return note;
13110
13398
  }
13111
13399
  return null;
@@ -13124,17 +13412,17 @@ var mountDAW = (target, options = {}) => {
13124
13412
  const onGridPointerDown = (event) => {
13125
13413
  event.preventDefault();
13126
13414
  options.onResumeAudio?.();
13127
- const { x, y, step, pitch } = getGridPosition(event);
13415
+ const { x: x2, y, step, pitch } = getGridPosition(event);
13128
13416
  const active = getActive();
13129
13417
  if (activeToolMode === "eraser") {
13130
13418
  if (isActiveLocked()) return;
13131
- const note = findActiveNoteAt(x, y);
13419
+ const note = findActiveNoteAt(x2, y);
13132
13420
  if (note) active.core.deleteNoteById(note.id);
13133
13421
  return;
13134
13422
  }
13135
13423
  if (activeToolMode === "select") {
13136
13424
  if (selectedNotes.length > 0) {
13137
- const clicked2 = findActiveNoteAt(x, y);
13425
+ const clicked2 = findActiveNoteAt(x2, y);
13138
13426
  if (clicked2 && selectedNotes.some((n) => n.id === clicked2.id)) {
13139
13427
  selectedOriginal = selectedNotes.map((n) => ({
13140
13428
  id: n.id,
@@ -13143,7 +13431,7 @@ var mountDAW = (target, options = {}) => {
13143
13431
  }));
13144
13432
  isSelecting = true;
13145
13433
  dragMode = "move";
13146
- selectionStart = { x, y, step, pitch };
13434
+ selectionStart = { x: x2, y, step, pitch };
13147
13435
  hasDragged = false;
13148
13436
  lastMultiPreviewPitch = null;
13149
13437
  return;
@@ -13151,7 +13439,7 @@ var mountDAW = (target, options = {}) => {
13151
13439
  selectedNotes = [];
13152
13440
  selectionRect = null;
13153
13441
  }
13154
- const clicked = findActiveNoteAt(x, y);
13442
+ const clicked = findActiveNoteAt(x2, y);
13155
13443
  if (clicked) {
13156
13444
  selectedNotes = [clicked];
13157
13445
  selectedOriginal = [
@@ -13169,19 +13457,19 @@ var mountDAW = (target, options = {}) => {
13169
13457
  isSelecting = true;
13170
13458
  dragMode = "rect";
13171
13459
  }
13172
- selectionStart = { x, y, step, pitch };
13460
+ selectionStart = { x: x2, y, step, pitch };
13173
13461
  hasDragged = false;
13174
13462
  return;
13175
13463
  }
13176
13464
  hasDragged = false;
13177
- const existing = findActiveNoteAt(x, y, TOUCH_HIT_MARGIN);
13465
+ const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
13178
13466
  if (existing) {
13179
13467
  playPreview(existing.pitch);
13180
13468
  const { stepWidth } = renderConfig;
13181
13469
  const offset = getDrawOffset();
13182
13470
  const renderX = existing.startStep * stepWidth - offset.x;
13183
13471
  const w = existing.durationSteps * stepWidth;
13184
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w) {
13472
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
13185
13473
  dragState = {
13186
13474
  noteId: existing.id,
13187
13475
  mode: "resize",
@@ -13260,12 +13548,13 @@ var mountDAW = (target, options = {}) => {
13260
13548
  return;
13261
13549
  }
13262
13550
  if (activeToolMode === "select" && isSelecting && selectionStart) {
13263
- const { x, y, step, pitch } = getGridPosition(event);
13551
+ ensureAutoScroll(event);
13552
+ const { x: x2, y, step, pitch } = getGridPosition(event);
13264
13553
  if (dragMode === "rect") {
13265
13554
  const rect = {
13266
- x: Math.min(x, selectionStart.x),
13555
+ x: Math.min(x2, selectionStart.x),
13267
13556
  y: Math.min(y, selectionStart.y),
13268
- width: Math.abs(x - selectionStart.x),
13557
+ width: Math.abs(x2 - selectionStart.x),
13269
13558
  height: Math.abs(y - selectionStart.y)
13270
13559
  };
13271
13560
  selectionRect = rect;
@@ -13278,7 +13567,7 @@ var mountDAW = (target, options = {}) => {
13278
13567
  const nx = logicalX - offset.x;
13279
13568
  const ny = logicalY - offset.y;
13280
13569
  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;
13570
+ return nx >= rect.x && nx + nw <= rect.x + rect.width && ny >= rect.y && ny + keyHeight <= rect.y + rect.height;
13282
13571
  });
13283
13572
  redrawAll();
13284
13573
  } else {
@@ -13335,8 +13624,11 @@ var mountDAW = (target, options = {}) => {
13335
13624
  selectionStart = null;
13336
13625
  hasDragged = false;
13337
13626
  lastMultiPreviewPitch = null;
13338
- selectionRect = null;
13627
+ if (dragMode !== "rect" || selectedNotes.length === 0) {
13628
+ selectionRect = null;
13629
+ }
13339
13630
  selectedOriginal = [];
13631
+ stopAutoScroll();
13340
13632
  redrawAll();
13341
13633
  }
13342
13634
  };
@@ -13484,8 +13776,8 @@ var mountDAW = (target, options = {}) => {
13484
13776
  headerCanvas.addEventListener("click", (event) => {
13485
13777
  if (playbackState === "playing") return;
13486
13778
  const rect = headerCanvas.getBoundingClientRect();
13487
- const x = event.clientX - rect.left;
13488
- const step = Math.floor((x + currentOffsetX) / renderConfig.stepWidth);
13779
+ const x2 = event.clientX - rect.left;
13780
+ const step = Math.floor((x2 + currentOffsetX) / renderConfig.stepWidth);
13489
13781
  playStartStep = Math.max(
13490
13782
  0,
13491
13783
  Math.floor(step / snapGridSteps) * snapGridSteps
@@ -13606,11 +13898,11 @@ var mountDAW = (target, options = {}) => {
13606
13898
  const semis = (lt.octave ?? 0) * 12;
13607
13899
  const count = Math.min(sorted.length, lt.syllables.length);
13608
13900
  const notes = [];
13609
- for (let i = 0; i < count; i++) {
13610
- const n = sorted[i];
13901
+ for (let i2 = 0; i2 < count; i2++) {
13902
+ const n = sorted[i2];
13611
13903
  if (n.startStep < fromStep) continue;
13612
13904
  notes.push({
13613
- syllable: lt.syllables[i],
13905
+ syllable: lt.syllables[i2],
13614
13906
  pitch: n.pitch + semis,
13615
13907
  startSec: (n.startStep - fromStep) * secondsPerStep,
13616
13908
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -13621,6 +13913,8 @@ var mountDAW = (target, options = {}) => {
13621
13913
  model: lt.model,
13622
13914
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
13623
13915
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
13916
+ vibrato: lt.vibrato,
13917
+ reverbSend: (lt.reverb ?? 0) / 100,
13624
13918
  notes
13625
13919
  };
13626
13920
  }) : [];
@@ -13709,13 +14003,13 @@ var mountDAW = (target, options = {}) => {
13709
14003
  const updateTrackPanel = () => {
13710
14004
  refs.trackTabs.innerHTML = "";
13711
14005
  trackPillEls.clear();
13712
- for (const [i, t] of trackStates.entries()) {
14006
+ for (const [i2, t] of trackStates.entries()) {
13713
14007
  const [r, g, b] = t.config.color;
13714
14008
  const btn = document.createElement("button");
13715
14009
  btn.className = `dtm-pill ${t.config.id === activeTrackId ? "dtm-pill--active" : ""}`;
13716
14010
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
13717
14011
  btn.title = t.config.name;
13718
- btn.textContent = String(i + 1);
14012
+ btn.textContent = String(i2 + 1);
13719
14013
  btn.addEventListener("click", () => switchTrack(t.config.id));
13720
14014
  refs.trackTabs.appendChild(btn);
13721
14015
  trackPillEls.set(t.config.id, btn);
@@ -13813,6 +14107,10 @@ var mountDAW = (target, options = {}) => {
13813
14107
  <option value="-1">-1 oct</option>
13814
14108
  <option value="-2">-2 oct</option>
13815
14109
  </select>
14110
+ <label class="dtm-label" style="display:inline-flex;align-items:center;gap:2px;white-space:nowrap" title="\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\u306B\u81EA\u52D5\u3067\u30D3\u30D6\u30E9\u30FC\u30C8\u3092\u639B\u3051\u307E\u3059">
14111
+ <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
14112
+ </label>
14113
+ <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
13816
14114
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
13817
14115
  </div>
13818
14116
  <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
@@ -13843,6 +14141,11 @@ var mountDAW = (target, options = {}) => {
13843
14141
  <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
14142
  <span class="dtm-label" data-dtm="lyric-pan-label"></span>
13845
14143
  </div>
14144
+ <div class="dtm-row">
14145
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14146
+ <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" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u306E\u30BB\u30F3\u30C9\u91CF">
14147
+ <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14148
+ </div>
13846
14149
  <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
14150
  </div>`;
13848
14151
  refs.trackBody.appendChild(lyricDiv);
@@ -13876,6 +14179,21 @@ var mountDAW = (target, options = {}) => {
13876
14179
  const lyricPanLabel = lyricDiv.querySelector(
13877
14180
  '[data-dtm="lyric-pan-label"]'
13878
14181
  );
14182
+ const lyricReverb = lyricDiv.querySelector(
14183
+ '[data-dtm="lyric-reverb"]'
14184
+ );
14185
+ const lyricReverbLabel = lyricDiv.querySelector(
14186
+ '[data-dtm="lyric-reverb-label"]'
14187
+ );
14188
+ const lyricVibrato = lyricDiv.querySelector(
14189
+ '[data-dtm="lyric-vibrato"]'
14190
+ );
14191
+ const lyricVibratoInfo = lyricDiv.querySelector(
14192
+ '[data-dtm="lyric-vibrato-info"]'
14193
+ );
14194
+ lyricVibratoInfo.addEventListener("click", () => {
14195
+ showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14196
+ });
13879
14197
  const lyricTerms = lyricDiv.querySelector(
13880
14198
  '[data-dtm="lyric-terms"]'
13881
14199
  );
@@ -13937,6 +14255,9 @@ var mountDAW = (target, options = {}) => {
13937
14255
  lyricVolLabel.textContent = String(active.vocalVolume);
13938
14256
  lyricPan.value = String(active.vocalPan);
13939
14257
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14258
+ lyricReverb.value = String(active.vocalReverb);
14259
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14260
+ lyricVibrato.checked = active.vocalVibrato;
13940
14261
  const updateLyricCount = () => {
13941
14262
  const n = normalizeLyrics(lyricInput.value).length;
13942
14263
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -14055,6 +14376,10 @@ var mountDAW = (target, options = {}) => {
14055
14376
  active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
14056
14377
  fireLyricsChange(active);
14057
14378
  });
14379
+ lyricVibrato.addEventListener("change", () => {
14380
+ active.vocalVibrato = lyricVibrato.checked;
14381
+ fireLyricsChange(active);
14382
+ });
14058
14383
  lyricInput.addEventListener("input", () => {
14059
14384
  active.lyrics = lyricInput.value;
14060
14385
  updateLyricCount();
@@ -14078,6 +14403,11 @@ var mountDAW = (target, options = {}) => {
14078
14403
  lyricPanLabel.textContent = fmtPan(64);
14079
14404
  fireLyricsChange(active);
14080
14405
  });
14406
+ lyricReverb.addEventListener("input", () => {
14407
+ active.vocalReverb = Number.parseInt(lyricReverb.value, 10);
14408
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14409
+ fireLyricsChange(active);
14410
+ });
14081
14411
  }
14082
14412
  if (active.config.id === "chord" && showChord) {
14083
14413
  const div = document.createElement("div");
@@ -14161,8 +14491,8 @@ var mountDAW = (target, options = {}) => {
14161
14491
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
14162
14492
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
14163
14493
  const trackInstrumentsForMeta = {};
14164
- trackStates.forEach((t, i) => {
14165
- if (t.trackInstrument) trackInstrumentsForMeta[i] = t.trackInstrument;
14494
+ trackStates.forEach((t, i2) => {
14495
+ if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14166
14496
  });
14167
14497
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14168
14498
  const metaLineFull = formatMmlMeta(
@@ -14197,10 +14527,10 @@ var mountDAW = (target, options = {}) => {
14197
14527
  const monoTracks = decomposeToMonophonic(allNotes);
14198
14528
  const refCore = trackStates[0].core;
14199
14529
  const decomposedFull = monoTracks.map(
14200
- (notes, i) => `@${i} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14530
+ (notes, i2) => `@${i2} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14201
14531
  );
14202
14532
  const decomposedMini = monoTracks.map(
14203
- (notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14533
+ (notes, i2) => `@${i2}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14204
14534
  );
14205
14535
  const full2 = [metaLineFull, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
14206
14536
  const minified2 = [metaLineMini, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
@@ -14214,34 +14544,38 @@ var mountDAW = (target, options = {}) => {
14214
14544
  }
14215
14545
  const trackLines = [];
14216
14546
  const trackLinesMini = [];
14217
- trackStates.forEach((t, i) => {
14547
+ trackStates.forEach((t, i2) => {
14218
14548
  const notes = clipNotes(t.core.getNotes());
14219
14549
  if (notes.length > 0) {
14220
14550
  const mml = t.core.getMMLFromNotes(notes, bpm, t.volume).trim();
14221
- trackLines.push(`@${i} ${mml}`);
14222
- trackLinesMini.push(`@${i}${mml.replace(/\s+/g, "")}`);
14551
+ trackLines.push(`@${i2} ${mml}`);
14552
+ trackLinesMini.push(`@${i2}${mml.replace(/\s+/g, "")}`);
14223
14553
  }
14224
14554
  });
14225
- const lyricLines = trackStates.map((t, i) => ({
14226
- i,
14555
+ const lyricLines = trackStates.map((t, i2) => ({
14556
+ i: i2,
14227
14557
  notes: clipNotes(t.core.getNotes()),
14228
14558
  text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
14229
14559
  model: t.lyricModel.trim(),
14230
14560
  vol: t.vocalVolume,
14231
14561
  gate: t.vocalGate,
14232
14562
  pan: t.vocalPan,
14233
- oct: t.vocalOctave
14563
+ oct: t.vocalOctave,
14564
+ vib: t.vocalVibrato,
14565
+ rev: t.vocalReverb
14234
14566
  })).filter(
14235
- (x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
14236
- ).map((x) => {
14567
+ (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14568
+ ).map((x2) => {
14237
14569
  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}`
14570
+ x2.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x2.vol}`,
14571
+ x2.gate === 100 ? "" : `q${x2.gate}`,
14572
+ x2.pan === 64 ? "" : `p${x2.pan}`,
14573
+ x2.oct === 0 ? "" : `o${x2.oct}`,
14574
+ x2.vib ? "b1" : "",
14575
+ x2.rev === 0 ? "" : `r${x2.rev}`
14242
14576
  ].filter((s) => s.length > 0).join(" ");
14243
- const head = params ? `${x.model} ${params}` : x.model;
14244
- return `@@${x.i} ${head} ${x.text}`;
14577
+ const head = params ? `${x2.model} ${params}` : x2.model;
14578
+ return `@@${x2.i} ${head} ${x2.text}`;
14245
14579
  });
14246
14580
  const customVocalDecls = [];
14247
14581
  for (const [key, def] of customVocalsMap) {
@@ -14379,17 +14713,17 @@ var mountDAW = (target, options = {}) => {
14379
14713
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
14380
14714
  }
14381
14715
  }
14382
- trackStates.forEach((t, i) => {
14383
- if (applyActiveOnly && i !== activeTrackIndex) return;
14384
- const name = normalizeInstrumentName(meta.trackInstruments?.[i] ?? "");
14716
+ trackStates.forEach((t, i2) => {
14717
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14718
+ const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
14385
14719
  if (t.trackInstrument !== name) {
14386
14720
  t.trackInstrument = name;
14387
- options.onTrackInstrumentChange?.(i, name);
14721
+ options.onTrackInstrumentChange?.(i2, name);
14388
14722
  }
14389
14723
  });
14390
- trackStates.forEach((t, i) => {
14391
- if (applyActiveOnly && i !== activeTrackIndex) return;
14392
- const v = trackVelocity.get(i);
14724
+ trackStates.forEach((t, i2) => {
14725
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14726
+ const v = trackVelocity.get(i2);
14393
14727
  if (v !== void 0 && v !== t.volume) {
14394
14728
  t.volume = v;
14395
14729
  t.core.setVolume(v);
@@ -14403,6 +14737,8 @@ var mountDAW = (target, options = {}) => {
14403
14737
  active.vocalGate = 100;
14404
14738
  active.vocalPan = 64;
14405
14739
  active.vocalOctave = 0;
14740
+ active.vocalVibrato = false;
14741
+ active.vocalReverb = 0;
14406
14742
  } else {
14407
14743
  for (const t of trackStates) {
14408
14744
  t.lyrics = "";
@@ -14411,6 +14747,8 @@ var mountDAW = (target, options = {}) => {
14411
14747
  t.vocalGate = 100;
14412
14748
  t.vocalPan = 64;
14413
14749
  t.vocalOctave = 0;
14750
+ t.vocalVibrato = false;
14751
+ t.vocalReverb = 0;
14414
14752
  }
14415
14753
  }
14416
14754
  lyrics?.forEach((lt) => {
@@ -14423,6 +14761,8 @@ var mountDAW = (target, options = {}) => {
14423
14761
  t.vocalGate = lt.gate;
14424
14762
  t.vocalPan = lt.pan;
14425
14763
  t.vocalOctave = lt.octave ?? 0;
14764
+ t.vocalVibrato = lt.vibrato ?? false;
14765
+ t.vocalReverb = lt.reverb ?? 0;
14426
14766
  });
14427
14767
  for (const p of placements) {
14428
14768
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -14501,6 +14841,8 @@ var mountDAW = (target, options = {}) => {
14501
14841
  active.vocalGate = 100;
14502
14842
  active.vocalPan = 64;
14503
14843
  active.vocalOctave = 0;
14844
+ active.vocalVibrato = false;
14845
+ active.vocalReverb = 0;
14504
14846
  } else {
14505
14847
  clearAll();
14506
14848
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -14511,6 +14853,8 @@ var mountDAW = (target, options = {}) => {
14511
14853
  t.vocalGate = 100;
14512
14854
  t.vocalPan = 64;
14513
14855
  t.vocalOctave = 0;
14856
+ t.vocalVibrato = false;
14857
+ t.vocalReverb = 0;
14514
14858
  }
14515
14859
  }
14516
14860
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -14720,6 +15064,11 @@ var mountDAW = (target, options = {}) => {
14720
15064
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
14721
15065
  options.singingVoices?.setVolume(masterVolume / 100);
14722
15066
  });
15067
+ refs.reverbAmount.addEventListener("input", () => {
15068
+ reverbAmount = Number.parseInt(refs.reverbAmount.value, 10) || 0;
15069
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
15070
+ options.onReverbChange?.(reverbAmount);
15071
+ });
14723
15072
  refs.drumSelect.addEventListener("change", () => {
14724
15073
  currentDrumPattern = refs.drumSelect.value;
14725
15074
  options.onDrumChange?.(currentDrumPattern);
@@ -15000,7 +15349,7 @@ var mountDAW = (target, options = {}) => {
15000
15349
  console.error(e);
15001
15350
  }
15002
15351
  refs.midiTrackSelection.innerHTML = `<span class="dtm-label">\u30C8\u30E9\u30C3\u30AF</span>`;
15003
- detectedTracks.forEach((t, i) => {
15352
+ detectedTracks.forEach((t, i2) => {
15004
15353
  const btn = document.createElement("button");
15005
15354
  btn.className = `dtm-btn ${t.selected ? "dtm-btn--primary" : "dtm-btn--ghost"}`;
15006
15355
  btn.dataset.selected = String(t.selected);
@@ -15012,7 +15361,7 @@ var mountDAW = (target, options = {}) => {
15012
15361
  btn.classList.toggle("dtm-btn--ghost", !on);
15013
15362
  });
15014
15363
  refs.midiTrackSelection.appendChild(btn);
15015
- if (i === 0) refs.midiTrackSelection.dataset.ready = "1";
15364
+ if (i2 === 0) refs.midiTrackSelection.dataset.ready = "1";
15016
15365
  });
15017
15366
  refs.midiTrackSelection.classList.remove("dtm-hidden");
15018
15367
  refs.overlay.hidden = true;
@@ -15022,9 +15371,9 @@ var mountDAW = (target, options = {}) => {
15022
15371
  if (!pendingMidi) return;
15023
15372
  const selected = [];
15024
15373
  const btns = refs.midiTrackSelection.querySelectorAll("button");
15025
- btns.forEach((b, i) => {
15374
+ btns.forEach((b, i2) => {
15026
15375
  if (b.dataset.selected === "true")
15027
- selected.push(detectedTracks[i].index);
15376
+ selected.push(detectedTracks[i2].index);
15028
15377
  });
15029
15378
  if (selected.length === 0) return;
15030
15379
  if (!isAdvanced && options.onRequestAdvancedMode && selected.length > trackStates.length) {
@@ -15058,7 +15407,7 @@ var mountDAW = (target, options = {}) => {
15058
15407
  const refCore = trackStates[0].core;
15059
15408
  const tempo = Math.round(parsedBpm) || bpm;
15060
15409
  const lines = [];
15061
- let i = 0;
15410
+ let i2 = 0;
15062
15411
  for (const notes of byTrack.values()) {
15063
15412
  const asNotes = notes.map((p) => ({
15064
15413
  id: 0,
@@ -15068,8 +15417,8 @@ var mountDAW = (target, options = {}) => {
15068
15417
  velocity: p.velocity
15069
15418
  }));
15070
15419
  const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
15071
- lines.push(`@${i} ${mml}`);
15072
- i++;
15420
+ lines.push(`@${i2} ${mml}`);
15421
+ i2++;
15073
15422
  }
15074
15423
  return [...lines, MML_END_MARKER].join(";\n");
15075
15424
  };
@@ -15684,6 +16033,12 @@ var mountDAW = (target, options = {}) => {
15684
16033
  refs.drumVolume.value = String(drumVolume);
15685
16034
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
15686
16035
  },
16036
+ setReverbAmount: (amount) => {
16037
+ reverbAmount = clamp3(amount, 0, 100);
16038
+ refs.reverbAmount.value = String(reverbAmount);
16039
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16040
+ options.onReverbChange?.(reverbAmount);
16041
+ },
15687
16042
  applyPatch: (trackId, added, removed) => {
15688
16043
  const track = trackStates.find((t) => t.config.id === trackId);
15689
16044
  if (!track) return;
@@ -15723,6 +16078,8 @@ var mountDAW = (target, options = {}) => {
15723
16078
  t.vocalGate = data.vocalGate;
15724
16079
  t.vocalPan = data.vocalPan;
15725
16080
  t.vocalOctave = data.vocalOctave;
16081
+ t.vocalVibrato = data.vocalVibrato ?? false;
16082
+ t.vocalReverb = data.vocalReverb ?? 0;
15726
16083
  },
15727
16084
  applyTrackInstrument: (trackIndex, instrumentName) => {
15728
16085
  const t = trackStates[trackIndex];
@@ -15733,17 +16090,17 @@ var mountDAW = (target, options = {}) => {
15733
16090
  },
15734
16091
  noteToCanvas: (step, pitch) => {
15735
16092
  const canvas = getGridCanvas();
15736
- const x = step * renderConfig.stepWidth - currentOffsetX;
16093
+ const x2 = step * renderConfig.stepWidth - currentOffsetX;
15737
16094
  const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
15738
- const onScreen = x >= 0 && x <= canvas.width && y >= 0 && y <= canvas.height;
16095
+ const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
15739
16096
  let side = null;
15740
16097
  if (!onScreen) {
15741
- if (x < 0) side = "left";
15742
- else if (x > canvas.width) side = "right";
16098
+ if (x2 < 0) side = "left";
16099
+ else if (x2 > canvas.width) side = "right";
15743
16100
  else if (y < 0) side = "top";
15744
16101
  else side = "bottom";
15745
16102
  }
15746
- return { x, y, onScreen, side };
16103
+ return { x: x2, y, onScreen, side };
15747
16104
  },
15748
16105
  destroy: () => {
15749
16106
  sequencer.stop();
@@ -15823,11 +16180,11 @@ var playSingingMML = async (mml, options = {}) => {
15823
16180
  const semis = (lt.octave ?? 0) * 12;
15824
16181
  const count = Math.min(sorted.length, lt.syllables.length);
15825
16182
  const notes = [];
15826
- for (let i = 0; i < count; i++) {
15827
- const n = sorted[i];
16183
+ for (let i2 = 0; i2 < count; i2++) {
16184
+ const n = sorted[i2];
15828
16185
  if (n.startStep < fromStep) continue;
15829
16186
  notes.push({
15830
- syllable: lt.syllables[i],
16187
+ syllable: lt.syllables[i2],
15831
16188
  pitch: n.pitch + semis,
15832
16189
  startSec: (n.startStep - fromStep) * secondsPerStep,
15833
16190
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -15838,6 +16195,8 @@ var playSingingMML = async (mml, options = {}) => {
15838
16195
  model: lt.model,
15839
16196
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
15840
16197
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16198
+ vibrato: lt.vibrato,
16199
+ reverbSend: (lt.reverb ?? 0) / 100,
15841
16200
  notes
15842
16201
  };
15843
16202
  });
@@ -16052,7 +16411,7 @@ var createPianoRoll = (options, handlers) => {
16052
16411
  let dragState = null;
16053
16412
  let hasDragged = false;
16054
16413
  let lastPreviewPitch = null;
16055
- const findNoteAtPosition = (x, y) => {
16414
+ const findNoteAtPosition = (x2, y) => {
16056
16415
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
16057
16416
  const offset = getDrawOffset();
16058
16417
  for (const note of core.getNotes()) {
@@ -16063,7 +16422,7 @@ var createPianoRoll = (options, handlers) => {
16063
16422
  const h = keyHeight;
16064
16423
  const renderX = logicalX - offset.x;
16065
16424
  const renderY = logicalY - offset.y;
16066
- if (x >= renderX && x <= renderX + w && y >= renderY && y <= renderY + h) {
16425
+ if (x2 >= renderX && x2 <= renderX + w && y >= renderY && y <= renderY + h) {
16067
16426
  return note;
16068
16427
  }
16069
16428
  }
@@ -16071,10 +16430,10 @@ var createPianoRoll = (options, handlers) => {
16071
16430
  };
16072
16431
  const handlePointerMove = (e) => {
16073
16432
  if (core.getToolMode() === "select" && isSelecting && selectionStart) {
16074
- const { x, y } = getGridPosition(e);
16075
- const minX = Math.min(x, selectionStart.x);
16433
+ const { x: x2, y } = getGridPosition(e);
16434
+ const minX = Math.min(x2, selectionStart.x);
16076
16435
  const minY = Math.min(y, selectionStart.y);
16077
- const width2 = Math.abs(x - selectionStart.x);
16436
+ const width2 = Math.abs(x2 - selectionStart.x);
16078
16437
  const height2 = Math.abs(y - selectionStart.y);
16079
16438
  selectionRect = { x: minX, y: minY, width: width2, height: height2 };
16080
16439
  selectedNotes = getNotesInRect(selectionRect);
@@ -16132,10 +16491,10 @@ var createPianoRoll = (options, handlers) => {
16132
16491
  }
16133
16492
  };
16134
16493
  gridCanvas.addEventListener("pointerdown", (e) => {
16135
- const { x, y, step, pitch } = getGridPosition(e);
16494
+ const { x: x2, y, step, pitch } = getGridPosition(e);
16136
16495
  const currentMode = core.getToolMode();
16137
16496
  if (currentMode === "select") {
16138
- const clickedNote = findNoteAtPosition(x, y);
16497
+ const clickedNote = findNoteAtPosition(x2, y);
16139
16498
  if (selectionRect && clickedNote) {
16140
16499
  const notesInRect = getNotesInRect(selectionRect);
16141
16500
  if (notesInRect.some((n) => n.id === clickedNote.id)) {
@@ -16156,10 +16515,10 @@ var createPianoRoll = (options, handlers) => {
16156
16515
  selectedNotes = [];
16157
16516
  selectionRect = null;
16158
16517
  isSelecting = true;
16159
- selectionStart = { x, y, step, pitch };
16518
+ selectionStart = { x: x2, y, step, pitch };
16160
16519
  return;
16161
16520
  }
16162
- const note = findNoteAtPosition(x, y);
16521
+ const note = findNoteAtPosition(x2, y);
16163
16522
  if (!note) return;
16164
16523
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
16165
16524
  const offset = getDrawOffset();
@@ -16169,7 +16528,7 @@ var createPianoRoll = (options, handlers) => {
16169
16528
  const renderX = logicalX - offset.x;
16170
16529
  const renderY = logicalY - offset.y;
16171
16530
  const w = note.durationSteps * stepWidth;
16172
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16531
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16173
16532
  dragState = {
16174
16533
  noteId: note.id,
16175
16534
  mode: "resize",
@@ -16445,6 +16804,25 @@ var isSupported = midiJsonParser.isSupported;
16445
16804
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
16446
16805
  URL.revokeObjectURL(url);
16447
16806
 
16807
+ // src/reverb.ts
16808
+ var IMPULSE_DURATION_SEC = 2.2;
16809
+ var IMPULSE_DECAY = 2.5;
16810
+ var createReverbImpulse = (ctx) => {
16811
+ const rate = ctx.sampleRate;
16812
+ const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
16813
+ const impulse = ctx.createBuffer(2, length, rate);
16814
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
16815
+ const data = impulse.getChannelData(ch);
16816
+ for (let i2 = 0; i2 < length; i2++) {
16817
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
16818
+ data[i2] = (Math.random() * 2 - 1) * envelope;
16819
+ }
16820
+ }
16821
+ return impulse;
16822
+ };
16823
+ var REVERB_MAX_WET = 0.6;
16824
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
16825
+
16448
16826
  // src/sf/SoundFont_drum.ts
16449
16827
  var touch = (map, key, ctor) => {
16450
16828
  if (!map.has(key)) map.set(key, new ctor());
@@ -16601,6 +16979,21 @@ var createDtmStudio = async (options = {}) => {
16601
16979
  const drumGain = audioCtx.createGain();
16602
16980
  drumGain.gain.value = options.drumVolume ?? 1;
16603
16981
  drumGain.connect(masterGain);
16982
+ const reverbConvolver = audioCtx.createConvolver();
16983
+ reverbConvolver.buffer = createReverbImpulse(audioCtx);
16984
+ reverbConvolver.normalize = true;
16985
+ const reverbWetGain = audioCtx.createGain();
16986
+ reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
16987
+ masterGain.connect(reverbConvolver);
16988
+ reverbConvolver.connect(reverbWetGain);
16989
+ reverbWetGain.connect(audioCtx.destination);
16990
+ const setReverbAmount = (amount) => {
16991
+ reverbWetGain.gain.setTargetAtTime(
16992
+ reverbAmountToGain(amount),
16993
+ audioCtx.currentTime,
16994
+ 0.02
16995
+ );
16996
+ };
16604
16997
  const resumeAudio = () => {
16605
16998
  if (audioCtx.state === "closed") return Promise.resolve();
16606
16999
  return audioCtx.resume();
@@ -16631,7 +17024,10 @@ var createDtmStudio = async (options = {}) => {
16631
17024
  const singingVoices = createSingingVoices(audioCtx, masterGain, {
16632
17025
  voiceWorkerUrl,
16633
17026
  voicebanks,
16634
- worldlineScriptUrl: options.worldlineScriptUrl
17027
+ worldlineScriptUrl: options.worldlineScriptUrl,
17028
+ // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17029
+ // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17030
+ reverbBus: reverbConvolver
16635
17031
  });
16636
17032
  const listReady = new Promise((resolve) => {
16637
17033
  sfList.init();
@@ -16906,6 +17302,8 @@ var createDtmStudio = async (options = {}) => {
16906
17302
  void handleTrackInstrumentChange(idx, name);
16907
17303
  externalOnTrackInstrumentChange?.(idx, name);
16908
17304
  },
17305
+ reverbAmount: options.reverbAmount,
17306
+ onReverbChange: setReverbAmount,
16909
17307
  ...dawOverrides,
16910
17308
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
16911
17309
  onDrumChange: (name) => {
@@ -16963,8 +17361,8 @@ var createDtmStudio = async (options = {}) => {
16963
17361
  presetSelect?.destroy();
16964
17362
  if (editorPresetSelects.get(target) === presetSelect)
16965
17363
  editorPresetSelects.delete(target);
16966
- const i = mountedEditors.indexOf(daw);
16967
- if (i >= 0) mountedEditors.splice(i, 1);
17364
+ const i2 = mountedEditors.indexOf(daw);
17365
+ if (i2 >= 0) mountedEditors.splice(i2, 1);
16968
17366
  };
16969
17367
  return {
16970
17368
  ...daw,
@@ -17067,8 +17465,8 @@ var createDtmStudio = async (options = {}) => {
17067
17465
  destroy: () => {
17068
17466
  doUnmount();
17069
17467
  wrapper.remove();
17070
- const i = mountedModeSwitches.indexOf(instance);
17071
- if (i >= 0) mountedModeSwitches.splice(i, 1);
17468
+ const i2 = mountedModeSwitches.indexOf(instance);
17469
+ if (i2 >= 0) mountedModeSwitches.splice(i2, 1);
17072
17470
  }
17073
17471
  };
17074
17472
  mountedModeSwitches.push(instance);
@@ -17155,8 +17553,8 @@ var createDtmStudio = async (options = {}) => {
17155
17553
  mountedPlayers.push(player);
17156
17554
  const destroy = () => {
17157
17555
  player.destroy();
17158
- const i = mountedPlayers.indexOf(player);
17159
- if (i >= 0) mountedPlayers.splice(i, 1);
17556
+ const i2 = mountedPlayers.indexOf(player);
17557
+ if (i2 >= 0) mountedPlayers.splice(i2, 1);
17160
17558
  };
17161
17559
  return { ...player, destroy };
17162
17560
  };
@@ -17417,8 +17815,8 @@ var createDtmStudio = async (options = {}) => {
17417
17815
  mountedChordPlayers.push(player);
17418
17816
  const destroy = () => {
17419
17817
  player.destroy();
17420
- const i = mountedChordPlayers.indexOf(player);
17421
- if (i >= 0) mountedChordPlayers.splice(i, 1);
17818
+ const i2 = mountedChordPlayers.indexOf(player);
17819
+ if (i2 >= 0) mountedChordPlayers.splice(i2, 1);
17422
17820
  };
17423
17821
  return {
17424
17822
  ...player,
@@ -17473,6 +17871,7 @@ var createDtmStudio = async (options = {}) => {
17473
17871
  mountModeSwitch,
17474
17872
  setMasterVolume,
17475
17873
  setVolume: setMasterVolume,
17874
+ setReverbAmount,
17476
17875
  dispose
17477
17876
  };
17478
17877
  };
@@ -17504,6 +17903,7 @@ var createDtmStudio = async (options = {}) => {
17504
17903
  PREWARM_NOTES,
17505
17904
  TRACKS_ADVANCED,
17506
17905
  TRACKS_SIMPLE,
17906
+ VIBRATO_MIN_SEC,
17507
17907
  VOICE_IMAGES,
17508
17908
  VOICE_IMAGE_KEY,
17509
17909
  analyzeMidiTracks,