@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.mjs CHANGED
@@ -219,8 +219,8 @@ var Input = class _Input {
219
219
  }
220
220
  return str.length ? Number(str) : null;
221
221
  }
222
- slice(i) {
223
- return this.str.slice(this.idx, this.idx + i);
222
+ slice(i2) {
223
+ return this.str.slice(this.idx, this.idx + i2);
224
224
  }
225
225
  };
226
226
  var Output = class {
@@ -256,8 +256,8 @@ var Matcher = class {
256
256
  else this._set(key, value);
257
257
  }
258
258
  parse(input) {
259
- for (const i of this.lengths) {
260
- const s = input.slice(i);
259
+ for (const i2 of this.lengths) {
260
+ const s = input.slice(i2);
261
261
  if (this.map.has(s)) {
262
262
  input.idx += s.length;
263
263
  return this.map.get(s);
@@ -311,7 +311,7 @@ var parseFormula = (input, output = new Output(), nest = 0) => {
311
311
  if (o.isChord) {
312
312
  output.value = [...o.value].concat(v);
313
313
  } else {
314
- const a = v.sort((x, y) => x - y);
314
+ const a = v.sort((x2, y) => x2 - y);
315
315
  const pitch = (o.pitch + 3) % 12 - 3;
316
316
  if (a[0] < pitch) {
317
317
  while (a[0] < pitch) a.push(a.shift() + 12);
@@ -348,16 +348,16 @@ halfMatcher.set("+", 1);
348
348
  halfMatcher.set("-", -1);
349
349
  var parseHalf = (input, isPitch = false) => (isPitch ? halfMatcherStrict : halfMatcher).parse(input);
350
350
  var idx2pitch = [0, 2, 4, 5, 7, 9, 11];
351
- for (const i of [...idx2pitch.keys()]) idx2pitch.push(idx2pitch[i] + 12);
351
+ for (const i2 of [...idx2pitch.keys()]) idx2pitch.push(idx2pitch[i2] + 12);
352
352
  var deg2pitch = (deg) => idx2pitch[deg - 1];
353
353
  var pitchMatcher = new Matcher();
354
- for (const [i, v] of [..."CDEFGAB"].entries())
355
- pitchMatcher.set(v, idx2pitch[i]);
354
+ for (const [i2, v] of [..."CDEFGAB"].entries())
355
+ pitchMatcher.set(v, idx2pitch[i2]);
356
356
  var parsePitch = (input, output) => {
357
357
  const pitch = pitchMatcher.parse(input);
358
358
  if (pitch === null) err(input, "Not found pitch");
359
359
  output.pitch = pitch;
360
- for (let i = 0; i < 2; i++) {
360
+ for (let i2 = 0; i2 < 2; i2++) {
361
361
  const half = parseHalf(input, true);
362
362
  if (half === null) break;
363
363
  output.pitch += half;
@@ -563,11 +563,11 @@ var pearson = (a, b) => {
563
563
  let num = 0;
564
564
  let da = 0;
565
565
  let db = 0;
566
- for (let i = 0; i < a.length; i++) {
567
- const x = a[i] - ma;
568
- const y = b[i] - mb;
569
- num += x * y;
570
- da += x * x;
566
+ for (let i2 = 0; i2 < a.length; i2++) {
567
+ const x2 = a[i2] - ma;
568
+ const y = b[i2] - mb;
569
+ num += x2 * y;
570
+ da += x2 * x2;
571
571
  db += y * y;
572
572
  }
573
573
  const den = Math.sqrt(da * db);
@@ -643,19 +643,19 @@ var coalesce = (segments) => {
643
643
  var mergeShortSegments = (segments, min) => {
644
644
  if (min <= 0) return segments;
645
645
  const result = segments.map((s) => ({ ...s }));
646
- let i = 0;
647
- while (i < result.length && result.length > 1) {
648
- if (result[i].duration >= min) {
649
- i++;
646
+ let i2 = 0;
647
+ while (i2 < result.length && result.length > 1) {
648
+ if (result[i2].duration >= min) {
649
+ i2++;
650
650
  continue;
651
651
  }
652
- if (i > 0) {
653
- result[i - 1].duration += result[i].duration;
654
- result.splice(i, 1);
652
+ if (i2 > 0) {
653
+ result[i2 - 1].duration += result[i2].duration;
654
+ result.splice(i2, 1);
655
655
  } else {
656
- result[i + 1].when = result[i].when;
657
- result[i + 1].duration += result[i].duration;
658
- result.splice(i, 1);
656
+ result[i2 + 1].when = result[i2].when;
657
+ result[i2 + 1].duration += result[i2].duration;
658
+ result.splice(i2, 1);
659
659
  }
660
660
  }
661
661
  return coalesce(result);
@@ -874,19 +874,19 @@ var viterbi = (emissions, changePenalty) => {
874
874
  const curr = new Array(N).fill(0);
875
875
  const em = emissions[t];
876
876
  const switchVal = bestPrevVal - changePenalty;
877
- for (let i = 0; i < N; i++) {
878
- if (prev[i] >= switchVal) {
879
- curr[i] = em[i] + prev[i];
880
- back[t][i] = i;
877
+ for (let i2 = 0; i2 < N; i2++) {
878
+ if (prev[i2] >= switchVal) {
879
+ curr[i2] = em[i2] + prev[i2];
880
+ back[t][i2] = i2;
881
881
  } else {
882
- curr[i] = em[i] + switchVal;
883
- back[t][i] = bestPrevIdx;
882
+ curr[i2] = em[i2] + switchVal;
883
+ back[t][i2] = bestPrevIdx;
884
884
  }
885
885
  }
886
886
  prev = curr;
887
887
  }
888
888
  let bestIdx = 0;
889
- for (let i = 1; i < N; i++) if (prev[i] > prev[bestIdx]) bestIdx = i;
889
+ for (let i2 = 1; i2 < N; i2++) if (prev[i2] > prev[bestIdx]) bestIdx = i2;
890
890
  const path = new Array(T).fill(0);
891
891
  path[T - 1] = bestIdx;
892
892
  for (let t = T - 1; t > 0; t--) path[t - 1] = back[t][path[t]];
@@ -983,20 +983,20 @@ var parseChords = (str, bpm = 120) => {
983
983
  if (!str2.length) continue;
984
984
  const when = idx++ * secBar;
985
985
  const a = [];
986
- for (let i = 0; i < str2.length; i++) {
987
- const char = str2[i];
988
- const prev = str2[i - 1];
989
- const prev2 = str2.slice(i - 2, i);
986
+ for (let i2 = 0; i2 < str2.length; i2++) {
987
+ const char = str2[i2];
988
+ const prev = str2[i2 - 1];
989
+ const prev2 = str2.slice(i2 - 2, i2);
990
990
  if (!frontChars.has(char)) continue;
991
991
  if (prev === "/" || prev2 === "on") continue;
992
992
  if (prev2 === "N." && char === "C") continue;
993
- a.push(i);
993
+ a.push(i2);
994
994
  }
995
995
  if (!a.length) continue;
996
996
  const divide = 2 ** Math.ceil(Math.log2(a.length));
997
997
  const unitTime = secBar / divide;
998
- for (const [i, v] of a.entries()) {
999
- const s = str2.slice(v, i === a.length - 1 ? str2.length : a[i + 1]).replace(/\s+/g, "");
998
+ for (const [i2, v] of a.entries()) {
999
+ const s = str2.slice(v, i2 === a.length - 1 ? str2.length : a[i2 + 1]).replace(/\s+/g, "");
1000
1000
  const c = s[0];
1001
1001
  if (c === "_" || c === "N") {
1002
1002
  last = null;
@@ -1006,7 +1006,7 @@ var parseChords = (str, bpm = 120) => {
1006
1006
  if (last) last.duration += unitTime;
1007
1007
  continue;
1008
1008
  }
1009
- const _when = when + i * unitTime;
1009
+ const _when = when + i2 * unitTime;
1010
1010
  if (c === "%") {
1011
1011
  if (last === null) continue;
1012
1012
  const base = last;
@@ -1171,19 +1171,19 @@ var resolveDrumPattern = (name, dict, currentBar) => {
1171
1171
  if (Array.isArray(patternObj) && patternObj.length > 0 && "ranges" in patternObj[0]) {
1172
1172
  const songDef = patternObj;
1173
1173
  const instructions = songDef.filter(
1174
- (i) => i.ranges.some(([s, e]) => currentBar >= s && currentBar <= e)
1174
+ (i2) => i2.ranges.some(([s, e]) => currentBar >= s && currentBar <= e)
1175
1175
  );
1176
1176
  if (instructions.length > 0) {
1177
- return instructions.flatMap((i) => {
1178
- const maxStep = Math.max(...i.pattern.map((p) => p.step), 0);
1179
- const loopLengthBars = i.patternBars ?? Math.max(1, Math.ceil((maxStep + 1) / 192));
1180
- const range = i.ranges.find(
1177
+ return instructions.flatMap((i2) => {
1178
+ const maxStep = Math.max(...i2.pattern.map((p) => p.step), 0);
1179
+ const loopLengthBars = i2.patternBars ?? Math.max(1, Math.ceil((maxStep + 1) / 192));
1180
+ const range = i2.ranges.find(
1181
1181
  ([s, e]) => currentBar >= s && currentBar <= e
1182
1182
  );
1183
1183
  const startBar = range ? range[0] : 1;
1184
1184
  const barInLoop = (currentBar - startBar) % loopLengthBars;
1185
1185
  const stepOffset = barInLoop * 192;
1186
- return i.pattern.filter((p) => p.step >= stepOffset && p.step < stepOffset + 192).map((p) => ({ ...p, step: p.step - stepOffset }));
1186
+ return i2.pattern.filter((p) => p.step >= stepOffset && p.step < stepOffset + 192).map((p) => ({ ...p, step: p.step - stepOffset }));
1187
1187
  });
1188
1188
  }
1189
1189
  }
@@ -1326,8 +1326,8 @@ var getDrumPatternKeys = (name, dict) => {
1326
1326
  const keys = /* @__PURE__ */ new Set();
1327
1327
  if (Array.isArray(patternObj) && patternObj.length > 0 && "ranges" in patternObj[0]) {
1328
1328
  const songDef = patternObj;
1329
- for (const i of songDef) {
1330
- for (const p of i.pattern) keys.add(p.pitch);
1329
+ for (const i2 of songDef) {
1330
+ for (const p of i2.pattern) keys.add(p.pitch);
1331
1331
  }
1332
1332
  } else if (Array.isArray(patternObj)) {
1333
1333
  for (const p of patternObj) keys.add(p.pitch);
@@ -1384,21 +1384,21 @@ var buildChordPlacements = (options) => {
1384
1384
  }
1385
1385
  } else if (patternType === "arpeggio") {
1386
1386
  const arpInterval = Math.floor(noteLength / notes.length);
1387
- notes.forEach((noteOffset, i) => {
1387
+ notes.forEach((noteOffset, i2) => {
1388
1388
  placements.push({
1389
- startStep: chord.whenStep + i * arpInterval,
1389
+ startStep: chord.whenStep + i2 * arpInterval,
1390
1390
  pitch: C3 + noteOffset + offset,
1391
- durationSteps: noteLength - i * arpInterval,
1391
+ durationSteps: noteLength - i2 * arpInterval,
1392
1392
  velocity: 100
1393
1393
  });
1394
1394
  });
1395
1395
  } else if (patternType === "arpeggio-fast") {
1396
1396
  const arpInterval = 6;
1397
- notes.forEach((noteOffset, i) => {
1397
+ notes.forEach((noteOffset, i2) => {
1398
1398
  placements.push({
1399
- startStep: chord.whenStep + i * arpInterval,
1399
+ startStep: chord.whenStep + i2 * arpInterval,
1400
1400
  pitch: C3 + noteOffset + offset,
1401
- durationSteps: Math.max(12, noteLength - i * arpInterval),
1401
+ durationSteps: Math.max(12, noteLength - i2 * arpInterval),
1402
1402
  velocity: 100
1403
1403
  });
1404
1404
  });
@@ -1438,8 +1438,8 @@ var buildChordPlacements = (options) => {
1438
1438
  }
1439
1439
  }
1440
1440
  } else if (patternType === "alternating") {
1441
- notes.forEach((noteOffset, i) => {
1442
- const stepOffset = i * Math.floor(stepsPerBar / 4);
1441
+ notes.forEach((noteOffset, i2) => {
1442
+ const stepOffset = i2 * Math.floor(stepsPerBar / 4);
1443
1443
  placements.push({
1444
1444
  startStep: chord.whenStep + stepOffset,
1445
1445
  pitch: C3 + noteOffset + offset,
@@ -1461,8 +1461,8 @@ var buildChordPlacements = (options) => {
1461
1461
  }
1462
1462
  if (notes.length === 0) return;
1463
1463
  const startStep = barIndex * chordLength;
1464
- notes.forEach((noteOffset, i) => {
1465
- const stepOffset = i * 3;
1464
+ notes.forEach((noteOffset, i2) => {
1465
+ const stepOffset = i2 * 3;
1466
1466
  placements.push({
1467
1467
  startStep: startStep + stepOffset,
1468
1468
  pitch: C3 + noteOffset + offset,
@@ -1475,7 +1475,136 @@ var buildChordPlacements = (options) => {
1475
1475
  return placements;
1476
1476
  };
1477
1477
 
1478
- // node_modules/.pnpm/@onjmin+koe@1.0.4/node_modules/@onjmin/koe/dist/index.js
1478
+ // node_modules/.pnpm/@onjmin+koe@1.0.5/node_modules/@onjmin/koe/dist/index.js
1479
+ var u8 = Uint8Array;
1480
+ var u16 = Uint16Array;
1481
+ var i32 = Int32Array;
1482
+ var fleb = new u8([
1483
+ 0,
1484
+ 0,
1485
+ 0,
1486
+ 0,
1487
+ 0,
1488
+ 0,
1489
+ 0,
1490
+ 0,
1491
+ 1,
1492
+ 1,
1493
+ 1,
1494
+ 1,
1495
+ 2,
1496
+ 2,
1497
+ 2,
1498
+ 2,
1499
+ 3,
1500
+ 3,
1501
+ 3,
1502
+ 3,
1503
+ 4,
1504
+ 4,
1505
+ 4,
1506
+ 4,
1507
+ 5,
1508
+ 5,
1509
+ 5,
1510
+ 5,
1511
+ 0,
1512
+ /* unused */
1513
+ 0,
1514
+ 0,
1515
+ /* impossible */
1516
+ 0
1517
+ ]);
1518
+ var fdeb = new u8([
1519
+ 0,
1520
+ 0,
1521
+ 0,
1522
+ 0,
1523
+ 1,
1524
+ 1,
1525
+ 2,
1526
+ 2,
1527
+ 3,
1528
+ 3,
1529
+ 4,
1530
+ 4,
1531
+ 5,
1532
+ 5,
1533
+ 6,
1534
+ 6,
1535
+ 7,
1536
+ 7,
1537
+ 8,
1538
+ 8,
1539
+ 9,
1540
+ 9,
1541
+ 10,
1542
+ 10,
1543
+ 11,
1544
+ 11,
1545
+ 12,
1546
+ 12,
1547
+ 13,
1548
+ 13,
1549
+ /* unused */
1550
+ 0,
1551
+ 0
1552
+ ]);
1553
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
1554
+ var freb = function(eb, start) {
1555
+ var b = new u16(31);
1556
+ for (var i2 = 0; i2 < 31; ++i2) {
1557
+ b[i2] = start += 1 << eb[i2 - 1];
1558
+ }
1559
+ var r = new i32(b[30]);
1560
+ for (var i2 = 1; i2 < 30; ++i2) {
1561
+ for (var j = b[i2]; j < b[i2 + 1]; ++j) {
1562
+ r[j] = j - b[i2] << 5 | i2;
1563
+ }
1564
+ }
1565
+ return { b, r };
1566
+ };
1567
+ var _a = freb(fleb, 2);
1568
+ var fl = _a.b;
1569
+ var revfl = _a.r;
1570
+ fl[28] = 258, revfl[258] = 28;
1571
+ var _b = freb(fdeb, 0);
1572
+ var fd = _b.b;
1573
+ var revfd = _b.r;
1574
+ var rev = new u16(32768);
1575
+ for (i = 0; i < 32768; ++i) {
1576
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
1577
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
1578
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
1579
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
1580
+ }
1581
+ var x;
1582
+ var i;
1583
+ var flt = new u8(288);
1584
+ for (i = 0; i < 144; ++i)
1585
+ flt[i] = 8;
1586
+ var i;
1587
+ for (i = 144; i < 256; ++i)
1588
+ flt[i] = 9;
1589
+ var i;
1590
+ for (i = 256; i < 280; ++i)
1591
+ flt[i] = 7;
1592
+ var i;
1593
+ for (i = 280; i < 288; ++i)
1594
+ flt[i] = 8;
1595
+ var i;
1596
+ var fdt = new u8(32);
1597
+ for (i = 0; i < 32; ++i)
1598
+ fdt[i] = 5;
1599
+ var i;
1600
+ var et = /* @__PURE__ */ new u8(0);
1601
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
1602
+ var tds = 0;
1603
+ try {
1604
+ td.decode(et, { stream: true });
1605
+ tds = 1;
1606
+ } catch (e) {
1607
+ }
1479
1608
  var MAGIC = 1263486208;
1480
1609
  function parseKoeHeader(headerBytes) {
1481
1610
  const view = new DataView(headerBytes);
@@ -1641,7 +1770,7 @@ var VoiceBank = class _VoiceBank {
1641
1770
  if (!buf) return null;
1642
1771
  const int16 = new Int16Array(buf, 0, Math.floor(buf.byteLength / 2));
1643
1772
  const f64 = new Float64Array(int16.length);
1644
- for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;
1773
+ for (let i2 = 0; i2 < int16.length; i2++) f64[i2] = int16[i2] / 32768;
1645
1774
  return f64;
1646
1775
  }
1647
1776
  };
@@ -1649,6 +1778,7 @@ var WORLDLINE_SAMPLE_RATE = 48e3;
1649
1778
  var MIN_WORLDLINE_SAMPLES = 4096;
1650
1779
  var SYNTH_REQ_SIZE = 120;
1651
1780
  var WL_FRAME_MS = 10;
1781
+ var sampleCurve = (input, curve, totalMs) => typeof curve === "function" ? curve(input, totalMs) : curve;
1652
1782
  var samplesToMs = (samples) => samples / WORLDLINE_SAMPLE_RATE * 1e3;
1653
1783
  function leadInFromEntry(entry) {
1654
1784
  return {
@@ -1734,11 +1864,23 @@ var Worldline = class _Worldline {
1734
1864
  * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
1735
1865
  */
1736
1866
  renderNote(params) {
1737
- const { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;
1867
+ const {
1868
+ pcm,
1869
+ pitch,
1870
+ durationMs,
1871
+ preMs,
1872
+ consonantMs,
1873
+ tempo = 120,
1874
+ gender = 0.5,
1875
+ tension = 0.5,
1876
+ breathiness = 0.5,
1877
+ voicing = 1
1878
+ } = params;
1738
1879
  if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
1739
1880
  const WL = this.wasm;
1740
1881
  const FS = WORLDLINE_SAMPLE_RATE;
1741
- const midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));
1882
+ const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
1883
+ const midiNote = Math.round(69 + 12 * Math.log2(basePitch / 440));
1742
1884
  const posMs = 0;
1743
1885
  const reqLen = preMs + durationMs;
1744
1886
  const cutMs = WL_FRAME_MS * 2;
@@ -1784,11 +1926,19 @@ var Worldline = class _Worldline {
1784
1926
  WL._free(reqPtr);
1785
1927
  const totalMs = posMs + reqLen + WL_FRAME_MS * 2;
1786
1928
  const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
1787
- const f0Arr = new Float64Array(nFrames).fill(pitch);
1788
- const gArr = new Float64Array(nFrames).fill(0.5);
1789
- const tArr = new Float64Array(nFrames).fill(0.5);
1790
- const bArr = new Float64Array(nFrames).fill(0.5);
1791
- const vArr = new Float64Array(nFrames).fill(1);
1929
+ const f0Arr = new Float64Array(nFrames);
1930
+ const gArr = new Float64Array(nFrames);
1931
+ const tArr = new Float64Array(nFrames);
1932
+ const bArr = new Float64Array(nFrames);
1933
+ const vArr = new Float64Array(nFrames);
1934
+ for (let i2 = 0; i2 < nFrames; i2++) {
1935
+ const tMs = i2 * WL_FRAME_MS;
1936
+ f0Arr[i2] = sampleCurve(tMs, pitch, totalMs);
1937
+ gArr[i2] = sampleCurve(tMs, gender, totalMs);
1938
+ tArr[i2] = sampleCurve(tMs, tension, totalMs);
1939
+ bArr[i2] = sampleCurve(tMs, breathiness, totalMs);
1940
+ vArr[i2] = sampleCurve(tMs, voicing, totalMs);
1941
+ }
1792
1942
  const f0Ptr = WL._malloc(nFrames * 8);
1793
1943
  const gPtr = WL._malloc(nFrames * 8);
1794
1944
  const tPtr = WL._malloc(nFrames * 8);
@@ -1848,6 +1998,19 @@ var DEFAULT_PLAYBACK_VELOCITY = 127;
1848
1998
  var DEFAULT_STEPS_PER_BAR = 192;
1849
1999
  var MML_END_MARKER = "#end;";
1850
2000
 
2001
+ // src/vibrato.ts
2002
+ var VIBRATO_MIN_SEC = 0.35;
2003
+ var VIBRATO_RATE_HZ = 5.5;
2004
+ var VIBRATO_DEPTH_CENTS = 35;
2005
+ var VIBRATO_FADE_MS = 150;
2006
+ var vibratoPitchCurve = (baseHz, preMs) => (tMs) => {
2007
+ const sinceOnset = tMs - preMs;
2008
+ if (sinceOnset <= 0) return baseHz;
2009
+ const depth = VIBRATO_DEPTH_CENTS * Math.min(1, sinceOnset / VIBRATO_FADE_MS);
2010
+ const cents = depth * Math.sin(2 * Math.PI * VIBRATO_RATE_HZ * sinceOnset / 1e3);
2011
+ return baseHz * 2 ** (cents / 1200);
2012
+ };
2013
+
1851
2014
  // src/lyrics.ts
1852
2015
  var kanaTable = {
1853
2016
  \u3042: ["", "a"],
@@ -2012,8 +2175,8 @@ var vocalVolumeToGain = (v) => {
2012
2175
  var parseLyrics = (mml) => {
2013
2176
  const tracks = /* @__PURE__ */ new Map();
2014
2177
  const segments = splitSegments(mml);
2015
- for (let i = 0; i < segments.length; i++) {
2016
- const m = segments[i].match(LYRIC_LINE);
2178
+ for (let i2 = 0; i2 < segments.length; i2++) {
2179
+ const m = segments[i2].match(LYRIC_LINE);
2017
2180
  if (!m) continue;
2018
2181
  const trackId = Number.parseInt(m[1], 10);
2019
2182
  let rest = m[2].trim();
@@ -2021,8 +2184,10 @@ var parseLyrics = (mml) => {
2021
2184
  let gate = 100;
2022
2185
  let pan = 64;
2023
2186
  let octave = 0;
2187
+ let vibrato = false;
2188
+ let reverb = 0;
2024
2189
  const modelMatch = rest.match(
2025
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpo]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2190
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobr]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2026
2191
  );
2027
2192
  let model = "";
2028
2193
  const metaTokens = [];
@@ -2063,11 +2228,25 @@ var parseLyrics = (mml) => {
2063
2228
  rest = rest.substring(oMatch[0].length).trim();
2064
2229
  continue;
2065
2230
  }
2231
+ const bMatch = rest.match(/^b([01])/i);
2232
+ if (bMatch) {
2233
+ vibrato = bMatch[1] === "1";
2234
+ metaTokens.push(bMatch[0]);
2235
+ rest = rest.substring(bMatch[0].length).trim();
2236
+ continue;
2237
+ }
2238
+ const rMatch = rest.match(/^r(\d+)/i);
2239
+ if (rMatch) {
2240
+ reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2241
+ metaTokens.push(rMatch[0]);
2242
+ rest = rest.substring(rMatch[0].length).trim();
2243
+ continue;
2244
+ }
2066
2245
  break;
2067
2246
  }
2068
2247
  const lyricLines = [rest];
2069
- while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
2070
- lyricLines.push(segments[++i]);
2248
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1])) {
2249
+ lyricLines.push(segments[++i2]);
2071
2250
  }
2072
2251
  const { syllables, lineBreaks } = normalizeLyricLines(lyricLines);
2073
2252
  tracks.set(trackId, {
@@ -2077,6 +2256,8 @@ var parseLyrics = (mml) => {
2077
2256
  gate,
2078
2257
  pan,
2079
2258
  octave,
2259
+ vibrato,
2260
+ reverb,
2080
2261
  syllables,
2081
2262
  metaText: metaTokens.join(" "),
2082
2263
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2087,13 +2268,13 @@ var parseLyrics = (mml) => {
2087
2268
  var stripLyrics = (mml) => {
2088
2269
  const segments = splitSegments(mml);
2089
2270
  const kept = [];
2090
- for (let i = 0; i < segments.length; i++) {
2091
- if (LYRIC_LINE.test(segments[i])) {
2092
- while (i + 1 < segments.length && isLyricContinuation(segments[i + 1]))
2093
- i++;
2271
+ for (let i2 = 0; i2 < segments.length; i2++) {
2272
+ if (LYRIC_LINE.test(segments[i2])) {
2273
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1]))
2274
+ i2++;
2094
2275
  continue;
2095
2276
  }
2096
- kept.push(segments[i]);
2277
+ kept.push(segments[i2]);
2097
2278
  }
2098
2279
  return kept.join("\n");
2099
2280
  };
@@ -2162,7 +2343,7 @@ var FORMANTS = {
2162
2343
  N: [250, 1e3]
2163
2344
  };
2164
2345
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2165
- var createKlattVoice = (ctx, destination) => {
2346
+ var createKlattVoice = (ctx, destination, reverbBus) => {
2166
2347
  const active = /* @__PURE__ */ new Set();
2167
2348
  const voice = (syllable, e) => {
2168
2349
  const t0 = ctx.currentTime + e.when;
@@ -2180,6 +2361,12 @@ var createKlattVoice = (ctx, destination) => {
2180
2361
  panner.connect(destination);
2181
2362
  out = panner;
2182
2363
  }
2364
+ let sendGain = null;
2365
+ if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2366
+ sendGain = ctx.createGain();
2367
+ sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2368
+ panner.connect(sendGain).connect(reverbBus);
2369
+ }
2183
2370
  const osc = ctx.createOscillator();
2184
2371
  osc.type = "sawtooth";
2185
2372
  osc.frequency.value = midiToFreq(e.pitch);
@@ -2208,7 +2395,7 @@ var createKlattVoice = (ctx, destination) => {
2208
2395
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
2209
2396
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
2210
2397
  const data = buffer.getChannelData(0);
2211
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
2398
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
2212
2399
  const src = ctx.createBufferSource();
2213
2400
  src.buffer = buffer;
2214
2401
  const hp = ctx.createBiquadFilter();
@@ -2235,6 +2422,7 @@ var createKlattVoice = (ctx, destination) => {
2235
2422
  active.delete(osc);
2236
2423
  osc.disconnect();
2237
2424
  panner?.disconnect();
2425
+ sendGain?.disconnect();
2238
2426
  };
2239
2427
  };
2240
2428
  voice.stopAll = () => {
@@ -2253,6 +2441,7 @@ var KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
2253
2441
  var KOE_VOICEBANKS = {
2254
2442
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
2255
2443
  rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
2444
+ rino121: "\u6625\u97F3\u30EA\u30CEver.1.1(226).koe",
2256
2445
  roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
2257
2446
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
2258
2447
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
@@ -2261,11 +2450,13 @@ var KOE_VOICEBANKS = {
2261
2450
  rei: "\u8DB3\u7ACB\u30EC\u30A4ver3.5.0.koe",
2262
2451
  mgroid: "MGRoid_\u539F\u97F3\u8A2D\u5B9A\u6E08\u307F.koe",
2263
2452
  motroid: "MOTRoid\u5B8C\u5168\u7248V2.koe",
2264
- nynroid: "NYNRoidver1.4.koe"
2453
+ nynroid: "NYNRoidver1.4.koe",
2454
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3\u97F3\u6E90.koe"
2265
2455
  };
2266
2456
  var KOE_VOICEBANK_LABELS = {
2267
2457
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
2268
2458
  rino: "\u6625\u97F3\u30EA\u30CE",
2459
+ rino121: "\u6625\u97F3\u30EA\u30CEv1.2.1",
2269
2460
  roze: "\u675F\u97F3\u30ED\u30BC",
2270
2461
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
2271
2462
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
@@ -2274,12 +2465,14 @@ var KOE_VOICEBANK_LABELS = {
2274
2465
  rei: "\u8DB3\u7ACB\u30EC\u30A4",
2275
2466
  mgroid: "MGRoid",
2276
2467
  motroid: "MOTRoid",
2277
- nynroid: "NYNRoid"
2468
+ nynroid: "NYNRoid",
2469
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3"
2278
2470
  };
2279
2471
  var VOICE_IMAGE_KEY = {
2280
2472
  klatt: "puyuyu",
2281
2473
  tsukuyomi: "tsukuyomi",
2282
2474
  rino: "rino",
2475
+ rino121: "rino",
2283
2476
  roze: "roze",
2284
2477
  ruko_male: "ruko",
2285
2478
  ruko_female: "ruko",
@@ -2288,11 +2481,13 @@ var VOICE_IMAGE_KEY = {
2288
2481
  rei: "rei",
2289
2482
  mgroid: "MGRoid",
2290
2483
  motroid: "MOTRoid",
2291
- nynroid: "NYNRoid"
2484
+ nynroid: "NYNRoid",
2485
+ uc: "uc"
2292
2486
  };
2293
2487
  var KOE_VOICEBANK_TERMS = {
2294
2488
  tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
2295
2489
  rino: "https://hatenakun1.github.io/halunelino/",
2490
+ rino121: "https://harunerino.vercel.app/",
2296
2491
  roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
2297
2492
  ruko_male: "https://long-sleeper.net/index.php?id=22",
2298
2493
  ruko_female: "https://long-sleeper.net/index.php?id=22",
@@ -2301,7 +2496,8 @@ var KOE_VOICEBANK_TERMS = {
2301
2496
  rei: "https://mechanicalgirl.jp/guidelines/",
2302
2497
  mgroid: "https://x.com/nisusansu/status/1048825378188353536",
2303
2498
  motroid: "https://www.nicovideo.jp/watch/sm40031282",
2304
- nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs"
2499
+ nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs",
2500
+ uc: "https://chi9nekiriko.wixsite.com/home/%E5%88%A9%E7%94%A8%E8%A6%8F%E7%B4%84"
2305
2501
  };
2306
2502
  var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
2307
2503
  var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
@@ -2390,7 +2586,7 @@ var createLocalBackend = async (options) => {
2390
2586
  }
2391
2587
  return p;
2392
2588
  };
2393
- const renderAlias = async (alias, pitch, durationMs) => {
2589
+ const renderAlias = async (alias, pitch, durationMs, vibrato) => {
2394
2590
  const pcm = await getPcm(alias);
2395
2591
  if (!pcm || pcm.length === 0) return null;
2396
2592
  const entry = bank.manifest.phonemes[alias];
@@ -2399,7 +2595,7 @@ var createLocalBackend = async (options) => {
2399
2595
  if (worldline) {
2400
2596
  const audio = worldline.renderNote({
2401
2597
  pcm,
2402
- pitch: targetHz,
2598
+ pitch: vibrato ? vibratoPitchCurve(targetHz, lead.preMs) : targetHz,
2403
2599
  durationMs,
2404
2600
  ...lead
2405
2601
  });
@@ -2466,7 +2662,7 @@ var createWorkerBackend = async (workerUrl, options) => {
2466
2662
  });
2467
2663
  onReady = null;
2468
2664
  onFail = null;
2469
- const renderAlias = (alias, pitch, durationMs) => new Promise((resolve) => {
2665
+ const renderAlias = (alias, pitch, durationMs, vibrato) => new Promise((resolve) => {
2470
2666
  const id = ++reqId;
2471
2667
  pending.set(
2472
2668
  id,
@@ -2479,7 +2675,8 @@ var createWorkerBackend = async (workerUrl, options) => {
2479
2675
  id,
2480
2676
  alias,
2481
2677
  pitch,
2482
- durationMs
2678
+ durationMs,
2679
+ vibrato
2483
2680
  });
2484
2681
  });
2485
2682
  return {
@@ -2508,15 +2705,15 @@ var createKoeVoice = async (ctx, destination, options) => {
2508
2705
  const inflight = /* @__PURE__ */ new Map();
2509
2706
  const active = /* @__PURE__ */ new Set();
2510
2707
  let prevVowel = "";
2511
- const keyOf = (alias, pitch, durationMs) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}`;
2512
- const renderInto = (alias, pitch, durationMs) => {
2513
- const key = keyOf(alias, pitch, durationMs);
2708
+ const keyOf = (alias, pitch, durationMs, vibrato) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}`;
2709
+ const renderInto = (alias, pitch, durationMs, vibrato) => {
2710
+ const key = keyOf(alias, pitch, durationMs, vibrato);
2514
2711
  const existing = renderCache.get(key);
2515
2712
  if (existing !== void 0) return Promise.resolve(existing);
2516
2713
  const flying = inflight.get(key);
2517
2714
  if (flying) return flying;
2518
2715
  const p = (async () => {
2519
- const out = await backend.renderAlias(alias, pitch, durationMs);
2716
+ const out = await backend.renderAlias(alias, pitch, durationMs, vibrato);
2520
2717
  let rendered = null;
2521
2718
  if (out) {
2522
2719
  const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
@@ -2531,7 +2728,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2531
2728
  return p;
2532
2729
  };
2533
2730
  const LEADCAP_S = 0.09;
2534
- const schedule = (r, t0, peak, pan) => {
2731
+ const schedule = (r, t0, peak, pan, reverbSend = 0) => {
2535
2732
  let out = destination;
2536
2733
  let panner = null;
2537
2734
  if (typeof ctx.createStereoPanner === "function") {
@@ -2540,6 +2737,12 @@ var createKoeVoice = async (ctx, destination, options) => {
2540
2737
  panner.connect(destination);
2541
2738
  out = panner;
2542
2739
  }
2740
+ let sendGain = null;
2741
+ if (options.reverbBus && reverbSend > 0 && panner) {
2742
+ sendGain = ctx.createGain();
2743
+ sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2744
+ panner.connect(sendGain).connect(options.reverbBus);
2745
+ }
2543
2746
  const src = ctx.createBufferSource();
2544
2747
  src.buffer = r.audio;
2545
2748
  src.playbackRate.value = r.rate;
@@ -2565,6 +2768,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2565
2768
  src.disconnect();
2566
2769
  env.disconnect();
2567
2770
  panner?.disconnect();
2771
+ sendGain?.disconnect();
2568
2772
  };
2569
2773
  };
2570
2774
  const model = (syllable, e) => {
@@ -2583,10 +2787,10 @@ var createKoeVoice = async (ctx, destination, options) => {
2583
2787
  const pan = e.pan ?? 0;
2584
2788
  const durationMs = Math.max(60, e.duration * 1e3);
2585
2789
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2586
- if (r) schedule(r, t0, peak, pan);
2790
+ if (r) schedule(r, t0, peak, pan, e.reverbSend);
2587
2791
  });
2588
2792
  };
2589
- model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs) => {
2793
+ model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato) => {
2590
2794
  if (syllable.consonant === "Q" || syllable.vowel === "") return null;
2591
2795
  const alias = resolveKoeAlias(
2592
2796
  backend.hasAlias,
@@ -2597,12 +2801,13 @@ var createKoeVoice = async (ctx, destination, options) => {
2597
2801
  );
2598
2802
  if (!alias) return null;
2599
2803
  const dMs = Math.max(60, durationMs);
2600
- const r = await renderInto(alias, pitch, dMs);
2601
- return r ? keyOf(alias, pitch, dMs) : null;
2804
+ const vib = !!vibrato && dMs / 1e3 >= VIBRATO_MIN_SEC;
2805
+ const r = await renderInto(alias, pitch, dMs, vib);
2806
+ return r ? keyOf(alias, pitch, dMs, vib) : null;
2602
2807
  };
2603
- model.scheduleCached = (key, t0, peak, pan) => {
2808
+ model.scheduleCached = (key, t0, peak, pan, reverbSend) => {
2604
2809
  const r = renderCache.get(key);
2605
- if (r) schedule(r, t0, peak, pan);
2810
+ if (r) schedule(r, t0, peak, pan, reverbSend);
2606
2811
  };
2607
2812
  model.stopAll = () => {
2608
2813
  for (const src of active) {
@@ -2633,7 +2838,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2633
2838
  const masterGain = ctx.createGain();
2634
2839
  masterGain.connect(destination);
2635
2840
  const loaded = /* @__PURE__ */ new Map([
2636
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain)]
2841
+ [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
2637
2842
  ]);
2638
2843
  const loading = /* @__PURE__ */ new Map();
2639
2844
  const load2 = (model) => {
@@ -2653,7 +2858,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2653
2858
  koe,
2654
2859
  worldlineScriptUrl: options.worldlineScriptUrl,
2655
2860
  lightweight: options.lightweight,
2656
- voiceWorkerUrl: options.voiceWorkerUrl
2861
+ voiceWorkerUrl: options.voiceWorkerUrl,
2862
+ reverbBus: options.reverbBus
2657
2863
  })
2658
2864
  ))().then((v) => {
2659
2865
  loaded.set(m, v);
@@ -2698,7 +2904,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2698
2904
  forEachSungNote(track, (note, prevVowel) => {
2699
2905
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2700
2906
  n++;
2701
- tasks.push({ model: m, note, prevVowel });
2907
+ tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato });
2702
2908
  });
2703
2909
  }
2704
2910
  const total = tasks.length;
@@ -2713,7 +2919,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2713
2919
  task.note.syllable,
2714
2920
  task.prevVowel,
2715
2921
  task.note.pitch,
2716
- task.note.durationSec * 1e3
2922
+ task.note.durationSec * 1e3,
2923
+ task.vibrato
2717
2924
  ) ?? Promise.resolve(null));
2718
2925
  done++;
2719
2926
  onProgress?.(done, total);
@@ -2758,13 +2965,14 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2758
2965
  note.syllable,
2759
2966
  prevVowel,
2760
2967
  note.pitch,
2761
- note.durationSec * 1e3
2968
+ note.durationSec * 1e3,
2969
+ track.vibrato
2762
2970
  );
2763
2971
  if (session !== streamSession) return;
2764
2972
  if (key) {
2765
2973
  const delay = ctx.currentTime - t0;
2766
2974
  if (delay < 0.05) {
2767
- scheduleCached(key, t0, peak, track.pan);
2975
+ scheduleCached(key, t0, peak, track.pan, track.reverbSend);
2768
2976
  opts?.onScheduled?.(track, note, t0);
2769
2977
  } else {
2770
2978
  console.warn(
@@ -2783,7 +2991,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2783
2991
  volume: peak,
2784
2992
  when,
2785
2993
  duration: note.durationSec,
2786
- pan: track.pan
2994
+ pan: track.pan,
2995
+ reverbSend: track.reverbSend
2787
2996
  });
2788
2997
  opts?.onScheduled?.(track, note, t0);
2789
2998
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -5644,7 +5853,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
5644
5853
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
5645
5854
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
5646
5855
  const data = buffer.getChannelData(0);
5647
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
5856
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
5648
5857
  const src = ctx.createBufferSource();
5649
5858
  src.buffer = buffer;
5650
5859
  const filter = ctx.createBiquadFilter();
@@ -7565,6 +7774,9 @@ var teto_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAA
7565
7774
  // assets/tsukuyomi.png
7566
7775
  var tsukuyomi_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB3UlEQVR42tWXPUsDQRCGnwuijYIoFqKmEDSCxIgIgqnsogbsrC3ERiwN4g8Ikk6sFH+BnYofvcTKxoigBCzEYGGjWIiCOYvNBHe5TdQUt051czc3e++zO7tzHnUst7vn04BlFue9Ws8jhGxN9ZTPzU43NkIlj41E6AQ8m/JYfByAoWgXAPtHJzRCRN43SbizBkzlv1X2VzLuVoHYzf0TAMnys+aLCTGThMTJGnKfQOpyWF1cvgHQsh4FYPDzRd3vmQHgoPShJbi9uqhJyPk1UK3Jwsq1/115rLst8AX/4UFP0NsbGHf7+Kr579l7zR/ZGvbcrAJR7vvqEOwcTdZMYIuTPCYJUS7Ewydgzn2Bay2gdWzqR4nMuMPlhcC5l/HcWQOnCaU4lVU3xI9tnwFQnOgDoDmdDs5QKmlxYhdGmBA2ibhTBfKFHCkCm00dAKymJn+USAidHx8DkI+0q7PBUC7j5BJ7jlSB7RyXziVT8fM7Oz7AeLmsz3VEaWi7UvtAcmvJC+qmq4T/XT8gdTsw2692to07AF7jeU35wFq/7COqzhdVnVOnu3aHgNm1VpVXlImJX9zQffO5kDg1dlb3CNj6dZsVK2vAVP7Xf8bwCfxWuc23Eanmt/xlh07gC6oBviFE8rZHAAAAAElFTkSuQmCC";
7567
7776
 
7777
+ // assets/uc.png
7778
+ 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==";
7779
+
7568
7780
  // src/voice-images.ts
7569
7781
  var VOICE_IMAGES = {
7570
7782
  puyuyu: puyuyu_default,
@@ -7577,7 +7789,8 @@ var VOICE_IMAGES = {
7577
7789
  rei: rei_default,
7578
7790
  MGRoid: MGRoid_default,
7579
7791
  MOTRoid: MOTRoid_default,
7580
- NYNRoid: NYNRoid_default
7792
+ NYNRoid: NYNRoid_default,
7793
+ uc: uc_default
7581
7794
  };
7582
7795
  var FALLBACK_VOCAL_ICON = Chip_default;
7583
7796
 
@@ -7639,8 +7852,8 @@ var copyToClipboard = async (doc, text) => {
7639
7852
  };
7640
7853
  var toBase64Url = (bytes) => {
7641
7854
  let bin = "";
7642
- for (let i = 0; i < bytes.length; i++) {
7643
- bin += String.fromCharCode(bytes[i]);
7855
+ for (let i2 = 0; i2 < bytes.length; i2++) {
7856
+ bin += String.fromCharCode(bytes[i2]);
7644
7857
  }
7645
7858
  return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
7646
7859
  };
@@ -7653,8 +7866,8 @@ var SHIFT_KATAKANA = 255;
7653
7866
  var VALUE_PROLONGED = 223;
7654
7867
  var customEncode = (str) => {
7655
7868
  const bytes = [];
7656
- for (let i = 0; i < str.length; i++) {
7657
- const code = str.charCodeAt(i);
7869
+ for (let i2 = 0; i2 < str.length; i2++) {
7870
+ const code = str.charCodeAt(i2);
7658
7871
  if (code === 32) {
7659
7872
  continue;
7660
7873
  }
@@ -7696,37 +7909,37 @@ var fromBase64Url = (s) => {
7696
7909
  }
7697
7910
  const bin = atob(normalized);
7698
7911
  const bytes = new Uint8Array(bin.length);
7699
- for (let i = 0; i < bin.length; i++) {
7700
- bytes[i] = bin.charCodeAt(i);
7912
+ for (let i2 = 0; i2 < bin.length; i2++) {
7913
+ bytes[i2] = bin.charCodeAt(i2);
7701
7914
  }
7702
7915
  return bytes;
7703
7916
  };
7704
7917
  var customDecode = (bytes) => {
7705
7918
  let str = "";
7706
- let i = 0;
7707
- while (i < bytes.length) {
7708
- const byte = bytes[i];
7919
+ let i2 = 0;
7920
+ while (i2 < bytes.length) {
7921
+ const byte = bytes[i2];
7709
7922
  if (byte <= 127) {
7710
7923
  str += String.fromCharCode(byte);
7711
- i++;
7924
+ i2++;
7712
7925
  } else if (byte === VALUE_PROLONGED) {
7713
7926
  str += String.fromCharCode(PROLONGED_MARK);
7714
- i++;
7927
+ i2++;
7715
7928
  } else if (byte >= 128 && byte <= 222) {
7716
7929
  str += String.fromCharCode(HIRAGANA_START + (byte - 128));
7717
- i++;
7930
+ i2++;
7718
7931
  } else if (byte === SHIFT_KATAKANA) {
7719
- if (i + 1 < bytes.length) {
7720
- const nextByte = bytes[i + 1];
7932
+ if (i2 + 1 < bytes.length) {
7933
+ const nextByte = bytes[i2 + 1];
7721
7934
  if (nextByte >= 128 && nextByte <= 222) {
7722
7935
  str += String.fromCharCode(HIRAGANA_START + 96 + (nextByte - 128));
7723
7936
  }
7724
- i += 2;
7937
+ i2 += 2;
7725
7938
  } else {
7726
- i++;
7939
+ i2++;
7727
7940
  }
7728
7941
  } else {
7729
- i++;
7942
+ i2++;
7730
7943
  }
7731
7944
  }
7732
7945
  return str;
@@ -8291,7 +8504,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8291
8504
  const beatRow = doc.createElement("div");
8292
8505
  beatRow.className = "dtm-player-beat-row";
8293
8506
  const beatDots = [];
8294
- for (let i = 0; i < 4; i++) {
8507
+ for (let i2 = 0; i2 < 4; i2++) {
8295
8508
  const d = doc.createElement("span");
8296
8509
  d.className = "dtm-player-beat-dot";
8297
8510
  beatRow.appendChild(d);
@@ -8395,9 +8608,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8395
8608
  lane.appendChild(metaEl);
8396
8609
  }
8397
8610
  const count = Math.min(notes.length, lyricTrack.syllables.length);
8398
- for (let i = 0; i < count; i++) {
8399
- const note = notes[i];
8400
- if (breaks.has(i)) {
8611
+ for (let i2 = 0; i2 < count; i2++) {
8612
+ const note = notes[i2];
8613
+ if (breaks.has(i2)) {
8401
8614
  const br = doc.createElement("span");
8402
8615
  br.className = "dtm-tk dtm-tk--break";
8403
8616
  br.textContent = "\\n";
@@ -8405,7 +8618,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8405
8618
  }
8406
8619
  const span = doc.createElement("span");
8407
8620
  span.className = "dtm-tk dtm-tk--lyric";
8408
- span.textContent = lyricTrack.syllables[i].kana;
8621
+ span.textContent = lyricTrack.syllables[i2].kana;
8409
8622
  lane.appendChild(span);
8410
8623
  laneTokens.push({
8411
8624
  el: span,
@@ -8558,8 +8771,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8558
8771
  const intStep = Math.floor(step);
8559
8772
  playStep = intStep;
8560
8773
  const beatIndex = Math.floor(step / STEPS_PER_BEAT2) % 4;
8561
- for (let i = 0; i < 4; i++)
8562
- beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
8774
+ for (let i2 = 0; i2 < 4; i2++)
8775
+ beatDots[i2].classList.toggle("dtm-player-beat-dot--on", i2 === beatIndex);
8563
8776
  barEl.textContent = String(Math.floor(step / STEPS_PER_BAR2) + 1);
8564
8777
  if (!isSeeking) {
8565
8778
  seekInput.value = String(Math.min(maxStep, Math.max(0, intStep)));
@@ -8654,11 +8867,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8654
8867
  const semis = (lt.octave ?? 0) * 12;
8655
8868
  const count = Math.min(sorted.length, lt.syllables.length);
8656
8869
  const notes = [];
8657
- for (let i = 0; i < count; i++) {
8658
- const n = sorted[i];
8870
+ for (let i2 = 0; i2 < count; i2++) {
8871
+ const n = sorted[i2];
8659
8872
  if (n.startStep < fromStep) continue;
8660
8873
  notes.push({
8661
- syllable: lt.syllables[i],
8874
+ syllable: lt.syllables[i2],
8662
8875
  pitch: n.pitch + semis,
8663
8876
  startSec: (n.startStep - fromStep) * secondsPerStep,
8664
8877
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -8669,6 +8882,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8669
8882
  model: lt.model,
8670
8883
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
8671
8884
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
8885
+ vibrato: lt.vibrato,
8886
+ reverbSend: (lt.reverb ?? 0) / 100,
8672
8887
  notes
8673
8888
  };
8674
8889
  });
@@ -8928,13 +9143,13 @@ var findZone = (ctx, fontName, zones, pitchs = []) => {
8928
9143
  for (const zone of zones) {
8929
9144
  const low = zone.keyRangeLow | 0, high = zone.keyRangeHigh | 0;
8930
9145
  if (low > high) continue;
8931
- for (let i = low; i <= high; i++) pitchs.push(i);
9146
+ for (let i2 = low; i2 <= high; i2++) pitchs.push(i2);
8932
9147
  }
8933
9148
  const set = new Set(pitchs);
8934
9149
  const map = new Map(pitchs.map((v) => [v, zones[0]]));
8935
- for (let i = zones.length - 1; i >= 0; i--)
9150
+ for (let i2 = zones.length - 1; i2 >= 0; i2--)
8936
9151
  for (const v of set) {
8937
- const zone = zones[i];
9152
+ const zone = zones[i2];
8938
9153
  if (v < zone.keyRangeLow || v > zone.keyRangeHigh) continue;
8939
9154
  set.delete(v);
8940
9155
  map.set(v, { ...zone });
@@ -8964,13 +9179,13 @@ var adjustZone = async (ctx, fontName, zone) => {
8964
9179
  const decoded = atob(zone.sample);
8965
9180
  zone.buffer = ctx.createBuffer(1, decoded.length / 2, zone.sampleRate);
8966
9181
  const a = zone.buffer.getChannelData(0);
8967
- for (let i = 0; i < decoded.length / 2; i++) {
8968
- let b1 = decoded.charCodeAt(i * 2), b2 = decoded.charCodeAt(i * 2 + 1);
9182
+ for (let i2 = 0; i2 < decoded.length / 2; i2++) {
9183
+ let b1 = decoded.charCodeAt(i2 * 2), b2 = decoded.charCodeAt(i2 * 2 + 1);
8969
9184
  if (b1 < 0) b1 = 256 + b1;
8970
9185
  if (b2 < 0) b2 = 256 + b2;
8971
9186
  let n = b2 * 256 + b1;
8972
9187
  if (n >= 65536 / 2) n = n - 65536;
8973
- a[i] = n / 65536;
9188
+ a[i2] = n / 65536;
8974
9189
  }
8975
9190
  } else if (zone.file) {
8976
9191
  const bytes = Uint8Array.from(atob(zone.file), (c) => c.charCodeAt(0));
@@ -9010,10 +9225,10 @@ var adjustZone = async (ctx, fontName, zone) => {
9010
9225
  let loopPeak = 0;
9011
9226
  if (oldBuf.numberOfChannels > 0) {
9012
9227
  const ch0 = oldBuf.getChannelData(0);
9013
- for (let i = 0; i < ch0.length; i++) {
9014
- const abs = Math.abs(ch0[i]);
9228
+ for (let i2 = 0; i2 < ch0.length; i2++) {
9229
+ const abs = Math.abs(ch0[i2]);
9015
9230
  if (abs > totalPeak) totalPeak = abs;
9016
- if (i >= loopStartFrame && i < loopEndFrame) {
9231
+ if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9017
9232
  if (abs > loopPeak) loopPeak = abs;
9018
9233
  }
9019
9234
  }
@@ -9032,16 +9247,16 @@ var adjustZone = async (ctx, fontName, zone) => {
9032
9247
  for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9033
9248
  const oldData = oldBuf.getChannelData(ch);
9034
9249
  const newData = newBuf.getChannelData(ch);
9035
- for (let i = 0; i < attackLength; i++) {
9036
- const ratio = attackLength > 1 ? i / (attackLength - 1) : 0;
9250
+ for (let i2 = 0; i2 < attackLength; i2++) {
9251
+ const ratio = attackLength > 1 ? i2 / (attackLength - 1) : 0;
9037
9252
  const m = 1 + (gainMultiplier - 1) * ratio;
9038
- newData[i] = oldData[i] * m;
9253
+ newData[i2] = oldData[i2] * m;
9039
9254
  }
9040
9255
  let offset = attackLength;
9041
9256
  const loopData = oldData.subarray(loopStartFrame, loopEndFrame);
9042
9257
  const normalizedLoopData = new Float32Array(loopLengthFrame);
9043
- for (let i = 0; i < loopLengthFrame; i++) {
9044
- normalizedLoopData[i] = loopData[i] * gainMultiplier;
9258
+ for (let i2 = 0; i2 < loopLengthFrame; i2++) {
9259
+ normalizedLoopData[i2] = loopData[i2] * gainMultiplier;
9045
9260
  }
9046
9261
  for (let r = 0; r < repeatCount; r++) {
9047
9262
  newData.set(normalizedLoopData, offset);
@@ -9051,8 +9266,8 @@ var adjustZone = async (ctx, fontName, zone) => {
9051
9266
  const releaseData = oldData.subarray(loopEndFrame);
9052
9267
  if (gainMultiplier !== 1) {
9053
9268
  const normalizedRelease = new Float32Array(releaseLength);
9054
- for (let i = 0; i < releaseLength; i++) {
9055
- normalizedRelease[i] = releaseData[i] * gainMultiplier;
9269
+ for (let i2 = 0; i2 < releaseLength; i2++) {
9270
+ normalizedRelease[i2] = releaseData[i2] * gainMultiplier;
9056
9271
  }
9057
9272
  newData.set(normalizedRelease, offset);
9058
9273
  } else {
@@ -9248,10 +9463,10 @@ var buildDisplayLines = (chords, eventsCount) => {
9248
9463
  if (segments.length === 0) continue;
9249
9464
  const hasMultiSeg = segments.length > 1;
9250
9465
  const parts = [];
9251
- for (let i = 0; i < segments.length; i++) {
9252
- if (hasMultiSeg && i > 0)
9466
+ for (let i2 = 0; i2 < segments.length; i2++) {
9467
+ if (hasMultiSeg && i2 > 0)
9253
9468
  parts.push({ text: "|", isChord: false, eventIdx: -1 });
9254
- const bar = segments[i].trim();
9469
+ const bar = segments[i2].trim();
9255
9470
  if (!bar) continue;
9256
9471
  const splitAt = [];
9257
9472
  for (let j = 0; j < bar.length; j++) {
@@ -10414,6 +10629,11 @@ var buildUI = (target, options) => {
10414
10629
  <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10415
10630
  <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10416
10631
  </div>
10632
+ <div class="dtm-row">
10633
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10634
+ <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">
10635
+ <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10636
+ </div>
10417
10637
  <div class="dtm-track-body" data-dtm="track-body"></div>
10418
10638
  </div>
10419
10639
  </details>
@@ -10637,6 +10857,8 @@ var buildUI = (target, options) => {
10637
10857
  hScrollThumb: sel("hscroll-thumb"),
10638
10858
  masterVolume: sel("master-volume"),
10639
10859
  masterVolumeLabel: sel("master-volume-label"),
10860
+ reverbAmount: sel("reverb-amount"),
10861
+ reverbAmountLabel: sel("reverb-amount-label"),
10640
10862
  trackTabs: sel("track-tabs"),
10641
10863
  trackBody: sel("track-body"),
10642
10864
  drumSelect: sel("drum-select"),
@@ -10805,16 +11027,16 @@ var generateRandomPattern = (core, options) => {
10805
11027
  const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
10806
11028
  const rootOffset = Math.floor(Math.random() * 12);
10807
11029
  const availablePitches = [];
10808
- for (let i = 0; i < 12; i++) {
10809
- const noteInOctave = (i - rootOffset + 12) % 12;
10810
- if (scale.includes(noteInOctave)) availablePitches.push(basePitch + i);
11030
+ for (let i2 = 0; i2 < 12; i2++) {
11031
+ const noteInOctave = (i2 - rootOffset + 12) % 12;
11032
+ if (scale.includes(noteInOctave)) availablePitches.push(basePitch + i2);
10811
11033
  }
10812
11034
  core.beginBatch();
10813
11035
  for (let bar = 0; bar < numBars; bar++) {
10814
11036
  const barStart = startStep + bar * stepsPerBar;
10815
11037
  const numNotes = Math.floor(Math.random() * 4) + 2;
10816
11038
  const occupied = /* @__PURE__ */ new Set();
10817
- for (let i = 0; i < numNotes; i++) {
11039
+ for (let i2 = 0; i2 < numNotes; i2++) {
10818
11040
  const stepInRange = Math.floor(Math.random() * (stepsPerBar / noteLength)) * noteLength;
10819
11041
  const step = barStart + stepInRange;
10820
11042
  if (occupied.has(step)) continue;
@@ -10915,10 +11137,10 @@ var STEPS_PER_BEAT3 = 48;
10915
11137
  var analyzeMidiTracks = (midi) => {
10916
11138
  const { tracks } = midi;
10917
11139
  const result = [];
10918
- for (let i = 0; i < tracks.length; i++) {
11140
+ for (let i2 = 0; i2 < tracks.length; i2++) {
10919
11141
  const notes = [];
10920
11142
  let currentTime = 0;
10921
- for (const event of tracks[i]) {
11143
+ for (const event of tracks[i2]) {
10922
11144
  currentTime += event.delta;
10923
11145
  if (event.noteOn && event.noteOn.velocity > 0) {
10924
11146
  notes.push({
@@ -10941,8 +11163,8 @@ var analyzeMidiTracks = (midi) => {
10941
11163
  const editableNotes = validNotes.filter((n) => n.channel !== 9);
10942
11164
  if (validNotes.length > 0 && editableNotes.length === 0) continue;
10943
11165
  result.push({
10944
- index: i,
10945
- name: `Ch${i + 1}`,
11166
+ index: i2,
11167
+ name: `Ch${i2 + 1}`,
10946
11168
  noteCount: editableNotes.length,
10947
11169
  selected: editableNotes.length > 0
10948
11170
  });
@@ -10989,8 +11211,8 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
10989
11211
  const pitch = noteOff.noteNumber;
10990
11212
  const channel = event.channel ?? 0;
10991
11213
  if (channelNotes[channel]) {
10992
- for (let i = channelNotes[channel].length - 1; i >= 0; i--) {
10993
- const note = channelNotes[channel][i];
11214
+ for (let i2 = channelNotes[channel].length - 1; i2 >= 0; i2--) {
11215
+ const note = channelNotes[channel][i2];
10994
11216
  if (note.pitch === pitch && note.end === null) {
10995
11217
  note.end = currentTime;
10996
11218
  break;
@@ -11020,10 +11242,10 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11020
11242
  const avgPitch = validNotes.reduce((sum, n) => sum + n.pitch, 0) / validNotes.length;
11021
11243
  let maxSimultaneous = 0;
11022
11244
  const sortedNotes = [...validNotes].sort((a, b) => a.start - b.start);
11023
- for (let i = 0; i < sortedNotes.length; i++) {
11245
+ for (let i2 = 0; i2 < sortedNotes.length; i2++) {
11024
11246
  let simultaneous = 1;
11025
- for (let j = i + 1; j < sortedNotes.length; j++) {
11026
- if (sortedNotes[j].start < sortedNotes[i].end) {
11247
+ for (let j = i2 + 1; j < sortedNotes.length; j++) {
11248
+ if (sortedNotes[j].start < sortedNotes[i2].end) {
11027
11249
  simultaneous++;
11028
11250
  }
11029
11251
  }
@@ -11034,14 +11256,14 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11034
11256
  const blocks = [];
11035
11257
  let blockStart = sortedNotes[0].start;
11036
11258
  let blockEnd = sortedNotes[0].end;
11037
- for (let i = 1; i < sortedNotes.length; i++) {
11038
- const gap = sortedNotes[i].start - sortedNotes[i - 1].end;
11259
+ for (let i2 = 1; i2 < sortedNotes.length; i2++) {
11260
+ const gap = sortedNotes[i2].start - sortedNotes[i2 - 1].end;
11039
11261
  if (gap >= ticksPerBar) {
11040
11262
  blocks.push({ start: blockStart, end: blockEnd });
11041
- blockStart = sortedNotes[i].start;
11042
- blockEnd = sortedNotes[i].end;
11263
+ blockStart = sortedNotes[i2].start;
11264
+ blockEnd = sortedNotes[i2].end;
11043
11265
  } else {
11044
- blockEnd = sortedNotes[i].end;
11266
+ blockEnd = sortedNotes[i2].end;
11045
11267
  }
11046
11268
  }
11047
11269
  blocks.push({ start: blockStart, end: blockEnd });
@@ -11155,9 +11377,9 @@ var extractMidiPlacementsByTrack = (midi, selectedIndices, trackIds) => {
11155
11377
  const noteOff = event.noteOff || event.noteOn;
11156
11378
  if (noteOff) {
11157
11379
  const pitch = noteOff.noteNumber;
11158
- for (let i = active.length - 1; i >= 0; i--) {
11159
- if (active[i].pitch === pitch && active[i].end === null) {
11160
- active[i].end = currentTime;
11380
+ for (let i2 = active.length - 1; i2 >= 0; i2--) {
11381
+ if (active[i2].pitch === pitch && active[i2].end === null) {
11382
+ active[i2].end = currentTime;
11161
11383
  break;
11162
11384
  }
11163
11385
  }
@@ -11322,17 +11544,17 @@ var extractDrumPatternFromNotes = (rawNotes, drumFont = "FluidR3_GM_sf2_file:0")
11322
11544
  let prev = bList[0];
11323
11545
  const parts = key.split("_");
11324
11546
  const noteObj = { step: +parts[0], pitch: +parts[1], velocity: +parts[2] };
11325
- for (let i = 1; i <= bList.length; i++) {
11326
- if (i === bList.length || bList[i] !== prev + 1) {
11547
+ for (let i2 = 1; i2 <= bList.length; i2++) {
11548
+ if (i2 === bList.length || bList[i2] !== prev + 1) {
11327
11549
  const rKey = `${start}-${prev}`;
11328
11550
  if (!rangeMap[rKey]) rangeMap[rKey] = [];
11329
11551
  rangeMap[rKey].push(noteObj);
11330
- if (i < bList.length) {
11331
- start = bList[i];
11332
- prev = bList[i];
11552
+ if (i2 < bList.length) {
11553
+ start = bList[i2];
11554
+ prev = bList[i2];
11333
11555
  }
11334
11556
  } else {
11335
- prev = bList[i];
11557
+ prev = bList[i2];
11336
11558
  }
11337
11559
  }
11338
11560
  }
@@ -11709,8 +11931,8 @@ var drawHeader = () => {
11709
11931
  (g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
11710
11932
  );
11711
11933
  for (let bar = startBar; bar <= endBar + 1; bar++) {
11712
- const x = bar * stepsPerBar * stepWidth;
11713
- const screenX = x;
11934
+ const x2 = bar * stepsPerBar * stepWidth;
11935
+ const screenX = x2;
11714
11936
  g_header_ctx.beginPath();
11715
11937
  g_header_ctx.moveTo(screenX, 0);
11716
11938
  g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
@@ -11757,11 +11979,11 @@ var drawGrid = (noteLengthSteps = 1) => {
11757
11979
  const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
11758
11980
  const endX = g_draw_offset_x + g_grid_canvas.width;
11759
11981
  const lineStep = stepWidth * gridStep;
11760
- for (let x = startX; x <= endX; x += lineStep) {
11761
- const step = x / stepWidth;
11982
+ for (let x2 = startX; x2 <= endX; x2 += lineStep) {
11983
+ const step = x2 / stepWidth;
11762
11984
  const isBarLine = step % stepsPerBar === 0;
11763
11985
  const isNoteLine = step % gridStep === 0;
11764
- const screenX = x - g_draw_offset_x;
11986
+ const screenX = x2 - g_draw_offset_x;
11765
11987
  g_grid_ctx.beginPath();
11766
11988
  g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
11767
11989
  g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
@@ -11823,26 +12045,26 @@ var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
11823
12045
  var getXY = (e) => {
11824
12046
  const { clientX, clientY } = e;
11825
12047
  const rect = g_grid_canvas.getBoundingClientRect();
11826
- const x = Math.floor(clientX - rect.left);
12048
+ const x2 = Math.floor(clientX - rect.left);
11827
12049
  const y = Math.floor(clientY - rect.top);
11828
- return [x, y, e.buttons];
12050
+ return [x2, y, e.buttons];
11829
12051
  };
11830
12052
  var getGridPosition = (e) => {
11831
- const [x, y] = getXY(e);
12053
+ const [x2, y] = getXY(e);
11832
12054
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11833
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12055
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11834
12056
  const absoluteY = y + g_draw_offset_y;
11835
12057
  const yIndex = Math.floor(absoluteY / keyHeight);
11836
12058
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
11837
- return { step, pitch, x, y };
12059
+ return { step, pitch, x: x2, y };
11838
12060
  };
11839
12061
  var onClick = (callback) => {
11840
12062
  g_grid_canvas.addEventListener(
11841
12063
  "click",
11842
12064
  (e) => {
11843
- const [x, y] = getXY(e);
12065
+ const [x2, y] = getXY(e);
11844
12066
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11845
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12067
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11846
12068
  const absoluteY = y + g_draw_offset_y;
11847
12069
  const yIndex = Math.floor(absoluteY / keyHeight);
11848
12070
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
@@ -11854,8 +12076,8 @@ var onClick = (callback) => {
11854
12076
  );
11855
12077
  g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
11856
12078
  };
11857
- var setDrawOffset = (x, y) => {
11858
- g_draw_offset_x = x;
12079
+ var setDrawOffset = (x2, y) => {
12080
+ g_draw_offset_x = x2;
11859
12081
  g_draw_offset_y = y;
11860
12082
  drawKeyboard();
11861
12083
  drawHeader();
@@ -12170,12 +12392,12 @@ var MMLCore = class _MMLCore {
12170
12392
  currentCursor += steps;
12171
12393
  }
12172
12394
  };
12173
- for (let i = 0; i < sortedSteps.length; i++) {
12174
- const startStep = sortedSteps[i];
12395
+ for (let i2 = 0; i2 < sortedSteps.length; i2++) {
12396
+ const startStep = sortedSteps[i2];
12175
12397
  const notes = notesByStep.get(startStep);
12176
12398
  if (!notes) continue;
12177
12399
  fillRests(startStep);
12178
- const nextStart = sortedSteps[i + 1] ?? endStep;
12400
+ const nextStart = sortedSteps[i2 + 1] ?? endStep;
12179
12401
  const physicsLimit = nextStart - currentCursor;
12180
12402
  if (physicsLimit < MIN_STEP) {
12181
12403
  continue;
@@ -12240,10 +12462,10 @@ var decomposeToMonophonic = (notes) => {
12240
12462
  for (const note of sorted) {
12241
12463
  let assigned = -1;
12242
12464
  let minEnd = Infinity;
12243
- for (let i = 0; i < tracks.length; i++) {
12244
- if (trackEnds[i] <= note.startStep && trackEnds[i] < minEnd) {
12245
- minEnd = trackEnds[i];
12246
- assigned = i;
12465
+ for (let i2 = 0; i2 < tracks.length; i2++) {
12466
+ if (trackEnds[i2] <= note.startStep && trackEnds[i2] < minEnd) {
12467
+ minEnd = trackEnds[i2];
12468
+ assigned = i2;
12247
12469
  }
12248
12470
  }
12249
12471
  if (assigned === -1) {
@@ -12299,6 +12521,14 @@ var CHORD_INFO_HTML2 = `
12299
12521
  </ul>
12300
12522
  </div>
12301
12523
  `;
12524
+ var VIBRATO_INFO_HTML = `
12525
+ <div class="dtm-modal-body-content">
12526
+ <h4>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u306F</h4>
12527
+ <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>
12528
+ <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>
12529
+ <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>
12530
+ </div>
12531
+ `;
12302
12532
  var MIDI_INFO_HTML = `
12303
12533
  <div class="dtm-modal-body-content">
12304
12534
  <h4>1. MIDI\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -12502,7 +12732,7 @@ var LYRIC_MODEL_CATEGORIES = [
12502
12732
  },
12503
12733
  {
12504
12734
  label: "\u304A\u3093J",
12505
- models: ["roze", "shiyo", "rino"]
12735
+ models: ["roze", "shiyo", "rino", "rino121", "uc"]
12506
12736
  },
12507
12737
  {
12508
12738
  label: "\u4E00\u822C",
@@ -12570,6 +12800,8 @@ var mountDAW = (target, options = {}) => {
12570
12800
  });
12571
12801
  refs.masterVolume.value = String(options.masterVolume ?? 50);
12572
12802
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
12803
+ refs.reverbAmount.value = String(options.reverbAmount ?? 0);
12804
+ refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
12573
12805
  refs.drumVolume.value = String(options.drumVolume ?? 80);
12574
12806
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
12575
12807
  const renderConfig = {
@@ -12585,6 +12817,8 @@ var mountDAW = (target, options = {}) => {
12585
12817
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
12586
12818
  let masterVolume = options.masterVolume ?? 50;
12587
12819
  options.singingVoices?.setVolume(masterVolume / 100);
12820
+ let reverbAmount = options.reverbAmount ?? 0;
12821
+ options.onReverbChange?.(reverbAmount);
12588
12822
  let drumVolume = options.drumVolume ?? 80;
12589
12823
  let currentDrumPattern = refs.drumSelect.value;
12590
12824
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -12662,7 +12896,9 @@ var mountDAW = (target, options = {}) => {
12662
12896
  vocalVolume: t.vocalVolume,
12663
12897
  vocalGate: t.vocalGate,
12664
12898
  vocalPan: t.vocalPan,
12665
- vocalOctave: t.vocalOctave
12899
+ vocalOctave: t.vocalOctave,
12900
+ vocalVibrato: t.vocalVibrato,
12901
+ vocalReverb: t.vocalReverb
12666
12902
  };
12667
12903
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
12668
12904
  lyricsDebounceTimer = setTimeout(() => {
@@ -12722,25 +12958,29 @@ var mountDAW = (target, options = {}) => {
12722
12958
  vocalGate: 100,
12723
12959
  vocalPan: 64,
12724
12960
  vocalOctave: 0,
12961
+ vocalVibrato: false,
12962
+ vocalReverb: 0,
12725
12963
  trackInstrument: ""
12726
12964
  };
12727
12965
  });
12728
12966
  };
12729
12967
  const buildLyricsMap = () => {
12730
12968
  const map = /* @__PURE__ */ new Map();
12731
- trackStates.forEach((t, i) => {
12969
+ trackStates.forEach((t, i2) => {
12732
12970
  const model = t.lyricModel.trim();
12733
12971
  const text = t.lyrics.trim();
12734
12972
  if (!model || !text) return;
12735
12973
  const syllables = normalizeLyrics(text);
12736
12974
  if (syllables.length === 0) return;
12737
- map.set(i, {
12738
- trackId: i,
12975
+ map.set(i2, {
12976
+ trackId: i2,
12739
12977
  model: model.toLowerCase(),
12740
12978
  volume: t.vocalVolume,
12741
12979
  gate: t.vocalGate,
12742
12980
  pan: t.vocalPan,
12743
12981
  octave: t.vocalOctave,
12982
+ vibrato: t.vocalVibrato,
12983
+ reverb: t.vocalReverb,
12744
12984
  syllables
12745
12985
  });
12746
12986
  });
@@ -12780,15 +13020,15 @@ var mountDAW = (target, options = {}) => {
12780
13020
  const ctx = getGridContext();
12781
13021
  const canvas = getGridCanvas();
12782
13022
  if (!ctx) return;
12783
- const x = playStartStep * renderConfig.stepWidth - currentOffsetX;
12784
- if (x < -10 || x > canvas.width + 10) return;
13023
+ const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
13024
+ if (x2 < -10 || x2 > canvas.width + 10) return;
12785
13025
  ctx.save();
12786
13026
  ctx.strokeStyle = "#ffec27";
12787
13027
  ctx.lineWidth = 2;
12788
13028
  ctx.setLineDash([4, 4]);
12789
13029
  ctx.beginPath();
12790
- ctx.moveTo(x, 0);
12791
- ctx.lineTo(x, canvas.height);
13030
+ ctx.moveTo(x2, 0);
13031
+ ctx.lineTo(x2, canvas.height);
12792
13032
  ctx.stroke();
12793
13033
  ctx.restore();
12794
13034
  };
@@ -12796,14 +13036,14 @@ var mountDAW = (target, options = {}) => {
12796
13036
  const ctx = getGridContext();
12797
13037
  const canvas = getGridCanvas();
12798
13038
  if (!ctx) return;
12799
- const x = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
12800
- if (x < 0 || x > canvas.width) return;
13039
+ const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
13040
+ if (x2 < 0 || x2 > canvas.width) return;
12801
13041
  ctx.save();
12802
13042
  ctx.strokeStyle = "#ff004d";
12803
13043
  ctx.lineWidth = 2;
12804
13044
  ctx.beginPath();
12805
- ctx.moveTo(x, 0);
12806
- ctx.lineTo(x, canvas.height);
13045
+ ctx.moveTo(x2, 0);
13046
+ ctx.lineTo(x2, canvas.height);
12807
13047
  ctx.stroke();
12808
13048
  ctx.restore();
12809
13049
  };
@@ -12931,8 +13171,8 @@ var mountDAW = (target, options = {}) => {
12931
13171
  if (maxOffsetX <= 0) return;
12932
13172
  const rect = refs.hScroll.getBoundingClientRect();
12933
13173
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
12934
- const x = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
12935
- const ratio = x / (rect.width - thumbW);
13174
+ const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13175
+ const ratio = x2 / (rect.width - thumbW);
12936
13176
  currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
12937
13177
  setDrawOffset(currentOffsetX, currentOffsetY);
12938
13178
  redrawAll();
@@ -12959,13 +13199,60 @@ var mountDAW = (target, options = {}) => {
12959
13199
  let selectionStart = null;
12960
13200
  let selectedOriginal = [];
12961
13201
  let lastMultiPreviewPitch = null;
13202
+ const AUTO_SCROLL_MARGIN = 30;
13203
+ const AUTO_SCROLL_MAX_SPEED = 22;
13204
+ let autoScrollRAF = null;
13205
+ let lastMoveEvent = null;
13206
+ const computeEdgeSpeed = (pos, size) => {
13207
+ if (pos < AUTO_SCROLL_MARGIN) {
13208
+ const t = (AUTO_SCROLL_MARGIN - pos) / AUTO_SCROLL_MARGIN;
13209
+ return -Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13210
+ }
13211
+ if (pos > size - AUTO_SCROLL_MARGIN) {
13212
+ const t = (pos - (size - AUTO_SCROLL_MARGIN)) / AUTO_SCROLL_MARGIN;
13213
+ return Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13214
+ }
13215
+ return 0;
13216
+ };
13217
+ const stopAutoScroll = () => {
13218
+ if (autoScrollRAF !== null) {
13219
+ cancelAnimationFrame(autoScrollRAF);
13220
+ autoScrollRAF = null;
13221
+ }
13222
+ lastMoveEvent = null;
13223
+ };
13224
+ const autoScrollTick = () => {
13225
+ autoScrollRAF = null;
13226
+ if (!isSelecting || !lastMoveEvent) return;
13227
+ const canvas = getGridCanvas();
13228
+ const { x: x2, y } = getGridPosition(lastMoveEvent);
13229
+ const dx = computeEdgeSpeed(x2, canvas.width);
13230
+ const dy = computeEdgeSpeed(y, canvas.height);
13231
+ if (dx !== 0 || dy !== 0) {
13232
+ const maxOffsetX = getMaxOffsetX();
13233
+ const maxOffsetY = getMaxOffsetY();
13234
+ currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13235
+ currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13236
+ setDrawOffset(currentOffsetX, currentOffsetY);
13237
+ onPointerMove(lastMoveEvent);
13238
+ }
13239
+ if (isSelecting) {
13240
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13241
+ }
13242
+ };
13243
+ const ensureAutoScroll = (event) => {
13244
+ lastMoveEvent = event;
13245
+ if (autoScrollRAF === null) {
13246
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13247
+ }
13248
+ };
12962
13249
  const playPreview = (pitch) => {
12963
13250
  if (isLoading) return;
12964
13251
  options.onResumeAudio?.();
12965
13252
  const active = getActive();
12966
13253
  dispatchNote(active.config.id, pitch, active.volume, 100, 0, 0.5);
12967
13254
  };
12968
- const findActiveNoteAt = (x, y, margin = 0) => {
13255
+ const findActiveNoteAt = (x2, y, margin = 0) => {
12969
13256
  const active = getActive();
12970
13257
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = renderConfig;
12971
13258
  const offset = getDrawOffset();
@@ -12976,7 +13263,7 @@ var mountDAW = (target, options = {}) => {
12976
13263
  const w = note.durationSteps * stepWidth;
12977
13264
  const renderX = logicalX - offset.x;
12978
13265
  const renderY = logicalY - offset.y;
12979
- if (x >= renderX - margin && x <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
13266
+ if (x2 >= renderX - margin && x2 <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
12980
13267
  return note;
12981
13268
  }
12982
13269
  return null;
@@ -12995,17 +13282,17 @@ var mountDAW = (target, options = {}) => {
12995
13282
  const onGridPointerDown = (event) => {
12996
13283
  event.preventDefault();
12997
13284
  options.onResumeAudio?.();
12998
- const { x, y, step, pitch } = getGridPosition(event);
13285
+ const { x: x2, y, step, pitch } = getGridPosition(event);
12999
13286
  const active = getActive();
13000
13287
  if (activeToolMode === "eraser") {
13001
13288
  if (isActiveLocked()) return;
13002
- const note = findActiveNoteAt(x, y);
13289
+ const note = findActiveNoteAt(x2, y);
13003
13290
  if (note) active.core.deleteNoteById(note.id);
13004
13291
  return;
13005
13292
  }
13006
13293
  if (activeToolMode === "select") {
13007
13294
  if (selectedNotes.length > 0) {
13008
- const clicked2 = findActiveNoteAt(x, y);
13295
+ const clicked2 = findActiveNoteAt(x2, y);
13009
13296
  if (clicked2 && selectedNotes.some((n) => n.id === clicked2.id)) {
13010
13297
  selectedOriginal = selectedNotes.map((n) => ({
13011
13298
  id: n.id,
@@ -13014,7 +13301,7 @@ var mountDAW = (target, options = {}) => {
13014
13301
  }));
13015
13302
  isSelecting = true;
13016
13303
  dragMode = "move";
13017
- selectionStart = { x, y, step, pitch };
13304
+ selectionStart = { x: x2, y, step, pitch };
13018
13305
  hasDragged = false;
13019
13306
  lastMultiPreviewPitch = null;
13020
13307
  return;
@@ -13022,7 +13309,7 @@ var mountDAW = (target, options = {}) => {
13022
13309
  selectedNotes = [];
13023
13310
  selectionRect = null;
13024
13311
  }
13025
- const clicked = findActiveNoteAt(x, y);
13312
+ const clicked = findActiveNoteAt(x2, y);
13026
13313
  if (clicked) {
13027
13314
  selectedNotes = [clicked];
13028
13315
  selectedOriginal = [
@@ -13040,19 +13327,19 @@ var mountDAW = (target, options = {}) => {
13040
13327
  isSelecting = true;
13041
13328
  dragMode = "rect";
13042
13329
  }
13043
- selectionStart = { x, y, step, pitch };
13330
+ selectionStart = { x: x2, y, step, pitch };
13044
13331
  hasDragged = false;
13045
13332
  return;
13046
13333
  }
13047
13334
  hasDragged = false;
13048
- const existing = findActiveNoteAt(x, y, TOUCH_HIT_MARGIN);
13335
+ const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
13049
13336
  if (existing) {
13050
13337
  playPreview(existing.pitch);
13051
13338
  const { stepWidth } = renderConfig;
13052
13339
  const offset = getDrawOffset();
13053
13340
  const renderX = existing.startStep * stepWidth - offset.x;
13054
13341
  const w = existing.durationSteps * stepWidth;
13055
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w) {
13342
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
13056
13343
  dragState = {
13057
13344
  noteId: existing.id,
13058
13345
  mode: "resize",
@@ -13131,12 +13418,13 @@ var mountDAW = (target, options = {}) => {
13131
13418
  return;
13132
13419
  }
13133
13420
  if (activeToolMode === "select" && isSelecting && selectionStart) {
13134
- const { x, y, step, pitch } = getGridPosition(event);
13421
+ ensureAutoScroll(event);
13422
+ const { x: x2, y, step, pitch } = getGridPosition(event);
13135
13423
  if (dragMode === "rect") {
13136
13424
  const rect = {
13137
- x: Math.min(x, selectionStart.x),
13425
+ x: Math.min(x2, selectionStart.x),
13138
13426
  y: Math.min(y, selectionStart.y),
13139
- width: Math.abs(x - selectionStart.x),
13427
+ width: Math.abs(x2 - selectionStart.x),
13140
13428
  height: Math.abs(y - selectionStart.y)
13141
13429
  };
13142
13430
  selectionRect = rect;
@@ -13149,7 +13437,7 @@ var mountDAW = (target, options = {}) => {
13149
13437
  const nx = logicalX - offset.x;
13150
13438
  const ny = logicalY - offset.y;
13151
13439
  const nw = note.durationSteps * stepWidth;
13152
- return rect.x < nx + nw && rect.x + rect.width > nx && rect.y < ny + keyHeight && rect.y + rect.height > ny;
13440
+ return nx >= rect.x && nx + nw <= rect.x + rect.width && ny >= rect.y && ny + keyHeight <= rect.y + rect.height;
13153
13441
  });
13154
13442
  redrawAll();
13155
13443
  } else {
@@ -13206,8 +13494,11 @@ var mountDAW = (target, options = {}) => {
13206
13494
  selectionStart = null;
13207
13495
  hasDragged = false;
13208
13496
  lastMultiPreviewPitch = null;
13209
- selectionRect = null;
13497
+ if (dragMode !== "rect" || selectedNotes.length === 0) {
13498
+ selectionRect = null;
13499
+ }
13210
13500
  selectedOriginal = [];
13501
+ stopAutoScroll();
13211
13502
  redrawAll();
13212
13503
  }
13213
13504
  };
@@ -13355,8 +13646,8 @@ var mountDAW = (target, options = {}) => {
13355
13646
  headerCanvas.addEventListener("click", (event) => {
13356
13647
  if (playbackState === "playing") return;
13357
13648
  const rect = headerCanvas.getBoundingClientRect();
13358
- const x = event.clientX - rect.left;
13359
- const step = Math.floor((x + currentOffsetX) / renderConfig.stepWidth);
13649
+ const x2 = event.clientX - rect.left;
13650
+ const step = Math.floor((x2 + currentOffsetX) / renderConfig.stepWidth);
13360
13651
  playStartStep = Math.max(
13361
13652
  0,
13362
13653
  Math.floor(step / snapGridSteps) * snapGridSteps
@@ -13477,11 +13768,11 @@ var mountDAW = (target, options = {}) => {
13477
13768
  const semis = (lt.octave ?? 0) * 12;
13478
13769
  const count = Math.min(sorted.length, lt.syllables.length);
13479
13770
  const notes = [];
13480
- for (let i = 0; i < count; i++) {
13481
- const n = sorted[i];
13771
+ for (let i2 = 0; i2 < count; i2++) {
13772
+ const n = sorted[i2];
13482
13773
  if (n.startStep < fromStep) continue;
13483
13774
  notes.push({
13484
- syllable: lt.syllables[i],
13775
+ syllable: lt.syllables[i2],
13485
13776
  pitch: n.pitch + semis,
13486
13777
  startSec: (n.startStep - fromStep) * secondsPerStep,
13487
13778
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -13492,6 +13783,8 @@ var mountDAW = (target, options = {}) => {
13492
13783
  model: lt.model,
13493
13784
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
13494
13785
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
13786
+ vibrato: lt.vibrato,
13787
+ reverbSend: (lt.reverb ?? 0) / 100,
13495
13788
  notes
13496
13789
  };
13497
13790
  }) : [];
@@ -13580,13 +13873,13 @@ var mountDAW = (target, options = {}) => {
13580
13873
  const updateTrackPanel = () => {
13581
13874
  refs.trackTabs.innerHTML = "";
13582
13875
  trackPillEls.clear();
13583
- for (const [i, t] of trackStates.entries()) {
13876
+ for (const [i2, t] of trackStates.entries()) {
13584
13877
  const [r, g, b] = t.config.color;
13585
13878
  const btn = document.createElement("button");
13586
13879
  btn.className = `dtm-pill ${t.config.id === activeTrackId ? "dtm-pill--active" : ""}`;
13587
13880
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
13588
13881
  btn.title = t.config.name;
13589
- btn.textContent = String(i + 1);
13882
+ btn.textContent = String(i2 + 1);
13590
13883
  btn.addEventListener("click", () => switchTrack(t.config.id));
13591
13884
  refs.trackTabs.appendChild(btn);
13592
13885
  trackPillEls.set(t.config.id, btn);
@@ -13684,6 +13977,10 @@ var mountDAW = (target, options = {}) => {
13684
13977
  <option value="-1">-1 oct</option>
13685
13978
  <option value="-2">-2 oct</option>
13686
13979
  </select>
13980
+ <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">
13981
+ <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
13982
+ </label>
13983
+ <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
13687
13984
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
13688
13985
  </div>
13689
13986
  <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
@@ -13714,6 +14011,11 @@ var mountDAW = (target, options = {}) => {
13714
14011
  <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">
13715
14012
  <span class="dtm-label" data-dtm="lyric-pan-label"></span>
13716
14013
  </div>
14014
+ <div class="dtm-row">
14015
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14016
+ <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">
14017
+ <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14018
+ </div>
13717
14019
  <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>
13718
14020
  </div>`;
13719
14021
  refs.trackBody.appendChild(lyricDiv);
@@ -13747,6 +14049,21 @@ var mountDAW = (target, options = {}) => {
13747
14049
  const lyricPanLabel = lyricDiv.querySelector(
13748
14050
  '[data-dtm="lyric-pan-label"]'
13749
14051
  );
14052
+ const lyricReverb = lyricDiv.querySelector(
14053
+ '[data-dtm="lyric-reverb"]'
14054
+ );
14055
+ const lyricReverbLabel = lyricDiv.querySelector(
14056
+ '[data-dtm="lyric-reverb-label"]'
14057
+ );
14058
+ const lyricVibrato = lyricDiv.querySelector(
14059
+ '[data-dtm="lyric-vibrato"]'
14060
+ );
14061
+ const lyricVibratoInfo = lyricDiv.querySelector(
14062
+ '[data-dtm="lyric-vibrato-info"]'
14063
+ );
14064
+ lyricVibratoInfo.addEventListener("click", () => {
14065
+ showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14066
+ });
13750
14067
  const lyricTerms = lyricDiv.querySelector(
13751
14068
  '[data-dtm="lyric-terms"]'
13752
14069
  );
@@ -13808,6 +14125,9 @@ var mountDAW = (target, options = {}) => {
13808
14125
  lyricVolLabel.textContent = String(active.vocalVolume);
13809
14126
  lyricPan.value = String(active.vocalPan);
13810
14127
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14128
+ lyricReverb.value = String(active.vocalReverb);
14129
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14130
+ lyricVibrato.checked = active.vocalVibrato;
13811
14131
  const updateLyricCount = () => {
13812
14132
  const n = normalizeLyrics(lyricInput.value).length;
13813
14133
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -13926,6 +14246,10 @@ var mountDAW = (target, options = {}) => {
13926
14246
  active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
13927
14247
  fireLyricsChange(active);
13928
14248
  });
14249
+ lyricVibrato.addEventListener("change", () => {
14250
+ active.vocalVibrato = lyricVibrato.checked;
14251
+ fireLyricsChange(active);
14252
+ });
13929
14253
  lyricInput.addEventListener("input", () => {
13930
14254
  active.lyrics = lyricInput.value;
13931
14255
  updateLyricCount();
@@ -13949,6 +14273,11 @@ var mountDAW = (target, options = {}) => {
13949
14273
  lyricPanLabel.textContent = fmtPan(64);
13950
14274
  fireLyricsChange(active);
13951
14275
  });
14276
+ lyricReverb.addEventListener("input", () => {
14277
+ active.vocalReverb = Number.parseInt(lyricReverb.value, 10);
14278
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14279
+ fireLyricsChange(active);
14280
+ });
13952
14281
  }
13953
14282
  if (active.config.id === "chord" && showChord) {
13954
14283
  const div = document.createElement("div");
@@ -14032,8 +14361,8 @@ var mountDAW = (target, options = {}) => {
14032
14361
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
14033
14362
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
14034
14363
  const trackInstrumentsForMeta = {};
14035
- trackStates.forEach((t, i) => {
14036
- if (t.trackInstrument) trackInstrumentsForMeta[i] = t.trackInstrument;
14364
+ trackStates.forEach((t, i2) => {
14365
+ if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14037
14366
  });
14038
14367
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14039
14368
  const metaLineFull = formatMmlMeta(
@@ -14068,10 +14397,10 @@ var mountDAW = (target, options = {}) => {
14068
14397
  const monoTracks = decomposeToMonophonic(allNotes);
14069
14398
  const refCore = trackStates[0].core;
14070
14399
  const decomposedFull = monoTracks.map(
14071
- (notes, i) => `@${i} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14400
+ (notes, i2) => `@${i2} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14072
14401
  );
14073
14402
  const decomposedMini = monoTracks.map(
14074
- (notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14403
+ (notes, i2) => `@${i2}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14075
14404
  );
14076
14405
  const full2 = [metaLineFull, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
14077
14406
  const minified2 = [metaLineMini, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
@@ -14085,34 +14414,38 @@ var mountDAW = (target, options = {}) => {
14085
14414
  }
14086
14415
  const trackLines = [];
14087
14416
  const trackLinesMini = [];
14088
- trackStates.forEach((t, i) => {
14417
+ trackStates.forEach((t, i2) => {
14089
14418
  const notes = clipNotes(t.core.getNotes());
14090
14419
  if (notes.length > 0) {
14091
14420
  const mml = t.core.getMMLFromNotes(notes, bpm, t.volume).trim();
14092
- trackLines.push(`@${i} ${mml}`);
14093
- trackLinesMini.push(`@${i}${mml.replace(/\s+/g, "")}`);
14421
+ trackLines.push(`@${i2} ${mml}`);
14422
+ trackLinesMini.push(`@${i2}${mml.replace(/\s+/g, "")}`);
14094
14423
  }
14095
14424
  });
14096
- const lyricLines = trackStates.map((t, i) => ({
14097
- i,
14425
+ const lyricLines = trackStates.map((t, i2) => ({
14426
+ i: i2,
14098
14427
  notes: clipNotes(t.core.getNotes()),
14099
14428
  text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
14100
14429
  model: t.lyricModel.trim(),
14101
14430
  vol: t.vocalVolume,
14102
14431
  gate: t.vocalGate,
14103
14432
  pan: t.vocalPan,
14104
- oct: t.vocalOctave
14433
+ oct: t.vocalOctave,
14434
+ vib: t.vocalVibrato,
14435
+ rev: t.vocalReverb
14105
14436
  })).filter(
14106
- (x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
14107
- ).map((x) => {
14437
+ (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14438
+ ).map((x2) => {
14108
14439
  const params = [
14109
- x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
14110
- x.gate === 100 ? "" : `q${x.gate}`,
14111
- x.pan === 64 ? "" : `p${x.pan}`,
14112
- x.oct === 0 ? "" : `o${x.oct}`
14440
+ x2.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x2.vol}`,
14441
+ x2.gate === 100 ? "" : `q${x2.gate}`,
14442
+ x2.pan === 64 ? "" : `p${x2.pan}`,
14443
+ x2.oct === 0 ? "" : `o${x2.oct}`,
14444
+ x2.vib ? "b1" : "",
14445
+ x2.rev === 0 ? "" : `r${x2.rev}`
14113
14446
  ].filter((s) => s.length > 0).join(" ");
14114
- const head = params ? `${x.model} ${params}` : x.model;
14115
- return `@@${x.i} ${head} ${x.text}`;
14447
+ const head = params ? `${x2.model} ${params}` : x2.model;
14448
+ return `@@${x2.i} ${head} ${x2.text}`;
14116
14449
  });
14117
14450
  const customVocalDecls = [];
14118
14451
  for (const [key, def] of customVocalsMap) {
@@ -14250,17 +14583,17 @@ var mountDAW = (target, options = {}) => {
14250
14583
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
14251
14584
  }
14252
14585
  }
14253
- trackStates.forEach((t, i) => {
14254
- if (applyActiveOnly && i !== activeTrackIndex) return;
14255
- const name = normalizeInstrumentName(meta.trackInstruments?.[i] ?? "");
14586
+ trackStates.forEach((t, i2) => {
14587
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14588
+ const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
14256
14589
  if (t.trackInstrument !== name) {
14257
14590
  t.trackInstrument = name;
14258
- options.onTrackInstrumentChange?.(i, name);
14591
+ options.onTrackInstrumentChange?.(i2, name);
14259
14592
  }
14260
14593
  });
14261
- trackStates.forEach((t, i) => {
14262
- if (applyActiveOnly && i !== activeTrackIndex) return;
14263
- const v = trackVelocity.get(i);
14594
+ trackStates.forEach((t, i2) => {
14595
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14596
+ const v = trackVelocity.get(i2);
14264
14597
  if (v !== void 0 && v !== t.volume) {
14265
14598
  t.volume = v;
14266
14599
  t.core.setVolume(v);
@@ -14274,6 +14607,8 @@ var mountDAW = (target, options = {}) => {
14274
14607
  active.vocalGate = 100;
14275
14608
  active.vocalPan = 64;
14276
14609
  active.vocalOctave = 0;
14610
+ active.vocalVibrato = false;
14611
+ active.vocalReverb = 0;
14277
14612
  } else {
14278
14613
  for (const t of trackStates) {
14279
14614
  t.lyrics = "";
@@ -14282,6 +14617,8 @@ var mountDAW = (target, options = {}) => {
14282
14617
  t.vocalGate = 100;
14283
14618
  t.vocalPan = 64;
14284
14619
  t.vocalOctave = 0;
14620
+ t.vocalVibrato = false;
14621
+ t.vocalReverb = 0;
14285
14622
  }
14286
14623
  }
14287
14624
  lyrics?.forEach((lt) => {
@@ -14294,6 +14631,8 @@ var mountDAW = (target, options = {}) => {
14294
14631
  t.vocalGate = lt.gate;
14295
14632
  t.vocalPan = lt.pan;
14296
14633
  t.vocalOctave = lt.octave ?? 0;
14634
+ t.vocalVibrato = lt.vibrato ?? false;
14635
+ t.vocalReverb = lt.reverb ?? 0;
14297
14636
  });
14298
14637
  for (const p of placements) {
14299
14638
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -14372,6 +14711,8 @@ var mountDAW = (target, options = {}) => {
14372
14711
  active.vocalGate = 100;
14373
14712
  active.vocalPan = 64;
14374
14713
  active.vocalOctave = 0;
14714
+ active.vocalVibrato = false;
14715
+ active.vocalReverb = 0;
14375
14716
  } else {
14376
14717
  clearAll();
14377
14718
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -14382,6 +14723,8 @@ var mountDAW = (target, options = {}) => {
14382
14723
  t.vocalGate = 100;
14383
14724
  t.vocalPan = 64;
14384
14725
  t.vocalOctave = 0;
14726
+ t.vocalVibrato = false;
14727
+ t.vocalReverb = 0;
14385
14728
  }
14386
14729
  }
14387
14730
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -14591,6 +14934,11 @@ var mountDAW = (target, options = {}) => {
14591
14934
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
14592
14935
  options.singingVoices?.setVolume(masterVolume / 100);
14593
14936
  });
14937
+ refs.reverbAmount.addEventListener("input", () => {
14938
+ reverbAmount = Number.parseInt(refs.reverbAmount.value, 10) || 0;
14939
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
14940
+ options.onReverbChange?.(reverbAmount);
14941
+ });
14594
14942
  refs.drumSelect.addEventListener("change", () => {
14595
14943
  currentDrumPattern = refs.drumSelect.value;
14596
14944
  options.onDrumChange?.(currentDrumPattern);
@@ -14871,7 +15219,7 @@ var mountDAW = (target, options = {}) => {
14871
15219
  console.error(e);
14872
15220
  }
14873
15221
  refs.midiTrackSelection.innerHTML = `<span class="dtm-label">\u30C8\u30E9\u30C3\u30AF</span>`;
14874
- detectedTracks.forEach((t, i) => {
15222
+ detectedTracks.forEach((t, i2) => {
14875
15223
  const btn = document.createElement("button");
14876
15224
  btn.className = `dtm-btn ${t.selected ? "dtm-btn--primary" : "dtm-btn--ghost"}`;
14877
15225
  btn.dataset.selected = String(t.selected);
@@ -14883,7 +15231,7 @@ var mountDAW = (target, options = {}) => {
14883
15231
  btn.classList.toggle("dtm-btn--ghost", !on);
14884
15232
  });
14885
15233
  refs.midiTrackSelection.appendChild(btn);
14886
- if (i === 0) refs.midiTrackSelection.dataset.ready = "1";
15234
+ if (i2 === 0) refs.midiTrackSelection.dataset.ready = "1";
14887
15235
  });
14888
15236
  refs.midiTrackSelection.classList.remove("dtm-hidden");
14889
15237
  refs.overlay.hidden = true;
@@ -14893,9 +15241,9 @@ var mountDAW = (target, options = {}) => {
14893
15241
  if (!pendingMidi) return;
14894
15242
  const selected = [];
14895
15243
  const btns = refs.midiTrackSelection.querySelectorAll("button");
14896
- btns.forEach((b, i) => {
15244
+ btns.forEach((b, i2) => {
14897
15245
  if (b.dataset.selected === "true")
14898
- selected.push(detectedTracks[i].index);
15246
+ selected.push(detectedTracks[i2].index);
14899
15247
  });
14900
15248
  if (selected.length === 0) return;
14901
15249
  if (!isAdvanced && options.onRequestAdvancedMode && selected.length > trackStates.length) {
@@ -14929,7 +15277,7 @@ var mountDAW = (target, options = {}) => {
14929
15277
  const refCore = trackStates[0].core;
14930
15278
  const tempo = Math.round(parsedBpm) || bpm;
14931
15279
  const lines = [];
14932
- let i = 0;
15280
+ let i2 = 0;
14933
15281
  for (const notes of byTrack.values()) {
14934
15282
  const asNotes = notes.map((p) => ({
14935
15283
  id: 0,
@@ -14939,8 +15287,8 @@ var mountDAW = (target, options = {}) => {
14939
15287
  velocity: p.velocity
14940
15288
  }));
14941
15289
  const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
14942
- lines.push(`@${i} ${mml}`);
14943
- i++;
15290
+ lines.push(`@${i2} ${mml}`);
15291
+ i2++;
14944
15292
  }
14945
15293
  return [...lines, MML_END_MARKER].join(";\n");
14946
15294
  };
@@ -15555,6 +15903,12 @@ var mountDAW = (target, options = {}) => {
15555
15903
  refs.drumVolume.value = String(drumVolume);
15556
15904
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
15557
15905
  },
15906
+ setReverbAmount: (amount) => {
15907
+ reverbAmount = clamp3(amount, 0, 100);
15908
+ refs.reverbAmount.value = String(reverbAmount);
15909
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
15910
+ options.onReverbChange?.(reverbAmount);
15911
+ },
15558
15912
  applyPatch: (trackId, added, removed) => {
15559
15913
  const track = trackStates.find((t) => t.config.id === trackId);
15560
15914
  if (!track) return;
@@ -15594,6 +15948,8 @@ var mountDAW = (target, options = {}) => {
15594
15948
  t.vocalGate = data.vocalGate;
15595
15949
  t.vocalPan = data.vocalPan;
15596
15950
  t.vocalOctave = data.vocalOctave;
15951
+ t.vocalVibrato = data.vocalVibrato ?? false;
15952
+ t.vocalReverb = data.vocalReverb ?? 0;
15597
15953
  },
15598
15954
  applyTrackInstrument: (trackIndex, instrumentName) => {
15599
15955
  const t = trackStates[trackIndex];
@@ -15604,17 +15960,17 @@ var mountDAW = (target, options = {}) => {
15604
15960
  },
15605
15961
  noteToCanvas: (step, pitch) => {
15606
15962
  const canvas = getGridCanvas();
15607
- const x = step * renderConfig.stepWidth - currentOffsetX;
15963
+ const x2 = step * renderConfig.stepWidth - currentOffsetX;
15608
15964
  const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
15609
- const onScreen = x >= 0 && x <= canvas.width && y >= 0 && y <= canvas.height;
15965
+ const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
15610
15966
  let side = null;
15611
15967
  if (!onScreen) {
15612
- if (x < 0) side = "left";
15613
- else if (x > canvas.width) side = "right";
15968
+ if (x2 < 0) side = "left";
15969
+ else if (x2 > canvas.width) side = "right";
15614
15970
  else if (y < 0) side = "top";
15615
15971
  else side = "bottom";
15616
15972
  }
15617
- return { x, y, onScreen, side };
15973
+ return { x: x2, y, onScreen, side };
15618
15974
  },
15619
15975
  destroy: () => {
15620
15976
  sequencer.stop();
@@ -15694,11 +16050,11 @@ var playSingingMML = async (mml, options = {}) => {
15694
16050
  const semis = (lt.octave ?? 0) * 12;
15695
16051
  const count = Math.min(sorted.length, lt.syllables.length);
15696
16052
  const notes = [];
15697
- for (let i = 0; i < count; i++) {
15698
- const n = sorted[i];
16053
+ for (let i2 = 0; i2 < count; i2++) {
16054
+ const n = sorted[i2];
15699
16055
  if (n.startStep < fromStep) continue;
15700
16056
  notes.push({
15701
- syllable: lt.syllables[i],
16057
+ syllable: lt.syllables[i2],
15702
16058
  pitch: n.pitch + semis,
15703
16059
  startSec: (n.startStep - fromStep) * secondsPerStep,
15704
16060
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -15709,6 +16065,8 @@ var playSingingMML = async (mml, options = {}) => {
15709
16065
  model: lt.model,
15710
16066
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
15711
16067
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16068
+ vibrato: lt.vibrato,
16069
+ reverbSend: (lt.reverb ?? 0) / 100,
15712
16070
  notes
15713
16071
  };
15714
16072
  });
@@ -15923,7 +16281,7 @@ var createPianoRoll = (options, handlers) => {
15923
16281
  let dragState = null;
15924
16282
  let hasDragged = false;
15925
16283
  let lastPreviewPitch = null;
15926
- const findNoteAtPosition = (x, y) => {
16284
+ const findNoteAtPosition = (x2, y) => {
15927
16285
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
15928
16286
  const offset = getDrawOffset();
15929
16287
  for (const note of core.getNotes()) {
@@ -15934,7 +16292,7 @@ var createPianoRoll = (options, handlers) => {
15934
16292
  const h = keyHeight;
15935
16293
  const renderX = logicalX - offset.x;
15936
16294
  const renderY = logicalY - offset.y;
15937
- if (x >= renderX && x <= renderX + w && y >= renderY && y <= renderY + h) {
16295
+ if (x2 >= renderX && x2 <= renderX + w && y >= renderY && y <= renderY + h) {
15938
16296
  return note;
15939
16297
  }
15940
16298
  }
@@ -15942,10 +16300,10 @@ var createPianoRoll = (options, handlers) => {
15942
16300
  };
15943
16301
  const handlePointerMove = (e) => {
15944
16302
  if (core.getToolMode() === "select" && isSelecting && selectionStart) {
15945
- const { x, y } = getGridPosition(e);
15946
- const minX = Math.min(x, selectionStart.x);
16303
+ const { x: x2, y } = getGridPosition(e);
16304
+ const minX = Math.min(x2, selectionStart.x);
15947
16305
  const minY = Math.min(y, selectionStart.y);
15948
- const width2 = Math.abs(x - selectionStart.x);
16306
+ const width2 = Math.abs(x2 - selectionStart.x);
15949
16307
  const height2 = Math.abs(y - selectionStart.y);
15950
16308
  selectionRect = { x: minX, y: minY, width: width2, height: height2 };
15951
16309
  selectedNotes = getNotesInRect(selectionRect);
@@ -16003,10 +16361,10 @@ var createPianoRoll = (options, handlers) => {
16003
16361
  }
16004
16362
  };
16005
16363
  gridCanvas.addEventListener("pointerdown", (e) => {
16006
- const { x, y, step, pitch } = getGridPosition(e);
16364
+ const { x: x2, y, step, pitch } = getGridPosition(e);
16007
16365
  const currentMode = core.getToolMode();
16008
16366
  if (currentMode === "select") {
16009
- const clickedNote = findNoteAtPosition(x, y);
16367
+ const clickedNote = findNoteAtPosition(x2, y);
16010
16368
  if (selectionRect && clickedNote) {
16011
16369
  const notesInRect = getNotesInRect(selectionRect);
16012
16370
  if (notesInRect.some((n) => n.id === clickedNote.id)) {
@@ -16027,10 +16385,10 @@ var createPianoRoll = (options, handlers) => {
16027
16385
  selectedNotes = [];
16028
16386
  selectionRect = null;
16029
16387
  isSelecting = true;
16030
- selectionStart = { x, y, step, pitch };
16388
+ selectionStart = { x: x2, y, step, pitch };
16031
16389
  return;
16032
16390
  }
16033
- const note = findNoteAtPosition(x, y);
16391
+ const note = findNoteAtPosition(x2, y);
16034
16392
  if (!note) return;
16035
16393
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
16036
16394
  const offset = getDrawOffset();
@@ -16040,7 +16398,7 @@ var createPianoRoll = (options, handlers) => {
16040
16398
  const renderX = logicalX - offset.x;
16041
16399
  const renderY = logicalY - offset.y;
16042
16400
  const w = note.durationSteps * stepWidth;
16043
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16401
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16044
16402
  dragState = {
16045
16403
  noteId: note.id,
16046
16404
  mode: "resize",
@@ -16316,6 +16674,25 @@ var isSupported = midiJsonParser.isSupported;
16316
16674
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
16317
16675
  URL.revokeObjectURL(url);
16318
16676
 
16677
+ // src/reverb.ts
16678
+ var IMPULSE_DURATION_SEC = 2.2;
16679
+ var IMPULSE_DECAY = 2.5;
16680
+ var createReverbImpulse = (ctx) => {
16681
+ const rate = ctx.sampleRate;
16682
+ const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
16683
+ const impulse = ctx.createBuffer(2, length, rate);
16684
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
16685
+ const data = impulse.getChannelData(ch);
16686
+ for (let i2 = 0; i2 < length; i2++) {
16687
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
16688
+ data[i2] = (Math.random() * 2 - 1) * envelope;
16689
+ }
16690
+ }
16691
+ return impulse;
16692
+ };
16693
+ var REVERB_MAX_WET = 0.6;
16694
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
16695
+
16319
16696
  // src/sf/SoundFont_drum.ts
16320
16697
  var touch = (map, key, ctor) => {
16321
16698
  if (!map.has(key)) map.set(key, new ctor());
@@ -16471,6 +16848,21 @@ var createDtmStudio = async (options = {}) => {
16471
16848
  const drumGain = audioCtx.createGain();
16472
16849
  drumGain.gain.value = options.drumVolume ?? 1;
16473
16850
  drumGain.connect(masterGain);
16851
+ const reverbConvolver = audioCtx.createConvolver();
16852
+ reverbConvolver.buffer = createReverbImpulse(audioCtx);
16853
+ reverbConvolver.normalize = true;
16854
+ const reverbWetGain = audioCtx.createGain();
16855
+ reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
16856
+ masterGain.connect(reverbConvolver);
16857
+ reverbConvolver.connect(reverbWetGain);
16858
+ reverbWetGain.connect(audioCtx.destination);
16859
+ const setReverbAmount = (amount) => {
16860
+ reverbWetGain.gain.setTargetAtTime(
16861
+ reverbAmountToGain(amount),
16862
+ audioCtx.currentTime,
16863
+ 0.02
16864
+ );
16865
+ };
16474
16866
  const resumeAudio = () => {
16475
16867
  if (audioCtx.state === "closed") return Promise.resolve();
16476
16868
  return audioCtx.resume();
@@ -16501,7 +16893,10 @@ var createDtmStudio = async (options = {}) => {
16501
16893
  const singingVoices = createSingingVoices(audioCtx, masterGain, {
16502
16894
  voiceWorkerUrl,
16503
16895
  voicebanks,
16504
- worldlineScriptUrl: options.worldlineScriptUrl
16896
+ worldlineScriptUrl: options.worldlineScriptUrl,
16897
+ // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
16898
+ // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
16899
+ reverbBus: reverbConvolver
16505
16900
  });
16506
16901
  const listReady = new Promise((resolve) => {
16507
16902
  sfList.init();
@@ -16776,6 +17171,8 @@ var createDtmStudio = async (options = {}) => {
16776
17171
  void handleTrackInstrumentChange(idx, name);
16777
17172
  externalOnTrackInstrumentChange?.(idx, name);
16778
17173
  },
17174
+ reverbAmount: options.reverbAmount,
17175
+ onReverbChange: setReverbAmount,
16779
17176
  ...dawOverrides,
16780
17177
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
16781
17178
  onDrumChange: (name) => {
@@ -16833,8 +17230,8 @@ var createDtmStudio = async (options = {}) => {
16833
17230
  presetSelect?.destroy();
16834
17231
  if (editorPresetSelects.get(target) === presetSelect)
16835
17232
  editorPresetSelects.delete(target);
16836
- const i = mountedEditors.indexOf(daw);
16837
- if (i >= 0) mountedEditors.splice(i, 1);
17233
+ const i2 = mountedEditors.indexOf(daw);
17234
+ if (i2 >= 0) mountedEditors.splice(i2, 1);
16838
17235
  };
16839
17236
  return {
16840
17237
  ...daw,
@@ -16937,8 +17334,8 @@ var createDtmStudio = async (options = {}) => {
16937
17334
  destroy: () => {
16938
17335
  doUnmount();
16939
17336
  wrapper.remove();
16940
- const i = mountedModeSwitches.indexOf(instance);
16941
- if (i >= 0) mountedModeSwitches.splice(i, 1);
17337
+ const i2 = mountedModeSwitches.indexOf(instance);
17338
+ if (i2 >= 0) mountedModeSwitches.splice(i2, 1);
16942
17339
  }
16943
17340
  };
16944
17341
  mountedModeSwitches.push(instance);
@@ -17025,8 +17422,8 @@ var createDtmStudio = async (options = {}) => {
17025
17422
  mountedPlayers.push(player);
17026
17423
  const destroy = () => {
17027
17424
  player.destroy();
17028
- const i = mountedPlayers.indexOf(player);
17029
- if (i >= 0) mountedPlayers.splice(i, 1);
17425
+ const i2 = mountedPlayers.indexOf(player);
17426
+ if (i2 >= 0) mountedPlayers.splice(i2, 1);
17030
17427
  };
17031
17428
  return { ...player, destroy };
17032
17429
  };
@@ -17287,8 +17684,8 @@ var createDtmStudio = async (options = {}) => {
17287
17684
  mountedChordPlayers.push(player);
17288
17685
  const destroy = () => {
17289
17686
  player.destroy();
17290
- const i = mountedChordPlayers.indexOf(player);
17291
- if (i >= 0) mountedChordPlayers.splice(i, 1);
17687
+ const i2 = mountedChordPlayers.indexOf(player);
17688
+ if (i2 >= 0) mountedChordPlayers.splice(i2, 1);
17292
17689
  };
17293
17690
  return {
17294
17691
  ...player,
@@ -17343,6 +17740,7 @@ var createDtmStudio = async (options = {}) => {
17343
17740
  mountModeSwitch,
17344
17741
  setMasterVolume,
17345
17742
  setVolume: setMasterVolume,
17743
+ setReverbAmount,
17346
17744
  dispose
17347
17745
  };
17348
17746
  };
@@ -17373,6 +17771,7 @@ export {
17373
17771
  PREWARM_NOTES,
17374
17772
  TRACKS_ADVANCED,
17375
17773
  TRACKS_SIMPLE,
17774
+ VIBRATO_MIN_SEC,
17376
17775
  VOICE_IMAGES,
17377
17776
  VOICE_IMAGE_KEY,
17378
17777
  analyzeMidiTracks,