@onjmin/dtm 0.1.88 → 0.1.90

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,37 @@ 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 reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100;
3752
+
3579
3753
  // src/sequencer.ts
3580
3754
  var STEPS_PER_BEAT = 48;
3581
3755
  var PLAN_TIME = 0.5;
@@ -6153,9 +6327,54 @@ var playPlacements = (placements, options) => {
6153
6327
  });
6154
6328
  const ownsCtx = !options.audioContext;
6155
6329
  const ctx = options.audioContext ?? new AudioContext();
6156
- const destination = options.destination ?? ctx.destination;
6330
+ const rawDestination = options.destination ?? ctx.destination;
6157
6331
  const useSynth = options.synth ?? !options.onPlayNote;
6158
- const synth = useSynth ? createSynth(ctx, destination) : null;
6332
+ const finalMix = ctx.createGain();
6333
+ const masterGain = ctx.createGain();
6334
+ masterGain.connect(finalMix);
6335
+ const reverbPreDelay = ctx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
6336
+ reverbPreDelay.delayTime.value = (options.metaReverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
6337
+ const reverbConvolver = ctx.createConvolver();
6338
+ const reverbDecaySec = (options.metaReverbDecay ?? 22) / 10 || DEFAULT_REVERB_DECAY_SEC;
6339
+ reverbConvolver.buffer = createReverbImpulse(ctx, reverbDecaySec);
6340
+ reverbConvolver.normalize = true;
6341
+ const reverbWetGain = ctx.createGain();
6342
+ reverbWetGain.gain.value = reverbAmountToGain(options.metaReverb ?? 0);
6343
+ reverbPreDelay.connect(reverbConvolver);
6344
+ reverbConvolver.connect(reverbWetGain);
6345
+ reverbWetGain.connect(finalMix);
6346
+ finalMix.connect(rawDestination);
6347
+ const channelStrips = /* @__PURE__ */ new Map();
6348
+ const getChannelStrip = (index) => {
6349
+ let strip = channelStrips.get(index);
6350
+ if (!strip) {
6351
+ strip = createChannelStrip(ctx, masterGain, {
6352
+ compression: options.trackCompression?.[index] ?? 0,
6353
+ width: options.trackWidth?.[index] ?? 100,
6354
+ eqLow: options.trackEqLow?.[index] ?? 0,
6355
+ eqMid: options.trackEqMid?.[index] ?? 0,
6356
+ eqHigh: options.trackEqHigh?.[index] ?? 0,
6357
+ reverbSend: options.trackReverbSend?.[index] ?? 0,
6358
+ reverbBus: reverbPreDelay
6359
+ });
6360
+ channelStrips.set(index, strip);
6361
+ }
6362
+ return strip;
6363
+ };
6364
+ const synths = /* @__PURE__ */ new Map();
6365
+ const getSynth = (index) => {
6366
+ let s = synths.get(index);
6367
+ if (!s) {
6368
+ s = createSynth(ctx, getChannelStrip(index).input);
6369
+ synths.set(index, s);
6370
+ }
6371
+ return s;
6372
+ };
6373
+ const drumSynth = useSynth ? createSynth(ctx, masterGain) : null;
6374
+ const trackIndexById = /* @__PURE__ */ new Map();
6375
+ trackIndices.forEach((idx) => {
6376
+ trackIndexById.set(TRACK_ID_BY_INDEX[idx] ?? `t${idx}`, idx);
6377
+ });
6159
6378
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
6160
6379
  let playing = false;
6161
6380
  const seq = createSequencer({
@@ -6170,12 +6389,14 @@ var playPlacements = (placements, options) => {
6170
6389
  getAudioTime: () => ctx.currentTime,
6171
6390
  onPlayNote: (e) => {
6172
6391
  options.onPlayNote?.(e);
6173
- synth?.playNote(e);
6392
+ if (!useSynth) return;
6393
+ const index = trackIndexById.get(e.trackId);
6394
+ (index === void 0 ? drumSynth : getSynth(index))?.playNote(e);
6174
6395
  },
6175
6396
  onPlayDrum: (e) => {
6176
6397
  const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
6177
6398
  options.onPlayDrum?.({ ...e, velocity });
6178
- synth?.playDrum({ ...e, velocity });
6399
+ drumSynth?.playDrum({ ...e, velocity });
6179
6400
  },
6180
6401
  onTick: (step) => {
6181
6402
  options.onTick?.(step);
@@ -6226,6 +6447,7 @@ var playPlacements = (placements, options) => {
6226
6447
  if (pauseWhenHidden && typeof document !== "undefined") {
6227
6448
  document.removeEventListener("visibilitychange", onVisibilityChange);
6228
6449
  }
6450
+ for (const strip of channelStrips.values()) strip.dispose();
6229
6451
  if (ownsCtx) void ctx.close();
6230
6452
  };
6231
6453
  return {
@@ -6245,7 +6467,16 @@ var playMML = (mml, options = {}) => {
6245
6467
  bpm,
6246
6468
  metaVolume: meta.volume,
6247
6469
  metaDrum: meta.drum,
6248
- metaDrumVolume: meta.drumVolume
6470
+ metaDrumVolume: meta.drumVolume,
6471
+ metaReverb: meta.reverb,
6472
+ metaReverbDecay: meta.reverbDecay,
6473
+ metaReverbPreDelay: meta.reverbPreDelay,
6474
+ trackCompression: meta.trackCompression,
6475
+ trackWidth: meta.trackWidth,
6476
+ trackReverbSend: meta.trackReverbSend,
6477
+ trackEqLow: meta.trackEqLow,
6478
+ trackEqMid: meta.trackEqMid,
6479
+ trackEqHigh: meta.trackEqHigh
6249
6480
  });
6250
6481
  };
6251
6482
  var playNote = (options) => {
@@ -9551,20 +9782,23 @@ var adjustZone = async (ctx, fontName, zone) => {
9551
9782
  const newLength = attackLength + loopLengthFrame * repeatCount + releaseLength;
9552
9783
  let totalPeak = 0;
9553
9784
  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]);
9785
+ for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9786
+ const chData = oldBuf.getChannelData(ch);
9787
+ for (let i2 = 0; i2 < chData.length; i2++) {
9788
+ const abs = Math.abs(chData[i2]);
9558
9789
  if (abs > totalPeak) totalPeak = abs;
9559
9790
  if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9560
9791
  if (abs > loopPeak) loopPeak = abs;
9561
9792
  }
9562
9793
  }
9563
9794
  }
9795
+ const decay = isDecayInstrument(fontName);
9796
+ const targetRatio = decay ? 0.4 : 0.75;
9797
+ const maxMultiplier = decay ? 6 : 20;
9564
9798
  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;
9799
+ if (loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9800
+ gainMultiplier = totalPeak * targetRatio / loopPeak;
9801
+ if (gainMultiplier > maxMultiplier) gainMultiplier = maxMultiplier;
9568
9802
  }
9569
9803
  try {
9570
9804
  const newBuf = ctx.createBuffer(
@@ -10896,14 +11130,14 @@ var DELAY_DIVISIONS = [
10896
11130
  { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10897
11131
  { value: "16", label: "16\u5206", beats: 0.25 }
10898
11132
  ];
10899
- var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
11133
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10900
11134
  var divisionToSeconds = (division, bpm) => {
10901
11135
  const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10902
11136
  const safeBpm = bpm > 0 ? bpm : 120;
10903
11137
  return 60 / safeBpm * beats;
10904
11138
  };
10905
11139
  var DELAY_MAX_WET = 0.45;
10906
- var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
11140
+ var delayAmountToGain = (amount) => clamp4(amount, 0, 100) / 100 * DELAY_MAX_WET;
10907
11141
  var FEEDBACK_GAIN = 0.3;
10908
11142
  var MAX_DELAY_SEC = 2;
10909
11143
  var createDelayBus = (ctx, destination, options = {}) => {
@@ -12978,38 +13212,6 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12978
13212
  return chordNotes / notes.length >= threshold;
12979
13213
  };
12980
13214
 
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
13215
  // src/daw.ts
13014
13216
  var CHORD_INFO_HTML2 = `
13015
13217
  <div class="dtm-modal-body-content">
@@ -13445,7 +13647,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13445
13647
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13446
13648
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13447
13649
  };
13448
- var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
13650
+ var clamp5 = (v, min, max) => Math.min(Math.max(v, min), max);
13449
13651
  var normalizeInstrumentName = (name) => {
13450
13652
  if (!name) return "";
13451
13653
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13534,6 +13736,8 @@ var mountDAW = (target, options = {}) => {
13534
13736
  refs.drumFontSelect.value = currentDrumFont;
13535
13737
  let currentInstrument = "";
13536
13738
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
13739
+ let trackFxAdvancedOpen = false;
13740
+ let lyricAdvancedOpen = false;
13537
13741
  let activeToolMode = "pen";
13538
13742
  let currentInsertLength = 48;
13539
13743
  let snapGridSteps = 12;
@@ -13830,7 +14034,7 @@ var mountDAW = (target, options = {}) => {
13830
14034
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13831
14035
  const ratio = currentOffsetX / maxOffsetX;
13832
14036
  refs.hScrollThumb.style.width = `${thumbW}px`;
13833
- refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
14037
+ refs.hScrollThumb.style.left = `${clamp5(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13834
14038
  }
13835
14039
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13836
14040
  const sbH = refs.vScroll.clientHeight;
@@ -13900,9 +14104,9 @@ var mountDAW = (target, options = {}) => {
13900
14104
  if (maxOffsetX <= 0) return;
13901
14105
  const rect = refs.hScroll.getBoundingClientRect();
13902
14106
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13903
- const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
14107
+ const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13904
14108
  const ratio = x2 / (rect.width - thumbW);
13905
- currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
14109
+ currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
13906
14110
  setDrawOffset(currentOffsetX, currentOffsetY);
13907
14111
  redrawAll();
13908
14112
  };
@@ -13911,9 +14115,9 @@ var mountDAW = (target, options = {}) => {
13911
14115
  if (maxOffset <= 0) return;
13912
14116
  const rect = refs.vScroll.getBoundingClientRect();
13913
14117
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13914
- const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
14118
+ const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13915
14119
  const ratio = y / (rect.height - thumbH);
13916
- currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
14120
+ currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
13917
14121
  setDrawOffset(currentOffsetX, currentOffsetY);
13918
14122
  redrawAll();
13919
14123
  };
@@ -13960,8 +14164,8 @@ var mountDAW = (target, options = {}) => {
13960
14164
  if (dx !== 0 || dy !== 0) {
13961
14165
  const maxOffsetX = getMaxOffsetX();
13962
14166
  const maxOffsetY = getMaxOffsetY();
13963
- currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
13964
- currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
14167
+ currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
14168
+ currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
13965
14169
  setDrawOffset(currentOffsetX, currentOffsetY);
13966
14170
  onPointerMove(lastMoveEvent);
13967
14171
  }
@@ -14351,12 +14555,12 @@ var mountDAW = (target, options = {}) => {
14351
14555
  "wheel",
14352
14556
  (event) => {
14353
14557
  event.preventDefault();
14354
- currentOffsetY = clamp4(
14558
+ currentOffsetY = clamp5(
14355
14559
  currentOffsetY + event.deltaY,
14356
14560
  0,
14357
14561
  getMaxOffsetY()
14358
14562
  );
14359
- currentOffsetX = clamp4(
14563
+ currentOffsetX = clamp5(
14360
14564
  currentOffsetX + event.deltaX,
14361
14565
  0,
14362
14566
  getMaxOffsetX()
@@ -14395,7 +14599,7 @@ var mountDAW = (target, options = {}) => {
14395
14599
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14396
14600
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14397
14601
  refs.zoomXLabel.textContent = `${zoomX}%`;
14398
- currentOffsetX = clamp4(
14602
+ currentOffsetX = clamp5(
14399
14603
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14400
14604
  0,
14401
14605
  getMaxOffsetX()
@@ -14408,7 +14612,7 @@ var mountDAW = (target, options = {}) => {
14408
14612
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14409
14613
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14410
14614
  refs.zoomYLabel.textContent = `${zoomY}%`;
14411
- currentOffsetY = clamp4(
14615
+ currentOffsetY = clamp5(
14412
14616
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14413
14617
  0,
14414
14618
  getMaxOffsetY()
@@ -14458,7 +14662,7 @@ var mountDAW = (target, options = {}) => {
14458
14662
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14459
14663
  if (currentPlayStep > threshold) {
14460
14664
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14461
- currentOffsetX = clamp4(
14665
+ currentOffsetX = clamp5(
14462
14666
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14463
14667
  0,
14464
14668
  getMaxOffsetX()
@@ -14545,7 +14749,7 @@ var mountDAW = (target, options = {}) => {
14545
14749
  }
14546
14750
  if (playbackState !== "paused") {
14547
14751
  const canvas = getGridCanvas();
14548
- currentOffsetX = clamp4(
14752
+ currentOffsetX = clamp5(
14549
14753
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14550
14754
  0,
14551
14755
  getMaxOffsetX()
@@ -14649,7 +14853,7 @@ var mountDAW = (target, options = {}) => {
14649
14853
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
14650
14854
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14651
14855
  </div>
14652
- <details class="dtm-advanced" data-dtm="track-fx-advanced">
14856
+ <details class="dtm-advanced" data-dtm="track-fx-advanced" ${trackFxAdvancedOpen ? "open" : ""}>
14653
14857
  <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14654
14858
  <div class="dtm-row">
14655
14859
  <span class="dtm-label">EQ\u4F4E\u57DF</span>
@@ -14686,6 +14890,11 @@ var mountDAW = (target, options = {}) => {
14686
14890
  <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
14891
  </div>
14688
14892
  </details>`;
14893
+ refs.trackBody.querySelector(
14894
+ '[data-dtm="track-fx-advanced"]'
14895
+ ).addEventListener("toggle", (e) => {
14896
+ trackFxAdvancedOpen = e.target.open;
14897
+ });
14689
14898
  const volInput = refs.trackBody.querySelector(
14690
14899
  '[data-dtm="track-vol"]'
14691
14900
  );
@@ -14899,7 +15108,7 @@ var mountDAW = (target, options = {}) => {
14899
15108
  <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
15109
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
14901
15110
  </div>
14902
- <details class="dtm-advanced" data-dtm="lyric-advanced">
15111
+ <details class="dtm-advanced" data-dtm="lyric-advanced" ${lyricAdvancedOpen ? "open" : ""}>
14903
15112
  <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14904
15113
  <div class="dtm-row">
14905
15114
  <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
@@ -14960,6 +15169,11 @@ var mountDAW = (target, options = {}) => {
14960
15169
  <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
15170
  </div>`;
14962
15171
  refs.trackBody.appendChild(lyricDiv);
15172
+ lyricDiv.querySelector(
15173
+ '[data-dtm="lyric-advanced"]'
15174
+ ).addEventListener("toggle", (e) => {
15175
+ lyricAdvancedOpen = e.target.open;
15176
+ });
14963
15177
  const lyricModelSel = lyricDiv.querySelector(
14964
15178
  '[data-dtm="lyric-model"]'
14965
15179
  );
@@ -15582,7 +15796,7 @@ var mountDAW = (target, options = {}) => {
15582
15796
  const canvas = getGridCanvas();
15583
15797
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15584
15798
  const logicalY = yIndex * renderConfig.keyHeight;
15585
- currentOffsetY = clamp4(
15799
+ currentOffsetY = clamp5(
15586
15800
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15587
15801
  0,
15588
15802
  getMaxOffsetY()
@@ -16157,8 +16371,8 @@ var mountDAW = (target, options = {}) => {
16157
16371
  refs.reverbAmount.value = "20";
16158
16372
  refs.reverbAmountLabel.textContent = "20%";
16159
16373
  options.onReverbChange?.(20);
16160
- const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16161
- reverbDecay = clamp4(
16374
+ const bpmT = clamp5((bpm - 60) / (180 - 60), 0, 1);
16375
+ reverbDecay = clamp5(
16162
16376
  3 - bpmT * (3 - 0.9),
16163
16377
  MIN_REVERB_DECAY_SEC,
16164
16378
  MAX_REVERB_DECAY_SEC
@@ -16193,7 +16407,7 @@ var mountDAW = (target, options = {}) => {
16193
16407
  }
16194
16408
  if (t.lyricModel) {
16195
16409
  t.vocalVibrato = true;
16196
- t.vocalReverb = isMainVocal ? 15 : 30;
16410
+ t.vocalReverb = isMainVocal ? 25 : 45;
16197
16411
  fireLyricsChange(t);
16198
16412
  }
16199
16413
  }
@@ -17089,7 +17303,7 @@ var mountDAW = (target, options = {}) => {
17089
17303
  currentPlayStep = step;
17090
17304
  playbackState = "paused";
17091
17305
  const canvas = getGridCanvas();
17092
- currentOffsetX = clamp4(
17306
+ currentOffsetX = clamp5(
17093
17307
  step * renderConfig.stepWidth - canvas.width * 0.5,
17094
17308
  0,
17095
17309
  getMaxOffsetX()
@@ -17135,11 +17349,11 @@ var mountDAW = (target, options = {}) => {
17135
17349
  getViewState,
17136
17350
  setViewState: (state) => {
17137
17351
  if (typeof state.zoomX === "number") {
17138
- zoomX = clamp4(state.zoomX, 25, 200);
17352
+ zoomX = clamp5(state.zoomX, 25, 200);
17139
17353
  applyZoomX();
17140
17354
  }
17141
17355
  if (typeof state.zoomY === "number") {
17142
- zoomY = clamp4(state.zoomY, 50, 200);
17356
+ zoomY = clamp5(state.zoomY, 50, 200);
17143
17357
  applyZoomY();
17144
17358
  }
17145
17359
  if (typeof state.decomposeChord === "boolean") {
@@ -17161,24 +17375,24 @@ var mountDAW = (target, options = {}) => {
17161
17375
  forcePauseAt,
17162
17376
  setLoading,
17163
17377
  setMasterVolume: (volume) => {
17164
- masterVolume = clamp4(volume, 0, 100);
17378
+ masterVolume = clamp5(volume, 0, 100);
17165
17379
  refs.masterVolume.value = String(masterVolume);
17166
17380
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17167
17381
  options.singingVoices?.setVolume(masterVolume / 100);
17168
17382
  },
17169
17383
  setVolume: (volume) => {
17170
- masterVolume = clamp4(volume, 0, 100);
17384
+ masterVolume = clamp5(volume, 0, 100);
17171
17385
  refs.masterVolume.value = String(masterVolume);
17172
17386
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17173
17387
  options.singingVoices?.setVolume(masterVolume / 100);
17174
17388
  },
17175
17389
  setDrumVolume: (volume) => {
17176
- drumVolume = clamp4(volume, 0, 100);
17390
+ drumVolume = clamp5(volume, 0, 100);
17177
17391
  refs.drumVolume.value = String(drumVolume);
17178
17392
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
17179
17393
  },
17180
17394
  setReverbAmount: (amount) => {
17181
- reverbAmount = clamp4(amount, 0, 100);
17395
+ reverbAmount = clamp5(amount, 0, 100);
17182
17396
  refs.reverbAmount.value = String(reverbAmount);
17183
17397
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
17184
17398
  options.onReverbChange?.(reverbAmount);
@@ -17202,7 +17416,7 @@ var mountDAW = (target, options = {}) => {
17202
17416
  options.onReverbPreDelayChange?.(reverbPreDelay);
17203
17417
  },
17204
17418
  setDelayAmount: (amount) => {
17205
- delayAmount = clamp4(amount, 0, 100);
17419
+ delayAmount = clamp5(amount, 0, 100);
17206
17420
  refs.delayAmount.value = String(delayAmount);
17207
17421
  refs.delayAmountLabel.textContent = `${delayAmount}%`;
17208
17422
  options.onDelayChange?.(delayAmount);
@@ -17981,149 +18195,6 @@ var isSupported = midiJsonParser.isSupported;
17981
18195
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17982
18196
  URL.revokeObjectURL(url);
17983
18197
 
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
18198
  // src/clip-meter.ts
18128
18199
  var createClipMeter = (ctx, source, options = {}) => {
18129
18200
  const threshold = options.threshold ?? 0.98;
@@ -18327,6 +18398,11 @@ var createDtmStudio = async (options = {}) => {
18327
18398
  presetUI: true,
18328
18399
  ...options.features
18329
18400
  };
18401
+ const resolveDrumPatterns = (custom) => ({
18402
+ ...DRUM_PATTERNS,
18403
+ ...SONG_DRUM_PATTERNS,
18404
+ ...normalizeDrumPatterns(custom ?? {})
18405
+ });
18330
18406
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
18331
18407
  const masterGain = audioCtx.createGain();
18332
18408
  masterGain.gain.value = options.masterVolume ?? 1;
@@ -18988,7 +19064,7 @@ var createDtmStudio = async (options = {}) => {
18988
19064
  await loadRequiredDrums(
18989
19065
  getDrumPatternKeys(
18990
19066
  meta.drum,
18991
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19067
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
18992
19068
  ),
18993
19069
  meta.drumFont || "FluidR3_GM_sf2_file:0"
18994
19070
  );
@@ -19079,7 +19155,7 @@ var createDtmStudio = async (options = {}) => {
19079
19155
  await loadRequiredDrums(
19080
19156
  getDrumPatternKeys(
19081
19157
  meta.drum,
19082
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19158
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19083
19159
  )
19084
19160
  );
19085
19161
  }
@@ -19159,7 +19235,7 @@ var createDtmStudio = async (options = {}) => {
19159
19235
  await loadRequiredDrums(
19160
19236
  getDrumPatternKeys(
19161
19237
  meta.drum,
19162
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19238
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19163
19239
  )
19164
19240
  );
19165
19241
  }