@onjmin/dtm 0.1.88 → 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) => {
@@ -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,28 +2384,28 @@ 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;
@@ -3178,7 +3321,7 @@ var PITCH_MAP = {
3178
3321
  a: 9,
3179
3322
  b: 11
3180
3323
  };
3181
- 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));
3182
3325
  var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3183
3326
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3184
3327
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
@@ -3202,24 +3345,24 @@ var parseMmlMeta = (mml) => {
3202
3345
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3203
3346
  } else if (key === "reverb") {
3204
3347
  const rv = Number.parseInt(m[2], 10);
3205
- if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3348
+ if (!Number.isNaN(rv)) meta.reverb = clamp3(rv, 0, 100);
3206
3349
  } else if (key === "reverbdecay") {
3207
3350
  const rd = Number.parseInt(m[2], 10);
3208
- if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3351
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp3(rd, 3, 40);
3209
3352
  } else if (key === "reverbpredelay") {
3210
3353
  const rp = Number.parseInt(m[2], 10);
3211
- if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3354
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp3(rp, 0, 150);
3212
3355
  } else if (key === "delay") {
3213
3356
  const dv = Number.parseInt(m[2], 10);
3214
- if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3357
+ if (!Number.isNaN(dv)) meta.delay = clamp3(dv, 0, 100);
3215
3358
  } else if (key === "delaydiv") {
3216
3359
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3217
3360
  } else if (key === "fadein") {
3218
3361
  const fi = Number.parseInt(m[2], 10);
3219
- if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3362
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp3(fi, 0, 100);
3220
3363
  } else if (key === "fadeout") {
3221
3364
  const fo = Number.parseInt(m[2], 10);
3222
- if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3365
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp3(fo, 0, 100);
3223
3366
  } else if (key === "mode") {
3224
3367
  if (m[2] === "simple" || m[2] === "advanced") {
3225
3368
  meta.mode = m[2];
@@ -3236,7 +3379,7 @@ var parseMmlMeta = (mml) => {
3236
3379
  }
3237
3380
  for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3238
3381
  const idx = Number.parseInt(m[1], 10);
3239
- const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3382
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3240
3383
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3241
3384
  meta.trackCompression ??= {};
3242
3385
  meta.trackCompression[idx] = val;
@@ -3244,7 +3387,7 @@ var parseMmlMeta = (mml) => {
3244
3387
  }
3245
3388
  for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3246
3389
  const idx = Number.parseInt(m[1], 10);
3247
- const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3390
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 200);
3248
3391
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3249
3392
  meta.trackWidth ??= {};
3250
3393
  meta.trackWidth[idx] = val;
@@ -3252,7 +3395,7 @@ var parseMmlMeta = (mml) => {
3252
3395
  }
3253
3396
  for (const m of mml.matchAll(TRACK_REVERBSEND_DIRECTIVE)) {
3254
3397
  const idx = Number.parseInt(m[1], 10);
3255
- const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3398
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3256
3399
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3257
3400
  meta.trackReverbSend ??= {};
3258
3401
  meta.trackReverbSend[idx] = val;
@@ -3260,7 +3403,7 @@ var parseMmlMeta = (mml) => {
3260
3403
  }
3261
3404
  for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3262
3405
  const idx = Number.parseInt(m[1], 10);
3263
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3406
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3264
3407
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3265
3408
  meta.trackEqLow ??= {};
3266
3409
  meta.trackEqLow[idx] = val;
@@ -3268,7 +3411,7 @@ var parseMmlMeta = (mml) => {
3268
3411
  }
3269
3412
  for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3270
3413
  const idx = Number.parseInt(m[1], 10);
3271
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3414
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3272
3415
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3273
3416
  meta.trackEqMid ??= {};
3274
3417
  meta.trackEqMid[idx] = val;
@@ -3276,7 +3419,7 @@ var parseMmlMeta = (mml) => {
3276
3419
  }
3277
3420
  for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3278
3421
  const idx = Number.parseInt(m[1], 10);
3279
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3422
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3280
3423
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3281
3424
  meta.trackEqHigh ??= {};
3282
3425
  meta.trackEqHigh[idx] = val;
@@ -3425,7 +3568,7 @@ var parseMML = (mml, options = {}) => {
3425
3568
  numStr += body[j];
3426
3569
  j++;
3427
3570
  }
3428
- const len = numStr ? clamp2(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3571
+ const len = numStr ? clamp3(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3429
3572
  let steps = Math.round(stepsPerBar / len);
3430
3573
  while (j < body.length && body[j] === ".") {
3431
3574
  steps = Math.round(steps * 1.5);
@@ -3443,7 +3586,7 @@ var parseMML = (mml, options = {}) => {
3443
3586
  numStr += body[j];
3444
3587
  j++;
3445
3588
  }
3446
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3589
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3447
3590
  pushTok("octave", currentStep, 0, tokStart);
3448
3591
  } else if (ch === ">") {
3449
3592
  octave = Math.min(8, octave + 1);
@@ -3460,7 +3603,7 @@ var parseMML = (mml, options = {}) => {
3460
3603
  numStr += body[j];
3461
3604
  j++;
3462
3605
  }
3463
- baseLength = clamp2(Number.parseInt(numStr, 10) || 16, 1, 64);
3606
+ baseLength = clamp3(Number.parseInt(numStr, 10) || 16, 1, 64);
3464
3607
  pushTok("length", currentStep, 0, tokStart);
3465
3608
  } else if (ch === "r") {
3466
3609
  j++;
@@ -3477,10 +3620,10 @@ var parseMML = (mml, options = {}) => {
3477
3620
  }
3478
3621
  if (ch === "t" && numStr) {
3479
3622
  if (bpm === null) {
3480
- bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3623
+ bpm = clamp3(Number.parseInt(numStr, 10), 1, 255);
3481
3624
  }
3482
3625
  } else if (ch === "v" && numStr) {
3483
- velocity = clamp2(Number.parseInt(numStr, 10), 0, 127);
3626
+ velocity = clamp3(Number.parseInt(numStr, 10), 0, 127);
3484
3627
  trackVelocity.set(trackIndex, velocity);
3485
3628
  }
3486
3629
  pushTok("ctrl", currentStep, 0, tokStart);
@@ -3514,7 +3657,7 @@ var parseMML = (mml, options = {}) => {
3514
3657
  numStr += body[j];
3515
3658
  j++;
3516
3659
  }
3517
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3660
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3518
3661
  } else {
3519
3662
  j++;
3520
3663
  }
@@ -3576,6 +3719,38 @@ var parseMML = (mml, options = {}) => {
3576
3719
  };
3577
3720
  };
3578
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
+
3579
3754
  // src/sequencer.ts
3580
3755
  var STEPS_PER_BEAT = 48;
3581
3756
  var PLAN_TIME = 0.5;
@@ -6153,9 +6328,54 @@ var playPlacements = (placements, options) => {
6153
6328
  });
6154
6329
  const ownsCtx = !options.audioContext;
6155
6330
  const ctx = options.audioContext ?? new AudioContext();
6156
- const destination = options.destination ?? ctx.destination;
6331
+ const rawDestination = options.destination ?? ctx.destination;
6157
6332
  const useSynth = options.synth ?? !options.onPlayNote;
6158
- 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
+ });
6159
6379
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
6160
6380
  let playing = false;
