@kidlib/web-audio 0.1.2 → 0.1.4

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
@@ -1035,15 +1035,12 @@ async function Je(e, t, n = {}) {
1035
1035
  }
1036
1036
  }
1037
1037
  if (o?.enabled && (f = c(e, f, o.maxAmplitudePeak)), s?.enabled) {
1038
- let t = Ke(f);
1039
- if (t.shouldCompress) {
1040
- let n = s.threshold !== void 0 || s.ratio !== void 0 || s.makeupGain !== void 0, r;
1041
- r = n && s.threshold !== void 0 ? {
1042
- threshold: s.threshold ?? .5,
1043
- ratio: s.ratio ?? 2,
1044
- makeupGain: s.makeupGain ?? 1
1045
- } : t.suggestedSettings, f = Ge(e, f, r.threshold, r.ratio, r.makeupGain);
1046
- }
1038
+ let t = s.threshold !== void 0 || s.ratio !== void 0 || s.makeupGain !== void 0 ? {
1039
+ threshold: s.threshold ?? .5,
1040
+ ratio: s.ratio ?? 2,
1041
+ makeupGain: s.makeupGain ?? 1
1042
+ } : Ke(f).suggestedSettings;
1043
+ t && (f = Ge(e, f, t.threshold, t.ratio, t.makeupGain));
1047
1044
  }
1048
1045
  if (d?.detectPitch || d?.autotune || i && "auto" in i && i.auto) {
1049
1046
  let e = await Xe(f), t = d?.targetMidiNote || 60, n = Ze(e.midiFloat, t);
@@ -3331,7 +3328,7 @@ var Z = {
3331
3328
  async init() {
3332
3329
  return this.#t ||= (async () => {
3333
3330
  try {
3334
- this.#m && this.#T(), this.#o = new ht(this.context), this.#a = new GainNode(this.context, { gain: 1 }), this.#M(), this.setParam("loopStart", 0, this.now), this.setParam("loopEnd", 0, this.now), this.#w(), this.#E(), this.#F(), this.#r.port.start();
3331
+ this.#m && this.#T(), this.#o = new ht(this.context), this.#a = new GainNode(this.context, { gain: 1 }), this.#j(), this.setParam("loopStart", 0, this.now), this.setParam("loopEnd", 0, this.now), this.#w(), this.#E(), this.#P(), this.#r.port.start();
3335
3332
  } catch (e) {
3336
3333
  throw this.dispose(), this.#t = null, e;
3337
3334
  }
@@ -3364,30 +3361,29 @@ var Z = {
3364
3361
  });
3365
3362
  this.#s.set("filter-env", t);
3366
3363
  }
3367
- this.#P();
3364
+ this.#N();
3368
3365
  }
3369
3366
  async loadBuffer(e, t) {
3370
- if (this.#c = Z.NOT_READY, e.sampleRate !== this.context.sampleRate) return console.warn(`Sample rate mismatch - buffer: ${e.sampleRate}, context: ${this.context.sampleRate}`), !1;
3371
- let n = Array.from({ length: e.numberOfChannels }, (t, n) => e.getChannelData(n).slice());
3367
+ return this.loadLayers([e], t);
3368
+ }
3369
+ async loadLayers(e, t) {
3370
+ this.#c = Z.NOT_READY;
3371
+ let n = e.filter((e) => e.sampleRate === this.context.sampleRate || (console.warn(`Sample rate mismatch - buffer: ${e.sampleRate}, context: ${this.context.sampleRate}`), !1));
3372
+ if (!n.length || n[0] !== e[0]) return console.error("SampleVoice.loadLayers: layer 0 is unusable, nothing loaded. Layer 0 sets duration and loop range for all layers."), !1;
3373
+ let r = n.map((e) => Array.from({ length: e.numberOfChannels }, (t, n) => e.getChannelData(n)));
3372
3374
  return this.sendToProcessor({
3373
- type: "voice:setBuffer",
3374
- buffer: n,
3375
- durationSeconds: e.duration
3376
- }), t?.length && (this.#D(t), this.sendToProcessor({
3375
+ type: "voice:setLayers",
3376
+ layers: r,
3377
+ durationSeconds: n[0].duration
3378
+ }), t?.length && this.sendToProcessor({
3377
3379
  type: "voice:setZeroCrossings",
3378
3380
  zeroCrossings: t
3379
- })), !0;
3381
+ }), !0;
3380
3382
  }
3381
3383
  freeze(e) {
3382
3384
  return console.info(`SampleVoice: freeze(${e}) called.
3383
3385
  Spectral freeze not implemented yet`), this;
3384
3386
  }
3385
- #D(e) {
3386
- return this.sendToProcessor({
3387
- type: "voice:set_zero_crossings",
3388
- zeroCrossings: e
3389
- }), this;
3390
- }
3391
3387
  setGlideTime(e) {
3392
3388
  this.#p = e;
3393
3389
  }
@@ -3396,7 +3392,7 @@ var Z = {
3396
3392
  if (this.#c === Z.PLAYING || this.#c === Z.RELEASING) return console.log(`had to stop a playing voice, midinote: ${t}`), this.stop(i), null;
3397
3393
  this.#c = Z.PLAYING, this.#d = i, this.#u = t;
3398
3394
  let a = (e.glide?.glideTime ?? this.#p) / 8, o = 1, s = 1;
3399
- if (this.#h || (o = v(t), e.glide && (s = v(e.glide.prevMidiNote)), this.#A(o, i, { glideTime: a }), this.#j(o, i, { glideTime: a })), !this.#h && e.glide && a > 0) {
3395
+ if (this.#h || (o = v(t), e.glide && (s = v(e.glide.prevMidiNote)), this.#k(o, i, { glideTime: a }), this.#A(o, i, { glideTime: a })), !this.#h && e.glide && a > 0) {
3400
3396
  let e = this.getParam("playbackRate");
3401
3397
  s > 0 && e.setValueAtTime(s, i), this.getParam("playbackRate").setTargetAtTime(o, i, a);
3402
3398
  } else this.setParam("playbackRate", o, i);
@@ -3451,8 +3447,8 @@ var Z = {
3451
3447
  }
3452
3448
  });
3453
3449
  }
3450
+ #D = null;
3454
3451
  #O = null;
3455
- #k = null;
3456
3452
  release({ releaseTime: e = this.releaseTime, secondsFromNow: t = 0 }) {
3457
3453
  if (this.#c === Z.RELEASING) return this;
3458
3454
  if (!this.getParam("envGain")) throw Error("Cannot release - envGain parameter is null");
@@ -3472,11 +3468,11 @@ var Z = {
3472
3468
  timestamp: n
3473
3469
  });
3474
3470
  let i = Array.from(this.#s.values()).filter((e) => e.isEnabled), a = i.length > 0 ? Math.max(...i.map((e) => e.effectiveReleaseDuration)) : e;
3475
- return this.#O && clearTimeout(this.#O), this.#O = setTimeout(() => {
3471
+ return this.#D && clearTimeout(this.#D), this.#D = setTimeout(() => {
3476
3472
  try {
3477
3473
  (this.#c === Z.RELEASING || this.#c === Z.PLAYING) && this.stop();
3478
3474
  } finally {
3479
- this.#O = null;
3475
+ this.#D = null;
3480
3476
  }
3481
3477
  }, a * 1e3 + 50), this;
3482
3478
  }
@@ -3484,35 +3480,35 @@ var Z = {
3484
3480
  if (this.#c === Z.STOPPED || this.#c === Z.STOPPING) return this;
3485
3481
  this.#c = Z.STOPPING;
3486
3482
  let t = .005, n = Math.max(e, this.now), r = this.getParam("envGain");
3487
- return r && (T(r, n), r.linearRampToValueAtTime(0, n + t)), this.#k && clearTimeout(this.#k), this.#k = setTimeout(() => {
3483
+ return r && (T(r, n), r.linearRampToValueAtTime(0, n + t)), this.#O && clearTimeout(this.#O), this.#O = setTimeout(() => {
3488
3484
  this.sendToProcessor({
3489
3485
  type: "voice:stop",
3490
3486
  timestamp: n
3491
- }), this.#k = null;
3487
+ }), this.#O = null;
3492
3488
  }, Math.max(0, (n + t - this.now) * 1e3)), this;
3493
3489
  }
