@onjmin/dtm 0.1.83 → 0.1.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,12 @@ 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;
2189
+ let gender = 50;
2190
+ let breathiness = 50;
2024
2191
  const modelMatch = rest.match(
2025
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpo]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2192
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2026
2193
  );
2027
2194
  let model = "";
2028
2195
  const metaTokens = [];
@@ -2063,11 +2230,39 @@ var parseLyrics = (mml) => {
2063
2230
  rest = rest.substring(oMatch[0].length).trim();
2064
2231
  continue;
2065
2232
  }
2233
+ const bMatch = rest.match(/^b([01])/i);
2234
+ if (bMatch) {
2235
+ vibrato = bMatch[1] === "1";
2236
+ metaTokens.push(bMatch[0]);
2237
+ rest = rest.substring(bMatch[0].length).trim();
2238
+ continue;
2239
+ }
2240
+ const rMatch = rest.match(/^r(\d+)/i);
2241
+ if (rMatch) {
2242
+ reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2243
+ metaTokens.push(rMatch[0]);
2244
+ rest = rest.substring(rMatch[0].length).trim();
2245
+ continue;
2246
+ }
2247
+ const gMatch = rest.match(/^g(\d+)/i);
2248
+ if (gMatch) {
2249
+ gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2250
+ metaTokens.push(gMatch[0]);
2251
+ rest = rest.substring(gMatch[0].length).trim();
2252
+ continue;
2253
+ }
2254
+ const hMatch = rest.match(/^h(\d+)/i);
2255
+ if (hMatch) {
2256
+ breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2257
+ metaTokens.push(hMatch[0]);
2258
+ rest = rest.substring(hMatch[0].length).trim();
2259
+ continue;
2260
+ }
2066
2261
  break;
2067
2262
  }
2068
2263
  const lyricLines = [rest];
2069
- while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
2070
- lyricLines.push(segments[++i]);
2264
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1])) {
2265
+ lyricLines.push(segments[++i2]);
2071
2266
  }
2072
2267
  const { syllables, lineBreaks } = normalizeLyricLines(lyricLines);