6161
6381
  const seq = createSequencer({
@@ -6170,12 +6390,14 @@ var playPlacements = (placements, options) => {
6170
6390
  getAudioTime: () => ctx.currentTime,
6171
6391
  onPlayNote: (e) => {
6172
6392
  options.onPlayNote?.(e);
6173
- synth?.playNote(e);
6393
+ if (!useSynth) return;
6394
+ const index = trackIndexById.get(e.trackId);
6395
+ (index === void 0 ? drumSynth : getSynth(index))?.playNote(e);
6174
6396
  },
6175
6397
  onPlayDrum: (e) => {
6176
6398
  const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
6177
6399
  options.onPlayDrum?.({ ...e, velocity });
6178
- synth?.playDrum({ ...e, velocity });
6400
+ drumSynth?.playDrum({ ...e, velocity });
6179
6401
  },
6180
6402
  onTick: (step) => {
6181
6403
  options.onTick?.(step);
@@ -6226,6 +6448,7 @@ var playPlacements = (placements, options) => {
6226
6448
  if (pauseWhenHidden && typeof document !== "undefined") {
6227
6449
  document.removeEventListener("visibilitychange", onVisibilityChange);
6228
6450
  }
6451
+ for (const strip of channelStrips.values()) strip.dispose();
6229
6452
  if (ownsCtx) void ctx.close();
6230
6453
  };
6231
6454
  return {
@@ -6245,7 +6468,16 @@ var playMML = (mml, options = {}) => {
6245
6468
  bpm,
6246
6469
  metaVolume: meta.volume,
6247
6470
  metaDrum: meta.drum,
6248
- 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
6249
6481
  });
6250
6482
  };
6251
6483
  var playNote = (options) => {
@@ -9551,20 +9783,23 @@ var adjustZone = async (ctx, fontName, zone) => {
9551
9783
  const newLength = attackLength + loopLengthFrame * repeatCount + releaseLength;
9552
9784
  let totalPeak = 0;
9553
9785
  let loopPeak = 0;
9554
- if (oldBuf.numberOfChannels > 0) {
9555
- const ch0 = oldBuf.getChannelData(0);
9556
- for (let i2 = 0; i2 < ch0.length; i2++) {
9557
- 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]);
9558
9790
  if (abs > totalPeak) totalPeak = abs;
9559
9791
  if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9560
9792
  if (abs > loopPeak) loopPeak = abs;
9561
9793
  }
9562
9794
  }
9563
9795
  }
9796
+ const decay = isDecayInstrument(fontName);
9797
+ const targetRatio = decay ? 0.4 : 0.75;
9798
+ const maxMultiplier = decay ? 6 : 20;
9564
9799
  let gainMultiplier = 1;
9565
- if (!isDecayInstrument(fontName) && loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9566
- gainMultiplier = totalPeak * 0.75 / loopPeak;
9567
- 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;
9568
9803
  }
9569
9804
  try {
9570
9805
  const newBuf = ctx.createBuffer(
@@ -10896,14 +11131,14 @@ var DELAY_DIVISIONS = [
10896
11131
  { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10897
11132
  { value: "16", label: "16\u5206", beats: 0.25 }
10898
11133
  ];
10899
- 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));
10900
11135
  var divisionToSeconds = (division, bpm) => {
10901
11136
  const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10902
11137
  const safeBpm = bpm > 0 ? bpm : 120;
10903
11138
  return 60 / safeBpm * beats;
10904
11139
  };
10905
11140
  var DELAY_MAX_WET = 0.45;
10906
- var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
11141
+ var delayAmountToGain = (amount) => clamp4(amount, 0, 100) / 100 * DELAY_MAX_WET;
10907
11142
  var FEEDBACK_GAIN = 0.3;
10908
11143
  var MAX_DELAY_SEC = 2;
10909
11144
  var createDelayBus = (ctx, destination, options = {}) => {
@@ -12978,38 +13213,6 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12978
13213
  return chordNotes / notes.length >= threshold;
12979
13214
  };
12980
13215
 
12981
- // src/reverb.ts
12982
- var DEFAULT_REVERB_DECAY_SEC = 2.2;
12983
- var MIN_REVERB_DECAY_SEC = 0.3;
12984
- var MAX_REVERB_DECAY_SEC = 4;
12985
- var IMPULSE_DECAY_CURVE = 2.5;
12986
- var DEFAULT_REVERB_PREDELAY_MS = 0;
12987
- var MIN_REVERB_PREDELAY_MS = 0;
12988
- var MAX_REVERB_PREDELAY_MS = 150;
12989
- var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
12990
- const rate = ctx.sampleRate;
12991
- const length = Math.max(
12992
- 1,
12993
- Math.floor(
12994
- rate * Math.max(
12995
- MIN_REVERB_DECAY_SEC,
12996
- Math.min(MAX_REVERB_DECAY_SEC, decaySec)
12997
- )
12998
- )
12999
- );
13000
- const impulse = ctx.createBuffer(2, length, rate);
13001
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
13002
- const data = impulse.getChannelData(ch);
13003
- for (let i2 = 0; i2 < length; i2++) {
13004
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
13005
- data[i2] = (Math.random() * 2 - 1) * envelope;
13006
- }
13007
- }
13008
- return impulse;
13009
- };
13010
- var REVERB_MAX_WET = 0.6;
13011
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
13012
-
13013
13216
  // src/daw.ts
13014
13217
  var CHORD_INFO_HTML2 = `
13015
13218
  <div class="dtm-modal-body-content">
@@ -13445,7 +13648,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13445
13648
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13446
13649
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13447
13650
  };
13448
- 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);
13449
13652
  var normalizeInstrumentName = (name) => {
13450
13653
  if (!name) return "";
13451
13654
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13534,6 +13737,8 @@ var mountDAW = (target, options = {}) => {
13534
13737
  refs.drumFontSelect.value = currentDrumFont;
13535
13738
  let currentInstrument = "";
13536
13739
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
13740
+ let trackFxAdvancedOpen = false;
13741
+ let lyricAdvancedOpen = false;
13537
13742
  let activeToolMode = "pen";
13538
13743
  let currentInsertLength = 48;
13539
13744
  let snapGridSteps = 12;
@@ -13830,7 +14035,7 @@ var mountDAW = (target, options = {}) => {
13830
14035
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13831
14036
  const ratio = currentOffsetX / maxOffsetX;
13832
14037
  refs.hScrollThumb.style.width = `${thumbW}px`;
13833
- 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`;
13834
14039
  }