3494
- #A(e, t = this.now, n = {}) {
3490
+ #k(e, t = this.now, n = {}) {
3495
3491
  if (!this.#u || !this.#g || this.#C <= 0) return;
3496
3492
  let r = this.#g.frequency, { glideTime: i = 0, cancelPrevious: a = !0 } = n || {};
3497
3493
  a && r.cancelScheduledValues(t);
3498
3494
  let o = O(this.#v * e * this.#C, 20, this.context.sampleRate / 2 - 1e3);
3499
3495
  i > 0 ? r.setTargetAtTime(o, t, i) : r.setValueAtTime(o, Math.max(t, this.now + .001));
3500
3496
  }
3501
- #j(e, t = this.now, n = {}) {
3497
+ #A(e, t = this.now, n = {}) {
3502
3498
  if (!this.#u || !this.#_ || this.#S <= 0) return;
3503
3499
  let r = this.#_.frequency, { glideTime: i = 0, cancelPrevious: a = !0 } = n || {};
3504
3500
  a && r.cancelScheduledValues(t);
3505
3501
  let o = O(this.#b * e * this.#S, 20, this.context.sampleRate / 2 - 1e3);
3506
3502
  i > 0 ? r.setTargetAtTime(o, t, i) : r.setValueAtTime(o, Math.max(t, this.now + .001));
3507
3503
  }
3508
- #M(e = 0, t = "square", n = {}) {
3504
+ #j(e = 0, t = "square", n = {}) {
3509
3505
  if (this.#i === null) {
3510
3506
  if (this.#i = new J(this.context), this.#i.setWaveform(t, n), this.#i.setDepth(e), this.#i.setMusicalNote(this.#u ?? 60), this.#a) this.#i.connect(this.#a.gain);
3511
3507
  else throw console.error("Missing gain node for AM-LFO in SampleVoice"), Error("Missing gain node for AM-LFO in SampleVoice");
3512
3508
  } else console.debug("setupAmpModLFO: LFO already setup: ", this.#i);
3513
3509
  return this;
3514
3510
  }
3515
- #N() {
3511
+ #M() {
3516
3512
  if (this.#i) return this.#i.dispose(), this.#i = null, this;
3517
3513
  }
3518
3514
  setModulationAmount(e, t) {
@@ -3520,10 +3516,10 @@ var Z = {
3520
3516
  warn: !0,
3521
3517
  name: "sampleVoice.setModulationAmount"
3522
3518
  });
3523
- return e === "AM" ? (this.#i || this.#M(n), this.#i?.setDepth(n)) : e === "FM" && console.warn("SampleVoice: FM modulation not implemented yet"), this;
3519
+ return e === "AM" ? (this.#i || this.#j(n), this.#i?.setDepth(n)) : e === "FM" && console.warn("SampleVoice: FM modulation not implemented yet"), this;
3524
3520
  }
3525
3521
  setModulationWaveform(e = "AM", t = "triangle", n = {}) {
3526
- return e === "AM" ? (this.#i || this.#M(), this.#i?.setWaveform(t, n)) : e === "FM" && console.info("SampleVoice: FM modulation not implemented yet"), this;
3522
+ return e === "AM" ? (this.#i || this.#j(), this.#i?.setWaveform(t, n)) : e === "FM" && console.info("SampleVoice: FM modulation not implemented yet"), this;
3527
3523
  }
3528
3524
  enableEnvelope = (e) => {
3529
3525
  this.#s.get(e)?.enable();
@@ -3612,14 +3608,14 @@ var Z = {
3612
3608
  disablePitch = () => {
3613
3609
  this.#h = !0;
3614
3610
  let e = this.now, t = .1;
3615
- this.getParam("playbackRate")?.linearRampToValueAtTime(1, e + t), this.#A(1, e, { glideTime: t }), this.#j(1, e, { glideTime: t });
3611
+ this.getParam("playbackRate")?.linearRampToValueAtTime(1, e + t), this.#k(1, e, { glideTime: t }), this.#A(1, e, { glideTime: t });
3616
3612
  };
3617
3613
  enablePitch = () => {
3618
3614
  this.#h = !1;
3619
3615
  let e = this.now, t = .1;
3620
3616
  if (this.#u) {
3621
3617
  let n = v(this.#u);
3622
- this.getParam("playbackRate")?.linearRampToValueAtTime(n, this.context.currentTime + .01), this.#A(n, e, { glideTime: t }), this.#j(n, e, { glideTime: t });
3618
+ this.getParam("playbackRate")?.linearRampToValueAtTime(n, this.context.currentTime + .01), this.#k(n, e, { glideTime: t }), this.#A(n, e, { glideTime: t });
3623
3619
  }
3624
3620
  };
3625
3621
  connect(e, t, n) {
@@ -3637,7 +3633,7 @@ var Z = {
3637
3633
  sendUpstreamMessage(e, t) {
3638
3634
  return this.#e.sendMessage(e, t), this;
3639
3635
  }
3640
- #P() {
3636
+ #N() {
3641
3637
  this.#s.forEach((e, t) => {
3642
3638
  this.#e.forwardFrom(e, [
3643
3639
  `${t}:trigger`,
@@ -3651,7 +3647,7 @@ var Z = {
3651
3647
  }));
3652
3648
  });
3653
3649
  }
3654
- #F() {
3650
+ #P() {
3655
3651
  this.#r.port.onmessage = (e) => {
3656
3652
  let { type: t, ...n } = e.data;
3657
3653
  switch (t) {
@@ -3775,14 +3771,14 @@ var Z = {
3775
3771
  setEnvelopeLoop = (e, t, n = "normal") => (this.#s.get(e)?.setLoopEnabled(t, n), this);
3776
3772
  syncEnvelopeToPlaybackRate = (e, t) => (this.#s.get(e)?.syncToPlaybackRate(t), this);
3777
3773
  setPlaybackRate(e, t = this.now, n) {
3778
- return this.setParam("playbackRate", e, t, n), this.#A(e, t, n), this.#j(e, t, n), this;
3774
+ return this.setParam("playbackRate", e, t, n), this.#k(e, t, n), this.#A(e, t, n), this;
3779
3775
  }
3780
3776
  setHpfCutoff(e, t = this.now, n = {}) {
3781
3777
  let r = O(e, 20, this.context.sampleRate / 2 - 1e3);
3782
3778
  if (this.#v = r, this.#g) {
3783
3779
  this.setParam("hpf", r, t, { glideTime: 0 });
3784
3780
  let e = this.getParam("playbackRate")?.value ?? 1;
3785
- this.#A(e, t, n);
3781
+ this.#k(e, t, n);
3786
3782
  }
3787
3783
  return this;
3788
3784
  }
@@ -3794,7 +3790,7 @@ var Z = {
3794
3790
  cancelPrevious: !0
3795
3791
  });
3796
3792
  let e = this.getParam("playbackRate")?.value ?? 1;
3797
- this.#j(e, t, n);
3793
+ this.#A(e, t, n);
3798
3794
  }
3799
3795
  return this;
3800
3796
  }
@@ -3838,7 +3834,7 @@ var Z = {
3838
3834
  `);
3839
3835
  }
3840
3836
  dispose() {
3841
- this.stop(), this.disconnect(), this.#N(), this.#s.forEach((e) => e.dispose()), this.#r.port.close(), this.#O && clearTimeout(this.#O), this.#k && clearTimeout(this.#k), B(this.nodeId);
3837
+ this.stop(), this.disconnect(), this.#M(), this.#s.forEach((e) => e.dispose()), this.#r.port.close(), this.#D && clearTimeout(this.#D), this.#O && clearTimeout(this.#O), B(this.nodeId);
3842
3838
  }
3843
3839
  getParam(e) {
3844
3840
  if (this.#r && this.#r.parameters.has(e)) return this.#r.parameters.get(e) ?? null;
@@ -3959,8 +3955,8 @@ var xt = class {
3959
3955
  type: "sample:loaded"
3960
3956
  } : null) : e);
3961
3957
  }
3962
- setBuffer(e, t) {
3963
- return this.#o.clear(), this.#a.forEach((n) => n.loadBuffer(e, t)), this;
3958
+ setLayers(e, t) {
3959
+ return this.#o.clear(), this.#a.forEach((n) => n.loadLayers(e, t)), this;
3964
3960
  }
3965
3961
  allocate(e = this.#s, t = this.#l) {
3966
3962
  let n;
@@ -4064,7 +4060,7 @@ async function St(e, t) {
4064
4060
  }
4065
4061
  //#endregion
4066
4062
  //#region src/nodes/instruments/Sample/SamplePlayer.ts
4067
- var Ct = class {
4063
+ var Ct = class e {
4068
4064
  nodeId;
4069
4065
  nodeType = "sample-player";
4070
4066
  context;
@@ -4077,52 +4073,54 @@ var Ct = class {
4077
4073
  #o = /* @__PURE__ */ new Set();
4078
4074
  #s = /* @__PURE__ */ new Set();
4079
4075
  #c = null;
4080
- #l = 0;
4081
- #u = !1;
4076
+ #l = [];
4077
+ static MAX_LAYERS = 4;
4078
+ #u = 0;
4082
4079
  #d = !1;
4083
4080
  #f = !1;
4084
4081
  #p = !1;
4085
4082
  #m = !1;
4086
- #h;
4083
+ #h = !1;
4087
4084
  #g;
4088
4085
  #_;
4089
- #v = null;
4086
+ #v;
4090
4087
  #y = null;
4091
- #b = 0;
4092
- #x = 120;
4093
- #S = q.glide.defaultValue;
4094
- #C = q.loopRampDuration.defaultValue;
4095
- #w = q.keytrackLoop.defaultValue;
4096
- #T = q.highpassFilter.defaultValue;
4097
- #E = q.lowpassFilter.defaultValue;
4098
- #D = !1;
4099
- #O = 300;
4100
- #k = 20;
4101
- #A = !1;
4088
+ #b = null;
4089
+ #x = 0;
4090
+ #S = 120;
4091
+ #C = q.glide.defaultValue;
4092
+ #w = q.loopRampDuration.defaultValue;
4093
+ #T = q.keytrackLoop.defaultValue;
4094
+ #E = q.highpassFilter.defaultValue;
4095
+ #D = q.lowpassFilter.defaultValue;
4096
+ #O = !1;
4097
+ #k = 300;
4098
+ #A = 20;
4102
4099
  #j = !1;
4103
- #M = [];
4104
- #N = !0;
4100
+ #M = !1;
4101
+ #N = [];
4105
4102
  #P = !0;
4103
+ #F = !0;
4106
4104
  randomizeVelocity = !1;
4107
4105
  voicePool;
4108
4106
  outBus;
4109
- #F = /* @__PURE__ */ new Set();
4107
+ #I = /* @__PURE__ */ new Set();
4110
4108
  constructor(e, t = 16, n) {
4111
- this.nodeId = z("sample-player", this), this.context = e, this.#e = V(this.nodeId), this.#h = new GainNode(this.context, { gain: .5 }), this.#g = new ot(this.context, 0), this.#_ = new ot(this.context, 0), this.#i = t, this.#a = n || null;
4109
+ this.nodeId = z("sample-player", this), this.context = e, this.#e = V(this.nodeId), this.#g = new GainNode(this.context, { gain: .5 }), this.#_ = new ot(this.context, 0), this.#v = new ot(this.context, 0), this.#i = t, this.#a = n || null;
4112
4110
  }
4113
4111
  async init() {
4114
4112
  if (!this.#t) return this.#n ||= (async () => {
4115
4113
  try {
4116
- this.outBus = await _t(this.context), this.voicePool = await St(this.context, this.#i), this.#z(), this.#I(), this.#R(), this.#B(), this.#L(), this.#a && await this.loadSample(this.#a, void 0, { skipPreProcessing: !0 }), this.#t = !0;
4114
+ this.outBus = await _t(this.context), this.voicePool = await St(this.context, this.#i), this.#B(), this.#L(), this.#z(), this.#V(), this.#R(), this.#a && await this.loadSample(this.#a, void 0, { skipPreProcessing: !0 }), this.#t = !0;
4117
4115
  } catch (e) {
4118
- this.voicePool?.dispose(), this.#g?.dispose(), this.#_?.dispose();
4116
+ this.voicePool?.dispose(), this.#_?.dispose(), this.#v?.dispose();
4119
4117
  let t = e instanceof Error ? e.message : String(e);
4120
4118
  throw Error(`Failed to initialize SamplePlayer: ${t}`);
4121
4119
  }
4122
4120
  })(), this.#n;
4123
4121
  }
4124
- #I() {
4125
- this.voicePool.connect(this.outBus.input), this.outBus.connect(this.#h), this.#h.connect(this.context.destination);
4122
+ #L() {
4123
+ this.voicePool.connect(this.outBus.input), this.outBus.connect(this.#g), this.#g.connect(this.context.destination);
4126
4124
  }
4127
4125
  onMessage(e, t) {
4128
4126
  return this.#e.onMessage(e, t);
@@ -4132,13 +4130,13 @@ var Ct = class {
4132
4130
  }
4133
4131
  connect(e) {
4134
4132
  let t = "input" in e && e.input ? e.input : e;
4135
- this.#h.connect(t), "nodeId" in e && (this.#o.add(e.nodeId), e.addIncoming?.(this.nodeId));
4133
+ this.#g.connect(t), "nodeId" in e && (this.#o.add(e.nodeId), e.addIncoming?.(this.nodeId));
4136
4134
  }
4137
4135
  disconnect(e) {
4138
4136
  if (e) {
4139
4137
  let t = "input" in e ? e.input : e;
4140
- this.#h.disconnect(t), "nodeId" in e && (this.#o.delete(e.nodeId), e.removeIncoming?.(this.nodeId));
4141
- } else this.#h.disconnect(), this.#o.clear();
4138
+ this.#g.disconnect(t), "nodeId" in e && (this.#o.delete(e.nodeId), e.removeIncoming?.(this.nodeId));
4139
+ } else this.#g.disconnect(), this.#o.clear();
4142
4140
  }
4143
4141
  addIncoming(e) {
4144
4142
  this.#s.add(e.nodeId);
@@ -4153,13 +4151,13 @@ var Ct = class {
4153
4151
  };
4154
4152
  }
4155
4153
  get audioNode() {
4156
- return this.#h;
4154
+ return this.#g;
4157
4155
  }
4158
4156
  get input() {
4159
4157
  return this.outBus.input;
4160
4158
  }
4161
4159
  get output() {
4162
- return this.#h;
4160
+ return this.#g;
4163
4161
  }
4164
4162
  get now() {
4165
4163
  return this.context.currentTime;
@@ -4167,7 +4165,7 @@ var Ct = class {
4167
4165
  get initialized() {
4168
4166
  return this.#t;
4169
4167
  }
4170
- #L() {
4168
+ #R() {
4171
4169
  return this.voicePool.onMessage("sample:loaded", (e) => {
4172
4170
  this.#r = !0;
4173
4171
  }), this.voicePool.onMessage("voice-pool:initialized", () => {
@@ -4194,26 +4192,26 @@ var Ct = class {
4194
4192
  }
4195
4193
  getMacrosAudioParam(e) {
4196
4194
  switch (e) {
4197
- case "loopStart": return this.#g.audioParam;
4198
- case "loopEnd": return this.#_.audioParam;
4195
+ case "loopStart": return this.#_.audioParam;
4196
+ case "loopEnd": return this.#v.audioParam;
4199
4197
  default: throw Error(`Unknown macro parameter: ${e}`);
4200
4198
  }
4201
4199
  }
4202
4200
  getMacro(e) {
4203
4201
  switch (e) {
4204
- case "loopStart": return this.#g;
4205
- case "loopEnd": return this.#_;
4202
+ case "loopStart": return this.#_;
4203
+ case "loopEnd": return this.#v;
4206
4204
  default: throw Error(`Unknown macro parameter: ${e}`);
4207
4205
  }
4208
4206
  }
4209
- #R() {
4207
+ #z() {
4210
4208
  return this.voicePool.allVoices.forEach((e, t) => {
4211
4209
  let n = e.getParam("loopStart"), r = e.getParam("loopEnd");
4212
- n ? this.#g.addTarget(n, "loopStart") : console.error("loopStart param is null!"), r ? this.#_.addTarget(r, "loopEnd") : console.error("loopEnd param is null!");
4210
+ n ? this.#_.addTarget(n, "loopStart") : console.error("loopStart param is null!"), r ? this.#v.addTarget(r, "loopEnd") : console.error("loopEnd param is null!");
4213
4211
  }), this;
4214
4212
  }
4215
- #z() {
4216
- return this.#g.setValue(0), this.#_.setValue(this.#l), this;
4213
+ #B() {
4214
+ return this.#_.setValue(0), this.#v.setValue(this.#u), this;
4217
4215
  }
4218
4216
  setModulationAmount = (e, t) => this.voicePool.applyToAllVoices((n) => n.setModulationAmount(e, t));
4219
4217
  setModulationWaveform(e = "AM", t = "triangle", n = {}) {
@@ -4221,14 +4219,6 @@ var Ct = class {
4221
4219
  }
4222
4220
  syncLFOsToNoteFreq(e, t) {
4223
4221
  if (e === "gain-lfo") {
4224
- if (t === !0) this.#v?.storeCurrentValues();
4225
- else {
4226
- let e = this.#v?.getStoredValues();
4227
- e && this.#v?.setFrequency(e.rate);
4228
- }
4229
- this.#A = t;
4230
- }
4231
- if (e === "pitch-lfo") {
4232
4222
  if (t === !0) this.#y?.storeCurrentValues();
4233
4223
  else {
4234
4224
  let e = this.#y?.getStoredValues();
@@ -4236,13 +4226,21 @@ var Ct = class {
4236
4226
  }
4237
4227
  this.#j = t;
4238
4228
  }
4229
+ if (e === "pitch-lfo") {
4230
+ if (t === !0) this.#b?.storeCurrentValues();
4231
+ else {
4232
+ let e = this.#b?.getStoredValues();
4233
+ e && this.#b?.setFrequency(e.rate);
4234
+ }
4235
+ this.#M = t;
4236
+ }
4239
4237
  }
4240
- #B() {
4241
- this.#v = new J(this.context), this.#v.setWaveform("sine"), this.#y = new J(this.context);
4242
- let e = this.#y.getPitchWobbleWaveform();
4243
- this.#y.setWaveform(e), this.#V(this.#y, "playbackRate"), this.#v.connect(this.outBus.input.gain);
4238
+ #V() {
4239
+ this.#y = new J(this.context), this.#y.setWaveform("sine"), this.#b = new J(this.context);
4240
+ let e = this.#b.getPitchWobbleWaveform();
4241
+ this.#b.setWaveform(e), this.#H(this.#b, "playbackRate"), this.#y.connect(this.outBus.input.gain);
4244
4242
  }
4245
- #V(e, t) {
4243
+ #H(e, t) {
4246
4244
  this.voicePool.applyToAllVoices((n) => {
4247
4245
  let r = n.getParam(t);
4248
4246
  r && e.connect(r);
@@ -4251,32 +4249,65 @@ var Ct = class {
4251
4249
  freezeActiveVoices(e) {
4252
4250
  return console.info(`SamplePlayer: freezeActiveVoices(${e}). Spectral freeze not implemented yet`), this;
4253
4251
  }
4254
- #H = !1;
4252
+ #U = !1;
4255
4253
  async loadSample(e, t, n) {
4256
- if (this.#H) throw Error("A sample load is already in progress");
4257
- this.#H = !0;
4258
- let r;
4254
+ return (await this.loadLayers([e], t, n))?.[0] ?? null;
4255
+ }
4256
+ async loadLayers(t, n, r) {
4257
+ if (this.#U) throw Error("A sample load is already in progress");
4258
+ this.#U = !0;
4259
+ let i;
4259
4260
  try {
4260
- if (e instanceof ArrayBuffer && (e = await this.context.decodeAudioData(e.slice(0))), !o(e)) return console.error("Invalid AudioBuffer provided to loadSample"), null;
4261
- if (e.sampleRate !== this.context.sampleRate) throw RangeError(`Sample rate mismatch: buffer rate ${e.sampleRate}, context rate ${this.context.sampleRate}`);
4262
- t && this.context.sampleRate !== t && console.warn(`Sample rate mismatch: context rate ${this.context.sampleRate}, requested rate ${t}`), this.releaseAll(0), this.transposeSemitones = 0, this.#r = !1, this.#c = null;
4263
- let i;
4264
- this.#P && (i = await Je(this.context, e, n), e = i.audiobuffer, this.#N && i.zeroCrossings && (this.#M = i.zeroCrossings)), this.#c = e, this.#l = e.duration;
4265
- let a = new Promise((e) => {
4266
- r = this.voicePool.onMessage("sample:loaded", () => {
4261
+ t.length > e.MAX_LAYERS && (console.warn(`Ignoring layers past ${e.MAX_LAYERS}; got ${t.length}`), t = t.slice(0, e.MAX_LAYERS));
4262
+ let a = [];
4263
+ for (let [e, n] of t.entries()) {
4264
+ let t = n;
4265
+ if (t instanceof ArrayBuffer) try {
4266
+ t = await this.context.decodeAudioData(t.slice(0));
4267
+ } catch (t) {
4268
+ if (e === 0) throw t;
4269
+ console.warn(`Failed to decode layer ${e}; skipping`, t);
4270
+ continue;
4271
+ }
4272
+ if (!o(t)) {
4273
+ if (console.error(`Invalid AudioBuffer provided for layer ${e}`), e === 0) return null;
4274
+ continue;
4275
+ }
4276
+ if (t.sampleRate !== this.context.sampleRate) {
4277
+ let n = `Sample rate mismatch: layer ${e} rate ${t.sampleRate}, context rate ${this.context.sampleRate}`;
4278
+ if (e === 0) throw RangeError(n);
4279
+ console.warn(n);
4280
+ continue;
4281
+ }
4282
+ a.push(t);
4283
+ }
4284
+ if (!a.length) return null;
4285
+ n && this.context.sampleRate !== n && console.warn(`Sample rate mismatch: context rate ${this.context.sampleRate}, requested rate ${n}`);
4286
+ let s = [], c = [];
4287
+ for (let [e, t] of a.entries()) {
4288
+ if (!this.#F) {
4289
+ s.push(t);
4290
+ continue;
4291
+ }
4292
+ let n = await Je(this.context, t, r);
4293
+ s.push(n.audiobuffer), e === 0 && this.#P && n.zeroCrossings && (c = n.zeroCrossings);
4294
+ }
4295
+ this.releaseAll(0), this.transposeSemitones = 0, this.#r = !1, this.#l = s, this.#c = s[0], this.#u = s[0].duration, this.#N = c;
4296
+ let l = new Promise((e) => {
4297
+ i = this.voicePool.onMessage("sample:loaded", () => {
4267
4298
  e();
4268
4299
  });
4269
4300
  });
4270
- return this.voicePool.setBuffer(e, this.#M), this.#z(), this.setScale({
4301
+ return this.voicePool.setLayers(s, c), this.#B(), this.setScale({
4271
4302
  rootNote: "C",
4272
4303
  scale: [0],
4273
4304
  lowestOctave: 0,
4274
4305
  highestOctave: 5,
4275
4306
  tuningOffset: 0,
4276
4307
  normalize: !1
4277
- }), await a, e;
4308
+ }), await l, [...s];
4278
4309
  } finally {
4279
- r?.(), this.#H = !1;
4310
+ i?.(), this.#U = !1;
4280
4311
  }
4281
4312
  }
4282
4313
  async cropSample(e = this.getStartPoint(), t = this.getEndPoint(), n = 4) {
@@ -4311,39 +4342,39 @@ var Ct = class {
4311
4342
  return n;
4312
4343
  }
4313
4344
  play(e, t = 100, n = this.getGlideTime()) {
4314
- let r = he(t) ? t : 100, i = e + this.#b;
4315
- return he(i) ? (this.#A && this.#v?.setMusicalNote(i), this.#j && this.#y?.setMusicalNote(i, { divisor: 4 }), this.outBus.noteOn(i, r, 0, n), this.voicePool.noteOn(i, r, 0, n)) : (console.warn(`Invalid midiNote: ${i}`), null);
4345
+ let r = he(t) ? t : 100, i = e + this.#x;
4346
+ return he(i) ? (this.#j && this.#y?.setMusicalNote(i), this.#M && this.#b?.setMusicalNote(i, { divisor: 4 }), this.outBus.noteOn(i, r, 0, n), this.voicePool.noteOn(i, r, 0, n)) : (console.warn(`Invalid midiNote: ${i}`), null);
4316
4347
  }
4317
4348
  release(e) {
4318
- if (this.holdEnabled || this.#p) return this;
4319
- let t = e + this.#b;
4320
- return this.#m ? (this.#F.add(t), this) : (this.#F.delete(t), this.voicePool.noteOff(t), this.sendUpstreamMessage("note:off", { transposedMidiNote: t }), this);
4349
+ if (this.holdEnabled || this.#m) return this;
4350
+ let t = e + this.#x;
4351
+ return this.#h ? (this.#I.add(t), this) : (this.#I.delete(t), this.voicePool.noteOff(t), this.sendUpstreamMessage("note:off", { transposedMidiNote: t }), this);
4321
4352
  }
4322
4353
  releaseAll(e) {
4323
- return this.#F.clear(), this.voicePool?.allNotesOff(e), this;
4354
+ return this.#I.clear(), this.voicePool?.allNotesOff(e), this;
4324
4355
  }
4325
4356
  panic = (e) => this.releaseAll(e);
4326
4357
  get transposedBySemitones() {
4327
- return this.#b;
4358
+ return this.#x;
4328
4359
  }
4329
4360
  set transposeSemitones(e) {
4330
- this.#b !== e && (this.#b = e);
4361
+ this.#x !== e && (this.#x = e);
4331
4362
  }
4332
4363
  setScale(e) {
4333
- return this.#g.setScale({
4334
- snapToZeroCrossings: this.#M,
4364
+ return this.#_.setScale({
4365
+ snapToZeroCrossings: this.#N,
4335
4366
  ...e
4336
- }), this.#_.setScale({
4337
- snapToZeroCrossings: this.#M,
4367
+ }), this.#v.setScale({
4368
+ snapToZeroCrossings: this.#N,
4338
4369
  ...e
4339
4370
  }), this;
4340
4371
  }
4341
4372
  setRootNote(e) {
4342
4373
  let t = b[e], n = t === 0 ? 0 : t - 12;
4343
- return this.transposedBySemitones === n ? this : (this.transposeSemitones = n, this.#_.setRootNote(e), this.#g.setRootNote(e), this);
4374
+ return this.transposedBySemitones === n ? this : (this.transposeSemitones = n, this.#v.setRootNote(e), this.#_.setRootNote(e), this);
4344
4375
  }
4345
4376
  setVolume(e) {
4346
- return e = O(e, 0, 1), this.#h.gain.setValueAtTime(e, this.now), this;
4377
+ return e = O(e, 0, 1), this.#g.gain.setValueAtTime(e, this.now), this;
4347
4378
  }
4348
4379
  setSampleStartPoint(e) {
4349
4380
  return this.voicePool.applyToAllVoices((t) => t.setStartPoint(e)), this.sendUpstreamMessage("start-point:updated", { startPoint: e }), this;
@@ -4352,29 +4383,29 @@ var Ct = class {
4352
4383
  return this.voicePool.applyToAllVoices((t) => t.setEndPoint(e)), this.sendUpstreamMessage("end-point:updated", { endPoint: e }), this;
4353
4384
  }
4354
4385
  setLoopRampDuration(e) {
4355
- return this.#C = e, this;
4386
+ return this.#w = e, this;
4356
4387
  }
4357
4388
  setGlideTime(e) {
4358
- return this.#S = e, this;
4389
+ return this.#C = e, this;
4359
4390
  }
4360
4391
  setLoopEnabled(e) {
4361
- return this.#u === e || this.#d && !e ? this : (this.voicePool.allVoices.forEach((t) => t.setLoopEnabled(e)), this.#u = e, this.sendUpstreamMessage("loop:enabled", { enabled: e }), this);
4392
+ return this.#d === e || this.#f && !e ? this : (this.voicePool.allVoices.forEach((t) => t.setLoopEnabled(e)), this.#d = e, this.sendUpstreamMessage("loop:enabled", { enabled: e }), this);
4362
4393
  }
4363
4394
  setLoopLocked(e) {
4364
- return this.#d === e ? this : (this.#d = e, this.setLoopEnabled(e), this.sendUpstreamMessage("loop:locked", { locked: e }), this);
4395
+ return this.#f === e ? this : (this.#f = e, this.setLoopEnabled(e), this.sendUpstreamMessage("loop:locked", { locked: e }), this);
4365
4396
  }
4366
4397
  setHoldEnabled(e) {
4367
- return this.#f === e || this.#p && !e ? this : (this.#f = e, e || this.releaseAll(.1), this.sendUpstreamMessage("hold:enabled", { enabled: e }), this);
4398
+ return this.#p === e || this.#m && !e ? this : (this.#p = e, e || this.releaseAll(.1), this.sendUpstreamMessage("hold:enabled", { enabled: e }), this);
4368
4399
  }
4369
4400
  setHoldLocked(e) {
4370
- return this.#p === e ? this : (this.#p = e, e === !1 && this.releaseAll(), this.sendUpstreamMessage("hold:locked", { locked: e }), this);
4401
+ return this.#m === e ? this : (this.#m = e, e === !1 && this.releaseAll(), this.sendUpstreamMessage("hold:locked", { locked: e }), this);
4371
4402
  }
4372
- #U = !1;
4403
+ #W = !1;
4373
4404
  setSustainPedal(e) {
4374
- if (this.#m === e) return this;
4375
- if (this.#m = e, this.#d || (this.#U && !e ? (this.#U = !1, this.setLoopEnabled(!1)) : e && !this.#u && (this.setLoopEnabled(!0), this.#U = !0)), this.#p || this.setHoldEnabled(e), !e) {
4376
- for (let e of this.#F) this.voicePool.noteOff(e), this.sendUpstreamMessage("note:off", { transposedMidiNote: e });
4377
- this.#F.clear();
4405
+ if (this.#h === e) return this;
4406
+ if (this.#h = e, this.#f || (this.#W && !e ? (this.#W = !1, this.setLoopEnabled(!1)) : e && !this.#d && (this.setLoopEnabled(!0), this.#W = !0)), this.#m || this.setHoldEnabled(e), !e) {
4407
+ for (let e of this.#I) this.voicePool.noteOff(e), this.sendUpstreamMessage("note:off", { transposedMidiNote: e });
4408
+ this.#I.clear();
4378
4409
  }
4379
4410
  return this;
4380
4411
  }
@@ -4395,37 +4426,37 @@ var Ct = class {
4395
4426
  setLoopDuration = (e, t = this.getLoopRampDuration()) => this.setLoopPoint("end", this.loopStart, this.loopStart + e, t);
4396
4427
  debugcounter = 0;
4397
4428
  setTempo(e) {
4398
- if (!(e < this.#k || e > this.#O)) return this.#x = e, this.voicePool.applyToAllVoices((t) => t.setTempo(e)), this.sendUpstreamMessage("tempo:updated", { bpm: e }), this;
4429
+ if (!(e < this.#A || e > this.#k)) return this.#S = e, this.voicePool.applyToAllVoices((t) => t.setTempo(e)), this.sendUpstreamMessage("tempo:updated", { bpm: e }), this;
4399
4430
  }
4400
4431
  syncLoopToTempo(e) {
4401
4432
  return this.voicePool.applyToAllVoices((t) => t.syncLoopToTempo(e)), this;
4402
4433
  }
4403
4434
  setKeytrackLoopAmount(e) {
4404
4435
  let t = Math.max(0, Math.min(1, e));
4405
- return this.#w = t, this.voicePool.applyToAllVoices((e) => e.setKeytrackLoopAmount(t)), this;
4436
+ return this.#T = t, this.voicePool.applyToAllVoices((e) => e.setKeytrackLoopAmount(t)), this;
4406
4437
  }
4407
- getKeytrackLoopAmount = () => this.#w;
4438
+ getKeytrackLoopAmount = () => this.#T;
4408
4439
  get tempo() {
4409
- return this.#x;
4440
+ return this.#S;
4410
4441
  }
4411
4442
  setLoopPoint(e, t, n, r = this.getLoopRampDuration()) {
4412
4443
  let i = e === "start" ? O(t, this.MIN_LOOP_DURATION_SECONDS / 2, n) : t;
4413
4444
  if (e === "start" && i === this.loopStart) return this;
4414
- let a = O(n, i, this.#l - this.MIN_LOOP_DURATION_SECONDS / 2);
4445
+ let a = O(n, i, this.#u - this.MIN_LOOP_DURATION_SECONDS / 2);
4415
4446
  if (e === "end" && a === this.loopEnd) return this;
4416
4447
  let o = a - i, s = r * 1;
4417
4448
  if (e === "start" && i !== this.loopStart) {
4418
- if (this.#D) {
4419
- let e = 60 / this.#x, t = Math.round(o / e);
4449
+ if (this.#O) {
4450
+ let e = 60 / this.#S, t = Math.round(o / e);
4420
4451
  i = a - t * e;
4421
4452
  }
4422
- o < this.MIN_LOOP_DURATION_SECONDS && (i = a - this.MIN_LOOP_DURATION_SECONDS), this.#g.ramp(i, s, a);
4453
+ o < this.MIN_LOOP_DURATION_SECONDS && (i = a - this.MIN_LOOP_DURATION_SECONDS), this.#_.ramp(i, s, a);
4423
4454
  } else if (e === "end" && a !== this.loopEnd) {
4424
- if (this.#D) {
4425
- let e = 60 / this.#x, t = Math.round(o / e);
4455
+ if (this.#O) {
4456
+ let e = 60 / this.#S, t = Math.round(o / e);
4426
4457
  a = i + t * e;
4427
4458
  }
4428
- o < this.MIN_LOOP_DURATION_SECONDS && (a = i + this.MIN_LOOP_DURATION_SECONDS), this.#_.ramp(a, s, i);
4459
+ o < this.MIN_LOOP_DURATION_SECONDS && (a = i + this.MIN_LOOP_DURATION_SECONDS), this.#v.ramp(a, s, i);
4429
4460
  }
4430
4461
  return this.sendUpstreamMessage("loop-points:updated", {
4431
4462
  loopStart: this.loopStart,
@@ -4434,7 +4465,7 @@ var Ct = class {
4434
4465
  }
4435
4466
  scrollLoopPoints(e, t) {
4436
4467
  let n = this.context.currentTime;
4437
- return this.#g.setValue(e, n), this.#_.setValue(t, n), this.sendUpstreamMessage("loop-points:updated", {
4468
+ return this.#_.setValue(e, n), this.#v.setValue(t, n), this.sendUpstreamMessage("loop-points:updated", {
4438
4469
  loopStart: this.loopStart,
4439
4470
  loopEnd: this.loopEnd
4440
4471
  }), this;
@@ -4471,8 +4502,8 @@ var Ct = class {
4471
4502
  }
4472
4503
  getAudioParam(e) {
4473
4504
  switch (e) {
4474
- case "loopStart": return this.#g.audioParam;
4475
- case "loopEnd": return this.#_.audioParam;
4505
+ case "loopStart": return this.#_.audioParam;
4506
+ case "loopEnd": return this.#v.audioParam;
4476
4507
  default: return console.warn(`Parameter '${e}' not found on SamplePlayer`), null;
4477
4508
  }
4478
4509
  }
@@ -4483,13 +4514,13 @@ var Ct = class {
4483
4514
  return this.voicePool?.allVoices[0]?.endPoint ?? this.sampleDuration;
4484
4515
  }
4485
4516
  getLoopRampDuration() {
4486
- return this.#C;
4517
+ return this.#w;
4487
4518
  }
4488
4519
  getGlideTime() {
4489
- return this.#S;
4520
+ return this.#C;
4490
4521
  }
4491
- getHpfCutoff = () => this.#T;
4492
- getLpfCutoff = () => this.#E;
4522
+ getHpfCutoff = () => this.#E;
4523
+ getLpfCutoff = () => this.#D;
4493
4524
  getParameterValue(e) {
4494
4525
  switch (e) {
4495
4526
  case "loopStart": return this.loopStart;
@@ -4554,12 +4585,12 @@ var Ct = class {
4554
4585
  this.outBus.setSendAmount(e, t);
4555
4586
  };
4556
4587
  setLpfCutoff = (e, t = "pre") => {
4557
- this.#E = e, t === "pre" ? this.voicePool.applyToAllVoices((t) => {
4588
+ this.#D = e, t === "pre" ? this.voicePool.applyToAllVoices((t) => {
4558
4589
  t.setLpfCutoff(e);
4559
4590
  }) : t === "post" && this.outBus.setLpfCutoff(e);
4560
4591
  };
4561
4592
  setHpfCutoff = (e, t = "pre") => {
4562
- this.#T = e, t === "pre" ? this.voicePool.applyToAllVoices((t) => {
4593
+ this.#E = e, t === "pre" ? this.voicePool.applyToAllVoices((t) => {
4563
4594
  t.setHpfCutoff(e);
4564
4595
  }) : t === "post" && this.outBus.setHpfCutoff(e);
4565
4596
  };
@@ -4577,13 +4608,13 @@ var Ct = class {
4577
4608
  });
4578
4609
  }
4579
4610
  setFeedbackAmount = (e) => {
4580
- e = O(e, 0, 1), (this.#W === "monophonic" || this.#W === "double-trouble") && this.outBus.setFeedbackAmount(e), (this.#W === "polyphonic" || this.#W === "double-trouble") && this.voicePool.applyToAllVoices((t) => {
4611
+ e = O(e, 0, 1), (this.#G === "monophonic" || this.#G === "double-trouble") && this.outBus.setFeedbackAmount(e), (this.#G === "polyphonic" || this.#G === "double-trouble") && this.voicePool.applyToAllVoices((t) => {
4581
4612
  t.feedback?.setAmountMacro(e);
4582
4613
  });
4583
4614
  };
4584
- #W = "monophonic";
4615
+ #G = "monophonic";
4585
4616
  setFeedbackMode(e) {
4586
- if (this.#W = e, e === "monophonic") {
4617
+ if (this.#G = e, e === "monophonic") {
4587
4618
  let e = this.voicePool.allVoices[0].feedback?.currentAmount ?? 0;
4588
4619
  this.voicePool.applyToAllVoices((e) => {
4589
4620
  e.feedback?.setAmountMacro(0);
@@ -4601,37 +4632,37 @@ var Ct = class {
4601
4632
  });
4602
4633
  }
4603
4634
  get mainOut() {
4604
- return this.#h;
4635
+ return this.#g;
4605
4636
  }
4606
4637
  get outputBus() {
4607
4638
  return this.outBus;
4608
4639
  }
4609
4640
  get sampleDuration() {
4610
- return this.#l;
4641
+ return this.#u;
4611
4642
  }
4612
4643
  get volume() {
4613
- return this.#h.gain.value;
4644
+ return this.#g.gain.value;
4614
4645
  }
4615
4646
  set volume(e) {
4616
- this.#h.gain.setValueAtTime(e, this.context.currentTime);
4647
+ this.#g.gain.setValueAtTime(e, this.context.currentTime);
4617
4648
  }
4618
4649
  get loopEnabled() {
4619
- return this.#u;
4650
+ return this.#d;
4620
4651
  }
4621
4652
  get holdEnabled() {
4622
- return this.#f;
4653
+ return this.#p;
4623
4654
  }
4624
4655
  get gainLFO() {
4625
- return this.#v;
4656
+ return this.#y;
4626
4657
  }
4627
4658
  get pitchLFO() {
4628
- return this.#y;
4659
+ return this.#b;
4629
4660
  }
4630
4661
  get loopStart() {
4631
- return this.#g.targetValue;
4662
+ return this.#_.targetValue;
4632
4663
  }
4633
4664
  get loopEnd() {
4634
- return this.#_.targetValue;
4665
+ return this.#v.targetValue;
4635
4666
  }
4636
4667
  get isLoaded() {
4637
4668
  return this.#r;
@@ -4639,14 +4670,17 @@ var Ct = class {
4639
4670
  get audiobuffer() {
4640
4671
  return this.#c;
4641
4672
  }
4673
+ get layers() {
4674
+ return [...this.#l];
4675
+ }
4642
4676
  dispose() {
4643
4677
  try {
4644
- this.releaseAll(), this.#F.clear(), this.voicePool &&= (this.voicePool.dispose(), null), this.outBus &&= (this.outBus.dispose(), null), this.#g?.dispose(), this.#_?.dispose(), this.#g = null, this.#_ = null, this.#v?.dispose(), this.#y?.dispose(), this.disconnect(), this.#l = 0, this.#t = !1, this.#r = !1, this.#M = [], this.#N = !1, this.#u = !1, B(this.nodeId);
4678
+ this.releaseAll(), this.#I.clear(), this.voicePool &&= (this.voicePool.dispose(), null), this.outBus &&= (this.outBus.dispose(), null), this.#_?.dispose(), this.#v?.dispose(), this.#_ = null, this.#v = null, this.#y?.dispose(), this.#b?.dispose(), this.disconnect(), this.#u = 0, this.#c = null, this.#l = [], this.#t = !1, this.#r = !1, this.#N = [], this.#P = !1, this.#d = !1, B(this.nodeId);
4645
4679
  } catch (e) {
4646
4680
  console.error(`Error disposing Sampler ${this.nodeId}:`, e);
4647
4681
  }
4648
4682
  }
4649
- }, wt = "//#region src/utils/search/findClosest.ts\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array index of the element which value is closest to the target value\n*/\nfunction findClosestIdx(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n if (sortedArray.length === 0) throw new Error(\"Array cannot be empty\");\n if (sortedArray.length === 1) return 0;\n const targetValue = target;\n const firstValue = getValue(sortedArray[0]);\n const lastValue = getValue(sortedArray[sortedArray.length - 1]);\n if (targetValue <= firstValue) return 0;\n if (targetValue >= lastValue) return sortedArray.length - 1;\n let left = 0;\n let right = sortedArray.length - 1;\n while (left < right - 1) {\n const mid = Math.floor((left + right) / 2);\n const midValue = getValue(sortedArray[mid]);\n if (midValue === targetValue) return mid;\n else if (midValue < targetValue) left = mid;\n else right = mid;\n }\n if (direction === \"left\") return left;\n if (direction === \"right\") return right;\n return getDistance(getValue(sortedArray[left]), targetValue) <= getDistance(getValue(sortedArray[right]), targetValue) ? left : right;\n}\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array element which value is closest to the target value\n*/\nfunction findClosest(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n return sortedArray[findClosestIdx(sortedArray, target, direction, getValue, getDistance)];\n}\nvar SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS = Object.values({\n masterGain: {\n name: \"masterGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n envGain: {\n name: \"envGain\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n velocity: {\n name: \"velocity\",\n defaultValue: 100,\n minValue: 0,\n maxValue: 127,\n automationRate: \"k-rate\"\n },\n pan: {\n name: \"pan\",\n defaultValue: 0,\n minValue: -1,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n playbackRate: {\n name: \"playbackRate\",\n defaultValue: 1,\n minValue: .1,\n maxValue: 24,\n automationRate: \"a-rate\"\n },\n loopStart: {\n name: \"loopStart\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopEnd: {\n name: \"loopEnd\",\n defaultValue: 99999,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n startPoint: {\n name: \"startPoint\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n endPoint: {\n name: \"endPoint\",\n defaultValue: 9999,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n playbackPosition: {\n name: \"playbackPosition\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopDurationDriftAmount: {\n name: \"loopDurationDriftAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n maxLoopCount: {\n name: \"maxLoopCount\",\n defaultValue: 999999,\n minValue: 1,\n maxValue: 999999,\n automationRate: \"k-rate\"\n },\n tempo: {\n name: \"tempo\",\n defaultValue: 120,\n minValue: 20,\n maxValue: 300,\n automationRate: \"k-rate\"\n }\n});\n//#endregion\n//#region src/worklets/processors/play/sample-player-processor.js\nvar SamplePlayerProcessor = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS;\n }\n constructor() {\n super();\n this.buffer = null;\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.usePlaybackPosition = false;\n this.enableLoopSmoothing = true;\n this.enableAdaptiveDrift = true;\n this.enableAmplitudeCompensation = true;\n this.syncLoopToTempo = false;\n this.keytrackLoopAmount = 0;\n this.durationPreservation = {\n enabled: false,\n maxDriftSamples: Math.floor(sampleRate * .04),\n timelinePosition: 0,\n resetPending: false\n };\n this.PITCH_PRESERVATION_THRESHOLD = Math.floor(sampleRate * .061);\n this.AMPLITUDE_COMPENSATION_THRESHOLD = Math.floor(sampleRate / 16.35);\n this.port.onmessage = this.#handleMessage.bind(this);\n this.#resetState();\n this.port.postMessage({ type: \"initialized\" });\n }\n #handleMessage(event) {\n const { type, value, buffer, timestamp, durationSeconds, zeroCrossings, semitones, allowedPeriods, playbackDirection } = event.data;\n switch (type) {\n case \"voice:reset\":\n this.#resetState();\n this.port.postMessage({ type: \"voice:reset\" });\n break;\n case \"voice:setBuffer\":\n this.#resetState();\n this.zeroCrossings = [];\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.buffer = null;\n this.buffer = buffer;\n this.port.postMessage({\n type: \"voice:loaded\",\n durationSeconds,\n time: currentTime\n });\n break;\n case \"transpose\":\n this.transpositionPlaybackrate = Math.pow(2, semitones / 12);\n this.port.postMessage({\n type: \"voice:transposed\",\n semitones,\n time: currentTime\n });\n break;\n case \"voice:setZeroCrossings\":\n this.zeroCrossings = (zeroCrossings || []).map((timeSec) => timeSec * sampleRate);\n if (this.zeroCrossings.length > 0) {\n this.minZeroCrossing = this.zeroCrossings[0];\n this.maxZeroCrossing = this.zeroCrossings[this.zeroCrossings.length - 1];\n }\n break;\n case \"voice:start\":\n this.isReleasing = false;\n this.isPlaying = true;\n this.loopCount = 0;\n this.playbackPosition = 0;\n this.port.postMessage({\n type: \"voice:started\",\n time: timestamp || currentTime\n });\n break;\n case \"voice:release\":\n this.isReleasing = true;\n this.port.postMessage({\n type: \"voice:releasing\",\n time: currentTime\n });\n break;\n case \"voice:stop\":\n this.#stop();\n break;\n case \"setLoopEnabled\":\n this.loopEnabled = value;\n this.port.postMessage({\n type: \"loop:enabled\",\n enabled: value\n });\n break;\n case \"setPanDriftEnabled\":\n this.panDriftEnabled = value;\n break;\n case \"voice:setPlaybackDirection\": {\n const reverse = playbackDirection === \"reverse\";\n if (reverse !== this.reversePlayback && this.playbackPosition > 0) this.playbackPosition += reverse ? 1 : -1;\n this.reversePlayback = reverse;\n this.port.postMessage({\n type: \"voice:playbackDirectionChange\",\n playbackDirection\n });\n break;\n }\n case \"voice:usePlaybackPosition\":\n this.usePlaybackPosition = value;\n break;\n case \"syncLoopToTempo\":\n this.syncLoopToTempo = value;\n this.port.postMessage({\n type: \"loop:syncToTempo\",\n enabled: value\n });\n break;\n case \"setKeytrackLoopAmount\":\n this.keytrackLoopAmount = Math.max(0, Math.min(1, value));\n break;\n case \"setPreserveDuration\":\n this.durationPreservation.enabled = Boolean(value);\n this.#resetDurationPreservation(this.playbackPosition);\n }\n }\n #resetState() {\n this.isPlaying = false;\n this.isReleasing = false;\n this.loopEnabled = false;\n this.transpositionPlaybackrate = 1;\n this.velocitySensitivity = 1;\n this.reversePlayback = false;\n this.playbackPosition = 0;\n this.debugCounter = 0;\n this.loopCount = 0;\n this.applyClickCompensation = false;\n this.loopClickCompensation = 0;\n this.driftUpdateCounter = 0;\n this.currentLoopDrift = 0;\n this.currentPanDrift = 0;\n this.panDriftEnabled = true;\n this.nextDriftGenerated = false;\n this.loopAmplitudeGain = 1;\n this.lastAnalyzedLoopStart = -1;\n this.lastAnalyzedLoopEnd = -1;\n this.#resetDurationPreservation();\n }\n #stop() {\n this.isPlaying = false;\n this.isReleasing = false;\n this.playbackPosition = 0;\n this.port.postMessage({ type: \"voice:stopped\" });\n }\n #smoothLoopWrap(lastLoopSample, newFirstSample) {\n const discontinuity = lastLoopSample - newFirstSample;\n if (this.enableLoopSmoothing && Math.abs(discontinuity) > .01) {\n this.loopClickCompensation = discontinuity * .5;\n this.compensationDecay = .9;\n this.applyClickCompensation = true;\n }\n }\n #clamp = (value, min, max) => Math.max(min, Math.min(max, value));\n #clampZeroCrossing = (value) => this.#clamp(value, this.minZeroCrossing, this.maxZeroCrossing);\n #findNearestZeroCrossing(position, direction = \"any\", maxDistance = null) {\n if (!this.zeroCrossings || this.zeroCrossings.length === 0) return position;\n const closestValue = findClosest(this.zeroCrossings, position, direction);\n if (maxDistance !== null && Math.abs(closestValue - position) > maxDistance) return position;\n return closestValue;\n }\n /**\n * Convert normalized position (0-1) to sample index\n * @param {number} normalizedPosition - Position as 0-1 value\n * @returns {number} - Sample index\n */\n #normalizedToSamples(normalizedPosition) {\n if (!this.buffer || !this.buffer[0]) return 0;\n return normalizedPosition * this.buffer[0].length;\n }\n /**\n * Convert sample index to normalized position (0-1)\n * @param {number} sampleIndex - Sample index\n * @returns {number} - Normalized position 0-1\n */\n #samplesToNormalized(sampleIndex) {\n if (!this.buffer || !this.buffer[0]) return 0;\n return sampleIndex / this.buffer[0].length;\n }\n /**\n * Convert MIDI velocity (0-127) to gain multiplier (0-1)\n * @param {number} midiVelocity - MIDI velocity 0-127\n * @returns {number} - Gain multiplier 0-1\n */\n #midiVelocityToGain(midiVelocity) {\n return Math.max(0, Math.min(1, midiVelocity / 127));\n }\n /**\n * Get buffer duration in seconds\n * @returns {number} - Buffer duration in seconds\n */\n #getBufferDurationSeconds() {\n return (this.buffer?.[0]?.length || 0) / sampleRate;\n }\n /**\n * Calculate musical note durations in samples for given tempo\n * @param {number} tempo - BPM\n * @returns {Object} - Musical note durations in samples\n */\n #getMusicalNoteDurations(tempo) {\n const beatsPerSecond = tempo / 60;\n const samplesPerBeat = sampleRate / beatsPerSecond;\n return {\n whole: samplesPerBeat * 4,\n half: samplesPerBeat * 2,\n quarter: samplesPerBeat,\n eighth: samplesPerBeat / 2,\n sixteenth: samplesPerBeat / 4,\n thirtySecond: samplesPerBeat / 8,\n quarterTriplet: samplesPerBeat * 2 / 3,\n eighthTriplet: samplesPerBeat / 2 * 2 / 3,\n sixteenthTriplet: samplesPerBeat / 4 * 2 / 3\n };\n }\n /**\n * Quantize loop duration to nearest musical interval (skips if below the smallest quantize option)\n * @param {number} loopDurationSamples - Current loop duration in samples\n * @param {number} tempo - Current tempo in BPM\n * @param {number} playbackRate - Current playback rate\n * @returns {number} - Quantized loop duration in samples\n */\n #quantizeLoopDuration(loopDurationSamples, tempo, playbackRate) {\n if (!this.syncLoopToTempo) return loopDurationSamples;\n const noteDurations = this.#getMusicalNoteDurations(tempo);\n const effectiveDuration = loopDurationSamples / Math.abs(playbackRate);\n if (effectiveDuration < noteDurations.thirtySecond) return loopDurationSamples;\n const intervals = Object.values(noteDurations);\n let closestInterval = intervals[0];\n let smallestDiff = Math.abs(effectiveDuration - closestInterval);\n for (const interval of intervals) {\n const diff = Math.abs(effectiveDuration - interval);\n if (diff < smallestDiff) {\n smallestDiff = diff;\n closestInterval = interval;\n }\n }\n return Math.floor(closestInterval * Math.abs(playbackRate));\n }\n /**\n * Extract and convert all position parameters from seconds to samples\n * @param {Object} parameters - AudioWorkletProcessor parameters\n * @returns {Object} - Converted parameters in samples\n */\n #extractPositionParams(parameters) {\n return {\n startPointSamples: Math.floor(parameters.startPoint[0] * sampleRate),\n endPointSamples: Math.floor(parameters.endPoint[0] * sampleRate),\n loopStartSamples: Math.floor(parameters.loopStart[0] * sampleRate),\n loopEndSamples: Math.floor(parameters.loopEnd[0] * sampleRate)\n };\n }\n /**\n * Calculate effective playback range in samples\n * @param {Object} params - Position parameters from #extractPositionParams\n * @returns {Object} - Effective start and end positions\n */\n #calculatePlaybackRange(params) {\n const bufferLength = this.buffer?.[0]?.length || 0;\n const start = Math.max(0, params.startPointSamples);\n const end = params.endPointSamples > start ? Math.min(bufferLength, params.endPointSamples) : bufferLength;\n const snappedStart = this.#findNearestZeroCrossing(start, \"right\");\n const snappedEnd = this.#findNearestZeroCrossing(end, \"left\");\n return {\n startSamples: snappedStart,\n endSamples: snappedEnd,\n durationSamples: snappedEnd - snappedStart\n };\n }\n /**\n * Calculate effective loop range in samples with optional drift\n * @param {Object} params - Position parameters from #extractPositionParams\n * @param {Object} playbackRange - Range from #calculatePlaybackRange\n * @param {number} driftAmount - Loop duration drift amount (0-1)\n * @param {number} tempo - Current tempo in BPM\n * @param {number} playbackRate - Current playback rate\n * @returns {Object} - Effective loop start and end positions with drift applied\n */\n #calculateLoopRange(params, playbackRange, driftAmount = 0, tempo = 120, playbackRate = 1) {\n const lpStart = params.loopStartSamples;\n const lpEnd = params.loopEndSamples;\n let calcLoopStart = lpStart < lpEnd && lpStart >= 0 ? lpStart : playbackRange.startSamples;\n let calcLoopEnd = lpEnd > lpStart && lpEnd <= playbackRange.endSamples ? lpEnd : playbackRange.endSamples;\n let baseDuration = calcLoopEnd - calcLoopStart;\n if (this.syncLoopToTempo) {\n const quantizedDuration = this.#quantizeLoopDuration(baseDuration, tempo, playbackRate);\n calcLoopEnd = calcLoopStart + quantizedDuration;\n calcLoopEnd = Math.min(calcLoopEnd, playbackRange.endSamples);\n }\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD && this.keytrackLoopAmount > 0 && !this.syncLoopToTempo) {\n const scale = 1 + this.keytrackLoopAmount * (Math.abs(playbackRate) - 1);\n baseDuration = Math.max(1, Math.floor(baseDuration * scale));\n calcLoopEnd = calcLoopStart + baseDuration;\n }\n baseDuration = calcLoopEnd - calcLoopStart;\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD) calcLoopStart = this.#findNearestZeroCrossing(calcLoopStart, \"right\");\n if (driftAmount > 0 && this.loopEnabled) {\n if (!this.nextDriftGenerated || this.loopCount === 0) {\n const updateInterval = baseDuration <= this.PITCH_PRESERVATION_THRESHOLD ? Math.max(1, Math.floor(this.PITCH_PRESERVATION_THRESHOLD / baseDuration)) : 1;\n if (this.driftUpdateCounter % updateInterval === 0) {\n this.currentLoopDrift = this.#generateLoopDrift(driftAmount, baseDuration);\n if (this.panDriftEnabled && driftAmount > 0 && this.loopCount > 0) {\n const panDriftAmountScalar = 1e-4;\n this.currentPanDrift = this.currentLoopDrift * panDriftAmountScalar;\n } else this.currentPanDrift = 0;\n }\n this.driftUpdateCounter++;\n this.nextDriftGenerated = true;\n }\n const driftedLoopEnd = calcLoopEnd + this.currentLoopDrift;\n const minLoopDuration = Math.max(1, Math.floor(baseDuration * .1));\n const maxLoopEnd = Math.max(playbackRange.endSamples, calcLoopEnd);\n calcLoopEnd = Math.max(calcLoopStart + minLoopDuration, Math.min(maxLoopEnd, driftedLoopEnd));\n } else this.currentPanDrift = 0;\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD && calcLoopEnd <= playbackRange.endSamples) calcLoopEnd = Math.max(calcLoopStart + 1, this.#findNearestZeroCrossing(calcLoopEnd, \"left\"));\n const loopDuration = calcLoopEnd - calcLoopStart;\n return {\n loopStartSamples: calcLoopStart,\n loopEndSamples: calcLoopEnd,\n loopDurationSamples: loopDuration\n };\n }\n #getSafeParam(paramArray, index, isConstant) {\n return isConstant ? paramArray[0] : paramArray[Math.min(index, paramArray.length - 1)];\n }\n #getConstantFlags(parameters) {\n this.constantFlags ??= {\n envGain: true,\n playbackRate: true\n };\n this.constantFlags.envGain = parameters.envGain.length === 1;\n this.constantFlags.playbackRate = parameters.playbackRate.length === 1;\n return this.constantFlags;\n }\n #resetDurationPreservation(position = 0) {\n this.durationPreservation.timelinePosition = position;\n this.durationPreservation.resetPending = false;\n }\n #isDurationPreservationActive(loopRange) {\n return this.durationPreservation.enabled && Boolean(this.zeroCrossings?.length) && (!this.loopEnabled || loopRange.loopDurationSamples > this.PITCH_PRESERVATION_THRESHOLD);\n }\n #prepareDurationPreservingSample(playbackRate, loopRange) {\n const state = this.durationPreservation;\n if (!this.#isDurationPreservationActive(loopRange)) return null;\n if (Math.abs(this.playbackPosition - state.timelinePosition) > state.maxDriftSamples) state.resetPending = true;\n if (!state.resetPending) return null;\n const direction = playbackRate < 0 ? \"left\" : \"right\";\n const outgoingZero = this.#findNearestZeroCrossing(this.playbackPosition, direction);\n if (Math.abs(outgoingZero - this.playbackPosition) > Math.abs(playbackRate)) return null;\n this.playbackPosition = outgoingZero;\n state.resetPending = false;\n return this.#findNearestZeroCrossing(state.timelinePosition, \"any\", state.maxDriftSamples);\n }\n #advanceDurationPreservingPlayback(playbackRate, resetTarget, loopRange, canWrapLoop) {\n const state = this.durationPreservation;\n this.playbackPosition = resetTarget === null ? this.playbackPosition + playbackRate : resetTarget;\n if (this.#isDurationPreservationActive(loopRange)) {\n state.timelinePosition += playbackRate < 0 ? -1 : 1;\n if (canWrapLoop && playbackRate >= 0 && state.timelinePosition >= loopRange.loopEndSamples) state.timelinePosition = loopRange.loopStartSamples;\n else if (canWrapLoop && playbackRate < 0 && state.timelinePosition <= loopRange.loopStartSamples) state.timelinePosition = loopRange.loopEndSamples - 1;\n } else this.#resetDurationPreservation(this.playbackPosition);\n }\n /**\n * Generate a new drift amount for the current loop iteration\n * @param {number} driftAmount - Maximum drift amount (0-1)\n * @param {number} baseDuration - Base loop duration in samples\n * @returns {number} - Drift amount in samples\n */\n #generateLoopDrift(driftAmount, baseDuration) {\n if (driftAmount <= 0) return 0;\n const randomFactor = (Math.random() - .5) * 2;\n let effectiveDriftAmount = driftAmount;\n if (this.enableAdaptiveDrift) {\n const shortThreshold = 1024;\n const longThreshold = 8192;\n if (baseDuration < shortThreshold) effectiveDriftAmount *= .1;\n else if (baseDuration < longThreshold) {\n const scaleFactor = .1 + .9 * (baseDuration - shortThreshold) / 7168;\n effectiveDriftAmount *= scaleFactor;\n }\n }\n const maxDriftSamples = effectiveDriftAmount * baseDuration;\n return Math.floor(randomFactor * maxDriftSamples);\n }\n /**\n * Analyze loop amplitude and calculate makeup gain for short loops\n * @param {number} loopStart - Loop start position in samples\n * @param {number} loopEnd - Loop end position in samples\n * @returns {number} - Makeup gain multiplier (1.0 = no change)\n */\n #analyzeLoopAmplitude(loopStart, loopEnd) {\n if (!this.enableAmplitudeCompensation || !this.buffer || !this.buffer[0]) return 1;\n if (loopEnd - loopStart >= this.AMPLITUDE_COMPENSATION_THRESHOLD) return 1;\n if (loopStart === this.lastAnalyzedLoopStart && loopEnd === this.lastAnalyzedLoopEnd) return this.loopAmplitudeGain;\n let sumSquares = 0;\n let sampleCount = 0;\n const channel = this.buffer[0];\n const startIndex = Math.floor(loopStart);\n const endIndex = Math.floor(loopEnd);\n for (let i = startIndex; i < endIndex && i < channel.length; i++) {\n const sample = channel[i];\n sumSquares += sample * sample;\n sampleCount++;\n }\n if (sampleCount === 0) return 1;\n const rmsAmplitude = Math.sqrt(sumSquares / sampleCount);\n const targetAmplitude = .3;\n let makeupGain = 1;\n if (rmsAmplitude < targetAmplitude) {\n makeupGain = targetAmplitude / Math.max(rmsAmplitude, .001);\n makeupGain = Math.min(2, makeupGain);\n }\n this.lastAnalyzedLoopStart = loopStart;\n this.lastAnalyzedLoopEnd = loopEnd;\n this.loopAmplitudeGain = makeupGain;\n return makeupGain;\n }\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n this.debugCounter++;\n if (!output || !this.isPlaying || !this.buffer?.[0]?.length) return true;\n const masterGain = parameters.masterGain[0];\n const positionParams = this.#extractPositionParams(parameters);\n const playbackRange = this.#calculatePlaybackRange(positionParams);\n const effectivePlaybackRate = parameters.playbackRate[0] * this.transpositionPlaybackrate;\n const tempo = parameters.tempo[0];\n const loopRange = this.#calculateLoopRange(positionParams, playbackRange, parameters.loopDurationDriftAmount[0], tempo, effectivePlaybackRate);\n const amplitudeGain = this.#analyzeLoopAmplitude(loopRange.loopStartSamples, loopRange.loopEndSamples);\n const velocityGain = this.#midiVelocityToGain(parameters.velocity[0]) * this.velocitySensitivity;\n const basePan = parameters.pan[0];\n const effectivePan = this.panDriftEnabled ? Math.max(-1, Math.min(1, basePan + this.currentPanDrift)) : basePan;\n let outputChannels;\n if (output instanceof Float32Array) outputChannels = [output];\n else if (Array.isArray(output) && output.every((ch) => ch instanceof Float32Array)) outputChannels = output;\n else {\n console.error(\"Unexpected output structure:\", {\n outputType: typeof output,\n isArray: Array.isArray(output),\n constructor: output?.constructor?.name,\n length: output?.length\n });\n return true;\n }\n const numChannels = outputChannels.length;\n const isConstant = this.#getConstantFlags(parameters);\n const silencePadTail = loopRange.loopEndSamples > playbackRange.endSamples;\n const TAIL_FADE_SAMPLES = 64;\n if (this.playbackPosition === 0) {\n this.playbackPosition = this.reversePlayback ? playbackRange.endSamples - 1 : playbackRange.startSamples;\n this.#resetDurationPreservation(this.playbackPosition);\n }\n for (let sample = 0; sample < outputChannels[0].length; sample++) {\n const envelopeGain = this.#getSafeParam(parameters.envGain, sample, isConstant.envGain);\n const baseRate = this.#getSafeParam(parameters.playbackRate, sample, isConstant.playbackRate);\n const playbackStep = (this.reversePlayback ? -Math.abs(baseRate) : Math.abs(baseRate)) * this.transpositionPlaybackrate;\n const canWrapLoop = this.loopEnabled && this.loopCount < parameters.maxLoopCount[0];\n if (canWrapLoop) {\n if (!this.reversePlayback && this.playbackPosition >= loopRange.loopEndSamples) {\n this.#smoothLoopWrap(silencePadTail ? 0 : this.buffer[0][Math.floor(this.playbackPosition - 1)] || 0, this.buffer[0][Math.floor(loopRange.loopStartSamples)] || 0);\n this.playbackPosition = loopRange.loopStartSamples;\n this.loopCount++;\n this.nextDriftGenerated = false;\n } else if (this.reversePlayback && this.playbackPosition <= loopRange.loopStartSamples) {\n this.#smoothLoopWrap(this.buffer[0][Math.floor(loopRange.loopStartSamples)] || 0, silencePadTail ? 0 : this.buffer[0][Math.floor(loopRange.loopEndSamples) - 1] || 0);\n this.playbackPosition = loopRange.loopEndSamples;\n this.loopCount++;\n this.nextDriftGenerated = false;\n }\n }\n const durationResetTarget = this.#prepareDurationPreservingSample(playbackStep, loopRange);\n const shouldStopForward = !this.reversePlayback && (this.#isDurationPreservationActive(loopRange) ? this.durationPreservation.timelinePosition : this.playbackPosition) >= playbackRange.endSamples;\n const shouldStopReverse = this.reversePlayback && (this.#isDurationPreservationActive(loopRange) ? this.durationPreservation.timelinePosition : this.playbackPosition) <= playbackRange.startSamples;\n const isWithinLoop = this.loopEnabled && this.playbackPosition >= loopRange.loopStartSamples && this.playbackPosition <= loopRange.loopEndSamples;\n if ((shouldStopForward || shouldStopReverse) && !(this.loopEnabled && isWithinLoop)) {\n this.#stop();\n return true;\n }\n let tailGain = 1;\n if (silencePadTail) {\n const distToEnd = playbackRange.endSamples - this.playbackPosition;\n if (distToEnd < TAIL_FADE_SAMPLES) tailGain = Math.max(0, distToEnd / TAIL_FADE_SAMPLES);\n }\n const currentPosition = Math.floor(this.playbackPosition);\n const positionOffset = this.playbackPosition - currentPosition;\n let nextPosition, interpWeight;\n if (this.reversePlayback) {\n nextPosition = Math.max(currentPosition - 1, playbackRange.startSamples);\n interpWeight = 1 - positionOffset;\n } else {\n nextPosition = Math.min(currentPosition + 1, playbackRange.endSamples - 1);\n interpWeight = positionOffset;\n }\n for (let channel = 0; channel < numChannels; channel++) {\n if (!outputChannels[channel]) {\n console.warn(`Output channel ${channel} does not exist. Available channels:`, outputChannels.length);\n continue;\n }\n const bufferChannelIndex = Math.min(channel, this.buffer.length - 1);\n const bufferChannel = this.buffer[bufferChannelIndex];\n const currentSample = bufferChannel[currentPosition] || 0;\n const nextSample = bufferChannel[nextPosition] || 0;\n let interpolatedSample = currentSample + interpWeight * (nextSample - currentSample);\n if (this.applyClickCompensation) {\n interpolatedSample += this.loopClickCompensation;\n if (this.compensationDecay) {\n this.loopClickCompensation *= this.compensationDecay;\n if (Math.abs(this.loopClickCompensation) < .001) this.applyClickCompensation = false;\n } else this.applyClickCompensation = false;\n }\n const finalSample = interpolatedSample * velocityGain * envelopeGain * masterGain * amplitudeGain * tailGain;\n let panAdjustedSample = finalSample;\n if (outputChannels.length === 2) {\n if (channel === 0) panAdjustedSample = finalSample * (1 - Math.max(0, effectivePan));\n else if (channel === 1) panAdjustedSample = finalSample * (1 - Math.max(0, -effectivePan));\n }\n outputChannels[channel][sample] = Math.max(-1, Math.min(1, isFinite(panAdjustedSample) ? panAdjustedSample : 0));\n }\n this.#advanceDurationPreservingPlayback(playbackStep, durationResetTarget, loopRange, canWrapLoop);\n }\n if (this.usePlaybackPosition) {\n const normalizedPosition = this.#samplesToNormalized(this.playbackPosition);\n this.port.postMessage({\n type: \"voice:position\",\n position: normalizedPosition\n });\n }\n return true;\n }\n};\nregisterProcessor(\"sample-player-processor\", SamplePlayerProcessor);\n//#endregion\n//#region src/worklets/processors/noise/random-noise-processor.js\nvar RandomNoiseProcessor = class extends AudioWorkletProcessor {\n constructor() {\n super();\n this.previousNoise = 0;\n this.previousFiltered = 0;\n this.hpfHz = 150;\n this.alpha = this.hpfHz / (this.hpfHz + sampleRate / (2 * Math.PI));\n this.port.onmessage = (event) => {\n if (event.data.type === \"setHpfHz\") {\n this.hpfHz = event.data.value;\n this.alpha = this.calculateAlpha(this.hpfHz);\n }\n };\n this.port.postMessage({ type: \"initialized\" });\n }\n calculateAlpha(frequency) {\n return frequency / (frequency + sampleRate / (2 * Math.PI));\n }\n process(inputs, outputs, parameters) {\n outputs[0].forEach((channel) => {\n for (let i = 0; i < channel.length; i++) {\n const noise = Math.random() * 2 - 1;\n const filtered = this.alpha * (noise - this.previousNoise) + this.previousFiltered;\n this.previousNoise = noise;\n this.previousFiltered = filtered;\n channel[i] = filtered;\n }\n });\n return true;\n }\n};\nregisterProcessor(\"random-noise-processor\", RandomNoiseProcessor);\n//#endregion\n//#region src/worklets/shared/utils/compress-utils.ts\nvar cheapSoftClipSingleSample = (sample, max = .9) => {\n const a = Math.abs(sample);\n if (a <= max) return sample;\n const x = a / max;\n const compressed = x / (1 + x);\n return Math.sign(sample) * max * compressed;\n};\n/**\n* Basic attenuation compressor for single sample\n* Note: No validation since optimized for real time use\n*/\nvar compressSingleSample = (input, threshold = .75, ratio = 4, limiter = {\n enabled: true,\n type: \"soft\",\n outputRange: {\n min: -1,\n max: 1\n }\n}) => {\n const { min, max } = limiter.outputRange;\n let x = input;\n if (Math.abs(x) > threshold) x = Math.sign(x) * (threshold + (Math.abs(x) - threshold) / ratio);\n if (limiter.enabled) {\n if (limiter.type === \"soft\") x = cheapSoftClipSingleSample(x, Math.abs(max));\n else if (limiter.type === \"hard\") x = Math.max(min, Math.min(max, x));\n }\n return x;\n};\n//#endregion\n//#region src/worklets/processors/delay/DelayBuffer.js\nvar DelayBuffer = class {\n constructor(maxDelaySamples) {\n this.buffer = new Float32Array(maxDelaySamples);\n this.writePtr = 0;\n this.readPtr = 0;\n }\n write(sample) {\n this.buffer[this.writePtr] = sample;\n }\n read() {\n return this.buffer[this.readPtr];\n }\n updatePointers(delaySamples) {\n this.writePtr = (this.writePtr + 1) % this.buffer.length;\n this.readPtr = (this.writePtr - delaySamples + this.buffer.length) % this.buffer.length;\n }\n};\n//#endregion\n//#region src/worklets/processors/delay/FeedbackDelay.js\nvar AUTO_GAIN_THRESHOLD = .8;\nvar SAFETY_GAIN_COMPENSATION = .2;\nvar FeedbackDelay = class {\n constructor(sampleRate) {\n this.sampleRate = sampleRate;\n this.buffers = [];\n this.initialized = false;\n this.autoGainEnabled = false;\n this.gainCompensation = SAFETY_GAIN_COMPENSATION;\n this.lowpassStates = [];\n this.highpassStates = [];\n this.highpassInputStates = [];\n }\n initializeBuffers(channelCount) {\n this.buffers = [];\n this.lowpassStates = [];\n this.highpassStates = [];\n this.highpassInputStates = [];\n const maxSamples = Math.floor(this.sampleRate * 2);\n for (let c = 0; c < channelCount; c++) {\n this.buffers[c] = new DelayBuffer(maxSamples);\n this.lowpassStates[c] = 0;\n this.highpassStates[c] = 0;\n this.highpassInputStates[c] = 0;\n }\n this.initialized = true;\n }\n /** Simple one-pole lowpass filter */\n lowpass(input, cutoffFreq, channelIndex) {\n if (cutoffFreq >= this.sampleRate * .4) return input;\n const omega = 2 * Math.PI * cutoffFreq / this.sampleRate;\n const alpha = Math.max(0, Math.min(.99, Math.sin(omega) / (Math.sin(omega) + Math.cos(omega))));\n this.lowpassStates[channelIndex] = alpha * input + (1 - alpha) * this.lowpassStates[channelIndex];\n return this.lowpassStates[channelIndex];\n }\n /** Simple one-pole highpass filter */\n highpass(input, cutoffFreq, channelIndex) {\n if (cutoffFreq < 5) return input;\n const omega = 2 * Math.PI * cutoffFreq / this.sampleRate;\n const alpha = Math.max(0, Math.min(.99, Math.sin(omega) / (Math.sin(omega) + Math.cos(omega))));\n const lowpassOutput = alpha * input + (1 - alpha) * this.highpassStates[channelIndex];\n const highpassOutput = input - lowpassOutput;\n this.highpassStates[channelIndex] = lowpassOutput;\n return highpassOutput;\n }\n process(inputSample, channelIndex, feedbackAmount, delayTime, lowpassFreq = 1e4, highpassFreq = 100) {\n if (!this.initialized) return inputSample;\n const buffer = this.buffers[channelIndex] || this.buffers[0];\n const delaySamples = Math.floor(this.sampleRate * delayTime);\n const delayedSample = buffer.read();\n let filteredDelay = this.highpass(delayedSample, highpassFreq, channelIndex);\n filteredDelay = this.lowpass(filteredDelay, lowpassFreq, channelIndex);\n const feedbackSample = feedbackAmount * filteredDelay + inputSample;\n let outputSample = feedbackSample;\n const compressedFeedback = compressSingleSample(feedbackSample, .5, 4, {\n enabled: true,\n outputRange: {\n min: -.99,\n max: .99\n },\n type: \"soft\"\n });\n if (this.autoGainEnabled && feedbackAmount > AUTO_GAIN_THRESHOLD) outputSample = compressedFeedback * (1 - (feedbackAmount - AUTO_GAIN_THRESHOLD) * this.gainCompensation);\n return {\n outputSample,\n feedbackSample: compressedFeedback,\n delaySamples\n };\n }\n updateBuffer(channelIndex, sample, delaySamples) {\n const buffer = this.buffers[channelIndex] || this.buffers[0];\n buffer.write(sample);\n buffer.updatePointers(delaySamples);\n }\n setAutoGain(enabled, compensation = SAFETY_GAIN_COMPENSATION) {\n this.autoGainEnabled = enabled;\n this.gainCompensation = compensation;\n }\n};\n//#endregion\n//#region src/worklets/processors/delay/feedback-delay-processor.js\nregisterProcessor(\"feedback-delay-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"feedbackAmount\",\n defaultValue: .5,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"delayTime\",\n defaultValue: .5,\n minValue: .00012656238799684143,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n {\n name: \"decay\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"lowpass\",\n defaultValue: 1e4,\n minValue: 100,\n maxValue: 16e3,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.feedbackDelay = new FeedbackDelay(sampleRate);\n this.decayStartTime = null;\n this.decayActive = false;\n this.baseFeedbackAmount = .5;\n this.setupMessageHandling();\n this.port.postMessage({ type: \"initialized\" });\n }\n setupMessageHandling() {\n this.port.onmessage = (event) => {\n switch (event.data.type) {\n case \"setAutoGain\":\n this.feedbackDelay.setAutoGain(event.data.enabled, event.data.amount);\n break;\n case \"triggerDecay\":\n this.decayStartTime = currentTime;\n this.decayActive = true;\n this.baseFeedbackAmount = event.data.baseFeedbackAmount || .5;\n break;\n case \"stopDecay\":\n this.decayActive = false;\n this.decayStartTime = null;\n }\n };\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output) return true;\n if (!this.feedbackDelay.initialized || this.feedbackDelay.buffers.length !== input.length) this.feedbackDelay.initializeBuffers(input.length);\n const baseFeedbackAmount = parameters.feedbackAmount[0];\n const delayTime = parameters.delayTime[0];\n const decay = parameters.decay[0];\n const lowpassFreq = parameters.lowpass[0];\n const channelCount = Math.min(input.length, output.length);\n const frameCount = output[0].length;\n for (let i = 0; i < frameCount; ++i) {\n let effectiveFeedbackAmount = baseFeedbackAmount;\n if (this.decayActive && this.decayStartTime !== null) {\n const elapsedTime = currentTime - this.decayStartTime + i / sampleRate;\n const delayCompensation = Math.min(100, .5 / delayTime);\n const timeConstant = Math.pow(decay, 5) * 1e3 * delayCompensation + .5;\n effectiveFeedbackAmount = baseFeedbackAmount * Math.exp(-elapsedTime / timeConstant);\n if (effectiveFeedbackAmount < .01) {\n this.decayActive = false;\n effectiveFeedbackAmount = 0;\n }\n }\n for (let c = 0; c < channelCount; c++) {\n const processed = this.feedbackDelay.process(input[c][i], c, effectiveFeedbackAmount, delayTime, lowpassFreq);\n output[c][i] = processed.outputSample;\n this.feedbackDelay.updateBuffer(c, processed.feedbackSample, processed.delaySamples);\n }\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/delay/delay-processor.js\nvar DEFAULT_DELAY_CONFIG = {\n CHARACTER: [\"filtered\"],\n SMOOTHING_FACTOR: {\n slowest: 1e-4,\n slow: 25e-5,\n medium: 35e-5,\n fast: 5e-4,\n veryFast: .001,\n superFast: .1,\n none: 1\n }\n};\nvar DEFAULT_CHARACTER_CONFIG = {\n bitCrushed: {\n bits: 11,\n downsample: 3\n },\n filtered: {\n freq: 900,\n Q: .15\n }\n};\nregisterProcessor(\"delay-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [{\n name: \"delayTime\",\n defaultValue: .5,\n minValue: .001,\n maxValue: 2,\n automationRate: \"k-rate\"\n }, {\n name: \"feedbackAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: .99,\n automationRate: \"k-rate\"\n }];\n }\n constructor() {\n super();\n this.buffers = [];\n this.smoothedDelaySamples = [];\n this.smoothingFactor = DEFAULT_DELAY_CONFIG.SMOOTHING_FACTOR.slowest;\n this.characterModes = [...DEFAULT_DELAY_CONFIG.CHARACTER];\n this._bpState = [];\n this._bpFreq = DEFAULT_CHARACTER_CONFIG.filtered.freq;\n this._bpQ = DEFAULT_CHARACTER_CONFIG.filtered.Q;\n this._bpCoeffs = null;\n this._lastBpFreq = -1;\n this._lastBpQ = -1;\n this.lofiBits = DEFAULT_CHARACTER_CONFIG[\"bitCrushed\"].bits;\n this.lofiDownsample = DEFAULT_CHARACTER_CONFIG[\"bitCrushed\"].downsample;\n this._lofiSampleHold = [];\n this._lofiSampleCount = [];\n this.initialized = false;\n this.port.onmessage = (event) => {\n if (event.data && event.data.type === \"setCharacter\" && Array.isArray(event.data.modes)) this.characterModes = [...event.data.modes];\n if (event.data && event.data.type === \"setBandpassFreq\" && typeof event.data.hz === \"number\") this.setBandpassFreq(event.data.hz);\n if (event.data && event.data.type === \"trigger\") {}\n };\n this.port.postMessage({ type: \"initialized\" });\n }\n setBandpassFreq(hz) {\n this._bpFreq = hz;\n this._lastBpFreq = -1;\n }\n _updateBandpassCoeffs() {\n if (this._lastBpFreq === this._bpFreq && this._lastBpQ === this._bpQ) return;\n const bpFreq = this._bpFreq;\n const bpQ = this._bpQ;\n const omega = 2 * Math.PI * bpFreq / sampleRate;\n const alpha = Math.sin(omega) / (2 * bpQ);\n const cosw = Math.cos(omega);\n const b0 = alpha;\n const b1 = 0;\n const b2 = -alpha;\n const a0 = 1 + alpha;\n const a1 = -2 * cosw;\n const a2 = 1 - alpha;\n this._bpCoeffs = {\n b0: b0 / a0,\n b1: b1 / a0,\n b2: b2 / a0,\n a1: a1 / a0,\n a2: a2 / a0\n };\n this._lastBpFreq = bpFreq;\n this._lastBpQ = bpQ;\n }\n initializeBuffers(channelCount) {\n const maxSamples = Math.floor(sampleRate * 2);\n this.buffers = [];\n this.smoothedDelaySamples = [];\n this._lofiSampleHold = [];\n this._lofiSampleCount = [];\n for (let c = 0; c < channelCount; c++) {\n this.buffers[c] = new DelayBuffer(maxSamples);\n this.smoothedDelaySamples[c] = Math.floor(sampleRate * .5);\n this._lofiSampleHold[c] = 0;\n this._lofiSampleCount[c] = 0;\n }\n this.initialized = true;\n }\n _processLoFi(delayed, c) {\n if (this._lofiSampleCount[c] % this.lofiDownsample === 0) {\n const levels = Math.pow(2, this.lofiBits);\n delayed = Math.round(delayed * levels) / levels;\n this._lofiSampleHold[c] = delayed;\n } else delayed = this._lofiSampleHold[c];\n this._lofiSampleCount[c]++;\n return delayed;\n }\n _processBandpass(delayed, c) {\n if (!this._bpState) this._bpState = [];\n if (!this._bpState[c]) this._bpState[c] = {\n x1: 0,\n x2: 0,\n y1: 0,\n y2: 0\n };\n this._updateBandpassCoeffs();\n if (!this._bpCoeffs) return delayed;\n const { b0, b1, b2, a1, a2 } = this._bpCoeffs;\n const s = this._bpState[c];\n const y = b0 * delayed + b1 * s.x1 + b2 * s.x2 - a1 * s.y1 - a2 * s.y2;\n s.x2 = s.x1;\n s.x1 = delayed;\n s.y2 = s.y1;\n s.y1 = y;\n return y;\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output || input.length === 0 || output.length === 0) return true;\n if (!input[0] || !output[0] || input[0].length === 0 || output[0].length === 0) return true;\n if (!this.initialized || this.buffers.length !== input.length) this.initializeBuffers(input.length);\n const delayTime = parameters.delayTime[0];\n const feedbackAmount = parameters.feedbackAmount[0];\n const targetDelaySamples = sampleRate * delayTime;\n const channelCount = Math.min(input.length, output.length);\n const frameCount = output[0].length;\n const smoothing = this.smoothingFactor;\n for (let i = 0; i < frameCount; ++i) for (let c = 0; c < channelCount; c++) {\n const buf = this.buffers[c];\n if (!buf) continue;\n this.smoothedDelaySamples[c] += (targetDelaySamples - this.smoothedDelaySamples[c]) * smoothing;\n const smoothedDelay = this.smoothedDelaySamples[c];\n const intDelay = Math.floor(smoothedDelay);\n const frac = smoothedDelay - intDelay;\n const readPtrA = (buf.writePtr - intDelay + buf.buffer.length) % buf.buffer.length;\n const readPtrB = (readPtrA - 1 + buf.buffer.length) % buf.buffer.length;\n const sampleA = buf.buffer[readPtrA];\n const sampleB = buf.buffer[readPtrB];\n let delayed = sampleA * (1 - frac) + sampleB * frac;\n for (const mode of this.characterModes) if (mode === \"bitCrushed\") delayed = this._processLoFi(delayed, c);\n else if (mode === \"filtered\") delayed = this._processBandpass(delayed, c);\n output[c][i] = compressSingleSample(delayed, .75, 4, {\n enabled: true,\n type: \"soft\",\n outputRange: {\n min: -.9,\n max: .9\n }\n });\n const inputSample = input[c] && input[c][i] !== void 0 ? input[c][i] : 0;\n buf.write(inputSample + delayed * feedbackAmount);\n buf.updatePointers(intDelay);\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/reverb/dattorro-reverb-processor.js\nvar DattorroReverb = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n [\n \"preDelay\",\n 0,\n 0,\n sampleRate - 1,\n \"k-rate\"\n ],\n [\n \"bandwidth\",\n .9999,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"inputDiffusion1\",\n .75,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"inputDiffusion2\",\n .625,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"decay\",\n .5,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"decayDiffusion1\",\n .7,\n 0,\n .999999,\n \"k-rate\"\n ],\n [\n \"decayDiffusion2\",\n .5,\n 0,\n .999999,\n \"k-rate\"\n ],\n [\n \"damping\",\n .005,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"excursionRate\",\n .5,\n 0,\n 2,\n \"k-rate\"\n ],\n [\n \"excursionDepth\",\n .7,\n 0,\n 2,\n \"k-rate\"\n ],\n [\n \"wet\",\n .3,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"dry\",\n .6,\n 0,\n 1,\n \"k-rate\"\n ]\n ].map((x) => /* @__PURE__ */ new Object({\n name: x[0],\n defaultValue: x[1],\n minValue: x[2],\n maxValue: x[3],\n automationRate: x[4]\n }));\n }\n constructor(options) {\n super(options);\n this._Delays = [];\n this._pDLength = sampleRate + (128 - sampleRate % 128);\n this._preDelay = new Float32Array(this._pDLength);\n this._pDWrite = 0;\n this._lp1 = 0;\n this._lp2 = 0;\n this._lp3 = 0;\n this._excPhase = 0;\n const SHORT_DELAY_SCALE = .5;\n [\n .004771345,\n .003595309,\n .012734787,\n .009307483,\n .022579886,\n .149625349,\n .060481839,\n .1249958,\n .030509727,\n .141695508,\n .089244313,\n .106280031\n ].map((x) => x * SHORT_DELAY_SCALE).forEach((x) => this.makeDelay(x));\n this._taps = Int16Array.from([\n .008937872,\n .099929438,\n .064278754,\n .067067639,\n .066866033,\n .006283391,\n .035818689,\n .011861161,\n .121870905,\n .041262054,\n .08981553,\n .070931756,\n .011256342,\n .004065724\n ], (x) => Math.round(x * sampleRate));\n this.port.postMessage({ type: \"initialized\" });\n }\n makeDelay(length) {\n let len = Math.round(length * sampleRate);\n let nextPow2 = 2 ** Math.ceil(Math.log2(len));\n this._Delays.push([\n new Float32Array(nextPow2),\n len - 1,\n 0,\n nextPow2 - 1\n ]);\n }\n writeDelay(index, data) {\n return this._Delays[index][0][this._Delays[index][1]] = data;\n }\n readDelay(index) {\n return this._Delays[index][0][this._Delays[index][2]];\n }\n readDelayAt(index, i) {\n let d = this._Delays[index];\n return d[0][d[2] + i & d[3]];\n }\n readDelayCAt(index, i) {\n let d = this._Delays[index], frac = i - ~~i, int = ~~i + d[2] - 1, mask = d[3];\n let x0 = d[0][int++ & mask], x1 = d[0][int++ & mask], x2 = d[0][int++ & mask], x3 = d[0][int & mask];\n let a = (3 * (x1 - x2) - x0 + x3) / 2, b = 2 * x2 + x0 - (5 * x1 + x3) / 2, c = (x2 - x0) / 2;\n return ((a * frac + b) * frac + c) * frac + x1;\n }\n process(inputs, outputs, parameters) {\n const TWO_PI = 6.283185307179586;\n const TWO_PI_DETUNE = 6.284702653297906;\n const pd = ~~parameters.preDelay[0], bw = parameters.bandwidth[0], fi = parameters.inputDiffusion1[0], si = parameters.inputDiffusion2[0], dc = parameters.decay[0], ft = parameters.decayDiffusion1[0], st = parameters.decayDiffusion2[0], dp = 1 - parameters.damping[0], ex = parameters.excursionRate[0] / sampleRate, ed = parameters.excursionDepth[0] * sampleRate / 1e3, we = parameters.wet[0] * .6, dr = parameters.dry[0];\n if (inputs[0].length == 2) for (let i = 127; i >= 0; i--) {\n this._preDelay[this._pDWrite + i] = (inputs[0][0][i] + inputs[0][1][i]) * .5;\n outputs[0][0][i] = inputs[0][0][i] * dr;\n outputs[0][1][i] = inputs[0][1][i] * dr;\n }\n else if (inputs[0].length > 0) {\n this._preDelay.set(inputs[0][0], this._pDWrite);\n for (let i = 127; i >= 0; i--) outputs[0][0][i] = outputs[0][1][i] = inputs[0][0][i] * dr;\n } else this._preDelay.set(/* @__PURE__ */ new Float32Array(128), this._pDWrite);\n let i = 0;\n while (i < 128) {\n let lo = 0, ro = 0;\n this._lp1 += bw * (this._preDelay[(this._pDLength + this._pDWrite - pd + i) % this._pDLength] - this._lp1);\n let pre = this.writeDelay(0, this._lp1 - fi * this.readDelay(0));\n pre = this.writeDelay(1, fi * (pre - this.readDelay(1)) + this.readDelay(0));\n pre = this.writeDelay(2, fi * pre + this.readDelay(1) - si * this.readDelay(2));\n pre = this.writeDelay(3, si * (pre - this.readDelay(3)) + this.readDelay(2));\n let split = si * pre + this.readDelay(3);\n let exc = ed * (1 + Math.cos(this._excPhase * TWO_PI));\n let exc2 = ed * (1 + Math.sin(this._excPhase * TWO_PI_DETUNE));\n let temp = this.writeDelay(4, split + dc * this.readDelay(11) + ft * this.readDelayCAt(4, exc));\n this.writeDelay(5, this.readDelayCAt(4, exc) - ft * temp);\n this._lp2 += dp * (this.readDelay(5) - this._lp2);\n temp = this.writeDelay(6, dc * this._lp2 - st * this.readDelay(6));\n this.writeDelay(7, this.readDelay(6) + st * temp);\n temp = this.writeDelay(8, split + dc * this.readDelay(7) + ft * this.readDelayCAt(8, exc2));\n this.writeDelay(9, this.readDelayCAt(8, exc2) - ft * temp);\n this._lp3 += dp * (this.readDelay(9) - this._lp3);\n temp = this.writeDelay(10, dc * this._lp3 - st * this.readDelay(10));\n this.writeDelay(11, this.readDelay(10) + st * temp);\n lo = this.readDelayAt(9, this._taps[0]) + this.readDelayAt(9, this._taps[1]) - this.readDelayAt(10, this._taps[2]) + this.readDelayAt(11, this._taps[3]) - this.readDelayAt(5, this._taps[4]) - this.readDelayAt(6, this._taps[5]) - this.readDelayAt(7, this._taps[6]);\n ro = this.readDelayAt(5, this._taps[7]) + this.readDelayAt(5, this._taps[8]) - this.readDelayAt(6, this._taps[9]) + this.readDelayAt(7, this._taps[10]) - this.readDelayAt(9, this._taps[11]) - this.readDelayAt(10, this._taps[12]) - this.readDelayAt(11, this._taps[13]);\n outputs[0][0][i] += lo * we;\n outputs[0][1][i] += ro * we;\n this._excPhase += ex;\n if (this._excPhase >= 1) this._excPhase -= 1;\n i++;\n const delays = this._Delays;\n for (let j = 0; j < delays.length; j++) {\n const d = delays[j];\n d[1] = d[1] + 1 & d[3];\n d[2] = d[2] + 1 & d[3];\n }\n }\n this._pDWrite = (this._pDWrite + 128) % this._pDLength;\n return true;\n }\n};\nregisterProcessor(\"dattorro-reverb-processor\", DattorroReverb);\n//#endregion\n//#region src/worklets/processors/distortion/distortion-processor.js\nvar Distortion = class {\n constructor() {\n this.limitingMode = \"hard-clipping\";\n }\n applyDrive(sample, driveAmount) {\n if (driveAmount <= 0) return sample;\n return sample * (1 + driveAmount * 3);\n }\n applyClipping(sample, clippingAmount, clipThreshold) {\n if (clippingAmount <= 0) return sample;\n let clippedSample;\n switch (this.limitingMode) {\n case \"soft-clipping\":\n clippedSample = clipThreshold * Math.tanh(sample / clipThreshold);\n break;\n case \"hard-clipping\":\n clippedSample = Math.max(-clipThreshold, Math.min(clipThreshold, sample));\n break;\n default: clippedSample = sample;\n }\n if (clipThreshold < .08) {\n const makeupGain = Math.min(2, Math.pow(.1 / clipThreshold, .5));\n clippedSample *= makeupGain;\n }\n return sample * (1 - clippingAmount) + clippedSample * clippingAmount;\n }\n setLimitingMode(mode) {\n this.limitingMode = mode;\n }\n};\nregisterProcessor(\"distortion-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"distortionDrive\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n {\n name: \"clippingAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n {\n name: \"clippingThreshold\",\n defaultValue: .5,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.distortion = new Distortion();\n this.setupMessageHandling();\n this.port.postMessage({ type: \"initialized\" });\n }\n setupMessageHandling() {\n this.port.onmessage = (event) => {\n switch (event.data.type) {\n case \"setLimitingMode\":\n this.distortion.setLimitingMode(event.data.mode);\n break;\n default: console.warn(\"distortion-processor: Unsupported message\");\n }\n };\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output) return true;\n const clipThreshold = parameters.clippingThreshold[0];\n for (let i = 0; i < output[0].length; ++i) {\n const distortionDrive = parameters.distortionDrive[Math.min(i, parameters.distortionDrive.length - 1)];\n const clippingAmount = parameters.clippingAmount[Math.min(i, parameters.clippingAmount.length - 1)];\n for (let c = 0; c < Math.min(input.length, output.length); c++) {\n let sample = input[c][i];\n sample = this.distortion.applyDrive(sample, distortionDrive);\n sample = this.distortion.applyClipping(sample, clippingAmount, clipThreshold);\n output[c][i] = Math.max(-.999, Math.min(.999, sample));\n }\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/follower/envelope-follower-processor.js\nregisterProcessor(\"envelope-follower-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"inputGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 10,\n automationRate: \"k-rate\"\n },\n {\n name: \"outputGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 10,\n automationRate: \"k-rate\"\n },\n {\n name: \"attack\",\n defaultValue: .003,\n minValue: .001,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"release\",\n defaultValue: .05,\n minValue: .001,\n maxValue: 5,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.envelope = 0;\n this.gateThreshold = .005;\n this.debugCounter = 0;\n this.port.postMessage({ type: \"initialized\" });\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n const channel = inputs[0][0];\n if (!input || !output || !channel || input.length === 0 || output.length === 0 || channel.length === 0) return true;\n const inChannel = input[0];\n if (!inChannel || inChannel.length === 0) return true;\n const attack = parameters.attack[0];\n const release = parameters.release[0];\n const inputGain = parameters.inputGain[0];\n const outputGain = parameters.outputGain[0];\n const attackCoeff = Math.exp(-1 / (attack * sampleRate));\n const releaseCoeff = Math.exp(-1 / (release * sampleRate));\n for (let sample = 0; sample < output[0].length; sample++) {\n const inputLevel = Math.abs((input[0][sample] || 0) * inputGain);\n if (inputLevel > 1e-6) {\n if (inputLevel > this.envelope) this.envelope = inputLevel + (this.envelope - inputLevel) * attackCoeff;\n else this.envelope = inputLevel + (this.envelope - inputLevel) * releaseCoeff;\n } else this.envelope *= releaseCoeff;\n if (this.envelope < this.gateThreshold) this.envelope = 0;\n const finalOutput = this.envelope * outputGain;\n for (let channel = 0; channel < output.length; channel++) output[channel][sample] = finalOutput;\n }\n return true;\n }\n});\n//#endregion\n", Tt = !1;
4683
+ }, wt = "//#region src/utils/search/findClosest.ts\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array index of the element which value is closest to the target value\n*/\nfunction findClosestIdx(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n if (sortedArray.length === 0) throw new Error(\"Array cannot be empty\");\n if (sortedArray.length === 1) return 0;\n const targetValue = target;\n const firstValue = getValue(sortedArray[0]);\n const lastValue = getValue(sortedArray[sortedArray.length - 1]);\n if (targetValue <= firstValue) return 0;\n if (targetValue >= lastValue) return sortedArray.length - 1;\n let left = 0;\n let right = sortedArray.length - 1;\n while (left < right - 1) {\n const mid = Math.floor((left + right) / 2);\n const midValue = getValue(sortedArray[mid]);\n if (midValue === targetValue) return mid;\n else if (midValue < targetValue) left = mid;\n else right = mid;\n }\n if (direction === \"left\") return left;\n if (direction === \"right\") return right;\n return getDistance(getValue(sortedArray[left]), targetValue) <= getDistance(getValue(sortedArray[right]), targetValue) ? left : right;\n}\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array element which value is closest to the target value\n*/\nfunction findClosest(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n return sortedArray[findClosestIdx(sortedArray, target, direction, getValue, getDistance)];\n}\nvar SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS = Object.values({\n masterGain: {\n name: \"masterGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n envGain: {\n name: \"envGain\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n velocity: {\n name: \"velocity\",\n defaultValue: 100,\n minValue: 0,\n maxValue: 127,\n automationRate: \"k-rate\"\n },\n pan: {\n name: \"pan\",\n defaultValue: 0,\n minValue: -1,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n playbackRate: {\n name: \"playbackRate\",\n defaultValue: 1,\n minValue: .1,\n maxValue: 24,\n automationRate: \"a-rate\"\n },\n loopStart: {\n name: \"loopStart\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopEnd: {\n name: \"loopEnd\",\n defaultValue: 99999,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n startPoint: {\n name: \"startPoint\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n endPoint: {\n name: \"endPoint\",\n defaultValue: 9999,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n playbackPosition: {\n name: \"playbackPosition\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopDurationDriftAmount: {\n name: \"loopDurationDriftAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n maxLoopCount: {\n name: \"maxLoopCount\",\n defaultValue: 999999,\n minValue: 1,\n maxValue: 999999,\n automationRate: \"k-rate\"\n },\n tempo: {\n name: \"tempo\",\n defaultValue: 120,\n minValue: 20,\n maxValue: 300,\n automationRate: \"k-rate\"\n }\n});\n//#endregion\n//#region src/worklets/processors/play/sample-player-processor.js\nvar SamplePlayerProcessor = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS;\n }\n constructor() {\n super();\n this.layers = [];\n this.layerGain = 1;\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.usePlaybackPosition = false;\n this.enableLoopSmoothing = true;\n this.enableAdaptiveDrift = true;\n this.enableAmplitudeCompensation = true;\n this.syncLoopToTempo = false;\n this.keytrackLoopAmount = 0;\n this.durationPreservation = {\n enabled: false,\n maxDriftSamples: Math.floor(sampleRate * .04),\n timelinePosition: 0,\n resetPending: false\n };\n this.PITCH_PRESERVATION_THRESHOLD = Math.floor(sampleRate * .061);\n this.AMPLITUDE_COMPENSATION_THRESHOLD = Math.floor(sampleRate / 16.35);\n this.port.onmessage = this.#handleMessage.bind(this);\n this.#resetState();\n this.port.postMessage({ type: \"initialized\" });\n }\n /** Authority layer. All range and duration math reads through this. */\n get buffer() {\n return this.layers[0] ?? null;\n }\n #handleMessage(event) {\n const { type, value, buffer, layers, timestamp, durationSeconds, zeroCrossings, allowedPeriods, playbackDirection } = event.data;\n switch (type) {\n case \"voice:reset\":\n this.#resetState();\n this.port.postMessage({ type: \"voice:reset\" });\n break;\n case \"voice:setBuffer\":\n case \"voice:setLayers\":\n this.#resetState();\n this.zeroCrossings = [];\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.layers = (layers ?? (buffer ? [buffer] : [])).filter(Boolean);\n this.layerGain = this.layers.length ? 1 / this.layers.length : 1;\n this.port.postMessage({\n type: \"voice:loaded\",\n durationSeconds,\n time: currentTime\n });\n break;\n case \"voice:setZeroCrossings\":\n this.zeroCrossings = (zeroCrossings || []).map((timeSec) => timeSec * sampleRate);\n if (this.zeroCrossings.length > 0) {\n this.minZeroCrossing = this.zeroCrossings[0];\n this.maxZeroCrossing = this.zeroCrossings[this.zeroCrossings.length - 1];\n }\n break;\n case \"voice:start\":\n this.isReleasing = false;\n this.isPlaying = true;\n this.loopCount = 0;\n this.playbackPosition = 0;\n this.port.postMessage({\n type: \"voice:started\",\n time: timestamp || currentTime\n });\n break;\n case \"voice:release\":\n this.isReleasing = true;\n this.port.postMessage({\n type: \"voice:releasing\",\n time: currentTime\n });\n break;\n case \"voice:stop\":\n this.#stop();\n break;\n case \"setLoopEnabled\":\n this.loopEnabled = value;\n this.port.postMessage({\n type: \"loop:enabled\",\n enabled: value\n });\n break;\n case \"setPanDriftEnabled\":\n this.panDriftEnabled = value;\n break;\n case \"voice:setPlaybackDirection\": {\n const reverse = playbackDirection === \"reverse\";\n if (reverse !== this.reversePlayback && this.playbackPosition > 0) this.playbackPosition += reverse ? 1 : -1;\n this.reversePlayback = reverse;\n this.port.postMessage({\n type: \"voice:playbackDirectionChange\",\n playbackDirection\n });\n break;\n }\n case \"voice:usePlaybackPosition\":\n this.usePlaybackPosition = value;\n break;\n case \"syncLoopToTempo\":\n this.syncLoopToTempo = value;\n this.port.postMessage({\n type: \"loop:syncToTempo\",\n enabled: value\n });\n break;\n case \"setKeytrackLoopAmount\":\n this.keytrackLoopAmount = Math.max(0, Math.min(1, value));\n break;\n case \"setPreserveDuration\":\n this.durationPreservation.enabled = Boolean(value);\n this.#resetDurationPreservation(this.playbackPosition);\n }\n }\n #resetState() {\n this.isPlaying = false;\n this.isReleasing = false;\n this.loopEnabled = false;\n this.velocitySensitivity = 1;\n this.reversePlayback = false;\n this.playbackPosition = 0;\n this.debugCounter = 0;\n this.loopCount = 0;\n this.applyClickCompensation = false;\n this.loopClickCompensation = 0;\n this.driftUpdateCounter = 0;\n this.currentLoopDrift = 0;\n this.currentPanDrift = 0;\n this.panDriftEnabled = true;\n this.nextDriftGenerated = false;\n this.loopAmplitudeGain = 1;\n this.lastAnalyzedLoopStart = -1;\n this.lastAnalyzedLoopEnd = -1;\n this.#resetDurationPreservation();\n }\n #stop() {\n this.isPlaying = false;\n this.isReleasing = false;\n this.playbackPosition = 0;\n this.port.postMessage({ type: \"voice:stopped\" });\n }\n #smoothLoopWrap(lastLoopSample, newFirstSample) {\n const discontinuity = lastLoopSample - newFirstSample;\n if (this.enableLoopSmoothing && Math.abs(discontinuity) > .01) {\n this.loopClickCompensation = discontinuity * .5;\n this.compensationDecay = .9;\n this.applyClickCompensation = true;\n }\n }\n #clamp = (value, min, max) => Math.max(min, Math.min(max, value));\n #clampZeroCrossing = (value) => this.#clamp(value, this.minZeroCrossing, this.maxZeroCrossing);\n #findNearestZeroCrossing(position, direction = \"any\", maxDistance = null) {\n if (!this.zeroCrossings || this.zeroCrossings.length === 0) return position;\n const closestValue = findClosest(this.zeroCrossings, position, direction);\n if (maxDistance !== null && Math.abs(closestValue - position) > maxDistance) return position;\n return closestValue;\n }\n /**\n * Convert normalized position (0-1) to sample index\n * @param {number} normalizedPosition - Position as 0-1 value\n * @returns {number} - Sample index\n */\n #normalizedToSamples(normalizedPosition) {\n if (!this.buffer || !this.buffer[0]) return 0;\n return normalizedPosition * this.buffer[0].length;\n }\n /**\n * Convert sample index to normalized position (0-1)\n * @param {number} sampleIndex - Sample index\n * @returns {number} - Normalized position 0-1\n */\n #samplesToNormalized(sampleIndex) {\n if (!this.buffer || !this.buffer[0]) return 0;\n return sampleIndex / this.buffer[0].length;\n }\n /**\n * Convert MIDI velocity (0-127) to gain multiplier (0-1)\n * @param {number} midiVelocity - MIDI velocity 0-127\n * @returns {number} - Gain multiplier 0-1\n */\n #midiVelocityToGain(midiVelocity) {\n return Math.max(0, Math.min(1, midiVelocity / 127));\n }\n /**\n * Get buffer duration in seconds\n * @returns {number} - Buffer duration in seconds\n */\n #getBufferDurationSeconds() {\n return (this.buffer?.[0]?.length || 0) / sampleRate;\n }\n /**\n * Calculate musical note durations in samples for given tempo\n * @param {number} tempo - BPM\n * @returns {Object} - Musical note durations in samples\n */\n #getMusicalNoteDurations(tempo) {\n const beatsPerSecond = tempo / 60;\n const samplesPerBeat = sampleRate / beatsPerSecond;\n return {\n whole: samplesPerBeat * 4,\n half: samplesPerBeat * 2,\n quarter: samplesPerBeat,\n eighth: samplesPerBeat / 2,\n sixteenth: samplesPerBeat / 4,\n thirtySecond: samplesPerBeat / 8,\n quarterTriplet: samplesPerBeat * 2 / 3,\n eighthTriplet: samplesPerBeat / 2 * 2 / 3,\n sixteenthTriplet: samplesPerBeat / 4 * 2 / 3\n };\n }\n /**\n * Quantize loop duration to nearest musical interval (skips if below the smallest quantize option)\n * @param {number} loopDurationSamples - Current loop duration in samples\n * @param {number} tempo - Current tempo in BPM\n * @param {number} playbackRate - Current playback rate\n * @returns {number} - Quantized loop duration in samples\n */\n #quantizeLoopDuration(loopDurationSamples, tempo, playbackRate) {\n if (!this.syncLoopToTempo) return loopDurationSamples;\n const noteDurations = this.#getMusicalNoteDurations(tempo);\n const effectiveDuration = loopDurationSamples / Math.abs(playbackRate);\n if (effectiveDuration < noteDurations.thirtySecond) return loopDurationSamples;\n const intervals = Object.values(noteDurations);\n let closestInterval = intervals[0];\n let smallestDiff = Math.abs(effectiveDuration - closestInterval);\n for (const interval of intervals) {\n const diff = Math.abs(effectiveDuration - interval);\n if (diff < smallestDiff) {\n smallestDiff = diff;\n closestInterval = interval;\n }\n }\n return Math.floor(closestInterval * Math.abs(playbackRate));\n }\n /**\n * Extract and convert all position parameters from seconds to samples\n * @param {Object} parameters - AudioWorkletProcessor parameters\n * @returns {Object} - Converted parameters in samples\n */\n #extractPositionParams(parameters) {\n return {\n startPointSamples: Math.floor(parameters.startPoint[0] * sampleRate),\n endPointSamples: Math.floor(parameters.endPoint[0] * sampleRate),\n loopStartSamples: Math.floor(parameters.loopStart[0] * sampleRate),\n loopEndSamples: Math.floor(parameters.loopEnd[0] * sampleRate)\n };\n }\n /**\n * Calculate effective playback range in samples\n * @param {Object} params - Position parameters from #extractPositionParams\n * @returns {Object} - Effective start and end positions\n */\n #calculatePlaybackRange(params) {\n const bufferLength = this.buffer?.[0]?.length || 0;\n const start = Math.max(0, params.startPointSamples);\n const end = params.endPointSamples > start ? Math.min(bufferLength, params.endPointSamples) : bufferLength;\n const snappedStart = this.#findNearestZeroCrossing(start, \"right\");\n const snappedEnd = this.#findNearestZeroCrossing(end, \"left\");\n return {\n startSamples: snappedStart,\n endSamples: snappedEnd,\n durationSamples: snappedEnd - snappedStart\n };\n }\n /**\n * Calculate effective loop range in samples with optional drift\n * @param {Object} params - Position parameters from #extractPositionParams\n * @param {Object} playbackRange - Range from #calculatePlaybackRange\n * @param {number} driftAmount - Loop duration drift amount (0-1)\n * @param {number} tempo - Current tempo in BPM\n * @param {number} playbackRate - Current playback rate\n * @returns {Object} - Effective loop start and end positions with drift applied\n */\n #calculateLoopRange(params, playbackRange, driftAmount = 0, tempo = 120, playbackRate = 1) {\n const lpStart = params.loopStartSamples;\n const lpEnd = params.loopEndSamples;\n let calcLoopStart = lpStart < lpEnd && lpStart >= 0 ? lpStart : playbackRange.startSamples;\n let calcLoopEnd = lpEnd > lpStart && lpEnd <= playbackRange.endSamples ? lpEnd : playbackRange.endSamples;\n let baseDuration = calcLoopEnd - calcLoopStart;\n if (this.syncLoopToTempo) {\n const quantizedDuration = this.#quantizeLoopDuration(baseDuration, tempo, playbackRate);\n calcLoopEnd = calcLoopStart + quantizedDuration;\n calcLoopEnd = Math.min(calcLoopEnd, playbackRange.endSamples);\n }\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD && this.keytrackLoopAmount > 0 && !this.syncLoopToTempo) {\n const scale = 1 + this.keytrackLoopAmount * (Math.abs(playbackRate) - 1);\n baseDuration = Math.max(1, Math.floor(baseDuration * scale));\n calcLoopEnd = calcLoopStart + baseDuration;\n }\n baseDuration = calcLoopEnd - calcLoopStart;\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD) calcLoopStart = this.#findNearestZeroCrossing(calcLoopStart, \"right\");\n if (driftAmount > 0 && this.loopEnabled) {\n if (!this.nextDriftGenerated || this.loopCount === 0) {\n const updateInterval = baseDuration <= this.PITCH_PRESERVATION_THRESHOLD ? Math.max(1, Math.floor(this.PITCH_PRESERVATION_THRESHOLD / baseDuration)) : 1;\n if (this.driftUpdateCounter % updateInterval === 0) {\n this.currentLoopDrift = this.#generateLoopDrift(driftAmount, baseDuration);\n if (this.panDriftEnabled && driftAmount > 0 && this.loopCount > 0) {\n const panDriftAmountScalar = 1e-4;\n this.currentPanDrift = this.currentLoopDrift * panDriftAmountScalar;\n } else this.currentPanDrift = 0;\n }\n this.driftUpdateCounter++;\n this.nextDriftGenerated = true;\n }\n const driftedLoopEnd = calcLoopEnd + this.currentLoopDrift;\n const minLoopDuration = Math.max(1, Math.floor(baseDuration * .1));\n const maxLoopEnd = Math.max(playbackRange.endSamples, calcLoopEnd);\n calcLoopEnd = Math.max(calcLoopStart + minLoopDuration, Math.min(maxLoopEnd, driftedLoopEnd));\n } else this.currentPanDrift = 0;\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD && calcLoopEnd <= playbackRange.endSamples) calcLoopEnd = Math.max(calcLoopStart + 1, this.#findNearestZeroCrossing(calcLoopEnd, \"left\"));\n const loopDuration = calcLoopEnd - calcLoopStart;\n return {\n loopStartSamples: calcLoopStart,\n loopEndSamples: calcLoopEnd,\n loopDurationSamples: loopDuration\n };\n }\n #getSafeParam(paramArray, index, isConstant) {\n return isConstant ? paramArray[0] : paramArray[Math.min(index, paramArray.length - 1)];\n }\n #getConstantFlags(parameters) {\n this.constantFlags ??= {\n envGain: true,\n playbackRate: true\n };\n this.constantFlags.envGain = parameters.envGain.length === 1;\n this.constantFlags.playbackRate = parameters.playbackRate.length === 1;\n return this.constantFlags;\n }\n #resetDurationPreservation(position = 0) {\n this.durationPreservation.timelinePosition = position;\n this.durationPreservation.resetPending = false;\n }\n #isDurationPreservationActive(loopRange) {\n return this.durationPreservation.enabled && Boolean(this.zeroCrossings?.length) && (!this.loopEnabled || loopRange.loopDurationSamples > this.PITCH_PRESERVATION_THRESHOLD);\n }\n #prepareDurationPreservingSample(playbackRate, loopRange) {\n const state = this.durationPreservation;\n if (!this.#isDurationPreservationActive(loopRange)) return null;\n if (Math.abs(this.playbackPosition - state.timelinePosition) > state.maxDriftSamples) state.resetPending = true;\n if (!state.resetPending) return null;\n const direction = playbackRate < 0 ? \"left\" : \"right\";\n const outgoingZero = this.#findNearestZeroCrossing(this.playbackPosition, direction);\n if (Math.abs(outgoingZero - this.playbackPosition) > Math.abs(playbackRate)) return null;\n this.playbackPosition = outgoingZero;\n state.resetPending = false;\n return this.#findNearestZeroCrossing(state.timelinePosition, \"any\", state.maxDriftSamples);\n }\n #advanceDurationPreservingPlayback(playbackRate, resetTarget, loopRange, canWrapLoop) {\n const state = this.durationPreservation;\n this.playbackPosition = resetTarget === null ? this.playbackPosition + playbackRate : resetTarget;\n if (this.#isDurationPreservationActive(loopRange)) {\n state.timelinePosition += playbackRate < 0 ? -1 : 1;\n if (canWrapLoop && playbackRate >= 0 && state.timelinePosition >= loopRange.loopEndSamples) state.timelinePosition = loopRange.loopStartSamples;\n else if (canWrapLoop && playbackRate < 0 && state.timelinePosition <= loopRange.loopStartSamples) state.timelinePosition = loopRange.loopEndSamples - 1;\n } else this.#resetDurationPreservation(this.playbackPosition);\n }\n /**\n * Generate a new drift amount for the current loop iteration\n * @param {number} driftAmount - Maximum drift amount (0-1)\n * @param {number} baseDuration - Base loop duration in samples\n * @returns {number} - Drift amount in samples\n */\n #generateLoopDrift(driftAmount, baseDuration) {\n if (driftAmount <= 0) return 0;\n const randomFactor = (Math.random() - .5) * 2;\n let effectiveDriftAmount = driftAmount;\n if (this.enableAdaptiveDrift) {\n const shortThreshold = 1024;\n const longThreshold = 8192;\n if (baseDuration < shortThreshold) effectiveDriftAmount *= .1;\n else if (baseDuration < longThreshold) {\n const scaleFactor = .1 + .9 * (baseDuration - shortThreshold) / 7168;\n effectiveDriftAmount *= scaleFactor;\n }\n }\n const maxDriftSamples = effectiveDriftAmount * baseDuration;\n return Math.floor(randomFactor * maxDriftSamples);\n }\n /**\n * Analyze loop amplitude and calculate makeup gain for short loops\n * @param {number} loopStart - Loop start position in samples\n * @param {number} loopEnd - Loop end position in samples\n * @returns {number} - Makeup gain multiplier (1.0 = no change)\n */\n #analyzeLoopAmplitude(loopStart, loopEnd) {\n if (!this.enableAmplitudeCompensation || !this.buffer || !this.buffer[0]) return 1;\n if (loopEnd - loopStart >= this.AMPLITUDE_COMPENSATION_THRESHOLD) return 1;\n if (loopStart === this.lastAnalyzedLoopStart && loopEnd === this.lastAnalyzedLoopEnd) return this.loopAmplitudeGain;\n let sumSquares = 0;\n let sampleCount = 0;\n const channel = this.buffer[0];\n const startIndex = Math.floor(loopStart);\n const endIndex = Math.floor(loopEnd);\n for (let i = startIndex; i < endIndex && i < channel.length; i++) {\n const sample = channel[i];\n sumSquares += sample * sample;\n sampleCount++;\n }\n if (sampleCount === 0) return 1;\n const rmsAmplitude = Math.sqrt(sumSquares / sampleCount);\n const targetAmplitude = .3;\n let makeupGain = 1;\n if (rmsAmplitude < targetAmplitude) {\n makeupGain = targetAmplitude / Math.max(rmsAmplitude, .001);\n makeupGain = Math.min(2, makeupGain);\n }\n this.lastAnalyzedLoopStart = loopStart;\n this.lastAnalyzedLoopEnd = loopEnd;\n this.loopAmplitudeGain = makeupGain;\n return makeupGain;\n }\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n this.debugCounter++;\n if (!output || !this.isPlaying || !this.buffer?.[0]?.length) return true;\n const masterGain = parameters.masterGain[0];\n const positionParams = this.#extractPositionParams(parameters);\n const playbackRange = this.#calculatePlaybackRange(positionParams);\n const effectivePlaybackRate = parameters.playbackRate[0];\n const tempo = parameters.tempo[0];\n const loopRange = this.#calculateLoopRange(positionParams, playbackRange, parameters.loopDurationDriftAmount[0], tempo, effectivePlaybackRate);\n const amplitudeGain = this.#analyzeLoopAmplitude(loopRange.loopStartSamples, loopRange.loopEndSamples);\n const velocityGain = this.#midiVelocityToGain(parameters.velocity[0]) * this.velocitySensitivity;\n const basePan = parameters.pan[0];\n const effectivePan = this.panDriftEnabled ? Math.max(-1, Math.min(1, basePan + this.currentPanDrift)) : basePan;\n let outputChannels;\n if (output instanceof Float32Array) outputChannels = [output];\n else if (Array.isArray(output) && output.every((ch) => ch instanceof Float32Array)) outputChannels = output;\n else {\n console.error(\"Unexpected output structure:\", {\n outputType: typeof output,\n isArray: Array.isArray(output),\n constructor: output?.constructor?.name,\n length: output?.length\n });\n return true;\n }\n const numChannels = outputChannels.length;\n const isConstant = this.#getConstantFlags(parameters);\n const silencePadTail = loopRange.loopEndSamples > playbackRange.endSamples;\n const TAIL_FADE_SAMPLES = 64;\n if (this.playbackPosition === 0) {\n this.playbackPosition = this.reversePlayback ? playbackRange.endSamples - 1 : playbackRange.startSamples;\n this.#resetDurationPreservation(this.playbackPosition);\n }\n for (let sample = 0; sample < outputChannels[0].length; sample++) {\n const envelopeGain = this.#getSafeParam(parameters.envGain, sample, isConstant.envGain);\n const baseRate = this.#getSafeParam(parameters.playbackRate, sample, isConstant.playbackRate);\n const playbackStep = this.reversePlayback ? -Math.abs(baseRate) : Math.abs(baseRate);\n const canWrapLoop = this.loopEnabled && this.loopCount < parameters.maxLoopCount[0];\n if (canWrapLoop) {\n if (!this.reversePlayback && this.playbackPosition >= loopRange.loopEndSamples) {\n this.#smoothLoopWrap(silencePadTail ? 0 : this.buffer[0][Math.floor(this.playbackPosition - 1)] || 0, this.buffer[0][Math.floor(loopRange.loopStartSamples)] || 0);\n this.playbackPosition = loopRange.loopStartSamples;\n this.loopCount++;\n this.nextDriftGenerated = false;\n } else if (this.reversePlayback && this.playbackPosition <= loopRange.loopStartSamples) {\n this.#smoothLoopWrap(this.buffer[0][Math.floor(loopRange.loopStartSamples)] || 0, silencePadTail ? 0 : this.buffer[0][Math.floor(loopRange.loopEndSamples) - 1] || 0);\n this.playbackPosition = loopRange.loopEndSamples;\n this.loopCount++;\n this.nextDriftGenerated = false;\n }\n }\n const durationResetTarget = this.#prepareDurationPreservingSample(playbackStep, loopRange);\n const shouldStopForward = !this.reversePlayback && (this.#isDurationPreservationActive(loopRange) ? this.durationPreservation.timelinePosition : this.playbackPosition) >= playbackRange.endSamples;\n const shouldStopReverse = this.reversePlayback && (this.#isDurationPreservationActive(loopRange) ? this.durationPreservation.timelinePosition : this.playbackPosition) <= playbackRange.startSamples;\n const isWithinLoop = this.loopEnabled && this.playbackPosition >= loopRange.loopStartSamples && this.playbackPosition <= loopRange.loopEndSamples;\n if ((shouldStopForward || shouldStopReverse) && !(this.loopEnabled && isWithinLoop)) {\n this.#stop();\n return true;\n }\n let tailGain = 1;\n if (silencePadTail) {\n const distToEnd = playbackRange.endSamples - this.playbackPosition;\n if (distToEnd < TAIL_FADE_SAMPLES) tailGain = Math.max(0, distToEnd / TAIL_FADE_SAMPLES);\n }\n const currentPosition = Math.floor(this.playbackPosition);\n const positionOffset = this.playbackPosition - currentPosition;\n let nextPosition, interpWeight;\n if (this.reversePlayback) {\n nextPosition = Math.max(currentPosition - 1, playbackRange.startSamples);\n interpWeight = 1 - positionOffset;\n } else {\n nextPosition = Math.min(currentPosition + 1, playbackRange.endSamples - 1);\n interpWeight = positionOffset;\n }\n for (let channel = 0; channel < numChannels; channel++) {\n if (!outputChannels[channel]) {\n console.warn(`Output channel ${channel} does not exist. Available channels:`, outputChannels.length);\n continue;\n }\n let interpolatedSample = 0;\n for (let l = 0; l < this.layers.length; l++) {\n const layer = this.layers[l];\n const layerChannel = layer[Math.min(channel, layer.length - 1)];\n const currentSample = layerChannel[currentPosition] || 0;\n const nextSample = layerChannel[nextPosition] || 0;\n interpolatedSample += (currentSample + interpWeight * (nextSample - currentSample)) * this.layerGain;\n }\n if (this.applyClickCompensation) {\n interpolatedSample += this.loopClickCompensation;\n if (this.compensationDecay) {\n this.loopClickCompensation *= this.compensationDecay;\n if (Math.abs(this.loopClickCompensation) < .001) this.applyClickCompensation = false;\n } else this.applyClickCompensation = false;\n }\n const finalSample = interpolatedSample * velocityGain * envelopeGain * masterGain * amplitudeGain * tailGain;\n let panAdjustedSample = finalSample;\n if (outputChannels.length === 2) {\n if (channel === 0) panAdjustedSample = finalSample * (1 - Math.max(0, effectivePan));\n else if (channel === 1) panAdjustedSample = finalSample * (1 - Math.max(0, -effectivePan));\n }\n outputChannels[channel][sample] = Math.max(-1, Math.min(1, isFinite(panAdjustedSample) ? panAdjustedSample : 0));\n }\n this.#advanceDurationPreservingPlayback(playbackStep, durationResetTarget, loopRange, canWrapLoop);\n }\n if (this.usePlaybackPosition) {\n const normalizedPosition = this.#samplesToNormalized(this.playbackPosition);\n this.port.postMessage({\n type: \"voice:position\",\n position: normalizedPosition\n });\n }\n return true;\n }\n};\nregisterProcessor(\"sample-player-processor\", SamplePlayerProcessor);\n//#endregion\n//#region src/worklets/processors/noise/random-noise-processor.js\nvar RandomNoiseProcessor = class extends AudioWorkletProcessor {\n constructor() {\n super();\n this.previousNoise = 0;\n this.previousFiltered = 0;\n this.hpfHz = 150;\n this.alpha = this.hpfHz / (this.hpfHz + sampleRate / (2 * Math.PI));\n this.port.onmessage = (event) => {\n if (event.data.type === \"setHpfHz\") {\n this.hpfHz = event.data.value;\n this.alpha = this.calculateAlpha(this.hpfHz);\n }\n };\n this.port.postMessage({ type: \"initialized\" });\n }\n calculateAlpha(frequency) {\n return frequency / (frequency + sampleRate / (2 * Math.PI));\n }\n process(inputs, outputs, parameters) {\n outputs[0].forEach((channel) => {\n for (let i = 0; i < channel.length; i++) {\n const noise = Math.random() * 2 - 1;\n const filtered = this.alpha * (noise - this.previousNoise) + this.previousFiltered;\n this.previousNoise = noise;\n this.previousFiltered = filtered;\n channel[i] = filtered;\n }\n });\n return true;\n }\n};\nregisterProcessor(\"random-noise-processor\", RandomNoiseProcessor);\n//#endregion\n//#region src/worklets/shared/utils/compress-utils.ts\nvar cheapSoftClipSingleSample = (sample, max = .9) => {\n const a = Math.abs(sample);\n if (a <= max) return sample;\n const x = a / max;\n const compressed = x / (1 + x);\n return Math.sign(sample) * max * compressed;\n};\n/**\n* Basic attenuation compressor for single sample\n* Note: No validation since optimized for real time use\n*/\nvar compressSingleSample = (input, threshold = .75, ratio = 4, limiter = {\n enabled: true,\n type: \"soft\",\n outputRange: {\n min: -1,\n max: 1\n }\n}) => {\n const { min, max } = limiter.outputRange;\n let x = input;\n if (Math.abs(x) > threshold) x = Math.sign(x) * (threshold + (Math.abs(x) - threshold) / ratio);\n if (limiter.enabled) {\n if (limiter.type === \"soft\") x = cheapSoftClipSingleSample(x, Math.abs(max));\n else if (limiter.type === \"hard\") x = Math.max(min, Math.min(max, x));\n }\n return x;\n};\n//#endregion\n//#region src/worklets/processors/delay/DelayBuffer.js\nvar DelayBuffer = class {\n constructor(maxDelaySamples) {\n this.buffer = new Float32Array(maxDelaySamples);\n this.writePtr = 0;\n this.readPtr = 0;\n }\n write(sample) {\n this.buffer[this.writePtr] = sample;\n }\n read() {\n return this.buffer[this.readPtr];\n }\n updatePointers(delaySamples) {\n this.writePtr = (this.writePtr + 1) % this.buffer.length;\n this.readPtr = (this.writePtr - delaySamples + this.buffer.length) % this.buffer.length;\n }\n};\n//#endregion\n//#region src/worklets/processors/delay/FeedbackDelay.js\nvar AUTO_GAIN_THRESHOLD = .8;\nvar SAFETY_GAIN_COMPENSATION = .2;\nvar FeedbackDelay = class {\n constructor(sampleRate) {\n this.sampleRate = sampleRate;\n this.buffers = [];\n this.initialized = false;\n this.autoGainEnabled = false;\n this.gainCompensation = SAFETY_GAIN_COMPENSATION;\n this.lowpassStates = [];\n this.highpassStates = [];\n this.highpassInputStates = [];\n }\n initializeBuffers(channelCount) {\n this.buffers = [];\n this.lowpassStates = [];\n this.highpassStates = [];\n this.highpassInputStates = [];\n const maxSamples = Math.floor(this.sampleRate * 2);\n for (let c = 0; c < channelCount; c++) {\n this.buffers[c] = new DelayBuffer(maxSamples);\n this.lowpassStates[c] = 0;\n this.highpassStates[c] = 0;\n this.highpassInputStates[c] = 0;\n }\n this.initialized = true;\n }\n /** Simple one-pole lowpass filter */\n lowpass(input, cutoffFreq, channelIndex) {\n if (cutoffFreq >= this.sampleRate * .4) return input;\n const omega = 2 * Math.PI * cutoffFreq / this.sampleRate;\n const alpha = Math.max(0, Math.min(.99, Math.sin(omega) / (Math.sin(omega) + Math.cos(omega))));\n this.lowpassStates[channelIndex] = alpha * input + (1 - alpha) * this.lowpassStates[channelIndex];\n return this.lowpassStates[channelIndex];\n }\n /** Simple one-pole highpass filter */\n highpass(input, cutoffFreq, channelIndex) {\n if (cutoffFreq < 5) return input;\n const omega = 2 * Math.PI * cutoffFreq / this.sampleRate;\n const alpha = Math.max(0, Math.min(.99, Math.sin(omega) / (Math.sin(omega) + Math.cos(omega))));\n const lowpassOutput = alpha * input + (1 - alpha) * this.highpassStates[channelIndex];\n const highpassOutput = input - lowpassOutput;\n this.highpassStates[channelIndex] = lowpassOutput;\n return highpassOutput;\n }\n process(inputSample, channelIndex, feedbackAmount, delayTime, lowpassFreq = 1e4, highpassFreq = 100) {\n if (!this.initialized) return inputSample;\n const buffer = this.buffers[channelIndex] || this.buffers[0];\n const delaySamples = Math.floor(this.sampleRate * delayTime);\n const delayedSample = buffer.read();\n let filteredDelay = this.highpass(delayedSample, highpassFreq, channelIndex);\n filteredDelay = this.lowpass(filteredDelay, lowpassFreq, channelIndex);\n const feedbackSample = feedbackAmount * filteredDelay + inputSample;\n let outputSample = feedbackSample;\n const compressedFeedback = compressSingleSample(feedbackSample, .5, 4, {\n enabled: true,\n outputRange: {\n min: -.99,\n max: .99\n },\n type: \"soft\"\n });\n if (this.autoGainEnabled && feedbackAmount > AUTO_GAIN_THRESHOLD) outputSample = compressedFeedback * (1 - (feedbackAmount - AUTO_GAIN_THRESHOLD) * this.gainCompensation);\n return {\n outputSample,\n feedbackSample: compressedFeedback,\n delaySamples\n };\n }\n updateBuffer(channelIndex, sample, delaySamples) {\n const buffer = this.buffers[channelIndex] || this.buffers[0];\n buffer.write(sample);\n buffer.updatePointers(delaySamples);\n }\n setAutoGain(enabled, compensation = SAFETY_GAIN_COMPENSATION) {\n this.autoGainEnabled = enabled;\n this.gainCompensation = compensation;\n }\n};\n//#endregion\n//#region src/worklets/processors/delay/feedback-delay-processor.js\nregisterProcessor(\"feedback-delay-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"feedbackAmount\",\n defaultValue: .5,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"delayTime\",\n defaultValue: .5,\n minValue: .00012656238799684143,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n {\n name: \"decay\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"lowpass\",\n defaultValue: 1e4,\n minValue: 100,\n maxValue: 16e3,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.feedbackDelay = new FeedbackDelay(sampleRate);\n this.decayStartTime = null;\n this.decayActive = false;\n this.baseFeedbackAmount = .5;\n this.setupMessageHandling();\n this.port.postMessage({ type: \"initialized\" });\n }\n setupMessageHandling() {\n this.port.onmessage = (event) => {\n switch (event.data.type) {\n case \"setAutoGain\":\n this.feedbackDelay.setAutoGain(event.data.enabled, event.data.amount);\n break;\n case \"triggerDecay\":\n this.decayStartTime = currentTime;\n this.decayActive = true;\n this.baseFeedbackAmount = event.data.baseFeedbackAmount || .5;\n break;\n case \"stopDecay\":\n this.decayActive = false;\n this.decayStartTime = null;\n }\n };\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output) return true;\n if (!this.feedbackDelay.initialized || this.feedbackDelay.buffers.length !== input.length) this.feedbackDelay.initializeBuffers(input.length);\n const baseFeedbackAmount = parameters.feedbackAmount[0];\n const delayTime = parameters.delayTime[0];\n const decay = parameters.decay[0];\n const lowpassFreq = parameters.lowpass[0];\n const channelCount = Math.min(input.length, output.length);\n const frameCount = output[0].length;\n for (let i = 0; i < frameCount; ++i) {\n let effectiveFeedbackAmount = baseFeedbackAmount;\n if (this.decayActive && this.decayStartTime !== null) {\n const elapsedTime = currentTime - this.decayStartTime + i / sampleRate;\n const delayCompensation = Math.min(100, .5 / delayTime);\n const timeConstant = Math.pow(decay, 5) * 1e3 * delayCompensation + .5;\n effectiveFeedbackAmount = baseFeedbackAmount * Math.exp(-elapsedTime / timeConstant);\n if (effectiveFeedbackAmount < .01) {\n this.decayActive = false;\n effectiveFeedbackAmount = 0;\n }\n }\n for (let c = 0; c < channelCount; c++) {\n const processed = this.feedbackDelay.process(input[c][i], c, effectiveFeedbackAmount, delayTime, lowpassFreq);\n output[c][i] = processed.outputSample;\n this.feedbackDelay.updateBuffer(c, processed.feedbackSample, processed.delaySamples);\n }\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/delay/delay-processor.js\nvar DEFAULT_DELAY_CONFIG = {\n CHARACTER: [\"filtered\"],\n SMOOTHING_FACTOR: {\n slowest: 1e-4,\n slow: 25e-5,\n medium: 35e-5,\n fast: 5e-4,\n veryFast: .001,\n superFast: .1,\n none: 1\n }\n};\nvar DEFAULT_CHARACTER_CONFIG = {\n bitCrushed: {\n bits: 11,\n downsample: 3\n },\n filtered: {\n freq: 900,\n Q: .15\n }\n};\nregisterProcessor(\"delay-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [{\n name: \"delayTime\",\n defaultValue: .5,\n minValue: .001,\n maxValue: 2,\n automationRate: \"k-rate\"\n }, {\n name: \"feedbackAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: .99,\n automationRate: \"k-rate\"\n }];\n }\n constructor() {\n super();\n this.buffers = [];\n this.smoothedDelaySamples = [];\n this.smoothingFactor = DEFAULT_DELAY_CONFIG.SMOOTHING_FACTOR.slowest;\n this.characterModes = [...DEFAULT_DELAY_CONFIG.CHARACTER];\n this._bpState = [];\n this._bpFreq = DEFAULT_CHARACTER_CONFIG.filtered.freq;\n this._bpQ = DEFAULT_CHARACTER_CONFIG.filtered.Q;\n this._bpCoeffs = null;\n this._lastBpFreq = -1;\n this._lastBpQ = -1;\n this.lofiBits = DEFAULT_CHARACTER_CONFIG[\"bitCrushed\"].bits;\n this.lofiDownsample = DEFAULT_CHARACTER_CONFIG[\"bitCrushed\"].downsample;\n this._lofiSampleHold = [];\n this._lofiSampleCount = [];\n this.initialized = false;\n this.port.onmessage = (event) => {\n if (event.data && event.data.type === \"setCharacter\" && Array.isArray(event.data.modes)) this.characterModes = [...event.data.modes];\n if (event.data && event.data.type === \"setBandpassFreq\" && typeof event.data.hz === \"number\") this.setBandpassFreq(event.data.hz);\n if (event.data && event.data.type === \"trigger\") {}\n };\n this.port.postMessage({ type: \"initialized\" });\n }\n setBandpassFreq(hz) {\n this._bpFreq = hz;\n this._lastBpFreq = -1;\n }\n _updateBandpassCoeffs() {\n if (this._lastBpFreq === this._bpFreq && this._lastBpQ === this._bpQ) return;\n const bpFreq = this._bpFreq;\n const bpQ = this._bpQ;\n const omega = 2 * Math.PI * bpFreq / sampleRate;\n const alpha = Math.sin(omega) / (2 * bpQ);\n const cosw = Math.cos(omega);\n const b0 = alpha;\n const b1 = 0;\n const b2 = -alpha;\n const a0 = 1 + alpha;\n const a1 = -2 * cosw;\n const a2 = 1 - alpha;\n this._bpCoeffs = {\n b0: b0 / a0,\n b1: b1 / a0,\n b2: b2 / a0,\n a1: a1 / a0,\n a2: a2 / a0\n };\n this._lastBpFreq = bpFreq;\n this._lastBpQ = bpQ;\n }\n initializeBuffers(channelCount) {\n const maxSamples = Math.floor(sampleRate * 2);\n this.buffers = [];\n this.smoothedDelaySamples = [];\n this._lofiSampleHold = [];\n this._lofiSampleCount = [];\n for (let c = 0; c < channelCount; c++) {\n this.buffers[c] = new DelayBuffer(maxSamples);\n this.smoothedDelaySamples[c] = Math.floor(sampleRate * .5);\n this._lofiSampleHold[c] = 0;\n this._lofiSampleCount[c] = 0;\n }\n this.initialized = true;\n }\n _processLoFi(delayed, c) {\n if (this._lofiSampleCount[c] % this.lofiDownsample === 0) {\n const levels = Math.pow(2, this.lofiBits);\n delayed = Math.round(delayed * levels) / levels;\n this._lofiSampleHold[c] = delayed;\n } else delayed = this._lofiSampleHold[c];\n this._lofiSampleCount[c]++;\n return delayed;\n }\n _processBandpass(delayed, c) {\n if (!this._bpState) this._bpState = [];\n if (!this._bpState[c]) this._bpState[c] = {\n x1: 0,\n x2: 0,\n y1: 0,\n y2: 0\n };\n this._updateBandpassCoeffs();\n if (!this._bpCoeffs) return delayed;\n const { b0, b1, b2, a1, a2 } = this._bpCoeffs;\n const s = this._bpState[c];\n const y = b0 * delayed + b1 * s.x1 + b2 * s.x2 - a1 * s.y1 - a2 * s.y2;\n s.x2 = s.x1;\n s.x1 = delayed;\n s.y2 = s.y1;\n s.y1 = y;\n return y;\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output || input.length === 0 || output.length === 0) return true;\n if (!input[0] || !output[0] || input[0].length === 0 || output[0].length === 0) return true;\n if (!this.initialized || this.buffers.length !== input.length) this.initializeBuffers(input.length);\n const delayTime = parameters.delayTime[0];\n const feedbackAmount = parameters.feedbackAmount[0];\n const targetDelaySamples = sampleRate * delayTime;\n const channelCount = Math.min(input.length, output.length);\n const frameCount = output[0].length;\n const smoothing = this.smoothingFactor;\n for (let i = 0; i < frameCount; ++i) for (let c = 0; c < channelCount; c++) {\n const buf = this.buffers[c];\n if (!buf) continue;\n this.smoothedDelaySamples[c] += (targetDelaySamples - this.smoothedDelaySamples[c]) * smoothing;\n const smoothedDelay = this.smoothedDelaySamples[c];\n const intDelay = Math.floor(smoothedDelay);\n const frac = smoothedDelay - intDelay;\n const readPtrA = (buf.writePtr - intDelay + buf.buffer.length) % buf.buffer.length;\n const readPtrB = (readPtrA - 1 + buf.buffer.length) % buf.buffer.length;\n const sampleA = buf.buffer[readPtrA];\n const sampleB = buf.buffer[readPtrB];\n let delayed = sampleA * (1 - frac) + sampleB * frac;\n for (const mode of this.characterModes) if (mode === \"bitCrushed\") delayed = this._processLoFi(delayed, c);\n else if (mode === \"filtered\") delayed = this._processBandpass(delayed, c);\n output[c][i] = compressSingleSample(delayed, .75, 4, {\n enabled: true,\n type: \"soft\",\n outputRange: {\n min: -.9,\n max: .9\n }\n });\n const inputSample = input[c] && input[c][i] !== void 0 ? input[c][i] : 0;\n buf.write(inputSample + delayed * feedbackAmount);\n buf.updatePointers(intDelay);\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/reverb/dattorro-reverb-processor.js\nvar DattorroReverb = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n [\n \"preDelay\",\n 0,\n 0,\n sampleRate - 1,\n \"k-rate\"\n ],\n [\n \"bandwidth\",\n .9999,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"inputDiffusion1\",\n .75,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"inputDiffusion2\",\n .625,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"decay\",\n .5,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"decayDiffusion1\",\n .7,\n 0,\n .999999,\n \"k-rate\"\n ],\n [\n \"decayDiffusion2\",\n .5,\n 0,\n .999999,\n \"k-rate\"\n ],\n [\n \"damping\",\n .005,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"excursionRate\",\n .5,\n 0,\n 2,\n \"k-rate\"\n ],\n [\n \"excursionDepth\",\n .7,\n 0,\n 2,\n \"k-rate\"\n ],\n [\n \"wet\",\n .3,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"dry\",\n .6,\n 0,\n 1,\n \"k-rate\"\n ]\n ].map((x) => /* @__PURE__ */ new Object({\n name: x[0],\n defaultValue: x[1],\n minValue: x[2],\n maxValue: x[3],\n automationRate: x[4]\n }));\n }\n constructor(options) {\n super(options);\n this._Delays = [];\n this._pDLength = sampleRate + (128 - sampleRate % 128);\n this._preDelay = new Float32Array(this._pDLength);\n this._pDWrite = 0;\n this._lp1 = 0;\n this._lp2 = 0;\n this._lp3 = 0;\n this._excPhase = 0;\n const SHORT_DELAY_SCALE = .5;\n [\n .004771345,\n .003595309,\n .012734787,\n .009307483,\n .022579886,\n .149625349,\n .060481839,\n .1249958,\n .030509727,\n .141695508,\n .089244313,\n .106280031\n ].map((x) => x * SHORT_DELAY_SCALE).forEach((x) => this.makeDelay(x));\n this._taps = Int16Array.from([\n .008937872,\n .099929438,\n .064278754,\n .067067639,\n .066866033,\n .006283391,\n .035818689,\n .011861161,\n .121870905,\n .041262054,\n .08981553,\n .070931756,\n .011256342,\n .004065724\n ], (x) => Math.round(x * sampleRate));\n this.port.postMessage({ type: \"initialized\" });\n }\n makeDelay(length) {\n let len = Math.round(length * sampleRate);\n let nextPow2 = 2 ** Math.ceil(Math.log2(len));\n this._Delays.push([\n new Float32Array(nextPow2),\n len - 1,\n 0,\n nextPow2 - 1\n ]);\n }\n writeDelay(index, data) {\n return this._Delays[index][0][this._Delays[index][1]] = data;\n }\n readDelay(index) {\n return this._Delays[index][0][this._Delays[index][2]];\n }\n readDelayAt(index, i) {\n let d = this._Delays[index];\n return d[0][d[2] + i & d[3]];\n }\n readDelayCAt(index, i) {\n let d = this._Delays[index], frac = i - ~~i, int = ~~i + d[2] - 1, mask = d[3];\n let x0 = d[0][int++ & mask], x1 = d[0][int++ & mask], x2 = d[0][int++ & mask], x3 = d[0][int & mask];\n let a = (3 * (x1 - x2) - x0 + x3) / 2, b = 2 * x2 + x0 - (5 * x1 + x3) / 2, c = (x2 - x0) / 2;\n return ((a * frac + b) * frac + c) * frac + x1;\n }\n process(inputs, outputs, parameters) {\n const TWO_PI = 6.283185307179586;\n const TWO_PI_DETUNE = 6.284702653297906;\n const pd = ~~parameters.preDelay[0], bw = parameters.bandwidth[0], fi = parameters.inputDiffusion1[0], si = parameters.inputDiffusion2[0], dc = parameters.decay[0], ft = parameters.decayDiffusion1[0], st = parameters.decayDiffusion2[0], dp = 1 - parameters.damping[0], ex = parameters.excursionRate[0] / sampleRate, ed = parameters.excursionDepth[0] * sampleRate / 1e3, we = parameters.wet[0] * .6, dr = parameters.dry[0];\n if (inputs[0].length == 2) for (let i = 127; i >= 0; i--) {\n this._preDelay[this._pDWrite + i] = (inputs[0][0][i] + inputs[0][1][i]) * .5;\n outputs[0][0][i] = inputs[0][0][i] * dr;\n outputs[0][1][i] = inputs[0][1][i] * dr;\n }\n else if (inputs[0].length > 0) {\n this._preDelay.set(inputs[0][0], this._pDWrite);\n for (let i = 127; i >= 0; i--) outputs[0][0][i] = outputs[0][1][i] = inputs[0][0][i] * dr;\n } else this._preDelay.set(/* @__PURE__ */ new Float32Array(128), this._pDWrite);\n let i = 0;\n while (i < 128) {\n let lo = 0, ro = 0;\n this._lp1 += bw * (this._preDelay[(this._pDLength + this._pDWrite - pd + i) % this._pDLength] - this._lp1);\n let pre = this.writeDelay(0, this._lp1 - fi * this.readDelay(0));\n pre = this.writeDelay(1, fi * (pre - this.readDelay(1)) + this.readDelay(0));\n pre = this.writeDelay(2, fi * pre + this.readDelay(1) - si * this.readDelay(2));\n pre = this.writeDelay(3, si * (pre - this.readDelay(3)) + this.readDelay(2));\n let split = si * pre + this.readDelay(3);\n let exc = ed * (1 + Math.cos(this._excPhase * TWO_PI));\n let exc2 = ed * (1 + Math.sin(this._excPhase * TWO_PI_DETUNE));\n let temp = this.writeDelay(4, split + dc * this.readDelay(11) + ft * this.readDelayCAt(4, exc));\n this.writeDelay(5, this.readDelayCAt(4, exc) - ft * temp);\n this._lp2 += dp * (this.readDelay(5) - this._lp2);\n temp = this.writeDelay(6, dc * this._lp2 - st * this.readDelay(6));\n this.writeDelay(7, this.readDelay(6) + st * temp);\n temp = this.writeDelay(8, split + dc * this.readDelay(7) + ft * this.readDelayCAt(8, exc2));\n this.writeDelay(9, this.readDelayCAt(8, exc2) - ft * temp);\n this._lp3 += dp * (this.readDelay(9) - this._lp3);\n temp = this.writeDelay(10, dc * this._lp3 - st * this.readDelay(10));\n this.writeDelay(11, this.readDelay(10) + st * temp);\n lo = this.readDelayAt(9, this._taps[0]) + this.readDelayAt(9, this._taps[1]) - this.readDelayAt(10, this._taps[2]) + this.readDelayAt(11, this._taps[3]) - this.readDelayAt(5, this._taps[4]) - this.readDelayAt(6, this._taps[5]) - this.readDelayAt(7, this._taps[6]);\n ro = this.readDelayAt(5, this._taps[7]) + this.readDelayAt(5, this._taps[8]) - this.readDelayAt(6, this._taps[9]) + this.readDelayAt(7, this._taps[10]) - this.readDelayAt(9, this._taps[11]) - this.readDelayAt(10, this._taps[12]) - this.readDelayAt(11, this._taps[13]);\n outputs[0][0][i] += lo * we;\n outputs[0][1][i] += ro * we;\n this._excPhase += ex;\n if (this._excPhase >= 1) this._excPhase -= 1;\n i++;\n const delays = this._Delays;\n for (let j = 0; j < delays.length; j++) {\n const d = delays[j];\n d[1] = d[1] + 1 & d[3];\n d[2] = d[2] + 1 & d[3];\n }\n }\n this._pDWrite = (this._pDWrite + 128) % this._pDLength;\n return true;\n }\n};\nregisterProcessor(\"dattorro-reverb-processor\", DattorroReverb);\n//#endregion\n//#region src/worklets/processors/distortion/distortion-processor.js\nvar Distortion = class {\n constructor() {\n this.limitingMode = \"hard-clipping\";\n }\n applyDrive(sample, driveAmount) {\n if (driveAmount <= 0) return sample;\n return sample * (1 + driveAmount * 3);\n }\n applyClipping(sample, clippingAmount, clipThreshold) {\n if (clippingAmount <= 0) return sample;\n let clippedSample;\n switch (this.limitingMode) {\n case \"soft-clipping\":\n clippedSample = clipThreshold * Math.tanh(sample / clipThreshold);\n break;\n case \"hard-clipping\":\n clippedSample = Math.max(-clipThreshold, Math.min(clipThreshold, sample));\n break;\n default: clippedSample = sample;\n }\n if (clipThreshold < .08) {\n const makeupGain = Math.min(2, Math.pow(.1 / clipThreshold, .5));\n clippedSample *= makeupGain;\n }\n return sample * (1 - clippingAmount) + clippedSample * clippingAmount;\n }\n setLimitingMode(mode) {\n this.limitingMode = mode;\n }\n};\nregisterProcessor(\"distortion-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"distortionDrive\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n {\n name: \"clippingAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n {\n name: \"clippingThreshold\",\n defaultValue: .5,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.distortion = new Distortion();\n this.setupMessageHandling();\n this.port.postMessage({ type: \"initialized\" });\n }\n setupMessageHandling() {\n this.port.onmessage = (event) => {\n switch (event.data.type) {\n case \"setLimitingMode\":\n this.distortion.setLimitingMode(event.data.mode);\n break;\n default: console.warn(\"distortion-processor: Unsupported message\");\n }\n };\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output) return true;\n const clipThreshold = parameters.clippingThreshold[0];\n for (let i = 0; i < output[0].length; ++i) {\n const distortionDrive = parameters.distortionDrive[Math.min(i, parameters.distortionDrive.length - 1)];\n const clippingAmount = parameters.clippingAmount[Math.min(i, parameters.clippingAmount.length - 1)];\n for (let c = 0; c < Math.min(input.length, output.length); c++) {\n let sample = input[c][i];\n sample = this.distortion.applyDrive(sample, distortionDrive);\n sample = this.distortion.applyClipping(sample, clippingAmount, clipThreshold);\n output[c][i] = Math.max(-.999, Math.min(.999, sample));\n }\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/follower/envelope-follower-processor.js\nregisterProcessor(\"envelope-follower-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"inputGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 10,\n automationRate: \"k-rate\"\n },\n {\n name: \"outputGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 10,\n automationRate: \"k-rate\"\n },\n {\n name: \"attack\",\n defaultValue: .003,\n minValue: .001,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"release\",\n defaultValue: .05,\n minValue: .001,\n maxValue: 5,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.envelope = 0;\n this.gateThreshold = .005;\n this.debugCounter = 0;\n this.port.postMessage({ type: \"initialized\" });\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n const channel = inputs[0][0];\n if (!input || !output || !channel || input.length === 0 || output.length === 0 || channel.length === 0) return true;\n const inChannel = input[0];\n if (!inChannel || inChannel.length === 0) return true;\n const attack = parameters.attack[0];\n const release = parameters.release[0];\n const inputGain = parameters.inputGain[0];\n const outputGain = parameters.outputGain[0];\n const attackCoeff = Math.exp(-1 / (attack * sampleRate));\n const releaseCoeff = Math.exp(-1 / (release * sampleRate));\n for (let sample = 0; sample < output[0].length; sample++) {\n const inputLevel = Math.abs((input[0][sample] || 0) * inputGain);\n if (inputLevel > 1e-6) {\n if (inputLevel > this.envelope) this.envelope = inputLevel + (this.envelope - inputLevel) * attackCoeff;\n else this.envelope = inputLevel + (this.envelope - inputLevel) * releaseCoeff;\n } else this.envelope *= releaseCoeff;\n if (this.envelope < this.gateThreshold) this.envelope = 0;\n const finalOutput = this.envelope * outputGain;\n for (let channel = 0; channel < output.length; channel++) output[channel][sample] = finalOutput;\n }\n return true;\n }\n});\n//#endregion\n", Tt = !1;
4650
4684
  async function Et(e) {
4651
4685
  if (Tt) return console.info("AudioWorklet processors already initialized, skipping"), {
4652
4686
  success: !0,
@@ -4799,9 +4833,7 @@ var Q = {
4799
4833
  startThreshold: -30,
4800
4834
  autoStop: !1,
4801
4835
  stopThreshold: -40,
4802
- silenceTimeoutMs: 1e3,
4803
- preprocess: !1,
4804
- preprocessOptions: {}
4836
+ silenceTimeoutMs: 1e3
4805
4837
  }, At = class {
4806
4838
  nodeId;
4807
4839
  nodeType = "recorder";
@@ -4899,8 +4931,8 @@ var Q = {
4899
4931
  if (this.#a === Q.ARMED) throw this.cancel(), Error("Recording was armed but never triggered");
4900
4932
  if (this.#a !== Q.RECORDING) throw Error("Not recording");
4901
4933
  this.#y();
4902
- let e = await this.#x(), t = await this.#S(e), n;
4903
- return this.#u?.preprocess && (n = await Je(this.#e, t, this.#u.preprocessOptions), t = n.audiobuffer), this.#i && await this.#i.loadSample(t), this.#a = Q.STOPPED, console.info(`Recorder state: ${this.#a}`), this.sendMessage("record:stop", { duration: t.duration }), this.#b(), t;
4934
+ let e = await this.#x(), t = await this.#S(e);
4935
+ return this.#i && await this.#i.loadSample(t), this.#a = Q.STOPPED, console.info(`Recorder state: ${this.#a}`), this.sendMessage("record:stop", { duration: t.duration }), this.#b(), t;
4904
4936
  }
4905
4937
  #x() {
4906
4938
  return new Promise((e) => {