@onjmin/dtm 0.1.87 → 0.1.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1466,6 +1466,149 @@ var getDrumPatternKeys = (name, dict) => {
1466
1466
  return Array.from(keys);
1467
1467
  };
1468
1468
 
1469
+ // src/channel-strip.ts
1470
+ var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1471
+ var compressionParams = (amount) => {
1472
+ const t = clamp(amount, 0, 100) / 100;
1473
+ return {
1474
+ threshold: 0 + (-24 - 0) * t,
1475
+ ratio: 1 + (12 - 1) * t,
1476
+ knee: 0 + (6 - 0) * t,
1477
+ attack: 0.02 + (3e-3 - 0.02) * t,
1478
+ release: 0.25 + (0.15 - 0.25) * t
1479
+ };
1480
+ };
1481
+ var EQ_LOW_FREQ = 200;
1482
+ var EQ_MID_FREQ = 1e3;
1483
+ var EQ_HIGH_FREQ = 5e3;
1484
+ var EQ_MID_Q = 1;
1485
+ var EQ_MAX_DB = 12;
1486
+ var createChannelStrip = (ctx, destination, options = {}) => {
1487
+ const input = ctx.createGain();
1488
+ const eqLow = ctx.createBiquadFilter();
1489
+ eqLow.type = "lowshelf";
1490
+ eqLow.frequency.value = EQ_LOW_FREQ;
1491
+ eqLow.gain.value = clamp(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1492
+ const eqMid = ctx.createBiquadFilter();
1493
+ eqMid.type = "peaking";
1494
+ eqMid.frequency.value = EQ_MID_FREQ;
1495
+ eqMid.Q.value = EQ_MID_Q;
1496
+ eqMid.gain.value = clamp(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1497
+ const eqHigh = ctx.createBiquadFilter();
1498
+ eqHigh.type = "highshelf";
1499
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
1500
+ eqHigh.gain.value = clamp(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1501
+ input.connect(eqLow);
1502
+ eqLow.connect(eqMid);
1503
+ eqMid.connect(eqHigh);
1504
+ const compressor = ctx.createDynamicsCompressor();
1505
+ const applyCompression = (amount) => {
1506
+ const p = compressionParams(amount);
1507
+ const now = ctx.currentTime;
1508
+ compressor.threshold.setValueAtTime(p.threshold, now);
1509
+ compressor.ratio.setValueAtTime(p.ratio, now);
1510
+ compressor.knee.setValueAtTime(p.knee, now);
1511
+ compressor.attack.setValueAtTime(p.attack, now);
1512
+ compressor.release.setValueAtTime(p.release, now);
1513
+ };
1514
+ applyCompression(options.compression ?? 0);
1515
+ const splitter = ctx.createChannelSplitter(2);
1516
+ const mid = ctx.createGain();
1517
+ mid.gain.value = 0.5;
1518
+ splitter.connect(mid, 0);
1519
+ splitter.connect(mid, 1);
1520
+ const sideSum = ctx.createGain();
1521
+ sideSum.gain.value = 1;
1522
+ const sideL = ctx.createGain();
1523
+ sideL.gain.value = 0.5;
1524
+ const sideR = ctx.createGain();
1525
+ sideR.gain.value = -0.5;
1526
+ splitter.connect(sideL, 0);
1527
+ splitter.connect(sideR, 1);
1528
+ sideL.connect(sideSum);
1529
+ sideR.connect(sideSum);
1530
+ const widthGain = ctx.createGain();
1531
+ sideSum.connect(widthGain);
1532
+ const widthInv = ctx.createGain();
1533
+ widthInv.gain.value = -1;
1534
+ widthGain.connect(widthInv);
1535
+ const outL = ctx.createGain();
1536
+ mid.connect(outL);
1537
+ widthGain.connect(outL);
1538
+ const outR = ctx.createGain();
1539
+ mid.connect(outR);
1540
+ widthInv.connect(outR);
1541
+ const merger = ctx.createChannelMerger(2);
1542
+ outL.connect(merger, 0, 0);
1543
+ outR.connect(merger, 0, 1);
1544
+ const setWidth = (width) => {
1545
+ widthGain.gain.setTargetAtTime(
1546
+ clamp(width, 0, 200) / 100,
1547
+ ctx.currentTime,
1548
+ 0.02
1549
+ );
1550
+ };
1551
+ setWidth(options.width ?? 100);
1552
+ eqHigh.connect(compressor);
1553
+ compressor.connect(splitter);
1554
+ merger.connect(destination);
1555
+ const reverbSendGain = ctx.createGain();
1556
+ reverbSendGain.gain.value = clamp(options.reverbSend ?? 0, 0, 100) / 100;
1557
+ merger.connect(reverbSendGain);
1558
+ if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
1559
+ return {
1560
+ input,
1561
+ setEqLow: (db) => {
1562
+ eqLow.gain.setTargetAtTime(
1563
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1564
+ ctx.currentTime,
1565
+ 0.02
1566
+ );
1567
+ },
1568
+ setEqMid: (db) => {
1569
+ eqMid.gain.setTargetAtTime(
1570
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1571
+ ctx.currentTime,
1572
+ 0.02
1573
+ );
1574
+ },
1575
+ setEqHigh: (db) => {
1576
+ eqHigh.gain.setTargetAtTime(
1577
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1578
+ ctx.currentTime,
1579
+ 0.02
1580
+ );
1581
+ },
1582
+ setCompression: applyCompression,
1583
+ setWidth,
1584
+ setReverbSend: (amount) => {
1585
+ reverbSendGain.gain.setTargetAtTime(
1586
+ clamp(amount, 0, 100) / 100,
1587
+ ctx.currentTime,
1588
+ 0.02
1589
+ );
1590
+ },
1591
+ dispose: () => {
1592
+ input.disconnect();
1593
+ eqLow.disconnect();
1594
+ eqMid.disconnect();
1595
+ eqHigh.disconnect();
1596
+ compressor.disconnect();
1597
+ splitter.disconnect();
1598
+ mid.disconnect();
1599
+ sideSum.disconnect();
1600
+ sideL.disconnect();
1601
+ sideR.disconnect();
1602
+ widthGain.disconnect();
1603
+ widthInv.disconnect();
1604
+ outL.disconnect();
1605
+ outR.disconnect();
1606
+ merger.disconnect();
1607
+ reverbSendGain.disconnect();
1608
+ }
1609
+ };
1610
+ };
1611
+
1469
1612
  // src/chords.ts
1470
1613
  var C3 = 48;
1471
1614
  var buildChordPlacements = (options) => {
@@ -2295,7 +2438,7 @@ var normalizeLyricLines = (lines) => {
2295
2438
  var LYRIC_LINE = /^@@(\d+)\s*(.*)$/;
2296
2439
  var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
2297
2440
  var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
2298
- var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2441
+ var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2299
2442
  var MAX_VOCAL_VOLUME = 400;
2300
2443
  var VOCAL_BOOST_DB_PER_PERCENT = 0.08;
2301
2444
  var vocalVolumeToGain = (v) => {
@@ -2320,7 +2463,7 @@ var parseLyrics = (mml) => {
2320
2463
  let delay = 0;
2321
2464
  let gender = 50;
2322
2465
  let breathiness = 50;
2323
- let octaveDouble = false;
2466
+ let octaveUnison = "none";
2324
2467
  const modelMatch = rest.match(
2325
2468
  /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2326
2469
  );
@@ -2329,7 +2472,7 @@ var parseLyrics = (mml) => {
2329
2472
  if (modelMatch) {
2330
2473
  model = modelMatch[1].toLowerCase();
2331
2474
  if (modelMatch[2]) {
2332
- volume = clamp(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2475
+ volume = clamp2(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2333
2476
  }
2334
2477
  metaTokens.push(modelMatch[0]);
2335
2478
  rest = rest.substring(modelMatch[0].length).trim();
@@ -2337,28 +2480,28 @@ var parseLyrics = (mml) => {
2337
2480
  while (true) {
2338
2481
  const vMatch = rest.match(/^v(\d+)/i);
2339
2482
  if (vMatch) {
2340
- volume = clamp(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2483
+ volume = clamp2(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2341
2484
  metaTokens.push(vMatch[0]);
2342
2485
  rest = rest.substring(vMatch[0].length).trim();
2343
2486
  continue;
2344
2487
  }
2345
2488
  const qMatch = rest.match(/^q(\d+)/i);
2346
2489
  if (qMatch) {
2347
- gate = clamp(Number.parseInt(qMatch[1], 10), 0, 100);
2490
+ gate = clamp2(Number.parseInt(qMatch[1], 10), 0, 100);
2348
2491
  metaTokens.push(qMatch[0]);
2349
2492
  rest = rest.substring(qMatch[0].length).trim();
2350
2493
  continue;
2351
2494
  }
2352
2495
  const pMatch = rest.match(/^p(\d+)/i);
2353
2496
  if (pMatch) {
2354
- pan = clamp(Number.parseInt(pMatch[1], 10), 0, 127);
2497
+ pan = clamp2(Number.parseInt(pMatch[1], 10), 0, 127);
2355
2498
  metaTokens.push(pMatch[0]);
2356
2499
  rest = rest.substring(pMatch[0].length).trim();
2357
2500
  continue;
2358
2501
  }
2359
2502
  const oMatch = rest.match(/^o(-?\d+)/i);
2360
2503
  if (oMatch) {
2361
- octave = clamp(Number.parseInt(oMatch[1], 10), -2, 2);
2504
+ octave = clamp2(Number.parseInt(oMatch[1], 10), -2, 2);
2362
2505
  metaTokens.push(oMatch[0]);
2363
2506
  rest = rest.substring(oMatch[0].length).trim();
2364
2507
  continue;
@@ -2372,35 +2515,35 @@ var parseLyrics = (mml) => {
2372
2515
  }
2373
2516
  const rMatch = rest.match(/^r(\d+)/i);
2374
2517
  if (rMatch) {
2375
- reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2518
+ reverb = clamp2(Number.parseInt(rMatch[1], 10), 0, 100);
2376
2519
  metaTokens.push(rMatch[0]);
2377
2520
  rest = rest.substring(rMatch[0].length).trim();
2378
2521
  continue;
2379
2522
  }
2380
2523
  const gMatch = rest.match(/^g(\d+)/i);
2381
2524
  if (gMatch) {
2382
- gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2525
+ gender = clamp2(Number.parseInt(gMatch[1], 10), 0, 100);
2383
2526
  metaTokens.push(gMatch[0]);
2384
2527
  rest = rest.substring(gMatch[0].length).trim();
2385
2528
  continue;
2386
2529
  }
2387
2530
  const hMatch = rest.match(/^h(\d+)/i);
2388
2531
  if (hMatch) {
2389
- breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2532
+ breathiness = clamp2(Number.parseInt(hMatch[1], 10), 0, 100);
2390
2533
  metaTokens.push(hMatch[0]);
2391
2534
  rest = rest.substring(hMatch[0].length).trim();
2392
2535
  continue;
2393
2536
  }
2394
2537
  const eMatch = rest.match(/^e(\d+)/i);
2395
2538
  if (eMatch) {
2396
- delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2539
+ delay = clamp2(Number.parseInt(eMatch[1], 10), 0, 100);
2397
2540
  metaTokens.push(eMatch[0]);
2398
2541
  rest = rest.substring(eMatch[0].length).trim();
2399
2542
  continue;
2400
2543
  }
2401
- const wMatch = rest.match(/^w([01])/i);
2544
+ const wMatch = rest.match(/^w([0-3])/i);
2402
2545
  if (wMatch) {
2403
- octaveDouble = wMatch[1] === "1";
2546
+ octaveUnison = ["none", "down", "up", "both"][Number.parseInt(wMatch[1], 10)];
2404
2547
  metaTokens.push(wMatch[0]);
2405
2548
  rest = rest.substring(wMatch[0].length).trim();
2406
2549
  continue;
@@ -2424,7 +2567,7 @@ var parseLyrics = (mml) => {
2424
2567
  delay,
2425
2568
  gender,
2426
2569
  breathiness,
2427
- octaveDouble,
2570
+ octaveUnison,
2428
2571
  syllables,
2429
2572
  metaText: metaTokens.join(" "),
2430
2573
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -3021,8 +3164,19 @@ var createKoeVoice = async (ctx, destination, options) => {
3021
3164
  var PREWARM_NOTES = 3;
3022
3165
  var STREAM_LOOKAHEAD_SEC = 1.5;
3023
3166
  var STREAM_POLL_MS = 100;
3024
- var OCTAVE_DOUBLE_SEMITONES = -12;
3025
- var OCTAVE_DOUBLE_PEAK_SCALE = 0.6;
3167
+ var OCTAVE_UNISON_PEAK_SCALE = 0.6;
3168
+ var octaveUnisonOffsets = (mode) => {
3169
+ switch (mode) {
3170
+ case "down":
3171
+ return [-12];
3172
+ case "up":
3173
+ return [12];
3174
+ case "both":
3175
+ return [-12, 12];
3176
+ default:
3177
+ return [];
3178
+ }
3179
+ };
3026
3180
  var FALLBACK_MODEL = "klatt";
3027
3181
  var createSingingVoices = (ctx, destination, options = {}) => {
3028
3182
  const catalog = {};
@@ -3117,12 +3271,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3117
3271
  vibrato: track.vibrato,
3118
3272
  expr
3119
3273
  });
3120
- if (track.octaveDouble) {
3274
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3121
3275
  tasks.push({
3122
3276
  model: m,
3123
3277
  note,
3124
3278
  prevVowel,
3125
- pitch: note.pitch + OCTAVE_DOUBLE_SEMITONES,
3279
+ pitch: note.pitch + offset,
3126
3280
  vibrato: track.vibrato,
3127
3281
  expr
3128
3282
  });
@@ -3237,11 +3391,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3237
3391
  }
3238
3392
  };
3239
3393
  dispatchNote(note.pitch, 1);
3240
- if (track.octaveDouble) {
3241
- dispatchNote(
3242
- note.pitch + OCTAVE_DOUBLE_SEMITONES,
3243
- OCTAVE_DOUBLE_PEAK_SCALE
3244
- );
3394
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3395
+ dispatchNote(note.pitch + offset, OCTAVE_UNISON_PEAK_SCALE);
3245
3396
  }
3246
3397
  if (!(model.renderToCache && model.scheduleCached)) {
3247
3398
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -3301,11 +3452,12 @@ var PITCH_MAP = {
3301
3452
  a: 9,
3302
3453
  b: 11
3303
3454
  };
3304
- var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3455
+ var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3305
3456
  var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3306
3457
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3307
3458
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3308
3459
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3460
+ var TRACK_REVERBSEND_DIRECTIVE = /#t(\d+)rev=(\d+)/gi;
3309
3461
  var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3310
3462
  var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3311
3463
  var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
@@ -3324,24 +3476,24 @@ var parseMmlMeta = (mml) => {
3324
3476
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3325
3477
  } else if (key === "reverb") {
3326
3478
  const rv = Number.parseInt(m[2], 10);
3327
- if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3479
+ if (!Number.isNaN(rv)) meta.reverb = clamp3(rv, 0, 100);
3328
3480
  } else if (key === "reverbdecay") {
3329
3481
  const rd = Number.parseInt(m[2], 10);
3330
- if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3482
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp3(rd, 3, 40);
3331
3483
  } else if (key === "reverbpredelay") {
3332
3484
  const rp = Number.parseInt(m[2], 10);
3333
- if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3485
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp3(rp, 0, 150);
3334
3486
  } else if (key === "delay") {
3335
3487
  const dv = Number.parseInt(m[2], 10);
3336
- if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3488
+ if (!Number.isNaN(dv)) meta.delay = clamp3(dv, 0, 100);
3337
3489
  } else if (key === "delaydiv") {
3338
3490
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3339
3491
  } else if (key === "fadein") {
3340
3492
  const fi = Number.parseInt(m[2], 10);
3341
- if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3493
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp3(fi, 0, 100);
3342
3494
  } else if (key === "fadeout") {
3343
3495
  const fo = Number.parseInt(m[2], 10);
3344
- if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3496
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp3(fo, 0, 100);
3345
3497
  } else if (key === "mode") {
3346
3498
  if (m[2] === "simple" || m[2] === "advanced") {
3347
3499
  meta.mode = m[2];
@@ -3358,7 +3510,7 @@ var parseMmlMeta = (mml) => {
3358
3510
  }
3359
3511
  for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3360
3512
  const idx = Number.parseInt(m[1], 10);
3361
- const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3513
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3362
3514
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3363
3515
  meta.trackCompression ??= {};
3364
3516
  meta.trackCompression[idx] = val;
@@ -3366,15 +3518,23 @@ var parseMmlMeta = (mml) => {
3366
3518
  }
3367
3519
  for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3368
3520
  const idx = Number.parseInt(m[1], 10);
3369
- const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3521
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 200);
3370
3522
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3371
3523
  meta.trackWidth ??= {};
3372
3524
  meta.trackWidth[idx] = val;
3373
3525
  }
3374
3526
  }
3527
+ for (const m of mml.matchAll(TRACK_REVERBSEND_DIRECTIVE)) {
3528
+ const idx = Number.parseInt(m[1], 10);
3529
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3530
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3531
+ meta.trackReverbSend ??= {};
3532
+ meta.trackReverbSend[idx] = val;
3533
+ }
3534
+ }
3375
3535
  for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3376
3536
  const idx = Number.parseInt(m[1], 10);
3377
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3537
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3378
3538
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3379
3539
  meta.trackEqLow ??= {};
3380
3540
  meta.trackEqLow[idx] = val;
@@ -3382,7 +3542,7 @@ var parseMmlMeta = (mml) => {
3382
3542
  }
3383
3543
  for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3384
3544
  const idx = Number.parseInt(m[1], 10);
3385
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3545
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3386
3546
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3387
3547
  meta.trackEqMid ??= {};
3388
3548
  meta.trackEqMid[idx] = val;
@@ -3390,7 +3550,7 @@ var parseMmlMeta = (mml) => {
3390
3550
  }
3391
3551
  for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3392
3552
  const idx = Number.parseInt(m[1], 10);
3393
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3553
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3394
3554
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3395
3555
  meta.trackEqHigh ??= {};
3396
3556
  meta.trackEqHigh[idx] = val;
@@ -3398,7 +3558,7 @@ var parseMmlMeta = (mml) => {
3398
3558
  }
3399
3559
  return meta;
3400
3560
  };
3401
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3561
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_REVERBSEND_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3402
3562
  var formatMmlMeta = (meta, space = "") => {
3403
3563
  const parts = [];
3404
3564
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3437,6 +3597,11 @@ var formatMmlMeta = (meta, space = "") => {
3437
3597
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3438
3598
  }
3439
3599
  }
3600
+ if (meta.trackReverbSend) {
3601
+ for (const [idx, val] of Object.entries(meta.trackReverbSend)) {
3602
+ if (val !== 0) parts.push(`#t${idx}rev=${val}`);
3603
+ }
3604
+ }
3440
3605
  if (meta.trackEqLow) {
3441
3606
  for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3442
3607
  if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
@@ -3534,7 +3699,7 @@ var parseMML = (mml, options = {}) => {
3534
3699
  numStr += body[j];
3535
3700
  j++;
3536
3701
  }
3537
- const len = numStr ? clamp2(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3702
+ const len = numStr ? clamp3(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3538
3703
  let steps = Math.round(stepsPerBar / len);
3539
3704
  while (j < body.length && body[j] === ".") {
3540
3705
  steps = Math.round(steps * 1.5);
@@ -3552,7 +3717,7 @@ var parseMML = (mml, options = {}) => {
3552
3717
  numStr += body[j];
3553
3718
  j++;
3554
3719
  }
3555
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3720
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3556
3721
  pushTok("octave", currentStep, 0, tokStart);
3557
3722
  } else if (ch === ">") {
3558
3723
  octave = Math.min(8, octave + 1);
@@ -3569,7 +3734,7 @@ var parseMML = (mml, options = {}) => {
3569
3734
  numStr += body[j];
3570
3735
  j++;
3571
3736
  }
3572
- baseLength = clamp2(Number.parseInt(numStr, 10) || 16, 1, 64);
3737
+ baseLength = clamp3(Number.parseInt(numStr, 10) || 16, 1, 64);
3573
3738
  pushTok("length", currentStep, 0, tokStart);
3574
3739
  } else if (ch === "r") {
3575
3740
  j++;
@@ -3586,10 +3751,10 @@ var parseMML = (mml, options = {}) => {
3586
3751
  }
3587
3752
  if (ch === "t" && numStr) {
3588
3753
  if (bpm === null) {
3589
- bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3754
+ bpm = clamp3(Number.parseInt(numStr, 10), 1, 255);
3590
3755
  }
3591
3756
  } else if (ch === "v" && numStr) {
3592
- velocity = clamp2(Number.parseInt(numStr, 10), 0, 127);
3757
+ velocity = clamp3(Number.parseInt(numStr, 10), 0, 127);
3593
3758
  trackVelocity.set(trackIndex, velocity);
3594
3759
  }
3595
3760
  pushTok("ctrl", currentStep, 0, tokStart);
@@ -3623,7 +3788,7 @@ var parseMML = (mml, options = {}) => {
3623
3788
  numStr += body[j];
3624
3789
  j++;
3625
3790
  }
3626
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3791
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3627
3792
  } else {
3628
3793
  j++;
3629
3794
  }
@@ -3685,6 +3850,38 @@ var parseMML = (mml, options = {}) => {
3685
3850
  };
3686
3851
  };
3687
3852
 
3853
+ // src/reverb.ts
3854
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
3855
+ var MIN_REVERB_DECAY_SEC = 0.3;
3856
+ var MAX_REVERB_DECAY_SEC = 4;
3857
+ var IMPULSE_DECAY_CURVE = 2.5;
3858
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
3859
+ var MIN_REVERB_PREDELAY_MS = 0;
3860
+ var MAX_REVERB_PREDELAY_MS = 150;
3861
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
3862
+ const rate = ctx.sampleRate;
3863
+ const length = Math.max(
3864
+ 1,
3865
+ Math.floor(
3866
+ rate * Math.max(
3867
+ MIN_REVERB_DECAY_SEC,
3868
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
3869
+ )
3870
+ )
3871
+ );
3872
+ const impulse = ctx.createBuffer(2, length, rate);
3873
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
3874
+ const data = impulse.getChannelData(ch);
3875
+ for (let i2 = 0; i2 < length; i2++) {
3876
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
3877
+ data[i2] = (Math.random() * 2 - 1) * envelope;
3878
+ }
3879
+ }
3880
+ return impulse;
3881
+ };
3882
+ var REVERB_MAX_WET = 0.6;
3883
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
3884
+
3688
3885
  // src/sequencer.ts
3689
3886
  var STEPS_PER_BEAT = 48;
3690
3887
  var PLAN_TIME = 0.5;
@@ -6262,9 +6459,54 @@ var playPlacements = (placements, options) => {
6262
6459
  });
6263
6460
  const ownsCtx = !options.audioContext;
6264
6461
  const ctx = options.audioContext ?? new AudioContext();
6265
- const destination = options.destination ?? ctx.destination;
6462
+ const rawDestination = options.destination ?? ctx.destination;
6266
6463
  const useSynth = options.synth ?? !options.onPlayNote;
6267
- const synth = useSynth ? createSynth(ctx, destination) : null;
6464
+ const finalMix = ctx.createGain();
6465
+ const masterGain = ctx.createGain();
6466
+ masterGain.connect(finalMix);
6467
+ const reverbPreDelay = ctx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
6468
+ reverbPreDelay.delayTime.value = (options.metaReverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
6469
+ const reverbConvolver = ctx.createConvolver();
6470
+ const reverbDecaySec = (options.metaReverbDecay ?? 22) / 10 || DEFAULT_REVERB_DECAY_SEC;
6471
+ reverbConvolver.buffer = createReverbImpulse(ctx, reverbDecaySec);
6472
+ reverbConvolver.normalize = true;
6473
+ const reverbWetGain = ctx.createGain();
6474
+ reverbWetGain.gain.value = reverbAmountToGain(options.metaReverb ?? 0);
6475
+ reverbPreDelay.connect(reverbConvolver);
6476
+ reverbConvolver.connect(reverbWetGain);
6477
+ reverbWetGain.connect(finalMix);
6478
+ finalMix.connect(rawDestination);
6479
+ const channelStrips = /* @__PURE__ */ new Map();
6480
+ const getChannelStrip = (index) => {
6481
+ let strip = channelStrips.get(index);
6482
+ if (!strip) {
6483
+ strip = createChannelStrip(ctx, masterGain, {
6484
+ compression: options.trackCompression?.[index] ?? 0,
6485
+ width: options.trackWidth?.[index] ?? 100,
6486
+ eqLow: options.trackEqLow?.[index] ?? 0,
6487
+ eqMid: options.trackEqMid?.[index] ?? 0,
6488
+ eqHigh: options.trackEqHigh?.[index] ?? 0,
6489
+ reverbSend: options.trackReverbSend?.[index] ?? 0,
6490
+ reverbBus: reverbPreDelay
6491
+ });
6492
+ channelStrips.set(index, strip);
6493
+ }
6494
+ return strip;
6495
+ };
6496
+ const synths = /* @__PURE__ */ new Map();
6497
+ const getSynth = (index) => {
6498
+ let s = synths.get(index);
6499
+ if (!s) {
6500
+ s = createSynth(ctx, getChannelStrip(index).input);
6501
+ synths.set(index, s);
6502
+ }
6503
+ return s;
6504
+ };
6505
+ const drumSynth = useSynth ? createSynth(ctx, masterGain) : null;
6506
+ const trackIndexById = /* @__PURE__ */ new Map();
6507
+ trackIndices.forEach((idx) => {
6508
+ trackIndexById.set(TRACK_ID_BY_INDEX[idx] ?? `t${idx}`, idx);
6509
+ });
6268
6510
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
6269
6511
  let playing = false;
6270
6512
  const seq = createSequencer({
@@ -6279,12 +6521,14 @@ var playPlacements = (placements, options) => {
6279
6521
  getAudioTime: () => ctx.currentTime,
6280
6522
  onPlayNote: (e) => {
6281
6523
  options.onPlayNote?.(e);
6282
- synth?.playNote(e);
6524
+ if (!useSynth) return;
6525
+ const index = trackIndexById.get(e.trackId);
6526
+ (index === void 0 ? drumSynth : getSynth(index))?.playNote(e);
6283
6527
  },
6284
6528
  onPlayDrum: (e) => {
6285
6529
  const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
6286
6530
  options.onPlayDrum?.({ ...e, velocity });
6287
- synth?.playDrum({ ...e, velocity });
6531
+ drumSynth?.playDrum({ ...e, velocity });
6288
6532
  },
6289
6533
  onTick: (step) => {
6290
6534
  options.onTick?.(step);
@@ -6335,6 +6579,7 @@ var playPlacements = (placements, options) => {
6335
6579
  if (pauseWhenHidden && typeof document !== "undefined") {
6336
6580
  document.removeEventListener("visibilitychange", onVisibilityChange);
6337
6581
  }
6582
+ for (const strip of channelStrips.values()) strip.dispose();
6338
6583
  if (ownsCtx) void ctx.close();
6339
6584
  };
6340
6585
  return {
@@ -6354,7 +6599,16 @@ var playMML = (mml, options = {}) => {
6354
6599
  bpm,
6355
6600
  metaVolume: meta.volume,
6356
6601
  metaDrum: meta.drum,
6357
- metaDrumVolume: meta.drumVolume
6602
+ metaDrumVolume: meta.drumVolume,
6603
+ metaReverb: meta.reverb,
6604
+ metaReverbDecay: meta.reverbDecay,
6605
+ metaReverbPreDelay: meta.reverbPreDelay,
6606
+ trackCompression: meta.trackCompression,
6607
+ trackWidth: meta.trackWidth,
6608
+ trackReverbSend: meta.trackReverbSend,
6609
+ trackEqLow: meta.trackEqLow,
6610
+ trackEqMid: meta.trackEqMid,
6611
+ trackEqHigh: meta.trackEqHigh
6358
6612
  });
6359
6613
  };
6360
6614
  var playNote = (options) => {
@@ -7752,11 +8006,14 @@ var DAW_CSS = `
7752
8006
  user-select: none;
7753
8007
  }
7754
8008
  .dtm-player-balloon {
7755
- position: absolute;
7756
- bottom: calc(100% + 6px);
7757
- left: 50%;
7758
- transform: translateX(-50%);
7759
- z-index: 100;
8009
+ /* body \u76F4\u4E0B\u306B\u751F\u6210\u3055\u308C\u3001JS(positionBalloon)\u304C\u6BCE\u56DE left/top \u3092\u30A2\u30F3\u30AB\u30FC\u306E\u753B\u9762\u5EA7\u6A19\u3067
8010
+ \u8A2D\u5B9A\u3059\u308B\u3002overflow:hidden \u306A\u7956\u5148\uFF08\u6295\u7A3F\u30AB\u30FC\u30C9\u7B49\uFF09\u306B\u30AF\u30EA\u30C3\u30D7\u3055\u308C\u306A\u3044\u3088\u3046 fixed \u3067\u6D6E\u304B\u305B\u308B\u3002
8011
+ top \u306F\u30A2\u30F3\u30AB\u30FC\u4E0A\u7AEF-6px\u3001transform \u3067\u81EA\u8EAB\u306E\u5DE6\u4E0B\u89D2\u3092\u305D\u306E\u70B9\u306B\u5408\u308F\u305B\u308B\u3002 */
8012
+ position: fixed;
8013
+ left: 0;
8014
+ top: 0;
8015
+ transform: translate(-50%, -100%);
8016
+ z-index: 1000;
7760
8017
  display: none;
7761
8018
  pointer-events: none;
7762
8019
  font-family: var(--dtm-font);
@@ -7785,8 +8042,8 @@ var DAW_CSS = `
7785
8042
  animation: dtm-balloon-fade-in 0.1s steps(2);
7786
8043
  }
7787
8044
  @keyframes dtm-balloon-fade-in {
7788
- from { opacity: 0; transform: translateX(-50%) translateY(4px); }
7789
- to { opacity: 1; transform: translateX(-50%) translateY(0); }
8045
+ from { opacity: 0; transform: translate(-50%, -100%) translateY(4px); }
8046
+ to { opacity: 1; transform: translate(-50%, -100%) translateY(0); }
7790
8047
  }
7791
8048
  @keyframes dtm-emoji-jump {
7792
8049
  0% { transform: translateY(0); }
@@ -8209,6 +8466,27 @@ var LYRIC_MODEL_LABELS = {
8209
8466
  };
8210
8467
  var activeBalloonEl = null;
8211
8468
  var activeBalloonTimer = null;
8469
+ var balloonAnchors = /* @__PURE__ */ new WeakMap();
8470
+ var positionBalloon = (balloonEl) => {
8471
+ const anchor = balloonAnchors.get(balloonEl);
8472
+ if (!anchor) return;
8473
+ const rect = anchor.getBoundingClientRect();
8474
+ balloonEl.style.left = `${rect.left + rect.width / 2}px`;
8475
+ balloonEl.style.top = `${rect.top - 6}px`;
8476
+ };
8477
+ var balloonListenersBound = false;
8478
+ var ensureBalloonListeners = () => {
8479
+ if (balloonListenersBound || typeof window === "undefined") return;
8480
+ balloonListenersBound = true;
8481
+ const reposition = () => {
8482
+ if (activeBalloonEl) positionBalloon(activeBalloonEl);
8483
+ };
8484
+ window.addEventListener("scroll", reposition, {
8485
+ capture: true,
8486
+ passive: true
8487
+ });
8488
+ window.addEventListener("resize", reposition, { passive: true });
8489
+ };
8212
8490
  var hideActiveBalloon = () => {
8213
8491
  if (activeBalloonEl) {
8214
8492
  activeBalloonEl.classList.remove("dtm-player-balloon--visible");
@@ -8220,6 +8498,7 @@ var hideActiveBalloon = () => {
8220
8498
  }
8221
8499
  };
8222
8500
  var showBalloon = (balloonEl) => {
8501
+ positionBalloon(balloonEl);
8223
8502
  if (activeBalloonEl === balloonEl) {
8224
8503
  if (activeBalloonTimer) {
8225
8504
  clearTimeout(activeBalloonTimer);
@@ -8484,6 +8763,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8484
8763
  const doc = target.ownerDocument ?? document;
8485
8764
  const root = doc.createElement("div");
8486
8765
  root.className = "dtm-daw dtm-player";
8766
+ const playerBalloons = [];
8487
8767
  const head = doc.createElement("div");
8488
8768
  head.className = "dtm-player-head";
8489
8769
  const playBtn = doc.createElement("button");
@@ -8829,7 +9109,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8829
9109
  balloon.className = "dtm-player-balloon";
8830
9110
  const modelKey = lt.model.toLowerCase();
8831
9111
  balloon.textContent = LYRIC_MODEL_LABELS[modelKey] ?? lt.model;
8832
- em.appendChild(balloon);
9112
+ balloonAnchors.set(balloon, em);
9113
+ doc.body.appendChild(balloon);
9114
+ playerBalloons.push(balloon);
9115
+ ensureBalloonListeners();
8833
9116
  em.addEventListener("mouseenter", () => {
8834
9117
  showBalloon(balloon);
8835
9118
  });
@@ -9290,7 +9573,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9290
9573
  delaySend: (lt.delay ?? 0) / 100,
9291
9574
  gender: (lt.gender ?? 50) / 100,
9292
9575
  breathiness: (lt.breathiness ?? 50) / 100,
9293
- octaveDouble: lt.octaveDouble,
9576
+ octaveUnison: lt.octaveUnison,
9294
9577
  notes
9295
9578
  };
9296
9579
  });
@@ -9426,9 +9709,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9426
9709
  }
9427
9710
  for (const t of blinkTimers) clearTimeout(t);
9428
9711
  clearJumpTimers();
9429
- if (activeBalloonEl && root.contains(activeBalloonEl)) {
9712
+ if (activeBalloonEl && playerBalloons.includes(activeBalloonEl)) {
9430
9713
  hideActiveBalloon();
9431
9714
  }
9715
+ for (const balloon of playerBalloons) balloon.remove();
9432
9716
  root.remove();
9433
9717
  consentOverlayEl?.remove();
9434
9718
  closeInfoModal();
@@ -9630,20 +9914,23 @@ var adjustZone = async (ctx, fontName, zone) => {
9630
9914
  const newLength = attackLength + loopLengthFrame * repeatCount + releaseLength;
9631
9915
  let totalPeak = 0;
9632
9916
  let loopPeak = 0;
9633
- if (oldBuf.numberOfChannels > 0) {
9634
- const ch0 = oldBuf.getChannelData(0);
9635
- for (let i2 = 0; i2 < ch0.length; i2++) {
9636
- const abs = Math.abs(ch0[i2]);
9917
+ for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9918
+ const chData = oldBuf.getChannelData(ch);
9919
+ for (let i2 = 0; i2 < chData.length; i2++) {
9920
+ const abs = Math.abs(chData[i2]);
9637
9921
  if (abs > totalPeak) totalPeak = abs;
9638
9922
  if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9639
9923
  if (abs > loopPeak) loopPeak = abs;
9640
9924
  }
9641
9925
  }
9642
9926
  }
9927
+ const decay = isDecayInstrument(fontName);
9928
+ const targetRatio = decay ? 0.4 : 0.75;
9929
+ const maxMultiplier = decay ? 6 : 20;
9643
9930
  let gainMultiplier = 1;
9644
- if (!isDecayInstrument(fontName) && loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9645
- gainMultiplier = totalPeak * 0.75 / loopPeak;
9646
- if (gainMultiplier > 20) gainMultiplier = 20;
9931
+ if (loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9932
+ gainMultiplier = totalPeak * targetRatio / loopPeak;
9933
+ if (gainMultiplier > maxMultiplier) gainMultiplier = maxMultiplier;
9647
9934
  }
9648
9935
  try {
9649
9936
  const newBuf = ctx.createBuffer(
@@ -10975,14 +11262,14 @@ var DELAY_DIVISIONS = [
10975
11262
  { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10976
11263
  { value: "16", label: "16\u5206", beats: 0.25 }
10977
11264
  ];
10978
- var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
11265
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10979
11266
  var divisionToSeconds = (division, bpm) => {
10980
11267
  const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10981
11268
  const safeBpm = bpm > 0 ? bpm : 120;
10982
11269
  return 60 / safeBpm * beats;
10983
11270
  };
10984
11271
  var DELAY_MAX_WET = 0.45;
10985
- var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
11272
+ var delayAmountToGain = (amount) => clamp4(amount, 0, 100) / 100 * DELAY_MAX_WET;
10986
11273
  var FEEDBACK_GAIN = 0.3;
10987
11274
  var MAX_DELAY_SEC = 2;
10988
11275
  var createDelayBus = (ctx, destination, options = {}) => {
@@ -13057,38 +13344,6 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
13057
13344
  return chordNotes / notes.length >= threshold;
13058
13345
  };
13059
13346
 
13060
- // src/reverb.ts
13061
- var DEFAULT_REVERB_DECAY_SEC = 2.2;
13062
- var MIN_REVERB_DECAY_SEC = 0.3;
13063
- var MAX_REVERB_DECAY_SEC = 4;
13064
- var IMPULSE_DECAY_CURVE = 2.5;
13065
- var DEFAULT_REVERB_PREDELAY_MS = 0;
13066
- var MIN_REVERB_PREDELAY_MS = 0;
13067
- var MAX_REVERB_PREDELAY_MS = 150;
13068
- var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
13069
- const rate = ctx.sampleRate;
13070
- const length = Math.max(
13071
- 1,
13072
- Math.floor(
13073
- rate * Math.max(
13074
- MIN_REVERB_DECAY_SEC,
13075
- Math.min(MAX_REVERB_DECAY_SEC, decaySec)
13076
- )
13077
- )
13078
- );
13079
- const impulse = ctx.createBuffer(2, length, rate);
13080
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
13081
- const data = impulse.getChannelData(ch);
13082
- for (let i2 = 0; i2 < length; i2++) {
13083
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
13084
- data[i2] = (Math.random() * 2 - 1) * envelope;
13085
- }
13086
- }
13087
- return impulse;
13088
- };
13089
- var REVERB_MAX_WET = 0.6;
13090
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
13091
-
13092
13347
  // src/daw.ts
13093
13348
  var CHORD_INFO_HTML2 = `
13094
13349
  <div class="dtm-modal-body-content">
@@ -13130,14 +13385,22 @@ var VIBRATO_INFO_HTML = `
13130
13385
  <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>
13131
13386
  </div>
13132
13387
  `;
13133
- var OCTAVE_DOUBLE_INFO_HTML = `
13388
+ var OCTAVE_UNISON_TOKEN = {
13389
+ none: "",
13390
+ down: "w1",
13391
+ up: "w2",
13392
+ both: "w3"
13393
+ };
13394
+ var OCTAVE_UNISON_INFO_HTML = `
13134
13395
  <div class="dtm-modal-body-content">
13135
13396
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13136
- <p>ON\u306B\u3059\u308B\u3068\u3001\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5404\u97F3\u7BC0\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\uFF08\u63A7\u3048\u3081\u306A\u97F3\u91CF\uFF09\u3067\u540C\u6642\u306B\u91CD\u306D\u3066\u767A\u97F3\u3057\u307E\u3059\u3002\u58F0\u306B\u539A\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u300C\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EA\u30F3\u30B0\u300D\u3068\u3044\u3046\u5B9A\u756A\u306E\u30DC\u30FC\u30AB\u30EB\u52A0\u5DE5\u3067\u3059\u3002</p>
13397
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5404\u97F3\u7BC0\u306B\u3001\u3082\u30461\u58F0\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A/\u4E0B/\u4E21\u65B9\uFF08\u63A7\u3048\u3081\u306A\u97F3\u91CF\uFF09\u3067\u540C\u6642\u306B\u91CD\u306D\u3066\u767A\u97F3\u3057\u307E\u3059\u3002\u58F0\u306B\u539A\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u300C\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\uFF08\u30C0\u30D6\u30EA\u30F3\u30B0\uFF09\u300D\u3068\u3044\u3046\u5B9A\u756A\u306E\u30DC\u30FC\u30AB\u30EB\u52A0\u5DE5\u3067\u3059\u3002</p>
13398
+ <h4>\u4E0B\u30FB\u4E0A\u30FB\u4E21\u65B9\u306E\u9055\u3044</h4>
13399
+ <p>\u300C\u4E0B\u300D\u306F\u58F0\u306B\u91CD\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u5B9A\u756A\u306E\u4F7F\u3044\u65B9\uFF08\u30ED\u30C3\u30AF/EDM\u306E\u30D0\u30C3\u30AD\u30F3\u30B0\u7B49\uFF09\u3002\u300C\u4E0A\u300D\u306F\u592A\u3055\u3067\u306F\u306A\u304F\u714C\u3073\u3084\u304B\u3055\u30FB\u53EF\u6190\u3055\u3092\u8DB3\u3059\u4F7F\u3044\u65B9\uFF08\u30A2\u30CB\u30BD\u30F3/\u30CF\u30E2\u30EA\u7B49\uFF09\u3002\u300C\u4E21\u65B9\u300D\u306F\u4E0A\u4E0B\u540C\u6642\u306B\u91CD\u306D\u308B\u7279\u6B8A\u306A\u52B9\u679C\u3067\u3001\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u6027\u3092\u5F37\u304F\u51FA\u3057\u305F\u3044\u3068\u304D\u306B\u3002</p>
13137
13400
  <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13138
13401
  <p>\u30B5\u30D3\u306E\u6C7A\u3081\u306E\u30D5\u30EC\u30FC\u30BA\u3001\u30ED\u30DC\u30C3\u30C8\u7684/\u30C0\u30FC\u30AF\u306A\u8CEA\u611F\u306E\u6F14\u51FA\u3001\u30E6\u30CB\u30BE\u30F3\u30CF\u30FC\u30E2\u30CB\u30FC\u306A\u3069\u3067\u3088\u304F\u4F7F\u308F\u308C\u307E\u3059\u3002\u66F2\u5168\u4F53\u306B\u639B\u3051\u3063\u3071\u306A\u3057\u306B\u3059\u308B\u3068\u5E38\u306B\u304F\u3069\u3044\u5370\u8C61\u306B\u306A\u308A\u3084\u3059\u3044\u306E\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u4F7F\u3044\u305F\u3044\u7B87\u6240\u3060\u3051\u306B\u7D5E\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
13139
13402
  <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13140
- <p>\u91CD\u306D\u308B\u58F0\u306B\u3082\u540C\u3058\u30D3\u30D6\u30E9\u30FC\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u8A2D\u5B9A\u304C\u305D\u306E\u307E\u307E\u9069\u7528\u3055\u308C\u307E\u3059\u3002\u97F3\u91CF\u6BD4\u30FB\u97F3\u7A0B\u5DEE\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u56FA\u5B9A\uFF09\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\u3002\u5408\u6210\u304C\u5358\u7D14\u306B2\u500D\u8D70\u308B\u305F\u3081\u3001\u97F3\u6570\u306E\u591A\u3044\u30C8\u30E9\u30C3\u30AF\u3067\u306F\u3084\u3084\u91CD\u304F\u306A\u308A\u307E\u3059\u3002</p>
13403
+ <p>\u91CD\u306D\u308B\u58F0\u306B\u3082\u540C\u3058\u30D3\u30D6\u30E9\u30FC\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u8A2D\u5B9A\u304C\u305D\u306E\u307E\u307E\u9069\u7528\u3055\u308C\u307E\u3059\u3002\u97F3\u91CF\u6BD4\u30FB\u97F3\u7A0B\u5DEE\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u56FA\u5B9A\uFF09\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\u3002\u300C\u4E21\u65B9\u300D\u306F\u5408\u6210\u304C3\u500D\u8D70\u308B\u305F\u3081\u3001\u97F3\u6570\u306E\u591A\u3044\u30C8\u30E9\u30C3\u30AF\u3067\u306F\u3084\u3084\u91CD\u304F\u306A\u308A\u307E\u3059\u3002</p>
13141
13404
  </div>
13142
13405
  `;
13143
13406
  var GENDER_INFO_HTML = `
@@ -13252,6 +13515,16 @@ var TRACK_WIDTH_INFO_HTML = `
13252
13515
  <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>
13253
13516
  </div>
13254
13517
  `;
13518
+ var TRACK_REVERBSEND_INFO_HTML = `
13519
+ <div class="dtm-modal-body-content">
13520
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13521
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u3092\u3001\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\uFF08[reverb]\u30D1\u30CD\u30EB\u306EDecay/Pre Delay/Mix\uFF09\u3078\u3069\u308C\u3060\u3051\u9001\u308B\u304B\u3092 0-100% \u3067\u6C7A\u3081\u307E\u3059\u30020\u306A\u3089\u9001\u3089\u306A\u3044\uFF1D\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u30EA\u30D0\u30FC\u30D6\u304C\u639B\u304B\u308A\u307E\u305B\u3093\u3002</p>
13522
+ <h4>\u300C\u58F0\u3060\u3051\u30EA\u30D0\u30FC\u30D6\u300D\u3092\u4F5C\u308B\u306B\u306F</h4>
13523
+ <p>\u30EA\u30D0\u30FC\u30D6\u306F\u5168\u30C8\u30E9\u30C3\u30AF\u3078\u4E00\u5F8B\u3067\u639B\u304B\u308B\u308F\u3051\u3067\u306F\u306A\u304F\u3001\u3053\u306E\u9001\u308A\u91CF\u3067\u500B\u5225\u306B\u6C7A\u307E\u308A\u307E\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u306E\u30C8\u30E9\u30C3\u30AF\u3060\u3051\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3001\u4ED6\u306E\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306F0\u306E\u307E\u307E\u306B\u3059\u308C\u3070\u3001\u58F0\u3060\u3051\u306B\u30EA\u30D0\u30FC\u30D6\u3092\u639B\u3051\u3089\u308C\u307E\u3059\u3002\u4F4E\u57DF\u306E\u697D\u5668\uFF08\u30D9\u30FC\u30B9\u30FB\u30AD\u30C3\u30AF\u7B49\uFF09\u306F\u9001\u3089\u306A\u3044\u3001\u30DC\u30FC\u30AB\u30EB\u3084\u30D1\u30C3\u30C9\u306F\u591A\u3081\u306B\u9001\u308B\u3001\u3068\u3044\u3046\u306E\u304C\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3067\u3059\u3002</p>
13524
+ <h4>[reverb]\u30D1\u30CD\u30EB\u306EMix\u3068\u306E\u95A2\u4FC2</h4>
13525
+ <p>Mix\u306F\u300C\u9001\u3089\u308C\u3066\u304D\u305F\u97F3\u3092\u3069\u308C\u3060\u3051\u8FD4\u3059\u304B\u300D\u3068\u3044\u3046\u5168\u4F53\u306E\u8FD4\u308A\u91CF\u3067\u3059\u3002\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u304C0\u306A\u3089\u3001Mix\u3092\u4E0A\u3052\u3066\u3082\u4F55\u3082\u8FD4\u3063\u3066\u304D\u307E\u305B\u3093\u3002\u307E\u305A\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u6C7A\u3081\u3066\u304B\u3089\u3001Mix\u3067\u5168\u4F53\u306E\u639B\u304B\u308A\u5177\u5408\u3092\u5FAE\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
13526
+ </div>
13527
+ `;
13255
13528
  var AUTO_MASTER_INFO_HTML = `
13256
13529
  <div class="dtm-modal-body-content">
13257
13530
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
@@ -13506,7 +13779,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13506
13779
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13507
13780
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13508
13781
  };
13509
- var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
13782
+ var clamp5 = (v, min, max) => Math.min(Math.max(v, min), max);
13510
13783
  var normalizeInstrumentName = (name) => {
13511
13784
  if (!name) return "";
13512
13785
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13595,6 +13868,8 @@ var mountDAW = (target, options = {}) => {
13595
13868
  refs.drumFontSelect.value = currentDrumFont;
13596
13869
  let currentInstrument = "";
13597
13870
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
13871
+ let trackFxAdvancedOpen = false;
13872
+ let lyricAdvancedOpen = false;
13598
13873
  let activeToolMode = "pen";
13599
13874
  let currentInsertLength = 48;
13600
13875
  let snapGridSteps = 12;
@@ -13672,7 +13947,7 @@ var mountDAW = (target, options = {}) => {
13672
13947
  vocalDelay: t.vocalDelay,
13673
13948
  vocalGender: t.vocalGender,
13674
13949
  vocalBreathiness: t.vocalBreathiness,
13675
- vocalOctaveDouble: t.vocalOctaveDouble
13950
+ vocalOctaveUnison: t.vocalOctaveUnison
13676
13951
  };
13677
13952
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13678
13953
  lyricsDebounceTimer = setTimeout(() => {
@@ -13737,10 +14012,11 @@ var mountDAW = (target, options = {}) => {
13737
14012
  vocalDelay: 0,
13738
14013
  vocalGender: 50,
13739
14014
  vocalBreathiness: 50,
13740
- vocalOctaveDouble: false,
14015
+ vocalOctaveUnison: "none",
13741
14016
  trackInstrument: "",
13742
14017
  trackCompression: 0,
13743
14018
  trackWidth: 100,
14019
+ trackReverbSend: 0,
13744
14020
  trackEqLow: 0,
13745
14021
  trackEqMid: 0,
13746
14022
  trackEqHigh: 0
@@ -13767,7 +14043,7 @@ var mountDAW = (target, options = {}) => {
13767
14043
  delay: t.vocalDelay,
13768
14044
  gender: t.vocalGender,
13769
14045
  breathiness: t.vocalBreathiness,
13770
- octaveDouble: t.vocalOctaveDouble,
14046
+ octaveUnison: t.vocalOctaveUnison,
13771
14047
  syllables
13772
14048
  });
13773
14049
  });
@@ -13890,7 +14166,7 @@ var mountDAW = (target, options = {}) => {
13890
14166
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13891
14167
  const ratio = currentOffsetX / maxOffsetX;
13892
14168
  refs.hScrollThumb.style.width = `${thumbW}px`;
13893
- refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
14169
+ refs.hScrollThumb.style.left = `${clamp5(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13894
14170
  }
13895
14171
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13896
14172
  const sbH = refs.vScroll.clientHeight;
@@ -13960,9 +14236,9 @@ var mountDAW = (target, options = {}) => {
13960
14236
  if (maxOffsetX <= 0) return;
13961
14237
  const rect = refs.hScroll.getBoundingClientRect();
13962
14238
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13963
- const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
14239
+ const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13964
14240
  const ratio = x2 / (rect.width - thumbW);
13965
- currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
14241
+ currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
13966
14242
  setDrawOffset(currentOffsetX, currentOffsetY);
13967
14243
  redrawAll();
13968
14244
  };
@@ -13971,9 +14247,9 @@ var mountDAW = (target, options = {}) => {
13971
14247
  if (maxOffset <= 0) return;
13972
14248
  const rect = refs.vScroll.getBoundingClientRect();
13973
14249
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13974
- const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
14250
+ const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13975
14251
  const ratio = y / (rect.height - thumbH);
13976
- currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
14252
+ currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
13977
14253
  setDrawOffset(currentOffsetX, currentOffsetY);
13978
14254
  redrawAll();
13979
14255
  };
@@ -14020,8 +14296,8 @@ var mountDAW = (target, options = {}) => {
14020
14296
  if (dx !== 0 || dy !== 0) {
14021
14297
  const maxOffsetX = getMaxOffsetX();
14022
14298
  const maxOffsetY = getMaxOffsetY();
14023
- currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
14024
- currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
14299
+ currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
14300
+ currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
14025
14301
  setDrawOffset(currentOffsetX, currentOffsetY);
14026
14302
  onPointerMove(lastMoveEvent);
14027
14303
  }
@@ -14411,12 +14687,12 @@ var mountDAW = (target, options = {}) => {
14411
14687
  "wheel",
14412
14688
  (event) => {
14413
14689
  event.preventDefault();
14414
- currentOffsetY = clamp4(
14690
+ currentOffsetY = clamp5(
14415
14691
  currentOffsetY + event.deltaY,
14416
14692
  0,
14417
14693
  getMaxOffsetY()
14418
14694
  );
14419
- currentOffsetX = clamp4(
14695
+ currentOffsetX = clamp5(
14420
14696
  currentOffsetX + event.deltaX,
14421
14697
  0,
14422
14698
  getMaxOffsetX()
@@ -14455,7 +14731,7 @@ var mountDAW = (target, options = {}) => {
14455
14731
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14456
14732
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14457
14733
  refs.zoomXLabel.textContent = `${zoomX}%`;
14458
- currentOffsetX = clamp4(
14734
+ currentOffsetX = clamp5(
14459
14735
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14460
14736
  0,
14461
14737
  getMaxOffsetX()
@@ -14468,7 +14744,7 @@ var mountDAW = (target, options = {}) => {
14468
14744
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14469
14745
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14470
14746
  refs.zoomYLabel.textContent = `${zoomY}%`;
14471
- currentOffsetY = clamp4(
14747
+ currentOffsetY = clamp5(
14472
14748
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14473
14749
  0,
14474
14750
  getMaxOffsetY()
@@ -14518,7 +14794,7 @@ var mountDAW = (target, options = {}) => {
14518
14794
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14519
14795
  if (currentPlayStep > threshold) {
14520
14796
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14521
- currentOffsetX = clamp4(
14797
+ currentOffsetX = clamp5(
14522
14798
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14523
14799
  0,
14524
14800
  getMaxOffsetX()
@@ -14577,7 +14853,7 @@ var mountDAW = (target, options = {}) => {
14577
14853
  delaySend: (lt.delay ?? 0) / 100,
14578
14854
  gender: (lt.gender ?? 50) / 100,
14579
14855
  breathiness: (lt.breathiness ?? 50) / 100,
14580
- octaveDouble: lt.octaveDouble,
14856
+ octaveUnison: lt.octaveUnison,
14581
14857
  notes
14582
14858
  };
14583
14859
  }) : [];
@@ -14605,7 +14881,7 @@ var mountDAW = (target, options = {}) => {
14605
14881
  }
14606
14882
  if (playbackState !== "paused") {
14607
14883
  const canvas = getGridCanvas();
14608
- currentOffsetX = clamp4(
14884
+ currentOffsetX = clamp5(
14609
14885
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14610
14886
  0,
14611
14887
  getMaxOffsetX()
@@ -14709,7 +14985,7 @@ var mountDAW = (target, options = {}) => {
14709
14985
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
14710
14986
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14711
14987
  </div>
14712
- <details class="dtm-advanced" data-dtm="track-fx-advanced">
14988
+ <details class="dtm-advanced" data-dtm="track-fx-advanced" ${trackFxAdvancedOpen ? "open" : ""}>
14713
14989
  <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14714
14990
  <div class="dtm-row">
14715
14991
  <span class="dtm-label">EQ\u4F4E\u57DF</span>
@@ -14739,7 +15015,18 @@ var mountDAW = (target, options = {}) => {
14739
15015
  <span class="dtm-label" data-dtm="track-width-label"></span>
14740
15016
  <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14741
15017
  </div>
15018
+ <div class="dtm-row">
15019
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
15020
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-reverb-send" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u306E\u9001\u308A\u91CF\uFF080=\u639B\u304B\u3089\u306A\u3044\uFF09">
15021
+ <span class="dtm-label" data-dtm="track-reverb-send-label"></span>
15022
+ <button class="dtm-infobtn" data-dtm="track-reverb-send-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15023
+ </div>
14742
15024
  </details>`;
15025
+ refs.trackBody.querySelector(
15026
+ '[data-dtm="track-fx-advanced"]'
15027
+ ).addEventListener("toggle", (e) => {
15028
+ trackFxAdvancedOpen = e.target.open;
15029
+ });
14743
15030
  const volInput = refs.trackBody.querySelector(
14744
15031
  '[data-dtm="track-vol"]'
14745
15032
  );
@@ -14843,6 +15130,28 @@ var mountDAW = (target, options = {}) => {
14843
15130
  trackWidthInfo.addEventListener("click", () => {
14844
15131
  showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14845
15132
  });
15133
+ const trackReverbSendInput = refs.trackBody.querySelector(
15134
+ '[data-dtm="track-reverb-send"]'
15135
+ );
15136
+ const trackReverbSendLabel = refs.trackBody.querySelector(
15137
+ '[data-dtm="track-reverb-send-label"]'
15138
+ );
15139
+ const trackReverbSendInfo = refs.trackBody.querySelector(
15140
+ '[data-dtm="track-reverb-send-info"]'
15141
+ );
15142
+ trackReverbSendInput.value = String(active.trackReverbSend);
15143
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
15144
+ trackReverbSendInput.addEventListener("input", () => {
15145
+ active.trackReverbSend = Number.parseInt(trackReverbSendInput.value, 10);
15146
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
15147
+ options.onTrackReverbSendChange?.(
15148
+ active.config.id,
15149
+ active.trackReverbSend
15150
+ );
15151
+ });
15152
+ trackReverbSendInfo.addEventListener("click", () => {
15153
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", TRACK_REVERBSEND_INFO_HTML);
15154
+ });
14846
15155
  const instRow = document.createElement("div");
14847
15156
  instRow.className = "dtm-row";
14848
15157
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -14931,7 +15240,7 @@ var mountDAW = (target, options = {}) => {
14931
15240
  <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">
14932
15241
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
14933
15242
  </div>
14934
- <details class="dtm-advanced" data-dtm="lyric-advanced">
15243
+ <details class="dtm-advanced" data-dtm="lyric-advanced" ${lyricAdvancedOpen ? "open" : ""}>
14935
15244
  <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14936
15245
  <div class="dtm-row">
14937
15246
  <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
@@ -14979,15 +15288,24 @@ var mountDAW = (target, options = {}) => {
14979
15288
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14980
15289
  </div>
14981
15290
  <div class="dtm-row">
14982
- <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14983
- <input type="checkbox" data-dtm="lyric-octave-double" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB">\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB
14984
- </span>
14985
- <button class="dtm-infobtn" data-dtm="lyric-octave-double-info" title="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15291
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3</span>
15292
+ <select class="dtm-select dtm-grow" data-dtm="lyric-octave-unison" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\uFF08\u3082\u30461\u58F0\u3092\u4E0A/\u4E0B\u306B\u91CD\u306D\u308B\uFF09">
15293
+ <option value="none">\u306A\u3057</option>
15294
+ <option value="down">\u4E0B (-1oct)</option>
15295
+ <option value="up">\u4E0A (+1oct)</option>
15296
+ <option value="both">\u4E0A\u4E0B\u4E21\u65B9</option>
15297
+ </select>
15298
+ <button class="dtm-infobtn" data-dtm="lyric-octave-unison-info" title="\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14986
15299
  </div>
14987
15300
  </details>
14988
15301
  <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>
14989
15302
  </div>`;
14990
15303
  refs.trackBody.appendChild(lyricDiv);
15304
+ lyricDiv.querySelector(
15305
+ '[data-dtm="lyric-advanced"]'
15306
+ ).addEventListener("toggle", (e) => {
15307
+ lyricAdvancedOpen = e.target.open;
15308
+ });
14991
15309
  const lyricModelSel = lyricDiv.querySelector(
14992
15310
  '[data-dtm="lyric-model"]'
14993
15311
  );
@@ -15075,14 +15393,14 @@ var mountDAW = (target, options = {}) => {
15075
15393
  lyricVibratoInfo.addEventListener("click", () => {
15076
15394
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
15077
15395
  });
15078
- const lyricOctaveDouble = lyricDiv.querySelector(
15079
- '[data-dtm="lyric-octave-double"]'
15396
+ const lyricOctaveUnison = lyricDiv.querySelector(
15397
+ '[data-dtm="lyric-octave-unison"]'
15080
15398
  );
15081
- const lyricOctaveDoubleInfo = lyricDiv.querySelector(
15082
- '[data-dtm="lyric-octave-double-info"]'
15399
+ const lyricOctaveUnisonInfo = lyricDiv.querySelector(
15400
+ '[data-dtm="lyric-octave-unison-info"]'
15083
15401
  );
15084
- lyricOctaveDoubleInfo.addEventListener("click", () => {
15085
- showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u89E3\u8AAC", OCTAVE_DOUBLE_INFO_HTML);
15402
+ lyricOctaveUnisonInfo.addEventListener("click", () => {
15403
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\u89E3\u8AAC", OCTAVE_UNISON_INFO_HTML);
15086
15404
  });
15087
15405
  const lyricTerms = lyricDiv.querySelector(
15088
15406
  '[data-dtm="lyric-terms"]'
@@ -15154,7 +15472,7 @@ var mountDAW = (target, options = {}) => {
15154
15472
  lyricBreathiness.value = String(active.vocalBreathiness);
15155
15473
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15156
15474
  lyricVibrato.checked = active.vocalVibrato;
15157
- lyricOctaveDouble.checked = active.vocalOctaveDouble;
15475
+ lyricOctaveUnison.value = active.vocalOctaveUnison;
15158
15476
  const updateLyricCount = () => {
15159
15477
  const n = normalizeLyrics(lyricInput.value).length;
15160
15478
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -15276,8 +15594,8 @@ var mountDAW = (target, options = {}) => {
15276
15594
  active.vocalVibrato = lyricVibrato.checked;
15277
15595
  fireLyricsChange(active);
15278
15596
  });
15279
- lyricOctaveDouble.addEventListener("change", () => {
15280
- active.vocalOctaveDouble = lyricOctaveDouble.checked;
15597
+ lyricOctaveUnison.addEventListener("change", () => {
15598
+ active.vocalOctaveUnison = lyricOctaveUnison.value;
15281
15599
  fireLyricsChange(active);
15282
15600
  });
15283
15601
  lyricInput.addEventListener("input", () => {
@@ -15408,6 +15726,7 @@ var mountDAW = (target, options = {}) => {
15408
15726
  const trackInstrumentsForMeta = {};
15409
15727
  const trackCompressionForMeta = {};
15410
15728
  const trackWidthForMeta = {};
15729
+ const trackReverbSendForMeta = {};
15411
15730
  const trackEqLowForMeta = {};
15412
15731
  const trackEqMidForMeta = {};
15413
15732
  const trackEqHighForMeta = {};
@@ -15416,6 +15735,8 @@ var mountDAW = (target, options = {}) => {
15416
15735
  if (t.trackCompression !== 0)
15417
15736
  trackCompressionForMeta[i2] = t.trackCompression;
15418
15737
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15738
+ if (t.trackReverbSend !== 0)
15739
+ trackReverbSendForMeta[i2] = t.trackReverbSend;
15419
15740
  if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15420
15741
  if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15421
15742
  if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
@@ -15423,6 +15744,7 @@ var mountDAW = (target, options = {}) => {
15423
15744
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
15424
15745
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
15425
15746
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15747
+ const trackReverbSendMeta = Object.keys(trackReverbSendForMeta).length > 0 ? trackReverbSendForMeta : void 0;
15426
15748
  const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15427
15749
  const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15428
15750
  const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
@@ -15443,6 +15765,7 @@ var mountDAW = (target, options = {}) => {
15443
15765
  trackInstruments: trackInstMeta,
15444
15766
  trackCompression: trackCompMeta,
15445
15767
  trackWidth: trackWidthMeta,
15768
+ trackReverbSend: trackReverbSendMeta,
15446
15769
  trackEqLow: trackEqLowMeta,
15447
15770
  trackEqMid: trackEqMidMeta,
15448
15771
  trackEqHigh: trackEqHighMeta
@@ -15466,6 +15789,7 @@ var mountDAW = (target, options = {}) => {
15466
15789
  trackInstruments: trackInstMeta,
15467
15790
  trackCompression: trackCompMeta,
15468
15791
  trackWidth: trackWidthMeta,
15792
+ trackReverbSend: trackReverbSendMeta,
15469
15793
  trackEqLow: trackEqLowMeta,
15470
15794
  trackEqMid: trackEqMidMeta,
15471
15795
  trackEqHigh: trackEqHighMeta
@@ -15521,7 +15845,7 @@ var mountDAW = (target, options = {}) => {
15521
15845
  del: t.vocalDelay,
15522
15846
  gen: t.vocalGender,
15523
15847
  bre: t.vocalBreathiness,
15524
- dbl: t.vocalOctaveDouble
15848
+ uni: t.vocalOctaveUnison
15525
15849
  })).filter(
15526
15850
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
15527
15851
  ).map((x2) => {
@@ -15535,7 +15859,7 @@ var mountDAW = (target, options = {}) => {
15535
15859
  x2.del === 0 ? "" : `e${x2.del}`,
15536
15860
  x2.gen === 50 ? "" : `g${x2.gen}`,
15537
15861
  x2.bre === 50 ? "" : `h${x2.bre}`,
15538
- x2.dbl ? "w1" : ""
15862
+ OCTAVE_UNISON_TOKEN[x2.uni]
15539
15863
  ].filter((s) => s.length > 0).join(" ");
15540
15864
  const head = params ? `${x2.model} ${params}` : x2.model;
15541
15865
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15604,7 +15928,7 @@ var mountDAW = (target, options = {}) => {
15604
15928
  const canvas = getGridCanvas();
15605
15929
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15606
15930
  const logicalY = yIndex * renderConfig.keyHeight;
15607
- currentOffsetY = clamp4(
15931
+ currentOffsetY = clamp5(
15608
15932
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15609
15933
  0,
15610
15934
  getMaxOffsetY()
@@ -15735,6 +16059,11 @@ var mountDAW = (target, options = {}) => {
15735
16059
  t.trackWidth = width;
15736
16060
  options.onTrackWidthChange?.(t.config.id, width);
15737
16061
  }
16062
+ const reverbSend = meta.trackReverbSend?.[i2] ?? 0;
16063
+ if (t.trackReverbSend !== reverbSend) {
16064
+ t.trackReverbSend = reverbSend;
16065
+ options.onTrackReverbSendChange?.(t.config.id, reverbSend);
16066
+ }
15738
16067
  const eqLow = meta.trackEqLow?.[i2] ?? 0;
15739
16068
  if (t.trackEqLow !== eqLow) {
15740
16069
  t.trackEqLow = eqLow;
@@ -15772,7 +16101,7 @@ var mountDAW = (target, options = {}) => {
15772
16101
  active.vocalDelay = 0;
15773
16102
  active.vocalGender = 50;
15774
16103
  active.vocalBreathiness = 50;
15775
- active.vocalOctaveDouble = false;
16104
+ active.vocalOctaveUnison = "none";
15776
16105
  } else {
15777
16106
  for (const t of trackStates) {
15778
16107
  t.lyrics = "";
@@ -15786,7 +16115,7 @@ var mountDAW = (target, options = {}) => {
15786
16115
  t.vocalDelay = 0;
15787
16116
  t.vocalGender = 50;
15788
16117
  t.vocalBreathiness = 50;
15789
- t.vocalOctaveDouble = false;
16118
+ t.vocalOctaveUnison = "none";
15790
16119
  }
15791
16120
  }
15792
16121
  lyrics?.forEach((lt) => {
@@ -15804,7 +16133,7 @@ var mountDAW = (target, options = {}) => {
15804
16133
  t.vocalDelay = lt.delay ?? 0;
15805
16134
  t.vocalGender = lt.gender ?? 50;
15806
16135
  t.vocalBreathiness = lt.breathiness ?? 50;
15807
- t.vocalOctaveDouble = lt.octaveDouble ?? false;
16136
+ t.vocalOctaveUnison = lt.octaveUnison ?? "none";
15808
16137
  });
15809
16138
  for (const p of placements) {
15810
16139
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15888,7 +16217,7 @@ var mountDAW = (target, options = {}) => {
15888
16217
  active.vocalDelay = 0;
15889
16218
  active.vocalGender = 50;
15890
16219
  active.vocalBreathiness = 50;
15891
- active.vocalOctaveDouble = false;
16220
+ active.vocalOctaveUnison = "none";
15892
16221
  } else {
15893
16222
  clearAll();
15894
16223
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15904,7 +16233,7 @@ var mountDAW = (target, options = {}) => {
15904
16233
  t.vocalDelay = 0;
15905
16234
  t.vocalGender = 50;
15906
16235
  t.vocalBreathiness = 50;
15907
- t.vocalOctaveDouble = false;
16236
+ t.vocalOctaveUnison = "none";
15908
16237
  }
15909
16238
  }
15910
16239
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -16174,8 +16503,8 @@ var mountDAW = (target, options = {}) => {
16174
16503
  refs.reverbAmount.value = "20";
16175
16504
  refs.reverbAmountLabel.textContent = "20%";
16176
16505
  options.onReverbChange?.(20);
16177
- const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16178
- reverbDecay = clamp4(
16506
+ const bpmT = clamp5((bpm - 60) / (180 - 60), 0, 1);
16507
+ reverbDecay = clamp5(
16179
16508
  3 - bpmT * (3 - 0.9),
16180
16509
  MIN_REVERB_DECAY_SEC,
16181
16510
  MAX_REVERB_DECAY_SEC
@@ -16204,6 +16533,10 @@ var mountDAW = (target, options = {}) => {
16204
16533
  t.trackWidth = 115;
16205
16534
  options.onTrackCompressionChange?.(t.config.id, comp);
16206
16535
  options.onTrackWidthChange?.(t.config.id, 115);
16536
+ if (!t.lyricModel) {
16537
+ t.trackReverbSend = 15;
16538
+ options.onTrackReverbSendChange?.(t.config.id, 15);
16539
+ }
16207
16540
  if (t.lyricModel) {
16208
16541
  t.vocalVibrato = true;
16209
16542
  t.vocalReverb = isMainVocal ? 15 : 30;
@@ -17102,7 +17435,7 @@ var mountDAW = (target, options = {}) => {
17102
17435
  currentPlayStep = step;
17103
17436
  playbackState = "paused";
17104
17437
  const canvas = getGridCanvas();
17105
- currentOffsetX = clamp4(
17438
+ currentOffsetX = clamp5(
17106
17439
  step * renderConfig.stepWidth - canvas.width * 0.5,
17107
17440
  0,
17108
17441
  getMaxOffsetX()
@@ -17148,11 +17481,11 @@ var mountDAW = (target, options = {}) => {
17148
17481
  getViewState,
17149
17482
  setViewState: (state) => {
17150
17483
  if (typeof state.zoomX === "number") {
17151
- zoomX = clamp4(state.zoomX, 25, 200);
17484
+ zoomX = clamp5(state.zoomX, 25, 200);
17152
17485
  applyZoomX();
17153
17486
  }
17154
17487
  if (typeof state.zoomY === "number") {
17155
- zoomY = clamp4(state.zoomY, 50, 200);
17488
+ zoomY = clamp5(state.zoomY, 50, 200);
17156
17489
  applyZoomY();
17157
17490
  }
17158
17491
  if (typeof state.decomposeChord === "boolean") {
@@ -17174,24 +17507,24 @@ var mountDAW = (target, options = {}) => {
17174
17507
  forcePauseAt,
17175
17508
  setLoading,
17176
17509
  setMasterVolume: (volume) => {
17177
- masterVolume = clamp4(volume, 0, 100);
17510
+ masterVolume = clamp5(volume, 0, 100);
17178
17511
  refs.masterVolume.value = String(masterVolume);
17179
17512
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17180
17513
  options.singingVoices?.setVolume(masterVolume / 100);
17181
17514
  },
17182
17515
  setVolume: (volume) => {
17183
- masterVolume = clamp4(volume, 0, 100);
17516
+ masterVolume = clamp5(volume, 0, 100);
17184
17517
  refs.masterVolume.value = String(masterVolume);
17185
17518
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17186
17519
  options.singingVoices?.setVolume(masterVolume / 100);
17187
17520
  },
17188
17521
  setDrumVolume: (volume) => {
17189
- drumVolume = clamp4(volume, 0, 100);
17522
+ drumVolume = clamp5(volume, 0, 100);
17190
17523
  refs.drumVolume.value = String(drumVolume);
17191
17524
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
17192
17525
  },
17193
17526
  setReverbAmount: (amount) => {
17194
- reverbAmount = clamp4(amount, 0, 100);
17527
+ reverbAmount = clamp5(amount, 0, 100);
17195
17528
  refs.reverbAmount.value = String(reverbAmount);
17196
17529
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
17197
17530
  options.onReverbChange?.(reverbAmount);
@@ -17215,7 +17548,7 @@ var mountDAW = (target, options = {}) => {
17215
17548
  options.onReverbPreDelayChange?.(reverbPreDelay);
17216
17549
  },
17217
17550
  setDelayAmount: (amount) => {
17218
- delayAmount = clamp4(amount, 0, 100);
17551
+ delayAmount = clamp5(amount, 0, 100);
17219
17552
  refs.delayAmount.value = String(delayAmount);
17220
17553
  refs.delayAmountLabel.textContent = `${delayAmount}%`;
17221
17554
  options.onDelayChange?.(delayAmount);
@@ -17264,7 +17597,7 @@ var mountDAW = (target, options = {}) => {
17264
17597
  t.vocalDelay = data.vocalDelay ?? 0;
17265
17598
  t.vocalGender = data.vocalGender ?? 50;
17266
17599
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17267
- t.vocalOctaveDouble = data.vocalOctaveDouble ?? false;
17600
+ t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
17268
17601
  },
17269
17602
  applyTrackInstrument: (trackIndex, instrumentName) => {
17270
17603
  const t = trackStates[trackIndex];
@@ -17386,7 +17719,7 @@ var playSingingMML = async (mml, options = {}) => {
17386
17719
  delaySend: (lt.delay ?? 0) / 100,
17387
17720
  gender: (lt.gender ?? 50) / 100,
17388
17721
  breathiness: (lt.breathiness ?? 50) / 100,
17389
- octaveDouble: lt.octaveDouble,
17722
+ octaveUnison: lt.octaveUnison,
17390
17723
  notes
17391
17724
  };
17392
17725
  });
@@ -17994,137 +18327,6 @@ var isSupported = midiJsonParser.isSupported;
17994
18327
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17995
18328
  URL.revokeObjectURL(url);
17996
18329
 
17997
- // src/channel-strip.ts
17998
- var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17999
- var compressionParams = (amount) => {
18000
- const t = clamp5(amount, 0, 100) / 100;
18001
- return {
18002
- threshold: 0 + (-24 - 0) * t,
18003
- ratio: 1 + (12 - 1) * t,
18004
- knee: 0 + (6 - 0) * t,
18005
- attack: 0.02 + (3e-3 - 0.02) * t,
18006
- release: 0.25 + (0.15 - 0.25) * t
18007
- };
18008
- };
18009
- var EQ_LOW_FREQ = 200;
18010
- var EQ_MID_FREQ = 1e3;
18011
- var EQ_HIGH_FREQ = 5e3;
18012
- var EQ_MID_Q = 1;
18013
- var EQ_MAX_DB = 12;
18014
- var createChannelStrip = (ctx, destination, options = {}) => {
18015
- const input = ctx.createGain();
18016
- const eqLow = ctx.createBiquadFilter();
18017
- eqLow.type = "lowshelf";
18018
- eqLow.frequency.value = EQ_LOW_FREQ;
18019
- eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18020
- const eqMid = ctx.createBiquadFilter();
18021
- eqMid.type = "peaking";
18022
- eqMid.frequency.value = EQ_MID_FREQ;
18023
- eqMid.Q.value = EQ_MID_Q;
18024
- eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18025
- const eqHigh = ctx.createBiquadFilter();
18026
- eqHigh.type = "highshelf";
18027
- eqHigh.frequency.value = EQ_HIGH_FREQ;
18028
- eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18029
- input.connect(eqLow);
18030
- eqLow.connect(eqMid);
18031
- eqMid.connect(eqHigh);
18032
- const compressor = ctx.createDynamicsCompressor();
18033
- const applyCompression = (amount) => {
18034
- const p = compressionParams(amount);
18035
- const now = ctx.currentTime;
18036
- compressor.threshold.setValueAtTime(p.threshold, now);
18037
- compressor.ratio.setValueAtTime(p.ratio, now);
18038
- compressor.knee.setValueAtTime(p.knee, now);
18039
- compressor.attack.setValueAtTime(p.attack, now);
18040
- compressor.release.setValueAtTime(p.release, now);
18041
- };
18042
- applyCompression(options.compression ?? 0);
18043
- const splitter = ctx.createChannelSplitter(2);
18044
- const mid = ctx.createGain();
18045
- mid.gain.value = 0.5;
18046
- splitter.connect(mid, 0);
18047
- splitter.connect(mid, 1);
18048
- const sideSum = ctx.createGain();
18049
- sideSum.gain.value = 1;
18050
- const sideL = ctx.createGain();
18051
- sideL.gain.value = 0.5;
18052
- const sideR = ctx.createGain();
18053
- sideR.gain.value = -0.5;
18054
- splitter.connect(sideL, 0);
18055
- splitter.connect(sideR, 1);
18056
- sideL.connect(sideSum);
18057
- sideR.connect(sideSum);
18058
- const widthGain = ctx.createGain();
18059
- sideSum.connect(widthGain);
18060
- const widthInv = ctx.createGain();
18061
- widthInv.gain.value = -1;
18062
- widthGain.connect(widthInv);
18063
- const outL = ctx.createGain();
18064
- mid.connect(outL);
18065
- widthGain.connect(outL);
18066
- const outR = ctx.createGain();
18067
- mid.connect(outR);
18068
- widthInv.connect(outR);
18069
- const merger = ctx.createChannelMerger(2);
18070
- outL.connect(merger, 0, 0);
18071
- outR.connect(merger, 0, 1);
18072
- const setWidth = (width) => {
18073
- widthGain.gain.setTargetAtTime(
18074
- clamp5(width, 0, 200) / 100,
18075
- ctx.currentTime,
18076
- 0.02
18077
- );
18078
- };
18079
- setWidth(options.width ?? 100);
18080
- eqHigh.connect(compressor);
18081
- compressor.connect(splitter);
18082
- merger.connect(destination);
18083
- return {
18084
- input,
18085
- setEqLow: (db) => {
18086
- eqLow.gain.setTargetAtTime(
18087
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18088
- ctx.currentTime,
18089
- 0.02
18090
- );
18091
- },
18092
- setEqMid: (db) => {
18093
- eqMid.gain.setTargetAtTime(
18094
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18095
- ctx.currentTime,
18096
- 0.02
18097
- );
18098
- },
18099
- setEqHigh: (db) => {
18100
- eqHigh.gain.setTargetAtTime(
18101
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18102
- ctx.currentTime,
18103
- 0.02
18104
- );
18105
- },
18106
- setCompression: applyCompression,
18107
- setWidth,
18108
- dispose: () => {
18109
- input.disconnect();
18110
- eqLow.disconnect();
18111
- eqMid.disconnect();
18112
- eqHigh.disconnect();
18113
- compressor.disconnect();
18114
- splitter.disconnect();
18115
- mid.disconnect();
18116
- sideSum.disconnect();
18117
- sideL.disconnect();
18118
- sideR.disconnect();
18119
- widthGain.disconnect();
18120
- widthInv.disconnect();
18121
- outL.disconnect();
18122
- outR.disconnect();
18123
- merger.disconnect();
18124
- }
18125
- };
18126
- };
18127
-
18128
18330
  // src/clip-meter.ts
18129
18331
  var createClipMeter = (ctx, source, options = {}) => {
18130
18332
  const threshold = options.threshold ?? 0.98;
@@ -18343,7 +18545,6 @@ var createDtmStudio = async (options = {}) => {
18343
18545
  reverbConvolver.normalize = true;
18344
18546
  const reverbWetGain = audioCtx.createGain();
18345
18547
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
18346
- masterGain.connect(reverbPreDelay);
18347
18548
  reverbPreDelay.connect(reverbConvolver);
18348
18549
  reverbConvolver.connect(reverbWetGain);
18349
18550
  const setReverbAmount = (amount) => {
@@ -18409,7 +18610,9 @@ var createDtmStudio = async (options = {}) => {
18409
18610
  const getChannelStrip = (trackId) => {
18410
18611
  let strip = channelStrips.get(trackId);
18411
18612
  if (!strip) {
18412
- strip = createChannelStrip(audioCtx, masterGain);
18613
+ strip = createChannelStrip(audioCtx, masterGain, {
18614
+ reverbBus: reverbPreDelay
18615
+ });
18413
18616
  channelStrips.set(trackId, strip);
18414
18617
  }
18415
18618
  return strip;
@@ -18446,7 +18649,8 @@ var createDtmStudio = async (options = {}) => {
18446
18649
  voicebanks,
18447
18650
  worldlineScriptUrl: options.worldlineScriptUrl,
18448
18651
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
18449
- // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
18652
+ // Convolver 入力へ直接センドする(PreDelayは経由しない。チャンネルストリップの
18653
+ // reverbSend とは別経路の、音節単位でより細かく制御できるセンド)。
18450
18654
  reverbBus: reverbConvolver,
18451
18655
  // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18452
18656
  delayBus: delayBus.input,
@@ -18744,6 +18948,7 @@ var createDtmStudio = async (options = {}) => {
18744
18948
  onScheduleFade: scheduleFade,
18745
18949
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
18746
18950
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18951
+ onTrackReverbSendChange: (trackId, amount) => getChannelStrip(trackId).setReverbSend(amount),
18747
18952
  onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18748
18953
  onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18749
18954
  onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),