@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.mjs CHANGED
@@ -1335,6 +1335,149 @@ var getDrumPatternKeys = (name, dict) => {
1335
1335
  return Array.from(keys);
1336
1336
  };
1337
1337
 
1338
+ // src/channel-strip.ts
1339
+ var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1340
+ var compressionParams = (amount) => {
1341
+ const t = clamp(amount, 0, 100) / 100;
1342
+ return {
1343
+ threshold: 0 + (-24 - 0) * t,
1344
+ ratio: 1 + (12 - 1) * t,
1345
+ knee: 0 + (6 - 0) * t,
1346
+ attack: 0.02 + (3e-3 - 0.02) * t,
1347
+ release: 0.25 + (0.15 - 0.25) * t
1348
+ };
1349
+ };
1350
+ var EQ_LOW_FREQ = 200;
1351
+ var EQ_MID_FREQ = 1e3;
1352
+ var EQ_HIGH_FREQ = 5e3;
1353
+ var EQ_MID_Q = 1;
1354
+ var EQ_MAX_DB = 12;
1355
+ var createChannelStrip = (ctx, destination, options = {}) => {
1356
+ const input = ctx.createGain();
1357
+ const eqLow = ctx.createBiquadFilter();
1358
+ eqLow.type = "lowshelf";
1359
+ eqLow.frequency.value = EQ_LOW_FREQ;
1360
+ eqLow.gain.value = clamp(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1361
+ const eqMid = ctx.createBiquadFilter();
1362
+ eqMid.type = "peaking";
1363
+ eqMid.frequency.value = EQ_MID_FREQ;
1364
+ eqMid.Q.value = EQ_MID_Q;
1365
+ eqMid.gain.value = clamp(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1366
+ const eqHigh = ctx.createBiquadFilter();
1367
+ eqHigh.type = "highshelf";
1368
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
1369
+ eqHigh.gain.value = clamp(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1370
+ input.connect(eqLow);
1371
+ eqLow.connect(eqMid);
1372
+ eqMid.connect(eqHigh);
1373
+ const compressor = ctx.createDynamicsCompressor();
1374
+ const applyCompression = (amount) => {
1375
+ const p = compressionParams(amount);
1376
+ const now = ctx.currentTime;
1377
+ compressor.threshold.setValueAtTime(p.threshold, now);
1378
+ compressor.ratio.setValueAtTime(p.ratio, now);
1379
+ compressor.knee.setValueAtTime(p.knee, now);
1380
+ compressor.attack.setValueAtTime(p.attack, now);
1381
+ compressor.release.setValueAtTime(p.release, now);
1382
+ };
1383
+ applyCompression(options.compression ?? 0);
1384
+ const splitter = ctx.createChannelSplitter(2);
1385
+ const mid = ctx.createGain();
1386
+ mid.gain.value = 0.5;
1387
+ splitter.connect(mid, 0);
1388
+ splitter.connect(mid, 1);
1389
+ const sideSum = ctx.createGain();
1390
+ sideSum.gain.value = 1;
1391
+ const sideL = ctx.createGain();
1392
+ sideL.gain.value = 0.5;
1393
+ const sideR = ctx.createGain();
1394
+ sideR.gain.value = -0.5;
1395
+ splitter.connect(sideL, 0);
1396
+ splitter.connect(sideR, 1);
1397
+ sideL.connect(sideSum);
1398
+ sideR.connect(sideSum);
1399
+ const widthGain = ctx.createGain();
1400
+ sideSum.connect(widthGain);
1401
+ const widthInv = ctx.createGain();
1402
+ widthInv.gain.value = -1;
1403
+ widthGain.connect(widthInv);
1404
+ const outL = ctx.createGain();
1405
+ mid.connect(outL);
1406
+ widthGain.connect(outL);
1407
+ const outR = ctx.createGain();
1408
+ mid.connect(outR);
1409
+ widthInv.connect(outR);
1410
+ const merger = ctx.createChannelMerger(2);
1411
+ outL.connect(merger, 0, 0);
1412
+ outR.connect(merger, 0, 1);
1413
+ const setWidth = (width) => {
1414
+ widthGain.gain.setTargetAtTime(
1415
+ clamp(width, 0, 200) / 100,
1416
+ ctx.currentTime,
1417
+ 0.02
1418
+ );
1419
+ };
1420
+ setWidth(options.width ?? 100);
1421
+ eqHigh.connect(compressor);
1422
+ compressor.connect(splitter);
1423
+ merger.connect(destination);
1424
+ const reverbSendGain = ctx.createGain();
1425
+ reverbSendGain.gain.value = clamp(options.reverbSend ?? 0, 0, 100) / 100;
1426
+ merger.connect(reverbSendGain);
1427
+ if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
1428
+ return {
1429
+ input,
1430
+ setEqLow: (db) => {
1431
+ eqLow.gain.setTargetAtTime(
1432
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1433
+ ctx.currentTime,
1434
+ 0.02
1435
+ );
1436
+ },
1437
+ setEqMid: (db) => {
1438
+ eqMid.gain.setTargetAtTime(
1439
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1440
+ ctx.currentTime,
1441
+ 0.02
1442
+ );
1443
+ },
1444
+ setEqHigh: (db) => {
1445
+ eqHigh.gain.setTargetAtTime(
1446
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1447
+ ctx.currentTime,
1448
+ 0.02
1449
+ );
1450
+ },
1451
+ setCompression: applyCompression,
1452
+ setWidth,
1453
+ setReverbSend: (amount) => {
1454
+ reverbSendGain.gain.setTargetAtTime(
1455
+ clamp(amount, 0, 100) / 100,
1456
+ ctx.currentTime,
1457
+ 0.02
1458
+ );
1459
+ },
1460
+ dispose: () => {
1461
+ input.disconnect();
1462
+ eqLow.disconnect();
1463
+ eqMid.disconnect();
1464
+ eqHigh.disconnect();
1465
+ compressor.disconnect();
1466
+ splitter.disconnect();
1467
+ mid.disconnect();
1468
+ sideSum.disconnect();
1469
+ sideL.disconnect();
1470
+ sideR.disconnect();
1471
+ widthGain.disconnect();
1472
+ widthInv.disconnect();
1473
+ outL.disconnect();
1474
+ outR.disconnect();
1475
+ merger.disconnect();
1476
+ reverbSendGain.disconnect();
1477
+ }
1478
+ };
1479
+ };
1480
+
1338
1481
  // src/chords.ts
1339
1482
  var C3 = 48;
1340
1483
  var buildChordPlacements = (options) => {
@@ -2164,7 +2307,7 @@ var normalizeLyricLines = (lines) => {
2164
2307
  var LYRIC_LINE = /^@@(\d+)\s*(.*)$/;
2165
2308
  var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
2166
2309
  var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
2167
- var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2310
+ var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2168
2311
  var MAX_VOCAL_VOLUME = 400;
2169
2312
  var VOCAL_BOOST_DB_PER_PERCENT = 0.08;
2170
2313
  var vocalVolumeToGain = (v) => {
@@ -2189,7 +2332,7 @@ var parseLyrics = (mml) => {
2189
2332
  let delay = 0;
2190
2333
  let gender = 50;
2191
2334
  let breathiness = 50;
2192
- let octaveDouble = false;
2335
+ let octaveUnison = "none";
2193
2336
  const modelMatch = rest.match(
2194
2337
  /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2195
2338
  );
@@ -2198,7 +2341,7 @@ var parseLyrics = (mml) => {
2198
2341
  if (modelMatch) {
2199
2342
  model = modelMatch[1].toLowerCase();
2200
2343
  if (modelMatch[2]) {
2201
- volume = clamp(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2344
+ volume = clamp2(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2202
2345
  }
2203
2346
  metaTokens.push(modelMatch[0]);
2204
2347
  rest = rest.substring(modelMatch[0].length).trim();
@@ -2206,28 +2349,28 @@ var parseLyrics = (mml) => {
2206
2349
  while (true) {
2207
2350
  const vMatch = rest.match(/^v(\d+)/i);
2208
2351
  if (vMatch) {
2209
- volume = clamp(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2352
+ volume = clamp2(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2210
2353
  metaTokens.push(vMatch[0]);
2211
2354
  rest = rest.substring(vMatch[0].length).trim();
2212
2355
  continue;
2213
2356
  }
2214
2357
  const qMatch = rest.match(/^q(\d+)/i);
2215
2358
  if (qMatch) {
2216
- gate = clamp(Number.parseInt(qMatch[1], 10), 0, 100);
2359
+ gate = clamp2(Number.parseInt(qMatch[1], 10), 0, 100);
2217
2360
  metaTokens.push(qMatch[0]);
2218
2361
  rest = rest.substring(qMatch[0].length).trim();
2219
2362
  continue;
2220
2363
  }
2221
2364
  const pMatch = rest.match(/^p(\d+)/i);
2222
2365
  if (pMatch) {
2223
- pan = clamp(Number.parseInt(pMatch[1], 10), 0, 127);
2366
+ pan = clamp2(Number.parseInt(pMatch[1], 10), 0, 127);
2224
2367
  metaTokens.push(pMatch[0]);
2225
2368
  rest = rest.substring(pMatch[0].length).trim();
2226
2369
  continue;
2227
2370
  }
2228
2371
  const oMatch = rest.match(/^o(-?\d+)/i);
2229
2372
  if (oMatch) {
2230
- octave = clamp(Number.parseInt(oMatch[1], 10), -2, 2);
2373
+ octave = clamp2(Number.parseInt(oMatch[1], 10), -2, 2);
2231
2374
  metaTokens.push(oMatch[0]);
2232
2375
  rest = rest.substring(oMatch[0].length).trim();
2233
2376
  continue;
@@ -2241,35 +2384,35 @@ var parseLyrics = (mml) => {
2241
2384
  }
2242
2385
  const rMatch = rest.match(/^r(\d+)/i);
2243
2386
  if (rMatch) {
2244
- reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2387
+ reverb = clamp2(Number.parseInt(rMatch[1], 10), 0, 100);
2245
2388
  metaTokens.push(rMatch[0]);
2246
2389
  rest = rest.substring(rMatch[0].length).trim();
2247
2390
  continue;
2248
2391
  }
2249
2392
  const gMatch = rest.match(/^g(\d+)/i);
2250
2393
  if (gMatch) {
2251
- gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2394
+ gender = clamp2(Number.parseInt(gMatch[1], 10), 0, 100);
2252
2395
  metaTokens.push(gMatch[0]);
2253
2396
  rest = rest.substring(gMatch[0].length).trim();
2254
2397
  continue;
2255
2398
  }
2256
2399
  const hMatch = rest.match(/^h(\d+)/i);
2257
2400
  if (hMatch) {
2258
- breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2401
+ breathiness = clamp2(Number.parseInt(hMatch[1], 10), 0, 100);
2259
2402
  metaTokens.push(hMatch[0]);
2260
2403
  rest = rest.substring(hMatch[0].length).trim();
2261
2404
  continue;
2262
2405
  }
2263
2406
  const eMatch = rest.match(/^e(\d+)/i);
2264
2407
  if (eMatch) {
2265
- delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2408
+ delay = clamp2(Number.parseInt(eMatch[1], 10), 0, 100);
2266
2409
  metaTokens.push(eMatch[0]);
2267
2410
  rest = rest.substring(eMatch[0].length).trim();
2268
2411
  continue;
2269
2412
  }
2270
- const wMatch = rest.match(/^w([01])/i);
2413
+ const wMatch = rest.match(/^w([0-3])/i);
2271
2414
  if (wMatch) {
2272
- octaveDouble = wMatch[1] === "1";
2415
+ octaveUnison = ["none", "down", "up", "both"][Number.parseInt(wMatch[1], 10)];
2273
2416
  metaTokens.push(wMatch[0]);
2274
2417
  rest = rest.substring(wMatch[0].length).trim();
2275
2418
  continue;
@@ -2293,7 +2436,7 @@ var parseLyrics = (mml) => {
2293
2436
  delay,
2294
2437
  gender,
2295
2438
  breathiness,
2296
- octaveDouble,
2439
+ octaveUnison,
2297
2440
  syllables,
2298
2441
  metaText: metaTokens.join(" "),
2299
2442
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2890,8 +3033,19 @@ var createKoeVoice = async (ctx, destination, options) => {
2890
3033
  var PREWARM_NOTES = 3;
2891
3034
  var STREAM_LOOKAHEAD_SEC = 1.5;
2892
3035
  var STREAM_POLL_MS = 100;
2893
- var OCTAVE_DOUBLE_SEMITONES = -12;
2894
- var OCTAVE_DOUBLE_PEAK_SCALE = 0.6;
3036
+ var OCTAVE_UNISON_PEAK_SCALE = 0.6;
3037
+ var octaveUnisonOffsets = (mode) => {
3038
+ switch (mode) {
3039
+ case "down":
3040
+ return [-12];
3041
+ case "up":
3042
+ return [12];
3043
+ case "both":
3044
+ return [-12, 12];
3045
+ default:
3046
+ return [];
3047
+ }
3048
+ };
2895
3049
  var FALLBACK_MODEL = "klatt";
2896
3050
  var createSingingVoices = (ctx, destination, options = {}) => {
2897
3051
  const catalog = {};
@@ -2986,12 +3140,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2986
3140
  vibrato: track.vibrato,
2987
3141
  expr
2988
3142
  });
2989
- if (track.octaveDouble) {
3143
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
2990
3144
  tasks.push({
2991
3145
  model: m,
2992
3146
  note,
2993
3147
  prevVowel,
2994
- pitch: note.pitch + OCTAVE_DOUBLE_SEMITONES,
3148
+ pitch: note.pitch + offset,
2995
3149
  vibrato: track.vibrato,
2996
3150
  expr
2997
3151
  });
@@ -3106,11 +3260,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3106
3260
  }
3107
3261
  };
3108
3262
  dispatchNote(note.pitch, 1);
3109
- if (track.octaveDouble) {
3110
- dispatchNote(
3111
- note.pitch + OCTAVE_DOUBLE_SEMITONES,
3112
- OCTAVE_DOUBLE_PEAK_SCALE
3113
- );
3263
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3264
+ dispatchNote(note.pitch + offset, OCTAVE_UNISON_PEAK_SCALE);
3114
3265
  }
3115
3266
  if (!(model.renderToCache && model.scheduleCached)) {
3116
3267
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -3170,11 +3321,12 @@ var PITCH_MAP = {
3170
3321
  a: 9,
3171
3322
  b: 11
3172
3323
  };
3173
- var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3324
+ var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3174
3325
  var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3175
3326
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3176
3327
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3177
3328
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3329
+ var TRACK_REVERBSEND_DIRECTIVE = /#t(\d+)rev=(\d+)/gi;
3178
3330
  var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3179
3331
  var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3180
3332
  var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
@@ -3193,24 +3345,24 @@ var parseMmlMeta = (mml) => {
3193
3345
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3194
3346
  } else if (key === "reverb") {
3195
3347
  const rv = Number.parseInt(m[2], 10);
3196
- if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3348
+ if (!Number.isNaN(rv)) meta.reverb = clamp3(rv, 0, 100);
3197
3349
  } else if (key === "reverbdecay") {
3198
3350
  const rd = Number.parseInt(m[2], 10);
3199
- if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3351
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp3(rd, 3, 40);
3200
3352
  } else if (key === "reverbpredelay") {
3201
3353
  const rp = Number.parseInt(m[2], 10);
3202
- if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3354
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp3(rp, 0, 150);
3203
3355
  } else if (key === "delay") {
3204
3356
  const dv = Number.parseInt(m[2], 10);
3205
- if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3357
+ if (!Number.isNaN(dv)) meta.delay = clamp3(dv, 0, 100);
3206
3358
  } else if (key === "delaydiv") {
3207
3359
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3208
3360
  } else if (key === "fadein") {
3209
3361
  const fi = Number.parseInt(m[2], 10);
3210
- if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3362
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp3(fi, 0, 100);
3211
3363
  } else if (key === "fadeout") {
3212
3364
  const fo = Number.parseInt(m[2], 10);
3213
- if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3365
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp3(fo, 0, 100);
3214
3366
  } else if (key === "mode") {
3215
3367
  if (m[2] === "simple" || m[2] === "advanced") {
3216
3368
  meta.mode = m[2];
@@ -3227,7 +3379,7 @@ var parseMmlMeta = (mml) => {
3227
3379
  }
3228
3380
  for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3229
3381
  const idx = Number.parseInt(m[1], 10);
3230
- const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3382
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3231
3383
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3232
3384
  meta.trackCompression ??= {};
3233
3385
  meta.trackCompression[idx] = val;
@@ -3235,15 +3387,23 @@ var parseMmlMeta = (mml) => {
3235
3387
  }
3236
3388
  for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3237
3389
  const idx = Number.parseInt(m[1], 10);
3238
- const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3390
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 200);
3239
3391
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3240
3392
  meta.trackWidth ??= {};
3241
3393
  meta.trackWidth[idx] = val;
3242
3394
  }
3243
3395
  }
3396
+ for (const m of mml.matchAll(TRACK_REVERBSEND_DIRECTIVE)) {
3397
+ const idx = Number.parseInt(m[1], 10);
3398
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3399
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3400
+ meta.trackReverbSend ??= {};
3401
+ meta.trackReverbSend[idx] = val;
3402
+ }
3403
+ }
3244
3404
  for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3245
3405
  const idx = Number.parseInt(m[1], 10);
3246
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3406
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3247
3407
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3248
3408
  meta.trackEqLow ??= {};
3249
3409
  meta.trackEqLow[idx] = val;
@@ -3251,7 +3411,7 @@ var parseMmlMeta = (mml) => {
3251
3411
  }
3252
3412
  for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3253
3413
  const idx = Number.parseInt(m[1], 10);
3254
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3414
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3255
3415
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3256
3416
  meta.trackEqMid ??= {};
3257
3417
  meta.trackEqMid[idx] = val;
@@ -3259,7 +3419,7 @@ var parseMmlMeta = (mml) => {
3259
3419
  }
3260
3420
  for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3261
3421
  const idx = Number.parseInt(m[1], 10);
3262
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3422
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3263
3423
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3264
3424
  meta.trackEqHigh ??= {};
3265
3425
  meta.trackEqHigh[idx] = val;
@@ -3267,7 +3427,7 @@ var parseMmlMeta = (mml) => {
3267
3427
  }
3268
3428
  return meta;
3269
3429
  };
3270
- 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, "");
3430
+ 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, "");
3271
3431
  var formatMmlMeta = (meta, space = "") => {
3272
3432
  const parts = [];
3273
3433
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3306,6 +3466,11 @@ var formatMmlMeta = (meta, space = "") => {
3306
3466
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3307
3467
  }
3308
3468
  }
3469
+ if (meta.trackReverbSend) {
3470
+ for (const [idx, val] of Object.entries(meta.trackReverbSend)) {
3471
+ if (val !== 0) parts.push(`#t${idx}rev=${val}`);
3472
+ }
3473
+ }
3309
3474
  if (meta.trackEqLow) {
3310
3475
  for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3311
3476
  if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
@@ -3403,7 +3568,7 @@ var parseMML = (mml, options = {}) => {
3403
3568
  numStr += body[j];
3404
3569
  j++;
3405
3570
  }
3406
- const len = numStr ? clamp2(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3571
+ const len = numStr ? clamp3(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3407
3572
  let steps = Math.round(stepsPerBar / len);
3408
3573
  while (j < body.length && body[j] === ".") {
3409
3574
  steps = Math.round(steps * 1.5);
@@ -3421,7 +3586,7 @@ var parseMML = (mml, options = {}) => {
3421
3586
  numStr += body[j];
3422
3587
  j++;
3423
3588
  }
3424
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3589
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3425
3590
  pushTok("octave", currentStep, 0, tokStart);
3426
3591
  } else if (ch === ">") {
3427
3592
  octave = Math.min(8, octave + 1);
@@ -3438,7 +3603,7 @@ var parseMML = (mml, options = {}) => {
3438
3603
  numStr += body[j];
3439
3604
  j++;
3440
3605
  }
3441
- baseLength = clamp2(Number.parseInt(numStr, 10) || 16, 1, 64);
3606
+ baseLength = clamp3(Number.parseInt(numStr, 10) || 16, 1, 64);
3442
3607
  pushTok("length", currentStep, 0, tokStart);
3443
3608
  } else if (ch === "r") {
3444
3609
  j++;
@@ -3455,10 +3620,10 @@ var parseMML = (mml, options = {}) => {
3455
3620
  }
3456
3621
  if (ch === "t" && numStr) {
3457
3622
  if (bpm === null) {
3458
- bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3623
+ bpm = clamp3(Number.parseInt(numStr, 10), 1, 255);
3459
3624
  }
3460
3625
  } else if (ch === "v" && numStr) {
3461
- velocity = clamp2(Number.parseInt(numStr, 10), 0, 127);
3626
+ velocity = clamp3(Number.parseInt(numStr, 10), 0, 127);
3462
3627
  trackVelocity.set(trackIndex, velocity);
3463
3628
  }
3464
3629
  pushTok("ctrl", currentStep, 0, tokStart);
@@ -3492,7 +3657,7 @@ var parseMML = (mml, options = {}) => {
3492
3657
  numStr += body[j];
3493
3658
  j++;
3494
3659
  }
3495
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3660
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3496
3661
  } else {
3497
3662
  j++;
3498
3663
  }
@@ -3554,6 +3719,38 @@ var parseMML = (mml, options = {}) => {
3554
3719
  };
3555
3720
  };
3556
3721
 
3722
+ // src/reverb.ts
3723
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
3724
+ var MIN_REVERB_DECAY_SEC = 0.3;
3725
+ var MAX_REVERB_DECAY_SEC = 4;
3726
+ var IMPULSE_DECAY_CURVE = 2.5;
3727
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
3728
+ var MIN_REVERB_PREDELAY_MS = 0;
3729
+ var MAX_REVERB_PREDELAY_MS = 150;
3730
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
3731
+ const rate = ctx.sampleRate;
3732
+ const length = Math.max(
3733
+ 1,
3734
+ Math.floor(
3735
+ rate * Math.max(
3736
+ MIN_REVERB_DECAY_SEC,
3737
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
3738
+ )
3739
+ )
3740
+ );
3741
+ const impulse = ctx.createBuffer(2, length, rate);
3742
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
3743
+ const data = impulse.getChannelData(ch);
3744
+ for (let i2 = 0; i2 < length; i2++) {
3745
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
3746
+ data[i2] = (Math.random() * 2 - 1) * envelope;
3747
+ }
3748
+ }
3749
+ return impulse;
3750
+ };
3751
+ var REVERB_MAX_WET = 0.6;
3752
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
3753
+
3557
3754
  // src/sequencer.ts
3558
3755
  var STEPS_PER_BEAT = 48;
3559
3756
  var PLAN_TIME = 0.5;
@@ -6131,9 +6328,54 @@ var playPlacements = (placements, options) => {
6131
6328
  });
6132
6329
  const ownsCtx = !options.audioContext;
6133
6330
  const ctx = options.audioContext ?? new AudioContext();
6134
- const destination = options.destination ?? ctx.destination;
6331
+ const rawDestination = options.destination ?? ctx.destination;
6135
6332
  const useSynth = options.synth ?? !options.onPlayNote;
6136
- const synth = useSynth ? createSynth(ctx, destination) : null;
6333
+ const finalMix = ctx.createGain();
6334
+ const masterGain = ctx.createGain();
6335
+ masterGain.connect(finalMix);
6336
+ const reverbPreDelay = ctx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
6337
+ reverbPreDelay.delayTime.value = (options.metaReverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
6338
+ const reverbConvolver = ctx.createConvolver();
6339
+ const reverbDecaySec = (options.metaReverbDecay ?? 22) / 10 || DEFAULT_REVERB_DECAY_SEC;
6340
+ reverbConvolver.buffer = createReverbImpulse(ctx, reverbDecaySec);
6341
+ reverbConvolver.normalize = true;
6342
+ const reverbWetGain = ctx.createGain();
6343
+ reverbWetGain.gain.value = reverbAmountToGain(options.metaReverb ?? 0);
6344
+ reverbPreDelay.connect(reverbConvolver);
6345
+ reverbConvolver.connect(reverbWetGain);
6346
+ reverbWetGain.connect(finalMix);
6347
+ finalMix.connect(rawDestination);
6348
+ const channelStrips = /* @__PURE__ */ new Map();
6349
+ const getChannelStrip = (index) => {
6350
+ let strip = channelStrips.get(index);
6351
+ if (!strip) {
6352
+ strip = createChannelStrip(ctx, masterGain, {
6353
+ compression: options.trackCompression?.[index] ?? 0,
6354
+ width: options.trackWidth?.[index] ?? 100,
6355
+ eqLow: options.trackEqLow?.[index] ?? 0,
6356
+ eqMid: options.trackEqMid?.[index] ?? 0,
6357
+ eqHigh: options.trackEqHigh?.[index] ?? 0,
6358
+ reverbSend: options.trackReverbSend?.[index] ?? 0,
6359
+ reverbBus: reverbPreDelay
6360
+ });
6361
+ channelStrips.set(index, strip);
6362
+ }
6363
+ return strip;
6364
+ };
6365
+ const synths = /* @__PURE__ */ new Map();
6366
+ const getSynth = (index) => {
6367
+ let s = synths.get(index);
6368
+ if (!s) {
6369
+ s = createSynth(ctx, getChannelStrip(index).input);
6370
+ synths.set(index, s);
6371
+ }
6372
+ return s;
6373
+ };
6374
+ const drumSynth = useSynth ? createSynth(ctx, masterGain) : null;
6375
+ const trackIndexById = /* @__PURE__ */ new Map();
6376
+ trackIndices.forEach((idx) => {
6377
+ trackIndexById.set(TRACK_ID_BY_INDEX[idx] ?? `t${idx}`, idx);
6378
+ });
6137
6379
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
6138
6380
  let playing = false;
6139
6381
  const seq = createSequencer({
@@ -6148,12 +6390,14 @@ var playPlacements = (placements, options) => {
6148
6390
  getAudioTime: () => ctx.currentTime,
6149
6391
  onPlayNote: (e) => {
6150
6392
  options.onPlayNote?.(e);
6151
- synth?.playNote(e);
6393
+ if (!useSynth) return;
6394
+ const index = trackIndexById.get(e.trackId);
6395
+ (index === void 0 ? drumSynth : getSynth(index))?.playNote(e);
6152
6396
  },
6153
6397
  onPlayDrum: (e) => {
6154
6398
  const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
6155
6399
  options.onPlayDrum?.({ ...e, velocity });
6156
- synth?.playDrum({ ...e, velocity });
6400
+ drumSynth?.playDrum({ ...e, velocity });
6157
6401
  },
6158
6402
  onTick: (step) => {
6159
6403
  options.onTick?.(step);
@@ -6204,6 +6448,7 @@ var playPlacements = (placements, options) => {
6204
6448
  if (pauseWhenHidden && typeof document !== "undefined") {
6205
6449
  document.removeEventListener("visibilitychange", onVisibilityChange);
6206
6450
  }
6451
+ for (const strip of channelStrips.values()) strip.dispose();
6207
6452
  if (ownsCtx) void ctx.close();
6208
6453
  };
6209
6454
  return {
@@ -6223,7 +6468,16 @@ var playMML = (mml, options = {}) => {
6223
6468
  bpm,
6224
6469
  metaVolume: meta.volume,
6225
6470
  metaDrum: meta.drum,
6226
- metaDrumVolume: meta.drumVolume
6471
+ metaDrumVolume: meta.drumVolume,
6472
+ metaReverb: meta.reverb,
6473
+ metaReverbDecay: meta.reverbDecay,
6474
+ metaReverbPreDelay: meta.reverbPreDelay,
6475
+ trackCompression: meta.trackCompression,
6476
+ trackWidth: meta.trackWidth,
6477
+ trackReverbSend: meta.trackReverbSend,
6478
+ trackEqLow: meta.trackEqLow,
6479
+ trackEqMid: meta.trackEqMid,
6480
+ trackEqHigh: meta.trackEqHigh
6227
6481
  });
6228
6482
  };
6229
6483
  var playNote = (options) => {
@@ -7621,11 +7875,14 @@ var DAW_CSS = `
7621
7875
  user-select: none;
7622
7876
  }
7623
7877
  .dtm-player-balloon {
7624
- position: absolute;
7625
- bottom: calc(100% + 6px);
7626
- left: 50%;
7627
- transform: translateX(-50%);
7628
- z-index: 100;
7878
+ /* 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
7879
+ \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
7880
+ 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 */
7881
+ position: fixed;
7882
+ left: 0;
7883
+ top: 0;
7884
+ transform: translate(-50%, -100%);
7885
+ z-index: 1000;
7629
7886
  display: none;
7630
7887
  pointer-events: none;
7631
7888
  font-family: var(--dtm-font);
@@ -7654,8 +7911,8 @@ var DAW_CSS = `
7654
7911
  animation: dtm-balloon-fade-in 0.1s steps(2);
7655
7912
  }
7656
7913
  @keyframes dtm-balloon-fade-in {
7657
- from { opacity: 0; transform: translateX(-50%) translateY(4px); }
7658
- to { opacity: 1; transform: translateX(-50%) translateY(0); }
7914
+ from { opacity: 0; transform: translate(-50%, -100%) translateY(4px); }
7915
+ to { opacity: 1; transform: translate(-50%, -100%) translateY(0); }
7659
7916
  }
7660
7917
  @keyframes dtm-emoji-jump {
7661
7918
  0% { transform: translateY(0); }
@@ -8078,6 +8335,27 @@ var LYRIC_MODEL_LABELS = {
8078
8335
  };
8079
8336
  var activeBalloonEl = null;
8080
8337
  var activeBalloonTimer = null;
8338
+ var balloonAnchors = /* @__PURE__ */ new WeakMap();
8339
+ var positionBalloon = (balloonEl) => {
8340
+ const anchor = balloonAnchors.get(balloonEl);
8341
+ if (!anchor) return;
8342
+ const rect = anchor.getBoundingClientRect();
8343
+ balloonEl.style.left = `${rect.left + rect.width / 2}px`;
8344
+ balloonEl.style.top = `${rect.top - 6}px`;
8345
+ };
8346
+ var balloonListenersBound = false;
8347
+ var ensureBalloonListeners = () => {
8348
+ if (balloonListenersBound || typeof window === "undefined") return;
8349
+ balloonListenersBound = true;
8350
+ const reposition = () => {
8351
+ if (activeBalloonEl) positionBalloon(activeBalloonEl);
8352
+ };
8353
+ window.addEventListener("scroll", reposition, {
8354
+ capture: true,
8355
+ passive: true
8356
+ });
8357
+ window.addEventListener("resize", reposition, { passive: true });
8358
+ };
8081
8359
  var hideActiveBalloon = () => {
8082
8360
  if (activeBalloonEl) {
8083
8361
  activeBalloonEl.classList.remove("dtm-player-balloon--visible");
@@ -8089,6 +8367,7 @@ var hideActiveBalloon = () => {
8089
8367
  }
8090
8368
  };
8091
8369
  var showBalloon = (balloonEl) => {
8370
+ positionBalloon(balloonEl);
8092
8371
  if (activeBalloonEl === balloonEl) {
8093
8372
  if (activeBalloonTimer) {
8094
8373
  clearTimeout(activeBalloonTimer);
@@ -8353,6 +8632,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8353
8632
  const doc = target.ownerDocument ?? document;
8354
8633
  const root = doc.createElement("div");
8355
8634
  root.className = "dtm-daw dtm-player";
8635
+ const playerBalloons = [];
8356
8636
  const head = doc.createElement("div");
8357
8637
  head.className = "dtm-player-head";
8358
8638
  const playBtn = doc.createElement("button");
@@ -8698,7 +8978,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8698
8978
  balloon.className = "dtm-player-balloon";
8699
8979
  const modelKey = lt.model.toLowerCase();
8700
8980
  balloon.textContent = LYRIC_MODEL_LABELS[modelKey] ?? lt.model;
8701
- em.appendChild(balloon);
8981
+ balloonAnchors.set(balloon, em);
8982
+ doc.body.appendChild(balloon);
8983
+ playerBalloons.push(balloon);
8984
+ ensureBalloonListeners();
8702
8985
  em.addEventListener("mouseenter", () => {
8703
8986
  showBalloon(balloon);
8704
8987
  });
@@ -9159,7 +9442,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9159
9442
  delaySend: (lt.delay ?? 0) / 100,
9160
9443
  gender: (lt.gender ?? 50) / 100,
9161
9444
  breathiness: (lt.breathiness ?? 50) / 100,
9162
- octaveDouble: lt.octaveDouble,
9445
+ octaveUnison: lt.octaveUnison,
9163
9446
  notes
9164
9447
  };
9165
9448
  });
@@ -9295,9 +9578,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9295
9578
  }
9296
9579
  for (const t of blinkTimers) clearTimeout(t);
9297
9580
  clearJumpTimers();
9298
- if (activeBalloonEl && root.contains(activeBalloonEl)) {
9581
+ if (activeBalloonEl && playerBalloons.includes(activeBalloonEl)) {
9299
9582
  hideActiveBalloon();
9300
9583
  }
9584
+ for (const balloon of playerBalloons) balloon.remove();
9301
9585
  root.remove();
9302
9586
  consentOverlayEl?.remove();
9303
9587
  closeInfoModal();
@@ -9499,20 +9783,23 @@ var adjustZone = async (ctx, fontName, zone) => {
9499
9783
  const newLength = attackLength + loopLengthFrame * repeatCount + releaseLength;
9500
9784
  let totalPeak = 0;
9501
9785
  let loopPeak = 0;
9502
- if (oldBuf.numberOfChannels > 0) {
9503
- const ch0 = oldBuf.getChannelData(0);
9504
- for (let i2 = 0; i2 < ch0.length; i2++) {
9505
- const abs = Math.abs(ch0[i2]);
9786
+ for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9787
+ const chData = oldBuf.getChannelData(ch);
9788
+ for (let i2 = 0; i2 < chData.length; i2++) {
9789
+ const abs = Math.abs(chData[i2]);
9506
9790
  if (abs > totalPeak) totalPeak = abs;
9507
9791
  if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9508
9792
  if (abs > loopPeak) loopPeak = abs;
9509
9793
  }
9510
9794
  }
9511
9795
  }
9796
+ const decay = isDecayInstrument(fontName);
9797
+ const targetRatio = decay ? 0.4 : 0.75;
9798
+ const maxMultiplier = decay ? 6 : 20;
9512
9799
  let gainMultiplier = 1;
9513
- if (!isDecayInstrument(fontName) && loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9514
- gainMultiplier = totalPeak * 0.75 / loopPeak;
9515
- if (gainMultiplier > 20) gainMultiplier = 20;
9800
+ if (loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9801
+ gainMultiplier = totalPeak * targetRatio / loopPeak;
9802
+ if (gainMultiplier > maxMultiplier) gainMultiplier = maxMultiplier;
9516
9803
  }
9517
9804
  try {
9518
9805
  const newBuf = ctx.createBuffer(
@@ -10844,14 +11131,14 @@ var DELAY_DIVISIONS = [
10844
11131
  { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10845
11132
  { value: "16", label: "16\u5206", beats: 0.25 }
10846
11133
  ];
10847
- var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
11134
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10848
11135
  var divisionToSeconds = (division, bpm) => {
10849
11136
  const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10850
11137
  const safeBpm = bpm > 0 ? bpm : 120;
10851
11138
  return 60 / safeBpm * beats;
10852
11139
  };
10853
11140
  var DELAY_MAX_WET = 0.45;
10854
- var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
11141
+ var delayAmountToGain = (amount) => clamp4(amount, 0, 100) / 100 * DELAY_MAX_WET;
10855
11142
  var FEEDBACK_GAIN = 0.3;
10856
11143
  var MAX_DELAY_SEC = 2;
10857
11144
  var createDelayBus = (ctx, destination, options = {}) => {
@@ -12926,38 +13213,6 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12926
13213
  return chordNotes / notes.length >= threshold;
12927
13214
  };
12928
13215
 
12929
- // src/reverb.ts
12930
- var DEFAULT_REVERB_DECAY_SEC = 2.2;
12931
- var MIN_REVERB_DECAY_SEC = 0.3;
12932
- var MAX_REVERB_DECAY_SEC = 4;
12933
- var IMPULSE_DECAY_CURVE = 2.5;
12934
- var DEFAULT_REVERB_PREDELAY_MS = 0;
12935
- var MIN_REVERB_PREDELAY_MS = 0;
12936
- var MAX_REVERB_PREDELAY_MS = 150;
12937
- var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
12938
- const rate = ctx.sampleRate;
12939
- const length = Math.max(
12940
- 1,
12941
- Math.floor(
12942
- rate * Math.max(
12943
- MIN_REVERB_DECAY_SEC,
12944
- Math.min(MAX_REVERB_DECAY_SEC, decaySec)
12945
- )
12946
- )
12947
- );
12948
- const impulse = ctx.createBuffer(2, length, rate);
12949
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
12950
- const data = impulse.getChannelData(ch);
12951
- for (let i2 = 0; i2 < length; i2++) {
12952
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
12953
- data[i2] = (Math.random() * 2 - 1) * envelope;
12954
- }
12955
- }
12956
- return impulse;
12957
- };
12958
- var REVERB_MAX_WET = 0.6;
12959
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
12960
-
12961
13216
  // src/daw.ts
12962
13217
  var CHORD_INFO_HTML2 = `
12963
13218
  <div class="dtm-modal-body-content">
@@ -12999,14 +13254,22 @@ var VIBRATO_INFO_HTML = `
12999
13254
  <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>
13000
13255
  </div>
13001
13256
  `;
13002
- var OCTAVE_DOUBLE_INFO_HTML = `
13257
+ var OCTAVE_UNISON_TOKEN = {
13258
+ none: "",
13259
+ down: "w1",
13260
+ up: "w2",
13261
+ both: "w3"
13262
+ };
13263
+ var OCTAVE_UNISON_INFO_HTML = `
13003
13264
  <div class="dtm-modal-body-content">
13004
13265
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13005
- <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>
13266
+ <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>
13267
+ <h4>\u4E0B\u30FB\u4E0A\u30FB\u4E21\u65B9\u306E\u9055\u3044</h4>
13268
+ <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>
13006
13269
  <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13007
13270
  <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>
13008
13271
  <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13009
- <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>
13272
+ <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>
13010
13273
  </div>
13011
13274
  `;
13012
13275
  var GENDER_INFO_HTML = `
@@ -13121,6 +13384,16 @@ var TRACK_WIDTH_INFO_HTML = `
13121
13384
  <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>
13122
13385
  </div>
13123
13386
  `;
13387
+ var TRACK_REVERBSEND_INFO_HTML = `
13388
+ <div class="dtm-modal-body-content">
13389
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13390
+ <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>
13391
+ <h4>\u300C\u58F0\u3060\u3051\u30EA\u30D0\u30FC\u30D6\u300D\u3092\u4F5C\u308B\u306B\u306F</h4>
13392
+ <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>
13393
+ <h4>[reverb]\u30D1\u30CD\u30EB\u306EMix\u3068\u306E\u95A2\u4FC2</h4>
13394
+ <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>
13395
+ </div>
13396
+ `;
13124
13397
  var AUTO_MASTER_INFO_HTML = `
13125
13398
  <div class="dtm-modal-body-content">
13126
13399
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
@@ -13375,7 +13648,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13375
13648
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13376
13649
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13377
13650
  };
13378
- var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
13651
+ var clamp5 = (v, min, max) => Math.min(Math.max(v, min), max);
13379
13652
  var normalizeInstrumentName = (name) => {
13380
13653
  if (!name) return "";
13381
13654
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13464,6 +13737,8 @@ var mountDAW = (target, options = {}) => {
13464
13737
  refs.drumFontSelect.value = currentDrumFont;
13465
13738
  let currentInstrument = "";
13466
13739
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
13740
+ let trackFxAdvancedOpen = false;
13741
+ let lyricAdvancedOpen = false;
13467
13742
  let activeToolMode = "pen";
13468
13743
  let currentInsertLength = 48;
13469
13744
  let snapGridSteps = 12;
@@ -13541,7 +13816,7 @@ var mountDAW = (target, options = {}) => {
13541
13816
  vocalDelay: t.vocalDelay,
13542
13817
  vocalGender: t.vocalGender,
13543
13818
  vocalBreathiness: t.vocalBreathiness,
13544
- vocalOctaveDouble: t.vocalOctaveDouble
13819
+ vocalOctaveUnison: t.vocalOctaveUnison
13545
13820
  };
13546
13821
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13547
13822
  lyricsDebounceTimer = setTimeout(() => {
@@ -13606,10 +13881,11 @@ var mountDAW = (target, options = {}) => {
13606
13881
  vocalDelay: 0,
13607
13882
  vocalGender: 50,
13608
13883
  vocalBreathiness: 50,
13609
- vocalOctaveDouble: false,
13884
+ vocalOctaveUnison: "none",
13610
13885
  trackInstrument: "",
13611
13886
  trackCompression: 0,
13612
13887
  trackWidth: 100,
13888
+ trackReverbSend: 0,
13613
13889
  trackEqLow: 0,
13614
13890
  trackEqMid: 0,
13615
13891
  trackEqHigh: 0
@@ -13636,7 +13912,7 @@ var mountDAW = (target, options = {}) => {
13636
13912
  delay: t.vocalDelay,
13637
13913
  gender: t.vocalGender,
13638
13914
  breathiness: t.vocalBreathiness,
13639
- octaveDouble: t.vocalOctaveDouble,
13915
+ octaveUnison: t.vocalOctaveUnison,
13640
13916
  syllables
13641
13917
  });
13642
13918
  });
@@ -13759,7 +14035,7 @@ var mountDAW = (target, options = {}) => {
13759
14035
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13760
14036
  const ratio = currentOffsetX / maxOffsetX;
13761
14037
  refs.hScrollThumb.style.width = `${thumbW}px`;
13762
- refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
14038
+ refs.hScrollThumb.style.left = `${clamp5(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13763
14039
  }
13764
14040
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13765
14041
  const sbH = refs.vScroll.clientHeight;
@@ -13829,9 +14105,9 @@ var mountDAW = (target, options = {}) => {
13829
14105
  if (maxOffsetX <= 0) return;
13830
14106
  const rect = refs.hScroll.getBoundingClientRect();
13831
14107
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13832
- const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
14108
+ const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13833
14109
  const ratio = x2 / (rect.width - thumbW);
13834
- currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
14110
+ currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
13835
14111
  setDrawOffset(currentOffsetX, currentOffsetY);
13836
14112
  redrawAll();
13837
14113
  };
@@ -13840,9 +14116,9 @@ var mountDAW = (target, options = {}) => {
13840
14116
  if (maxOffset <= 0) return;
13841
14117
  const rect = refs.vScroll.getBoundingClientRect();
13842
14118
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13843
- const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
14119
+ const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13844
14120
  const ratio = y / (rect.height - thumbH);
13845
- currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
14121
+ currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
13846
14122
  setDrawOffset(currentOffsetX, currentOffsetY);
13847
14123
  redrawAll();
13848
14124
  };
@@ -13889,8 +14165,8 @@ var mountDAW = (target, options = {}) => {
13889
14165
  if (dx !== 0 || dy !== 0) {
13890
14166
  const maxOffsetX = getMaxOffsetX();
13891
14167
  const maxOffsetY = getMaxOffsetY();
13892
- currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
13893
- currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
14168
+ currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
14169
+ currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
13894
14170
  setDrawOffset(currentOffsetX, currentOffsetY);
13895
14171
  onPointerMove(lastMoveEvent);
13896
14172
  }
@@ -14280,12 +14556,12 @@ var mountDAW = (target, options = {}) => {
14280
14556
  "wheel",
14281
14557
  (event) => {
14282
14558
  event.preventDefault();
14283
- currentOffsetY = clamp4(
14559
+ currentOffsetY = clamp5(
14284
14560
  currentOffsetY + event.deltaY,
14285
14561
  0,
14286
14562
  getMaxOffsetY()
14287
14563
  );
14288
- currentOffsetX = clamp4(
14564
+ currentOffsetX = clamp5(
14289
14565
  currentOffsetX + event.deltaX,
14290
14566
  0,
14291
14567
  getMaxOffsetX()
@@ -14324,7 +14600,7 @@ var mountDAW = (target, options = {}) => {
14324
14600
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14325
14601
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14326
14602
  refs.zoomXLabel.textContent = `${zoomX}%`;
14327
- currentOffsetX = clamp4(
14603
+ currentOffsetX = clamp5(
14328
14604
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14329
14605
  0,
14330
14606
  getMaxOffsetX()
@@ -14337,7 +14613,7 @@ var mountDAW = (target, options = {}) => {
14337
14613
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14338
14614
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14339
14615
  refs.zoomYLabel.textContent = `${zoomY}%`;
14340
- currentOffsetY = clamp4(
14616
+ currentOffsetY = clamp5(
14341
14617
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14342
14618
  0,
14343
14619
  getMaxOffsetY()
@@ -14387,7 +14663,7 @@ var mountDAW = (target, options = {}) => {
14387
14663
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14388
14664
  if (currentPlayStep > threshold) {
14389
14665
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14390
- currentOffsetX = clamp4(
14666
+ currentOffsetX = clamp5(
14391
14667
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14392
14668
  0,
14393
14669
  getMaxOffsetX()
@@ -14446,7 +14722,7 @@ var mountDAW = (target, options = {}) => {
14446
14722
  delaySend: (lt.delay ?? 0) / 100,
14447
14723
  gender: (lt.gender ?? 50) / 100,
14448
14724
  breathiness: (lt.breathiness ?? 50) / 100,
14449
- octaveDouble: lt.octaveDouble,
14725
+ octaveUnison: lt.octaveUnison,
14450
14726
  notes
14451
14727
  };
14452
14728
  }) : [];
@@ -14474,7 +14750,7 @@ var mountDAW = (target, options = {}) => {
14474
14750
  }
14475
14751
  if (playbackState !== "paused") {
14476
14752
  const canvas = getGridCanvas();
14477
- currentOffsetX = clamp4(
14753
+ currentOffsetX = clamp5(
14478
14754
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14479
14755
  0,
14480
14756
  getMaxOffsetX()
@@ -14578,7 +14854,7 @@ var mountDAW = (target, options = {}) => {
14578
14854
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
14579
14855
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14580
14856
  </div>
14581
- <details class="dtm-advanced" data-dtm="track-fx-advanced">
14857
+ <details class="dtm-advanced" data-dtm="track-fx-advanced" ${trackFxAdvancedOpen ? "open" : ""}>
14582
14858
  <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14583
14859
  <div class="dtm-row">
14584
14860
  <span class="dtm-label">EQ\u4F4E\u57DF</span>
@@ -14608,7 +14884,18 @@ var mountDAW = (target, options = {}) => {
14608
14884
  <span class="dtm-label" data-dtm="track-width-label"></span>
14609
14885
  <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14610
14886
  </div>
14887
+ <div class="dtm-row">
14888
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14889
+ <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">
14890
+ <span class="dtm-label" data-dtm="track-reverb-send-label"></span>
14891
+ <button class="dtm-infobtn" data-dtm="track-reverb-send-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14892
+ </div>
14611
14893
  </details>`;
14894
+ refs.trackBody.querySelector(
14895
+ '[data-dtm="track-fx-advanced"]'
14896
+ ).addEventListener("toggle", (e) => {
14897
+ trackFxAdvancedOpen = e.target.open;
14898
+ });
14612
14899
  const volInput = refs.trackBody.querySelector(
14613
14900
  '[data-dtm="track-vol"]'
14614
14901
  );
@@ -14712,6 +14999,28 @@ var mountDAW = (target, options = {}) => {
14712
14999
  trackWidthInfo.addEventListener("click", () => {
14713
15000
  showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14714
15001
  });
15002
+ const trackReverbSendInput = refs.trackBody.querySelector(
15003
+ '[data-dtm="track-reverb-send"]'
15004
+ );
15005
+ const trackReverbSendLabel = refs.trackBody.querySelector(
15006
+ '[data-dtm="track-reverb-send-label"]'
15007
+ );
15008
+ const trackReverbSendInfo = refs.trackBody.querySelector(
15009
+ '[data-dtm="track-reverb-send-info"]'
15010
+ );
15011
+ trackReverbSendInput.value = String(active.trackReverbSend);
15012
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
15013
+ trackReverbSendInput.addEventListener("input", () => {
15014
+ active.trackReverbSend = Number.parseInt(trackReverbSendInput.value, 10);
15015
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
15016
+ options.onTrackReverbSendChange?.(
15017
+ active.config.id,
15018
+ active.trackReverbSend
15019
+ );
15020
+ });
15021
+ trackReverbSendInfo.addEventListener("click", () => {
15022
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", TRACK_REVERBSEND_INFO_HTML);
15023
+ });
14715
15024
  const instRow = document.createElement("div");
14716
15025
  instRow.className = "dtm-row";
14717
15026
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -14800,7 +15109,7 @@ var mountDAW = (target, options = {}) => {
14800
15109
  <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">
14801
15110
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
14802
15111
  </div>
14803
- <details class="dtm-advanced" data-dtm="lyric-advanced">
15112
+ <details class="dtm-advanced" data-dtm="lyric-advanced" ${lyricAdvancedOpen ? "open" : ""}>
14804
15113
  <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14805
15114
  <div class="dtm-row">
14806
15115
  <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
@@ -14848,15 +15157,24 @@ var mountDAW = (target, options = {}) => {
14848
15157
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14849
15158
  </div>
14850
15159
  <div class="dtm-row">
14851
- <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14852
- <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
14853
- </span>
14854
- <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>
15160
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3</span>
15161
+ <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">
15162
+ <option value="none">\u306A\u3057</option>
15163
+ <option value="down">\u4E0B (-1oct)</option>
15164
+ <option value="up">\u4E0A (+1oct)</option>
15165
+ <option value="both">\u4E0A\u4E0B\u4E21\u65B9</option>
15166
+ </select>
15167
+ <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>
14855
15168
  </div>
14856
15169
  </details>
14857
15170
  <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>
14858
15171
  </div>`;
14859
15172
  refs.trackBody.appendChild(lyricDiv);
15173
+ lyricDiv.querySelector(
15174
+ '[data-dtm="lyric-advanced"]'
15175
+ ).addEventListener("toggle", (e) => {
15176
+ lyricAdvancedOpen = e.target.open;
15177
+ });
14860
15178
  const lyricModelSel = lyricDiv.querySelector(
14861
15179
  '[data-dtm="lyric-model"]'
14862
15180
  );
@@ -14944,14 +15262,14 @@ var mountDAW = (target, options = {}) => {
14944
15262
  lyricVibratoInfo.addEventListener("click", () => {
14945
15263
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14946
15264
  });
14947
- const lyricOctaveDouble = lyricDiv.querySelector(
14948
- '[data-dtm="lyric-octave-double"]'
15265
+ const lyricOctaveUnison = lyricDiv.querySelector(
15266
+ '[data-dtm="lyric-octave-unison"]'
14949
15267
  );
14950
- const lyricOctaveDoubleInfo = lyricDiv.querySelector(
14951
- '[data-dtm="lyric-octave-double-info"]'
15268
+ const lyricOctaveUnisonInfo = lyricDiv.querySelector(
15269
+ '[data-dtm="lyric-octave-unison-info"]'
14952
15270
  );
14953
- lyricOctaveDoubleInfo.addEventListener("click", () => {
14954
- showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u89E3\u8AAC", OCTAVE_DOUBLE_INFO_HTML);
15271
+ lyricOctaveUnisonInfo.addEventListener("click", () => {
15272
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\u89E3\u8AAC", OCTAVE_UNISON_INFO_HTML);
14955
15273
  });
14956
15274
  const lyricTerms = lyricDiv.querySelector(
14957
15275
  '[data-dtm="lyric-terms"]'
@@ -15023,7 +15341,7 @@ var mountDAW = (target, options = {}) => {
15023
15341
  lyricBreathiness.value = String(active.vocalBreathiness);
15024
15342
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15025
15343
  lyricVibrato.checked = active.vocalVibrato;
15026
- lyricOctaveDouble.checked = active.vocalOctaveDouble;
15344
+ lyricOctaveUnison.value = active.vocalOctaveUnison;
15027
15345
  const updateLyricCount = () => {
15028
15346
  const n = normalizeLyrics(lyricInput.value).length;
15029
15347
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -15145,8 +15463,8 @@ var mountDAW = (target, options = {}) => {
15145
15463
  active.vocalVibrato = lyricVibrato.checked;
15146
15464
  fireLyricsChange(active);
15147
15465
  });
15148
- lyricOctaveDouble.addEventListener("change", () => {
15149
- active.vocalOctaveDouble = lyricOctaveDouble.checked;
15466
+ lyricOctaveUnison.addEventListener("change", () => {
15467
+ active.vocalOctaveUnison = lyricOctaveUnison.value;
15150
15468
  fireLyricsChange(active);
15151
15469
  });
15152
15470
  lyricInput.addEventListener("input", () => {
@@ -15277,6 +15595,7 @@ var mountDAW = (target, options = {}) => {
15277
15595
  const trackInstrumentsForMeta = {};
15278
15596
  const trackCompressionForMeta = {};
15279
15597
  const trackWidthForMeta = {};
15598
+ const trackReverbSendForMeta = {};
15280
15599
  const trackEqLowForMeta = {};
15281
15600
  const trackEqMidForMeta = {};
15282
15601
  const trackEqHighForMeta = {};
@@ -15285,6 +15604,8 @@ var mountDAW = (target, options = {}) => {
15285
15604
  if (t.trackCompression !== 0)
15286
15605
  trackCompressionForMeta[i2] = t.trackCompression;
15287
15606
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15607
+ if (t.trackReverbSend !== 0)
15608
+ trackReverbSendForMeta[i2] = t.trackReverbSend;
15288
15609
  if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15289
15610
  if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15290
15611
  if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
@@ -15292,6 +15613,7 @@ var mountDAW = (target, options = {}) => {
15292
15613
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
15293
15614
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
15294
15615
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15616
+ const trackReverbSendMeta = Object.keys(trackReverbSendForMeta).length > 0 ? trackReverbSendForMeta : void 0;
15295
15617
  const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15296
15618
  const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15297
15619
  const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
@@ -15312,6 +15634,7 @@ var mountDAW = (target, options = {}) => {
15312
15634
  trackInstruments: trackInstMeta,
15313
15635
  trackCompression: trackCompMeta,
15314
15636
  trackWidth: trackWidthMeta,
15637
+ trackReverbSend: trackReverbSendMeta,
15315
15638
  trackEqLow: trackEqLowMeta,
15316
15639
  trackEqMid: trackEqMidMeta,
15317
15640
  trackEqHigh: trackEqHighMeta
@@ -15335,6 +15658,7 @@ var mountDAW = (target, options = {}) => {
15335
15658
  trackInstruments: trackInstMeta,
15336
15659
  trackCompression: trackCompMeta,
15337
15660
  trackWidth: trackWidthMeta,
15661
+ trackReverbSend: trackReverbSendMeta,
15338
15662
  trackEqLow: trackEqLowMeta,
15339
15663
  trackEqMid: trackEqMidMeta,
15340
15664
  trackEqHigh: trackEqHighMeta
@@ -15390,7 +15714,7 @@ var mountDAW = (target, options = {}) => {
15390
15714
  del: t.vocalDelay,
15391
15715
  gen: t.vocalGender,
15392
15716
  bre: t.vocalBreathiness,
15393
- dbl: t.vocalOctaveDouble
15717
+ uni: t.vocalOctaveUnison
15394
15718
  })).filter(
15395
15719
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
15396
15720
  ).map((x2) => {
@@ -15404,7 +15728,7 @@ var mountDAW = (target, options = {}) => {
15404
15728
  x2.del === 0 ? "" : `e${x2.del}`,
15405
15729
  x2.gen === 50 ? "" : `g${x2.gen}`,
15406
15730
  x2.bre === 50 ? "" : `h${x2.bre}`,
15407
- x2.dbl ? "w1" : ""
15731
+ OCTAVE_UNISON_TOKEN[x2.uni]
15408
15732
  ].filter((s) => s.length > 0).join(" ");
15409
15733
  const head = params ? `${x2.model} ${params}` : x2.model;
15410
15734
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15473,7 +15797,7 @@ var mountDAW = (target, options = {}) => {
15473
15797
  const canvas = getGridCanvas();
15474
15798
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15475
15799
  const logicalY = yIndex * renderConfig.keyHeight;
15476
- currentOffsetY = clamp4(
15800
+ currentOffsetY = clamp5(
15477
15801
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15478
15802
  0,
15479
15803
  getMaxOffsetY()
@@ -15604,6 +15928,11 @@ var mountDAW = (target, options = {}) => {
15604
15928
  t.trackWidth = width;
15605
15929
  options.onTrackWidthChange?.(t.config.id, width);
15606
15930
  }
15931
+ const reverbSend = meta.trackReverbSend?.[i2] ?? 0;
15932
+ if (t.trackReverbSend !== reverbSend) {
15933
+ t.trackReverbSend = reverbSend;
15934
+ options.onTrackReverbSendChange?.(t.config.id, reverbSend);
15935
+ }
15607
15936
  const eqLow = meta.trackEqLow?.[i2] ?? 0;
15608
15937
  if (t.trackEqLow !== eqLow) {
15609
15938
  t.trackEqLow = eqLow;
@@ -15641,7 +15970,7 @@ var mountDAW = (target, options = {}) => {
15641
15970
  active.vocalDelay = 0;
15642
15971
  active.vocalGender = 50;
15643
15972
  active.vocalBreathiness = 50;
15644
- active.vocalOctaveDouble = false;
15973
+ active.vocalOctaveUnison = "none";
15645
15974
  } else {
15646
15975
  for (const t of trackStates) {
15647
15976
  t.lyrics = "";
@@ -15655,7 +15984,7 @@ var mountDAW = (target, options = {}) => {
15655
15984
  t.vocalDelay = 0;
15656
15985
  t.vocalGender = 50;
15657
15986
  t.vocalBreathiness = 50;
15658
- t.vocalOctaveDouble = false;
15987
+ t.vocalOctaveUnison = "none";
15659
15988
  }
15660
15989
  }
15661
15990
  lyrics?.forEach((lt) => {
@@ -15673,7 +16002,7 @@ var mountDAW = (target, options = {}) => {
15673
16002
  t.vocalDelay = lt.delay ?? 0;
15674
16003
  t.vocalGender = lt.gender ?? 50;
15675
16004
  t.vocalBreathiness = lt.breathiness ?? 50;
15676
- t.vocalOctaveDouble = lt.octaveDouble ?? false;
16005
+ t.vocalOctaveUnison = lt.octaveUnison ?? "none";
15677
16006
  });
15678
16007
  for (const p of placements) {
15679
16008
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15757,7 +16086,7 @@ var mountDAW = (target, options = {}) => {
15757
16086
  active.vocalDelay = 0;
15758
16087
  active.vocalGender = 50;
15759
16088
  active.vocalBreathiness = 50;
15760
- active.vocalOctaveDouble = false;
16089
+ active.vocalOctaveUnison = "none";
15761
16090
  } else {
15762
16091
  clearAll();
15763
16092
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15773,7 +16102,7 @@ var mountDAW = (target, options = {}) => {
15773
16102
  t.vocalDelay = 0;
15774
16103
  t.vocalGender = 50;
15775
16104
  t.vocalBreathiness = 50;
15776
- t.vocalOctaveDouble = false;
16105
+ t.vocalOctaveUnison = "none";
15777
16106
  }
15778
16107
  }
15779
16108
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -16043,8 +16372,8 @@ var mountDAW = (target, options = {}) => {
16043
16372
  refs.reverbAmount.value = "20";
16044
16373
  refs.reverbAmountLabel.textContent = "20%";
16045
16374
  options.onReverbChange?.(20);
16046
- const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16047
- reverbDecay = clamp4(
16375
+ const bpmT = clamp5((bpm - 60) / (180 - 60), 0, 1);
16376
+ reverbDecay = clamp5(
16048
16377
  3 - bpmT * (3 - 0.9),
16049
16378
  MIN_REVERB_DECAY_SEC,
16050
16379
  MAX_REVERB_DECAY_SEC
@@ -16073,6 +16402,10 @@ var mountDAW = (target, options = {}) => {
16073
16402
  t.trackWidth = 115;
16074
16403
  options.onTrackCompressionChange?.(t.config.id, comp);
16075
16404
  options.onTrackWidthChange?.(t.config.id, 115);
16405
+ if (!t.lyricModel) {
16406
+ t.trackReverbSend = 15;
16407
+ options.onTrackReverbSendChange?.(t.config.id, 15);
16408
+ }
16076
16409
  if (t.lyricModel) {
16077
16410
  t.vocalVibrato = true;
16078
16411
  t.vocalReverb = isMainVocal ? 15 : 30;
@@ -16971,7 +17304,7 @@ var mountDAW = (target, options = {}) => {
16971
17304
  currentPlayStep = step;
16972
17305
  playbackState = "paused";
16973
17306
  const canvas = getGridCanvas();
16974
- currentOffsetX = clamp4(
17307
+ currentOffsetX = clamp5(
16975
17308
  step * renderConfig.stepWidth - canvas.width * 0.5,
16976
17309
  0,
16977
17310
  getMaxOffsetX()
@@ -17017,11 +17350,11 @@ var mountDAW = (target, options = {}) => {
17017
17350
  getViewState,
17018
17351
  setViewState: (state) => {
17019
17352
  if (typeof state.zoomX === "number") {
17020
- zoomX = clamp4(state.zoomX, 25, 200);
17353
+ zoomX = clamp5(state.zoomX, 25, 200);
17021
17354
  applyZoomX();
17022
17355
  }
17023
17356
  if (typeof state.zoomY === "number") {
17024
- zoomY = clamp4(state.zoomY, 50, 200);
17357
+ zoomY = clamp5(state.zoomY, 50, 200);
17025
17358
  applyZoomY();
17026
17359
  }
17027
17360
  if (typeof state.decomposeChord === "boolean") {
@@ -17043,24 +17376,24 @@ var mountDAW = (target, options = {}) => {
17043
17376
  forcePauseAt,
17044
17377
  setLoading,
17045
17378
  setMasterVolume: (volume) => {
17046
- masterVolume = clamp4(volume, 0, 100);
17379
+ masterVolume = clamp5(volume, 0, 100);
17047
17380
  refs.masterVolume.value = String(masterVolume);
17048
17381
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17049
17382
  options.singingVoices?.setVolume(masterVolume / 100);
17050
17383
  },
17051
17384
  setVolume: (volume) => {
17052
- masterVolume = clamp4(volume, 0, 100);
17385
+ masterVolume = clamp5(volume, 0, 100);
17053
17386
  refs.masterVolume.value = String(masterVolume);
17054
17387
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17055
17388
  options.singingVoices?.setVolume(masterVolume / 100);
17056
17389
  },
17057
17390
  setDrumVolume: (volume) => {
17058
- drumVolume = clamp4(volume, 0, 100);
17391
+ drumVolume = clamp5(volume, 0, 100);
17059
17392
  refs.drumVolume.value = String(drumVolume);
17060
17393
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
17061
17394
  },
17062
17395
  setReverbAmount: (amount) => {
17063
- reverbAmount = clamp4(amount, 0, 100);
17396
+ reverbAmount = clamp5(amount, 0, 100);
17064
17397
  refs.reverbAmount.value = String(reverbAmount);
17065
17398
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
17066
17399
  options.onReverbChange?.(reverbAmount);
@@ -17084,7 +17417,7 @@ var mountDAW = (target, options = {}) => {
17084
17417
  options.onReverbPreDelayChange?.(reverbPreDelay);
17085
17418
  },
17086
17419
  setDelayAmount: (amount) => {
17087
- delayAmount = clamp4(amount, 0, 100);
17420
+ delayAmount = clamp5(amount, 0, 100);
17088
17421
  refs.delayAmount.value = String(delayAmount);
17089
17422
  refs.delayAmountLabel.textContent = `${delayAmount}%`;
17090
17423
  options.onDelayChange?.(delayAmount);
@@ -17133,7 +17466,7 @@ var mountDAW = (target, options = {}) => {
17133
17466
  t.vocalDelay = data.vocalDelay ?? 0;
17134
17467
  t.vocalGender = data.vocalGender ?? 50;
17135
17468
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17136
- t.vocalOctaveDouble = data.vocalOctaveDouble ?? false;
17469
+ t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
17137
17470
  },
17138
17471
  applyTrackInstrument: (trackIndex, instrumentName) => {
17139
17472
  const t = trackStates[trackIndex];
@@ -17255,7 +17588,7 @@ var playSingingMML = async (mml, options = {}) => {
17255
17588
  delaySend: (lt.delay ?? 0) / 100,
17256
17589
  gender: (lt.gender ?? 50) / 100,
17257
17590
  breathiness: (lt.breathiness ?? 50) / 100,
17258
- octaveDouble: lt.octaveDouble,
17591
+ octaveUnison: lt.octaveUnison,
17259
17592
  notes
17260
17593
  };
17261
17594
  });
@@ -17863,137 +18196,6 @@ var isSupported = midiJsonParser.isSupported;
17863
18196
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17864
18197
  URL.revokeObjectURL(url);
17865
18198
 
17866
- // src/channel-strip.ts
17867
- var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17868
- var compressionParams = (amount) => {
17869
- const t = clamp5(amount, 0, 100) / 100;
17870
- return {
17871
- threshold: 0 + (-24 - 0) * t,
17872
- ratio: 1 + (12 - 1) * t,
17873
- knee: 0 + (6 - 0) * t,
17874
- attack: 0.02 + (3e-3 - 0.02) * t,
17875
- release: 0.25 + (0.15 - 0.25) * t
17876
- };
17877
- };
17878
- var EQ_LOW_FREQ = 200;
17879
- var EQ_MID_FREQ = 1e3;
17880
- var EQ_HIGH_FREQ = 5e3;
17881
- var EQ_MID_Q = 1;
17882
- var EQ_MAX_DB = 12;
17883
- var createChannelStrip = (ctx, destination, options = {}) => {
17884
- const input = ctx.createGain();
17885
- const eqLow = ctx.createBiquadFilter();
17886
- eqLow.type = "lowshelf";
17887
- eqLow.frequency.value = EQ_LOW_FREQ;
17888
- eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17889
- const eqMid = ctx.createBiquadFilter();
17890
- eqMid.type = "peaking";
17891
- eqMid.frequency.value = EQ_MID_FREQ;
17892
- eqMid.Q.value = EQ_MID_Q;
17893
- eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17894
- const eqHigh = ctx.createBiquadFilter();
17895
- eqHigh.type = "highshelf";
17896
- eqHigh.frequency.value = EQ_HIGH_FREQ;
17897
- eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17898
- input.connect(eqLow);
17899
- eqLow.connect(eqMid);
17900
- eqMid.connect(eqHigh);
17901
- const compressor = ctx.createDynamicsCompressor();
17902
- const applyCompression = (amount) => {
17903
- const p = compressionParams(amount);
17904
- const now = ctx.currentTime;
17905
- compressor.threshold.setValueAtTime(p.threshold, now);
17906
- compressor.ratio.setValueAtTime(p.ratio, now);
17907
- compressor.knee.setValueAtTime(p.knee, now);
17908
- compressor.attack.setValueAtTime(p.attack, now);
17909
- compressor.release.setValueAtTime(p.release, now);
17910
- };
17911
- applyCompression(options.compression ?? 0);
17912
- const splitter = ctx.createChannelSplitter(2);
17913
- const mid = ctx.createGain();
17914
- mid.gain.value = 0.5;
17915
- splitter.connect(mid, 0);
17916
- splitter.connect(mid, 1);
17917
- const sideSum = ctx.createGain();
17918
- sideSum.gain.value = 1;
17919
- const sideL = ctx.createGain();
17920
- sideL.gain.value = 0.5;
17921
- const sideR = ctx.createGain();
17922
- sideR.gain.value = -0.5;
17923
- splitter.connect(sideL, 0);
17924
- splitter.connect(sideR, 1);
17925
- sideL.connect(sideSum);
17926
- sideR.connect(sideSum);
17927
- const widthGain = ctx.createGain();
17928
- sideSum.connect(widthGain);
17929
- const widthInv = ctx.createGain();
17930
- widthInv.gain.value = -1;
17931
- widthGain.connect(widthInv);
17932
- const outL = ctx.createGain();
17933
- mid.connect(outL);
17934
- widthGain.connect(outL);
17935
- const outR = ctx.createGain();
17936
- mid.connect(outR);
17937
- widthInv.connect(outR);
17938
- const merger = ctx.createChannelMerger(2);
17939
- outL.connect(merger, 0, 0);
17940
- outR.connect(merger, 0, 1);
17941
- const setWidth = (width) => {
17942
- widthGain.gain.setTargetAtTime(
17943
- clamp5(width, 0, 200) / 100,
17944
- ctx.currentTime,
17945
- 0.02
17946
- );
17947
- };
17948
- setWidth(options.width ?? 100);
17949
- eqHigh.connect(compressor);
17950
- compressor.connect(splitter);
17951
- merger.connect(destination);
17952
- return {
17953
- input,
17954
- setEqLow: (db) => {
17955
- eqLow.gain.setTargetAtTime(
17956
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17957
- ctx.currentTime,
17958
- 0.02
17959
- );
17960
- },
17961
- setEqMid: (db) => {
17962
- eqMid.gain.setTargetAtTime(
17963
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17964
- ctx.currentTime,
17965
- 0.02
17966
- );
17967
- },
17968
- setEqHigh: (db) => {
17969
- eqHigh.gain.setTargetAtTime(
17970
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17971
- ctx.currentTime,
17972
- 0.02
17973
- );
17974
- },
17975
- setCompression: applyCompression,
17976
- setWidth,
17977
- dispose: () => {
17978
- input.disconnect();
17979
- eqLow.disconnect();
17980
- eqMid.disconnect();
17981
- eqHigh.disconnect();
17982
- compressor.disconnect();
17983
- splitter.disconnect();
17984
- mid.disconnect();
17985
- sideSum.disconnect();
17986
- sideL.disconnect();
17987
- sideR.disconnect();
17988
- widthGain.disconnect();
17989
- widthInv.disconnect();
17990
- outL.disconnect();
17991
- outR.disconnect();
17992
- merger.disconnect();
17993
- }
17994
- };
17995
- };
17996
-
17997
18199
  // src/clip-meter.ts
17998
18200
  var createClipMeter = (ctx, source, options = {}) => {
17999
18201
  const threshold = options.threshold ?? 0.98;
@@ -18211,7 +18413,6 @@ var createDtmStudio = async (options = {}) => {
18211
18413
  reverbConvolver.normalize = true;
18212
18414
  const reverbWetGain = audioCtx.createGain();
18213
18415
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
18214
- masterGain.connect(reverbPreDelay);
18215
18416
  reverbPreDelay.connect(reverbConvolver);
18216
18417
  reverbConvolver.connect(reverbWetGain);
18217
18418
  const setReverbAmount = (amount) => {
@@ -18277,7 +18478,9 @@ var createDtmStudio = async (options = {}) => {
18277
18478
  const getChannelStrip = (trackId) => {
18278
18479
  let strip = channelStrips.get(trackId);
18279
18480
  if (!strip) {
18280
- strip = createChannelStrip(audioCtx, masterGain);
18481
+ strip = createChannelStrip(audioCtx, masterGain, {
18482
+ reverbBus: reverbPreDelay
18483
+ });
18281
18484
  channelStrips.set(trackId, strip);
18282
18485
  }
18283
18486
  return strip;
@@ -18314,7 +18517,8 @@ var createDtmStudio = async (options = {}) => {
18314
18517
  voicebanks,
18315
18518
  worldlineScriptUrl: options.worldlineScriptUrl,
18316
18519
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
18317
- // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
18520
+ // Convolver 入力へ直接センドする(PreDelayは経由しない。チャンネルストリップの
18521
+ // reverbSend とは別経路の、音節単位でより細かく制御できるセンド)。
18318
18522
  reverbBus: reverbConvolver,
18319
18523
  // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18320
18524
  delayBus: delayBus.input,
@@ -18612,6 +18816,7 @@ var createDtmStudio = async (options = {}) => {
18612
18816
  onScheduleFade: scheduleFade,
18613
18817
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
18614
18818
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18819
+ onTrackReverbSendChange: (trackId, amount) => getChannelStrip(trackId).setReverbSend(amount),
18615
18820
  onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18616
18821
  onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18617
18822
  onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),