@kidlib/web-audio 0.1.2 → 0.1.3
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/README.md +2 -0
- package/dist/index.d.ts +22 -3
- package/dist/index.js +202 -167
- package/dist/processors/processors.js +20 -20
- package/package.json +22 -19
package/dist/index.js
CHANGED
|
@@ -3331,7 +3331,7 @@ var Z = {
|
|
|
3331
3331
|
async init() {
|
|
3332
3332
|
return this.#t ||= (async () => {
|
|
3333
3333
|
try {
|
|
3334
|
-
this.#m && this.#T(), this.#o = new ht(this.context), this.#a = new GainNode(this.context, { gain: 1 }), this.#
|
|
3334
|
+
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
3335
|
} catch (e) {
|
|
3336
3336
|
throw this.dispose(), this.#t = null, e;
|
|
3337
3337
|
}
|
|
@@ -3364,30 +3364,29 @@ var Z = {
|
|
|
3364
3364
|
});
|
|
3365
3365
|
this.#s.set("filter-env", t);
|
|
3366
3366
|
}
|
|
3367
|
-
this.#
|
|
3367
|
+
this.#N();
|
|
3368
3368
|
}
|
|
3369
3369
|
async loadBuffer(e, t) {
|
|
3370
|
-
|
|
3371
|
-
|
|
3370
|
+
return this.loadLayers([e], t);
|
|
3371
|
+
}
|
|
3372
|
+
async loadLayers(e, t) {
|
|
3373
|
+
this.#c = Z.NOT_READY;
|
|
3374
|
+
let n = e.filter((e) => e.sampleRate === this.context.sampleRate || (console.warn(`Sample rate mismatch - buffer: ${e.sampleRate}, context: ${this.context.sampleRate}`), !1));
|
|
3375
|
+
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;
|
|
3376
|
+
let r = n.map((e) => Array.from({ length: e.numberOfChannels }, (t, n) => e.getChannelData(n)));
|
|
3372
3377
|
return this.sendToProcessor({
|
|
3373
|
-
type: "voice:
|
|
3374
|
-
|
|
3375
|
-
durationSeconds:
|
|
3376
|
-
}), t?.length &&
|
|
3378
|
+
type: "voice:setLayers",
|
|
3379
|
+
layers: r,
|
|
3380
|
+
durationSeconds: n[0].duration
|
|
3381
|
+
}), t?.length && this.sendToProcessor({
|
|
3377
3382
|
type: "voice:setZeroCrossings",
|
|
3378
3383
|
zeroCrossings: t
|
|
3379
|
-
})
|
|
3384
|
+
}), !0;
|
|
3380
3385
|
}
|
|
3381
3386
|
freeze(e) {
|
|
3382
3387
|
return console.info(`SampleVoice: freeze(${e}) called.
|
|
3383
3388
|
Spectral freeze not implemented yet`), this;
|
|
3384
3389
|
}
|
|
3385
|
-
#D(e) {
|
|
3386
|
-
return this.sendToProcessor({
|
|
3387
|
-
type: "voice:set_zero_crossings",
|
|
3388
|
-
zeroCrossings: e
|
|
3389
|
-
}), this;
|
|
3390
|
-
}
|
|
3391
3390
|
setGlideTime(e) {
|
|
3392
3391
|
this.#p = e;
|
|
3393
3392
|
}
|
|
@@ -3396,7 +3395,7 @@ var Z = {
|
|
|
3396
3395
|
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
3396
|
this.#c = Z.PLAYING, this.#d = i, this.#u = t;
|
|
3398
3397
|
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.#
|
|
3398
|
+
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
3399
|
let e = this.getParam("playbackRate");
|
|
3401
3400
|
s > 0 && e.setValueAtTime(s, i), this.getParam("playbackRate").setTargetAtTime(o, i, a);
|
|
3402
3401
|
} else this.setParam("playbackRate", o, i);
|
|
@@ -3451,8 +3450,8 @@ var Z = {
|
|
|
3451
3450
|
}
|
|
3452
3451
|
});
|
|
3453
3452
|
}
|
|
3453
|
+
#D = null;
|
|
3454
3454
|
#O = null;
|
|
3455
|
-
#k = null;
|
|
3456
3455
|
release({ releaseTime: e = this.releaseTime, secondsFromNow: t = 0 }) {
|
|
3457
3456
|
if (this.#c === Z.RELEASING) return this;
|
|
3458
3457
|
if (!this.getParam("envGain")) throw Error("Cannot release - envGain parameter is null");
|
|
@@ -3472,11 +3471,11 @@ var Z = {
|
|
|
3472
3471
|
timestamp: n
|
|
3473
3472
|
});
|
|
3474
3473
|
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.#
|
|
3474
|
+
return this.#D && clearTimeout(this.#D), this.#D = setTimeout(() => {
|
|
3476
3475
|
try {
|
|
3477
3476
|
(this.#c === Z.RELEASING || this.#c === Z.PLAYING) && this.stop();
|
|
3478
3477
|
} finally {
|
|
3479
|
-
this.#
|
|
3478
|
+
this.#D = null;
|
|
3480
3479
|
}
|
|
3481
3480
|
}, a * 1e3 + 50), this;
|
|
3482
3481
|
}
|
|
@@ -3484,35 +3483,35 @@ var Z = {
|
|
|
3484
3483
|
if (this.#c === Z.STOPPED || this.#c === Z.STOPPING) return this;
|
|
3485
3484
|
this.#c = Z.STOPPING;
|
|
3486
3485
|
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.#
|
|
3486
|
+
return r && (T(r, n), r.linearRampToValueAtTime(0, n + t)), this.#O && clearTimeout(this.#O), this.#O = setTimeout(() => {
|
|
3488
3487
|
this.sendToProcessor({
|
|
3489
3488
|
type: "voice:stop",
|
|
3490
3489
|
timestamp: n
|
|
3491
|
-
}), this.#
|
|
3490
|
+
}), this.#O = null;
|
|
3492
3491
|
}, Math.max(0, (n + t - this.now) * 1e3)), this;
|
|
3493
3492
|
}
|
|
3494
|
-
#
|
|
3493
|
+
#k(e, t = this.now, n = {}) {
|
|
3495
3494
|
if (!this.#u || !this.#g || this.#C <= 0) return;
|
|
3496
3495
|
let r = this.#g.frequency, { glideTime: i = 0, cancelPrevious: a = !0 } = n || {};
|
|
3497
3496
|
a && r.cancelScheduledValues(t);
|
|
3498
3497
|
let o = O(this.#v * e * this.#C, 20, this.context.sampleRate / 2 - 1e3);
|
|
3499
3498
|
i > 0 ? r.setTargetAtTime(o, t, i) : r.setValueAtTime(o, Math.max(t, this.now + .001));
|
|
3500
3499
|
}
|
|
3501
|
-
#
|
|
3500
|
+
#A(e, t = this.now, n = {}) {
|
|
3502
3501
|
if (!this.#u || !this.#_ || this.#S <= 0) return;
|
|
3503
3502
|
let r = this.#_.frequency, { glideTime: i = 0, cancelPrevious: a = !0 } = n || {};
|
|
3504
3503
|
a && r.cancelScheduledValues(t);
|
|
3505
3504
|
let o = O(this.#b * e * this.#S, 20, this.context.sampleRate / 2 - 1e3);
|
|
3506
3505
|
i > 0 ? r.setTargetAtTime(o, t, i) : r.setValueAtTime(o, Math.max(t, this.now + .001));
|
|
3507
3506
|
}
|
|
3508
|
-
#
|
|
3507
|
+
#j(e = 0, t = "square", n = {}) {
|
|
3509
3508
|
if (this.#i === null) {
|
|
3510
3509
|
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
3510
|
else throw console.error("Missing gain node for AM-LFO in SampleVoice"), Error("Missing gain node for AM-LFO in SampleVoice");
|
|
3512
3511
|
} else console.debug("setupAmpModLFO: LFO already setup: ", this.#i);
|
|
3513
3512
|
return this;
|
|
3514
3513
|
}
|
|
3515
|
-
#
|
|
3514
|
+
#M() {
|
|
3516
3515
|
if (this.#i) return this.#i.dispose(), this.#i = null, this;
|
|
3517
3516
|
}
|
|
3518
3517
|
setModulationAmount(e, t) {
|
|
@@ -3520,10 +3519,10 @@ var Z = {
|
|
|
3520
3519
|
warn: !0,
|
|
3521
3520
|
name: "sampleVoice.setModulationAmount"
|
|
3522
3521
|
});
|
|
3523
|
-
return e === "AM" ? (this.#i || this.#
|
|
3522
|
+
return e === "AM" ? (this.#i || this.#j(n), this.#i?.setDepth(n)) : e === "FM" && console.warn("SampleVoice: FM modulation not implemented yet"), this;
|
|
3524
3523
|
}
|
|
3525
3524
|
setModulationWaveform(e = "AM", t = "triangle", n = {}) {
|
|
3526
|
-
return e === "AM" ? (this.#i || this.#
|
|
3525
|
+
return e === "AM" ? (this.#i || this.#j(), this.#i?.setWaveform(t, n)) : e === "FM" && console.info("SampleVoice: FM modulation not implemented yet"), this;
|
|
3527
3526
|
}
|
|
3528
3527
|
enableEnvelope = (e) => {
|
|
3529
3528
|
this.#s.get(e)?.enable();
|
|
@@ -3612,14 +3611,14 @@ var Z = {
|
|
|
3612
3611
|
disablePitch = () => {
|
|
3613
3612
|
this.#h = !0;
|
|
3614
3613
|
let e = this.now, t = .1;
|
|
3615
|
-
this.getParam("playbackRate")?.linearRampToValueAtTime(1, e + t), this.#
|
|
3614
|
+
this.getParam("playbackRate")?.linearRampToValueAtTime(1, e + t), this.#k(1, e, { glideTime: t }), this.#A(1, e, { glideTime: t });
|
|
3616
3615
|
};
|
|
3617
3616
|
enablePitch = () => {
|
|
3618
3617
|
this.#h = !1;
|
|
3619
3618
|
let e = this.now, t = .1;
|
|
3620
3619
|
if (this.#u) {
|
|
3621
3620
|
let n = v(this.#u);
|
|
3622
|
-
this.getParam("playbackRate")?.linearRampToValueAtTime(n, this.context.currentTime + .01), this.#
|
|
3621
|
+
this.getParam("playbackRate")?.linearRampToValueAtTime(n, this.context.currentTime + .01), this.#k(n, e, { glideTime: t }), this.#A(n, e, { glideTime: t });
|
|
3623
3622
|
}
|
|
3624
3623
|
};
|
|
3625
3624
|
connect(e, t, n) {
|
|
@@ -3637,7 +3636,7 @@ var Z = {
|
|
|
3637
3636
|
sendUpstreamMessage(e, t) {
|
|
3638
3637
|
return this.#e.sendMessage(e, t), this;
|
|
3639
3638
|
}
|
|
3640
|
-
#
|
|
3639
|
+
#N() {
|
|
3641
3640
|
this.#s.forEach((e, t) => {
|
|
3642
3641
|
this.#e.forwardFrom(e, [
|
|
3643
3642
|
`${t}:trigger`,
|
|
@@ -3651,7 +3650,7 @@ var Z = {
|
|
|
3651
3650
|
}));
|
|
3652
3651
|
});
|
|
3653
3652
|
}
|
|
3654
|
-
#
|
|
3653
|
+
#P() {
|
|
3655
3654
|
this.#r.port.onmessage = (e) => {
|
|
3656
3655
|
let { type: t, ...n } = e.data;
|
|
3657
3656
|
switch (t) {
|
|
@@ -3775,14 +3774,14 @@ var Z = {
|
|
|
3775
3774
|
setEnvelopeLoop = (e, t, n = "normal") => (this.#s.get(e)?.setLoopEnabled(t, n), this);
|
|
3776
3775
|
syncEnvelopeToPlaybackRate = (e, t) => (this.#s.get(e)?.syncToPlaybackRate(t), this);
|
|
3777
3776
|
setPlaybackRate(e, t = this.now, n) {
|
|
3778
|
-
return this.setParam("playbackRate", e, t, n), this.#
|
|
3777
|
+
return this.setParam("playbackRate", e, t, n), this.#k(e, t, n), this.#A(e, t, n), this;
|
|
3779
3778
|
}
|
|
3780
3779
|
setHpfCutoff(e, t = this.now, n = {}) {
|
|
3781
3780
|
let r = O(e, 20, this.context.sampleRate / 2 - 1e3);
|
|
3782
3781
|
if (this.#v = r, this.#g) {
|
|
3783
3782
|
this.setParam("hpf", r, t, { glideTime: 0 });
|
|
3784
3783
|
let e = this.getParam("playbackRate")?.value ?? 1;
|
|
3785
|
-
this.#
|
|
3784
|
+
this.#k(e, t, n);
|
|
3786
3785
|
}
|
|
3787
3786
|
return this;
|
|
3788
3787
|
}
|
|
@@ -3794,7 +3793,7 @@ var Z = {
|
|
|
3794
3793
|
cancelPrevious: !0
|
|
3795
3794
|
});
|
|
3796
3795
|
let e = this.getParam("playbackRate")?.value ?? 1;
|
|
3797
|
-
this.#
|
|
3796
|
+
this.#A(e, t, n);
|
|
3798
3797
|
}
|
|
3799
3798
|
return this;
|
|
3800
3799
|
}
|
|
@@ -3838,7 +3837,7 @@ var Z = {
|
|
|
3838
3837
|
`);
|
|
3839
3838
|
}
|
|
3840
3839
|
dispose() {
|
|
3841
|
-
this.stop(), this.disconnect(), this.#
|
|
3840
|
+
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
3841
|
}
|
|
3843
3842
|
getParam(e) {
|
|
3844
3843
|
if (this.#r && this.#r.parameters.has(e)) return this.#r.parameters.get(e) ?? null;
|
|
@@ -3959,8 +3958,8 @@ var xt = class {
|
|
|
3959
3958
|
type: "sample:loaded"
|
|
3960
3959
|
} : null) : e);
|
|
3961
3960
|
}
|
|
3962
|
-
|
|
3963
|
-
return this.#o.clear(), this.#a.forEach((n) => n.
|
|
3961
|
+
setLayers(e, t) {
|
|
3962
|
+
return this.#o.clear(), this.#a.forEach((n) => n.loadLayers(e, t)), this;
|
|
3964
3963
|
}
|
|
3965
3964
|
allocate(e = this.#s, t = this.#l) {
|
|
3966
3965
|
let n;
|
|
@@ -4064,7 +4063,7 @@ async function St(e, t) {
|
|
|
4064
4063
|
}
|
|
4065
4064
|
//#endregion
|
|
4066
4065
|
//#region src/nodes/instruments/Sample/SamplePlayer.ts
|
|
4067
|
-
var Ct = class {
|
|
4066
|
+
var Ct = class e {
|
|
4068
4067
|
nodeId;
|
|
4069
4068
|
nodeType = "sample-player";
|
|
4070
4069
|
context;
|
|
@@ -4077,52 +4076,54 @@ var Ct = class {
|
|
|
4077
4076
|
#o = /* @__PURE__ */ new Set();
|
|
4078
4077
|
#s = /* @__PURE__ */ new Set();
|
|
4079
4078
|
#c = null;
|
|
4080
|
-
#l =
|
|
4081
|
-
|
|
4079
|
+
#l = [];
|
|
4080
|
+
static MAX_LAYERS = 4;
|
|
4081
|
+
#u = 0;
|
|
4082
4082
|
#d = !1;
|
|
4083
4083
|
#f = !1;
|
|
4084
4084
|
#p = !1;
|
|
4085
4085
|
#m = !1;
|
|
4086
|
-
#h;
|
|
4086
|
+
#h = !1;
|
|
4087
4087
|
#g;
|
|
4088
4088
|
#_;
|
|
4089
|
-
#v
|
|
4089
|
+
#v;
|
|
4090
4090
|
#y = null;
|
|
4091
|
-
#b =
|
|
4092
|
-
#x =
|
|
4093
|
-
#S =
|
|
4094
|
-
#C = q.
|
|
4095
|
-
#w = q.
|
|
4096
|
-
#T = q.
|
|
4097
|
-
#E = q.
|
|
4098
|
-
#D =
|
|
4099
|
-
#O =
|
|
4100
|
-
#k =
|
|
4101
|
-
#A =
|
|
4091
|
+
#b = null;
|
|
4092
|
+
#x = 0;
|
|
4093
|
+
#S = 120;
|
|
4094
|
+
#C = q.glide.defaultValue;
|
|
4095
|
+
#w = q.loopRampDuration.defaultValue;
|
|
4096
|
+
#T = q.keytrackLoop.defaultValue;
|
|
4097
|
+
#E = q.highpassFilter.defaultValue;
|
|
4098
|
+
#D = q.lowpassFilter.defaultValue;
|
|
4099
|
+
#O = !1;
|
|
4100
|
+
#k = 300;
|
|
4101
|
+
#A = 20;
|
|
4102
4102
|
#j = !1;
|
|
4103
|
-
#M =
|
|
4104
|
-
#N =
|
|
4103
|
+
#M = !1;
|
|
4104
|
+
#N = [];
|
|
4105
4105
|
#P = !0;
|
|
4106
|
+
#F = !0;
|
|
4106
4107
|
randomizeVelocity = !1;
|
|
4107
4108
|
voicePool;
|
|
4108
4109
|
outBus;
|
|
4109
|
-
#
|
|
4110
|
+
#I = /* @__PURE__ */ new Set();
|
|
4110
4111
|
constructor(e, t = 16, n) {
|
|
4111
|
-
this.nodeId = z("sample-player", this), this.context = e, this.#e = V(this.nodeId), this.#
|
|
4112
|
+
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
4113
|
}
|
|
4113
4114
|
async init() {
|
|
4114
4115
|
if (!this.#t) return this.#n ||= (async () => {
|
|
4115
4116
|
try {
|
|
4116
|
-
this.outBus = await _t(this.context), this.voicePool = await St(this.context, this.#i), this.#
|
|
4117
|
+
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
4118
|
} catch (e) {
|
|
4118
|
-
this.voicePool?.dispose(), this.#
|
|
4119
|
+
this.voicePool?.dispose(), this.#_?.dispose(), this.#v?.dispose();
|
|
4119
4120
|
let t = e instanceof Error ? e.message : String(e);
|
|
4120
4121
|
throw Error(`Failed to initialize SamplePlayer: ${t}`);
|
|
4121
4122
|
}
|
|
4122
4123
|
})(), this.#n;
|
|
4123
4124
|
}
|
|
4124
|
-
#
|
|
4125
|
-
this.voicePool.connect(this.outBus.input), this.outBus.connect(this.#
|
|
4125
|
+
#L() {
|
|
4126
|
+
this.voicePool.connect(this.outBus.input), this.outBus.connect(this.#g), this.#g.connect(this.context.destination);
|
|
4126
4127
|
}
|
|
4127
4128
|
onMessage(e, t) {
|
|
4128
4129
|
return this.#e.onMessage(e, t);
|
|
@@ -4132,13 +4133,13 @@ var Ct = class {
|
|
|
4132
4133
|
}
|
|
4133
4134
|
connect(e) {
|
|
4134
4135
|
let t = "input" in e && e.input ? e.input : e;
|
|
4135
|
-
this.#
|
|
4136
|
+
this.#g.connect(t), "nodeId" in e && (this.#o.add(e.nodeId), e.addIncoming?.(this.nodeId));
|
|
4136
4137
|
}
|
|
4137
4138
|
disconnect(e) {
|
|
4138
4139
|
if (e) {
|
|
4139
4140
|
let t = "input" in e ? e.input : e;
|
|
4140
|
-
this.#
|
|
4141
|
-
} else this.#
|
|
4141
|
+
this.#g.disconnect(t), "nodeId" in e && (this.#o.delete(e.nodeId), e.removeIncoming?.(this.nodeId));
|
|
4142
|
+
} else this.#g.disconnect(), this.#o.clear();
|
|
4142
4143
|
}
|
|
4143
4144
|
addIncoming(e) {
|
|
4144
4145
|
this.#s.add(e.nodeId);
|
|
@@ -4153,13 +4154,13 @@ var Ct = class {
|
|
|
4153
4154
|
};
|
|
4154
4155
|
}
|
|
4155
4156
|
get audioNode() {
|
|
4156
|
-
return this.#
|
|
4157
|
+
return this.#g;
|
|
4157
4158
|
}
|
|
4158
4159
|
get input() {
|
|
4159
4160
|
return this.outBus.input;
|
|
4160
4161
|
}
|
|
4161
4162
|
get output() {
|
|
4162
|
-
return this.#
|
|
4163
|
+
return this.#g;
|
|
4163
4164
|
}
|
|
4164
4165
|
get now() {
|
|
4165
4166
|
return this.context.currentTime;
|
|
@@ -4167,7 +4168,7 @@ var Ct = class {
|
|
|
4167
4168
|
get initialized() {
|
|
4168
4169
|
return this.#t;
|
|
4169
4170
|
}
|
|
4170
|
-
#
|
|
4171
|
+
#R() {
|
|
4171
4172
|
return this.voicePool.onMessage("sample:loaded", (e) => {
|
|
4172
4173
|
this.#r = !0;
|
|
4173
4174
|
}), this.voicePool.onMessage("voice-pool:initialized", () => {
|
|
@@ -4194,26 +4195,26 @@ var Ct = class {
|
|
|
4194
4195
|
}
|
|
4195
4196
|
getMacrosAudioParam(e) {
|
|
4196
4197
|
switch (e) {
|
|
4197
|
-
case "loopStart": return this.#
|
|
4198
|
-
case "loopEnd": return this.#
|
|
4198
|
+
case "loopStart": return this.#_.audioParam;
|
|
4199
|
+
case "loopEnd": return this.#v.audioParam;
|
|
4199
4200
|
default: throw Error(`Unknown macro parameter: ${e}`);
|
|
4200
4201
|
}
|
|
4201
4202
|
}
|
|
4202
4203
|
getMacro(e) {
|
|
4203
4204
|
switch (e) {
|
|
4204
|
-
case "loopStart": return this.#
|
|
4205
|
-
case "loopEnd": return this.#
|
|
4205
|
+
case "loopStart": return this.#_;
|
|
4206
|
+
case "loopEnd": return this.#v;
|
|
4206
4207
|
default: throw Error(`Unknown macro parameter: ${e}`);
|
|
4207
4208
|
}
|
|
4208
4209
|
}
|
|
4209
|
-
#
|
|
4210
|
+
#z() {
|
|
4210
4211
|
return this.voicePool.allVoices.forEach((e, t) => {
|
|
4211
4212
|
let n = e.getParam("loopStart"), r = e.getParam("loopEnd");
|
|
4212
|
-
n ? this.#
|
|
4213
|
+
n ? this.#_.addTarget(n, "loopStart") : console.error("loopStart param is null!"), r ? this.#v.addTarget(r, "loopEnd") : console.error("loopEnd param is null!");
|
|
4213
4214
|
}), this;
|
|
4214
4215
|
}
|
|
4215
|
-
#
|
|
4216
|
-
return this.#
|
|
4216
|
+
#B() {
|
|
4217
|
+
return this.#_.setValue(0), this.#v.setValue(this.#u), this;
|
|
4217
4218
|
}
|
|
4218
4219
|
setModulationAmount = (e, t) => this.voicePool.applyToAllVoices((n) => n.setModulationAmount(e, t));
|
|
4219
4220
|
setModulationWaveform(e = "AM", t = "triangle", n = {}) {
|
|
@@ -4221,14 +4222,6 @@ var Ct = class {
|
|
|
4221
4222
|
}
|
|
4222
4223
|
syncLFOsToNoteFreq(e, t) {
|
|
4223
4224
|
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
4225
|
if (t === !0) this.#y?.storeCurrentValues();
|
|
4233
4226
|
else {
|
|
4234
4227
|
let e = this.#y?.getStoredValues();
|
|
@@ -4236,13 +4229,21 @@ var Ct = class {
|
|
|
4236
4229
|
}
|
|
4237
4230
|
this.#j = t;
|
|
4238
4231
|
}
|
|
4232
|
+
if (e === "pitch-lfo") {
|
|
4233
|
+
if (t === !0) this.#b?.storeCurrentValues();
|
|
4234
|
+
else {
|
|
4235
|
+
let e = this.#b?.getStoredValues();
|
|
4236
|
+
e && this.#b?.setFrequency(e.rate);
|
|
4237
|
+
}
|
|
4238
|
+
this.#M = t;
|
|
4239
|
+
}
|
|
4239
4240
|
}
|
|
4240
|
-
#
|
|
4241
|
-
this.#
|
|
4242
|
-
let e = this.#
|
|
4243
|
-
this.#
|
|
4241
|
+
#V() {
|
|
4242
|
+
this.#y = new J(this.context), this.#y.setWaveform("sine"), this.#b = new J(this.context);
|
|
4243
|
+
let e = this.#b.getPitchWobbleWaveform();
|
|
4244
|
+
this.#b.setWaveform(e), this.#H(this.#b, "playbackRate"), this.#y.connect(this.outBus.input.gain);
|
|
4244
4245
|
}
|
|
4245
|
-
#
|
|
4246
|
+
#H(e, t) {
|
|
4246
4247
|
this.voicePool.applyToAllVoices((n) => {
|
|
4247
4248
|
let r = n.getParam(t);
|
|
4248
4249
|
r && e.connect(r);
|
|
@@ -4251,32 +4252,65 @@ var Ct = class {
|
|
|
4251
4252
|
freezeActiveVoices(e) {
|
|
4252
4253
|
return console.info(`SamplePlayer: freezeActiveVoices(${e}). Spectral freeze not implemented yet`), this;
|
|
4253
4254
|
}
|
|
4254
|
-
#
|
|
4255
|
+
#U = !1;
|
|
4255
4256
|
async loadSample(e, t, n) {
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4257
|
+
return (await this.loadLayers([e], t, n))?.[0] ?? null;
|
|
4258
|
+
}
|
|
4259
|
+
async loadLayers(t, n, r) {
|
|
4260
|
+
if (this.#U) throw Error("A sample load is already in progress");
|
|
4261
|
+
this.#U = !0;
|
|
4262
|
+
let i;
|
|
4259
4263
|
try {
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4264
|
+
t.length > e.MAX_LAYERS && (console.warn(`Ignoring layers past ${e.MAX_LAYERS}; got ${t.length}`), t = t.slice(0, e.MAX_LAYERS));
|
|
4265
|
+
let a = [];
|
|
4266
|
+
for (let [e, n] of t.entries()) {
|
|
4267
|
+
let t = n;
|
|
4268
|
+
if (t instanceof ArrayBuffer) try {
|
|
4269
|
+
t = await this.context.decodeAudioData(t.slice(0));
|
|
4270
|
+
} catch (t) {
|
|
4271
|
+
if (e === 0) throw t;
|
|
4272
|
+
console.warn(`Failed to decode layer ${e}; skipping`, t);
|
|
4273
|
+
continue;
|
|
4274
|
+
}
|
|
4275
|
+
if (!o(t)) {
|
|
4276
|
+
if (console.error(`Invalid AudioBuffer provided for layer ${e}`), e === 0) return null;
|
|
4277
|
+
continue;
|
|
4278
|
+
}
|
|
4279
|
+
if (t.sampleRate !== this.context.sampleRate) {
|
|
4280
|
+
let n = `Sample rate mismatch: layer ${e} rate ${t.sampleRate}, context rate ${this.context.sampleRate}`;
|
|
4281
|
+
if (e === 0) throw RangeError(n);
|
|
4282
|
+
console.warn(n);
|
|
4283
|
+
continue;
|
|
4284
|
+
}
|
|
4285
|
+
a.push(t);
|
|
4286
|
+
}
|
|
4287
|
+
if (!a.length) return null;
|
|
4288
|
+
n && this.context.sampleRate !== n && console.warn(`Sample rate mismatch: context rate ${this.context.sampleRate}, requested rate ${n}`);
|
|
4289
|
+
let s = [], c = [];
|
|
4290
|
+
for (let [e, t] of a.entries()) {
|
|
4291
|
+
if (!this.#F) {
|
|
4292
|
+
s.push(t);
|
|
4293
|
+
continue;
|
|
4294
|
+
}
|
|
4295
|
+
let n = await Je(this.context, t, r);
|
|
4296
|
+
s.push(n.audiobuffer), e === 0 && this.#P && n.zeroCrossings && (c = n.zeroCrossings);
|
|
4297
|
+
}
|
|
4298
|
+
this.releaseAll(0), this.transposeSemitones = 0, this.#r = !1, this.#l = s, this.#c = s[0], this.#u = s[0].duration, this.#N = c;
|
|
4299
|
+
let l = new Promise((e) => {
|
|
4300
|
+
i = this.voicePool.onMessage("sample:loaded", () => {
|
|
4267
4301
|
e();
|
|
4268
4302
|
});
|
|
4269
4303
|
});
|
|
4270
|
-
return this.voicePool.
|
|
4304
|
+
return this.voicePool.setLayers(s, c), this.#B(), this.setScale({
|
|
4271
4305
|
rootNote: "C",
|
|
4272
4306
|
scale: [0],
|
|
4273
4307
|
lowestOctave: 0,
|
|
4274
4308
|
highestOctave: 5,
|
|
4275
4309
|
tuningOffset: 0,
|
|
4276
4310
|
normalize: !1
|
|
4277
|
-
}), await
|
|
4311
|
+
}), await l, [...s];
|
|
4278
4312
|
} finally {
|
|
4279
|
-
|
|
4313
|
+
i?.(), this.#U = !1;
|
|
4280
4314
|
}
|
|
4281
4315
|
}
|
|
4282
4316
|
async cropSample(e = this.getStartPoint(), t = this.getEndPoint(), n = 4) {
|
|
@@ -4311,39 +4345,39 @@ var Ct = class {
|
|
|
4311
4345
|
return n;
|
|
4312
4346
|
}
|
|
4313
4347
|
play(e, t = 100, n = this.getGlideTime()) {
|
|
4314
|
-
let r = he(t) ? t : 100, i = e + this.#
|
|
4315
|
-
return he(i) ? (this.#
|
|
4348
|
+
let r = he(t) ? t : 100, i = e + this.#x;
|
|
4349
|
+
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
4350
|
}
|
|
4317
4351
|
release(e) {
|
|
4318
|
-
if (this.holdEnabled || this.#
|
|
4319
|
-
let t = e + this.#
|
|
4320
|
-
return this.#
|
|
4352
|
+
if (this.holdEnabled || this.#m) return this;
|
|
4353
|
+
let t = e + this.#x;
|
|
4354
|
+
return this.#h ? (this.#I.add(t), this) : (this.#I.delete(t), this.voicePool.noteOff(t), this.sendUpstreamMessage("note:off", { transposedMidiNote: t }), this);
|
|
4321
4355
|
}
|
|
4322
4356
|
releaseAll(e) {
|
|
4323
|
-
return this.#
|
|
4357
|
+
return this.#I.clear(), this.voicePool?.allNotesOff(e), this;
|
|
4324
4358
|
}
|
|
4325
4359
|
panic = (e) => this.releaseAll(e);
|
|
4326
4360
|
get transposedBySemitones() {
|
|
4327
|
-
return this.#
|
|
4361
|
+
return this.#x;
|
|
4328
4362
|
}
|
|
4329
4363
|
set transposeSemitones(e) {
|
|
4330
|
-
this.#
|
|
4364
|
+
this.#x !== e && (this.#x = e);
|
|
4331
4365
|
}
|
|
4332
4366
|
setScale(e) {
|
|
4333
|
-
return this.#
|
|
4334
|
-
snapToZeroCrossings: this.#
|
|
4367
|
+
return this.#_.setScale({
|
|
4368
|
+
snapToZeroCrossings: this.#N,
|
|
4335
4369
|
...e
|
|
4336
|
-
}), this.#
|
|
4337
|
-
snapToZeroCrossings: this.#
|
|
4370
|
+
}), this.#v.setScale({
|
|
4371
|
+
snapToZeroCrossings: this.#N,
|
|
4338
4372
|
...e
|
|
4339
4373
|
}), this;
|
|
4340
4374
|
}
|
|
4341
4375
|
setRootNote(e) {
|
|
4342
4376
|
let t = b[e], n = t === 0 ? 0 : t - 12;
|
|
4343
|
-
return this.transposedBySemitones === n ? this : (this.transposeSemitones = n, this.#
|
|
4377
|
+
return this.transposedBySemitones === n ? this : (this.transposeSemitones = n, this.#v.setRootNote(e), this.#_.setRootNote(e), this);
|
|
4344
4378
|
}
|
|
4345
4379
|
setVolume(e) {
|
|
4346
|
-
return e = O(e, 0, 1), this.#
|
|
4380
|
+
return e = O(e, 0, 1), this.#g.gain.setValueAtTime(e, this.now), this;
|
|
4347
4381
|
}
|
|
4348
4382
|
setSampleStartPoint(e) {
|
|
4349
4383
|
return this.voicePool.applyToAllVoices((t) => t.setStartPoint(e)), this.sendUpstreamMessage("start-point:updated", { startPoint: e }), this;
|
|
@@ -4352,29 +4386,29 @@ var Ct = class {
|
|
|
4352
4386
|
return this.voicePool.applyToAllVoices((t) => t.setEndPoint(e)), this.sendUpstreamMessage("end-point:updated", { endPoint: e }), this;
|
|
4353
4387
|
}
|
|
4354
4388
|
setLoopRampDuration(e) {
|
|
4355
|
-
return this.#
|
|
4389
|
+
return this.#w = e, this;
|
|
4356
4390
|
}
|
|
4357
4391
|
setGlideTime(e) {
|
|
4358
|
-
return this.#
|
|
4392
|
+
return this.#C = e, this;
|
|
4359
4393
|
}
|
|
4360
4394
|
setLoopEnabled(e) {
|
|
4361
|
-
return this.#
|
|
4395
|
+
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
4396
|
}
|
|
4363
4397
|
setLoopLocked(e) {
|
|
4364
|
-
return this.#
|
|
4398
|
+
return this.#f === e ? this : (this.#f = e, this.setLoopEnabled(e), this.sendUpstreamMessage("loop:locked", { locked: e }), this);
|
|
4365
4399
|
}
|
|
4366
4400
|
setHoldEnabled(e) {
|
|
4367
|
-
return this.#
|
|
4401
|
+
return this.#p === e || this.#m && !e ? this : (this.#p = e, e || this.releaseAll(.1), this.sendUpstreamMessage("hold:enabled", { enabled: e }), this);
|
|
4368
4402
|
}
|
|
4369
4403
|
setHoldLocked(e) {
|
|
4370
|
-
return this.#
|
|
4404
|
+
return this.#m === e ? this : (this.#m = e, e === !1 && this.releaseAll(), this.sendUpstreamMessage("hold:locked", { locked: e }), this);
|
|
4371
4405
|
}
|
|
4372
|
-
#
|
|
4406
|
+
#W = !1;
|
|
4373
4407
|
setSustainPedal(e) {
|
|
4374
|
-
if (this.#
|
|
4375
|
-
if (this.#
|
|
4376
|
-
for (let e of this.#
|
|
4377
|
-
this.#
|
|
4408
|
+
if (this.#h === e) return this;
|
|
4409
|
+
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) {
|
|
4410
|
+
for (let e of this.#I) this.voicePool.noteOff(e), this.sendUpstreamMessage("note:off", { transposedMidiNote: e });
|
|
4411
|
+
this.#I.clear();
|
|
4378
4412
|
}
|
|
4379
4413
|
return this;
|
|
4380
4414
|
}
|
|
@@ -4395,37 +4429,37 @@ var Ct = class {
|
|
|
4395
4429
|
setLoopDuration = (e, t = this.getLoopRampDuration()) => this.setLoopPoint("end", this.loopStart, this.loopStart + e, t);
|
|
4396
4430
|
debugcounter = 0;
|
|
4397
4431
|
setTempo(e) {
|
|
4398
|
-
if (!(e < this.#
|
|
4432
|
+
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
4433
|
}
|
|
4400
4434
|
syncLoopToTempo(e) {
|
|
4401
4435
|
return this.voicePool.applyToAllVoices((t) => t.syncLoopToTempo(e)), this;
|
|
4402
4436
|
}
|
|
4403
4437
|
setKeytrackLoopAmount(e) {
|
|
4404
4438
|
let t = Math.max(0, Math.min(1, e));
|
|
4405
|
-
return this.#
|
|
4439
|
+
return this.#T = t, this.voicePool.applyToAllVoices((e) => e.setKeytrackLoopAmount(t)), this;
|
|
4406
4440
|
}
|
|
4407
|
-
getKeytrackLoopAmount = () => this.#
|
|
4441
|
+
getKeytrackLoopAmount = () => this.#T;
|
|
4408
4442
|
get tempo() {
|
|
4409
|
-
return this.#
|
|
4443
|
+
return this.#S;
|
|
4410
4444
|
}
|
|
4411
4445
|
setLoopPoint(e, t, n, r = this.getLoopRampDuration()) {
|
|
4412
4446
|
let i = e === "start" ? O(t, this.MIN_LOOP_DURATION_SECONDS / 2, n) : t;
|
|
4413
4447
|
if (e === "start" && i === this.loopStart) return this;
|
|
4414
|
-
let a = O(n, i, this.#
|
|
4448
|
+
let a = O(n, i, this.#u - this.MIN_LOOP_DURATION_SECONDS / 2);
|
|
4415
4449
|
if (e === "end" && a === this.loopEnd) return this;
|
|
4416
4450
|
let o = a - i, s = r * 1;
|
|
4417
4451
|
if (e === "start" && i !== this.loopStart) {
|
|
4418
|
-
if (this.#
|
|
4419
|
-
let e = 60 / this.#
|
|
4452
|
+
if (this.#O) {
|
|
4453
|
+
let e = 60 / this.#S, t = Math.round(o / e);
|
|
4420
4454
|
i = a - t * e;
|
|
4421
4455
|
}
|
|
4422
|
-
o < this.MIN_LOOP_DURATION_SECONDS && (i = a - this.MIN_LOOP_DURATION_SECONDS), this.#
|
|
4456
|
+
o < this.MIN_LOOP_DURATION_SECONDS && (i = a - this.MIN_LOOP_DURATION_SECONDS), this.#_.ramp(i, s, a);
|
|
4423
4457
|
} else if (e === "end" && a !== this.loopEnd) {
|
|
4424
|
-
if (this.#
|
|
4425
|
-
let e = 60 / this.#
|
|
4458
|
+
if (this.#O) {
|
|
4459
|
+
let e = 60 / this.#S, t = Math.round(o / e);
|
|
4426
4460
|
a = i + t * e;
|
|
4427
4461
|
}
|
|
4428
|
-
o < this.MIN_LOOP_DURATION_SECONDS && (a = i + this.MIN_LOOP_DURATION_SECONDS), this.#
|
|
4462
|
+
o < this.MIN_LOOP_DURATION_SECONDS && (a = i + this.MIN_LOOP_DURATION_SECONDS), this.#v.ramp(a, s, i);
|
|
4429
4463
|
}
|
|
4430
4464
|
return this.sendUpstreamMessage("loop-points:updated", {
|
|
4431
4465
|
loopStart: this.loopStart,
|
|
@@ -4434,7 +4468,7 @@ var Ct = class {
|
|
|
4434
4468
|
}
|
|
4435
4469
|
scrollLoopPoints(e, t) {
|
|
4436
4470
|
let n = this.context.currentTime;
|
|
4437
|
-
return this.#
|
|
4471
|
+
return this.#_.setValue(e, n), this.#v.setValue(t, n), this.sendUpstreamMessage("loop-points:updated", {
|
|
4438
4472
|
loopStart: this.loopStart,
|
|
4439
4473
|
loopEnd: this.loopEnd
|
|
4440
4474
|
}), this;
|
|
@@ -4471,8 +4505,8 @@ var Ct = class {
|
|
|
4471
4505
|
}
|
|
4472
4506
|
getAudioParam(e) {
|
|
4473
4507
|
switch (e) {
|
|
4474
|
-
case "loopStart": return this.#
|
|
4475
|
-
case "loopEnd": return this.#
|
|
4508
|
+
case "loopStart": return this.#_.audioParam;
|
|
4509
|
+
case "loopEnd": return this.#v.audioParam;
|
|
4476
4510
|
default: return console.warn(`Parameter '${e}' not found on SamplePlayer`), null;
|
|
4477
4511
|
}
|
|
4478
4512
|
}
|
|
@@ -4483,13 +4517,13 @@ var Ct = class {
|
|
|
4483
4517
|
return this.voicePool?.allVoices[0]?.endPoint ?? this.sampleDuration;
|
|
4484
4518
|
}
|
|
4485
4519
|
getLoopRampDuration() {
|
|
4486
|
-
return this.#
|
|
4520
|
+
return this.#w;
|
|
4487
4521
|
}
|
|
4488
4522
|
getGlideTime() {
|
|
4489
|
-
return this.#
|
|
4523
|
+
return this.#C;
|
|
4490
4524
|
}
|
|
4491
|
-
getHpfCutoff = () => this.#
|
|
4492
|
-
getLpfCutoff = () => this.#
|
|
4525
|
+
getHpfCutoff = () => this.#E;
|
|
4526
|
+
getLpfCutoff = () => this.#D;
|
|
4493
4527
|
getParameterValue(e) {
|
|
4494
4528
|
switch (e) {
|
|
4495
4529
|
case "loopStart": return this.loopStart;
|
|
@@ -4554,12 +4588,12 @@ var Ct = class {
|
|
|
4554
4588
|
this.outBus.setSendAmount(e, t);
|
|
4555
4589
|
};
|
|
4556
4590
|
setLpfCutoff = (e, t = "pre") => {
|
|
4557
|
-
this.#
|
|
4591
|
+
this.#D = e, t === "pre" ? this.voicePool.applyToAllVoices((t) => {
|
|
4558
4592
|
t.setLpfCutoff(e);
|
|
4559
4593
|
}) : t === "post" && this.outBus.setLpfCutoff(e);
|
|
4560
4594
|
};
|
|
4561
4595
|
setHpfCutoff = (e, t = "pre") => {
|
|
4562
|
-
this.#
|
|
4596
|
+
this.#E = e, t === "pre" ? this.voicePool.applyToAllVoices((t) => {
|
|
4563
4597
|
t.setHpfCutoff(e);
|
|
4564
4598
|
}) : t === "post" && this.outBus.setHpfCutoff(e);
|
|
4565
4599
|
};
|
|
@@ -4577,13 +4611,13 @@ var Ct = class {
|
|
|
4577
4611
|
});
|
|
4578
4612
|
}
|
|
4579
4613
|
setFeedbackAmount = (e) => {
|
|
4580
|
-
e = O(e, 0, 1), (this.#
|
|
4614
|
+
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
4615
|
t.feedback?.setAmountMacro(e);
|
|
4582
4616
|
});
|
|
4583
4617
|
};
|
|
4584
|
-
#
|
|
4618
|
+
#G = "monophonic";
|
|
4585
4619
|
setFeedbackMode(e) {
|
|
4586
|
-
if (this.#
|
|
4620
|
+
if (this.#G = e, e === "monophonic") {
|
|
4587
4621
|
let e = this.voicePool.allVoices[0].feedback?.currentAmount ?? 0;
|
|
4588
4622
|
this.voicePool.applyToAllVoices((e) => {
|
|
4589
4623
|
e.feedback?.setAmountMacro(0);
|
|
@@ -4601,37 +4635,37 @@ var Ct = class {
|
|
|
4601
4635
|
});
|
|
4602
4636
|
}
|
|
4603
4637
|
get mainOut() {
|
|
4604
|
-
return this.#
|
|
4638
|
+
return this.#g;
|
|
4605
4639
|
}
|
|
4606
4640
|
get outputBus() {
|
|
4607
4641
|
return this.outBus;
|
|
4608
4642
|
}
|
|
4609
4643
|
get sampleDuration() {
|
|
4610
|
-
return this.#
|
|
4644
|
+
return this.#u;
|
|
4611
4645
|
}
|
|
4612
4646
|
get volume() {
|
|
4613
|
-
return this.#
|
|
4647
|
+
return this.#g.gain.value;
|
|
4614
4648
|
}
|
|
4615
4649
|
set volume(e) {
|
|
4616
|
-
this.#
|
|
4650
|
+
this.#g.gain.setValueAtTime(e, this.context.currentTime);
|
|
4617
4651
|
}
|
|
4618
4652
|
get loopEnabled() {
|
|
4619
|
-
return this.#
|
|
4653
|
+
return this.#d;
|
|
4620
4654
|
}
|
|
4621
4655
|
get holdEnabled() {
|
|
4622
|
-
return this.#
|
|
4656
|
+
return this.#p;
|
|
4623
4657
|
}
|
|
4624
4658
|
get gainLFO() {
|
|
4625
|
-
return this.#
|
|
4659
|
+
return this.#y;
|
|
4626
4660
|
}
|
|
4627
4661
|
get pitchLFO() {
|
|
4628
|
-
return this.#
|
|
4662
|
+
return this.#b;
|
|
4629
4663
|
}
|
|
4630
4664
|
get loopStart() {
|
|
4631
|
-
return this.#
|
|
4665
|
+
return this.#_.targetValue;
|
|
4632
4666
|
}
|
|
4633
4667
|
get loopEnd() {
|
|
4634
|
-
return this.#
|
|
4668
|
+
return this.#v.targetValue;
|
|
4635
4669
|
}
|
|
4636
4670
|
get isLoaded() {
|
|
4637
4671
|
return this.#r;
|
|
@@ -4639,14 +4673,17 @@ var Ct = class {
|
|
|
4639
4673
|
get audiobuffer() {
|
|
4640
4674
|
return this.#c;
|
|
4641
4675
|
}
|
|
4676
|
+
get layers() {
|
|
4677
|
+
return [...this.#l];
|
|
4678
|
+
}
|
|
4642
4679
|
dispose() {
|
|
4643
4680
|
try {
|
|
4644
|
-
this.releaseAll(), this.#
|
|
4681
|
+
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
4682
|
} catch (e) {
|
|
4646
4683
|
console.error(`Error disposing Sampler ${this.nodeId}:`, e);
|
|
4647
4684
|
}
|
|
4648
4685
|
}
|
|
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;
|
|
4686
|
+
}, 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
4687
|
async function Et(e) {
|
|
4651
4688
|
if (Tt) return console.info("AudioWorklet processors already initialized, skipping"), {
|
|
4652
4689
|
success: !0,
|
|
@@ -4799,9 +4836,7 @@ var Q = {
|
|
|
4799
4836
|
startThreshold: -30,
|
|
4800
4837
|
autoStop: !1,
|
|
4801
4838
|
stopThreshold: -40,
|
|
4802
|
-
silenceTimeoutMs: 1e3
|
|
4803
|
-
preprocess: !1,
|
|
4804
|
-
preprocessOptions: {}
|
|
4839
|
+
silenceTimeoutMs: 1e3
|
|
4805
4840
|
}, At = class {
|
|
4806
4841
|
nodeId;
|
|
4807
4842
|
nodeType = "recorder";
|
|
@@ -4899,8 +4934,8 @@ var Q = {
|
|
|
4899
4934
|
if (this.#a === Q.ARMED) throw this.cancel(), Error("Recording was armed but never triggered");
|
|
4900
4935
|
if (this.#a !== Q.RECORDING) throw Error("Not recording");
|
|
4901
4936
|
this.#y();
|
|
4902
|
-
let e = await this.#x(), t = await this.#S(e)
|
|
4903
|
-
return this.#
|
|
4937
|
+
let e = await this.#x(), t = await this.#S(e);
|
|
4938
|
+
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
4939
|
}
|
|
4905
4940
|
#x() {
|
|
4906
4941
|
return new Promise((e) => {
|