13835
14040
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13836
14041
  const sbH = refs.vScroll.clientHeight;
@@ -13900,9 +14105,9 @@ var mountDAW = (target, options = {}) => {
13900
14105
  if (maxOffsetX <= 0) return;
13901
14106
  const rect = refs.hScroll.getBoundingClientRect();
13902
14107
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13903
- 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);
13904
14109
  const ratio = x2 / (rect.width - thumbW);
13905
- currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
14110
+ currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
13906
14111
  setDrawOffset(currentOffsetX, currentOffsetY);
13907
14112
  redrawAll();
13908
14113
  };
@@ -13911,9 +14116,9 @@ var mountDAW = (target, options = {}) => {
13911
14116
  if (maxOffset <= 0) return;
13912
14117
  const rect = refs.vScroll.getBoundingClientRect();
13913
14118
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13914
- 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);
13915
14120
  const ratio = y / (rect.height - thumbH);
13916
- currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
14121
+ currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
13917
14122
  setDrawOffset(currentOffsetX, currentOffsetY);
13918
14123
  redrawAll();
13919
14124
  };
@@ -13960,8 +14165,8 @@ var mountDAW = (target, options = {}) => {
13960
14165
  if (dx !== 0 || dy !== 0) {
13961
14166
  const maxOffsetX = getMaxOffsetX();
13962
14167
  const maxOffsetY = getMaxOffsetY();
13963
- currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
13964
- currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
14168
+ currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
14169
+ currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
13965
14170
  setDrawOffset(currentOffsetX, currentOffsetY);
13966
14171
  onPointerMove(lastMoveEvent);
13967
14172
  }
@@ -14351,12 +14556,12 @@ var mountDAW = (target, options = {}) => {
14351
14556
  "wheel",
14352
14557
  (event) => {
14353
14558
  event.preventDefault();
14354
- currentOffsetY = clamp4(
14559
+ currentOffsetY = clamp5(
14355
14560
  currentOffsetY + event.deltaY,
14356
14561
  0,
14357
14562
  getMaxOffsetY()
14358
14563
  );
14359
- currentOffsetX = clamp4(
14564
+ currentOffsetX = clamp5(
14360
14565
  currentOffsetX + event.deltaX,
14361
14566
  0,
14362
14567
  getMaxOffsetX()
@@ -14395,7 +14600,7 @@ var mountDAW = (target, options = {}) => {
14395
14600
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14396
14601
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14397
14602
  refs.zoomXLabel.textContent = `${zoomX}%`;
14398
- currentOffsetX = clamp4(
14603
+ currentOffsetX = clamp5(
14399
14604
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14400
14605
  0,
14401
14606
  getMaxOffsetX()
@@ -14408,7 +14613,7 @@ var mountDAW = (target, options = {}) => {
14408
14613
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14409
14614
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14410
14615
  refs.zoomYLabel.textContent = `${zoomY}%`;
14411
- currentOffsetY = clamp4(
14616
+ currentOffsetY = clamp5(
14412
14617
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14413
14618
  0,
14414
14619
  getMaxOffsetY()
@@ -14458,7 +14663,7 @@ var mountDAW = (target, options = {}) => {
14458
14663
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14459
14664
  if (currentPlayStep > threshold) {
14460
14665
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14461
- currentOffsetX = clamp4(
14666
+ currentOffsetX = clamp5(
14462
14667
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14463
14668
  0,
14464
14669
  getMaxOffsetX()
@@ -14545,7 +14750,7 @@ var mountDAW = (target, options = {}) => {
14545
14750
  }
14546
14751
  if (playbackState !== "paused") {
14547
14752
  const canvas = getGridCanvas();
14548
- currentOffsetX = clamp4(
14753
+ currentOffsetX = clamp5(
14549
14754
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14550
14755
  0,
14551
14756
  getMaxOffsetX()
@@ -14649,7 +14854,7 @@ var mountDAW = (target, options = {}) => {
14649
14854
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
14650
14855
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14651
14856
  </div>
14652
- <details class="dtm-advanced" data-dtm="track-fx-advanced">
14857
+ <details class="dtm-advanced" data-dtm="track-fx-advanced" ${trackFxAdvancedOpen ? "open" : ""}>
14653
14858
  <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14654
14859
  <div class="dtm-row">
14655
14860
  <span class="dtm-label">EQ\u4F4E\u57DF</span>
@@ -14686,6 +14891,11 @@ var mountDAW = (target, options = {}) => {
14686
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>
14687
14892
  </div>
14688
14893
  </details>`;
14894
+ refs.trackBody.querySelector(
14895
+ '[data-dtm="track-fx-advanced"]'
14896
+ ).addEventListener("toggle", (e) => {
14897
+ trackFxAdvancedOpen = e.target.open;
14898
+ });
14689
14899
  const volInput = refs.trackBody.querySelector(
14690
14900
  '[data-dtm="track-vol"]'
14691
14901
  );
@@ -14899,7 +15109,7 @@ var mountDAW = (target, options = {}) => {
14899
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">
14900
15110
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
14901
15111
  </div>
14902
- <details class="dtm-advanced" data-dtm="lyric-advanced">
15112
+ <details class="dtm-advanced" data-dtm="lyric-advanced" ${lyricAdvancedOpen ? "open" : ""}>
14903
15113
  <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14904
15114
  <div class="dtm-row">
14905
15115
  <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
@@ -14960,6 +15170,11 @@ var mountDAW = (target, options = {}) => {
14960
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>
14961
15171
  </div>`;
14962
15172
  refs.trackBody.appendChild(lyricDiv);
15173
+ lyricDiv.querySelector(
15174
+ '[data-dtm="lyric-advanced"]'
15175
+ ).addEventListener("toggle", (e) => {
15176
+ lyricAdvancedOpen = e.target.open;
15177
+ });
14963
15178
  const lyricModelSel = lyricDiv.querySelector(
14964
15179
  '[data-dtm="lyric-model"]'
14965
15180
  );
@@ -15582,7 +15797,7 @@ var mountDAW = (target, options = {}) => {
15582
15797
  const canvas = getGridCanvas();
15583
15798
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15584
15799
  const logicalY = yIndex * renderConfig.keyHeight;
15585
- currentOffsetY = clamp4(
15800
+ currentOffsetY = clamp5(
15586
15801
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15587
15802
  0,
15588
15803
  getMaxOffsetY()
@@ -16157,8 +16372,8 @@ var mountDAW = (target, options = {}) => {
16157
16372
  refs.reverbAmount.value = "20";
16158
16373
  refs.reverbAmountLabel.textContent = "20%";
16159
16374
  options.onReverbChange?.(20);
16160
- const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16161
- reverbDecay = clamp4(
16375
+ const bpmT = clamp5((bpm - 60) / (180 - 60), 0, 1);
16376
+ reverbDecay = clamp5(
16162
16377
  3 - bpmT * (3 - 0.9),
16163
16378
  MIN_REVERB_DECAY_SEC,
16164
16379
  MAX_REVERB_DECAY_SEC
@@ -17089,7 +17304,7 @@ var mountDAW = (target, options = {}) => {
17089
17304
  currentPlayStep = step;
17090
17305
  playbackState = "paused";
17091
17306
  const canvas = getGridCanvas();
17092
- currentOffsetX = clamp4(
17307
+ currentOffsetX = clamp5(
17093
17308
  step * renderConfig.stepWidth - canvas.width * 0.5,
17094
17309
  0,
17095
17310
  getMaxOffsetX()
@@ -17135,11 +17350,11 @@ var mountDAW = (target, options = {}) => {
17135
17350
  getViewState,
17136
17351
  setViewState: (state) => {
17137
17352
  if (typeof state.zoomX === "number") {
17138
- zoomX = clamp4(state.zoomX, 25, 200);
17353
+ zoomX = clamp5(state.zoomX, 25, 200);
17139
17354
  applyZoomX();
17140
17355
  }
17141
17356
  if (typeof state.zoomY === "number") {
17142
- zoomY = clamp4(state.zoomY, 50, 200);
17357
+ zoomY = clamp5(state.zoomY, 50, 200);
17143
17358
  applyZoomY();
17144
17359
  }
17145
17360
  if (typeof state.decomposeChord === "boolean") {
@@ -17161,24 +17376,24 @@ var mountDAW = (target, options = {}) => {
17161
17376
  forcePauseAt,
17162
17377
  setLoading,
17163
17378
  setMasterVolume: (volume) => {
17164
- masterVolume = clamp4(volume, 0, 100);
17379
+ masterVolume = clamp5(volume, 0, 100);
17165
17380
  refs.masterVolume.value = String(masterVolume);
17166
17381
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17167
17382
  options.singingVoices?.setVolume(masterVolume / 100);
17168
17383
  },
17169
17384
  setVolume: (volume) => {
17170
- masterVolume = clamp4(volume, 0, 100);
17385
+ masterVolume = clamp5(volume, 0, 100);
17171
17386
  refs.masterVolume.value = String(masterVolume);
17172
17387
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17173
17388
  options.singingVoices?.setVolume(masterVolume / 100);
17174
17389
  },
17175
17390
  setDrumVolume: (volume) => {
17176
- drumVolume = clamp4(volume, 0, 100);
17391
+ drumVolume = clamp5(volume, 0, 100);
17177
17392
  refs.drumVolume.value = String(drumVolume);
17178
17393
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
17179
17394
  },
17180
17395
  setReverbAmount: (amount) => {
17181
- reverbAmount = clamp4(amount, 0, 100);
17396
+ reverbAmount = clamp5(amount, 0, 100);
17182
17397
  refs.reverbAmount.value = String(reverbAmount);
17183
17398
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
17184
17399
  options.onReverbChange?.(reverbAmount);
@@ -17202,7 +17417,7 @@ var mountDAW = (target, options = {}) => {
17202
17417
  options.onReverbPreDelayChange?.(reverbPreDelay);
17203
17418
  },
17204
17419
  setDelayAmount: (amount) => {
17205
- delayAmount = clamp4(amount, 0, 100);
17420
+ delayAmount = clamp5(amount, 0, 100);
17206
17421
  refs.delayAmount.value = String(delayAmount);
17207
17422
  refs.delayAmountLabel.textContent = `${delayAmount}%`;
17208
17423
  options.onDelayChange?.(delayAmount);
@@ -17981,149 +18196,6 @@ var isSupported = midiJsonParser.isSupported;
17981
18196
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17982
18197
  URL.revokeObjectURL(url);
17983
18198
 
17984
- // src/channel-strip.ts
17985
- var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17986
- var compressionParams = (amount) => {
17987
- const t = clamp5(amount, 0, 100) / 100;
17988
- return {
17989
- threshold: 0 + (-24 - 0) * t,
17990
- ratio: 1 + (12 - 1) * t,
17991
- knee: 0 + (6 - 0) * t,
17992
- attack: 0.02 + (3e-3 - 0.02) * t,
17993
- release: 0.25 + (0.15 - 0.25) * t
17994
- };
17995
- };
17996
- var EQ_LOW_FREQ = 200;
17997
- var EQ_MID_FREQ = 1e3;
17998
- var EQ_HIGH_FREQ = 5e3;
17999
- var EQ_MID_Q = 1;
18000
- var EQ_MAX_DB = 12;
18001
- var createChannelStrip = (ctx, destination, options = {}) => {
18002
- const input = ctx.createGain();
18003
- const eqLow = ctx.createBiquadFilter();
18004
- eqLow.type = "lowshelf";
18005
- eqLow.frequency.value = EQ_LOW_FREQ;
18006
- eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18007
- const eqMid = ctx.createBiquadFilter();
18008
- eqMid.type = "peaking";
18009
- eqMid.frequency.value = EQ_MID_FREQ;
18010
- eqMid.Q.value = EQ_MID_Q;
18011
- eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18012
- const eqHigh = ctx.createBiquadFilter();
18013
- eqHigh.type = "highshelf";
18014
- eqHigh.frequency.value = EQ_HIGH_FREQ;
18015
- eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18016
- input.connect(eqLow);
18017
- eqLow.connect(eqMid);
18018
- eqMid.connect(eqHigh);
18019
- const compressor = ctx.createDynamicsCompressor();
18020
- const applyCompression = (amount) => {
18021
- const p = compressionParams(amount);
18022
- const now = ctx.currentTime;
18023
- compressor.threshold.setValueAtTime(p.threshold, now);
18024
- compressor.ratio.setValueAtTime(p.ratio, now);
18025
- compressor.knee.setValueAtTime(p.knee, now);
18026
- compressor.attack.setValueAtTime(p.attack, now);
18027
- compressor.release.setValueAtTime(p.release, now);
18028
- };
18029
- applyCompression(options.compression ?? 0);
18030
- const splitter = ctx.createChannelSplitter(2);
18031
- const mid = ctx.createGain();
18032
- mid.gain.value = 0.5;
18033
- splitter.connect(mid, 0);
18034
- splitter.connect(mid, 1);
18035
- const sideSum = ctx.createGain();
18036
- sideSum.gain.value = 1;
18037
- const sideL = ctx.createGain();
18038
- sideL.gain.value = 0.5;
18039
- const sideR = ctx.createGain();
18040
- sideR.gain.value = -0.5;
18041
- splitter.connect(sideL, 0);
18042
- splitter.connect(sideR, 1);
18043
- sideL.connect(sideSum);
18044
- sideR.connect(sideSum);
18045
- const widthGain = ctx.createGain();
18046
- sideSum.connect(widthGain);
18047
- const widthInv = ctx.createGain();
18048
- widthInv.gain.value = -1;
18049
- widthGain.connect(widthInv);
18050
- const outL = ctx.createGain();
18051
- mid.connect(outL);
18052
- widthGain.connect(outL);
18053
- const outR = ctx.createGain();
18054
- mid.connect(outR);
18055
- widthInv.connect(outR);
18056
- const merger = ctx.createChannelMerger(2);
18057
- outL.connect(merger, 0, 0);
18058
- outR.connect(merger, 0, 1);
18059
- const setWidth = (width) => {
18060
- widthGain.gain.setTargetAtTime(
18061
- clamp5(width, 0, 200) / 100,
18062
- ctx.currentTime,
18063
- 0.02
18064
- );
18065
- };
18066
- setWidth(options.width ?? 100);
18067
- eqHigh.connect(compressor);
18068
- compressor.connect(splitter);
18069
- merger.connect(destination);
18070
- const reverbSendGain = ctx.createGain();
18071
- reverbSendGain.gain.value = clamp5(options.reverbSend ?? 0, 0, 100) / 100;
18072
- merger.connect(reverbSendGain);
18073
- if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
18074
- return {
18075
- input,
18076
- setEqLow: (db) => {
18077
- eqLow.gain.setTargetAtTime(
18078
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18079
- ctx.currentTime,
18080
- 0.02
18081
- );
18082
- },
18083
- setEqMid: (db) => {
18084
- eqMid.gain.setTargetAtTime(
18085
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18086
- ctx.currentTime,
18087
- 0.02
18088
- );
18089
- },
18090
- setEqHigh: (db) => {
18091
- eqHigh.gain.setTargetAtTime(
18092
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18093
- ctx.currentTime,
18094
- 0.02
18095
- );
18096
- },
18097
- setCompression: applyCompression,
18098
- setWidth,
18099
- setReverbSend: (amount) => {
18100
- reverbSendGain.gain.setTargetAtTime(
18101
- clamp5(amount, 0, 100) / 100,
18102
- ctx.currentTime,
18103
- 0.02
18104
- );
18105
- },
18106
- dispose: () => {
18107
- input.disconnect();
18108
- eqLow.disconnect();
18109
- eqMid.disconnect();
18110
- eqHigh.disconnect();
18111
- compressor.disconnect();
18112
- splitter.disconnect();
18113
- mid.disconnect();
18114
- sideSum.disconnect();
18115
- sideL.disconnect();
18116
- sideR.disconnect();
18117
- widthGain.disconnect();
18118
- widthInv.disconnect();
18119
- outL.disconnect();
18120
- outR.disconnect();
18121
- merger.disconnect();
18122
- reverbSendGain.disconnect();
18123
- }
18124
- };
18125
- };
18126
-
18127
18199
  // src/clip-meter.ts
18128
18200
  var createClipMeter = (ctx, source, options = {}) => {
18129
18201
  const threshold = options.threshold ?? 0.98;