2073
2268
  tracks.set(trackId, {
@@ -2077,6 +2272,10 @@ var parseLyrics = (mml) => {
2077
2272
  gate,
2078
2273
  pan,
2079
2274
  octave,
2275
+ vibrato,
2276
+ reverb,
2277
+ gender,
2278
+ breathiness,
2080
2279
  syllables,
2081
2280
  metaText: metaTokens.join(" "),
2082
2281
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2087,13 +2286,13 @@ var parseLyrics = (mml) => {
2087
2286
  var stripLyrics = (mml) => {
2088
2287
  const segments = splitSegments(mml);
2089
2288
  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++;
2289
+ for (let i2 = 0; i2 < segments.length; i2++) {
2290
+ if (LYRIC_LINE.test(segments[i2])) {
2291
+ while (i2 + 1 < segments.length && isLyricContinuation(segments[i2 + 1]))
2292
+ i2++;
2094
2293
  continue;
2095
2294
  }
2096
- kept.push(segments[i]);
2295
+ kept.push(segments[i2]);
2097
2296
  }
2098
2297
  return kept.join("\n");
2099
2298
  };
@@ -2162,7 +2361,7 @@ var FORMANTS = {
2162
2361
  N: [250, 1e3]
2163
2362
  };
2164
2363
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2165
- var createKlattVoice = (ctx, destination) => {
2364
+ var createKlattVoice = (ctx, destination, reverbBus) => {
2166
2365
  const active = /* @__PURE__ */ new Set();
2167
2366
  const voice = (syllable, e) => {
2168
2367
  const t0 = ctx.currentTime + e.when;
@@ -2172,14 +2371,21 @@ var createKlattVoice = (ctx, destination) => {
2172
2371
  const attack = 0.02;
2173
2372
  const release = 0.06;
2174
2373
  const sustainEnd = t0 + Math.max(attack + 0.02, e.duration);
2374
+ const dest = e.destination ?? destination;
2175
2375
  let panner = null;
2176
- let out = destination;
2376
+ let out = dest;
2177
2377
  if (typeof ctx.createStereoPanner === "function") {
2178
2378
  panner = ctx.createStereoPanner();
2179
2379
  panner.pan.value = Math.max(-1, Math.min(1, e.pan ?? 0));
2180
- panner.connect(destination);
2380
+ panner.connect(dest);
2181
2381
  out = panner;
2182
2382
  }
2383
+ let sendGain = null;
2384
+ if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2385
+ sendGain = ctx.createGain();
2386
+ sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2387
+ panner.connect(sendGain).connect(reverbBus);
2388
+ }
2183
2389
  const osc = ctx.createOscillator();
2184
2390
  osc.type = "sawtooth";
2185
2391
  osc.frequency.value = midiToFreq(e.pitch);
@@ -2208,7 +2414,7 @@ var createKlattVoice = (ctx, destination) => {
2208
2414
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
2209
2415
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
2210
2416
  const data = buffer.getChannelData(0);
2211
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
2417
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
2212
2418
  const src = ctx.createBufferSource();
2213
2419
  src.buffer = buffer;
2214
2420
  const hp = ctx.createBiquadFilter();
@@ -2235,6 +2441,7 @@ var createKlattVoice = (ctx, destination) => {
2235
2441
  active.delete(osc);
2236
2442
  osc.disconnect();
2237
2443
  panner?.disconnect();
2444
+ sendGain?.disconnect();
2238
2445
  };
2239
2446
  };
2240
2447
  voice.stopAll = () => {
@@ -2253,6 +2460,7 @@ var KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
2253
2460
  var KOE_VOICEBANKS = {
2254
2461
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
2255
2462
  rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
2463
+ rino121: "\u6625\u97F3\u30EA\u30CEver.1.1(226).koe",
2256
2464
  roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
2257
2465
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
2258
2466
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
@@ -2261,11 +2469,13 @@ var KOE_VOICEBANKS = {
2261
2469
  rei: "\u8DB3\u7ACB\u30EC\u30A4ver3.5.0.koe",
2262
2470
  mgroid: "MGRoid_\u539F\u97F3\u8A2D\u5B9A\u6E08\u307F.koe",
2263
2471
  motroid: "MOTRoid\u5B8C\u5168\u7248V2.koe",
2264
- nynroid: "NYNRoidver1.4.koe"
2472
+ nynroid: "NYNRoidver1.4.koe",
2473
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3\u97F3\u6E90.koe"
2265
2474
  };
2266
2475
  var KOE_VOICEBANK_LABELS = {
2267
2476
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
2268
2477
  rino: "\u6625\u97F3\u30EA\u30CE",
2478
+ rino121: "\u6625\u97F3\u30EA\u30CEv1.2.1",
2269
2479
  roze: "\u675F\u97F3\u30ED\u30BC",
2270
2480
  ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
2271
2481
  ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
@@ -2274,12 +2484,14 @@ var KOE_VOICEBANK_LABELS = {
2274
2484
  rei: "\u8DB3\u7ACB\u30EC\u30A4",
2275
2485
  mgroid: "MGRoid",
2276
2486
  motroid: "MOTRoid",
2277
- nynroid: "NYNRoid"
2487
+ nynroid: "NYNRoid",
2488
+ uc: "\u84C4\u97F3\u30AD\u30EA\u30B3"
2278
2489
  };
2279
2490
  var VOICE_IMAGE_KEY = {
2280
2491
  klatt: "puyuyu",
2281
2492
  tsukuyomi: "tsukuyomi",
2282
2493
  rino: "rino",
2494
+ rino121: "rino",
2283
2495
  roze: "roze",
2284
2496
  ruko_male: "ruko",
2285
2497
  ruko_female: "ruko",
@@ -2288,11 +2500,13 @@ var VOICE_IMAGE_KEY = {
2288
2500
  rei: "rei",
2289
2501
  mgroid: "MGRoid",
2290
2502
  motroid: "MOTRoid",
2291
- nynroid: "NYNRoid"
2503
+ nynroid: "NYNRoid",
2504
+ uc: "uc"
2292
2505
  };
2293
2506
  var KOE_VOICEBANK_TERMS = {
2294
2507
  tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
2295
2508
  rino: "https://hatenakun1.github.io/halunelino/",
2509
+ rino121: "https://harunerino.vercel.app/",
2296
2510
  roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
2297
2511
  ruko_male: "https://long-sleeper.net/index.php?id=22",
2298
2512
  ruko_female: "https://long-sleeper.net/index.php?id=22",
@@ -2301,7 +2515,8 @@ var KOE_VOICEBANK_TERMS = {
2301
2515
  rei: "https://mechanicalgirl.jp/guidelines/",
2302
2516
  mgroid: "https://x.com/nisusansu/status/1048825378188353536",
2303
2517
  motroid: "https://www.nicovideo.jp/watch/sm40031282",
2304
- nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs"
2518
+ nynroid: "https://www.bilibili.com/video/BV1V24y1a7qs",
2519
+ uc: "https://chi9nekiriko.wixsite.com/home/%E5%88%A9%E7%94%A8%E8%A6%8F%E7%B4%84"
2305
2520
  };
2306
2521
  var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
2307
2522
  var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
@@ -2390,7 +2605,7 @@ var createLocalBackend = async (options) => {
2390
2605
  }
2391
2606
  return p;
2392
2607
  };
2393
- const renderAlias = async (alias, pitch, durationMs) => {
2608
+ const renderAlias = async (alias, pitch, durationMs, vibrato, expr) => {
2394
2609
  const pcm = await getPcm(alias);
2395
2610
  if (!pcm || pcm.length === 0) return null;
2396
2611
  const entry = bank.manifest.phonemes[alias];
@@ -2399,9 +2614,11 @@ var createLocalBackend = async (options) => {
2399
2614
  if (worldline) {
2400
2615
  const audio = worldline.renderNote({
2401
2616
  pcm,
2402
- pitch: targetHz,
2617
+ pitch: vibrato ? vibratoPitchCurve(targetHz, lead.preMs) : targetHz,
2403
2618
  durationMs,
2404
- ...lead
2619
+ ...lead,
2620
+ gender: expr?.gender,
2621
+ breathiness: expr?.breathiness
2405
2622
  });
2406
2623
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
2407
2624
  }
@@ -2466,7 +2683,7 @@ var createWorkerBackend = async (workerUrl, options) => {
2466
2683
  });
2467
2684
  onReady = null;
2468
2685
  onFail = null;
2469
- const renderAlias = (alias, pitch, durationMs) => new Promise((resolve) => {
2686
+ const renderAlias = (alias, pitch, durationMs, vibrato, expr) => new Promise((resolve) => {
2470
2687
  const id = ++reqId;
2471
2688
  pending.set(
2472
2689
  id,
@@ -2479,7 +2696,10 @@ var createWorkerBackend = async (workerUrl, options) => {
2479
2696
  id,
2480
2697
  alias,
2481
2698
  pitch,
2482
- durationMs
2699
+ durationMs,
2700
+ vibrato,
2701
+ gender: expr?.gender,
2702
+ breathiness: expr?.breathiness
2483
2703
  });
2484
2704
  });
2485
2705
  return {
@@ -2508,15 +2728,21 @@ var createKoeVoice = async (ctx, destination, options) => {
2508
2728
  const inflight = /* @__PURE__ */ new Map();
2509
2729
  const active = /* @__PURE__ */ new Set();
2510
2730
  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);
2731
+ const keyOf = (alias, pitch, durationMs, vibrato, expr) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}${expr?.gender !== void 0 ? `|g${Math.round(expr.gender * 100)}` : ""}${expr?.breathiness !== void 0 ? `|h${Math.round(expr.breathiness * 100)}` : ""}`;
2732
+ const renderInto = (alias, pitch, durationMs, vibrato, expr) => {
2733
+ const key = keyOf(alias, pitch, durationMs, vibrato, expr);
2514
2734
  const existing = renderCache.get(key);
2515
2735
  if (existing !== void 0) return Promise.resolve(existing);
2516
2736
  const flying = inflight.get(key);
2517
2737
  if (flying) return flying;
2518
2738
  const p = (async () => {
2519
- const out = await backend.renderAlias(alias, pitch, durationMs);
2739
+ const out = await backend.renderAlias(
2740
+ alias,
2741
+ pitch,
2742
+ durationMs,
2743
+ vibrato,
2744
+ expr
2745
+ );
2520
2746
  let rendered = null;
2521
2747
  if (out) {
2522
2748
  const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
@@ -2531,15 +2757,22 @@ var createKoeVoice = async (ctx, destination, options) => {
2531
2757
  return p;
2532
2758
  };
2533
2759
  const LEADCAP_S = 0.09;
2534
- const schedule = (r, t0, peak, pan) => {
2535
- let out = destination;
2760
+ const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2761
+ const dest = destOverride ?? destination;
2762
+ let out = dest;
2536
2763
  let panner = null;
2537
2764
  if (typeof ctx.createStereoPanner === "function") {
2538
2765
  panner = ctx.createStereoPanner();
2539
2766
  panner.pan.value = Math.max(-1, Math.min(1, pan));
2540
- panner.connect(destination);
2767
+ panner.connect(dest);
2541
2768
  out = panner;
2542
2769
  }
2770
+ let sendGain = null;
2771
+ if (options.reverbBus && reverbSend > 0 && panner) {
2772
+ sendGain = ctx.createGain();
2773
+ sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2774
+ panner.connect(sendGain).connect(options.reverbBus);
2775
+ }
2543
2776
  const src = ctx.createBufferSource();
2544
2777
  src.buffer = r.audio;
2545
2778
  src.playbackRate.value = r.rate;
@@ -2565,6 +2798,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2565
2798
  src.disconnect();
2566
2799
  env.disconnect();
2567
2800
  panner?.disconnect();
2801
+ sendGain?.disconnect();
2568
2802
  };
2569
2803
  };
2570
2804
  const model = (syllable, e) => {
@@ -2583,10 +2817,10 @@ var createKoeVoice = async (ctx, destination, options) => {
2583
2817
  const pan = e.pan ?? 0;
2584
2818
  const durationMs = Math.max(60, e.duration * 1e3);
2585
2819
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2586
- if (r) schedule(r, t0, peak, pan);
2820
+ if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2587
2821
  });
2588
2822
  };
2589
- model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs) => {
2823
+ model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
2590
2824
  if (syllable.consonant === "Q" || syllable.vowel === "") return null;
2591
2825
  const alias = resolveKoeAlias(
2592
2826
  backend.hasAlias,
@@ -2597,12 +2831,13 @@ var createKoeVoice = async (ctx, destination, options) => {
2597
2831
  );
2598
2832
  if (!alias) return null;
2599
2833
  const dMs = Math.max(60, durationMs);
2600
- const r = await renderInto(alias, pitch, dMs);
2601
- return r ? keyOf(alias, pitch, dMs) : null;
2834
+ const vib = !!vibrato && dMs / 1e3 >= VIBRATO_MIN_SEC;
2835
+ const r = await renderInto(alias, pitch, dMs, vib, expr);
2836
+ return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2602
2837
  };
2603
- model.scheduleCached = (key, t0, peak, pan) => {
2838
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2604
2839
  const r = renderCache.get(key);
2605
- if (r) schedule(r, t0, peak, pan);
2840
+ if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2606
2841
  };
2607
2842
  model.stopAll = () => {
2608
2843
  for (const src of active) {
@@ -2633,7 +2868,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2633
2868
  const masterGain = ctx.createGain();
2634
2869
  masterGain.connect(destination);
2635
2870
  const loaded = /* @__PURE__ */ new Map([
2636
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain)]
2871
+ [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
2637
2872
  ]);
2638
2873
  const loading = /* @__PURE__ */ new Map();
2639
2874
  const load2 = (model) => {
@@ -2653,7 +2888,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2653
2888
  koe,
2654
2889
  worldlineScriptUrl: options.worldlineScriptUrl,
2655
2890
  lightweight: options.lightweight,
2656
- voiceWorkerUrl: options.voiceWorkerUrl
2891
+ voiceWorkerUrl: options.voiceWorkerUrl,
2892
+ reverbBus: options.reverbBus
2657
2893
  })
2658
2894
  ))().then((v) => {
2659
2895
  loaded.set(m, v);
@@ -2695,10 +2931,14 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2695
2931
  const m = loaded.get(track.model.toLowerCase());
2696
2932
  if (!m?.renderToCache) continue;
2697
2933
  let n = 0;
2934
+ const expr = {
2935
+ gender: track.gender,
2936
+ breathiness: track.breathiness
2937
+ };
2698
2938
  forEachSungNote(track, (note, prevVowel) => {
2699
2939
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2700
2940
  n++;
2701
- tasks.push({ model: m, note, prevVowel });
2941
+ tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
2702
2942
  });
2703
2943
  }
2704
2944
  const total = tasks.length;
@@ -2713,7 +2953,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2713
2953
  task.note.syllable,
2714
2954
  task.prevVowel,
2715
2955
  task.note.pitch,
2716
- task.note.durationSec * 1e3
2956
+ task.note.durationSec * 1e3,
2957
+ task.vibrato,
2958
+ task.expr
2717
2959
  ) ?? Promise.resolve(null));
2718
2960
  done++;
2719
2961
  onProgress?.(done, total);
@@ -2758,13 +3000,22 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2758
3000
  note.syllable,
2759
3001
  prevVowel,
2760
3002
  note.pitch,
2761
- note.durationSec * 1e3
3003
+ note.durationSec * 1e3,
3004
+ track.vibrato,
3005
+ { gender: track.gender, breathiness: track.breathiness }
2762
3006
  );
2763
3007
  if (session !== streamSession) return;
2764
3008
  if (key) {
2765
3009
  const delay = ctx.currentTime - t0;
2766
3010
  if (delay < 0.05) {
2767
- scheduleCached(key, t0, peak, track.pan);
3011
+ scheduleCached(
3012
+ key,
3013
+ t0,
3014
+ peak,
3015
+ track.pan,
3016
+ track.reverbSend,
3017
+ options.getTrackDestination?.(track.id ?? "")
3018
+ );
2768
3019
  opts?.onScheduled?.(track, note, t0);
2769
3020
  } else {
2770
3021
  console.warn(
@@ -2777,13 +3028,15 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2777
3028
  } else {
2778
3029
  const when = t0 - ctx.currentTime;
2779
3030
  model(note.syllable, {
2780
- trackId: "",
3031
+ trackId: track.id ?? "",
2781
3032
  pitch: note.pitch,
2782
3033
  velocity: 100,
2783
3034
  volume: peak,
2784
3035
  when,
2785
3036
  duration: note.durationSec,
2786
- pan: track.pan
3037
+ pan: track.pan,
3038
+ reverbSend: track.reverbSend,
3039
+ destination: options.getTrackDestination?.(track.id ?? "")
2787
3040
  });
2788
3041
  opts?.onScheduled?.(track, note, t0);
2789
3042
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -2842,8 +3095,10 @@ var PITCH_MAP = {
2842
3095
  b: 11
2843
3096
  };
2844
3097
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2845
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|mode)=([\w-]+)/gi;
3098
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
2846
3099
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3100
+ var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3101
+ var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
2847
3102
  var parseMmlMeta = (mml) => {
2848
3103
  const meta = {};
2849
3104
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -2857,6 +3112,9 @@ var parseMmlMeta = (mml) => {
2857
3112
  } else if (key === "drumvolume") {
2858
3113
  const dv = Number.parseInt(m[2], 10);
2859
3114
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3115
+ } else if (key === "reverb") {
3116
+ const rv = Number.parseInt(m[2], 10);
3117
+ if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
2860
3118
  } else if (key === "mode") {
2861
3119
  if (m[2] === "simple" || m[2] === "advanced") {
2862
3120
  meta.mode = m[2];
@@ -2871,9 +3129,25 @@ var parseMmlMeta = (mml) => {
2871
3129
  meta.trackInstruments[idx] = name;
2872
3130
  }
2873
3131
  }
3132
+ for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3133
+ const idx = Number.parseInt(m[1], 10);
3134
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3135
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3136
+ meta.trackCompression ??= {};
3137
+ meta.trackCompression[idx] = val;
3138
+ }
3139
+ }
3140
+ for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3141
+ const idx = Number.parseInt(m[1], 10);
3142
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3143
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3144
+ meta.trackWidth ??= {};
3145
+ meta.trackWidth[idx] = val;
3146
+ }
3147
+ }
2874
3148
  return meta;
2875
3149
  };
2876
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "");
3150
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
2877
3151
  var formatMmlMeta = (meta, space = "") => {
2878
3152
  const parts = [];
2879
3153
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -2882,12 +3156,24 @@ var formatMmlMeta = (meta, space = "") => {
2882
3156
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
2883
3157
  if (meta.drumVolume !== void 0)
2884
3158
  parts.push(`#drumvolume=${meta.drumVolume}`);
3159
+ if (meta.reverb !== void 0 && meta.reverb !== 0)
3160
+ parts.push(`#reverb=${meta.reverb}`);
2885
3161
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
2886
3162
  if (meta.trackInstruments) {
2887
3163
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
2888
3164
  if (name) parts.push(`#t${idx}inst=${name}`);
2889
3165
  }
2890
3166
  }
3167
+ if (meta.trackCompression) {
3168
+ for (const [idx, val] of Object.entries(meta.trackCompression)) {
3169
+ if (val !== 0) parts.push(`#t${idx}comp=${val}`);
3170
+ }
3171
+ }
3172
+ if (meta.trackWidth) {
3173
+ for (const [idx, val] of Object.entries(meta.trackWidth)) {
3174
+ if (val !== 100) parts.push(`#t${idx}width=${val}`);
3175
+ }
3176
+ }
2891
3177
  return parts.join(space);
2892
3178
  };
2893
3179
  var parseMML = (mml, options = {}) => {
@@ -5644,7 +5930,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
5644
5930
  const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
5645
5931
  const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
5646
5932
  const data = buffer.getChannelData(0);
5647
- for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
5933
+ for (let i2 = 0; i2 < length; i2++) data[i2] = Math.random() * 2 - 1;
5648
5934
  const src = ctx.createBufferSource();
5649
5935
  src.buffer = buffer;
5650
5936
  const filter = ctx.createBiquadFilter();
@@ -6629,6 +6915,23 @@ var DAW_CSS = `
6629
6915
  @keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
6630
6916
  .dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
6631
6917
 
6918
+ /* \u2500\u2500\u2500 \u97F3\u5272\u308C\u691C\u77E5\u30D0\u30C3\u30B8 \u2500\u2500\u2500 \u30AF\u30EA\u30C3\u30D7\u767A\u751F\u4E2D\u3060\u3051\u8868\u793A\u3055\u308C\u308B\u8B66\u544A\u30DC\u30BF\u30F3\u3002\u30AF\u30EA\u30C3\u30AF\u3067\u6D88\u305B\u308B\u3002 */
6919
+ .dtm-clip-badge {
6920
+ flex: 0 0 auto;
6921
+ min-height: 24px;
6922
+ padding: 0 8px;
6923
+ border: 2px solid var(--c-black);
6924
+ background: var(--dtm-danger);
6925
+ color: var(--c-white);
6926
+ font-family: var(--dtm-font);
6927
+ font-size: 10px;
6928
+ letter-spacing: .1em;
6929
+ cursor: pointer;
6930
+ box-shadow: 0 0 0 2px var(--dtm-danger), 2px 2px 0 var(--c-black);
6931
+ animation: dtm-blink .5s steps(1) infinite;
6932
+ }
6933
+ .dtm-clip-badge:active { transform: translate(2px,2px); box-shadow: none; }
6934
+
6632
6935
  /* \u2500\u2500\u2500 \u30A4\u30F3\u30D5\u30A9\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
6633
6936
  .dtm-infobtn {
6634
6937
  display: inline-flex;
@@ -6904,6 +7207,37 @@ var DAW_CSS = `
6904
7207
  object-fit: cover;
6905
7208
  image-rendering: pixelated;
6906
7209
  }
7210
+ /* \u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u8A73\u7D30\u8A2D\u5B9A\u300D\u2014 \u5E38\u7528\u3057\u306A\u3044\u30D1\u30E9\u30E1\u30FC\u30BF\uFF08\u30AA\u30AF\u30BF\u30FC\u30D6/\u5B9A\u4F4D/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/
7211
+ \u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30D3\u30D6\u30E9\u30FC\u30C8\uFF09\u3092\u7573\u3093\u3067\u304A\u304F\u8EFD\u91CF\u306A details\u3002dtm-panel \u307B\u3069
7212
+ \u4EF0\u3005\u3057\u304F\u305B\u305A\u3001\u63A7\u3048\u3081\u306A\u4ED5\u5207\u308A\u7DDA\uFF0B\u5C0F\u3055\u3044\u25B6\u30DE\u30FC\u30AB\u30FC\u3060\u3051\u4ED8\u3051\u308B\u3002 */
7213
+ .dtm-advanced {
7214
+ border-top: 1px dashed var(--dtm-border2);
7215
+ padding-top: 6px;
7216
+ display: flex;
7217
+ flex-direction: column;
7218
+ gap: 6px;
7219
+ }
7220
+ .dtm-advanced > summary {
7221
+ list-style: none;
7222
+ cursor: pointer;
7223
+ font-size: 11px;
7224
+ letter-spacing: .08em;
7225
+ color: var(--dtm-text);
7226
+ display: flex;
7227
+ align-items: center;
7228
+ gap: 6px;
7229
+ min-height: 28px;
7230
+ padding: 0 8px;
7231
+ border: 2px solid var(--dtm-border2);
7232
+ background: var(--dtm-deep);
7233
+ box-shadow: 2px 2px 0 var(--c-black);
7234
+ width: fit-content;
7235
+ }
7236
+ .dtm-advanced > summary:active { transform: translate(2px,2px); box-shadow: none; }
7237
+ .dtm-advanced > summary::-webkit-details-marker { display: none; }
7238
+ .dtm-advanced > summary::before { content: "\u25B6"; font-size: 9px; color: var(--dtm-accent); }
7239
+ .dtm-advanced[open] > summary::before { content: "\u25BC"; }
7240
+ .dtm-advanced[open] > summary { border-color: var(--dtm-primary); color: var(--dtm-primary); }
6907
7241
 
6908
7242
  /* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
6909
7243
  @media (min-width: 768px) {
@@ -7565,6 +7899,9 @@ var teto_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAA
7565
7899
  // assets/tsukuyomi.png
7566
7900
  var tsukuyomi_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB3UlEQVR42tWXPUsDQRCGnwuijYIoFqKmEDSCxIgIgqnsogbsrC3ERiwN4g8Ikk6sFH+BnYofvcTKxoigBCzEYGGjWIiCOYvNBHe5TdQUt051czc3e++zO7tzHnUst7vn04BlFue9Ws8jhGxN9ZTPzU43NkIlj41E6AQ8m/JYfByAoWgXAPtHJzRCRN43SbizBkzlv1X2VzLuVoHYzf0TAMnys+aLCTGThMTJGnKfQOpyWF1cvgHQsh4FYPDzRd3vmQHgoPShJbi9uqhJyPk1UK3Jwsq1/115rLst8AX/4UFP0NsbGHf7+Kr579l7zR/ZGvbcrAJR7vvqEOwcTdZMYIuTPCYJUS7Ewydgzn2Bay2gdWzqR4nMuMPlhcC5l/HcWQOnCaU4lVU3xI9tnwFQnOgDoDmdDs5QKmlxYhdGmBA2ibhTBfKFHCkCm00dAKymJn+USAidHx8DkI+0q7PBUC7j5BJ7jlSB7RyXziVT8fM7Oz7AeLmsz3VEaWi7UvtAcmvJC+qmq4T/XT8gdTsw2692to07AF7jeU35wFq/7COqzhdVnVOnu3aHgNm1VpVXlImJX9zQffO5kDg1dlb3CNj6dZsVK2vAVP7Xf8bwCfxWuc23Eanmt/xlh07gC6oBviFE8rZHAAAAAElFTkSuQmCC";
7567
7901
 
7902
+ // assets/uc.png
7903
+ 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==";
7904
+
7568
7905
  // src/voice-images.ts
7569
7906
  var VOICE_IMAGES = {
7570
7907
  puyuyu: puyuyu_default,
@@ -7577,7 +7914,8 @@ var VOICE_IMAGES = {
7577
7914
  rei: rei_default,
7578
7915
  MGRoid: MGRoid_default,
7579
7916
  MOTRoid: MOTRoid_default,
7580
- NYNRoid: NYNRoid_default
7917
+ NYNRoid: NYNRoid_default,
7918
+ uc: uc_default
7581
7919
  };
7582
7920
  var FALLBACK_VOCAL_ICON = Chip_default;
7583
7921
 
@@ -7639,8 +7977,8 @@ var copyToClipboard = async (doc, text) => {
7639
7977
  };
7640
7978
  var toBase64Url = (bytes) => {
7641
7979
  let bin = "";
7642
- for (let i = 0; i < bytes.length; i++) {
7643
- bin += String.fromCharCode(bytes[i]);
7980
+ for (let i2 = 0; i2 < bytes.length; i2++) {
7981
+ bin += String.fromCharCode(bytes[i2]);
7644
7982
  }
7645
7983
  return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
7646
7984
  };
@@ -7653,8 +7991,8 @@ var SHIFT_KATAKANA = 255;
7653
7991
  var VALUE_PROLONGED = 223;
7654
7992
  var customEncode = (str) => {
7655
7993
  const bytes = [];
7656
- for (let i = 0; i < str.length; i++) {
7657
- const code = str.charCodeAt(i);
7994
+ for (let i2 = 0; i2 < str.length; i2++) {
7995
+ const code = str.charCodeAt(i2);
7658
7996
  if (code === 32) {
7659
7997
  continue;
7660
7998
  }
@@ -7696,37 +8034,37 @@ var fromBase64Url = (s) => {
7696
8034
  }
7697
8035
  const bin = atob(normalized);
7698
8036
  const bytes = new Uint8Array(bin.length);
7699
- for (let i = 0; i < bin.length; i++) {
7700
- bytes[i] = bin.charCodeAt(i);
8037
+ for (let i2 = 0; i2 < bin.length; i2++) {
8038
+ bytes[i2] = bin.charCodeAt(i2);
7701
8039
  }
7702
8040
  return bytes;
7703
8041
  };
7704
8042
  var customDecode = (bytes) => {
7705
8043
  let str = "";
7706
- let i = 0;
7707
- while (i < bytes.length) {
7708
- const byte = bytes[i];
8044
+ let i2 = 0;
8045
+ while (i2 < bytes.length) {
8046
+ const byte = bytes[i2];
7709
8047
  if (byte <= 127) {
7710
8048
  str += String.fromCharCode(byte);
7711
- i++;
8049
+ i2++;
7712
8050
  } else if (byte === VALUE_PROLONGED) {
7713
8051
  str += String.fromCharCode(PROLONGED_MARK);
7714
- i++;
8052
+ i2++;
7715
8053
  } else if (byte >= 128 && byte <= 222) {
7716
8054
  str += String.fromCharCode(HIRAGANA_START + (byte - 128));
7717
- i++;
8055
+ i2++;
7718
8056
  } else if (byte === SHIFT_KATAKANA) {
7719
- if (i + 1 < bytes.length) {
7720
- const nextByte = bytes[i + 1];
8057
+ if (i2 + 1 < bytes.length) {
8058
+ const nextByte = bytes[i2 + 1];
7721
8059
  if (nextByte >= 128 && nextByte <= 222) {
7722
8060
  str += String.fromCharCode(HIRAGANA_START + 96 + (nextByte - 128));
7723
8061
  }
7724
- i += 2;
8062
+ i2 += 2;
7725
8063
  } else {
7726
- i++;
8064
+ i2++;
7727
8065
  }
7728
8066
  } else {
7729
- i++;
8067
+ i2++;
7730
8068
  }
7731
8069
  }
7732
8070
  return str;
@@ -8291,7 +8629,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8291
8629
  const beatRow = doc.createElement("div");
8292
8630
  beatRow.className = "dtm-player-beat-row";
8293
8631
  const beatDots = [];
8294
- for (let i = 0; i < 4; i++) {
8632
+ for (let i2 = 0; i2 < 4; i2++) {
8295
8633
  const d = doc.createElement("span");
8296
8634
  d.className = "dtm-player-beat-dot";
8297
8635
  beatRow.appendChild(d);
@@ -8395,9 +8733,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8395
8733
  lane.appendChild(metaEl);
8396
8734
  }
8397
8735
  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)) {
8736
+ for (let i2 = 0; i2 < count; i2++) {
8737
+ const note = notes[i2];
8738
+ if (breaks.has(i2)) {
8401
8739
  const br = doc.createElement("span");
8402
8740
  br.className = "dtm-tk dtm-tk--break";
8403
8741
  br.textContent = "\\n";
@@ -8405,7 +8743,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8405
8743
  }
8406
8744
  const span = doc.createElement("span");
8407
8745
  span.className = "dtm-tk dtm-tk--lyric";
8408
- span.textContent = lyricTrack.syllables[i].kana;
8746
+ span.textContent = lyricTrack.syllables[i2].kana;
8409
8747
  lane.appendChild(span);
8410
8748
  laneTokens.push({
8411
8749
  el: span,
@@ -8558,8 +8896,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8558
8896
  const intStep = Math.floor(step);
8559
8897
  playStep = intStep;
8560
8898
  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);
8899
+ for (let i2 = 0; i2 < 4; i2++)
8900
+ beatDots[i2].classList.toggle("dtm-player-beat-dot--on", i2 === beatIndex);
8563
8901
  barEl.textContent = String(Math.floor(step / STEPS_PER_BAR2) + 1);
8564
8902
  if (!isSeeking) {
8565
8903
  seekInput.value = String(Math.min(maxStep, Math.max(0, intStep)));
@@ -8654,11 +8992,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8654
8992
  const semis = (lt.octave ?? 0) * 12;
8655
8993
  const count = Math.min(sorted.length, lt.syllables.length);
8656
8994
  const notes = [];
8657
- for (let i = 0; i < count; i++) {
8658
- const n = sorted[i];
8995
+ for (let i2 = 0; i2 < count; i2++) {
8996
+ const n = sorted[i2];
8659
8997
  if (n.startStep < fromStep) continue;
8660
8998
  notes.push({
8661
- syllable: lt.syllables[i],
8999
+ syllable: lt.syllables[i2],
8662
9000
  pitch: n.pitch + semis,
8663
9001
  startSec: (n.startStep - fromStep) * secondsPerStep,
8664
9002
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -8669,6 +9007,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8669
9007
  model: lt.model,
8670
9008
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
8671
9009
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9010
+ vibrato: lt.vibrato,
9011
+ reverbSend: (lt.reverb ?? 0) / 100,
9012
+ gender: (lt.gender ?? 50) / 100,
9013
+ breathiness: (lt.breathiness ?? 50) / 100,
8672
9014
  notes
8673
9015
  };
8674
9016
  });
@@ -8928,13 +9270,13 @@ var findZone = (ctx, fontName, zones, pitchs = []) => {
8928
9270
  for (const zone of zones) {
8929
9271
  const low = zone.keyRangeLow | 0, high = zone.keyRangeHigh | 0;
8930
9272
  if (low > high) continue;
8931
- for (let i = low; i <= high; i++) pitchs.push(i);
9273
+ for (let i2 = low; i2 <= high; i2++) pitchs.push(i2);
8932
9274
  }
8933
9275
  const set = new Set(pitchs);
8934
9276
  const map = new Map(pitchs.map((v) => [v, zones[0]]));
8935
- for (let i = zones.length - 1; i >= 0; i--)
9277
+ for (let i2 = zones.length - 1; i2 >= 0; i2--)
8936
9278
  for (const v of set) {
8937
- const zone = zones[i];
9279
+ const zone = zones[i2];
8938
9280
  if (v < zone.keyRangeLow || v > zone.keyRangeHigh) continue;
8939
9281
  set.delete(v);
8940
9282
  map.set(v, { ...zone });
@@ -8964,13 +9306,13 @@ var adjustZone = async (ctx, fontName, zone) => {
8964
9306
  const decoded = atob(zone.sample);
8965
9307
  zone.buffer = ctx.createBuffer(1, decoded.length / 2, zone.sampleRate);
8966
9308
  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);
9309
+ for (let i2 = 0; i2 < decoded.length / 2; i2++) {
9310
+ let b1 = decoded.charCodeAt(i2 * 2), b2 = decoded.charCodeAt(i2 * 2 + 1);
8969
9311
  if (b1 < 0) b1 = 256 + b1;
8970
9312
  if (b2 < 0) b2 = 256 + b2;
8971
9313
  let n = b2 * 256 + b1;
8972
9314
  if (n >= 65536 / 2) n = n - 65536;
8973
- a[i] = n / 65536;
9315
+ a[i2] = n / 65536;
8974
9316
  }
8975
9317
  } else if (zone.file) {
8976
9318
  const bytes = Uint8Array.from(atob(zone.file), (c) => c.charCodeAt(0));
@@ -9010,10 +9352,10 @@ var adjustZone = async (ctx, fontName, zone) => {
9010
9352
  let loopPeak = 0;
9011
9353
  if (oldBuf.numberOfChannels > 0) {
9012
9354
  const ch0 = oldBuf.getChannelData(0);
9013
- for (let i = 0; i < ch0.length; i++) {
9014
- const abs = Math.abs(ch0[i]);
9355
+ for (let i2 = 0; i2 < ch0.length; i2++) {
9356
+ const abs = Math.abs(ch0[i2]);
9015
9357
  if (abs > totalPeak) totalPeak = abs;
9016
- if (i >= loopStartFrame && i < loopEndFrame) {
9358
+ if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9017
9359
  if (abs > loopPeak) loopPeak = abs;
9018
9360
  }
9019
9361
  }
@@ -9032,16 +9374,16 @@ var adjustZone = async (ctx, fontName, zone) => {
9032
9374
  for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9033
9375
  const oldData = oldBuf.getChannelData(ch);
9034
9376
  const newData = newBuf.getChannelData(ch);
9035
- for (let i = 0; i < attackLength; i++) {
9036
- const ratio = attackLength > 1 ? i / (attackLength - 1) : 0;
9377
+ for (let i2 = 0; i2 < attackLength; i2++) {
9378
+ const ratio = attackLength > 1 ? i2 / (attackLength - 1) : 0;
9037
9379
  const m = 1 + (gainMultiplier - 1) * ratio;
9038
- newData[i] = oldData[i] * m;
9380
+ newData[i2] = oldData[i2] * m;
9039
9381
  }
9040
9382
  let offset = attackLength;
9041
9383
  const loopData = oldData.subarray(loopStartFrame, loopEndFrame);
9042
9384
  const normalizedLoopData = new Float32Array(loopLengthFrame);
9043
- for (let i = 0; i < loopLengthFrame; i++) {
9044
- normalizedLoopData[i] = loopData[i] * gainMultiplier;
9385
+ for (let i2 = 0; i2 < loopLengthFrame; i2++) {
9386
+ normalizedLoopData[i2] = loopData[i2] * gainMultiplier;
9045
9387
  }
9046
9388
  for (let r = 0; r < repeatCount; r++) {
9047
9389
  newData.set(normalizedLoopData, offset);
@@ -9051,8 +9393,8 @@ var adjustZone = async (ctx, fontName, zone) => {
9051
9393
  const releaseData = oldData.subarray(loopEndFrame);
9052
9394
  if (gainMultiplier !== 1) {
9053
9395
  const normalizedRelease = new Float32Array(releaseLength);
9054
- for (let i = 0; i < releaseLength; i++) {
9055
- normalizedRelease[i] = releaseData[i] * gainMultiplier;
9396
+ for (let i2 = 0; i2 < releaseLength; i2++) {
9397
+ normalizedRelease[i2] = releaseData[i2] * gainMultiplier;
9056
9398
  }
9057
9399
  newData.set(normalizedRelease, offset);
9058
9400
  } else {
@@ -9248,10 +9590,10 @@ var buildDisplayLines = (chords, eventsCount) => {
9248
9590
  if (segments.length === 0) continue;
9249
9591
  const hasMultiSeg = segments.length > 1;
9250
9592
  const parts = [];
9251
- for (let i = 0; i < segments.length; i++) {
9252
- if (hasMultiSeg && i > 0)
9593
+ for (let i2 = 0; i2 < segments.length; i2++) {
9594
+ if (hasMultiSeg && i2 > 0)
9253
9595
  parts.push({ text: "|", isChord: false, eventIdx: -1 });
9254
- const bar = segments[i].trim();
9596
+ const bar = segments[i2].trim();
9255
9597
  if (!bar) continue;
9256
9598
  const splitAt = [];
9257
9599
  for (let j = 0; j < bar.length; j++) {
@@ -10370,6 +10712,7 @@ var buildUI = (target, options) => {
10370
10712
  <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
10371
10713
  <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
10372
10714
  <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
10715
+ <button class="dtm-clip-badge dtm-hidden" data-dtm="clip-badge" title="\u97F3\u5272\u308C\u691C\u77E5\uFF08\u30AF\u30EA\u30C3\u30AF\u3067\u6D88\u3059\uFF09">CLIP</button>
10373
10716
  <span class="dtm-grow"></span>
10374
10717
  <span class="dtm-label">BPM</span>
10375
10718
  <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
@@ -10407,13 +10750,8 @@ var buildUI = (target, options) => {
10407
10750
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
10408
10751
 
10409
10752
  <details class="dtm-panel" open>
10410
- <summary>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10753
+ <summary>\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10411
10754
  <div class="dtm-panel-body">
10412
- <div class="dtm-row">
10413
- <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10414
- <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10415
- <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10416
- </div>
10417
10755
  <div class="dtm-track-body" data-dtm="track-body"></div>
10418
10756
  </div>
10419
10757
  </details>
@@ -10446,6 +10784,29 @@ var buildUI = (target, options) => {
10446
10784
  </div>
10447
10785
  </details>
10448
10786
 
10787
+ <details class="dtm-panel" open>
10788
+ <summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10789
+ <div class="dtm-panel-body">
10790
+ <div data-dtm="preset-select-slot"></div>
10791
+ <div class="dtm-row">
10792
+ <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10793
+ <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10794
+ <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10795
+ </div>
10796
+ <div class="dtm-row">
10797
+ <button class="dtm-btn dtm-btn--ghost dtm-btn--xs" data-dtm="auto-master" title="\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\u30FB\u30EA\u30D0\u30FC\u30D6\u3092\u5546\u696D\u66F2\u5BC4\u308A\u306E\u5024\u306B\u4E00\u62EC\u8A2D\u5B9A\u3057\u307E\u3059">\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0</button>
10798
+ <button class="dtm-infobtn" data-dtm="auto-master-info" title="\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10799
+ <span class="dtm-grow"></span>
10800
+ </div>
10801
+ <div class="dtm-row">
10802
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10803
+ <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">
10804
+ <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10805
+ <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10806
+ </div>
10807
+ </div>
10808
+ </details>
10809
+
10449
10810
  <details class="dtm-panel">
10450
10811
  <summary>\u30C9\u30E9\u30E0\u8A2D\u5B9A</summary>
10451
10812
  <div class="dtm-panel-body">
@@ -10611,6 +10972,7 @@ var buildUI = (target, options) => {
10611
10972
  prevBarBtn: sel("prev-bar"),
10612
10973
  nextBarBtn: sel("next-bar"),
10613
10974
  soloCheckbox: sel("solo"),
10975
+ clipBadge: sel("clip-badge"),
10614
10976
  toolPen: sel("tool-pen"),
10615
10977
  toolSelect: sel("tool-select"),
10616
10978
  toolEraser: sel("tool-eraser"),
@@ -10637,6 +10999,11 @@ var buildUI = (target, options) => {
10637
10999
  hScrollThumb: sel("hscroll-thumb"),
10638
11000
  masterVolume: sel("master-volume"),
10639
11001
  masterVolumeLabel: sel("master-volume-label"),
11002
+ reverbAmount: sel("reverb-amount"),
11003
+ reverbAmountLabel: sel("reverb-amount-label"),
11004
+ reverbAmountInfoBtn: sel("reverb-amount-info"),
11005
+ autoMasterBtn: sel("auto-master"),
11006
+ autoMasterInfoBtn: sel("auto-master-info"),
10640
11007
  trackTabs: sel("track-tabs"),
10641
11008
  trackBody: sel("track-body"),
10642
11009
  drumSelect: sel("drum-select"),
@@ -10805,16 +11172,16 @@ var generateRandomPattern = (core, options) => {
10805
11172
  const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
10806
11173
  const rootOffset = Math.floor(Math.random() * 12);
10807
11174
  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);
11175
+ for (let i2 = 0; i2 < 12; i2++) {
11176
+ const noteInOctave = (i2 - rootOffset + 12) % 12;
11177
+ if (scale.includes(noteInOctave)) availablePitches.push(basePitch + i2);
10811
11178
  }
10812
11179
  core.beginBatch();
10813
11180
  for (let bar = 0; bar < numBars; bar++) {
10814
11181
  const barStart = startStep + bar * stepsPerBar;
10815
11182
  const numNotes = Math.floor(Math.random() * 4) + 2;
10816
11183
  const occupied = /* @__PURE__ */ new Set();
10817
- for (let i = 0; i < numNotes; i++) {
11184
+ for (let i2 = 0; i2 < numNotes; i2++) {
10818
11185
  const stepInRange = Math.floor(Math.random() * (stepsPerBar / noteLength)) * noteLength;
10819
11186
  const step = barStart + stepInRange;
10820
11187
  if (occupied.has(step)) continue;
@@ -10915,10 +11282,10 @@ var STEPS_PER_BEAT3 = 48;
10915
11282
  var analyzeMidiTracks = (midi) => {
10916
11283
  const { tracks } = midi;
10917
11284
  const result = [];
10918
- for (let i = 0; i < tracks.length; i++) {
11285
+ for (let i2 = 0; i2 < tracks.length; i2++) {
10919
11286
  const notes = [];
10920
11287
  let currentTime = 0;
10921
- for (const event of tracks[i]) {
11288
+ for (const event of tracks[i2]) {
10922
11289
  currentTime += event.delta;
10923
11290
  if (event.noteOn && event.noteOn.velocity > 0) {
10924
11291
  notes.push({
@@ -10941,8 +11308,8 @@ var analyzeMidiTracks = (midi) => {
10941
11308
  const editableNotes = validNotes.filter((n) => n.channel !== 9);
10942
11309
  if (validNotes.length > 0 && editableNotes.length === 0) continue;
10943
11310
  result.push({
10944
- index: i,
10945
- name: `Ch${i + 1}`,
11311
+ index: i2,
11312
+ name: `Ch${i2 + 1}`,
10946
11313
  noteCount: editableNotes.length,
10947
11314
  selected: editableNotes.length > 0
10948
11315
  });
@@ -10989,8 +11356,8 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
10989
11356
  const pitch = noteOff.noteNumber;
10990
11357
  const channel = event.channel ?? 0;
10991
11358
  if (channelNotes[channel]) {
10992
- for (let i = channelNotes[channel].length - 1; i >= 0; i--) {
10993
- const note = channelNotes[channel][i];
11359
+ for (let i2 = channelNotes[channel].length - 1; i2 >= 0; i2--) {
11360
+ const note = channelNotes[channel][i2];
10994
11361
  if (note.pitch === pitch && note.end === null) {
10995
11362
  note.end = currentTime;
10996
11363
  break;
@@ -11020,10 +11387,10 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11020
11387
  const avgPitch = validNotes.reduce((sum, n) => sum + n.pitch, 0) / validNotes.length;
11021
11388
  let maxSimultaneous = 0;
11022
11389
  const sortedNotes = [...validNotes].sort((a, b) => a.start - b.start);
11023
- for (let i = 0; i < sortedNotes.length; i++) {
11390
+ for (let i2 = 0; i2 < sortedNotes.length; i2++) {
11024
11391
  let simultaneous = 1;
11025
- for (let j = i + 1; j < sortedNotes.length; j++) {
11026
- if (sortedNotes[j].start < sortedNotes[i].end) {
11392
+ for (let j = i2 + 1; j < sortedNotes.length; j++) {
11393
+ if (sortedNotes[j].start < sortedNotes[i2].end) {
11027
11394
  simultaneous++;
11028
11395
  }
11029
11396
  }
@@ -11034,14 +11401,14 @@ var extractMidiPlacements = (midi, selectedTrackIndices) => {
11034
11401
  const blocks = [];
11035
11402
  let blockStart = sortedNotes[0].start;
11036
11403
  let blockEnd = sortedNotes[0].end;
11037
- for (let i = 1; i < sortedNotes.length; i++) {
11038
- const gap = sortedNotes[i].start - sortedNotes[i - 1].end;
11404
+ for (let i2 = 1; i2 < sortedNotes.length; i2++) {
11405
+ const gap = sortedNotes[i2].start - sortedNotes[i2 - 1].end;
11039
11406
  if (gap >= ticksPerBar) {
11040
11407
  blocks.push({ start: blockStart, end: blockEnd });
11041
- blockStart = sortedNotes[i].start;
11042
- blockEnd = sortedNotes[i].end;
11408
+ blockStart = sortedNotes[i2].start;
11409
+ blockEnd = sortedNotes[i2].end;
11043
11410
  } else {
11044
- blockEnd = sortedNotes[i].end;
11411
+ blockEnd = sortedNotes[i2].end;
11045
11412
  }
11046
11413
  }
11047
11414
  blocks.push({ start: blockStart, end: blockEnd });
@@ -11155,9 +11522,9 @@ var extractMidiPlacementsByTrack = (midi, selectedIndices, trackIds) => {
11155
11522
  const noteOff = event.noteOff || event.noteOn;
11156
11523
  if (noteOff) {
11157
11524
  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;
11525
+ for (let i2 = active.length - 1; i2 >= 0; i2--) {
11526
+ if (active[i2].pitch === pitch && active[i2].end === null) {
11527
+ active[i2].end = currentTime;
11161
11528
  break;
11162
11529
  }
11163
11530
  }
@@ -11322,17 +11689,17 @@ var extractDrumPatternFromNotes = (rawNotes, drumFont = "FluidR3_GM_sf2_file:0")
11322
11689
  let prev = bList[0];
11323
11690
  const parts = key.split("_");
11324
11691
  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) {
11692
+ for (let i2 = 1; i2 <= bList.length; i2++) {
11693
+ if (i2 === bList.length || bList[i2] !== prev + 1) {
11327
11694
  const rKey = `${start}-${prev}`;
11328
11695
  if (!rangeMap[rKey]) rangeMap[rKey] = [];
11329
11696
  rangeMap[rKey].push(noteObj);
11330
- if (i < bList.length) {
11331
- start = bList[i];
11332
- prev = bList[i];
11697
+ if (i2 < bList.length) {
11698
+ start = bList[i2];
11699
+ prev = bList[i2];
11333
11700
  }
11334
11701
  } else {
11335
- prev = bList[i];
11702
+ prev = bList[i2];
11336
11703
  }
11337
11704
  }
11338
11705
  }
@@ -11709,8 +12076,8 @@ var drawHeader = () => {
11709
12076
  (g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
11710
12077
  );
11711
12078
  for (let bar = startBar; bar <= endBar + 1; bar++) {
11712
- const x = bar * stepsPerBar * stepWidth;
11713
- const screenX = x;
12079
+ const x2 = bar * stepsPerBar * stepWidth;
12080
+ const screenX = x2;
11714
12081
  g_header_ctx.beginPath();
11715
12082
  g_header_ctx.moveTo(screenX, 0);
11716
12083
  g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
@@ -11757,11 +12124,11 @@ var drawGrid = (noteLengthSteps = 1) => {
11757
12124
  const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
11758
12125
  const endX = g_draw_offset_x + g_grid_canvas.width;
11759
12126
  const lineStep = stepWidth * gridStep;
11760
- for (let x = startX; x <= endX; x += lineStep) {
11761
- const step = x / stepWidth;
12127
+ for (let x2 = startX; x2 <= endX; x2 += lineStep) {
12128
+ const step = x2 / stepWidth;
11762
12129
  const isBarLine = step % stepsPerBar === 0;
11763
12130
  const isNoteLine = step % gridStep === 0;
11764
- const screenX = x - g_draw_offset_x;
12131
+ const screenX = x2 - g_draw_offset_x;
11765
12132
  g_grid_ctx.beginPath();
11766
12133
  g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
11767
12134
  g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
@@ -11823,26 +12190,26 @@ var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
11823
12190
  var getXY = (e) => {
11824
12191
  const { clientX, clientY } = e;
11825
12192
  const rect = g_grid_canvas.getBoundingClientRect();
11826
- const x = Math.floor(clientX - rect.left);
12193
+ const x2 = Math.floor(clientX - rect.left);
11827
12194
  const y = Math.floor(clientY - rect.top);
11828
- return [x, y, e.buttons];
12195
+ return [x2, y, e.buttons];
11829
12196
  };
11830
12197
  var getGridPosition = (e) => {
11831
- const [x, y] = getXY(e);
12198
+ const [x2, y] = getXY(e);
11832
12199
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11833
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12200
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11834
12201
  const absoluteY = y + g_draw_offset_y;
11835
12202
  const yIndex = Math.floor(absoluteY / keyHeight);
11836
12203
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
11837
- return { step, pitch, x, y };
12204
+ return { step, pitch, x: x2, y };
11838
12205
  };
11839
12206
  var onClick = (callback) => {
11840
12207
  g_grid_canvas.addEventListener(
11841
12208
  "click",
11842
12209
  (e) => {
11843
- const [x, y] = getXY(e);
12210
+ const [x2, y] = getXY(e);
11844
12211
  const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
11845
- const step = Math.floor((x + g_draw_offset_x) / stepWidth);
12212
+ const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
11846
12213
  const absoluteY = y + g_draw_offset_y;
11847
12214
  const yIndex = Math.floor(absoluteY / keyHeight);
11848
12215
  const pitch = keyCount - 1 - yIndex + pitchRangeStart;
@@ -11854,8 +12221,8 @@ var onClick = (callback) => {
11854
12221
  );
11855
12222
  g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
11856
12223
  };
11857
- var setDrawOffset = (x, y) => {
11858
- g_draw_offset_x = x;
12224
+ var setDrawOffset = (x2, y) => {
12225
+ g_draw_offset_x = x2;
11859
12226
  g_draw_offset_y = y;
11860
12227
  drawKeyboard();
11861
12228
  drawHeader();
@@ -12170,12 +12537,12 @@ var MMLCore = class _MMLCore {
12170
12537
  currentCursor += steps;
12171
12538
  }
12172
12539
  };
12173
- for (let i = 0; i < sortedSteps.length; i++) {
12174
- const startStep = sortedSteps[i];
12540
+ for (let i2 = 0; i2 < sortedSteps.length; i2++) {
12541
+ const startStep = sortedSteps[i2];
12175
12542
  const notes = notesByStep.get(startStep);
12176
12543
  if (!notes) continue;
12177
12544
  fillRests(startStep);
12178
- const nextStart = sortedSteps[i + 1] ?? endStep;
12545
+ const nextStart = sortedSteps[i2 + 1] ?? endStep;
12179
12546
  const physicsLimit = nextStart - currentCursor;
12180
12547
  if (physicsLimit < MIN_STEP) {
12181
12548
  continue;
@@ -12240,10 +12607,10 @@ var decomposeToMonophonic = (notes) => {
12240
12607
  for (const note of sorted) {
12241
12608
  let assigned = -1;
12242
12609
  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;
12610
+ for (let i2 = 0; i2 < tracks.length; i2++) {
12611
+ if (trackEnds[i2] <= note.startStep && trackEnds[i2] < minEnd) {
12612
+ minEnd = trackEnds[i2];
12613
+ assigned = i2;
12247
12614
  }
12248
12615
  }
12249
12616
  if (assigned === -1) {
@@ -12299,6 +12666,89 @@ var CHORD_INFO_HTML2 = `
12299
12666
  </ul>
12300
12667
  </div>
12301
12668
  `;
12669
+ var VIBRATO_INFO_HTML = `
12670
+ <div class="dtm-modal-body-content">
12671
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12672
+ <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>
12673
+ <h4>\u77ED\u3044\u97F3\u7B26\u306B\u639B\u304B\u3089\u306A\u3044\u7406\u7531</h4>
12674
+ <p>1\u5468\u671F\u3082\u63FA\u308C\u304D\u3089\u306A\u3044\u3046\u3061\u306B\u6B21\u306E\u97F3\u3078\u79FB\u3063\u3066\u3057\u307E\u3044\u3001\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u3044\u3046\u3088\u308A\u5358\u306A\u308B\u97F3\u7A0B\u306E\u30D6\u30EC\u3068\u3057\u3066\u4E0D\u81EA\u7136\u306B\u805E\u3053\u3048\u308B\u305F\u3081\u3067\u3059\u3002</p>
12675
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12676
+ <p>\u30D4\u30C3\u30C1\u3092\u63FA\u3089\u3059\u52B9\u679C\u306A\u306E\u3067\u3001\u58F0\u8CEA\u305D\u306E\u3082\u306E\u3092\u5909\u3048\u308B\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306F\u72EC\u7ACB\u3057\u3066\u7D44\u307F\u5408\u308F\u305B\u3089\u308C\u307E\u3059\u3002\u901F\u3055\u30FB\u6DF1\u3055\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\uFF08\u6B4C\u3068\u3057\u3066\u7834\u7DBB\u3057\u306B\u304F\u3044\u63A7\u3048\u3081\u306A\u91CF\u306B\u56FA\u5B9A\uFF09\u3002\u66F2\u3084\u7B87\u6240\u3054\u3068\u306B\u639B\u3051\u305F\u3044/\u639B\u3051\u305F\u304F\u306A\u3044\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u6B4C\u8A5E\u3092\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002</p>
12677
+ </div>
12678
+ `;
12679
+ var GENDER_INFO_HTML = `
12680
+ <div class="dtm-modal-body-content">
12681
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12682
+ <p>\u30D4\u30C3\u30C1\uFF08\u97F3\u306E\u9AD8\u3055\uFF09\u306F\u305D\u306E\u307E\u307E\u306B\u3001\u58F0\u306E\u592A\u3055/\u7D30\u3055\uFF08\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8\uFF1D\u58F0\u9053\u306E\u5171\u9CF4\u3001\u5E74\u9F62\u30FB\u6027\u5225\u611F\u306E\u5370\u8C61\uFF09\u3060\u3051\u3092\u52D5\u304B\u3057\u307E\u3059\u300250\u304C\u7121\u5909\u5316\u300150\u672A\u6E80\u3067\u4F4E\u3081/\u592A\u3081\uFF08\u5927\u4EBA\u3073\u308B\uFF09\u300150\u8D85\u3067\u9AD8\u3081/\u7D30\u3081\uFF08\u82E5\u304F/\u660E\u308B\u304F\uFF09\u306B\u5BC4\u308A\u307E\u3059\u3002</p>
12683
+ <h4>\u30AA\u30AF\u30BF\u30FC\u30D6\u30B7\u30D5\u30C8\u3068\u306E\u9055\u3044</h4>
12684
+ <p>\u30AA\u30AF\u30BF\u30FC\u30D6\u306F\u97F3\u7A0B\u305D\u306E\u3082\u306E\u3092\u4E0A\u4E0B\u3055\u305B\u307E\u3059\u304C\u3001\u30B8\u30A7\u30F3\u30C0\u30FC\u306F\u97F3\u7A0B\u3092\u5909\u3048\u305A\u306B\u58F0\u8272\u3060\u3051\u3092\u52D5\u304B\u3057\u307E\u3059\u3002\u300C\u9AD8\u3044\u58F0\u306E\u307E\u307E\u5927\u4EBA\u3073\u3055\u305B\u308B\u300D\u300C\u4F4E\u3044\u58F0\u306E\u307E\u307E\u82E5\u3005\u3057\u304F\u3059\u308B\u300D\u3068\u3044\u3063\u305F\u3001\u97F3\u7A0B\u3068\u58F0\u8CEA\u3092\u5225\u3005\u306B\u8ABF\u6574\u3057\u305F\u3044\u3068\u304D\u306B\u4F7F\u3044\u307E\u3059\u3002</p>
12685
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12686
+ <p>\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u7D44\u307F\u5408\u308F\u305B\u3066\u58F0\u306E\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3092\u4F5C\u308A\u307E\u3059\uFF08\u4F8B: \u4F4E\u3081+\u606F\u591A\u3081\u3067\u6E0B\u3044/\u5927\u4EBA\u3063\u307D\u3044\u5370\u8C61\u3001\u9AD8\u3081+\u606F\u5C11\u306A\u3081\u3067\u5143\u6C17/\u82E5\u3005\u3057\u3044\u5370\u8C61\uFF09\u3002koe\u97F3\u6E90\uFF08UTAU\u7531\u6765\u306E.koe\u97F3\u6E90\uFF09\u9650\u5B9A\u306E\u52B9\u679C\u3067\u3001klatt\uFF08\u5185\u8535\u306E\u7C21\u6613\u5408\u6210\uFF09\u3067\u306F\u5909\u5316\u3057\u307E\u305B\u3093\u3002</p>
12687
+ </div>
12688
+ `;
12689
+ var BREATHINESS_INFO_HTML = `
12690
+ <div class="dtm-modal-body-content">
12691
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12692
+ <p>\u606F\u6210\u5206\u306E\u91CF\u3067\u3059\u300250\u304C\u7121\u5909\u5316\u3001\u5927\u304D\u3044\u307B\u3069\u606F\u3063\u307D\u304F\uFF08\u3055\u3055\u3084\u304D\u5BC4\u308A\uFF09\u3001\u5C0F\u3055\u3044\u307B\u3069\u82AF\u306E\u3042\u308B\u58F0\u306B\u306A\u308A\u307E\u3059\u3002</p>
12693
+ <h4>\u4E0A\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12694
+ <p>\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\u3067\u97F3\u7A0B\u611F\u304C\u8584\u308C\u3066\u805E\u3053\u3048\u307E\u3059\uFF08\u606F\u306E\u97F3\u304C\u30D4\u30C3\u30C1\u611F\u3092\u96A0\u3059\u305F\u3081\uFF09\u3002\u56C1\u304F\u3088\u3046\u306A\u30D0\u30E9\u30FC\u30C9\u8868\u73FE\u306B\u306F\u52B9\u679C\u7684\u3067\u3059\u304C\u3001\u4E0A\u3052\u3059\u304E\u308B\u3068\u30E1\u30ED\u30C7\u30A3\u304C\u4F1D\u308F\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002</p>
12695
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12696
+ <p>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u7D44\u307F\u5408\u308F\u305B\u308B\u3068\u3001\u63FA\u308C\u306A\u304C\u3089\u606F\u3063\u307D\u3044\u3001\u3088\u308A\u30A8\u30E2\u30FC\u30B7\u30E7\u30CA\u30EB\u306A\u8868\u73FE\u306B\u306A\u308A\u3084\u3059\u3044\u3067\u3059\u3002koe\u97F3\u6E90\uFF08UTAU\u7531\u6765\u306E.koe\u97F3\u6E90\uFF09\u9650\u5B9A\u306E\u52B9\u679C\u3067\u3001klatt\uFF08\u5185\u8535\u306E\u7C21\u6613\u5408\u6210\uFF09\u3067\u306F\u5909\u5316\u3057\u307E\u305B\u3093\u3002</p>
12697
+ </div>
12698
+ `;
12699
+ var LYRIC_REVERB_INFO_HTML = `
12700
+ <div class="dtm-modal-body-content">
12701
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12702
+ <p>\u3053\u306E\u30DC\u30FC\u30AB\u30EB\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u3001\u66F2\u5168\u4F53\u306B\u639B\u304B\u308B\u300C\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u300D\u3078\u3069\u308C\u3060\u3051\u97F3\u3092\u9001\u308B\u304B\u3092\u6C7A\u3081\u307E\u3059\uFF080\u3067\u9001\u3089\u306A\u3044\uFF1D\u30C9\u30E9\u30A4\u3001100\u3067\u76EE\u4E00\u676F\u9001\u308B\uFF09\u3002</p>
12703
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12704
+ <p>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u30D1\u30CD\u30EB\u306B\u3042\u308B\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u304C0%\u3060\u3068\u3001\u3053\u3053\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u300C\u9001\u308A\u91CF\uFF08\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304C\u3069\u308C\u3060\u3051\u63D0\u4F9B\u3059\u308B\u304B\uFF09\u300D\u3068\u300C\u30DE\u30B9\u30BF\u306E\u6B8B\u97FF\u8A2D\u5B9A\uFF08\u5B9F\u969B\u306B\u3069\u3093\u306A\u97FF\u304D\u304C\u639B\u304B\u308B\u304B\uFF09\u300D\u306E\u4E8C\u6BB5\u69CB\u3048\u306B\u306A\u3063\u3066\u3044\u308B\u305F\u3081\u3067\u3059\u3002\u4E21\u65B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12705
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12706
+ <p>\u6B8B\u97FF\u3067\u97F3\u304C\u6EF2\u307F\u3001\u6B4C\u8A5E\u304C\u805E\u304D\u53D6\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002\u30C9\u30E9\u30A4\u306A\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u8FD1\u3044\u300D\u300C\u524D\u306B\u51FA\u308B\u300D\u5370\u8C61\u3001\u30EA\u30D0\u30FC\u30D6\u305F\u3063\u3077\u308A\u306E\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u5965\u884C\u304D\u304C\u3042\u308B\u300D\u300C\u5E7B\u60F3\u7684\u300D\u306A\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u3002\u8868\u73FE\u3057\u305F\u3044\u8DDD\u96E2\u611F\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12707
+ </div>
12708
+ `;
12709
+ var MASTER_REVERB_INFO_HTML = `
12710
+ <div class="dtm-modal-body-content">
12711
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12712
+ <p>\u66F2\u5168\u4F53\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308B\u6B8B\u97FF\uFF08\u90E8\u5C4B\u9CF4\u308A\u30FB\u7A7A\u9593\u306E\u97FF\u304D\uFF09\u3067\u3059\u30020%\u3067\u5B8C\u5168\u306B\u30C9\u30E9\u30A4\uFF08\u97FF\u304D\u306A\u3057\uFF09\u3001\u4E0A\u3052\u308B\u307B\u3069\u5E83\u3044\u7A7A\u9593\u3067\u9CF4\u3063\u3066\u3044\u308B\u3088\u3046\u306A\u5965\u884C\u304D\u304C\u51FA\u307E\u3059\u3002</p>
12713
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12714
+ <p>\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u305D\u308C\u305E\u308C\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u3044\u3046\u500B\u5225\u306E\u3064\u307E\u307F\u304C\u3042\u308A\u3001\u305D\u3061\u3089\u304C0%\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3001\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002\u300C\u30DE\u30B9\u30BF\uFF1D\u6B8B\u97FF\u306E\u8CEA\u3068\u91CF\u305D\u306E\u3082\u306E\u300D\u300C\u30C8\u30E9\u30C3\u30AF\u9001\u308A\uFF1D\u305D\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u3069\u308C\u3060\u3051\u6DF7\u305C\u308B\u304B\u300D\u3068\u3044\u3046\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u73FE\u72B6\u30BB\u30F3\u30C9\u6A5F\u80FD\u304C\u7121\u3044\u305F\u3081\u3001\u5E38\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308A\u307E\u3059\u3002</p>
12715
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12716
+ <p>\u30DF\u30C3\u30AF\u30B9\u5168\u4F53\u306E\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3001\u9060\u304F\u30FB\u3053\u3082\u3063\u305F\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u300210\u301C30%\u7A0B\u5EA6\u3092\u51FA\u767A\u70B9\u306B\u3001\u66F2\u306E\u30B8\u30E3\u30F3\u30EB\u3084\u7A7A\u9593\u306E\u5E83\u3055\u306E\u30A4\u30E1\u30FC\u30B8\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3059\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
12717
+ </div>
12718
+ `;
12719
+ var TRACK_COMPRESSION_INFO_HTML = `
12720
+ <div class="dtm-modal-body-content">
12721
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12722
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306B\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u3092\u639B\u3051\u3001\u97F3\u91CF\u306E\u5927\u5C0F\u5DEE\u3092\u7E2E\u3081\u3066\u300C\u524D\u306B\u51FA\u308B\u300D\u300C\u805E\u3053\u3048\u3084\u3059\u3044\u300D\u97F3\u306B\u3057\u307E\u3059\u3002\u5E02\u8CA9\u66F2\u306E\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3084\u30DF\u30C3\u30AF\u30B9\u3067\u5B9A\u756A\u306E\u51E6\u7406\u3067\u3059\u30020\u3067\u7121\u5727\u7E2E\u3001100\u306B\u8FD1\u3065\u304F\u307B\u3069\u5F37\u304F\u5727\u7E2E\u3055\u308C\u307E\u3059\u3002</p>
12723
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12724
+ <p>\u5F37\u5F31\u306E\u8868\u60C5\uFF08\u30C0\u30A4\u30CA\u30DF\u30AF\u30B9\uFF09\u304C\u5931\u308F\u308C\u3066\u5358\u8ABF\u306B\u805E\u3053\u3048\u307E\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u3084\u30EA\u30FC\u30C9\u697D\u5668\u306F\u63A7\u3048\u3081\uFF0820\u301C40\u7A0B\u5EA6\uFF09\u3001\u30C9\u30E9\u30E0\u3084\u30D9\u30FC\u30B9\u306F\u3084\u3084\u5F37\u3081\u3001\u304C\u4E00\u822C\u7684\u306A\u76EE\u5B89\u3067\u3059\u3002</p>
12725
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12726
+ <p>\u30DE\u30B9\u30BF\u306E\u300C\u5B89\u5168\u30EA\u30DF\u30C3\u30BF\u30FC\u300D\uFF08\u5E38\u6642ON\u3001[reverb]\u30D1\u30CD\u30EB\u306E\u5916\u5074\u3067\u81EA\u52D5\u7684\u306B\u50CD\u304F\u4FDD\u967A\uFF09\u3068\u306F\u5225\u7269\u3067\u3059\u3002\u3042\u3061\u3089\u306F\u97F3\u5272\u308C\u3092\u7269\u7406\u7684\u306B\u9632\u3050\u305F\u3081\u306E\u6700\u7D42\u9632\u885B\u30E9\u30A4\u30F3\u3067\u3001\u5E38\u306B\u63A7\u3048\u3081\u306B\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u3053\u3061\u3089\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u306F\u97F3\u4F5C\u308A\uFF08\u8868\u73FE\uFF09\u306E\u305F\u3081\u306E\u51E6\u7406\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3054\u3068\u306B\u597D\u304D\u306A\u3060\u3051\u5F37\u304F/\u5F31\u304F\u639B\u3051\u3089\u308C\u307E\u3059\u3002\u300C\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u300D\u306F\u5168\u30C8\u30E9\u30C3\u30AF\u4E00\u5F8B35%\u3092\u5F53\u3066\u308B\u3060\u3051\u306A\u306E\u3067\u3001\u30DC\u30FC\u30AB\u30EB\u306A\u3069\u76EE\u7ACB\u305F\u305B\u305F\u3044\u30D1\u30FC\u30C8\u306F\u5F8C\u3067\u500B\u5225\u306B\u4E0B\u3052\u308B\u3068\u8F2A\u90ED\u304C\u51FA\u3084\u3059\u304F\u306A\u308A\u307E\u3059\u3002</p>
12727
+ </div>
12728
+ `;
12729
+ var TRACK_WIDTH_INFO_HTML = `
12730
+ <div class="dtm-modal-body-content">
12731
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12732
+ <p>\u5DE6\u53F3\u306E\u5E83\u304C\u308A\u3092\u8ABF\u6574\u3057\u307E\u3059\u3002100\u304C\u539F\u97F3\u306E\u307E\u307E\u30010\u3067\u5B8C\u5168\u30E2\u30CE\u30E9\u30EB\uFF08\u5DE6\u53F3\u304C\u540C\u3058\u97F3\uFF09\u3001100\u3092\u8D85\u3048\u308B\u3068\u5DE6\u53F3\u306E\u9055\u3044\u304C\u8A87\u5F35\u3055\u308C\u3066\u5E83\u304F\u805E\u3053\u3048\u307E\u3059\u3002</p>
12733
+ <h4>\u300C\u5B9A\u4F4D\uFF08\u30D1\u30F3\uFF09\u300D\u3068\u306E\u9055\u3044</h4>
12734
+ <p>\u5B9A\u4F4D\u306F\u300C\u97F3\u3092\u3069\u3053\u306B\u7F6E\u304F\u304B\u300D\uFF08\u5DE6\u5BC4\u308A/\u4E2D\u592E/\u53F3\u5BC4\u308A\uFF09\u3001\u30B9\u30C6\u30EC\u30AA\u5E45\u306F\u300C\u305D\u306E\u97F3\u81EA\u4F53\u304C\u3069\u308C\u3060\u3051\u5E83\u304C\u3063\u3066\u805E\u3053\u3048\u308B\u304B\u300D\u3067\u3001\u5F79\u5272\u304C\u7570\u306A\u308A\u307E\u3059\u3002\u4E21\u65B9\u3092\u5F37\u304F\u4F7F\u3046\u3068\u5B9A\u4F4D\u304C\u307C\u3084\u3051\u3066\u66D6\u6627\u306B\u306A\u308A\u304C\u3061\u3067\u3059\u3002</p>
12735
+ <h4>\u5E83\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12736
+ <p>\u30B9\u30DE\u30DB\u306E\u30B9\u30D4\u30FC\u30AB\u30FC1\u500B\u306A\u3069\u3001\u30E2\u30CE\u30E9\u30EB\u306B\u8FD1\u3044\u74B0\u5883\u3067\u518D\u751F\u3059\u308B\u3068\u97F3\u304C\u8584\u304F/\u4F4D\u76F8\u304C\u4E71\u308C\u3066\u805E\u3053\u3048\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\uFF08\u5DE6\u53F3\u306E\u5DEE\u5206\u3092\u8A87\u5F35\u3057\u3066\u3044\u308B\u305F\u3081\u3001\u8DB3\u3057\u5408\u308F\u305B\u308B\u3068\u6253\u3061\u6D88\u3057\u5408\u3046\u6210\u5206\u304C\u5897\u3048\u308B\uFF09\u3002\u4E3B\u65CB\u5F8B\u3084\u30DC\u30FC\u30AB\u30EB\u306F\u4E2D\u592E\u4ED8\u8FD1\u3067\u72ED\u3081\u3001\u30D1\u30C3\u30C9\u3084\u30B7\u30F3\u30BB\u306E\u88C5\u98FE\u30D1\u30FC\u30C8\u306F\u5E83\u3052\u308B\u3001\u3068\u3044\u3046\u306E\u304C\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3067\u3059\u3002</p>
12737
+ </div>
12738
+ `;
12739
+ var AUTO_MASTER_INFO_HTML = `
12740
+ <div class="dtm-modal-body-content">
12741
+ <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12742
+ <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002</p>
12743
+ <ul>
12744
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12745
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
12746
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12747
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u3001\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%</li>
12748
+ </ul>
12749
+ <p style="margin-top:4px;"><small>\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
12750
+ </div>
12751
+ `;
12302
12752
  var MIDI_INFO_HTML = `
12303
12753
  <div class="dtm-modal-body-content">
12304
12754
  <h4>1. MIDI\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -12502,7 +12952,7 @@ var LYRIC_MODEL_CATEGORIES = [
12502
12952
  },
12503
12953
  {
12504
12954
  label: "\u304A\u3093J",
12505
- models: ["roze", "shiyo", "rino"]
12955
+ models: ["roze", "shiyo", "rino", "rino121", "uc"]
12506
12956
  },
12507
12957
  {
12508
12958
  label: "\u4E00\u822C",
@@ -12570,6 +13020,8 @@ var mountDAW = (target, options = {}) => {
12570
13020
  });
12571
13021
  refs.masterVolume.value = String(options.masterVolume ?? 50);
12572
13022
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
13023
+ refs.reverbAmount.value = String(options.reverbAmount ?? 0);
13024
+ refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
12573
13025
  refs.drumVolume.value = String(options.drumVolume ?? 80);
12574
13026
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
12575
13027
  const renderConfig = {
@@ -12585,6 +13037,9 @@ var mountDAW = (target, options = {}) => {
12585
13037
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
12586
13038
  let masterVolume = options.masterVolume ?? 50;
12587
13039
  options.singingVoices?.setVolume(masterVolume / 100);
13040
+ let reverbAmount = options.reverbAmount ?? 0;
13041
+ let unsubscribeClip;
13042
+ options.onReverbChange?.(reverbAmount);
12588
13043
  let drumVolume = options.drumVolume ?? 80;
12589
13044
  let currentDrumPattern = refs.drumSelect.value;
12590
13045
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -12662,7 +13117,11 @@ var mountDAW = (target, options = {}) => {
12662
13117
  vocalVolume: t.vocalVolume,
12663
13118
  vocalGate: t.vocalGate,
12664
13119
  vocalPan: t.vocalPan,
12665
- vocalOctave: t.vocalOctave
13120
+ vocalOctave: t.vocalOctave,
13121
+ vocalVibrato: t.vocalVibrato,
13122
+ vocalReverb: t.vocalReverb,
13123
+ vocalGender: t.vocalGender,
13124
+ vocalBreathiness: t.vocalBreathiness
12666
13125
  };
12667
13126
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
12668
13127
  lyricsDebounceTimer = setTimeout(() => {
@@ -12722,25 +13181,35 @@ var mountDAW = (target, options = {}) => {
12722
13181
  vocalGate: 100,
12723
13182
  vocalPan: 64,
12724
13183
  vocalOctave: 0,
12725
- trackInstrument: ""
13184
+ vocalVibrato: false,
13185
+ vocalReverb: 0,
13186
+ vocalGender: 50,
13187
+ vocalBreathiness: 50,
13188
+ trackInstrument: "",
13189
+ trackCompression: 0,
13190
+ trackWidth: 100
12726
13191
  };
12727
13192
  });
12728
13193
  };
12729
13194
  const buildLyricsMap = () => {
12730
13195
  const map = /* @__PURE__ */ new Map();
12731
- trackStates.forEach((t, i) => {
13196
+ trackStates.forEach((t, i2) => {
12732
13197
  const model = t.lyricModel.trim();
12733
13198
  const text = t.lyrics.trim();
12734
13199
  if (!model || !text) return;
12735
13200
  const syllables = normalizeLyrics(text);
12736
13201
  if (syllables.length === 0) return;
12737
- map.set(i, {
12738
- trackId: i,
13202
+ map.set(i2, {
13203
+ trackId: i2,
12739
13204
  model: model.toLowerCase(),
12740
13205
  volume: t.vocalVolume,
12741
13206
  gate: t.vocalGate,
12742
13207
  pan: t.vocalPan,
12743
13208
  octave: t.vocalOctave,
13209
+ vibrato: t.vocalVibrato,
13210
+ reverb: t.vocalReverb,
13211
+ gender: t.vocalGender,
13212
+ breathiness: t.vocalBreathiness,
12744
13213
  syllables
12745
13214
  });
12746
13215
  });
@@ -12780,15 +13249,15 @@ var mountDAW = (target, options = {}) => {
12780
13249
  const ctx = getGridContext();
12781
13250
  const canvas = getGridCanvas();
12782
13251
  if (!ctx) return;
12783
- const x = playStartStep * renderConfig.stepWidth - currentOffsetX;
12784
- if (x < -10 || x > canvas.width + 10) return;
13252
+ const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
13253
+ if (x2 < -10 || x2 > canvas.width + 10) return;
12785
13254
  ctx.save();
12786
13255
  ctx.strokeStyle = "#ffec27";
12787
13256
  ctx.lineWidth = 2;
12788
13257
  ctx.setLineDash([4, 4]);
12789
13258
  ctx.beginPath();
12790
- ctx.moveTo(x, 0);
12791
- ctx.lineTo(x, canvas.height);
13259
+ ctx.moveTo(x2, 0);
13260
+ ctx.lineTo(x2, canvas.height);
12792
13261
  ctx.stroke();
12793
13262
  ctx.restore();
12794
13263
  };
@@ -12796,14 +13265,14 @@ var mountDAW = (target, options = {}) => {
12796
13265
  const ctx = getGridContext();
12797
13266
  const canvas = getGridCanvas();
12798
13267
  if (!ctx) return;
12799
- const x = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
12800
- if (x < 0 || x > canvas.width) return;
13268
+ const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
13269
+ if (x2 < 0 || x2 > canvas.width) return;
12801
13270
  ctx.save();
12802
13271
  ctx.strokeStyle = "#ff004d";
12803
13272
  ctx.lineWidth = 2;
12804
13273
  ctx.beginPath();
12805
- ctx.moveTo(x, 0);
12806
- ctx.lineTo(x, canvas.height);
13274
+ ctx.moveTo(x2, 0);
13275
+ ctx.lineTo(x2, canvas.height);
12807
13276
  ctx.stroke();
12808
13277
  ctx.restore();
12809
13278
  };
@@ -12931,8 +13400,8 @@ var mountDAW = (target, options = {}) => {
12931
13400
  if (maxOffsetX <= 0) return;
12932
13401
  const rect = refs.hScroll.getBoundingClientRect();
12933
13402
  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);
13403
+ const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13404
+ const ratio = x2 / (rect.width - thumbW);
12936
13405
  currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
12937
13406
  setDrawOffset(currentOffsetX, currentOffsetY);
12938
13407
  redrawAll();
@@ -12959,13 +13428,60 @@ var mountDAW = (target, options = {}) => {
12959
13428
  let selectionStart = null;
12960
13429
  let selectedOriginal = [];
12961
13430
  let lastMultiPreviewPitch = null;
13431
+ const AUTO_SCROLL_MARGIN = 30;
13432
+ const AUTO_SCROLL_MAX_SPEED = 22;
13433
+ let autoScrollRAF = null;
13434
+ let lastMoveEvent = null;
13435
+ const computeEdgeSpeed = (pos, size) => {
13436
+ if (pos < AUTO_SCROLL_MARGIN) {
13437
+ const t = (AUTO_SCROLL_MARGIN - pos) / AUTO_SCROLL_MARGIN;
13438
+ return -Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13439
+ }
13440
+ if (pos > size - AUTO_SCROLL_MARGIN) {
13441
+ const t = (pos - (size - AUTO_SCROLL_MARGIN)) / AUTO_SCROLL_MARGIN;
13442
+ return Math.ceil(t * AUTO_SCROLL_MAX_SPEED);
13443
+ }
13444
+ return 0;
13445
+ };
13446
+ const stopAutoScroll = () => {
13447
+ if (autoScrollRAF !== null) {
13448
+ cancelAnimationFrame(autoScrollRAF);
13449
+ autoScrollRAF = null;
13450
+ }
13451
+ lastMoveEvent = null;
13452
+ };
13453
+ const autoScrollTick = () => {
13454
+ autoScrollRAF = null;
13455
+ if (!isSelecting || !lastMoveEvent) return;
13456
+ const canvas = getGridCanvas();
13457
+ const { x: x2, y } = getGridPosition(lastMoveEvent);
13458
+ const dx = computeEdgeSpeed(x2, canvas.width);
13459
+ const dy = computeEdgeSpeed(y, canvas.height);
13460
+ if (dx !== 0 || dy !== 0) {
13461
+ const maxOffsetX = getMaxOffsetX();
13462
+ const maxOffsetY = getMaxOffsetY();
13463
+ currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13464
+ currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13465
+ setDrawOffset(currentOffsetX, currentOffsetY);
13466
+ onPointerMove(lastMoveEvent);
13467
+ }
13468
+ if (isSelecting) {
13469
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13470
+ }
13471
+ };
13472
+ const ensureAutoScroll = (event) => {
13473
+ lastMoveEvent = event;
13474
+ if (autoScrollRAF === null) {
13475
+ autoScrollRAF = requestAnimationFrame(autoScrollTick);
13476
+ }
13477
+ };
12962
13478
  const playPreview = (pitch) => {
12963
13479
  if (isLoading) return;
12964
13480
  options.onResumeAudio?.();
12965
13481
  const active = getActive();
12966
13482
  dispatchNote(active.config.id, pitch, active.volume, 100, 0, 0.5);
12967
13483
  };
12968
- const findActiveNoteAt = (x, y, margin = 0) => {
13484
+ const findActiveNoteAt = (x2, y, margin = 0) => {
12969
13485
  const active = getActive();
12970
13486
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = renderConfig;
12971
13487
  const offset = getDrawOffset();
@@ -12976,7 +13492,7 @@ var mountDAW = (target, options = {}) => {
12976
13492
  const w = note.durationSteps * stepWidth;
12977
13493
  const renderX = logicalX - offset.x;
12978
13494
  const renderY = logicalY - offset.y;
12979
- if (x >= renderX - margin && x <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
13495
+ if (x2 >= renderX - margin && x2 <= renderX + w + margin && y >= renderY - margin && y <= renderY + keyHeight + margin)
12980
13496
  return note;
12981
13497
  }
12982
13498
  return null;
@@ -12995,17 +13511,17 @@ var mountDAW = (target, options = {}) => {
12995
13511
  const onGridPointerDown = (event) => {
12996
13512
  event.preventDefault();
12997
13513
  options.onResumeAudio?.();
12998
- const { x, y, step, pitch } = getGridPosition(event);
13514
+ const { x: x2, y, step, pitch } = getGridPosition(event);
12999
13515
  const active = getActive();
13000
13516
  if (activeToolMode === "eraser") {
13001
13517
  if (isActiveLocked()) return;
13002
- const note = findActiveNoteAt(x, y);
13518
+ const note = findActiveNoteAt(x2, y);
13003
13519
  if (note) active.core.deleteNoteById(note.id);
13004
13520
  return;
13005
13521
  }
13006
13522
  if (activeToolMode === "select") {
13007
13523
  if (selectedNotes.length > 0) {
13008
- const clicked2 = findActiveNoteAt(x, y);
13524
+ const clicked2 = findActiveNoteAt(x2, y);
13009
13525
  if (clicked2 && selectedNotes.some((n) => n.id === clicked2.id)) {
13010
13526
  selectedOriginal = selectedNotes.map((n) => ({
13011
13527
  id: n.id,
@@ -13014,7 +13530,7 @@ var mountDAW = (target, options = {}) => {
13014
13530
  }));
13015
13531
  isSelecting = true;
13016
13532
  dragMode = "move";
13017
- selectionStart = { x, y, step, pitch };
13533
+ selectionStart = { x: x2, y, step, pitch };
13018
13534
  hasDragged = false;
13019
13535
  lastMultiPreviewPitch = null;
13020
13536
  return;
@@ -13022,7 +13538,7 @@ var mountDAW = (target, options = {}) => {
13022
13538
  selectedNotes = [];
13023
13539
  selectionRect = null;
13024
13540
  }
13025
- const clicked = findActiveNoteAt(x, y);
13541
+ const clicked = findActiveNoteAt(x2, y);
13026
13542
  if (clicked) {
13027
13543
  selectedNotes = [clicked];
13028
13544
  selectedOriginal = [
@@ -13040,19 +13556,19 @@ var mountDAW = (target, options = {}) => {
13040
13556
  isSelecting = true;
13041
13557
  dragMode = "rect";
13042
13558
  }
13043
- selectionStart = { x, y, step, pitch };
13559
+ selectionStart = { x: x2, y, step, pitch };
13044
13560
  hasDragged = false;
13045
13561
  return;
13046
13562
  }
13047
13563
  hasDragged = false;
13048
- const existing = findActiveNoteAt(x, y, TOUCH_HIT_MARGIN);
13564
+ const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
13049
13565
  if (existing) {
13050
13566
  playPreview(existing.pitch);
13051
13567
  const { stepWidth } = renderConfig;
13052
13568
  const offset = getDrawOffset();
13053
13569
  const renderX = existing.startStep * stepWidth - offset.x;
13054
13570
  const w = existing.durationSteps * stepWidth;
13055
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w) {
13571
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
13056
13572
  dragState = {
13057
13573
  noteId: existing.id,
13058
13574
  mode: "resize",
@@ -13131,12 +13647,13 @@ var mountDAW = (target, options = {}) => {
13131
13647
  return;
13132
13648
  }
13133
13649
  if (activeToolMode === "select" && isSelecting && selectionStart) {
13134
- const { x, y, step, pitch } = getGridPosition(event);
13650
+ ensureAutoScroll(event);
13651
+ const { x: x2, y, step, pitch } = getGridPosition(event);
13135
13652
  if (dragMode === "rect") {
13136
13653
  const rect = {
13137
- x: Math.min(x, selectionStart.x),
13654
+ x: Math.min(x2, selectionStart.x),
13138
13655
  y: Math.min(y, selectionStart.y),
13139
- width: Math.abs(x - selectionStart.x),
13656
+ width: Math.abs(x2 - selectionStart.x),
13140
13657
  height: Math.abs(y - selectionStart.y)
13141
13658
  };
13142
13659
  selectionRect = rect;
@@ -13149,7 +13666,7 @@ var mountDAW = (target, options = {}) => {
13149
13666
  const nx = logicalX - offset.x;
13150
13667
  const ny = logicalY - offset.y;
13151
13668
  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;
13669
+ return nx >= rect.x && nx + nw <= rect.x + rect.width && ny >= rect.y && ny + keyHeight <= rect.y + rect.height;
13153
13670
  });
13154
13671
  redrawAll();
13155
13672
  } else {
@@ -13206,8 +13723,11 @@ var mountDAW = (target, options = {}) => {
13206
13723
  selectionStart = null;
13207
13724
  hasDragged = false;
13208
13725
  lastMultiPreviewPitch = null;
13209
- selectionRect = null;
13726
+ if (dragMode !== "rect" || selectedNotes.length === 0) {
13727
+ selectionRect = null;
13728
+ }
13210
13729
  selectedOriginal = [];
13730
+ stopAutoScroll();
13211
13731
  redrawAll();
13212
13732
  }
13213
13733
  };
@@ -13355,8 +13875,8 @@ var mountDAW = (target, options = {}) => {
13355
13875
  headerCanvas.addEventListener("click", (event) => {
13356
13876
  if (playbackState === "playing") return;
13357
13877
  const rect = headerCanvas.getBoundingClientRect();
13358
- const x = event.clientX - rect.left;
13359
- const step = Math.floor((x + currentOffsetX) / renderConfig.stepWidth);
13878
+ const x2 = event.clientX - rect.left;
13879
+ const step = Math.floor((x2 + currentOffsetX) / renderConfig.stepWidth);
13360
13880
  playStartStep = Math.max(
13361
13881
  0,
13362
13882
  Math.floor(step / snapGridSteps) * snapGridSteps
@@ -13477,11 +13997,11 @@ var mountDAW = (target, options = {}) => {
13477
13997
  const semis = (lt.octave ?? 0) * 12;
13478
13998
  const count = Math.min(sorted.length, lt.syllables.length);
13479
13999
  const notes = [];
13480
- for (let i = 0; i < count; i++) {
13481
- const n = sorted[i];
14000
+ for (let i2 = 0; i2 < count; i2++) {
14001
+ const n = sorted[i2];
13482
14002
  if (n.startStep < fromStep) continue;
13483
14003
  notes.push({
13484
- syllable: lt.syllables[i],
14004
+ syllable: lt.syllables[i2],
13485
14005
  pitch: n.pitch + semis,
13486
14006
  startSec: (n.startStep - fromStep) * secondsPerStep,
13487
14007
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -13492,6 +14012,10 @@ var mountDAW = (target, options = {}) => {
13492
14012
  model: lt.model,
13493
14013
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
13494
14014
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
14015
+ vibrato: lt.vibrato,
14016
+ reverbSend: (lt.reverb ?? 0) / 100,
14017
+ gender: (lt.gender ?? 50) / 100,
14018
+ breathiness: (lt.breathiness ?? 50) / 100,
13495
14019
  notes
13496
14020
  };
13497
14021
  }) : [];
@@ -13580,13 +14104,13 @@ var mountDAW = (target, options = {}) => {
13580
14104
  const updateTrackPanel = () => {
13581
14105
  refs.trackTabs.innerHTML = "";
13582
14106
  trackPillEls.clear();
13583
- for (const [i, t] of trackStates.entries()) {
14107
+ for (const [i2, t] of trackStates.entries()) {
13584
14108
  const [r, g, b] = t.config.color;
13585
14109
  const btn = document.createElement("button");
13586
14110
  btn.className = `dtm-pill ${t.config.id === activeTrackId ? "dtm-pill--active" : ""}`;
13587
14111
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
13588
14112
  btn.title = t.config.name;
13589
- btn.textContent = String(i + 1);
14113
+ btn.textContent = String(i2 + 1);
13590
14114
  btn.addEventListener("click", () => switchTrack(t.config.id));
13591
14115
  refs.trackTabs.appendChild(btn);
13592
14116
  trackPillEls.set(t.config.id, btn);
@@ -13597,7 +14121,22 @@ var mountDAW = (target, options = {}) => {
13597
14121
  <span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
13598
14122
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
13599
14123
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
13600
- </div>`;
14124
+ </div>
14125
+ <details class="dtm-advanced" data-dtm="track-fx-advanced">
14126
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14127
+ <div class="dtm-row">
14128
+ <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14129
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-comp" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u91CF\uFF08\u97F3\u5727\u5F37\u5316\uFF09">
14130
+ <span class="dtm-label" data-dtm="track-comp-label"></span>
14131
+ <button class="dtm-infobtn" data-dtm="track-comp-info" title="\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14132
+ </div>
14133
+ <div class="dtm-row">
14134
+ <span class="dtm-label">\u30B9\u30C6\u30EC\u30AA\u5E45</span>
14135
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-width" min="0" max="200" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45\uFF08100=\u539F\u97F3\u30010=\u30E2\u30CE\u30E9\u30EB\uFF09">
14136
+ <span class="dtm-label" data-dtm="track-width-label"></span>
14137
+ <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14138
+ </div>
14139
+ </details>`;
13601
14140
  const volInput = refs.trackBody.querySelector(
13602
14141
  '[data-dtm="track-vol"]'
13603
14142
  );
@@ -13614,6 +14153,47 @@ var mountDAW = (target, options = {}) => {
13614
14153
  volInput.title = active.lyricModel ? "\u6B4C\u8A5E\u30E2\u30FC\u30C9\u306E\u3068\u304D\u306F\u30D9\u30ED\u30B7\u30C6\u30A3\u304C\u518D\u751F\u306B\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF08\u58F0\u91CF\u3067\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
13615
14154
  };
13616
14155
  syncVelocityDisabled();
14156
+ const trackCompInput = refs.trackBody.querySelector(
14157
+ '[data-dtm="track-comp"]'
14158
+ );
14159
+ const trackCompLabel = refs.trackBody.querySelector(
14160
+ '[data-dtm="track-comp-label"]'
14161
+ );
14162
+ const trackWidthInput = refs.trackBody.querySelector(
14163
+ '[data-dtm="track-width"]'
14164
+ );
14165
+ const trackWidthLabel = refs.trackBody.querySelector(
14166
+ '[data-dtm="track-width-label"]'
14167
+ );
14168
+ const trackCompInfo = refs.trackBody.querySelector(
14169
+ '[data-dtm="track-comp-info"]'
14170
+ );
14171
+ const trackWidthInfo = refs.trackBody.querySelector(
14172
+ '[data-dtm="track-width-info"]'
14173
+ );
14174
+ trackCompInput.value = String(active.trackCompression);
14175
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14176
+ trackWidthInput.value = String(active.trackWidth);
14177
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14178
+ trackCompInput.addEventListener("input", () => {
14179
+ active.trackCompression = Number.parseInt(trackCompInput.value, 10);
14180
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14181
+ options.onTrackCompressionChange?.(
14182
+ active.config.id,
14183
+ active.trackCompression
14184
+ );
14185
+ });
14186
+ trackWidthInput.addEventListener("input", () => {
14187
+ active.trackWidth = Number.parseInt(trackWidthInput.value, 10);
14188
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14189
+ options.onTrackWidthChange?.(active.config.id, active.trackWidth);
14190
+ });
14191
+ trackCompInfo.addEventListener("click", () => {
14192
+ showModal("\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC", TRACK_COMPRESSION_INFO_HTML);
14193
+ });
14194
+ trackWidthInfo.addEventListener("click", () => {
14195
+ showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14196
+ });
13617
14197
  const instRow = document.createElement("div");
13618
14198
  instRow.className = "dtm-row";
13619
14199
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -13677,13 +14257,6 @@ var mountDAW = (target, options = {}) => {
13677
14257
  <span class="dtm-label">\u266A UTAU</span>
13678
14258
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
13679
14259
  <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
13680
- <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
13681
- <option value="2">+2 oct</option>
13682
- <option value="1">+1 oct</option>
13683
- <option value="0">\xB10 oct</option>
13684
- <option value="-1">-1 oct</option>
13685
- <option value="-2">-2 oct</option>
13686
- </select>
13687
14260
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
13688
14261
  </div>
13689
14262
  <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
@@ -13709,11 +14282,48 @@ var mountDAW = (target, options = {}) => {
13709
14282
  <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A200\uFF09">
13710
14283
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
13711
14284
  </div>
13712
- <div class="dtm-row">
13713
- <span class="dtm-label">\u5B9A\u4F4D</span>
13714
- <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
- <span class="dtm-label" data-dtm="lyric-pan-label"></span>
13716
- </div>
14285
+ <details class="dtm-advanced" data-dtm="lyric-advanced">
14286
+ <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14287
+ <div class="dtm-row">
14288
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
14289
+ <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
14290
+ <option value="2">+2 oct</option>
14291
+ <option value="1">+1 oct</option>
14292
+ <option value="0">\xB10 oct</option>
14293
+ <option value="-1">-1 oct</option>
14294
+ <option value="-2">-2 oct</option>
14295
+ </select>
14296
+ </div>
14297
+ <div class="dtm-row">
14298
+ <span class="dtm-label">\u5B9A\u4F4D</span>
14299
+ <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">
14300
+ <span class="dtm-label" data-dtm="lyric-pan-label"></span>
14301
+ </div>
14302
+ <div class="dtm-row">
14303
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14304
+ <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">
14305
+ <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14306
+ <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14307
+ </div>
14308
+ <div class="dtm-row">
14309
+ <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14310
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-gender" min="0" max="100" aria-label="\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC\u30D5\u30A1\u30AF\u30BF\u30FC\uFF08koe\u97F3\u6E90\u9650\u5B9A\uFF09">
14311
+ <span class="dtm-label" data-dtm="lyric-gender-label"></span>
14312
+ <button class="dtm-infobtn" data-dtm="lyric-gender-info" title="\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14313
+ </div>
14314
+ <div class="dtm-row">
14315
+ <span class="dtm-label">\u30D6\u30EC\u30B7\u30CD\u30B9</span>
14316
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-breathiness" min="0" max="100" aria-label="\u30D6\u30EC\u30B7\u30CD\u30B9\uFF08\u606F\u6210\u5206\u3001koe\u97F3\u6E90\u9650\u5B9A\uFF09">
14317
+ <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
14318
+ <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14319
+ </div>
14320
+ <div class="dtm-row">
14321
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14322
+ <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
14323
+ </span>
14324
+ <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14325
+ </div>
14326
+ </details>
13717
14327
  <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
14328
  </div>`;
13719
14329
  refs.trackBody.appendChild(lyricDiv);
@@ -13747,6 +14357,51 @@ var mountDAW = (target, options = {}) => {
13747
14357
  const lyricPanLabel = lyricDiv.querySelector(
13748
14358
  '[data-dtm="lyric-pan-label"]'
13749
14359
  );
14360
+ const lyricReverb = lyricDiv.querySelector(
14361
+ '[data-dtm="lyric-reverb"]'
14362
+ );
14363
+ const lyricReverbLabel = lyricDiv.querySelector(
14364
+ '[data-dtm="lyric-reverb-label"]'
14365
+ );
14366
+ const lyricReverbInfo = lyricDiv.querySelector(
14367
+ '[data-dtm="lyric-reverb-info"]'
14368
+ );
14369
+ lyricReverbInfo.addEventListener("click", () => {
14370
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14371
+ });
14372
+ const lyricGender = lyricDiv.querySelector(
14373
+ '[data-dtm="lyric-gender"]'
14374
+ );
14375
+ const lyricGenderLabel = lyricDiv.querySelector(
14376
+ '[data-dtm="lyric-gender-label"]'
14377
+ );
14378
+ const lyricGenderInfo = lyricDiv.querySelector(
14379
+ '[data-dtm="lyric-gender-info"]'
14380
+ );
14381
+ lyricGenderInfo.addEventListener("click", () => {
14382
+ showModal("\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC", GENDER_INFO_HTML);
14383
+ });
14384
+ const lyricBreathiness = lyricDiv.querySelector(
14385
+ '[data-dtm="lyric-breathiness"]'
14386
+ );
14387
+ const lyricBreathinessLabel = lyricDiv.querySelector(
14388
+ '[data-dtm="lyric-breathiness-label"]'
14389
+ );
14390
+ const lyricBreathinessInfo = lyricDiv.querySelector(
14391
+ '[data-dtm="lyric-breathiness-info"]'
14392
+ );
14393
+ lyricBreathinessInfo.addEventListener("click", () => {
14394
+ showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
14395
+ });
14396
+ const lyricVibrato = lyricDiv.querySelector(
14397
+ '[data-dtm="lyric-vibrato"]'
14398
+ );
14399
+ const lyricVibratoInfo = lyricDiv.querySelector(
14400
+ '[data-dtm="lyric-vibrato-info"]'
14401
+ );
14402
+ lyricVibratoInfo.addEventListener("click", () => {
14403
+ showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14404
+ });
13750
14405
  const lyricTerms = lyricDiv.querySelector(
13751
14406
  '[data-dtm="lyric-terms"]'
13752
14407
  );
@@ -13808,6 +14463,13 @@ var mountDAW = (target, options = {}) => {
13808
14463
  lyricVolLabel.textContent = String(active.vocalVolume);
13809
14464
  lyricPan.value = String(active.vocalPan);
13810
14465
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14466
+ lyricReverb.value = String(active.vocalReverb);
14467
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14468
+ lyricGender.value = String(active.vocalGender);
14469
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14470
+ lyricBreathiness.value = String(active.vocalBreathiness);
14471
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14472
+ lyricVibrato.checked = active.vocalVibrato;
13811
14473
  const updateLyricCount = () => {
13812
14474
  const n = normalizeLyrics(lyricInput.value).length;
13813
14475
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -13857,7 +14519,6 @@ var mountDAW = (target, options = {}) => {
13857
14519
  };
13858
14520
  const syncLyricVisibility = () => {
13859
14521
  lyricBody.style.display = active.lyricModel ? "" : "none";
13860
- lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
13861
14522
  updateLyricCount();
13862
14523
  syncLyricTerms();
13863
14524
  syncLyricIcon();
@@ -13926,6 +14587,10 @@ var mountDAW = (target, options = {}) => {
13926
14587
  active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
13927
14588
  fireLyricsChange(active);
13928
14589
  });
14590
+ lyricVibrato.addEventListener("change", () => {
14591
+ active.vocalVibrato = lyricVibrato.checked;
14592
+ fireLyricsChange(active);
14593
+ });
13929
14594
  lyricInput.addEventListener("input", () => {
13930
14595
  active.lyrics = lyricInput.value;
13931
14596
  updateLyricCount();
@@ -13949,6 +14614,21 @@ var mountDAW = (target, options = {}) => {
13949
14614
  lyricPanLabel.textContent = fmtPan(64);
13950
14615
  fireLyricsChange(active);
13951
14616
  });
14617
+ lyricReverb.addEventListener("input", () => {
14618
+ active.vocalReverb = Number.parseInt(lyricReverb.value, 10);
14619
+ lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14620
+ fireLyricsChange(active);
14621
+ });
14622
+ lyricGender.addEventListener("input", () => {
14623
+ active.vocalGender = Number.parseInt(lyricGender.value, 10);
14624
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14625
+ fireLyricsChange(active);
14626
+ });
14627
+ lyricBreathiness.addEventListener("input", () => {
14628
+ active.vocalBreathiness = Number.parseInt(lyricBreathiness.value, 10);
14629
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14630
+ fireLyricsChange(active);
14631
+ });
13952
14632
  }
13953
14633
  if (active.config.id === "chord" && showChord) {
13954
14634
  const div = document.createElement("div");
@@ -14032,18 +14712,28 @@ var mountDAW = (target, options = {}) => {
14032
14712
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
14033
14713
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
14034
14714
  const trackInstrumentsForMeta = {};
14035
- trackStates.forEach((t, i) => {
14036
- if (t.trackInstrument) trackInstrumentsForMeta[i] = t.trackInstrument;
14715
+ const trackCompressionForMeta = {};
14716
+ const trackWidthForMeta = {};
14717
+ trackStates.forEach((t, i2) => {
14718
+ if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14719
+ if (t.trackCompression !== 0)
14720
+ trackCompressionForMeta[i2] = t.trackCompression;
14721
+ if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
14037
14722
  });
14038
14723
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14724
+ const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14725
+ const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
14039
14726
  const metaLineFull = formatMmlMeta(
14040
14727
  {
14041
14728
  instrument: currentInstrument || void 0,
14042
14729
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14043
14730
  volume: masterVolume,
14044
14731
  drumVolume,
14732
+ reverb: reverbAmount,
14045
14733
  mode,
14046
- trackInstruments: trackInstMeta
14734
+ trackInstruments: trackInstMeta,
14735
+ trackCompression: trackCompMeta,
14736
+ trackWidth: trackWidthMeta
14047
14737
  },
14048
14738
  " "
14049
14739
  );
@@ -14053,8 +14743,11 @@ var mountDAW = (target, options = {}) => {
14053
14743
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14054
14744
  volume: masterVolume,
14055
14745
  drumVolume,
14746
+ reverb: reverbAmount,
14056
14747
  mode,
14057
- trackInstruments: trackInstMeta
14748
+ trackInstruments: trackInstMeta,
14749
+ trackCompression: trackCompMeta,
14750
+ trackWidth: trackWidthMeta
14058
14751
  },
14059
14752
  ""
14060
14753
  );
@@ -14068,10 +14761,10 @@ var mountDAW = (target, options = {}) => {
14068
14761
  const monoTracks = decomposeToMonophonic(allNotes);
14069
14762
  const refCore = trackStates[0].core;
14070
14763
  const decomposedFull = monoTracks.map(
14071
- (notes, i) => `@${i} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14764
+ (notes, i2) => `@${i2} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
14072
14765
  );
14073
14766
  const decomposedMini = monoTracks.map(
14074
- (notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14767
+ (notes, i2) => `@${i2}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
14075
14768
  );
14076
14769
  const full2 = [metaLineFull, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
14077
14770
  const minified2 = [metaLineMini, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
@@ -14085,34 +14778,42 @@ var mountDAW = (target, options = {}) => {
14085
14778
  }
14086
14779
  const trackLines = [];
14087
14780
  const trackLinesMini = [];
14088
- trackStates.forEach((t, i) => {
14781
+ trackStates.forEach((t, i2) => {
14089
14782
  const notes = clipNotes(t.core.getNotes());
14090
14783
  if (notes.length > 0) {
14091
14784
  const mml = t.core.getMMLFromNotes(notes, bpm, t.volume).trim();
14092
- trackLines.push(`@${i} ${mml}`);
14093
- trackLinesMini.push(`@${i}${mml.replace(/\s+/g, "")}`);
14785
+ trackLines.push(`@${i2} ${mml}`);
14786
+ trackLinesMini.push(`@${i2}${mml.replace(/\s+/g, "")}`);
14094
14787
  }
14095
14788
  });
14096
- const lyricLines = trackStates.map((t, i) => ({
14097
- i,
14789
+ const lyricLines = trackStates.map((t, i2) => ({
14790
+ i: i2,
14098
14791
  notes: clipNotes(t.core.getNotes()),
14099
14792
  text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
14100
14793
  model: t.lyricModel.trim(),
14101
14794
  vol: t.vocalVolume,
14102
14795
  gate: t.vocalGate,
14103
14796
  pan: t.vocalPan,
14104
- oct: t.vocalOctave
14797
+ oct: t.vocalOctave,
14798
+ vib: t.vocalVibrato,
14799
+ rev: t.vocalReverb,
14800
+ gen: t.vocalGender,
14801
+ bre: t.vocalBreathiness
14105
14802
  })).filter(
14106
- (x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
14107
- ).map((x) => {
14803
+ (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14804
+ ).map((x2) => {
14108
14805
  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}`
14806
+ x2.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x2.vol}`,
14807
+ x2.gate === 100 ? "" : `q${x2.gate}`,
14808
+ x2.pan === 64 ? "" : `p${x2.pan}`,
14809
+ x2.oct === 0 ? "" : `o${x2.oct}`,
14810
+ x2.vib ? "b1" : "",
14811
+ x2.rev === 0 ? "" : `r${x2.rev}`,
14812
+ x2.gen === 50 ? "" : `g${x2.gen}`,
14813
+ x2.bre === 50 ? "" : `h${x2.bre}`
14113
14814
  ].filter((s) => s.length > 0).join(" ");
14114
- const head = params ? `${x.model} ${params}` : x.model;
14115
- return `@@${x.i} ${head} ${x.text}`;
14815
+ const head = params ? `${x2.model} ${params}` : x2.model;
14816
+ return `@@${x2.i} ${head} ${x2.text}`;
14116
14817
  });
14117
14818
  const customVocalDecls = [];
14118
14819
  for (const [key, def] of customVocalsMap) {
@@ -14249,18 +14950,37 @@ var mountDAW = (target, options = {}) => {
14249
14950
  refs.drumVolume.value = String(meta.drumVolume);
14250
14951
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
14251
14952
  }
14953
+ if (meta.reverb !== void 0) {
14954
+ reverbAmount = meta.reverb;
14955
+ refs.reverbAmount.value = String(meta.reverb);
14956
+ refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
14957
+ options.onReverbChange?.(meta.reverb);
14958
+ }
14252
14959
  }
14253
- trackStates.forEach((t, i) => {
14254
- if (applyActiveOnly && i !== activeTrackIndex) return;
14255
- const name = normalizeInstrumentName(meta.trackInstruments?.[i] ?? "");
14960
+ trackStates.forEach((t, i2) => {
14961
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14962
+ const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
14256
14963
  if (t.trackInstrument !== name) {
14257
14964
  t.trackInstrument = name;
14258
- options.onTrackInstrumentChange?.(i, name);
14965
+ options.onTrackInstrumentChange?.(i2, name);
14966
+ }
14967
+ });
14968
+ trackStates.forEach((t, i2) => {
14969
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14970
+ const comp = meta.trackCompression?.[i2] ?? 0;
14971
+ if (t.trackCompression !== comp) {
14972
+ t.trackCompression = comp;
14973
+ options.onTrackCompressionChange?.(t.config.id, comp);
14974
+ }
14975
+ const width = meta.trackWidth?.[i2] ?? 100;
14976
+ if (t.trackWidth !== width) {
14977
+ t.trackWidth = width;
14978
+ options.onTrackWidthChange?.(t.config.id, width);
14259
14979
  }
14260
14980
  });
14261
- trackStates.forEach((t, i) => {
14262
- if (applyActiveOnly && i !== activeTrackIndex) return;
14263
- const v = trackVelocity.get(i);
14981
+ trackStates.forEach((t, i2) => {
14982
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14983
+ const v = trackVelocity.get(i2);
14264
14984
  if (v !== void 0 && v !== t.volume) {
14265
14985
  t.volume = v;
14266
14986
  t.core.setVolume(v);
@@ -14274,6 +14994,10 @@ var mountDAW = (target, options = {}) => {
14274
14994
  active.vocalGate = 100;
14275
14995
  active.vocalPan = 64;
14276
14996
  active.vocalOctave = 0;
14997
+ active.vocalVibrato = false;
14998
+ active.vocalReverb = 0;
14999
+ active.vocalGender = 50;
15000
+ active.vocalBreathiness = 50;
14277
15001
  } else {
14278
15002
  for (const t of trackStates) {
14279
15003
  t.lyrics = "";
@@ -14282,6 +15006,10 @@ var mountDAW = (target, options = {}) => {
14282
15006
  t.vocalGate = 100;
14283
15007
  t.vocalPan = 64;
14284
15008
  t.vocalOctave = 0;
15009
+ t.vocalVibrato = false;
15010
+ t.vocalReverb = 0;
15011
+ t.vocalGender = 50;
15012
+ t.vocalBreathiness = 50;
14285
15013
  }
14286
15014
  }
14287
15015
  lyrics?.forEach((lt) => {
@@ -14294,6 +15022,10 @@ var mountDAW = (target, options = {}) => {
14294
15022
  t.vocalGate = lt.gate;
14295
15023
  t.vocalPan = lt.pan;
14296
15024
  t.vocalOctave = lt.octave ?? 0;
15025
+ t.vocalVibrato = lt.vibrato ?? false;
15026
+ t.vocalReverb = lt.reverb ?? 0;
15027
+ t.vocalGender = lt.gender ?? 50;
15028
+ t.vocalBreathiness = lt.breathiness ?? 50;
14297
15029
  });
14298
15030
  for (const p of placements) {
14299
15031
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -14372,6 +15104,10 @@ var mountDAW = (target, options = {}) => {
14372
15104
  active.vocalGate = 100;
14373
15105
  active.vocalPan = 64;
14374
15106
  active.vocalOctave = 0;
15107
+ active.vocalVibrato = false;
15108
+ active.vocalReverb = 0;
15109
+ active.vocalGender = 50;
15110
+ active.vocalBreathiness = 50;
14375
15111
  } else {
14376
15112
  clearAll();
14377
15113
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -14382,6 +15118,10 @@ var mountDAW = (target, options = {}) => {
14382
15118
  t.vocalGate = 100;
14383
15119
  t.vocalPan = 64;
14384
15120
  t.vocalOctave = 0;
15121
+ t.vocalVibrato = false;
15122
+ t.vocalReverb = 0;
15123
+ t.vocalGender = 50;
15124
+ t.vocalBreathiness = 50;
14385
15125
  }
14386
15126
  }
14387
15127
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -14516,6 +15256,12 @@ var mountDAW = (target, options = {}) => {
14516
15256
  isSolo = refs.soloCheckbox.checked;
14517
15257
  redrawAll();
14518
15258
  });
15259
+ unsubscribeClip = options.clipMeter?.onClipChange((clipping) => {
15260
+ refs.clipBadge.classList.toggle("dtm-hidden", !clipping);
15261
+ });
15262
+ refs.clipBadge.addEventListener("click", () => {
15263
+ options.clipMeter?.reset();
15264
+ });
14519
15265
  refs.toolPen.addEventListener("click", () => setToolMode("pen"));
14520
15266
  refs.toolSelect.addEventListener("click", () => setToolMode("select"));
14521
15267
  refs.toolEraser.addEventListener("click", () => setToolMode("eraser"));
@@ -14591,6 +15337,35 @@ var mountDAW = (target, options = {}) => {
14591
15337
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
14592
15338
  options.singingVoices?.setVolume(masterVolume / 100);
14593
15339
  });
15340
+ refs.reverbAmount.addEventListener("input", () => {
15341
+ reverbAmount = Number.parseInt(refs.reverbAmount.value, 10) || 0;
15342
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
15343
+ options.onReverbChange?.(reverbAmount);
15344
+ });
15345
+ refs.reverbAmountInfoBtn.addEventListener("click", () => {
15346
+ showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15347
+ });
15348
+ refs.autoMasterInfoBtn.addEventListener("click", () => {
15349
+ showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15350
+ });
15351
+ refs.autoMasterBtn.addEventListener("click", () => {
15352
+ reverbAmount = 20;
15353
+ refs.reverbAmount.value = "20";
15354
+ refs.reverbAmountLabel.textContent = "20%";
15355
+ options.onReverbChange?.(20);
15356
+ for (const t of trackStates) {
15357
+ t.trackCompression = 35;
15358
+ t.trackWidth = 115;
15359
+ options.onTrackCompressionChange?.(t.config.id, 35);
15360
+ options.onTrackWidthChange?.(t.config.id, 115);
15361
+ if (t.lyricModel) {
15362
+ t.vocalVibrato = true;
15363
+ t.vocalReverb = 25;
15364
+ fireLyricsChange(t);
15365
+ }
15366
+ }
15367
+ updateTrackPanel();
15368
+ });
14594
15369
  refs.drumSelect.addEventListener("change", () => {
14595
15370
  currentDrumPattern = refs.drumSelect.value;
14596
15371
  options.onDrumChange?.(currentDrumPattern);
@@ -14871,7 +15646,7 @@ var mountDAW = (target, options = {}) => {
14871
15646
  console.error(e);
14872
15647
  }
14873
15648
  refs.midiTrackSelection.innerHTML = `<span class="dtm-label">\u30C8\u30E9\u30C3\u30AF</span>`;
14874
- detectedTracks.forEach((t, i) => {
15649
+ detectedTracks.forEach((t, i2) => {
14875
15650
  const btn = document.createElement("button");
14876
15651
  btn.className = `dtm-btn ${t.selected ? "dtm-btn--primary" : "dtm-btn--ghost"}`;
14877
15652
  btn.dataset.selected = String(t.selected);
@@ -14883,7 +15658,7 @@ var mountDAW = (target, options = {}) => {
14883
15658
  btn.classList.toggle("dtm-btn--ghost", !on);
14884
15659
  });
14885
15660
  refs.midiTrackSelection.appendChild(btn);
14886
- if (i === 0) refs.midiTrackSelection.dataset.ready = "1";
15661
+ if (i2 === 0) refs.midiTrackSelection.dataset.ready = "1";
14887
15662
  });
14888
15663
  refs.midiTrackSelection.classList.remove("dtm-hidden");
14889
15664
  refs.overlay.hidden = true;
@@ -14893,9 +15668,9 @@ var mountDAW = (target, options = {}) => {
14893
15668
  if (!pendingMidi) return;
14894
15669
  const selected = [];
14895
15670
  const btns = refs.midiTrackSelection.querySelectorAll("button");
14896
- btns.forEach((b, i) => {
15671
+ btns.forEach((b, i2) => {
14897
15672
  if (b.dataset.selected === "true")
14898
- selected.push(detectedTracks[i].index);
15673
+ selected.push(detectedTracks[i2].index);
14899
15674
  });
14900
15675
  if (selected.length === 0) return;
14901
15676
  if (!isAdvanced && options.onRequestAdvancedMode && selected.length > trackStates.length) {
@@ -14929,7 +15704,7 @@ var mountDAW = (target, options = {}) => {
14929
15704
  const refCore = trackStates[0].core;
14930
15705
  const tempo = Math.round(parsedBpm) || bpm;
14931
15706
  const lines = [];
14932
- let i = 0;
15707
+ let i2 = 0;
14933
15708
  for (const notes of byTrack.values()) {
14934
15709
  const asNotes = notes.map((p) => ({
14935
15710
  id: 0,
@@ -14939,8 +15714,8 @@ var mountDAW = (target, options = {}) => {
14939
15714
  velocity: p.velocity
14940
15715
  }));
14941
15716
  const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
14942
- lines.push(`@${i} ${mml}`);
14943
- i++;
15717
+ lines.push(`@${i2} ${mml}`);
15718
+ i2++;
14944
15719
  }
14945
15720
  return [...lines, MML_END_MARKER].join(";\n");
14946
15721
  };
@@ -15555,6 +16330,12 @@ var mountDAW = (target, options = {}) => {
15555
16330
  refs.drumVolume.value = String(drumVolume);
15556
16331
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
15557
16332
  },
16333
+ setReverbAmount: (amount) => {
16334
+ reverbAmount = clamp3(amount, 0, 100);
16335
+ refs.reverbAmount.value = String(reverbAmount);
16336
+ refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16337
+ options.onReverbChange?.(reverbAmount);
16338
+ },
15558
16339
  applyPatch: (trackId, added, removed) => {
15559
16340
  const track = trackStates.find((t) => t.config.id === trackId);
15560
16341
  if (!track) return;
@@ -15594,6 +16375,10 @@ var mountDAW = (target, options = {}) => {
15594
16375
  t.vocalGate = data.vocalGate;
15595
16376
  t.vocalPan = data.vocalPan;
15596
16377
  t.vocalOctave = data.vocalOctave;
16378
+ t.vocalVibrato = data.vocalVibrato ?? false;
16379
+ t.vocalReverb = data.vocalReverb ?? 0;
16380
+ t.vocalGender = data.vocalGender ?? 50;
16381
+ t.vocalBreathiness = data.vocalBreathiness ?? 50;
15597
16382
  },
15598
16383
  applyTrackInstrument: (trackIndex, instrumentName) => {
15599
16384
  const t = trackStates[trackIndex];
@@ -15604,21 +16389,22 @@ var mountDAW = (target, options = {}) => {
15604
16389
  },
15605
16390
  noteToCanvas: (step, pitch) => {
15606
16391
  const canvas = getGridCanvas();
15607
- const x = step * renderConfig.stepWidth - currentOffsetX;
16392
+ const x2 = step * renderConfig.stepWidth - currentOffsetX;
15608
16393
  const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
15609
- const onScreen = x >= 0 && x <= canvas.width && y >= 0 && y <= canvas.height;
16394
+ const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
15610
16395
  let side = null;
15611
16396
  if (!onScreen) {
15612
- if (x < 0) side = "left";
15613
- else if (x > canvas.width) side = "right";
16397
+ if (x2 < 0) side = "left";
16398
+ else if (x2 > canvas.width) side = "right";
15614
16399
  else if (y < 0) side = "top";
15615
16400
  else side = "bottom";
15616
16401
  }
15617
- return { x, y, onScreen, side };
16402
+ return { x: x2, y, onScreen, side };
15618
16403
  },
15619
16404
  destroy: () => {
15620
16405
  sequencer.stop();
15621
16406
  options.singingVoices?.stopStream();
16407
+ unsubscribeClip?.();
15622
16408
  resizeObserver.disconnect();
15623
16409
  document.removeEventListener("pointermove", onPointerMove);
15624
16410
  document.removeEventListener("pointerup", onPointerUp);
@@ -15694,11 +16480,11 @@ var playSingingMML = async (mml, options = {}) => {
15694
16480
  const semis = (lt.octave ?? 0) * 12;
15695
16481
  const count = Math.min(sorted.length, lt.syllables.length);
15696
16482
  const notes = [];
15697
- for (let i = 0; i < count; i++) {
15698
- const n = sorted[i];
16483
+ for (let i2 = 0; i2 < count; i2++) {
16484
+ const n = sorted[i2];
15699
16485
  if (n.startStep < fromStep) continue;
15700
16486
  notes.push({
15701
- syllable: lt.syllables[i],
16487
+ syllable: lt.syllables[i2],
15702
16488
  pitch: n.pitch + semis,
15703
16489
  startSec: (n.startStep - fromStep) * secondsPerStep,
15704
16490
  durationSec: n.durationSteps * secondsPerStep * gate
@@ -15709,6 +16495,10 @@ var playSingingMML = async (mml, options = {}) => {
15709
16495
  model: lt.model,
15710
16496
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
15711
16497
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16498
+ vibrato: lt.vibrato,
16499
+ reverbSend: (lt.reverb ?? 0) / 100,
16500
+ gender: (lt.gender ?? 50) / 100,
16501
+ breathiness: (lt.breathiness ?? 50) / 100,
15712
16502
  notes
15713
16503
  };
15714
16504
  });
@@ -15923,7 +16713,7 @@ var createPianoRoll = (options, handlers) => {
15923
16713
  let dragState = null;
15924
16714
  let hasDragged = false;
15925
16715
  let lastPreviewPitch = null;
15926
- const findNoteAtPosition = (x, y) => {
16716
+ const findNoteAtPosition = (x2, y) => {
15927
16717
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
15928
16718
  const offset = getDrawOffset();
15929
16719
  for (const note of core.getNotes()) {
@@ -15934,7 +16724,7 @@ var createPianoRoll = (options, handlers) => {
15934
16724
  const h = keyHeight;
15935
16725
  const renderX = logicalX - offset.x;
15936
16726
  const renderY = logicalY - offset.y;
15937
- if (x >= renderX && x <= renderX + w && y >= renderY && y <= renderY + h) {
16727
+ if (x2 >= renderX && x2 <= renderX + w && y >= renderY && y <= renderY + h) {
15938
16728
  return note;
15939
16729
  }
15940
16730
  }
@@ -15942,10 +16732,10 @@ var createPianoRoll = (options, handlers) => {
15942
16732
  };
15943
16733
  const handlePointerMove = (e) => {
15944
16734
  if (core.getToolMode() === "select" && isSelecting && selectionStart) {
15945
- const { x, y } = getGridPosition(e);
15946
- const minX = Math.min(x, selectionStart.x);
16735
+ const { x: x2, y } = getGridPosition(e);
16736
+ const minX = Math.min(x2, selectionStart.x);
15947
16737
  const minY = Math.min(y, selectionStart.y);
15948
- const width2 = Math.abs(x - selectionStart.x);
16738
+ const width2 = Math.abs(x2 - selectionStart.x);
15949
16739
  const height2 = Math.abs(y - selectionStart.y);
15950
16740
  selectionRect = { x: minX, y: minY, width: width2, height: height2 };
15951
16741
  selectedNotes = getNotesInRect(selectionRect);
@@ -16003,10 +16793,10 @@ var createPianoRoll = (options, handlers) => {
16003
16793
  }
16004
16794
  };
16005
16795
  gridCanvas.addEventListener("pointerdown", (e) => {
16006
- const { x, y, step, pitch } = getGridPosition(e);
16796
+ const { x: x2, y, step, pitch } = getGridPosition(e);
16007
16797
  const currentMode = core.getToolMode();
16008
16798
  if (currentMode === "select") {
16009
- const clickedNote = findNoteAtPosition(x, y);
16799
+ const clickedNote = findNoteAtPosition(x2, y);
16010
16800
  if (selectionRect && clickedNote) {
16011
16801
  const notesInRect = getNotesInRect(selectionRect);
16012
16802
  if (notesInRect.some((n) => n.id === clickedNote.id)) {
@@ -16027,10 +16817,10 @@ var createPianoRoll = (options, handlers) => {
16027
16817
  selectedNotes = [];
16028
16818
  selectionRect = null;
16029
16819
  isSelecting = true;
16030
- selectionStart = { x, y, step, pitch };
16820
+ selectionStart = { x: x2, y, step, pitch };
16031
16821
  return;
16032
16822
  }
16033
- const note = findNoteAtPosition(x, y);
16823
+ const note = findNoteAtPosition(x2, y);
16034
16824
  if (!note) return;
16035
16825
  const { stepWidth, keyHeight, keyCount, pitchRangeStart } = getRenderConfig();
16036
16826
  const offset = getDrawOffset();
@@ -16040,7 +16830,7 @@ var createPianoRoll = (options, handlers) => {
16040
16830
  const renderX = logicalX - offset.x;
16041
16831
  const renderY = logicalY - offset.y;
16042
16832
  const w = note.durationSteps * stepWidth;
16043
- if (x >= renderX + w - resizeHandleWidth && x <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16833
+ if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w && y >= renderY && y <= renderY + keyHeight) {
16044
16834
  dragState = {
16045
16835
  noteId: note.id,
16046
16836
  mode: "resize",
@@ -16316,6 +17106,166 @@ var isSupported = midiJsonParser.isSupported;
16316
17106
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
16317
17107
  URL.revokeObjectURL(url);
16318
17108
 
17109
+ // src/channel-strip.ts
17110
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17111
+ var compressionParams = (amount) => {
17112
+ const t = clamp4(amount, 0, 100) / 100;
17113
+ return {
17114
+ threshold: 0 + (-24 - 0) * t,
17115
+ ratio: 1 + (12 - 1) * t,
17116
+ knee: 0 + (6 - 0) * t,
17117
+ attack: 0.02 + (3e-3 - 0.02) * t,
17118
+ release: 0.25 + (0.15 - 0.25) * t
17119
+ };
17120
+ };
17121
+ var createChannelStrip = (ctx, destination, options = {}) => {
17122
+ const input = ctx.createGain();
17123
+ const compressor = ctx.createDynamicsCompressor();
17124
+ const applyCompression = (amount) => {
17125
+ const p = compressionParams(amount);
17126
+ const now = ctx.currentTime;
17127
+ compressor.threshold.setValueAtTime(p.threshold, now);
17128
+ compressor.ratio.setValueAtTime(p.ratio, now);
17129
+ compressor.knee.setValueAtTime(p.knee, now);
17130
+ compressor.attack.setValueAtTime(p.attack, now);
17131
+ compressor.release.setValueAtTime(p.release, now);
17132
+ };
17133
+ applyCompression(options.compression ?? 0);
17134
+ const splitter = ctx.createChannelSplitter(2);
17135
+ const mid = ctx.createGain();
17136
+ mid.gain.value = 0.5;
17137
+ splitter.connect(mid, 0);
17138
+ splitter.connect(mid, 1);
17139
+ const sideSum = ctx.createGain();
17140
+ sideSum.gain.value = 1;
17141
+ const sideL = ctx.createGain();
17142
+ sideL.gain.value = 0.5;
17143
+ const sideR = ctx.createGain();
17144
+ sideR.gain.value = -0.5;
17145
+ splitter.connect(sideL, 0);
17146
+ splitter.connect(sideR, 1);
17147
+ sideL.connect(sideSum);
17148
+ sideR.connect(sideSum);
17149
+ const widthGain = ctx.createGain();
17150
+ sideSum.connect(widthGain);
17151
+ const widthInv = ctx.createGain();
17152
+ widthInv.gain.value = -1;
17153
+ widthGain.connect(widthInv);
17154
+ const outL = ctx.createGain();
17155
+ mid.connect(outL);
17156
+ widthGain.connect(outL);
17157
+ const outR = ctx.createGain();
17158
+ mid.connect(outR);
17159
+ widthInv.connect(outR);
17160
+ const merger = ctx.createChannelMerger(2);
17161
+ outL.connect(merger, 0, 0);
17162
+ outR.connect(merger, 0, 1);
17163
+ const setWidth = (width) => {
17164
+ widthGain.gain.setTargetAtTime(
17165
+ clamp4(width, 0, 200) / 100,
17166
+ ctx.currentTime,
17167
+ 0.02
17168
+ );
17169
+ };
17170
+ setWidth(options.width ?? 100);
17171
+ input.connect(compressor);
17172
+ compressor.connect(splitter);
17173
+ merger.connect(destination);
17174
+ return {
17175
+ input,
17176
+ setCompression: applyCompression,
17177
+ setWidth,
17178
+ dispose: () => {
17179
+ input.disconnect();
17180
+ compressor.disconnect();
17181
+ splitter.disconnect();
17182
+ mid.disconnect();
17183
+ sideSum.disconnect();
17184
+ sideL.disconnect();
17185
+ sideR.disconnect();
17186
+ widthGain.disconnect();
17187
+ widthInv.disconnect();
17188
+ outL.disconnect();
17189
+ outR.disconnect();
17190
+ merger.disconnect();
17191
+ }
17192
+ };
17193
+ };
17194
+
17195
+ // src/clip-meter.ts
17196
+ var createClipMeter = (ctx, source, options = {}) => {
17197
+ const threshold = options.threshold ?? 0.98;
17198
+ const holdMs = options.holdMs ?? 800;
17199
+ const analyser = ctx.createAnalyser();
17200
+ analyser.fftSize = 512;
17201
+ source.connect(analyser);
17202
+ const buf = new Float32Array(analyser.fftSize);
17203
+ let clipping = false;
17204
+ let clipUntil = 0;
17205
+ let peakLevel = 0;
17206
+ const listeners = /* @__PURE__ */ new Set();
17207
+ let rafId = null;
17208
+ const tick = () => {
17209
+ analyser.getFloatTimeDomainData(buf);
17210
+ let peak = 0;
17211
+ let clipped = false;
17212
+ for (let i2 = 0; i2 < buf.length; i2++) {
17213
+ const v = Math.abs(buf[i2]);
17214
+ if (v > peak) peak = v;
17215
+ if (v >= threshold) clipped = true;
17216
+ }
17217
+ peakLevel = peak;
17218
+ const now = performance.now();
17219
+ if (clipped) clipUntil = now + holdMs;
17220
+ const nextClipping = now < clipUntil;
17221
+ if (nextClipping !== clipping) {
17222
+ clipping = nextClipping;
17223
+ for (const cb of listeners) cb(clipping);
17224
+ }
17225
+ rafId = requestAnimationFrame(tick);
17226
+ };
17227
+ rafId = requestAnimationFrame(tick);
17228
+ return {
17229
+ onClipChange: (cb) => {
17230
+ listeners.add(cb);
17231
+ return () => listeners.delete(cb);
17232
+ },
17233
+ getPeakLevel: () => peakLevel,
17234
+ reset: () => {
17235
+ clipUntil = 0;
17236
+ if (clipping) {
17237
+ clipping = false;
17238
+ for (const cb of listeners) cb(false);
17239
+ }
17240
+ },
17241
+ dispose: () => {
17242
+ if (rafId !== null) cancelAnimationFrame(rafId);
17243
+ rafId = null;
17244
+ analyser.disconnect();
17245
+ listeners.clear();
17246
+ }
17247
+ };
17248
+ };
17249
+
17250
+ // src/reverb.ts
17251
+ var IMPULSE_DURATION_SEC = 2.2;
17252
+ var IMPULSE_DECAY = 2.5;
17253
+ var createReverbImpulse = (ctx) => {
17254
+ const rate = ctx.sampleRate;
17255
+ const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
17256
+ const impulse = ctx.createBuffer(2, length, rate);
17257
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
17258
+ const data = impulse.getChannelData(ch);
17259
+ for (let i2 = 0; i2 < length; i2++) {
17260
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
17261
+ data[i2] = (Math.random() * 2 - 1) * envelope;
17262
+ }
17263
+ }
17264
+ return impulse;
17265
+ };
17266
+ var REVERB_MAX_WET = 0.6;
17267
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
17268
+
16319
17269
  // src/sf/SoundFont_drum.ts
16320
17270
  var touch = (map, key, ctor) => {
16321
17271
  if (!map.has(key)) map.set(key, new ctor());
@@ -16467,10 +17417,44 @@ var createDtmStudio = async (options = {}) => {
16467
17417
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
16468
17418
  const masterGain = audioCtx.createGain();
16469
17419
  masterGain.gain.value = options.masterVolume ?? 1;
16470
- masterGain.connect(audioCtx.destination);
16471
17420
  const drumGain = audioCtx.createGain();
16472
17421
  drumGain.gain.value = options.drumVolume ?? 1;
16473
17422
  drumGain.connect(masterGain);
17423
+ const reverbConvolver = audioCtx.createConvolver();
17424
+ reverbConvolver.buffer = createReverbImpulse(audioCtx);
17425
+ reverbConvolver.normalize = true;
17426
+ const reverbWetGain = audioCtx.createGain();
17427
+ reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17428
+ masterGain.connect(reverbConvolver);
17429
+ reverbConvolver.connect(reverbWetGain);
17430
+ const setReverbAmount = (amount) => {
17431
+ reverbWetGain.gain.setTargetAtTime(
17432
+ reverbAmountToGain(amount),
17433
+ audioCtx.currentTime,
17434
+ 0.02
17435
+ );
17436
+ };
17437
+ const finalMix = audioCtx.createGain();
17438
+ masterGain.connect(finalMix);
17439
+ reverbWetGain.connect(finalMix);
17440
+ const safetyLimiter = audioCtx.createDynamicsCompressor();
17441
+ safetyLimiter.threshold.value = -1;
17442
+ safetyLimiter.knee.value = 0;
17443
+ safetyLimiter.ratio.value = 20;
17444
+ safetyLimiter.attack.value = 1e-3;
17445
+ safetyLimiter.release.value = 0.1;
17446
+ finalMix.connect(safetyLimiter);
17447
+ safetyLimiter.connect(options.destination ?? audioCtx.destination);
17448
+ const clipMeter = createClipMeter(audioCtx, finalMix);
17449
+ const channelStrips = /* @__PURE__ */ new Map();
17450
+ const getChannelStrip = (trackId) => {
17451
+ let strip = channelStrips.get(trackId);
17452
+ if (!strip) {
17453
+ strip = createChannelStrip(audioCtx, masterGain);
17454
+ channelStrips.set(trackId, strip);
17455
+ }
17456
+ return strip;
17457
+ };
16474
17458
  const resumeAudio = () => {
16475
17459
  if (audioCtx.state === "closed") return Promise.resolve();
16476
17460
  return audioCtx.resume();
@@ -16501,7 +17485,14 @@ var createDtmStudio = async (options = {}) => {
16501
17485
  const singingVoices = createSingingVoices(audioCtx, masterGain, {
16502
17486
  voiceWorkerUrl,
16503
17487
  voicebanks,
16504
- worldlineScriptUrl: options.worldlineScriptUrl
17488
+ worldlineScriptUrl: options.worldlineScriptUrl,
17489
+ // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17490
+ // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17491
+ reverbBus: reverbConvolver,
17492
+ // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17493
+ // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17494
+ // 同じ trackId を指していれば同じ処理が掛かる。
17495
+ getTrackDestination: (trackId) => getChannelStrip(trackId).input
16505
17496
  });
16506
17497
  const listReady = new Promise((resolve) => {
16507
17498
  sfList.init();
@@ -16731,7 +17722,7 @@ var createDtmStudio = async (options = {}) => {
16731
17722
  if (!sfInst) return;
16732
17723
  sfInst.play({
16733
17724
  ctx: audioCtx,
16734
- destination: masterGain,
17725
+ destination: getChannelStrip(e.trackId).input,
16735
17726
  pitch: e.pitch,
16736
17727
  volume: e.volume,
16737
17728
  when: e.when,
@@ -16776,6 +17767,11 @@ var createDtmStudio = async (options = {}) => {
16776
17767
  void handleTrackInstrumentChange(idx, name);
16777
17768
  externalOnTrackInstrumentChange?.(idx, name);
16778
17769
  },
17770
+ reverbAmount: options.reverbAmount,
17771
+ onReverbChange: setReverbAmount,
17772
+ onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17773
+ onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
17774
+ clipMeter,
16779
17775
  ...dawOverrides,
16780
17776
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
16781
17777
  onDrumChange: (name) => {
@@ -16802,17 +17798,22 @@ var createDtmStudio = async (options = {}) => {
16802
17798
  mountedEditors.push(daw);
16803
17799
  const wantPresetUI = presetUI ?? features.presetUI;
16804
17800
  const rollEl = target.querySelector('[data-dtm="roll"]');
17801
+ const presetSlot = target.querySelector(
17802
+ '[data-dtm="preset-select-slot"]'
17803
+ );
16805
17804
  if (wantPresetUI) {
16806
17805
  editorPresetSelects.get(target)?.destroy();
16807
- presetSelect = mountPresetSelect(target, {
17806
+ presetSelect = mountPresetSelect(presetSlot ?? target, {
16808
17807
  getDaw: () => daw,
16809
17808
  getTrackIds: () => trackIds,
16810
17809
  value: initialPreset,
16811
17810
  loadingTarget: rollEl ?? target,
16812
- position: "prepend",
16813
- // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
17811
+ position: presetSlot ? "append" : "prepend",
17812
+ // 楽器変更時、このエディタの発音解決が使うプリセットも追従させ、
17813
+ // 外部の onInstrumentChange(呼び出し側の永続化フック等)へも通知する。
17814
+ // #inst= 付きMML読込時の通知経路(handleInstrumentChange)と揃える。
16814
17815
  onChange: (key) => {
16815
- editorPreset = key;
17816
+ handleInstrumentChange(key);
16816
17817
  }
16817
17818
  });
16818
17819
  editorPresetSelects.set(target, presetSelect);
@@ -16833,8 +17834,8 @@ var createDtmStudio = async (options = {}) => {
16833
17834
  presetSelect?.destroy();
16834
17835
  if (editorPresetSelects.get(target) === presetSelect)
16835
17836
  editorPresetSelects.delete(target);
16836
- const i = mountedEditors.indexOf(daw);
16837
- if (i >= 0) mountedEditors.splice(i, 1);
17837
+ const i2 = mountedEditors.indexOf(daw);
17838
+ if (i2 >= 0) mountedEditors.splice(i2, 1);
16838
17839
  };
16839
17840
  return {
16840
17841
  ...daw,
@@ -16937,8 +17938,8 @@ var createDtmStudio = async (options = {}) => {
16937
17938
  destroy: () => {
16938
17939
  doUnmount();
16939
17940
  wrapper.remove();
16940
- const i = mountedModeSwitches.indexOf(instance);
16941
- if (i >= 0) mountedModeSwitches.splice(i, 1);
17941
+ const i2 = mountedModeSwitches.indexOf(instance);
17942
+ if (i2 >= 0) mountedModeSwitches.splice(i2, 1);
16942
17943
  }
16943
17944
  };
16944
17945
  mountedModeSwitches.push(instance);
@@ -16995,7 +17996,7 @@ var createDtmStudio = async (options = {}) => {
16995
17996
  if (!sfInst) return;
16996
17997
  sfInst.play({
16997
17998
  ctx: audioCtx,
16998
- destination: masterGain,
17999
+ destination: getChannelStrip(e.trackId).input,
16999
18000
  pitch: e.pitch,
17000
18001
  volume: e.volume,
17001
18002
  when: e.when,
@@ -17025,8 +18026,8 @@ var createDtmStudio = async (options = {}) => {
17025
18026
  mountedPlayers.push(player);
17026
18027
  const destroy = () => {
17027
18028
  player.destroy();
17028
- const i = mountedPlayers.indexOf(player);
17029
- if (i >= 0) mountedPlayers.splice(i, 1);
18029
+ const i2 = mountedPlayers.indexOf(player);
18030
+ if (i2 >= 0) mountedPlayers.splice(i2, 1);
17030
18031
  };
17031
18032
  return { ...player, destroy };
17032
18033
  };
@@ -17081,7 +18082,7 @@ var createDtmStudio = async (options = {}) => {
17081
18082
  if (!sfInst) return;
17082
18083
  sfInst.play({
17083
18084
  ctx: audioCtx,
17084
- destination: masterGain,
18085
+ destination: getChannelStrip(e.trackId).input,
17085
18086
  pitch: e.pitch,
17086
18087
  volume: e.volume,
17087
18088
  when: e.when,
@@ -17160,7 +18161,7 @@ var createDtmStudio = async (options = {}) => {
17160
18161
  if (!sfInst) return;
17161
18162
  sfInst.play({
17162
18163
  ctx: audioCtx,
17163
- destination: masterGain,
18164
+ destination: getChannelStrip(e.trackId).input,
17164
18165
  pitch: e.pitch,
17165
18166
  volume: e.volume,
17166
18167
  when: e.when,
@@ -17199,7 +18200,7 @@ var createDtmStudio = async (options = {}) => {
17199
18200
  if (!sfInst) return;
17200
18201
  sfInst.play({
17201
18202
  ctx: audioCtx,
17202
- destination: masterGain,
18203
+ destination: getChannelStrip(e.trackId).input,
17203
18204
  pitch: e.pitch,
17204
18205
  volume: e.volume,
17205
18206
  when: e.when,
@@ -17255,7 +18256,7 @@ var createDtmStudio = async (options = {}) => {
17255
18256
  if (!sfInst) return;
17256
18257
  sfInst.play({
17257
18258
  ctx: audioCtx,
17258
- destination: masterGain,
18259
+ destination: getChannelStrip("chord").input,
17259
18260
  pitch: e.pitch,
17260
18261
  volume: e.volume,
17261
18262
  when: e.when,
@@ -17287,8 +18288,8 @@ var createDtmStudio = async (options = {}) => {
17287
18288
  mountedChordPlayers.push(player);
17288
18289
  const destroy = () => {
17289
18290
  player.destroy();
17290
- const i = mountedChordPlayers.indexOf(player);
17291
- if (i >= 0) mountedChordPlayers.splice(i, 1);
18291
+ const i2 = mountedChordPlayers.indexOf(player);
18292
+ if (i2 >= 0) mountedChordPlayers.splice(i2, 1);
17292
18293
  };
17293
18294
  return {
17294
18295
  ...player,
@@ -17305,6 +18306,9 @@ var createDtmStudio = async (options = {}) => {
17305
18306
  mountedPlayers.length = 0;
17306
18307
  mountedChordPlayers.length = 0;
17307
18308
  mountedEditors.length = 0;
18309
+ clipMeter.dispose();
18310
+ for (const strip of channelStrips.values()) strip.dispose();
18311
+ channelStrips.clear();
17308
18312
  void audioCtx.close();
17309
18313
  };
17310
18314
  const setMasterVolume = (volume) => {
@@ -17343,6 +18347,7 @@ var createDtmStudio = async (options = {}) => {
17343
18347
  mountModeSwitch,
17344
18348
  setMasterVolume,
17345
18349
  setVolume: setMasterVolume,
18350
+ setReverbAmount,
17346
18351
  dispose
17347
18352
  };
17348
18353
  };
@@ -17373,6 +18378,7 @@ export {
17373
18378
  PREWARM_NOTES,
17374
18379
  TRACKS_ADVANCED,
17375
18380
  TRACKS_SIMPLE,
18381
+ VIBRATO_MIN_SEC,
17376
18382
  VOICE_IMAGES,
17377
18383
  VOICE_IMAGE_KEY,
17378
18384
  analyzeMidiTracks,