@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.js CHANGED
@@ -1466,6 +1466,149 @@ var getDrumPatternKeys = (name, dict) => {
1466
1466
  return Array.from(keys);
1467
1467
  };
1468
1468
 
1469
+ // src/channel-strip.ts
1470
+ var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1471
+ var compressionParams = (amount) => {
1472
+ const t = clamp(amount, 0, 100) / 100;
1473
+ return {
1474
+ threshold: 0 + (-24 - 0) * t,
1475
+ ratio: 1 + (12 - 1) * t,
1476
+ knee: 0 + (6 - 0) * t,
1477
+ attack: 0.02 + (3e-3 - 0.02) * t,
1478
+ release: 0.25 + (0.15 - 0.25) * t
1479
+ };
1480
+ };
1481
+ var EQ_LOW_FREQ = 200;
1482
+ var EQ_MID_FREQ = 1e3;
1483
+ var EQ_HIGH_FREQ = 5e3;
1484
+ var EQ_MID_Q = 1;
1485
+ var EQ_MAX_DB = 12;
1486
+ var createChannelStrip = (ctx, destination, options = {}) => {
1487
+ const input = ctx.createGain();
1488
+ const eqLow = ctx.createBiquadFilter();
1489
+ eqLow.type = "lowshelf";
1490
+ eqLow.frequency.value = EQ_LOW_FREQ;
1491
+ eqLow.gain.value = clamp(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1492
+ const eqMid = ctx.createBiquadFilter();
1493
+ eqMid.type = "peaking";
1494
+ eqMid.frequency.value = EQ_MID_FREQ;
1495
+ eqMid.Q.value = EQ_MID_Q;
1496
+ eqMid.gain.value = clamp(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1497
+ const eqHigh = ctx.createBiquadFilter();
1498
+ eqHigh.type = "highshelf";
1499
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
1500
+ eqHigh.gain.value = clamp(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
1501
+ input.connect(eqLow);
1502
+ eqLow.connect(eqMid);
1503
+ eqMid.connect(eqHigh);
1504
+ const compressor = ctx.createDynamicsCompressor();
1505
+ const applyCompression = (amount) => {
1506
+ const p = compressionParams(amount);
1507
+ const now = ctx.currentTime;
1508
+ compressor.threshold.setValueAtTime(p.threshold, now);
1509
+ compressor.ratio.setValueAtTime(p.ratio, now);
1510
+ compressor.knee.setValueAtTime(p.knee, now);
1511
+ compressor.attack.setValueAtTime(p.attack, now);
1512
+ compressor.release.setValueAtTime(p.release, now);
1513
+ };
1514
+ applyCompression(options.compression ?? 0);
1515
+ const splitter = ctx.createChannelSplitter(2);
1516
+ const mid = ctx.createGain();
1517
+ mid.gain.value = 0.5;
1518
+ splitter.connect(mid, 0);
1519
+ splitter.connect(mid, 1);
1520
+ const sideSum = ctx.createGain();
1521
+ sideSum.gain.value = 1;
1522
+ const sideL = ctx.createGain();
1523
+ sideL.gain.value = 0.5;
1524
+ const sideR = ctx.createGain();
1525
+ sideR.gain.value = -0.5;
1526
+ splitter.connect(sideL, 0);
1527
+ splitter.connect(sideR, 1);
1528
+ sideL.connect(sideSum);
1529
+ sideR.connect(sideSum);
1530
+ const widthGain = ctx.createGain();
1531
+ sideSum.connect(widthGain);
1532
+ const widthInv = ctx.createGain();
1533
+ widthInv.gain.value = -1;
1534
+ widthGain.connect(widthInv);
1535
+ const outL = ctx.createGain();
1536
+ mid.connect(outL);
1537
+ widthGain.connect(outL);
1538
+ const outR = ctx.createGain();
1539
+ mid.connect(outR);
1540
+ widthInv.connect(outR);
1541
+ const merger = ctx.createChannelMerger(2);
1542
+ outL.connect(merger, 0, 0);
1543
+ outR.connect(merger, 0, 1);
1544
+ const setWidth = (width) => {
1545
+ widthGain.gain.setTargetAtTime(
1546
+ clamp(width, 0, 200) / 100,
1547
+ ctx.currentTime,
1548
+ 0.02
1549
+ );
1550
+ };
1551
+ setWidth(options.width ?? 100);
1552
+ eqHigh.connect(compressor);
1553
+ compressor.connect(splitter);
1554
+ merger.connect(destination);
1555
+ const reverbSendGain = ctx.createGain();
1556
+ reverbSendGain.gain.value = clamp(options.reverbSend ?? 0, 0, 100) / 100;
1557
+ merger.connect(reverbSendGain);
1558
+ if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
1559
+ return {
1560
+ input,
1561
+ setEqLow: (db) => {
1562
+ eqLow.gain.setTargetAtTime(
1563
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1564
+ ctx.currentTime,
1565
+ 0.02
1566
+ );
1567
+ },
1568
+ setEqMid: (db) => {
1569
+ eqMid.gain.setTargetAtTime(
1570
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1571
+ ctx.currentTime,
1572
+ 0.02
1573
+ );
1574
+ },
1575
+ setEqHigh: (db) => {
1576
+ eqHigh.gain.setTargetAtTime(
1577
+ clamp(db, -EQ_MAX_DB, EQ_MAX_DB),
1578
+ ctx.currentTime,
1579
+ 0.02
1580
+ );
1581
+ },
1582
+ setCompression: applyCompression,
1583
+ setWidth,
1584
+ setReverbSend: (amount) => {
1585
+ reverbSendGain.gain.setTargetAtTime(
1586
+ clamp(amount, 0, 100) / 100,
1587
+ ctx.currentTime,
1588
+ 0.02
1589
+ );
1590
+ },
1591
+ dispose: () => {
1592
+ input.disconnect();
1593
+ eqLow.disconnect();
1594
+ eqMid.disconnect();
1595
+ eqHigh.disconnect();
1596
+ compressor.disconnect();
1597
+ splitter.disconnect();
1598
+ mid.disconnect();
1599
+ sideSum.disconnect();
1600
+ sideL.disconnect();
1601
+ sideR.disconnect();
1602
+ widthGain.disconnect();
1603
+ widthInv.disconnect();
1604
+ outL.disconnect();
1605
+ outR.disconnect();
1606
+ merger.disconnect();
1607
+ reverbSendGain.disconnect();
1608
+ }
1609
+ };
1610
+ };
1611
+
1469
1612
  // src/chords.ts
1470
1613
  var C3 = 48;
1471
1614
  var buildChordPlacements = (options) => {
@@ -2295,7 +2438,7 @@ var normalizeLyricLines = (lines) => {
2295
2438
  var LYRIC_LINE = /^@@(\d+)\s*(.*)$/;
2296
2439
  var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
2297
2440
  var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
2298
- var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2441
+ var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
2299
2442
  var MAX_VOCAL_VOLUME = 400;
2300
2443
  var VOCAL_BOOST_DB_PER_PERCENT = 0.08;
2301
2444
  var vocalVolumeToGain = (v) => {
@@ -2329,7 +2472,7 @@ var parseLyrics = (mml) => {
2329
2472
  if (modelMatch) {
2330
2473
  model = modelMatch[1].toLowerCase();
2331
2474
  if (modelMatch[2]) {
2332
- volume = clamp(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2475
+ volume = clamp2(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2333
2476
  }
2334
2477
  metaTokens.push(modelMatch[0]);
2335
2478
  rest = rest.substring(modelMatch[0].length).trim();
@@ -2337,28 +2480,28 @@ var parseLyrics = (mml) => {
2337
2480
  while (true) {
2338
2481
  const vMatch = rest.match(/^v(\d+)/i);
2339
2482
  if (vMatch) {
2340
- volume = clamp(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2483
+ volume = clamp2(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2341
2484
  metaTokens.push(vMatch[0]);
2342
2485
  rest = rest.substring(vMatch[0].length).trim();
2343
2486
  continue;
2344
2487
  }
2345
2488
  const qMatch = rest.match(/^q(\d+)/i);
2346
2489
  if (qMatch) {
2347
- gate = clamp(Number.parseInt(qMatch[1], 10), 0, 100);
2490
+ gate = clamp2(Number.parseInt(qMatch[1], 10), 0, 100);
2348
2491
  metaTokens.push(qMatch[0]);
2349
2492
  rest = rest.substring(qMatch[0].length).trim();
2350
2493
  continue;
2351
2494
  }
2352
2495
  const pMatch = rest.match(/^p(\d+)/i);
2353
2496
  if (pMatch) {
2354
- pan = clamp(Number.parseInt(pMatch[1], 10), 0, 127);
2497
+ pan = clamp2(Number.parseInt(pMatch[1], 10), 0, 127);
2355
2498
  metaTokens.push(pMatch[0]);
2356
2499
  rest = rest.substring(pMatch[0].length).trim();
2357
2500
  continue;
2358
2501
  }
2359
2502
  const oMatch = rest.match(/^o(-?\d+)/i);
2360
2503
  if (oMatch) {
2361
- octave = clamp(Number.parseInt(oMatch[1], 10), -2, 2);
2504
+ octave = clamp2(Number.parseInt(oMatch[1], 10), -2, 2);
2362
2505
  metaTokens.push(oMatch[0]);
2363
2506
  rest = rest.substring(oMatch[0].length).trim();
2364
2507
  continue;
@@ -2372,28 +2515,28 @@ var parseLyrics = (mml) => {
2372
2515
  }
2373
2516
  const rMatch = rest.match(/^r(\d+)/i);
2374
2517
  if (rMatch) {
2375
- reverb = clamp(Number.parseInt(rMatch[1], 10), 0, 100);
2518
+ reverb = clamp2(Number.parseInt(rMatch[1], 10), 0, 100);
2376
2519
  metaTokens.push(rMatch[0]);
2377
2520
  rest = rest.substring(rMatch[0].length).trim();
2378
2521
  continue;
2379
2522
  }
2380
2523
  const gMatch = rest.match(/^g(\d+)/i);
2381
2524
  if (gMatch) {
2382
- gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2525
+ gender = clamp2(Number.parseInt(gMatch[1], 10), 0, 100);
2383
2526
  metaTokens.push(gMatch[0]);
2384
2527
  rest = rest.substring(gMatch[0].length).trim();
2385
2528
  continue;
2386
2529
  }
2387
2530
  const hMatch = rest.match(/^h(\d+)/i);
2388
2531
  if (hMatch) {
2389
- breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2532
+ breathiness = clamp2(Number.parseInt(hMatch[1], 10), 0, 100);
2390
2533
  metaTokens.push(hMatch[0]);
2391
2534
  rest = rest.substring(hMatch[0].length).trim();
2392
2535
  continue;
2393
2536
  }
2394
2537
  const eMatch = rest.match(/^e(\d+)/i);
2395
2538
  if (eMatch) {
2396
- delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2539
+ delay = clamp2(Number.parseInt(eMatch[1], 10), 0, 100);
2397
2540
  metaTokens.push(eMatch[0]);
2398
2541
  rest = rest.substring(eMatch[0].length).trim();
2399
2542
  continue;
@@ -3309,7 +3452,7 @@ var PITCH_MAP = {
3309
3452
  a: 9,
3310
3453
  b: 11
3311
3454
  };
3312
- var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3455
+ var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3313
3456
  var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3314
3457
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3315
3458
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
@@ -3333,24 +3476,24 @@ var parseMmlMeta = (mml) => {
3333
3476
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3334
3477
  } else if (key === "reverb") {
3335
3478
  const rv = Number.parseInt(m[2], 10);
3336
- if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3479
+ if (!Number.isNaN(rv)) meta.reverb = clamp3(rv, 0, 100);
3337
3480
  } else if (key === "reverbdecay") {
3338
3481
  const rd = Number.parseInt(m[2], 10);
3339
- if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3482
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp3(rd, 3, 40);
3340
3483
  } else if (key === "reverbpredelay") {
3341
3484
  const rp = Number.parseInt(m[2], 10);
3342
- if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3485
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp3(rp, 0, 150);
3343
3486
  } else if (key === "delay") {
3344
3487
  const dv = Number.parseInt(m[2], 10);
3345
- if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3488
+ if (!Number.isNaN(dv)) meta.delay = clamp3(dv, 0, 100);
3346
3489
  } else if (key === "delaydiv") {
3347
3490
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3348
3491
  } else if (key === "fadein") {
3349
3492
  const fi = Number.parseInt(m[2], 10);
3350
- if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3493
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp3(fi, 0, 100);
3351
3494
  } else if (key === "fadeout") {
3352
3495
  const fo = Number.parseInt(m[2], 10);
3353
- if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3496
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp3(fo, 0, 100);
3354
3497
  } else if (key === "mode") {
3355
3498
  if (m[2] === "simple" || m[2] === "advanced") {
3356
3499
  meta.mode = m[2];
@@ -3367,7 +3510,7 @@ var parseMmlMeta = (mml) => {
3367
3510
  }
3368
3511
  for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3369
3512
  const idx = Number.parseInt(m[1], 10);
3370
- const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3513
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3371
3514
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3372
3515
  meta.trackCompression ??= {};
3373
3516
  meta.trackCompression[idx] = val;
@@ -3375,7 +3518,7 @@ var parseMmlMeta = (mml) => {
3375
3518
  }
3376
3519
  for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3377
3520
  const idx = Number.parseInt(m[1], 10);
3378
- const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3521
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 200);
3379
3522
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3380
3523
  meta.trackWidth ??= {};
3381
3524
  meta.trackWidth[idx] = val;
@@ -3383,7 +3526,7 @@ var parseMmlMeta = (mml) => {
3383
3526
  }
3384
3527
  for (const m of mml.matchAll(TRACK_REVERBSEND_DIRECTIVE)) {
3385
3528
  const idx = Number.parseInt(m[1], 10);
3386
- const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3529
+ const val = clamp3(Number.parseInt(m[2], 10), 0, 100);
3387
3530
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3388
3531
  meta.trackReverbSend ??= {};
3389
3532
  meta.trackReverbSend[idx] = val;
@@ -3391,7 +3534,7 @@ var parseMmlMeta = (mml) => {
3391
3534
  }
3392
3535
  for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3393
3536
  const idx = Number.parseInt(m[1], 10);
3394
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3537
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3395
3538
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3396
3539
  meta.trackEqLow ??= {};
3397
3540
  meta.trackEqLow[idx] = val;
@@ -3399,7 +3542,7 @@ var parseMmlMeta = (mml) => {
3399
3542
  }
3400
3543
  for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3401
3544
  const idx = Number.parseInt(m[1], 10);
3402
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3545
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3403
3546
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3404
3547
  meta.trackEqMid ??= {};
3405
3548
  meta.trackEqMid[idx] = val;
@@ -3407,7 +3550,7 @@ var parseMmlMeta = (mml) => {
3407
3550
  }
3408
3551
  for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3409
3552
  const idx = Number.parseInt(m[1], 10);
3410
- const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3553
+ const val = clamp3(Number.parseInt(m[2], 10), -12, 12);
3411
3554
  if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3412
3555
  meta.trackEqHigh ??= {};
3413
3556
  meta.trackEqHigh[idx] = val;
@@ -3556,7 +3699,7 @@ var parseMML = (mml, options = {}) => {
3556
3699
  numStr += body[j];
3557
3700
  j++;
3558
3701
  }
3559
- const len = numStr ? clamp2(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3702
+ const len = numStr ? clamp3(Number.parseInt(numStr, 10), 1, 64) : baseLength;
3560
3703
  let steps = Math.round(stepsPerBar / len);
3561
3704
  while (j < body.length && body[j] === ".") {
3562
3705
  steps = Math.round(steps * 1.5);
@@ -3574,7 +3717,7 @@ var parseMML = (mml, options = {}) => {
3574
3717
  numStr += body[j];
3575
3718
  j++;
3576
3719
  }
3577
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3720
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3578
3721
  pushTok("octave", currentStep, 0, tokStart);
3579
3722
  } else if (ch === ">") {
3580
3723
  octave = Math.min(8, octave + 1);
@@ -3591,7 +3734,7 @@ var parseMML = (mml, options = {}) => {
3591
3734
  numStr += body[j];
3592
3735
  j++;
3593
3736
  }
3594
- baseLength = clamp2(Number.parseInt(numStr, 10) || 16, 1, 64);
3737
+ baseLength = clamp3(Number.parseInt(numStr, 10) || 16, 1, 64);
3595
3738
  pushTok("length", currentStep, 0, tokStart);
3596
3739
  } else if (ch === "r") {
3597
3740
  j++;
@@ -3608,10 +3751,10 @@ var parseMML = (mml, options = {}) => {
3608
3751
  }
3609
3752
  if (ch === "t" && numStr) {
3610
3753
  if (bpm === null) {
3611
- bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3754
+ bpm = clamp3(Number.parseInt(numStr, 10), 1, 255);
3612
3755
  }
3613
3756
  } else if (ch === "v" && numStr) {
3614
- velocity = clamp2(Number.parseInt(numStr, 10), 0, 127);
3757
+ velocity = clamp3(Number.parseInt(numStr, 10), 0, 127);
3615
3758
  trackVelocity.set(trackIndex, velocity);
3616
3759
  }
3617
3760
  pushTok("ctrl", currentStep, 0, tokStart);
@@ -3645,7 +3788,7 @@ var parseMML = (mml, options = {}) => {
3645
3788
  numStr += body[j];
3646
3789
  j++;
3647
3790
  }
3648
- octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3791
+ octave = numStr ? clamp3(Number.parseInt(numStr, 10), 0, 8) : 4;
3649
3792
  } else {
3650
3793
  j++;
3651
3794
  }
@@ -3707,6 +3850,38 @@ var parseMML = (mml, options = {}) => {
3707
3850
  };
3708
3851
  };
3709
3852
 
3853
+ // src/reverb.ts
3854
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
3855
+ var MIN_REVERB_DECAY_SEC = 0.3;
3856
+ var MAX_REVERB_DECAY_SEC = 4;
3857
+ var IMPULSE_DECAY_CURVE = 2.5;
3858
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
3859
+ var MIN_REVERB_PREDELAY_MS = 0;
3860
+ var MAX_REVERB_PREDELAY_MS = 150;
3861
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
3862
+ const rate = ctx.sampleRate;
3863
+ const length = Math.max(
3864
+ 1,
3865
+ Math.floor(
3866
+ rate * Math.max(
3867
+ MIN_REVERB_DECAY_SEC,
3868
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
3869
+ )
3870
+ )
3871
+ );
3872
+ const impulse = ctx.createBuffer(2, length, rate);
3873
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
3874
+ const data = impulse.getChannelData(ch);
3875
+ for (let i2 = 0; i2 < length; i2++) {
3876
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
3877
+ data[i2] = (Math.random() * 2 - 1) * envelope;
3878
+ }
3879
+ }
3880
+ return impulse;
3881
+ };
3882
+ var REVERB_MAX_WET = 0.6;
3883
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
3884
+
3710
3885
  // src/sequencer.ts
3711
3886
  var STEPS_PER_BEAT = 48;
3712
3887
  var PLAN_TIME = 0.5;
@@ -6284,9 +6459,54 @@ var playPlacements = (placements, options) => {
6284
6459
  });
6285
6460
  const ownsCtx = !options.audioContext;
6286
6461
  const ctx = options.audioContext ?? new AudioContext();
6287
- const destination = options.destination ?? ctx.destination;
6462
+ const rawDestination = options.destination ?? ctx.destination;
6288
6463
  const useSynth = options.synth ?? !options.onPlayNote;
6289
- const synth = useSynth ? createSynth(ctx, destination) : null;
6464
+ const finalMix = ctx.createGain();
6465
+ const masterGain = ctx.createGain();
6466
+ masterGain.connect(finalMix);
6467
+ const reverbPreDelay = ctx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
6468
+ reverbPreDelay.delayTime.value = (options.metaReverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
6469
+ const reverbConvolver = ctx.createConvolver();
6470
+ const reverbDecaySec = (options.metaReverbDecay ?? 22) / 10 || DEFAULT_REVERB_DECAY_SEC;
6471
+ reverbConvolver.buffer = createReverbImpulse(ctx, reverbDecaySec);
6472
+ reverbConvolver.normalize = true;
6473
+ const reverbWetGain = ctx.createGain();
6474
+ reverbWetGain.gain.value = reverbAmountToGain(options.metaReverb ?? 0);
6475
+ reverbPreDelay.connect(reverbConvolver);
6476
+ reverbConvolver.connect(reverbWetGain);
6477
+ reverbWetGain.connect(finalMix);
6478
+ finalMix.connect(rawDestination);
6479
+ const channelStrips = /* @__PURE__ */ new Map();
6480
+ const getChannelStrip = (index) => {
6481
+ let strip = channelStrips.get(index);
6482
+ if (!strip) {
6483
+ strip = createChannelStrip(ctx, masterGain, {
6484
+ compression: options.trackCompression?.[index] ?? 0,
6485
+ width: options.trackWidth?.[index] ?? 100,
6486
+ eqLow: options.trackEqLow?.[index] ?? 0,
6487
+ eqMid: options.trackEqMid?.[index] ?? 0,
6488
+ eqHigh: options.trackEqHigh?.[index] ?? 0,
6489
+ reverbSend: options.trackReverbSend?.[index] ?? 0,
6490
+ reverbBus: reverbPreDelay
6491
+ });
6492
+ channelStrips.set(index, strip);
6493
+ }
6494
+ return strip;
6495
+ };
6496
+ const synths = /* @__PURE__ */ new Map();
6497
+ const getSynth = (index) => {
6498
+ let s = synths.get(index);
6499
+ if (!s) {
6500
+ s = createSynth(ctx, getChannelStrip(index).input);
6501
+ synths.set(index, s);
6502
+ }
6503
+ return s;
6504
+ };
6505
+ const drumSynth = useSynth ? createSynth(ctx, masterGain) : null;
6506
+ const trackIndexById = /* @__PURE__ */ new Map();
6507
+ trackIndices.forEach((idx) => {
6508
+ trackIndexById.set(TRACK_ID_BY_INDEX[idx] ?? `t${idx}`, idx);
6509
+ });
6290
6510
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
6291
6511
  let playing = false;
6292
6512
  const seq = createSequencer({
@@ -6301,12 +6521,14 @@ var playPlacements = (placements, options) => {
6301
6521
  getAudioTime: () => ctx.currentTime,
6302
6522
  onPlayNote: (e) => {
6303
6523
  options.onPlayNote?.(e);
6304
- synth?.playNote(e);
6524
+ if (!useSynth) return;
6525
+ const index = trackIndexById.get(e.trackId);
6526
+ (index === void 0 ? drumSynth : getSynth(index))?.playNote(e);
6305
6527
  },
6306
6528
  onPlayDrum: (e) => {
6307
6529
  const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
6308
6530
  options.onPlayDrum?.({ ...e, velocity });
6309
- synth?.playDrum({ ...e, velocity });
6531
+ drumSynth?.playDrum({ ...e, velocity });
6310
6532
  },
6311
6533
  onTick: (step) => {
6312
6534
  options.onTick?.(step);
@@ -6357,6 +6579,7 @@ var playPlacements = (placements, options) => {
6357
6579
  if (pauseWhenHidden && typeof document !== "undefined") {
6358
6580
  document.removeEventListener("visibilitychange", onVisibilityChange);
6359
6581
  }
6582
+ for (const strip of channelStrips.values()) strip.dispose();
6360
6583
  if (ownsCtx) void ctx.close();
6361
6584
  };
6362
6585
  return {
@@ -6376,7 +6599,16 @@ var playMML = (mml, options = {}) => {
6376
6599
  bpm,
6377
6600
  metaVolume: meta.volume,
6378
6601
  metaDrum: meta.drum,
6379
- metaDrumVolume: meta.drumVolume
6602
+ metaDrumVolume: meta.drumVolume,
6603
+ metaReverb: meta.reverb,
6604
+ metaReverbDecay: meta.reverbDecay,
6605
+ metaReverbPreDelay: meta.reverbPreDelay,
6606
+ trackCompression: meta.trackCompression,
6607
+ trackWidth: meta.trackWidth,
6608
+ trackReverbSend: meta.trackReverbSend,
6609
+ trackEqLow: meta.trackEqLow,
6610
+ trackEqMid: meta.trackEqMid,
6611
+ trackEqHigh: meta.trackEqHigh
6380
6612
  });
6381
6613
  };
6382
6614
  var playNote = (options) => {
@@ -9682,20 +9914,23 @@ var adjustZone = async (ctx, fontName, zone) => {
9682
9914
  const newLength = attackLength + loopLengthFrame * repeatCount + releaseLength;
9683
9915
  let totalPeak = 0;
9684
9916
  let loopPeak = 0;
9685
- if (oldBuf.numberOfChannels > 0) {
9686
- const ch0 = oldBuf.getChannelData(0);
9687
- for (let i2 = 0; i2 < ch0.length; i2++) {
9688
- const abs = Math.abs(ch0[i2]);
9917
+ for (let ch = 0; ch < oldBuf.numberOfChannels; ch++) {
9918
+ const chData = oldBuf.getChannelData(ch);
9919
+ for (let i2 = 0; i2 < chData.length; i2++) {
9920
+ const abs = Math.abs(chData[i2]);
9689
9921
  if (abs > totalPeak) totalPeak = abs;
9690
9922
  if (i2 >= loopStartFrame && i2 < loopEndFrame) {
9691
9923
  if (abs > loopPeak) loopPeak = abs;
9692
9924
  }
9693
9925
  }
9694
9926
  }
9927
+ const decay = isDecayInstrument(fontName);
9928
+ const targetRatio = decay ? 0.4 : 0.75;
9929
+ const maxMultiplier = decay ? 6 : 20;
9695
9930
  let gainMultiplier = 1;
9696
- if (!isDecayInstrument(fontName) && loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9697
- gainMultiplier = totalPeak * 0.75 / loopPeak;
9698
- if (gainMultiplier > 20) gainMultiplier = 20;
9931
+ if (loopPeak > 0 && totalPeak > 0 && loopPeak < totalPeak * 0.8) {
9932
+ gainMultiplier = totalPeak * targetRatio / loopPeak;
9933
+ if (gainMultiplier > maxMultiplier) gainMultiplier = maxMultiplier;
9699
9934
  }
9700
9935
  try {
9701
9936
  const newBuf = ctx.createBuffer(
@@ -11027,14 +11262,14 @@ var DELAY_DIVISIONS = [
11027
11262
  { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
11028
11263
  { value: "16", label: "16\u5206", beats: 0.25 }
11029
11264
  ];
11030
- var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
11265
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
11031
11266
  var divisionToSeconds = (division, bpm) => {
11032
11267
  const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
11033
11268
  const safeBpm = bpm > 0 ? bpm : 120;
11034
11269
  return 60 / safeBpm * beats;
11035
11270
  };
11036
11271
  var DELAY_MAX_WET = 0.45;
11037
- var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
11272
+ var delayAmountToGain = (amount) => clamp4(amount, 0, 100) / 100 * DELAY_MAX_WET;
11038
11273
  var FEEDBACK_GAIN = 0.3;
11039
11274
  var MAX_DELAY_SEC = 2;
11040
11275
  var createDelayBus = (ctx, destination, options = {}) => {
@@ -13109,38 +13344,6 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
13109
13344
  return chordNotes / notes.length >= threshold;
13110
13345
  };
13111
13346
 
13112
- // src/reverb.ts
13113
- var DEFAULT_REVERB_DECAY_SEC = 2.2;
13114
- var MIN_REVERB_DECAY_SEC = 0.3;
13115
- var MAX_REVERB_DECAY_SEC = 4;
13116
- var IMPULSE_DECAY_CURVE = 2.5;
13117
- var DEFAULT_REVERB_PREDELAY_MS = 0;
13118
- var MIN_REVERB_PREDELAY_MS = 0;
13119
- var MAX_REVERB_PREDELAY_MS = 150;
13120
- var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
13121
- const rate = ctx.sampleRate;
13122
- const length = Math.max(
13123
- 1,
13124
- Math.floor(
13125
- rate * Math.max(
13126
- MIN_REVERB_DECAY_SEC,
13127
- Math.min(MAX_REVERB_DECAY_SEC, decaySec)
13128
- )
13129
- )
13130
- );
13131
- const impulse = ctx.createBuffer(2, length, rate);
13132
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
13133
- const data = impulse.getChannelData(ch);
13134
- for (let i2 = 0; i2 < length; i2++) {
13135
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
13136
- data[i2] = (Math.random() * 2 - 1) * envelope;
13137
- }
13138
- }
13139
- return impulse;
13140
- };
13141
- var REVERB_MAX_WET = 0.6;
13142
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
13143
-
13144
13347
  // src/daw.ts
13145
13348
  var CHORD_INFO_HTML2 = `
13146
13349
  <div class="dtm-modal-body-content">
@@ -13576,7 +13779,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13576
13779
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13577
13780
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13578
13781
  };
13579
- var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
13782
+ var clamp5 = (v, min, max) => Math.min(Math.max(v, min), max);
13580
13783
  var normalizeInstrumentName = (name) => {
13581
13784
  if (!name) return "";
13582
13785
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13665,6 +13868,8 @@ var mountDAW = (target, options = {}) => {
13665
13868
  refs.drumFontSelect.value = currentDrumFont;
13666
13869
  let currentInstrument = "";
13667
13870
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
13871
+ let trackFxAdvancedOpen = false;
13872
+ let lyricAdvancedOpen = false;
13668
13873
  let activeToolMode = "pen";
13669
13874
  let currentInsertLength = 48;
13670
13875
  let snapGridSteps = 12;
@@ -13961,7 +14166,7 @@ var mountDAW = (target, options = {}) => {
13961
14166
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13962
14167
  const ratio = currentOffsetX / maxOffsetX;
13963
14168
  refs.hScrollThumb.style.width = `${thumbW}px`;
13964
- refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
14169
+ refs.hScrollThumb.style.left = `${clamp5(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13965
14170
  }
13966
14171
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13967
14172
  const sbH = refs.vScroll.clientHeight;
@@ -14031,9 +14236,9 @@ var mountDAW = (target, options = {}) => {
14031
14236
  if (maxOffsetX <= 0) return;
14032
14237
  const rect = refs.hScroll.getBoundingClientRect();
14033
14238
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
14034
- const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
14239
+ const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
14035
14240
  const ratio = x2 / (rect.width - thumbW);
14036
- currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
14241
+ currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
14037
14242
  setDrawOffset(currentOffsetX, currentOffsetY);
14038
14243
  redrawAll();
14039
14244
  };
@@ -14042,9 +14247,9 @@ var mountDAW = (target, options = {}) => {
14042
14247
  if (maxOffset <= 0) return;
14043
14248
  const rect = refs.vScroll.getBoundingClientRect();
14044
14249
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
14045
- const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
14250
+ const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
14046
14251
  const ratio = y / (rect.height - thumbH);
14047
- currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
14252
+ currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
14048
14253
  setDrawOffset(currentOffsetX, currentOffsetY);
14049
14254
  redrawAll();
14050
14255
  };
@@ -14091,8 +14296,8 @@ var mountDAW = (target, options = {}) => {
14091
14296
  if (dx !== 0 || dy !== 0) {
14092
14297
  const maxOffsetX = getMaxOffsetX();
14093
14298
  const maxOffsetY = getMaxOffsetY();
14094
- currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
14095
- currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
14299
+ currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
14300
+ currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
14096
14301
  setDrawOffset(currentOffsetX, currentOffsetY);
14097
14302
  onPointerMove(lastMoveEvent);
14098
14303
  }
@@ -14482,12 +14687,12 @@ var mountDAW = (target, options = {}) => {
14482
14687
  "wheel",
14483
14688
  (event) => {
14484
14689
  event.preventDefault();
14485
- currentOffsetY = clamp4(
14690
+ currentOffsetY = clamp5(
14486
14691
  currentOffsetY + event.deltaY,
14487
14692
  0,
14488
14693
  getMaxOffsetY()
14489
14694
  );
14490
- currentOffsetX = clamp4(
14695
+ currentOffsetX = clamp5(
14491
14696
  currentOffsetX + event.deltaX,
14492
14697
  0,
14493
14698
  getMaxOffsetX()
@@ -14526,7 +14731,7 @@ var mountDAW = (target, options = {}) => {
14526
14731
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14527
14732
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14528
14733
  refs.zoomXLabel.textContent = `${zoomX}%`;
14529
- currentOffsetX = clamp4(
14734
+ currentOffsetX = clamp5(
14530
14735
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14531
14736
  0,
14532
14737
  getMaxOffsetX()
@@ -14539,7 +14744,7 @@ var mountDAW = (target, options = {}) => {
14539
14744
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14540
14745
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14541
14746
  refs.zoomYLabel.textContent = `${zoomY}%`;
14542
- currentOffsetY = clamp4(
14747
+ currentOffsetY = clamp5(
14543
14748
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14544
14749
  0,
14545
14750
  getMaxOffsetY()
@@ -14589,7 +14794,7 @@ var mountDAW = (target, options = {}) => {
14589
14794
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14590
14795
  if (currentPlayStep > threshold) {
14591
14796
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14592
- currentOffsetX = clamp4(
14797
+ currentOffsetX = clamp5(
14593
14798
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14594
14799
  0,
14595
14800
  getMaxOffsetX()
@@ -14676,7 +14881,7 @@ var mountDAW = (target, options = {}) => {
14676
14881
  }
14677
14882
  if (playbackState !== "paused") {
14678
14883
  const canvas = getGridCanvas();
14679
- currentOffsetX = clamp4(
14884
+ currentOffsetX = clamp5(
14680
14885
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14681
14886
  0,
14682
14887
  getMaxOffsetX()
@@ -14780,7 +14985,7 @@ var mountDAW = (target, options = {}) => {
14780
14985
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
14781
14986
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14782
14987
  </div>
14783
- <details class="dtm-advanced" data-dtm="track-fx-advanced">
14988
+ <details class="dtm-advanced" data-dtm="track-fx-advanced" ${trackFxAdvancedOpen ? "open" : ""}>
14784
14989
  <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14785
14990
  <div class="dtm-row">
14786
14991
  <span class="dtm-label">EQ\u4F4E\u57DF</span>
@@ -14817,6 +15022,11 @@ var mountDAW = (target, options = {}) => {
14817
15022
  <button class="dtm-infobtn" data-dtm="track-reverb-send-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14818
15023
  </div>
14819
15024
  </details>`;
15025
+ refs.trackBody.querySelector(
15026
+ '[data-dtm="track-fx-advanced"]'
15027
+ ).addEventListener("toggle", (e) => {
15028
+ trackFxAdvancedOpen = e.target.open;
15029
+ });
14820
15030
  const volInput = refs.trackBody.querySelector(
14821
15031
  '[data-dtm="track-vol"]'
14822
15032
  );
@@ -15030,7 +15240,7 @@ var mountDAW = (target, options = {}) => {
15030
15240
  <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A200\uFF09">
15031
15241
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
15032
15242
  </div>
15033
- <details class="dtm-advanced" data-dtm="lyric-advanced">
15243
+ <details class="dtm-advanced" data-dtm="lyric-advanced" ${lyricAdvancedOpen ? "open" : ""}>
15034
15244
  <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
15035
15245
  <div class="dtm-row">
15036
15246
  <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
@@ -15091,6 +15301,11 @@ var mountDAW = (target, options = {}) => {
15091
15301
  <textarea class="dtm-textarea" data-dtm="lyric-input" rows="2" placeholder="\u3072\u3089\u304C\u306A\u30FB\u30AB\u30BF\u30AB\u30CA\u3067\u6B4C\u8A5E\uFF08\u4F8B: \u3069\u308C\u307F\u3075\u3041\u305D\u3089\u3057\u3069\uFF09"></textarea>
15092
15302
  </div>`;
15093
15303
  refs.trackBody.appendChild(lyricDiv);
15304
+ lyricDiv.querySelector(
15305
+ '[data-dtm="lyric-advanced"]'
15306
+ ).addEventListener("toggle", (e) => {
15307
+ lyricAdvancedOpen = e.target.open;
15308
+ });
15094
15309
  const lyricModelSel = lyricDiv.querySelector(
15095
15310
  '[data-dtm="lyric-model"]'
15096
15311
  );
@@ -15713,7 +15928,7 @@ var mountDAW = (target, options = {}) => {
15713
15928
  const canvas = getGridCanvas();
15714
15929
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15715
15930
  const logicalY = yIndex * renderConfig.keyHeight;
15716
- currentOffsetY = clamp4(
15931
+ currentOffsetY = clamp5(
15717
15932
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15718
15933
  0,
15719
15934
  getMaxOffsetY()
@@ -16288,8 +16503,8 @@ var mountDAW = (target, options = {}) => {
16288
16503
  refs.reverbAmount.value = "20";
16289
16504
  refs.reverbAmountLabel.textContent = "20%";
16290
16505
  options.onReverbChange?.(20);
16291
- const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16292
- reverbDecay = clamp4(
16506
+ const bpmT = clamp5((bpm - 60) / (180 - 60), 0, 1);
16507
+ reverbDecay = clamp5(
16293
16508
  3 - bpmT * (3 - 0.9),
16294
16509
  MIN_REVERB_DECAY_SEC,
16295
16510
  MAX_REVERB_DECAY_SEC
@@ -17220,7 +17435,7 @@ var mountDAW = (target, options = {}) => {
17220
17435
  currentPlayStep = step;
17221
17436
  playbackState = "paused";
17222
17437
  const canvas = getGridCanvas();
17223
- currentOffsetX = clamp4(
17438
+ currentOffsetX = clamp5(
17224
17439
  step * renderConfig.stepWidth - canvas.width * 0.5,
17225
17440
  0,
17226
17441
  getMaxOffsetX()
@@ -17266,11 +17481,11 @@ var mountDAW = (target, options = {}) => {
17266
17481
  getViewState,
17267
17482
  setViewState: (state) => {
17268
17483
  if (typeof state.zoomX === "number") {
17269
- zoomX = clamp4(state.zoomX, 25, 200);
17484
+ zoomX = clamp5(state.zoomX, 25, 200);
17270
17485
  applyZoomX();
17271
17486
  }
17272
17487
  if (typeof state.zoomY === "number") {
17273
- zoomY = clamp4(state.zoomY, 50, 200);
17488
+ zoomY = clamp5(state.zoomY, 50, 200);
17274
17489
  applyZoomY();
17275
17490
  }
17276
17491
  if (typeof state.decomposeChord === "boolean") {
@@ -17292,24 +17507,24 @@ var mountDAW = (target, options = {}) => {
17292
17507
  forcePauseAt,
17293
17508
  setLoading,
17294
17509
  setMasterVolume: (volume) => {
17295
- masterVolume = clamp4(volume, 0, 100);
17510
+ masterVolume = clamp5(volume, 0, 100);
17296
17511
  refs.masterVolume.value = String(masterVolume);
17297
17512
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17298
17513
  options.singingVoices?.setVolume(masterVolume / 100);
17299
17514
  },
17300
17515
  setVolume: (volume) => {
17301
- masterVolume = clamp4(volume, 0, 100);
17516
+ masterVolume = clamp5(volume, 0, 100);
17302
17517
  refs.masterVolume.value = String(masterVolume);
17303
17518
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
17304
17519
  options.singingVoices?.setVolume(masterVolume / 100);
17305
17520
  },
17306
17521
  setDrumVolume: (volume) => {
17307
- drumVolume = clamp4(volume, 0, 100);
17522
+ drumVolume = clamp5(volume, 0, 100);
17308
17523
  refs.drumVolume.value = String(drumVolume);
17309
17524
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
17310
17525
  },
17311
17526
  setReverbAmount: (amount) => {
17312
- reverbAmount = clamp4(amount, 0, 100);
17527
+ reverbAmount = clamp5(amount, 0, 100);
17313
17528
  refs.reverbAmount.value = String(reverbAmount);
17314
17529
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
17315
17530
  options.onReverbChange?.(reverbAmount);
@@ -17333,7 +17548,7 @@ var mountDAW = (target, options = {}) => {
17333
17548
  options.onReverbPreDelayChange?.(reverbPreDelay);
17334
17549
  },
17335
17550
  setDelayAmount: (amount) => {
17336
- delayAmount = clamp4(amount, 0, 100);
17551
+ delayAmount = clamp5(amount, 0, 100);
17337
17552
  refs.delayAmount.value = String(delayAmount);
17338
17553
  refs.delayAmountLabel.textContent = `${delayAmount}%`;
17339
17554
  options.onDelayChange?.(delayAmount);
@@ -18112,149 +18327,6 @@ var isSupported = midiJsonParser.isSupported;
18112
18327
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
18113
18328
  URL.revokeObjectURL(url);
18114
18329
 
18115
- // src/channel-strip.ts
18116
- var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
18117
- var compressionParams = (amount) => {
18118
- const t = clamp5(amount, 0, 100) / 100;
18119
- return {
18120
- threshold: 0 + (-24 - 0) * t,
18121
- ratio: 1 + (12 - 1) * t,
18122
- knee: 0 + (6 - 0) * t,
18123
- attack: 0.02 + (3e-3 - 0.02) * t,
18124
- release: 0.25 + (0.15 - 0.25) * t
18125
- };
18126
- };
18127
- var EQ_LOW_FREQ = 200;
18128
- var EQ_MID_FREQ = 1e3;
18129
- var EQ_HIGH_FREQ = 5e3;
18130
- var EQ_MID_Q = 1;
18131
- var EQ_MAX_DB = 12;
18132
- var createChannelStrip = (ctx, destination, options = {}) => {
18133
- const input = ctx.createGain();
18134
- const eqLow = ctx.createBiquadFilter();
18135
- eqLow.type = "lowshelf";
18136
- eqLow.frequency.value = EQ_LOW_FREQ;
18137
- eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18138
- const eqMid = ctx.createBiquadFilter();
18139
- eqMid.type = "peaking";
18140
- eqMid.frequency.value = EQ_MID_FREQ;
18141
- eqMid.Q.value = EQ_MID_Q;
18142
- eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18143
- const eqHigh = ctx.createBiquadFilter();
18144
- eqHigh.type = "highshelf";
18145
- eqHigh.frequency.value = EQ_HIGH_FREQ;
18146
- eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18147
- input.connect(eqLow);
18148
- eqLow.connect(eqMid);
18149
- eqMid.connect(eqHigh);
18150
- const compressor = ctx.createDynamicsCompressor();
18151
- const applyCompression = (amount) => {
18152
- const p = compressionParams(amount);
18153
- const now = ctx.currentTime;
18154
- compressor.threshold.setValueAtTime(p.threshold, now);
18155
- compressor.ratio.setValueAtTime(p.ratio, now);
18156
- compressor.knee.setValueAtTime(p.knee, now);
18157
- compressor.attack.setValueAtTime(p.attack, now);
18158
- compressor.release.setValueAtTime(p.release, now);
18159
- };
18160
- applyCompression(options.compression ?? 0);
18161
- const splitter = ctx.createChannelSplitter(2);
18162
- const mid = ctx.createGain();
18163
- mid.gain.value = 0.5;
18164
- splitter.connect(mid, 0);
18165
- splitter.connect(mid, 1);
18166
- const sideSum = ctx.createGain();
18167
- sideSum.gain.value = 1;
18168
- const sideL = ctx.createGain();
18169
- sideL.gain.value = 0.5;
18170
- const sideR = ctx.createGain();
18171
- sideR.gain.value = -0.5;
18172
- splitter.connect(sideL, 0);
18173
- splitter.connect(sideR, 1);
18174
- sideL.connect(sideSum);
18175
- sideR.connect(sideSum);
18176
- const widthGain = ctx.createGain();
18177
- sideSum.connect(widthGain);
18178
- const widthInv = ctx.createGain();
18179
- widthInv.gain.value = -1;
18180
- widthGain.connect(widthInv);
18181
- const outL = ctx.createGain();
18182
- mid.connect(outL);
18183
- widthGain.connect(outL);
18184
- const outR = ctx.createGain();
18185
- mid.connect(outR);
18186
- widthInv.connect(outR);
18187
- const merger = ctx.createChannelMerger(2);
18188
- outL.connect(merger, 0, 0);
18189
- outR.connect(merger, 0, 1);
18190
- const setWidth = (width) => {
18191
- widthGain.gain.setTargetAtTime(
18192
- clamp5(width, 0, 200) / 100,
18193
- ctx.currentTime,
18194
- 0.02
18195
- );
18196
- };
18197
- setWidth(options.width ?? 100);
18198
- eqHigh.connect(compressor);
18199
- compressor.connect(splitter);
18200
- merger.connect(destination);
18201
- const reverbSendGain = ctx.createGain();
18202
- reverbSendGain.gain.value = clamp5(options.reverbSend ?? 0, 0, 100) / 100;
18203
- merger.connect(reverbSendGain);
18204
- if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
18205
- return {
18206
- input,
18207
- setEqLow: (db) => {
18208
- eqLow.gain.setTargetAtTime(
18209
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18210
- ctx.currentTime,
18211
- 0.02
18212
- );
18213
- },
18214
- setEqMid: (db) => {
18215
- eqMid.gain.setTargetAtTime(
18216
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18217
- ctx.currentTime,
18218
- 0.02
18219
- );
18220
- },
18221
- setEqHigh: (db) => {
18222
- eqHigh.gain.setTargetAtTime(
18223
- clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18224
- ctx.currentTime,
18225
- 0.02
18226
- );
18227
- },
18228
- setCompression: applyCompression,
18229
- setWidth,
18230
- setReverbSend: (amount) => {
18231
- reverbSendGain.gain.setTargetAtTime(
18232
- clamp5(amount, 0, 100) / 100,
18233
- ctx.currentTime,
18234
- 0.02
18235
- );
18236
- },
18237
- dispose: () => {
18238
- input.disconnect();
18239
- eqLow.disconnect();
18240
- eqMid.disconnect();
18241
- eqHigh.disconnect();
18242
- compressor.disconnect();
18243
- splitter.disconnect();
18244
- mid.disconnect();
18245
- sideSum.disconnect();
18246
- sideL.disconnect();
18247
- sideR.disconnect();
18248
- widthGain.disconnect();
18249
- widthInv.disconnect();
18250
- outL.disconnect();
18251
- outR.disconnect();
18252
- merger.disconnect();
18253
- reverbSendGain.disconnect();
18254
- }
18255
- };
18256
- };
18257
-
18258
18330
  // src/clip-meter.ts
18259
18331
  var createClipMeter = (ctx, source, options = {}) => {
18260
18332
  const threshold = options.threshold ?? 0.98;