@kidlib/web-audio 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components.d.ts +3 -0
- package/dist/components.js +52 -29
- package/dist/index.d.ts +5 -3
- package/dist/index.js +20 -20
- package/dist/processors/processors.js +2 -20
- package/package.json +3 -3
package/dist/components.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare class KnobElement extends HTMLElement {
|
|
|
31
31
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
|
|
32
32
|
private injectGlobalStyles;
|
|
33
33
|
private updateConfigFromAttributes;
|
|
34
|
+
private updateAccessibility;
|
|
34
35
|
private updateDimensions;
|
|
35
36
|
private updateColorFromAttribute;
|
|
36
37
|
private render;
|
|
@@ -39,10 +40,12 @@ export declare class KnobElement extends HTMLElement {
|
|
|
39
40
|
private dragHandlers?;
|
|
40
41
|
private lastClickTime;
|
|
41
42
|
private readonly DOUBLE_CLICK_THRESHOLD;
|
|
43
|
+
private handleKeyDown;
|
|
42
44
|
private createDraggable;
|
|
43
45
|
private updateBorder;
|
|
44
46
|
private dispatchChangeEvent;
|
|
45
47
|
setValue(value: number): void;
|
|
48
|
+
private updateValue;
|
|
46
49
|
/**
|
|
47
50
|
* Sets the knob value using a normalized 0-1 input, automatically handling
|
|
48
51
|
* the knob's range and curve transformation.
|
package/dist/components.js
CHANGED
|
@@ -46,7 +46,7 @@ var e = class e extends HTMLElement {
|
|
|
46
46
|
super();
|
|
47
47
|
}
|
|
48
48
|
connectedCallback() {
|
|
49
|
-
this.injectGlobalStyles(), this.createUtilityFunctions(), this.render(), this.updateColorFromAttribute(), this.setValue(this.config.defaultValue ?? this.config.minValue), this.createDraggable();
|
|
49
|
+
this.injectGlobalStyles(), this.createUtilityFunctions(), this.render(), this.updateColorFromAttribute(), this.updateAccessibility(), this.setValue(this.config.defaultValue ?? this.config.minValue), this.createDraggable(), this.addEventListener("keydown", this.handleKeyDown);
|
|
50
50
|
}
|
|
51
51
|
disconnectedCallback() {
|
|
52
52
|
this.cleanup();
|
|
@@ -74,7 +74,7 @@ var e = class e extends HTMLElement {
|
|
|
74
74
|
injectGlobalStyles() {
|
|
75
75
|
if (e.stylesInjected) return;
|
|
76
76
|
let t = document.createElement("style");
|
|
77
|
-
t.id = "knob-element-styles", t.textContent = "\n knob-element {\n display: block;\n box-sizing: border-box;\n --knob-size: 120px;\n --knob-stroke: rgb(234, 234, 234);\n\n width: var(--knob-size, 120px); \n height: var(--knob-size, 120px);\n\n touch-action: none; /* Prevents browser touch gestures */\n user-select: none; /* Prevents text selection during drag */\n border-radius: 50%;\n cursor: grab;\n }\n \n knob-element[disabled] {\n opacity: 0.5;\n pointer-events: none; \n }\n \n knob-element:active {\n cursor: grabbing;\n }\n ", document.head.appendChild(t), e.stylesInjected = !0;
|
|
77
|
+
t.id = "knob-element-styles", t.textContent = "\n knob-element {\n display: block;\n box-sizing: border-box;\n --knob-size: 120px;\n --knob-stroke: rgb(234, 234, 234);\n\n width: var(--knob-size, 120px); \n height: var(--knob-size, 120px);\n\n touch-action: none; /* Prevents browser touch gestures */\n user-select: none; /* Prevents text selection during drag */\n border-radius: 50%;\n cursor: grab;\n }\n \n knob-element[disabled] {\n opacity: 0.5;\n pointer-events: none; \n }\n \n knob-element:active {\n cursor: grabbing;\n }\n\n knob-element:focus-visible {\n outline: 1px solid rgb(255 255 255 / 45%);\n outline-offset: 4px;\n }\n ", document.head.appendChild(t), e.stylesInjected = !0;
|
|
78
78
|
}
|
|
79
79
|
updateConfigFromAttributes() {
|
|
80
80
|
let e = (e, t) => {
|
|
@@ -105,7 +105,10 @@ var e = class e extends HTMLElement {
|
|
|
105
105
|
allowedValues: r ? [...r].sort((e, t) => e - t) : void 0,
|
|
106
106
|
snapThresholds: n("snap-thresholds"),
|
|
107
107
|
disabled: this.hasAttribute("disabled")
|
|
108
|
-
}, this.updateDimensions();
|
|
108
|
+
}, this.updateDimensions(), this.updateAccessibility();
|
|
109
|
+
}
|
|
110
|
+
updateAccessibility() {
|
|
111
|
+
this.setAttribute("role", "slider"), this.setAttribute("aria-valuemin", String(this.config.minValue)), this.setAttribute("aria-valuemax", String(this.config.maxValue)), this.setAttribute("aria-valuenow", String(this.currentValue)), this.setAttribute("aria-disabled", String(!!this.config.disabled)), this.tabIndex = this.config.disabled ? -1 : 0;
|
|
109
112
|
}
|
|
110
113
|
updateDimensions() {
|
|
111
114
|
let e = this.getAttribute("width"), t = this.getAttribute("height");
|
|
@@ -122,7 +125,7 @@ var e = class e extends HTMLElement {
|
|
|
122
125
|
this.innerHTML = "\n <svg class=\"ac-knob\" width=\"100%\" height=\"100%\" viewBox=\"0 0 100 100\">\n <path class=\"knob-path\" \n fill=\"none\" \n stroke=\"var(--knob-stroke)\" \n stroke-width=\"5\" \n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M50,50 L50,2\"\n />\n </svg>\n ", this.pathElement = this.querySelector(".knob-path");
|
|
123
126
|
}
|
|
124
127
|
cleanup() {
|
|
125
|
-
this.dragHandlers && (this.removeEventListener("mousedown", this.dragHandlers.start), this.removeEventListener("touchstart", this.dragHandlers.start), document.removeEventListener("mousemove", this.dragHandlers.move), document.removeEventListener("mouseup", this.dragHandlers.end), document.removeEventListener("touchmove", this.dragHandlers.move), document.removeEventListener("touchend", this.dragHandlers.end));
|
|
128
|
+
this.removeEventListener("keydown", this.handleKeyDown), this.dragHandlers && (this.removeEventListener("mousedown", this.dragHandlers.start), this.removeEventListener("touchstart", this.dragHandlers.start), document.removeEventListener("mousemove", this.dragHandlers.move), document.removeEventListener("mouseup", this.dragHandlers.end), document.removeEventListener("touchmove", this.dragHandlers.move), document.removeEventListener("touchend", this.dragHandlers.end));
|
|
126
129
|
}
|
|
127
130
|
createUtilityFunctions() {
|
|
128
131
|
let t = this.config.curve || 1;
|
|
@@ -148,39 +151,56 @@ var e = class e extends HTMLElement {
|
|
|
148
151
|
dragHandlers;
|
|
149
152
|
lastClickTime = 0;
|
|
150
153
|
DOUBLE_CLICK_THRESHOLD = 300;
|
|
154
|
+
handleKeyDown = (e) => {
|
|
155
|
+
if (this.config.disabled) return;
|
|
156
|
+
let t = e.key === "ArrowUp" || e.key === "ArrowRight" ? 1 : e.key === "ArrowDown" || e.key === "ArrowLeft" ? -1 : 0, n;
|
|
157
|
+
if (e.key === "Home") n = this.config.minValue;
|
|
158
|
+
else if (e.key === "End") n = this.config.maxValue;
|
|
159
|
+
else if (t && this.config.allowedValues?.length) {
|
|
160
|
+
let e = this.config.allowedValues;
|
|
161
|
+
n = t > 0 ? e.find((e) => e > this.currentValue) ?? e[e.length - 1] : [...e].reverse().find((e) => e < this.currentValue) ?? e[0];
|
|
162
|
+
} else if (t) {
|
|
163
|
+
let e = this.config.maxValue - this.config.minValue, r = this.hasAttribute("snap-increment") && this.config.snapIncrement > 0, i = r ? this.config.snapIncrement : e * .01, a = this.currentValue + t * i;
|
|
164
|
+
n = r ? this.applySnapping(a) : a;
|
|
165
|
+
} else return;
|
|
166
|
+
e.preventDefault(), e.stopPropagation(), this.updateValue(n, "user");
|
|
167
|
+
};
|
|
151
168
|
createDraggable() {
|
|
152
|
-
let t = "pointerLockElement" in document && "requestPointerLock" in HTMLElement.prototype, n = !1, r = 0, i = 0, a = 0, o = !1,
|
|
153
|
-
r = e.clientY,
|
|
154
|
-
|
|
169
|
+
let t = "pointerLockElement" in document && "requestPointerLock" in HTMLElement.prototype, n = !1, r = 0, i = 0, a = 0, o = 0, s = !1, c = !1, l = (e) => {
|
|
170
|
+
r = e.clientY, i = r, s = document.pointerLockElement === this, !(!t || document.pointerLockElement) && this.requestPointerLock().then(() => {
|
|
171
|
+
s = document.pointerLockElement === this;
|
|
155
172
|
}, () => {
|
|
156
|
-
|
|
173
|
+
s = !1;
|
|
157
174
|
});
|
|
158
|
-
},
|
|
175
|
+
}, u = (e) => {
|
|
159
176
|
if (this.config.disabled) return;
|
|
160
|
-
let t = Date.now(),
|
|
161
|
-
if (
|
|
162
|
-
this.
|
|
177
|
+
let t = Date.now(), u = t - this.lastClickTime;
|
|
178
|
+
if (u < this.DOUBLE_CLICK_THRESHOLD && u > 0) {
|
|
179
|
+
this.updateValue(this.config.defaultValue, "user");
|
|
163
180
|
return;
|
|
164
181
|
}
|
|
165
|
-
this.lastClickTime = t, n = !0,
|
|
166
|
-
},
|
|
182
|
+
this.lastClickTime = t, n = !0, a = this.currentRotation, o = 0, c = "shiftKey" in e && e.shiftKey, "touches" in e ? (r = e.touches[0].clientY, i = r, s = !1) : l(e);
|
|
183
|
+
}, d = (t) => {
|
|
167
184
|
if (!n) return;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
let e = "touches" in t ? t.touches[0].clientY : t.clientY;
|
|
172
|
-
s = (r - e) * 2;
|
|
185
|
+
if (this.config.disabled) {
|
|
186
|
+
f();
|
|
187
|
+
return;
|
|
173
188
|
}
|
|
174
|
-
let
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
let l = "touches" in t ? t.touches[0].clientY : t.clientY, u = "shiftKey" in t && t.shiftKey;
|
|
190
|
+
u !== c && (c = u, a = this.currentRotation, r = i, o = 0);
|
|
191
|
+
let d, p = c ? .2 : 2;
|
|
192
|
+
s && document.pointerLockElement ? (o += t.movementY, d = -o * p) : d = (r - l) * p, i = l;
|
|
193
|
+
let m = a + d, h = e.clamp(m, this.config.minRotation, this.config.maxRotation), g = this.rotationToValue(h), _ = this.applySnapping(g);
|
|
194
|
+
this.currentValue = _, this.setAttribute("aria-valuenow", String(this.currentValue)), this.currentRotation = _ === g ? h : this.valueToRotation(_), this.updateBorder(), this.dispatchChangeEvent("user"), t.preventDefault();
|
|
178
195
|
};
|
|
196
|
+
function f() {
|
|
197
|
+
n = !1, s && document.pointerLockElement && document.exitPointerLock(), s = !1;
|
|
198
|
+
}
|
|
179
199
|
this.dragHandlers = {
|
|
180
|
-
start:
|
|
181
|
-
move:
|
|
182
|
-
end:
|
|
183
|
-
}, this.addEventListener("mousedown",
|
|
200
|
+
start: u,
|
|
201
|
+
move: d,
|
|
202
|
+
end: f
|
|
203
|
+
}, this.addEventListener("mousedown", u), this.addEventListener("touchstart", u, { passive: !1 }), document.addEventListener("mousemove", d), document.addEventListener("mouseup", f), document.addEventListener("touchmove", d, { passive: !1 }), document.addEventListener("touchend", f);
|
|
184
204
|
}
|
|
185
205
|
updateBorder() {
|
|
186
206
|
if (this.pathElement) {
|
|
@@ -202,8 +222,11 @@ var e = class e extends HTMLElement {
|
|
|
202
222
|
});
|
|
203
223
|
this.dispatchEvent(r);
|
|
204
224
|
}
|
|
205
|
-
setValue(
|
|
206
|
-
|
|
225
|
+
setValue(e) {
|
|
226
|
+
this.updateValue(e, "programmatic");
|
|
227
|
+
}
|
|
228
|
+
updateValue(t, n) {
|
|
229
|
+
!this.valueToRotation || !this.pathElement || (this.currentValue = e.clamp(t, this.config.minValue, this.config.maxValue), this.currentRotation = this.valueToRotation(this.currentValue), this.updateBorder(), this.setAttribute("aria-valuenow", String(this.currentValue)), this.dispatchChangeEvent(n));
|
|
207
230
|
}
|
|
208
231
|
setValueNormalized(e) {
|
|
209
232
|
let { minRotation: t, maxRotation: n } = this.config, r = t + Math.max(0, Math.min(1, e)) * (n - t), i = this.rotationToValue(r);
|
package/dist/index.d.ts
CHANGED
|
@@ -559,6 +559,8 @@ export declare type KeymapKey = "piano" | "major" | "minor" | "pentatonic" | "ch
|
|
|
559
559
|
|
|
560
560
|
export declare const keymaps: Record<KeymapKey, KeyMap>;
|
|
561
561
|
|
|
562
|
+
declare type KnownNodeType = NativeAudioNode | BaseNodeType | InstrumentType | VoiceType | ContainerType | CustomFxType;
|
|
563
|
+
|
|
562
564
|
declare class LFO {
|
|
563
565
|
#private;
|
|
564
566
|
constructor(context: AudioContext);
|
|
@@ -667,7 +669,7 @@ declare class MacroParam {
|
|
|
667
669
|
protected sendMessage(type: string, data: any): void;
|
|
668
670
|
dispose(): void;
|
|
669
671
|
connect(target: AudioParam, nodeType: NodeType, scaleFactor?: number): this;
|
|
670
|
-
disconnect(
|
|
672
|
+
disconnect(_target?: AudioParam): void;
|
|
671
673
|
}
|
|
672
674
|
|
|
673
675
|
declare interface Message {
|
|
@@ -682,7 +684,7 @@ declare type NativeAudioNode = "AudioWorkletNode" | "GainNode" | "BiquadFilterNo
|
|
|
682
684
|
|
|
683
685
|
declare type NodeID_2 = string;
|
|
684
686
|
|
|
685
|
-
declare type NodeType =
|
|
687
|
+
declare type NodeType = KnownNodeType | (string & Record<never, never>);
|
|
686
688
|
|
|
687
689
|
declare type NormalizeOptions = {
|
|
688
690
|
from: [number, number];
|
|
@@ -1232,7 +1234,7 @@ declare class ValueSnapper {
|
|
|
1232
1234
|
setScale(rootNote: keyof typeof ROOT_NOTES, scalePattern: readonly number[] | number[], tuningOffset: number | undefined, // in semitones
|
|
1233
1235
|
lowestOctave: number | undefined, highestOctave: number | undefined, normalize: NormalizeOptions | false, snapToZeroCrossings?: number[] | false): number[];
|
|
1234
1236
|
setRootNote(rootNote: keyof typeof ROOT_NOTES): void;
|
|
1235
|
-
setAllowedPeriods(periods: number[], normalize: NormalizeOptions | false,
|
|
1237
|
+
setAllowedPeriods(periods: number[], normalize: NormalizeOptions | false, _snapToZeroCrossings?: number[] | false, _direction?: "left" | "right" | "any"): number[];
|
|
1236
1238
|
snapToValue(target: number, allowedValues?: number[], tolerance?: number, preferDirection?: "left" | "right" | "any"): number;
|
|
1237
1239
|
snapToMusicalPeriod(targetPeriod: number, allowedPeriods?: number[]): number;
|
|
1238
1240
|
setAllowedValues(values: number[], normalize: NormalizeOptions | false): number[];
|
package/dist/index.js
CHANGED
|
@@ -246,7 +246,7 @@ function m(e, t, n = {}) {
|
|
|
246
246
|
seed: n.seed
|
|
247
247
|
});
|
|
248
248
|
case "custom-function": return ie(e, n.waveFunction || ((e) => Math.sin(e)), { harmonics: n.harmonics });
|
|
249
|
-
default: throw Error(
|
|
249
|
+
default: throw Error("Invalid waveform type");
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
function h(e, t = {}) {
|
|
@@ -786,12 +786,13 @@ function Pe(e, t) {
|
|
|
786
786
|
return r.min + a * (r.max - r.min);
|
|
787
787
|
}
|
|
788
788
|
function Fe(e, t) {
|
|
789
|
-
let { inputRange: n, outputRange: r, blend: i = 1,
|
|
790
|
-
(e > n.max || e < n.min) && console.warn("
|
|
791
|
-
let
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
789
|
+
let { inputRange: n, outputRange: r, blend: i = 1, curve: a = "linear" } = t;
|
|
790
|
+
(e > n.max || e < n.min) && console.warn("interpolateLinearToGeometric: Value outside of input range, will be clamped"), r.min <= 0 && console.warn("interpolateLinearToGeometric: Output min must be > 0 for geometric interpolation");
|
|
791
|
+
let o = (Math.max(n.min, Math.min(e, n.max)) - n.min) / (n.max - n.min), s = Math.max(0, Math.min(i, 1)), c = typeof a == "number" ? a : a === "smooth" ? 2 : a === "steep" ? 3 : a === "gentle" ? 1.5 : 1;
|
|
792
|
+
if (c !== 1 && (o **= 1 / c), o === 0) return r.min;
|
|
793
|
+
if (o === 1) return r.max;
|
|
794
|
+
let l = r.min + o * (r.max - r.min), u = Math.log(r.min), d = Math.exp(u + o * (Math.log(r.max) - u));
|
|
795
|
+
return (1 - s) * l + s * d;
|
|
795
796
|
}
|
|
796
797
|
//#endregion
|
|
797
798
|
//#region src/utils/code/set-utils.ts
|
|
@@ -1989,7 +1990,7 @@ var nt = class {
|
|
|
1989
1990
|
return this.#t.setAllowedPeriods(e, t, n);
|
|
1990
1991
|
}
|
|
1991
1992
|
setScale(e) {
|
|
1992
|
-
let { rootNote: t, scale: n, tuningOffset: r
|
|
1993
|
+
let { rootNote: t, scale: n, tuningOffset: r, lowestOctave: i, highestOctave: a } = e, o = Array.isArray(n) ? n : xe[n];
|
|
1993
1994
|
return this.#t.setScale(t, o, r, i, a, e.normalize, e.snapToZeroCrossings);
|
|
1994
1995
|
}
|
|
1995
1996
|
setValue(e, t) {
|
|
@@ -2735,8 +2736,8 @@ var ht = class {
|
|
|
2735
2736
|
this.#s = t, this.#e = !0;
|
|
2736
2737
|
}
|
|
2737
2738
|
trigger(e, t = {}) {
|
|
2738
|
-
let { secondsFromNow: n = 0, cents: r = 0,
|
|
2739
|
-
return this.setPitch(e, r,
|
|
2739
|
+
let { secondsFromNow: n = 0, cents: r = 0, glideTime: i = 0, triggerDecay: a = !0 } = t, o = this.now + n;
|
|
2740
|
+
return this.setPitch(e, r, o, i), a && this.#_(), this;
|
|
2740
2741
|
}
|
|
2741
2742
|
#g = 0;
|
|
2742
2743
|
setAmountMacro(e) {
|
|
@@ -3388,7 +3389,7 @@ var Z = {
|
|
|
3388
3389
|
this.#p = e;
|
|
3389
3390
|
}
|
|
3390
3391
|
trigger(e) {
|
|
3391
|
-
let { midiNote: t
|
|
3392
|
+
let { midiNote: t, velocity: n, secondsFromNow: r = 0 } = e, i = this.now + r;
|
|
3392
3393
|
if (this.#c === Z.PLAYING || this.#c === Z.RELEASING) return console.log(`had to stop a playing voice, midinote: ${t}`), this.stop(i), null;
|
|
3393
3394
|
this.#c = Z.PLAYING, this.#d = i, this.#u = t;
|
|
3394
3395
|
let a = (e.glide?.glideTime ?? this.#p) / 8, o = 1, s = 1;
|
|
@@ -3619,7 +3620,7 @@ var Z = {
|
|
|
3619
3620
|
}
|
|
3620
3621
|
};
|
|
3621
3622
|
connect(e, t, n) {
|
|
3622
|
-
return e instanceof Y ? this.out.connect(e.input, t) : e instanceof AudioParam ? this.out.connect(e, t) : e instanceof AudioNode ? this.out.connect(e, t, n) : console.warn(
|
|
3623
|
+
return e instanceof Y ? this.out.connect(e.input, t) : e instanceof AudioParam ? this.out.connect(e, t) : e instanceof AudioNode ? this.out.connect(e, t, n) : console.warn("SampleVoice: Unsupported destination", e), e;
|
|
3623
3624
|
}
|
|
3624
3625
|
disconnect(e = "main", t) {
|
|
3625
3626
|
return e === "alt" ? (console.warn("SampleVoice has no \"alt\" output to disconnect"), this) : (t ? (t instanceof AudioNode || t instanceof AudioParam) && this.out.disconnect(t) : this.out.disconnect(), this);
|
|
@@ -3812,7 +3813,6 @@ var Z = {
|
|
|
3812
3813
|
max: 1
|
|
3813
3814
|
},
|
|
3814
3815
|
blend: 1,
|
|
3815
|
-
logBase: "dB",
|
|
3816
3816
|
curve: "linear"
|
|
3817
3817
|
});
|
|
3818
3818
|
return this.setParam("loopDurationDriftAmount", t, this.now), this;
|
|
@@ -4166,7 +4166,7 @@ var Ct = class e {
|
|
|
4166
4166
|
return this.#t;
|
|
4167
4167
|
}
|
|
4168
4168
|
#R() {
|
|
4169
|
-
return this.voicePool.onMessage("sample:loaded", (
|
|
4169
|
+
return this.voicePool.onMessage("sample:loaded", () => {
|
|
4170
4170
|
this.#r = !0;
|
|
4171
4171
|
}), this.voicePool.onMessage("voice-pool:initialized", () => {
|
|
4172
4172
|
this.sendUpstreamMessage("sample-player:initialized", {});
|
|
@@ -4194,20 +4194,20 @@ var Ct = class e {
|
|
|
4194
4194
|
switch (e) {
|
|
4195
4195
|
case "loopStart": return this.#_.audioParam;
|
|
4196
4196
|
case "loopEnd": return this.#v.audioParam;
|
|
4197
|
-
default: throw Error(
|
|
4197
|
+
default: throw Error("Unknown macro parameter");
|
|
4198
4198
|
}
|
|
4199
4199
|
}
|
|
4200
4200
|
getMacro(e) {
|
|
4201
4201
|
switch (e) {
|
|
4202
4202
|
case "loopStart": return this.#_;
|
|
4203
4203
|
case "loopEnd": return this.#v;
|
|
4204
|
-
default: throw Error(
|
|
4204
|
+
default: throw Error("Unknown macro parameter");
|
|
4205
4205
|
}
|
|
4206
4206
|
}
|
|
4207
4207
|
#z() {
|
|
4208
|
-
return this.voicePool.allVoices.forEach((e
|
|
4209
|
-
let
|
|
4210
|
-
|
|
4208
|
+
return this.voicePool.allVoices.forEach((e) => {
|
|
4209
|
+
let t = e.getParam("loopStart"), n = e.getParam("loopEnd");
|
|
4210
|
+
t ? this.#_.addTarget(t, "loopStart") : console.error("loopStart param is null!"), n ? this.#v.addTarget(n, "loopEnd") : console.error("loopEnd param is null!");
|
|
4211
4211
|
}), this;
|
|
4212
4212
|
}
|
|
4213
4213
|
#B() {
|
|
@@ -4680,7 +4680,7 @@ var Ct = class e {
|
|
|
4680
4680
|
console.error(`Error disposing Sampler ${this.nodeId}:`, e);
|
|
4681
4681
|
}
|
|
4682
4682
|
}
|
|
4683
|
-
}, wt = "//#region src/utils/search/findClosest.ts\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array index of the element which value is closest to the target value\n*/\nfunction findClosestIdx(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n if (sortedArray.length === 0) throw new Error(\"Array cannot be empty\");\n if (sortedArray.length === 1) return 0;\n const targetValue = target;\n const firstValue = getValue(sortedArray[0]);\n const lastValue = getValue(sortedArray[sortedArray.length - 1]);\n if (targetValue <= firstValue) return 0;\n if (targetValue >= lastValue) return sortedArray.length - 1;\n let left = 0;\n let right = sortedArray.length - 1;\n while (left < right - 1) {\n const mid = Math.floor((left + right) / 2);\n const midValue = getValue(sortedArray[mid]);\n if (midValue === targetValue) return mid;\n else if (midValue < targetValue) left = mid;\n else right = mid;\n }\n if (direction === \"left\") return left;\n if (direction === \"right\") return right;\n return getDistance(getValue(sortedArray[left]), targetValue) <= getDistance(getValue(sortedArray[right]), targetValue) ? left : right;\n}\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array element which value is closest to the target value\n*/\nfunction findClosest(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n return sortedArray[findClosestIdx(sortedArray, target, direction, getValue, getDistance)];\n}\nvar SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS = Object.values({\n masterGain: {\n name: \"masterGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n envGain: {\n name: \"envGain\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n velocity: {\n name: \"velocity\",\n defaultValue: 100,\n minValue: 0,\n maxValue: 127,\n automationRate: \"k-rate\"\n },\n pan: {\n name: \"pan\",\n defaultValue: 0,\n minValue: -1,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n playbackRate: {\n name: \"playbackRate\",\n defaultValue: 1,\n minValue: .1,\n maxValue: 24,\n automationRate: \"a-rate\"\n },\n loopStart: {\n name: \"loopStart\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopEnd: {\n name: \"loopEnd\",\n defaultValue: 99999,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n startPoint: {\n name: \"startPoint\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n endPoint: {\n name: \"endPoint\",\n defaultValue: 9999,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n playbackPosition: {\n name: \"playbackPosition\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopDurationDriftAmount: {\n name: \"loopDurationDriftAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n maxLoopCount: {\n name: \"maxLoopCount\",\n defaultValue: 999999,\n minValue: 1,\n maxValue: 999999,\n automationRate: \"k-rate\"\n },\n tempo: {\n name: \"tempo\",\n defaultValue: 120,\n minValue: 20,\n maxValue: 300,\n automationRate: \"k-rate\"\n }\n});\n//#endregion\n//#region src/worklets/processors/play/sample-player-processor.js\nvar SamplePlayerProcessor = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS;\n }\n constructor() {\n super();\n this.layers = [];\n this.layerGain = 1;\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.usePlaybackPosition = false;\n this.enableLoopSmoothing = true;\n this.enableAdaptiveDrift = true;\n this.enableAmplitudeCompensation = true;\n this.syncLoopToTempo = false;\n this.keytrackLoopAmount = 0;\n this.durationPreservation = {\n enabled: false,\n maxDriftSamples: Math.floor(sampleRate * .04),\n timelinePosition: 0,\n resetPending: false\n };\n this.PITCH_PRESERVATION_THRESHOLD = Math.floor(sampleRate * .061);\n this.AMPLITUDE_COMPENSATION_THRESHOLD = Math.floor(sampleRate / 16.35);\n this.port.onmessage = this.#handleMessage.bind(this);\n this.#resetState();\n this.port.postMessage({ type: \"initialized\" });\n }\n /** Authority layer. All range and duration math reads through this. */\n get buffer() {\n return this.layers[0] ?? null;\n }\n #handleMessage(event) {\n const { type, value, buffer, layers, timestamp, durationSeconds, zeroCrossings, allowedPeriods, playbackDirection } = event.data;\n switch (type) {\n case \"voice:reset\":\n this.#resetState();\n this.port.postMessage({ type: \"voice:reset\" });\n break;\n case \"voice:setBuffer\":\n case \"voice:setLayers\":\n this.#resetState();\n this.zeroCrossings = [];\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.layers = (layers ?? (buffer ? [buffer] : [])).filter(Boolean);\n this.layerGain = this.layers.length ? 1 / this.layers.length : 1;\n this.port.postMessage({\n type: \"voice:loaded\",\n durationSeconds,\n time: currentTime\n });\n break;\n case \"voice:setZeroCrossings\":\n this.zeroCrossings = (zeroCrossings || []).map((timeSec) => timeSec * sampleRate);\n if (this.zeroCrossings.length > 0) {\n this.minZeroCrossing = this.zeroCrossings[0];\n this.maxZeroCrossing = this.zeroCrossings[this.zeroCrossings.length - 1];\n }\n break;\n case \"voice:start\":\n this.isReleasing = false;\n this.isPlaying = true;\n this.loopCount = 0;\n this.playbackPosition = 0;\n this.port.postMessage({\n type: \"voice:started\",\n time: timestamp || currentTime\n });\n break;\n case \"voice:release\":\n this.isReleasing = true;\n this.port.postMessage({\n type: \"voice:releasing\",\n time: currentTime\n });\n break;\n case \"voice:stop\":\n this.#stop();\n break;\n case \"setLoopEnabled\":\n this.loopEnabled = value;\n this.port.postMessage({\n type: \"loop:enabled\",\n enabled: value\n });\n break;\n case \"setPanDriftEnabled\":\n this.panDriftEnabled = value;\n break;\n case \"voice:setPlaybackDirection\": {\n const reverse = playbackDirection === \"reverse\";\n if (reverse !== this.reversePlayback && this.playbackPosition > 0) this.playbackPosition += reverse ? 1 : -1;\n this.reversePlayback = reverse;\n this.port.postMessage({\n type: \"voice:playbackDirectionChange\",\n playbackDirection\n });\n break;\n }\n case \"voice:usePlaybackPosition\":\n this.usePlaybackPosition = value;\n break;\n case \"syncLoopToTempo\":\n this.syncLoopToTempo = value;\n this.port.postMessage({\n type: \"loop:syncToTempo\",\n enabled: value\n });\n break;\n case \"setKeytrackLoopAmount\":\n this.keytrackLoopAmount = Math.max(0, Math.min(1, value));\n break;\n case \"setPreserveDuration\":\n this.durationPreservation.enabled = Boolean(value);\n this.#resetDurationPreservation(this.playbackPosition);\n }\n }\n #resetState() {\n this.isPlaying = false;\n this.isReleasing = false;\n this.loopEnabled = false;\n this.velocitySensitivity = 1;\n this.reversePlayback = false;\n this.playbackPosition = 0;\n this.debugCounter = 0;\n this.loopCount = 0;\n this.applyClickCompensation = false;\n this.loopClickCompensation = 0;\n this.driftUpdateCounter = 0;\n this.currentLoopDrift = 0;\n this.currentPanDrift = 0;\n this.panDriftEnabled = true;\n this.nextDriftGenerated = false;\n this.loopAmplitudeGain = 1;\n this.lastAnalyzedLoopStart = -1;\n this.lastAnalyzedLoopEnd = -1;\n this.#resetDurationPreservation();\n }\n #stop() {\n this.isPlaying = false;\n this.isReleasing = false;\n this.playbackPosition = 0;\n this.port.postMessage({ type: \"voice:stopped\" });\n }\n #smoothLoopWrap(lastLoopSample, newFirstSample) {\n const discontinuity = lastLoopSample - newFirstSample;\n if (this.enableLoopSmoothing && Math.abs(discontinuity) > .01) {\n this.loopClickCompensation = discontinuity * .5;\n this.compensationDecay = .9;\n this.applyClickCompensation = true;\n }\n }\n #clamp = (value, min, max) => Math.max(min, Math.min(max, value));\n #clampZeroCrossing = (value) => this.#clamp(value, this.minZeroCrossing, this.maxZeroCrossing);\n #findNearestZeroCrossing(position, direction = \"any\", maxDistance = null) {\n if (!this.zeroCrossings || this.zeroCrossings.length === 0) return position;\n const closestValue = findClosest(this.zeroCrossings, position, direction);\n if (maxDistance !== null && Math.abs(closestValue - position) > maxDistance) return position;\n return closestValue;\n }\n /**\n * Convert normalized position (0-1) to sample index\n * @param {number} normalizedPosition - Position as 0-1 value\n * @returns {number} - Sample index\n */\n #normalizedToSamples(normalizedPosition) {\n if (!this.buffer || !this.buffer[0]) return 0;\n return normalizedPosition * this.buffer[0].length;\n }\n /**\n * Convert sample index to normalized position (0-1)\n * @param {number} sampleIndex - Sample index\n * @returns {number} - Normalized position 0-1\n */\n #samplesToNormalized(sampleIndex) {\n if (!this.buffer || !this.buffer[0]) return 0;\n return sampleIndex / this.buffer[0].length;\n }\n /**\n * Convert MIDI velocity (0-127) to gain multiplier (0-1)\n * @param {number} midiVelocity - MIDI velocity 0-127\n * @returns {number} - Gain multiplier 0-1\n */\n #midiVelocityToGain(midiVelocity) {\n return Math.max(0, Math.min(1, midiVelocity / 127));\n }\n /**\n * Get buffer duration in seconds\n * @returns {number} - Buffer duration in seconds\n */\n #getBufferDurationSeconds() {\n return (this.buffer?.[0]?.length || 0) / sampleRate;\n }\n /**\n * Calculate musical note durations in samples for given tempo\n * @param {number} tempo - BPM\n * @returns {Object} - Musical note durations in samples\n */\n #getMusicalNoteDurations(tempo) {\n const beatsPerSecond = tempo / 60;\n const samplesPerBeat = sampleRate / beatsPerSecond;\n return {\n whole: samplesPerBeat * 4,\n half: samplesPerBeat * 2,\n quarter: samplesPerBeat,\n eighth: samplesPerBeat / 2,\n sixteenth: samplesPerBeat / 4,\n thirtySecond: samplesPerBeat / 8,\n quarterTriplet: samplesPerBeat * 2 / 3,\n eighthTriplet: samplesPerBeat / 2 * 2 / 3,\n sixteenthTriplet: samplesPerBeat / 4 * 2 / 3\n };\n }\n /**\n * Quantize loop duration to nearest musical interval (skips if below the smallest quantize option)\n * @param {number} loopDurationSamples - Current loop duration in samples\n * @param {number} tempo - Current tempo in BPM\n * @param {number} playbackRate - Current playback rate\n * @returns {number} - Quantized loop duration in samples\n */\n #quantizeLoopDuration(loopDurationSamples, tempo, playbackRate) {\n if (!this.syncLoopToTempo) return loopDurationSamples;\n const noteDurations = this.#getMusicalNoteDurations(tempo);\n const effectiveDuration = loopDurationSamples / Math.abs(playbackRate);\n if (effectiveDuration < noteDurations.thirtySecond) return loopDurationSamples;\n const intervals = Object.values(noteDurations);\n let closestInterval = intervals[0];\n let smallestDiff = Math.abs(effectiveDuration - closestInterval);\n for (const interval of intervals) {\n const diff = Math.abs(effectiveDuration - interval);\n if (diff < smallestDiff) {\n smallestDiff = diff;\n closestInterval = interval;\n }\n }\n return Math.floor(closestInterval * Math.abs(playbackRate));\n }\n /**\n * Extract and convert all position parameters from seconds to samples\n * @param {Object} parameters - AudioWorkletProcessor parameters\n * @returns {Object} - Converted parameters in samples\n */\n #extractPositionParams(parameters) {\n return {\n startPointSamples: Math.floor(parameters.startPoint[0] * sampleRate),\n endPointSamples: Math.floor(parameters.endPoint[0] * sampleRate),\n loopStartSamples: Math.floor(parameters.loopStart[0] * sampleRate),\n loopEndSamples: Math.floor(parameters.loopEnd[0] * sampleRate)\n };\n }\n /**\n * Calculate effective playback range in samples\n * @param {Object} params - Position parameters from #extractPositionParams\n * @returns {Object} - Effective start and end positions\n */\n #calculatePlaybackRange(params) {\n const bufferLength = this.buffer?.[0]?.length || 0;\n const start = Math.max(0, params.startPointSamples);\n const end = params.endPointSamples > start ? Math.min(bufferLength, params.endPointSamples) : bufferLength;\n const snappedStart = this.#findNearestZeroCrossing(start, \"right\");\n const snappedEnd = this.#findNearestZeroCrossing(end, \"left\");\n return {\n startSamples: snappedStart,\n endSamples: snappedEnd,\n durationSamples: snappedEnd - snappedStart\n };\n }\n /**\n * Calculate effective loop range in samples with optional drift\n * @param {Object} params - Position parameters from #extractPositionParams\n * @param {Object} playbackRange - Range from #calculatePlaybackRange\n * @param {number} driftAmount - Loop duration drift amount (0-1)\n * @param {number} tempo - Current tempo in BPM\n * @param {number} playbackRate - Current playback rate\n * @returns {Object} - Effective loop start and end positions with drift applied\n */\n #calculateLoopRange(params, playbackRange, driftAmount = 0, tempo = 120, playbackRate = 1) {\n const lpStart = params.loopStartSamples;\n const lpEnd = params.loopEndSamples;\n let calcLoopStart = lpStart < lpEnd && lpStart >= 0 ? lpStart : playbackRange.startSamples;\n let calcLoopEnd = lpEnd > lpStart && lpEnd <= playbackRange.endSamples ? lpEnd : playbackRange.endSamples;\n let baseDuration = calcLoopEnd - calcLoopStart;\n if (this.syncLoopToTempo) {\n const quantizedDuration = this.#quantizeLoopDuration(baseDuration, tempo, playbackRate);\n calcLoopEnd = calcLoopStart + quantizedDuration;\n calcLoopEnd = Math.min(calcLoopEnd, playbackRange.endSamples);\n }\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD && this.keytrackLoopAmount > 0 && !this.syncLoopToTempo) {\n const scale = 1 + this.keytrackLoopAmount * (Math.abs(playbackRate) - 1);\n baseDuration = Math.max(1, Math.floor(baseDuration * scale));\n calcLoopEnd = calcLoopStart + baseDuration;\n }\n baseDuration = calcLoopEnd - calcLoopStart;\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD) calcLoopStart = this.#findNearestZeroCrossing(calcLoopStart, \"right\");\n if (driftAmount > 0 && this.loopEnabled) {\n if (!this.nextDriftGenerated || this.loopCount === 0) {\n const updateInterval = baseDuration <= this.PITCH_PRESERVATION_THRESHOLD ? Math.max(1, Math.floor(this.PITCH_PRESERVATION_THRESHOLD / baseDuration)) : 1;\n if (this.driftUpdateCounter % updateInterval === 0) {\n this.currentLoopDrift = this.#generateLoopDrift(driftAmount, baseDuration);\n if (this.panDriftEnabled && driftAmount > 0 && this.loopCount > 0) {\n const panDriftAmountScalar = 1e-4;\n this.currentPanDrift = this.currentLoopDrift * panDriftAmountScalar;\n } else this.currentPanDrift = 0;\n }\n this.driftUpdateCounter++;\n this.nextDriftGenerated = true;\n }\n const driftedLoopEnd = calcLoopEnd + this.currentLoopDrift;\n const minLoopDuration = Math.max(1, Math.floor(baseDuration * .1));\n const maxLoopEnd = Math.max(playbackRange.endSamples, calcLoopEnd);\n calcLoopEnd = Math.max(calcLoopStart + minLoopDuration, Math.min(maxLoopEnd, driftedLoopEnd));\n } else this.currentPanDrift = 0;\n if (baseDuration > this.PITCH_PRESERVATION_THRESHOLD && calcLoopEnd <= playbackRange.endSamples) calcLoopEnd = Math.max(calcLoopStart + 1, this.#findNearestZeroCrossing(calcLoopEnd, \"left\"));\n const loopDuration = calcLoopEnd - calcLoopStart;\n return {\n loopStartSamples: calcLoopStart,\n loopEndSamples: calcLoopEnd,\n loopDurationSamples: loopDuration\n };\n }\n #getSafeParam(paramArray, index, isConstant) {\n return isConstant ? paramArray[0] : paramArray[Math.min(index, paramArray.length - 1)];\n }\n #getConstantFlags(parameters) {\n this.constantFlags ??= {\n envGain: true,\n playbackRate: true\n };\n this.constantFlags.envGain = parameters.envGain.length === 1;\n this.constantFlags.playbackRate = parameters.playbackRate.length === 1;\n return this.constantFlags;\n }\n #resetDurationPreservation(position = 0) {\n this.durationPreservation.timelinePosition = position;\n this.durationPreservation.resetPending = false;\n }\n #isDurationPreservationActive(loopRange) {\n return this.durationPreservation.enabled && Boolean(this.zeroCrossings?.length) && (!this.loopEnabled || loopRange.loopDurationSamples > this.PITCH_PRESERVATION_THRESHOLD);\n }\n #prepareDurationPreservingSample(playbackRate, loopRange) {\n const state = this.durationPreservation;\n if (!this.#isDurationPreservationActive(loopRange)) return null;\n if (Math.abs(this.playbackPosition - state.timelinePosition) > state.maxDriftSamples) state.resetPending = true;\n if (!state.resetPending) return null;\n const direction = playbackRate < 0 ? \"left\" : \"right\";\n const outgoingZero = this.#findNearestZeroCrossing(this.playbackPosition, direction);\n if (Math.abs(outgoingZero - this.playbackPosition) > Math.abs(playbackRate)) return null;\n this.playbackPosition = outgoingZero;\n state.resetPending = false;\n return this.#findNearestZeroCrossing(state.timelinePosition, \"any\", state.maxDriftSamples);\n }\n #advanceDurationPreservingPlayback(playbackRate, resetTarget, loopRange, canWrapLoop) {\n const state = this.durationPreservation;\n this.playbackPosition = resetTarget === null ? this.playbackPosition + playbackRate : resetTarget;\n if (this.#isDurationPreservationActive(loopRange)) {\n state.timelinePosition += playbackRate < 0 ? -1 : 1;\n if (canWrapLoop && playbackRate >= 0 && state.timelinePosition >= loopRange.loopEndSamples) state.timelinePosition = loopRange.loopStartSamples;\n else if (canWrapLoop && playbackRate < 0 && state.timelinePosition <= loopRange.loopStartSamples) state.timelinePosition = loopRange.loopEndSamples - 1;\n } else this.#resetDurationPreservation(this.playbackPosition);\n }\n /**\n * Generate a new drift amount for the current loop iteration\n * @param {number} driftAmount - Maximum drift amount (0-1)\n * @param {number} baseDuration - Base loop duration in samples\n * @returns {number} - Drift amount in samples\n */\n #generateLoopDrift(driftAmount, baseDuration) {\n if (driftAmount <= 0) return 0;\n const randomFactor = (Math.random() - .5) * 2;\n let effectiveDriftAmount = driftAmount;\n if (this.enableAdaptiveDrift) {\n const shortThreshold = 1024;\n const longThreshold = 8192;\n if (baseDuration < shortThreshold) effectiveDriftAmount *= .1;\n else if (baseDuration < longThreshold) {\n const scaleFactor = .1 + .9 * (baseDuration - shortThreshold) / 7168;\n effectiveDriftAmount *= scaleFactor;\n }\n }\n const maxDriftSamples = effectiveDriftAmount * baseDuration;\n return Math.floor(randomFactor * maxDriftSamples);\n }\n /**\n * Analyze loop amplitude and calculate makeup gain for short loops\n * @param {number} loopStart - Loop start position in samples\n * @param {number} loopEnd - Loop end position in samples\n * @returns {number} - Makeup gain multiplier (1.0 = no change)\n */\n #analyzeLoopAmplitude(loopStart, loopEnd) {\n if (!this.enableAmplitudeCompensation || !this.buffer || !this.buffer[0]) return 1;\n if (loopEnd - loopStart >= this.AMPLITUDE_COMPENSATION_THRESHOLD) return 1;\n if (loopStart === this.lastAnalyzedLoopStart && loopEnd === this.lastAnalyzedLoopEnd) return this.loopAmplitudeGain;\n let sumSquares = 0;\n let sampleCount = 0;\n const channel = this.buffer[0];\n const startIndex = Math.floor(loopStart);\n const endIndex = Math.floor(loopEnd);\n for (let i = startIndex; i < endIndex && i < channel.length; i++) {\n const sample = channel[i];\n sumSquares += sample * sample;\n sampleCount++;\n }\n if (sampleCount === 0) return 1;\n const rmsAmplitude = Math.sqrt(sumSquares / sampleCount);\n const targetAmplitude = .3;\n let makeupGain = 1;\n if (rmsAmplitude < targetAmplitude) {\n makeupGain = targetAmplitude / Math.max(rmsAmplitude, .001);\n makeupGain = Math.min(2, makeupGain);\n }\n this.lastAnalyzedLoopStart = loopStart;\n this.lastAnalyzedLoopEnd = loopEnd;\n this.loopAmplitudeGain = makeupGain;\n return makeupGain;\n }\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n this.debugCounter++;\n if (!output || !this.isPlaying || !this.buffer?.[0]?.length) return true;\n const masterGain = parameters.masterGain[0];\n const positionParams = this.#extractPositionParams(parameters);\n const playbackRange = this.#calculatePlaybackRange(positionParams);\n const effectivePlaybackRate = parameters.playbackRate[0];\n const tempo = parameters.tempo[0];\n const loopRange = this.#calculateLoopRange(positionParams, playbackRange, parameters.loopDurationDriftAmount[0], tempo, effectivePlaybackRate);\n const amplitudeGain = this.#analyzeLoopAmplitude(loopRange.loopStartSamples, loopRange.loopEndSamples);\n const velocityGain = this.#midiVelocityToGain(parameters.velocity[0]) * this.velocitySensitivity;\n const basePan = parameters.pan[0];\n const effectivePan = this.panDriftEnabled ? Math.max(-1, Math.min(1, basePan + this.currentPanDrift)) : basePan;\n let outputChannels;\n if (output instanceof Float32Array) outputChannels = [output];\n else if (Array.isArray(output) && output.every((ch) => ch instanceof Float32Array)) outputChannels = output;\n else {\n console.error(\"Unexpected output structure:\", {\n outputType: typeof output,\n isArray: Array.isArray(output),\n constructor: output?.constructor?.name,\n length: output?.length\n });\n return true;\n }\n const numChannels = outputChannels.length;\n const isConstant = this.#getConstantFlags(parameters);\n const silencePadTail = loopRange.loopEndSamples > playbackRange.endSamples;\n const TAIL_FADE_SAMPLES = 64;\n if (this.playbackPosition === 0) {\n this.playbackPosition = this.reversePlayback ? playbackRange.endSamples - 1 : playbackRange.startSamples;\n this.#resetDurationPreservation(this.playbackPosition);\n }\n for (let sample = 0; sample < outputChannels[0].length; sample++) {\n const envelopeGain = this.#getSafeParam(parameters.envGain, sample, isConstant.envGain);\n const baseRate = this.#getSafeParam(parameters.playbackRate, sample, isConstant.playbackRate);\n const playbackStep = this.reversePlayback ? -Math.abs(baseRate) : Math.abs(baseRate);\n const canWrapLoop = this.loopEnabled && this.loopCount < parameters.maxLoopCount[0];\n if (canWrapLoop) {\n if (!this.reversePlayback && this.playbackPosition >= loopRange.loopEndSamples) {\n this.#smoothLoopWrap(silencePadTail ? 0 : this.buffer[0][Math.floor(this.playbackPosition - 1)] || 0, this.buffer[0][Math.floor(loopRange.loopStartSamples)] || 0);\n this.playbackPosition = loopRange.loopStartSamples;\n this.loopCount++;\n this.nextDriftGenerated = false;\n } else if (this.reversePlayback && this.playbackPosition <= loopRange.loopStartSamples) {\n this.#smoothLoopWrap(this.buffer[0][Math.floor(loopRange.loopStartSamples)] || 0, silencePadTail ? 0 : this.buffer[0][Math.floor(loopRange.loopEndSamples) - 1] || 0);\n this.playbackPosition = loopRange.loopEndSamples;\n this.loopCount++;\n this.nextDriftGenerated = false;\n }\n }\n const durationResetTarget = this.#prepareDurationPreservingSample(playbackStep, loopRange);\n const shouldStopForward = !this.reversePlayback && (this.#isDurationPreservationActive(loopRange) ? this.durationPreservation.timelinePosition : this.playbackPosition) >= playbackRange.endSamples;\n const shouldStopReverse = this.reversePlayback && (this.#isDurationPreservationActive(loopRange) ? this.durationPreservation.timelinePosition : this.playbackPosition) <= playbackRange.startSamples;\n const isWithinLoop = this.loopEnabled && this.playbackPosition >= loopRange.loopStartSamples && this.playbackPosition <= loopRange.loopEndSamples;\n if ((shouldStopForward || shouldStopReverse) && !(this.loopEnabled && isWithinLoop)) {\n this.#stop();\n return true;\n }\n let tailGain = 1;\n if (silencePadTail) {\n const distToEnd = playbackRange.endSamples - this.playbackPosition;\n if (distToEnd < TAIL_FADE_SAMPLES) tailGain = Math.max(0, distToEnd / TAIL_FADE_SAMPLES);\n }\n const currentPosition = Math.floor(this.playbackPosition);\n const positionOffset = this.playbackPosition - currentPosition;\n let nextPosition, interpWeight;\n if (this.reversePlayback) {\n nextPosition = Math.max(currentPosition - 1, playbackRange.startSamples);\n interpWeight = 1 - positionOffset;\n } else {\n nextPosition = Math.min(currentPosition + 1, playbackRange.endSamples - 1);\n interpWeight = positionOffset;\n }\n for (let channel = 0; channel < numChannels; channel++) {\n if (!outputChannels[channel]) {\n console.warn(`Output channel ${channel} does not exist. Available channels:`, outputChannels.length);\n continue;\n }\n let interpolatedSample = 0;\n for (let l = 0; l < this.layers.length; l++) {\n const layer = this.layers[l];\n const layerChannel = layer[Math.min(channel, layer.length - 1)];\n const currentSample = layerChannel[currentPosition] || 0;\n const nextSample = layerChannel[nextPosition] || 0;\n interpolatedSample += (currentSample + interpWeight * (nextSample - currentSample)) * this.layerGain;\n }\n if (this.applyClickCompensation) {\n interpolatedSample += this.loopClickCompensation;\n if (this.compensationDecay) {\n this.loopClickCompensation *= this.compensationDecay;\n if (Math.abs(this.loopClickCompensation) < .001) this.applyClickCompensation = false;\n } else this.applyClickCompensation = false;\n }\n const finalSample = interpolatedSample * velocityGain * envelopeGain * masterGain * amplitudeGain * tailGain;\n let panAdjustedSample = finalSample;\n if (outputChannels.length === 2) {\n if (channel === 0) panAdjustedSample = finalSample * (1 - Math.max(0, effectivePan));\n else if (channel === 1) panAdjustedSample = finalSample * (1 - Math.max(0, -effectivePan));\n }\n outputChannels[channel][sample] = Math.max(-1, Math.min(1, isFinite(panAdjustedSample) ? panAdjustedSample : 0));\n }\n this.#advanceDurationPreservingPlayback(playbackStep, durationResetTarget, loopRange, canWrapLoop);\n }\n if (this.usePlaybackPosition) {\n const normalizedPosition = this.#samplesToNormalized(this.playbackPosition);\n this.port.postMessage({\n type: \"voice:position\",\n position: normalizedPosition\n });\n }\n return true;\n }\n};\nregisterProcessor(\"sample-player-processor\", SamplePlayerProcessor);\n//#endregion\n//#region src/worklets/processors/noise/random-noise-processor.js\nvar RandomNoiseProcessor = class extends AudioWorkletProcessor {\n constructor() {\n super();\n this.previousNoise = 0;\n this.previousFiltered = 0;\n this.hpfHz = 150;\n this.alpha = this.hpfHz / (this.hpfHz + sampleRate / (2 * Math.PI));\n this.port.onmessage = (event) => {\n if (event.data.type === \"setHpfHz\") {\n this.hpfHz = event.data.value;\n this.alpha = this.calculateAlpha(this.hpfHz);\n }\n };\n this.port.postMessage({ type: \"initialized\" });\n }\n calculateAlpha(frequency) {\n return frequency / (frequency + sampleRate / (2 * Math.PI));\n }\n process(inputs, outputs, parameters) {\n outputs[0].forEach((channel) => {\n for (let i = 0; i < channel.length; i++) {\n const noise = Math.random() * 2 - 1;\n const filtered = this.alpha * (noise - this.previousNoise) + this.previousFiltered;\n this.previousNoise = noise;\n this.previousFiltered = filtered;\n channel[i] = filtered;\n }\n });\n return true;\n }\n};\nregisterProcessor(\"random-noise-processor\", RandomNoiseProcessor);\n//#endregion\n//#region src/worklets/shared/utils/compress-utils.ts\nvar cheapSoftClipSingleSample = (sample, max = .9) => {\n const a = Math.abs(sample);\n if (a <= max) return sample;\n const x = a / max;\n const compressed = x / (1 + x);\n return Math.sign(sample) * max * compressed;\n};\n/**\n* Basic attenuation compressor for single sample\n* Note: No validation since optimized for real time use\n*/\nvar compressSingleSample = (input, threshold = .75, ratio = 4, limiter = {\n enabled: true,\n type: \"soft\",\n outputRange: {\n min: -1,\n max: 1\n }\n}) => {\n const { min, max } = limiter.outputRange;\n let x = input;\n if (Math.abs(x) > threshold) x = Math.sign(x) * (threshold + (Math.abs(x) - threshold) / ratio);\n if (limiter.enabled) {\n if (limiter.type === \"soft\") x = cheapSoftClipSingleSample(x, Math.abs(max));\n else if (limiter.type === \"hard\") x = Math.max(min, Math.min(max, x));\n }\n return x;\n};\n//#endregion\n//#region src/worklets/processors/delay/DelayBuffer.js\nvar DelayBuffer = class {\n constructor(maxDelaySamples) {\n this.buffer = new Float32Array(maxDelaySamples);\n this.writePtr = 0;\n this.readPtr = 0;\n }\n write(sample) {\n this.buffer[this.writePtr] = sample;\n }\n read() {\n return this.buffer[this.readPtr];\n }\n updatePointers(delaySamples) {\n this.writePtr = (this.writePtr + 1) % this.buffer.length;\n this.readPtr = (this.writePtr - delaySamples + this.buffer.length) % this.buffer.length;\n }\n};\n//#endregion\n//#region src/worklets/processors/delay/FeedbackDelay.js\nvar AUTO_GAIN_THRESHOLD = .8;\nvar SAFETY_GAIN_COMPENSATION = .2;\nvar FeedbackDelay = class {\n constructor(sampleRate) {\n this.sampleRate = sampleRate;\n this.buffers = [];\n this.initialized = false;\n this.autoGainEnabled = false;\n this.gainCompensation = SAFETY_GAIN_COMPENSATION;\n this.lowpassStates = [];\n this.highpassStates = [];\n this.highpassInputStates = [];\n }\n initializeBuffers(channelCount) {\n this.buffers = [];\n this.lowpassStates = [];\n this.highpassStates = [];\n this.highpassInputStates = [];\n const maxSamples = Math.floor(this.sampleRate * 2);\n for (let c = 0; c < channelCount; c++) {\n this.buffers[c] = new DelayBuffer(maxSamples);\n this.lowpassStates[c] = 0;\n this.highpassStates[c] = 0;\n this.highpassInputStates[c] = 0;\n }\n this.initialized = true;\n }\n /** Simple one-pole lowpass filter */\n lowpass(input, cutoffFreq, channelIndex) {\n if (cutoffFreq >= this.sampleRate * .4) return input;\n const omega = 2 * Math.PI * cutoffFreq / this.sampleRate;\n const alpha = Math.max(0, Math.min(.99, Math.sin(omega) / (Math.sin(omega) + Math.cos(omega))));\n this.lowpassStates[channelIndex] = alpha * input + (1 - alpha) * this.lowpassStates[channelIndex];\n return this.lowpassStates[channelIndex];\n }\n /** Simple one-pole highpass filter */\n highpass(input, cutoffFreq, channelIndex) {\n if (cutoffFreq < 5) return input;\n const omega = 2 * Math.PI * cutoffFreq / this.sampleRate;\n const alpha = Math.max(0, Math.min(.99, Math.sin(omega) / (Math.sin(omega) + Math.cos(omega))));\n const lowpassOutput = alpha * input + (1 - alpha) * this.highpassStates[channelIndex];\n const highpassOutput = input - lowpassOutput;\n this.highpassStates[channelIndex] = lowpassOutput;\n return highpassOutput;\n }\n process(inputSample, channelIndex, feedbackAmount, delayTime, lowpassFreq = 1e4, highpassFreq = 100) {\n if (!this.initialized) return inputSample;\n const buffer = this.buffers[channelIndex] || this.buffers[0];\n const delaySamples = Math.floor(this.sampleRate * delayTime);\n const delayedSample = buffer.read();\n let filteredDelay = this.highpass(delayedSample, highpassFreq, channelIndex);\n filteredDelay = this.lowpass(filteredDelay, lowpassFreq, channelIndex);\n const feedbackSample = feedbackAmount * filteredDelay + inputSample;\n let outputSample = feedbackSample;\n const compressedFeedback = compressSingleSample(feedbackSample, .5, 4, {\n enabled: true,\n outputRange: {\n min: -.99,\n max: .99\n },\n type: \"soft\"\n });\n if (this.autoGainEnabled && feedbackAmount > AUTO_GAIN_THRESHOLD) outputSample = compressedFeedback * (1 - (feedbackAmount - AUTO_GAIN_THRESHOLD) * this.gainCompensation);\n return {\n outputSample,\n feedbackSample: compressedFeedback,\n delaySamples\n };\n }\n updateBuffer(channelIndex, sample, delaySamples) {\n const buffer = this.buffers[channelIndex] || this.buffers[0];\n buffer.write(sample);\n buffer.updatePointers(delaySamples);\n }\n setAutoGain(enabled, compensation = SAFETY_GAIN_COMPENSATION) {\n this.autoGainEnabled = enabled;\n this.gainCompensation = compensation;\n }\n};\n//#endregion\n//#region src/worklets/processors/delay/feedback-delay-processor.js\nregisterProcessor(\"feedback-delay-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"feedbackAmount\",\n defaultValue: .5,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"delayTime\",\n defaultValue: .5,\n minValue: .00012656238799684143,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n {\n name: \"decay\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"lowpass\",\n defaultValue: 1e4,\n minValue: 100,\n maxValue: 16e3,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.feedbackDelay = new FeedbackDelay(sampleRate);\n this.decayStartTime = null;\n this.decayActive = false;\n this.baseFeedbackAmount = .5;\n this.setupMessageHandling();\n this.port.postMessage({ type: \"initialized\" });\n }\n setupMessageHandling() {\n this.port.onmessage = (event) => {\n switch (event.data.type) {\n case \"setAutoGain\":\n this.feedbackDelay.setAutoGain(event.data.enabled, event.data.amount);\n break;\n case \"triggerDecay\":\n this.decayStartTime = currentTime;\n this.decayActive = true;\n this.baseFeedbackAmount = event.data.baseFeedbackAmount || .5;\n break;\n case \"stopDecay\":\n this.decayActive = false;\n this.decayStartTime = null;\n }\n };\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output) return true;\n if (!this.feedbackDelay.initialized || this.feedbackDelay.buffers.length !== input.length) this.feedbackDelay.initializeBuffers(input.length);\n const baseFeedbackAmount = parameters.feedbackAmount[0];\n const delayTime = parameters.delayTime[0];\n const decay = parameters.decay[0];\n const lowpassFreq = parameters.lowpass[0];\n const channelCount = Math.min(input.length, output.length);\n const frameCount = output[0].length;\n for (let i = 0; i < frameCount; ++i) {\n let effectiveFeedbackAmount = baseFeedbackAmount;\n if (this.decayActive && this.decayStartTime !== null) {\n const elapsedTime = currentTime - this.decayStartTime + i / sampleRate;\n const delayCompensation = Math.min(100, .5 / delayTime);\n const timeConstant = Math.pow(decay, 5) * 1e3 * delayCompensation + .5;\n effectiveFeedbackAmount = baseFeedbackAmount * Math.exp(-elapsedTime / timeConstant);\n if (effectiveFeedbackAmount < .01) {\n this.decayActive = false;\n effectiveFeedbackAmount = 0;\n }\n }\n for (let c = 0; c < channelCount; c++) {\n const processed = this.feedbackDelay.process(input[c][i], c, effectiveFeedbackAmount, delayTime, lowpassFreq);\n output[c][i] = processed.outputSample;\n this.feedbackDelay.updateBuffer(c, processed.feedbackSample, processed.delaySamples);\n }\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/delay/delay-processor.js\nvar DEFAULT_DELAY_CONFIG = {\n CHARACTER: [\"filtered\"],\n SMOOTHING_FACTOR: {\n slowest: 1e-4,\n slow: 25e-5,\n medium: 35e-5,\n fast: 5e-4,\n veryFast: .001,\n superFast: .1,\n none: 1\n }\n};\nvar DEFAULT_CHARACTER_CONFIG = {\n bitCrushed: {\n bits: 11,\n downsample: 3\n },\n filtered: {\n freq: 900,\n Q: .15\n }\n};\nregisterProcessor(\"delay-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [{\n name: \"delayTime\",\n defaultValue: .5,\n minValue: .001,\n maxValue: 2,\n automationRate: \"k-rate\"\n }, {\n name: \"feedbackAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: .99,\n automationRate: \"k-rate\"\n }];\n }\n constructor() {\n super();\n this.buffers = [];\n this.smoothedDelaySamples = [];\n this.smoothingFactor = DEFAULT_DELAY_CONFIG.SMOOTHING_FACTOR.slowest;\n this.characterModes = [...DEFAULT_DELAY_CONFIG.CHARACTER];\n this._bpState = [];\n this._bpFreq = DEFAULT_CHARACTER_CONFIG.filtered.freq;\n this._bpQ = DEFAULT_CHARACTER_CONFIG.filtered.Q;\n this._bpCoeffs = null;\n this._lastBpFreq = -1;\n this._lastBpQ = -1;\n this.lofiBits = DEFAULT_CHARACTER_CONFIG[\"bitCrushed\"].bits;\n this.lofiDownsample = DEFAULT_CHARACTER_CONFIG[\"bitCrushed\"].downsample;\n this._lofiSampleHold = [];\n this._lofiSampleCount = [];\n this.initialized = false;\n this.port.onmessage = (event) => {\n if (event.data && event.data.type === \"setCharacter\" && Array.isArray(event.data.modes)) this.characterModes = [...event.data.modes];\n if (event.data && event.data.type === \"setBandpassFreq\" && typeof event.data.hz === \"number\") this.setBandpassFreq(event.data.hz);\n if (event.data && event.data.type === \"trigger\") {}\n };\n this.port.postMessage({ type: \"initialized\" });\n }\n setBandpassFreq(hz) {\n this._bpFreq = hz;\n this._lastBpFreq = -1;\n }\n _updateBandpassCoeffs() {\n if (this._lastBpFreq === this._bpFreq && this._lastBpQ === this._bpQ) return;\n const bpFreq = this._bpFreq;\n const bpQ = this._bpQ;\n const omega = 2 * Math.PI * bpFreq / sampleRate;\n const alpha = Math.sin(omega) / (2 * bpQ);\n const cosw = Math.cos(omega);\n const b0 = alpha;\n const b1 = 0;\n const b2 = -alpha;\n const a0 = 1 + alpha;\n const a1 = -2 * cosw;\n const a2 = 1 - alpha;\n this._bpCoeffs = {\n b0: b0 / a0,\n b1: b1 / a0,\n b2: b2 / a0,\n a1: a1 / a0,\n a2: a2 / a0\n };\n this._lastBpFreq = bpFreq;\n this._lastBpQ = bpQ;\n }\n initializeBuffers(channelCount) {\n const maxSamples = Math.floor(sampleRate * 2);\n this.buffers = [];\n this.smoothedDelaySamples = [];\n this._lofiSampleHold = [];\n this._lofiSampleCount = [];\n for (let c = 0; c < channelCount; c++) {\n this.buffers[c] = new DelayBuffer(maxSamples);\n this.smoothedDelaySamples[c] = Math.floor(sampleRate * .5);\n this._lofiSampleHold[c] = 0;\n this._lofiSampleCount[c] = 0;\n }\n this.initialized = true;\n }\n _processLoFi(delayed, c) {\n if (this._lofiSampleCount[c] % this.lofiDownsample === 0) {\n const levels = Math.pow(2, this.lofiBits);\n delayed = Math.round(delayed * levels) / levels;\n this._lofiSampleHold[c] = delayed;\n } else delayed = this._lofiSampleHold[c];\n this._lofiSampleCount[c]++;\n return delayed;\n }\n _processBandpass(delayed, c) {\n if (!this._bpState) this._bpState = [];\n if (!this._bpState[c]) this._bpState[c] = {\n x1: 0,\n x2: 0,\n y1: 0,\n y2: 0\n };\n this._updateBandpassCoeffs();\n if (!this._bpCoeffs) return delayed;\n const { b0, b1, b2, a1, a2 } = this._bpCoeffs;\n const s = this._bpState[c];\n const y = b0 * delayed + b1 * s.x1 + b2 * s.x2 - a1 * s.y1 - a2 * s.y2;\n s.x2 = s.x1;\n s.x1 = delayed;\n s.y2 = s.y1;\n s.y1 = y;\n return y;\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output || input.length === 0 || output.length === 0) return true;\n if (!input[0] || !output[0] || input[0].length === 0 || output[0].length === 0) return true;\n if (!this.initialized || this.buffers.length !== input.length) this.initializeBuffers(input.length);\n const delayTime = parameters.delayTime[0];\n const feedbackAmount = parameters.feedbackAmount[0];\n const targetDelaySamples = sampleRate * delayTime;\n const channelCount = Math.min(input.length, output.length);\n const frameCount = output[0].length;\n const smoothing = this.smoothingFactor;\n for (let i = 0; i < frameCount; ++i) for (let c = 0; c < channelCount; c++) {\n const buf = this.buffers[c];\n if (!buf) continue;\n this.smoothedDelaySamples[c] += (targetDelaySamples - this.smoothedDelaySamples[c]) * smoothing;\n const smoothedDelay = this.smoothedDelaySamples[c];\n const intDelay = Math.floor(smoothedDelay);\n const frac = smoothedDelay - intDelay;\n const readPtrA = (buf.writePtr - intDelay + buf.buffer.length) % buf.buffer.length;\n const readPtrB = (readPtrA - 1 + buf.buffer.length) % buf.buffer.length;\n const sampleA = buf.buffer[readPtrA];\n const sampleB = buf.buffer[readPtrB];\n let delayed = sampleA * (1 - frac) + sampleB * frac;\n for (const mode of this.characterModes) if (mode === \"bitCrushed\") delayed = this._processLoFi(delayed, c);\n else if (mode === \"filtered\") delayed = this._processBandpass(delayed, c);\n output[c][i] = compressSingleSample(delayed, .75, 4, {\n enabled: true,\n type: \"soft\",\n outputRange: {\n min: -.9,\n max: .9\n }\n });\n const inputSample = input[c] && input[c][i] !== void 0 ? input[c][i] : 0;\n buf.write(inputSample + delayed * feedbackAmount);\n buf.updatePointers(intDelay);\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/reverb/dattorro-reverb-processor.js\nvar DattorroReverb = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n [\n \"preDelay\",\n 0,\n 0,\n sampleRate - 1,\n \"k-rate\"\n ],\n [\n \"bandwidth\",\n .9999,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"inputDiffusion1\",\n .75,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"inputDiffusion2\",\n .625,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"decay\",\n .5,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"decayDiffusion1\",\n .7,\n 0,\n .999999,\n \"k-rate\"\n ],\n [\n \"decayDiffusion2\",\n .5,\n 0,\n .999999,\n \"k-rate\"\n ],\n [\n \"damping\",\n .005,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"excursionRate\",\n .5,\n 0,\n 2,\n \"k-rate\"\n ],\n [\n \"excursionDepth\",\n .7,\n 0,\n 2,\n \"k-rate\"\n ],\n [\n \"wet\",\n .3,\n 0,\n 1,\n \"k-rate\"\n ],\n [\n \"dry\",\n .6,\n 0,\n 1,\n \"k-rate\"\n ]\n ].map((x) => /* @__PURE__ */ new Object({\n name: x[0],\n defaultValue: x[1],\n minValue: x[2],\n maxValue: x[3],\n automationRate: x[4]\n }));\n }\n constructor(options) {\n super(options);\n this._Delays = [];\n this._pDLength = sampleRate + (128 - sampleRate % 128);\n this._preDelay = new Float32Array(this._pDLength);\n this._pDWrite = 0;\n this._lp1 = 0;\n this._lp2 = 0;\n this._lp3 = 0;\n this._excPhase = 0;\n const SHORT_DELAY_SCALE = .5;\n [\n .004771345,\n .003595309,\n .012734787,\n .009307483,\n .022579886,\n .149625349,\n .060481839,\n .1249958,\n .030509727,\n .141695508,\n .089244313,\n .106280031\n ].map((x) => x * SHORT_DELAY_SCALE).forEach((x) => this.makeDelay(x));\n this._taps = Int16Array.from([\n .008937872,\n .099929438,\n .064278754,\n .067067639,\n .066866033,\n .006283391,\n .035818689,\n .011861161,\n .121870905,\n .041262054,\n .08981553,\n .070931756,\n .011256342,\n .004065724\n ], (x) => Math.round(x * sampleRate));\n this.port.postMessage({ type: \"initialized\" });\n }\n makeDelay(length) {\n let len = Math.round(length * sampleRate);\n let nextPow2 = 2 ** Math.ceil(Math.log2(len));\n this._Delays.push([\n new Float32Array(nextPow2),\n len - 1,\n 0,\n nextPow2 - 1\n ]);\n }\n writeDelay(index, data) {\n return this._Delays[index][0][this._Delays[index][1]] = data;\n }\n readDelay(index) {\n return this._Delays[index][0][this._Delays[index][2]];\n }\n readDelayAt(index, i) {\n let d = this._Delays[index];\n return d[0][d[2] + i & d[3]];\n }\n readDelayCAt(index, i) {\n let d = this._Delays[index], frac = i - ~~i, int = ~~i + d[2] - 1, mask = d[3];\n let x0 = d[0][int++ & mask], x1 = d[0][int++ & mask], x2 = d[0][int++ & mask], x3 = d[0][int & mask];\n let a = (3 * (x1 - x2) - x0 + x3) / 2, b = 2 * x2 + x0 - (5 * x1 + x3) / 2, c = (x2 - x0) / 2;\n return ((a * frac + b) * frac + c) * frac + x1;\n }\n process(inputs, outputs, parameters) {\n const TWO_PI = 6.283185307179586;\n const TWO_PI_DETUNE = 6.284702653297906;\n const pd = ~~parameters.preDelay[0], bw = parameters.bandwidth[0], fi = parameters.inputDiffusion1[0], si = parameters.inputDiffusion2[0], dc = parameters.decay[0], ft = parameters.decayDiffusion1[0], st = parameters.decayDiffusion2[0], dp = 1 - parameters.damping[0], ex = parameters.excursionRate[0] / sampleRate, ed = parameters.excursionDepth[0] * sampleRate / 1e3, we = parameters.wet[0] * .6, dr = parameters.dry[0];\n if (inputs[0].length == 2) for (let i = 127; i >= 0; i--) {\n this._preDelay[this._pDWrite + i] = (inputs[0][0][i] + inputs[0][1][i]) * .5;\n outputs[0][0][i] = inputs[0][0][i] * dr;\n outputs[0][1][i] = inputs[0][1][i] * dr;\n }\n else if (inputs[0].length > 0) {\n this._preDelay.set(inputs[0][0], this._pDWrite);\n for (let i = 127; i >= 0; i--) outputs[0][0][i] = outputs[0][1][i] = inputs[0][0][i] * dr;\n } else this._preDelay.set(/* @__PURE__ */ new Float32Array(128), this._pDWrite);\n let i = 0;\n while (i < 128) {\n let lo = 0, ro = 0;\n this._lp1 += bw * (this._preDelay[(this._pDLength + this._pDWrite - pd + i) % this._pDLength] - this._lp1);\n let pre = this.writeDelay(0, this._lp1 - fi * this.readDelay(0));\n pre = this.writeDelay(1, fi * (pre - this.readDelay(1)) + this.readDelay(0));\n pre = this.writeDelay(2, fi * pre + this.readDelay(1) - si * this.readDelay(2));\n pre = this.writeDelay(3, si * (pre - this.readDelay(3)) + this.readDelay(2));\n let split = si * pre + this.readDelay(3);\n let exc = ed * (1 + Math.cos(this._excPhase * TWO_PI));\n let exc2 = ed * (1 + Math.sin(this._excPhase * TWO_PI_DETUNE));\n let temp = this.writeDelay(4, split + dc * this.readDelay(11) + ft * this.readDelayCAt(4, exc));\n this.writeDelay(5, this.readDelayCAt(4, exc) - ft * temp);\n this._lp2 += dp * (this.readDelay(5) - this._lp2);\n temp = this.writeDelay(6, dc * this._lp2 - st * this.readDelay(6));\n this.writeDelay(7, this.readDelay(6) + st * temp);\n temp = this.writeDelay(8, split + dc * this.readDelay(7) + ft * this.readDelayCAt(8, exc2));\n this.writeDelay(9, this.readDelayCAt(8, exc2) - ft * temp);\n this._lp3 += dp * (this.readDelay(9) - this._lp3);\n temp = this.writeDelay(10, dc * this._lp3 - st * this.readDelay(10));\n this.writeDelay(11, this.readDelay(10) + st * temp);\n lo = this.readDelayAt(9, this._taps[0]) + this.readDelayAt(9, this._taps[1]) - this.readDelayAt(10, this._taps[2]) + this.readDelayAt(11, this._taps[3]) - this.readDelayAt(5, this._taps[4]) - this.readDelayAt(6, this._taps[5]) - this.readDelayAt(7, this._taps[6]);\n ro = this.readDelayAt(5, this._taps[7]) + this.readDelayAt(5, this._taps[8]) - this.readDelayAt(6, this._taps[9]) + this.readDelayAt(7, this._taps[10]) - this.readDelayAt(9, this._taps[11]) - this.readDelayAt(10, this._taps[12]) - this.readDelayAt(11, this._taps[13]);\n outputs[0][0][i] += lo * we;\n outputs[0][1][i] += ro * we;\n this._excPhase += ex;\n if (this._excPhase >= 1) this._excPhase -= 1;\n i++;\n const delays = this._Delays;\n for (let j = 0; j < delays.length; j++) {\n const d = delays[j];\n d[1] = d[1] + 1 & d[3];\n d[2] = d[2] + 1 & d[3];\n }\n }\n this._pDWrite = (this._pDWrite + 128) % this._pDLength;\n return true;\n }\n};\nregisterProcessor(\"dattorro-reverb-processor\", DattorroReverb);\n//#endregion\n//#region src/worklets/processors/distortion/distortion-processor.js\nvar Distortion = class {\n constructor() {\n this.limitingMode = \"hard-clipping\";\n }\n applyDrive(sample, driveAmount) {\n if (driveAmount <= 0) return sample;\n return sample * (1 + driveAmount * 3);\n }\n applyClipping(sample, clippingAmount, clipThreshold) {\n if (clippingAmount <= 0) return sample;\n let clippedSample;\n switch (this.limitingMode) {\n case \"soft-clipping\":\n clippedSample = clipThreshold * Math.tanh(sample / clipThreshold);\n break;\n case \"hard-clipping\":\n clippedSample = Math.max(-clipThreshold, Math.min(clipThreshold, sample));\n break;\n default: clippedSample = sample;\n }\n if (clipThreshold < .08) {\n const makeupGain = Math.min(2, Math.pow(.1 / clipThreshold, .5));\n clippedSample *= makeupGain;\n }\n return sample * (1 - clippingAmount) + clippedSample * clippingAmount;\n }\n setLimitingMode(mode) {\n this.limitingMode = mode;\n }\n};\nregisterProcessor(\"distortion-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"distortionDrive\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n {\n name: \"clippingAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n {\n name: \"clippingThreshold\",\n defaultValue: .5,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.distortion = new Distortion();\n this.setupMessageHandling();\n this.port.postMessage({ type: \"initialized\" });\n }\n setupMessageHandling() {\n this.port.onmessage = (event) => {\n switch (event.data.type) {\n case \"setLimitingMode\":\n this.distortion.setLimitingMode(event.data.mode);\n break;\n default: console.warn(\"distortion-processor: Unsupported message\");\n }\n };\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n if (!input || !output) return true;\n const clipThreshold = parameters.clippingThreshold[0];\n for (let i = 0; i < output[0].length; ++i) {\n const distortionDrive = parameters.distortionDrive[Math.min(i, parameters.distortionDrive.length - 1)];\n const clippingAmount = parameters.clippingAmount[Math.min(i, parameters.clippingAmount.length - 1)];\n for (let c = 0; c < Math.min(input.length, output.length); c++) {\n let sample = input[c][i];\n sample = this.distortion.applyDrive(sample, distortionDrive);\n sample = this.distortion.applyClipping(sample, clippingAmount, clipThreshold);\n output[c][i] = Math.max(-.999, Math.min(.999, sample));\n }\n }\n return true;\n }\n});\n//#endregion\n//#region src/worklets/processors/follower/envelope-follower-processor.js\nregisterProcessor(\"envelope-follower-processor\", class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n {\n name: \"inputGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 10,\n automationRate: \"k-rate\"\n },\n {\n name: \"outputGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 10,\n automationRate: \"k-rate\"\n },\n {\n name: \"attack\",\n defaultValue: .003,\n minValue: .001,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n {\n name: \"release\",\n defaultValue: .05,\n minValue: .001,\n maxValue: 5,\n automationRate: \"k-rate\"\n }\n ];\n }\n constructor() {\n super();\n this.envelope = 0;\n this.gateThreshold = .005;\n this.debugCounter = 0;\n this.port.postMessage({ type: \"initialized\" });\n }\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n const output = outputs[0];\n const channel = inputs[0][0];\n if (!input || !output || !channel || input.length === 0 || output.length === 0 || channel.length === 0) return true;\n const inChannel = input[0];\n if (!inChannel || inChannel.length === 0) return true;\n const attack = parameters.attack[0];\n const release = parameters.release[0];\n const inputGain = parameters.inputGain[0];\n const outputGain = parameters.outputGain[0];\n const attackCoeff = Math.exp(-1 / (attack * sampleRate));\n const releaseCoeff = Math.exp(-1 / (release * sampleRate));\n for (let sample = 0; sample < output[0].length; sample++) {\n const inputLevel = Math.abs((input[0][sample] || 0) * inputGain);\n if (inputLevel > 1e-6) {\n if (inputLevel > this.envelope) this.envelope = inputLevel + (this.envelope - inputLevel) * attackCoeff;\n else this.envelope = inputLevel + (this.envelope - inputLevel) * releaseCoeff;\n } else this.envelope *= releaseCoeff;\n if (this.envelope < this.gateThreshold) this.envelope = 0;\n const finalOutput = this.envelope * outputGain;\n for (let channel = 0; channel < output.length; channel++) output[channel][sample] = finalOutput;\n }\n return true;\n }\n});\n//#endregion\n", Tt = !1;
|
|
4683
|
+
}, wt = "//#region src/utils/search/findClosest.ts\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array index of the element which value is closest to the target value\n*/\nfunction findClosestIdx(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n if (sortedArray.length === 0) throw new Error(\"Array cannot be empty\");\n if (sortedArray.length === 1) return 0;\n const targetValue = target;\n const firstValue = getValue(sortedArray[0]);\n const lastValue = getValue(sortedArray[sortedArray.length - 1]);\n if (targetValue <= firstValue) return 0;\n if (targetValue >= lastValue) return sortedArray.length - 1;\n let left = 0;\n let right = sortedArray.length - 1;\n while (left < right - 1) {\n const mid = Math.floor((left + right) / 2);\n const midValue = getValue(sortedArray[mid]);\n if (midValue === targetValue) return mid;\n else if (midValue < targetValue) left = mid;\n else right = mid;\n }\n if (direction === \"left\") return left;\n if (direction === \"right\") return right;\n return getDistance(getValue(sortedArray[left]), targetValue) <= getDistance(getValue(sortedArray[right]), targetValue) ? left : right;\n}\n/**\n* Generic binary search that finds the closest element using a custom comparison function\n* @param sortedArray - Array sorted according to the compareValue function\n* @param target - Target value to search for\n* @param getValue - Function to extract comparison value from array elements (defaults to identity for number arrays)\n* @param getDistance - Optional function to calculate distance (defaults to absolute difference)\n* @returns The array element which value is closest to the target value\n*/\nfunction findClosest(sortedArray, target, direction = \"any\", getValue = (x) => x, getDistance = (a, b) => Math.abs(a - b)) {\n return sortedArray[findClosestIdx(sortedArray, target, direction, getValue, getDistance)];\n}\nvar SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS = Object.values({\n masterGain: {\n name: \"masterGain\",\n defaultValue: 1,\n minValue: 0,\n maxValue: 2,\n automationRate: \"k-rate\"\n },\n envGain: {\n name: \"envGain\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"a-rate\"\n },\n velocity: {\n name: \"velocity\",\n defaultValue: 100,\n minValue: 0,\n maxValue: 127,\n automationRate: \"k-rate\"\n },\n pan: {\n name: \"pan\",\n defaultValue: 0,\n minValue: -1,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n playbackRate: {\n name: \"playbackRate\",\n defaultValue: 1,\n minValue: .1,\n maxValue: 24,\n automationRate: \"a-rate\"\n },\n loopStart: {\n name: \"loopStart\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopEnd: {\n name: \"loopEnd\",\n defaultValue: 99999,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n startPoint: {\n name: \"startPoint\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n endPoint: {\n name: \"endPoint\",\n defaultValue: 9999,\n minValue: 0,\n maxValue: 9999,\n automationRate: \"k-rate\"\n },\n playbackPosition: {\n name: \"playbackPosition\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 99999,\n automationRate: \"k-rate\"\n },\n loopDurationDriftAmount: {\n name: \"loopDurationDriftAmount\",\n defaultValue: 0,\n minValue: 0,\n maxValue: 1,\n automationRate: \"k-rate\"\n },\n maxLoopCount: {\n name: \"maxLoopCount\",\n defaultValue: 999999,\n minValue: 1,\n maxValue: 999999,\n automationRate: \"k-rate\"\n },\n tempo: {\n name: \"tempo\",\n defaultValue: 120,\n minValue: 20,\n maxValue: 300,\n automationRate: \"k-rate\"\n }\n});\n//#endregion\n//#region src/worklets/processors/play/sample-player-processor.js\nvar SamplePlayerProcessor = class extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return SAMPLE_PLAYER_WORKLET_AUDIOPARAM_DESCRIPTORS;\n }\n constructor() {\n super();\n this.layers = [];\n this.layerGain = 1;\n this.minZeroCrossing = 0;\n this.maxZeroCrossing = 0;\n this.usePlaybackPosition = false;\n this.enableLoopSmoothing = true;\n this.enableAdaptiveDrift = true;\n this.enableAmplitudeCompensation = true;\n this.syncLoopToTempo = false;\n this.keytrackLoopAmount = 0;\n this.durationPreservation = {\n enabled: false,\n maxDriftSamples: Math.floor(sampleRate * .04),\n timelinePosition: 0,\n resetPending: false\n };\n this.PITCH_PRESERVATION_THRESHOLD = Math.floor(sampleRate * .061);\n this.AMPLITUDE_COMPENSATION_THRESHOLD = Math.floor(sampleRate / 16.35);\n this.port.onmessage = this.#handleMessage.bind(this);\n this.#resetState();\n this.port.postMessage({ type: \"initialized\" });\n }\n /** Authority layer. All range and duration math reads through this. */\n get buffer() {\n return this.layers[0] ?? null;\n }\n #handleMessage(event) {\n const { type, value, buffer, layers, timestamp, durationSeconds, zeroCrossings, 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 #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 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 * 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;
|
|
4684
4684
|
async function Et(e) {
|
|
4685
4685
|
if (Tt) return console.info("AudioWorklet processors already initialized, skipping"), {
|
|
4686
4686
|
success: !0,
|
|
@@ -167,7 +167,7 @@ var SamplePlayerProcessor = class extends AudioWorkletProcessor {
|
|
|
167
167
|
return this.layers[0] ?? null;
|
|
168
168
|
}
|
|
169
169
|
#handleMessage(event) {
|
|
170
|
-
const { type, value, buffer, layers, timestamp, durationSeconds, zeroCrossings,
|
|
170
|
+
const { type, value, buffer, layers, timestamp, durationSeconds, zeroCrossings, playbackDirection } = event.data;
|
|
171
171
|
switch (type) {
|
|
172
172
|
case "voice:reset":
|
|
173
173
|
this.#resetState();
|
|
@@ -287,8 +287,6 @@ var SamplePlayerProcessor = class extends AudioWorkletProcessor {
|
|
|
287
287
|
this.applyClickCompensation = true;
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
|
-
#clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
291
|
-
#clampZeroCrossing = (value) => this.#clamp(value, this.minZeroCrossing, this.maxZeroCrossing);
|
|
292
290
|
#findNearestZeroCrossing(position, direction = "any", maxDistance = null) {
|
|
293
291
|
if (!this.zeroCrossings || this.zeroCrossings.length === 0) return position;
|
|
294
292
|
const closestValue = findClosest(this.zeroCrossings, position, direction);
|
|
@@ -296,15 +294,6 @@ var SamplePlayerProcessor = class extends AudioWorkletProcessor {
|
|
|
296
294
|
return closestValue;
|
|
297
295
|
}
|
|
298
296
|
/**
|
|
299
|
-
* Convert normalized position (0-1) to sample index
|
|
300
|
-
* @param {number} normalizedPosition - Position as 0-1 value
|
|
301
|
-
* @returns {number} - Sample index
|
|
302
|
-
*/
|
|
303
|
-
#normalizedToSamples(normalizedPosition) {
|
|
304
|
-
if (!this.buffer || !this.buffer[0]) return 0;
|
|
305
|
-
return normalizedPosition * this.buffer[0].length;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
297
|
* Convert sample index to normalized position (0-1)
|
|
309
298
|
* @param {number} sampleIndex - Sample index
|
|
310
299
|
* @returns {number} - Normalized position 0-1
|
|
@@ -322,13 +311,6 @@ var SamplePlayerProcessor = class extends AudioWorkletProcessor {
|
|
|
322
311
|
return Math.max(0, Math.min(1, midiVelocity / 127));
|
|
323
312
|
}
|
|
324
313
|
/**
|
|
325
|
-
* Get buffer duration in seconds
|
|
326
|
-
* @returns {number} - Buffer duration in seconds
|
|
327
|
-
*/
|
|
328
|
-
#getBufferDurationSeconds() {
|
|
329
|
-
return (this.buffer?.[0]?.length || 0) / sampleRate;
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
314
|
* Calculate musical note durations in samples for given tempo
|
|
333
315
|
* @param {number} tempo - BPM
|
|
334
316
|
* @returns {Object} - Musical note durations in samples
|
|
@@ -686,7 +668,7 @@ var RandomNoiseProcessor = class extends AudioWorkletProcessor {
|
|
|
686
668
|
calculateAlpha(frequency) {
|
|
687
669
|
return frequency / (frequency + sampleRate / (2 * Math.PI));
|
|
688
670
|
}
|
|
689
|
-
process(inputs, outputs,
|
|
671
|
+
process(inputs, outputs, _parameters) {
|
|
690
672
|
outputs[0].forEach((channel) => {
|
|
691
673
|
for (let i = 0; i < channel.length; i++) {
|
|
692
674
|
const noise = Math.random() * 2 - 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kidlib/web-audio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "High-level Web Audio primitives for musical instruments and tools.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"changeset": "vp exec changeset",
|
|
53
53
|
"bump": "vp exec changeset version",
|
|
54
54
|
"release": "vp check && vp run test:all && vp exec changeset publish",
|
|
55
|
-
"build:worklets": "
|
|
55
|
+
"build:worklets": "node build-processors.js",
|
|
56
56
|
"build": "vp run clean && vp build",
|
|
57
|
-
"watch:processors": "
|
|
57
|
+
"watch:processors": "node watch-processors.js",
|
|
58
58
|
"watch": "concurrently \"vp test watch\" \"vp build --watch\" \"vp run watch:processors\"",
|
|
59
59
|
"clean": "rimraf dist",
|
|
60
60
|
"test": "vp test run",
|