gitframes 1.2.35
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/LICENCE +202 -0
- package/README.md +77 -0
- package/dist/audio/index.d.mts +2 -0
- package/dist/audio/index.mjs +6 -0
- package/dist/audio-C0YslYEG.d.mts +32 -0
- package/dist/audio-C0YslYEG.d.mts.map +1 -0
- package/dist/audio-C4kiPbrV.mjs +1866 -0
- package/dist/audio-C4kiPbrV.mjs.map +1 -0
- package/dist/audio-CiXUMGyk.mjs +578 -0
- package/dist/audio-CiXUMGyk.mjs.map +1 -0
- package/dist/chunk-GMkBE123.mjs +47 -0
- package/dist/client-Dyypr8tt.mjs +66 -0
- package/dist/client-Dyypr8tt.mjs.map +1 -0
- package/dist/dist-C37594c6.mjs +27195 -0
- package/dist/dist-C37594c6.mjs.map +1 -0
- package/dist/dist-CMF9eNLB.mjs +4062 -0
- package/dist/dist-CMF9eNLB.mjs.map +1 -0
- package/dist/dist-cjs--KIl7Gqy.mjs +6 -0
- package/dist/dist-cjs-B-_4TVjP.mjs +66 -0
- package/dist/dist-cjs-B-_4TVjP.mjs.map +1 -0
- package/dist/dist-cjs-B3TIyZ3K.mjs +437 -0
- package/dist/dist-cjs-B3TIyZ3K.mjs.map +1 -0
- package/dist/dist-cjs-Bsz2eGzl.mjs +9069 -0
- package/dist/dist-cjs-Bsz2eGzl.mjs.map +1 -0
- package/dist/dist-cjs-BuJ2FXob.mjs +181 -0
- package/dist/dist-cjs-BuJ2FXob.mjs.map +1 -0
- package/dist/dist-cjs-C-FMsqmo.mjs +1091 -0
- package/dist/dist-cjs-C-FMsqmo.mjs.map +1 -0
- package/dist/dist-cjs-C4b9TCXd.mjs +48 -0
- package/dist/dist-cjs-C4b9TCXd.mjs.map +1 -0
- package/dist/dist-cjs-C817PNLc.mjs +51 -0
- package/dist/dist-cjs-C817PNLc.mjs.map +1 -0
- package/dist/dist-cjs-CBnnS4k8.mjs +43 -0
- package/dist/dist-cjs-CBnnS4k8.mjs.map +1 -0
- package/dist/dist-cjs-CWbeD0Mu.mjs +64 -0
- package/dist/dist-cjs-CWbeD0Mu.mjs.map +1 -0
- package/dist/dist-cjs-ChXEJ1ro.mjs +85 -0
- package/dist/dist-cjs-ChXEJ1ro.mjs.map +1 -0
- package/dist/dist-cjs-CslF8Dju.mjs +153 -0
- package/dist/dist-cjs-CslF8Dju.mjs.map +1 -0
- package/dist/dist-cjs-D52jYyEm.mjs +98 -0
- package/dist/dist-cjs-D52jYyEm.mjs.map +1 -0
- package/dist/dist-cjs-DhWYgNfa.mjs +209 -0
- package/dist/dist-cjs-DhWYgNfa.mjs.map +1 -0
- package/dist/dist-cjs-DssYqayv.mjs +5015 -0
- package/dist/dist-cjs-DssYqayv.mjs.map +1 -0
- package/dist/dist-cjs-Du6DnSjo.mjs +137 -0
- package/dist/dist-cjs-Du6DnSjo.mjs.map +1 -0
- package/dist/dist-cjs-b-oBQaxe.mjs +323 -0
- package/dist/dist-cjs-b-oBQaxe.mjs.map +1 -0
- package/dist/effects/index.d.mts +2 -0
- package/dist/effects/index.mjs +4 -0
- package/dist/effects-4t4qk-BR.mjs +21130 -0
- package/dist/effects-4t4qk-BR.mjs.map +1 -0
- package/dist/event-streams-B0YUBtyM.mjs +204 -0
- package/dist/event-streams-B0YUBtyM.mjs.map +1 -0
- package/dist/fonts/index.d.mts +2 -0
- package/dist/fonts/index.mjs +3 -0
- package/dist/fonts-Dn18r_ST.mjs +188 -0
- package/dist/fonts-Dn18r_ST.mjs.map +1 -0
- package/dist/index-B1557AUi.d.mts +928 -0
- package/dist/index-B1557AUi.d.mts.map +1 -0
- package/dist/index-DSaMXGoA.d.mts +65 -0
- package/dist/index-DSaMXGoA.d.mts.map +1 -0
- package/dist/index-FICUHo4P.d.mts +69 -0
- package/dist/index-FICUHo4P.d.mts.map +1 -0
- package/dist/index-YZsTjqut.d.mts +147 -0
- package/dist/index-YZsTjqut.d.mts.map +1 -0
- package/dist/index.d.mts +736 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7135 -0
- package/dist/index.mjs.map +1 -0
- package/dist/package-5RsA21g-.mjs +6 -0
- package/dist/package-5RsA21g-.mjs.map +1 -0
- package/dist/react/index.d.mts +3 -0
- package/dist/react/index.mjs +4 -0
- package/dist/renderer/index.d.mts +2 -0
- package/dist/renderer/index.mjs +6 -0
- package/dist/renderer-DVi31fSv.mjs +23274 -0
- package/dist/renderer-DVi31fSv.mjs.map +1 -0
- package/dist/renderers-CLJQRPVU-CMZnHNK0.mjs +49 -0
- package/dist/renderers-CLJQRPVU-CMZnHNK0.mjs.map +1 -0
- package/dist/signals/index.d.mts +9 -0
- package/dist/signals/index.mjs +8 -0
- package/dist/signals-BB_kdndF.mjs +227 -0
- package/dist/signals-BB_kdndF.mjs.map +1 -0
- package/dist/signin-CVUAIuED.mjs +865 -0
- package/dist/signin-CVUAIuED.mjs.map +1 -0
- package/dist/sso-oidc-D1GnNQYW.mjs +1051 -0
- package/dist/sso-oidc-D1GnNQYW.mjs.map +1 -0
- package/dist/sts-Bz-e3kNW.mjs +6 -0
- package/dist/sts-I4KhuCBs.mjs +1395 -0
- package/dist/sts-I4KhuCBs.mjs.map +1 -0
- package/package.json +174 -0
|
@@ -0,0 +1,1866 @@
|
|
|
1
|
+
import { F as defineRenderer } from "./dist-CMF9eNLB.mjs";
|
|
2
|
+
import { T as WebGPUAudioProcessor } from "./dist-C37594c6.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../../nodes/node-audio-compressor/dist/renderers-BxN6ejmd.mjs
|
|
5
|
+
const PARAM_ORDER$5 = [
|
|
6
|
+
"threshold",
|
|
7
|
+
"ratio",
|
|
8
|
+
"attack",
|
|
9
|
+
"release",
|
|
10
|
+
"knee",
|
|
11
|
+
"makeupGain"
|
|
12
|
+
];
|
|
13
|
+
const COMPRESSOR_SHADER_TEMPLATE = () => `
|
|
14
|
+
struct Uniforms {
|
|
15
|
+
sampleRate : f32,
|
|
16
|
+
threshold : f32,
|
|
17
|
+
ratio : f32,
|
|
18
|
+
attack : f32,
|
|
19
|
+
release : f32,
|
|
20
|
+
knee : f32,
|
|
21
|
+
makeupGain : f32,
|
|
22
|
+
hasThresholdSig : f32,
|
|
23
|
+
hasRatioSig : f32,
|
|
24
|
+
hasAttackSig : f32,
|
|
25
|
+
hasReleaseSig : f32,
|
|
26
|
+
hasKneeSig : f32,
|
|
27
|
+
hasMakeupGainSig : f32,
|
|
28
|
+
baseTime : f32,
|
|
29
|
+
frame : f32,
|
|
30
|
+
numSamples : f32,
|
|
31
|
+
numChannels : f32,
|
|
32
|
+
pad1 : f32,
|
|
33
|
+
pad2 : f32,
|
|
34
|
+
pad3 : f32,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
38
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
39
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
40
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>; // state[0] = envelope
|
|
41
|
+
@group(0) @binding(4) var<storage, read> thresholdSignal : array<f32>;
|
|
42
|
+
@group(0) @binding(5) var<storage, read> ratioSignal : array<f32>;
|
|
43
|
+
@group(0) @binding(6) var<storage, read> attackSignal : array<f32>;
|
|
44
|
+
@group(0) @binding(7) var<storage, read> releaseSignal : array<f32>;
|
|
45
|
+
@group(0) @binding(8) var<storage, read> kneeSignal : array<f32>;
|
|
46
|
+
@group(0) @binding(9) var<storage, read> makeupGainSignal : array<f32>;
|
|
47
|
+
|
|
48
|
+
fn log10(x: f32) -> f32 {
|
|
49
|
+
return log(x) * 0.4342944819032518f;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn tanh(x: f32) -> f32 {
|
|
53
|
+
let exp2x = exp(2.0f * x);
|
|
54
|
+
return (exp2x - 1.0f) / (exp2x + 1.0f);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn is_nan_or_inf(v: f32) -> bool {
|
|
58
|
+
return (v != v) || (abs(v) > 3.402823466e+38f);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@compute @workgroup_size(1)
|
|
62
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
63
|
+
if (gid.x != 0u) { return; }
|
|
64
|
+
|
|
65
|
+
let numSamples = u32(u.numSamples);
|
|
66
|
+
let numChannels = u32(u.numChannels);
|
|
67
|
+
|
|
68
|
+
var envelope = state[0];
|
|
69
|
+
if (is_nan_or_inf(envelope)) {
|
|
70
|
+
envelope = 0.0f;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
74
|
+
// 1. Dynamic Parameter Modulation & Anti-Explosion Clamping
|
|
75
|
+
var effThreshold = u.threshold;
|
|
76
|
+
if (u.hasThresholdSig > 0.5f) {
|
|
77
|
+
effThreshold = thresholdSignal[index];
|
|
78
|
+
}
|
|
79
|
+
effThreshold = clamp(effThreshold, -120.0f, 0.0f);
|
|
80
|
+
|
|
81
|
+
var effRatio = u.ratio;
|
|
82
|
+
if (u.hasRatioSig > 0.5f) {
|
|
83
|
+
effRatio = ratioSignal[index];
|
|
84
|
+
}
|
|
85
|
+
effRatio = clamp(effRatio, 1.0f, 100.0f);
|
|
86
|
+
|
|
87
|
+
var effAttack = u.attack;
|
|
88
|
+
if (u.hasAttackSig > 0.5f) {
|
|
89
|
+
effAttack = attackSignal[index];
|
|
90
|
+
}
|
|
91
|
+
effAttack = clamp(effAttack, 0.0001f, 10.0f);
|
|
92
|
+
|
|
93
|
+
var effRelease = u.release;
|
|
94
|
+
if (u.hasReleaseSig > 0.5f) {
|
|
95
|
+
effRelease = releaseSignal[index];
|
|
96
|
+
}
|
|
97
|
+
effRelease = clamp(effRelease, 0.001f, 10.0f);
|
|
98
|
+
|
|
99
|
+
var effKnee = u.knee;
|
|
100
|
+
if (u.hasKneeSig > 0.5f) {
|
|
101
|
+
effKnee = kneeSignal[index];
|
|
102
|
+
}
|
|
103
|
+
effKnee = clamp(effKnee, 0.0f, 40.0f);
|
|
104
|
+
|
|
105
|
+
var effMakeupGain = u.makeupGain;
|
|
106
|
+
if (u.hasMakeupGainSig > 0.5f) {
|
|
107
|
+
effMakeupGain = makeupGainSignal[index];
|
|
108
|
+
}
|
|
109
|
+
effMakeupGain = clamp(effMakeupGain, -60.0f, 36.0f);
|
|
110
|
+
|
|
111
|
+
// 2. Safe Attack / Release Coefficients
|
|
112
|
+
let attackCoeff = clamp(1.0f - exp(-1.0f / (u.sampleRate * effAttack)), 0.00001f, 1.0f);
|
|
113
|
+
let releaseCoeff = clamp(1.0f - exp(-1.0f / (u.sampleRate * effRelease)), 0.00001f, 1.0f);
|
|
114
|
+
|
|
115
|
+
// 3. Peak Detection with NaN / Inf sanitization
|
|
116
|
+
var peak = 0.0f;
|
|
117
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
118
|
+
var val = abs(inputChannels[c * numSamples + index]);
|
|
119
|
+
if (is_nan_or_inf(val)) {
|
|
120
|
+
val = 0.0f;
|
|
121
|
+
}
|
|
122
|
+
if (val > peak) {
|
|
123
|
+
peak = val;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 4. Convert Peak to dBFS
|
|
128
|
+
let dB = 20.0f * log10(max(peak, 1e-9f));
|
|
129
|
+
|
|
130
|
+
// 5. Compute target reduction (soft-knee & ratio)
|
|
131
|
+
var targetReductiondB = 0.0f;
|
|
132
|
+
if (effKnee > 0.0f) {
|
|
133
|
+
let x = dB - effThreshold;
|
|
134
|
+
if (x <= -effKnee / 2.0f) {
|
|
135
|
+
targetReductiondB = 0.0f;
|
|
136
|
+
} else if (x >= effKnee / 2.0f) {
|
|
137
|
+
targetReductiondB = (dB - effThreshold) * (1.0f - 1.0f / effRatio);
|
|
138
|
+
} else {
|
|
139
|
+
let kneeTerm = x + effKnee / 2.0f;
|
|
140
|
+
targetReductiondB = ((kneeTerm * kneeTerm) / (2.0f * effKnee)) * (1.0f - 1.0f / effRatio);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
if (dB > effThreshold) {
|
|
144
|
+
targetReductiondB = (dB - effThreshold) * (1.0f - 1.0f / effRatio);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
targetReductiondB = clamp(targetReductiondB, 0.0f, 120.0f);
|
|
149
|
+
|
|
150
|
+
// 6. Envelope Smoothing
|
|
151
|
+
let targetGaindB = -targetReductiondB;
|
|
152
|
+
if (targetGaindB < envelope) {
|
|
153
|
+
envelope += (targetGaindB - envelope) * attackCoeff;
|
|
154
|
+
} else {
|
|
155
|
+
envelope += (targetGaindB - envelope) * releaseCoeff;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (is_nan_or_inf(envelope)) {
|
|
159
|
+
envelope = 0.0f;
|
|
160
|
+
}
|
|
161
|
+
envelope = clamp(envelope, -120.0f, 0.0f);
|
|
162
|
+
|
|
163
|
+
// 7. Apply linear gain and soft clipping
|
|
164
|
+
let totalGaindB = clamp(envelope + effMakeupGain, -120.0f, 36.0f);
|
|
165
|
+
let gainLinear = clamp(pow(10.0f, totalGaindB / 20.0f), 0.0f, 63.1f);
|
|
166
|
+
|
|
167
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
168
|
+
let sampleIndex = c * numSamples + index;
|
|
169
|
+
var inVal = inputChannels[sampleIndex];
|
|
170
|
+
if (is_nan_or_inf(inVal)) {
|
|
171
|
+
inVal = 0.0f;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
var outVal = inVal * gainLinear;
|
|
175
|
+
if (is_nan_or_inf(outVal)) {
|
|
176
|
+
outVal = 0.0f;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Soft clipping at ±1.0 with smooth tanh transition starting at ±0.9
|
|
180
|
+
if (outVal > 1.0f) {
|
|
181
|
+
outVal = 1.0f;
|
|
182
|
+
} else if (outVal < -1.0f) {
|
|
183
|
+
outVal = -1.0f;
|
|
184
|
+
} else if (outVal > 0.9f) {
|
|
185
|
+
outVal = 0.9f + 0.1f * tanh((outVal - 0.9f) / 0.1f);
|
|
186
|
+
} else if (outVal < -0.9f) {
|
|
187
|
+
outVal = -0.9f + 0.1f * tanh((outVal + 0.9f) / 0.1f);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
outputChannels[sampleIndex] = outVal;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (is_nan_or_inf(envelope)) {
|
|
195
|
+
state[0] = 0.0f;
|
|
196
|
+
} else {
|
|
197
|
+
state[0] = clamp(envelope, -120.0f, 0.0f);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
const compressorAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
|
|
202
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Dynamics Compressor.");
|
|
203
|
+
const op = virtualMedia.operation || {};
|
|
204
|
+
const inputs = op.inputs || {};
|
|
205
|
+
const threshold = typeof op.threshold === "number" ? op.threshold : -24;
|
|
206
|
+
const ratio = typeof op.ratio === "number" ? op.ratio : 4;
|
|
207
|
+
const attack = typeof op.attack === "number" ? op.attack : .003;
|
|
208
|
+
const release = typeof op.release === "number" ? op.release : .25;
|
|
209
|
+
const knee = typeof op.knee === "number" ? op.knee : 6;
|
|
210
|
+
const makeupGain = typeof op.makeupGain === "number" ? op.makeupGain : 0;
|
|
211
|
+
const isHandleConnected = (handleIdKey) => {
|
|
212
|
+
const handleId = op[handleIdKey];
|
|
213
|
+
if (typeof handleId !== "string" || !handleId) return false;
|
|
214
|
+
const input = inputs[handleId];
|
|
215
|
+
return Boolean(input?.connectionValid && input.outputItem && (input.outputItem.type === "Signal" || input.outputItem.type === "Number"));
|
|
216
|
+
};
|
|
217
|
+
const hasThresholdSig = isHandleConnected("thresholdHandleId") ? 1 : 0;
|
|
218
|
+
const hasRatioSig = isHandleConnected("ratioHandleId") ? 1 : 0;
|
|
219
|
+
const hasAttackSig = isHandleConnected("attackHandleId") ? 1 : 0;
|
|
220
|
+
const hasReleaseSig = isHandleConnected("releaseHandleId") ? 1 : 0;
|
|
221
|
+
const hasKneeSig = isHandleConnected("kneeHandleId") ? 1 : 0;
|
|
222
|
+
const hasMakeupGainSig = isHandleConnected("makeupGainHandleId") ? 1 : 0;
|
|
223
|
+
const numChannels = channels.length;
|
|
224
|
+
if (numChannels === 0) return;
|
|
225
|
+
const numSamples = channels[0].length;
|
|
226
|
+
if (numSamples === 0) return;
|
|
227
|
+
const nodeId = op.id || "audio-compressor";
|
|
228
|
+
const frame = ctx.frame ?? 0;
|
|
229
|
+
const fps = ctx.fps ?? 24;
|
|
230
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, virtualMedia, frame, fps, COMPRESSOR_SHADER_TEMPLATE, (t) => [
|
|
231
|
+
sampleRate,
|
|
232
|
+
threshold,
|
|
233
|
+
ratio,
|
|
234
|
+
attack,
|
|
235
|
+
release,
|
|
236
|
+
knee,
|
|
237
|
+
makeupGain,
|
|
238
|
+
hasThresholdSig,
|
|
239
|
+
hasRatioSig,
|
|
240
|
+
hasAttackSig,
|
|
241
|
+
hasReleaseSig,
|
|
242
|
+
hasKneeSig,
|
|
243
|
+
hasMakeupGainSig,
|
|
244
|
+
t,
|
|
245
|
+
frame,
|
|
246
|
+
numSamples,
|
|
247
|
+
numChannels,
|
|
248
|
+
0,
|
|
249
|
+
0,
|
|
250
|
+
0
|
|
251
|
+
], 20, 1, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs, void 0, void 0, PARAM_ORDER$5);
|
|
252
|
+
};
|
|
253
|
+
var renderers_default$6 = defineRenderer({ audioProcessor: compressorAudioProcessor });
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region ../../nodes/node-audio-delay/dist/renderers-DxDAZF2y.mjs
|
|
257
|
+
const PARAM_ORDER$4 = [
|
|
258
|
+
"delayTime",
|
|
259
|
+
"feedback",
|
|
260
|
+
"wet",
|
|
261
|
+
"dry"
|
|
262
|
+
];
|
|
263
|
+
const DELAY_SHADER_TEMPLATE = () => `
|
|
264
|
+
struct Uniforms {
|
|
265
|
+
sampleRate : f32,
|
|
266
|
+
delaySec : f32,
|
|
267
|
+
feedback : f32,
|
|
268
|
+
wet : f32,
|
|
269
|
+
dry : f32,
|
|
270
|
+
pingPong : f32,
|
|
271
|
+
bufferLength : f32,
|
|
272
|
+
hasDelayTimeSig : f32,
|
|
273
|
+
hasFeedbackSig : f32,
|
|
274
|
+
hasWetSig : f32,
|
|
275
|
+
hasDrySig : f32,
|
|
276
|
+
baseTime : f32,
|
|
277
|
+
frame : f32,
|
|
278
|
+
numSamples : f32,
|
|
279
|
+
numChannels : f32,
|
|
280
|
+
pad1 : f32,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
284
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
285
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
286
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>;
|
|
287
|
+
@group(0) @binding(4) var<storage, read> delayTimeSignal : array<f32>;
|
|
288
|
+
@group(0) @binding(5) var<storage, read> feedbackSignal : array<f32>;
|
|
289
|
+
@group(0) @binding(6) var<storage, read> wetSignal : array<f32>;
|
|
290
|
+
@group(0) @binding(7) var<storage, read> drySignal : array<f32>;
|
|
291
|
+
|
|
292
|
+
fn tanh(x: f32) -> f32 {
|
|
293
|
+
let exp2x = exp(2.0f * x);
|
|
294
|
+
return (exp2x - 1.0f) / (exp2x + 1.0f);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fn is_nan_or_inf(v: f32) -> bool {
|
|
298
|
+
return (v != v) || (abs(v) > 3.402823466e+38f);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@compute @workgroup_size(1)
|
|
302
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
303
|
+
if (gid.x != 0u) { return; }
|
|
304
|
+
|
|
305
|
+
let numSamples = u32(u.numSamples);
|
|
306
|
+
let numChannels = u32(u.numChannels);
|
|
307
|
+
let bufferLength = u32(u.bufferLength);
|
|
308
|
+
|
|
309
|
+
var writeIndex = u32(state[0]);
|
|
310
|
+
if (is_nan_or_inf(state[0])) {
|
|
311
|
+
writeIndex = 0u;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
315
|
+
// 1. Dynamic Parameter Modulation & Anti-Explosion Clamping
|
|
316
|
+
var effDelaySec = u.delaySec;
|
|
317
|
+
if (u.hasDelayTimeSig > 0.5f) {
|
|
318
|
+
effDelaySec = delayTimeSignal[index];
|
|
319
|
+
}
|
|
320
|
+
effDelaySec = clamp(effDelaySec, 0.001f, 5.0f);
|
|
321
|
+
|
|
322
|
+
var effFeedback = u.feedback;
|
|
323
|
+
if (u.hasFeedbackSig > 0.5f) {
|
|
324
|
+
effFeedback = feedbackSignal[index];
|
|
325
|
+
}
|
|
326
|
+
effFeedback = clamp(effFeedback, 0.0f, 0.95f);
|
|
327
|
+
|
|
328
|
+
var effWet = u.wet;
|
|
329
|
+
if (u.hasWetSig > 0.5f) {
|
|
330
|
+
effWet = wetSignal[index];
|
|
331
|
+
}
|
|
332
|
+
effWet = clamp(effWet, 0.0f, 2.0f);
|
|
333
|
+
|
|
334
|
+
var effDry = u.dry;
|
|
335
|
+
if (u.hasDrySig > 0.5f) {
|
|
336
|
+
effDry = drySignal[index];
|
|
337
|
+
}
|
|
338
|
+
effDry = clamp(effDry, 0.0f, 2.0f);
|
|
339
|
+
|
|
340
|
+
// 2. Safe Dynamic Read Index
|
|
341
|
+
let readOffset = u32(clamp(effDelaySec * u.sampleRate, 1.0f, f32(bufferLength - 1u)));
|
|
342
|
+
let readIndex = (writeIndex + bufferLength - readOffset) % bufferLength;
|
|
343
|
+
|
|
344
|
+
if (numChannels == 1u || u.pingPong == 0.0f) {
|
|
345
|
+
// Standard Delay
|
|
346
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
347
|
+
let sampleIndex = c * numSamples + index;
|
|
348
|
+
var inputVal = inputChannels[sampleIndex];
|
|
349
|
+
if (is_nan_or_inf(inputVal)) {
|
|
350
|
+
inputVal = 0.0f;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let stateOffset = c * bufferLength + 1u;
|
|
354
|
+
var delayedSample = state[stateOffset + readIndex];
|
|
355
|
+
if (is_nan_or_inf(delayedSample)) {
|
|
356
|
+
delayedSample = 0.0f;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
var nextStateVal = inputVal + delayedSample * effFeedback;
|
|
360
|
+
if (is_nan_or_inf(nextStateVal)) {
|
|
361
|
+
nextStateVal = 0.0f;
|
|
362
|
+
}
|
|
363
|
+
state[stateOffset + writeIndex] = clamp(nextStateVal, -4.0f, 4.0f);
|
|
364
|
+
|
|
365
|
+
var outVal = effDry * inputVal + effWet * delayedSample;
|
|
366
|
+
if (is_nan_or_inf(outVal)) {
|
|
367
|
+
outVal = 0.0f;
|
|
368
|
+
}
|
|
369
|
+
if (outVal > 1.0f) {
|
|
370
|
+
outVal = 1.0f;
|
|
371
|
+
} else if (outVal < -1.0f) {
|
|
372
|
+
outVal = -1.0f;
|
|
373
|
+
} else if (outVal > 0.9f) {
|
|
374
|
+
outVal = 0.9f + 0.1f * tanh((outVal - 0.9f) / 0.1f);
|
|
375
|
+
} else if (outVal < -0.9f) {
|
|
376
|
+
outVal = -0.9f + 0.1f * tanh((outVal + 0.9f) / 0.1f);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
outputChannels[sampleIndex] = outVal;
|
|
380
|
+
}
|
|
381
|
+
} else {
|
|
382
|
+
// Stereo Ping-Pong Delay
|
|
383
|
+
var sampleL = inputChannels[index];
|
|
384
|
+
if (is_nan_or_inf(sampleL)) { sampleL = 0.0f; }
|
|
385
|
+
|
|
386
|
+
var sampleR = inputChannels[numSamples + index];
|
|
387
|
+
if (is_nan_or_inf(sampleR)) { sampleR = 0.0f; }
|
|
388
|
+
|
|
389
|
+
var delayedL = state[1u + readIndex];
|
|
390
|
+
if (is_nan_or_inf(delayedL)) { delayedL = 0.0f; }
|
|
391
|
+
|
|
392
|
+
var delayedR = state[bufferLength + 1u + readIndex];
|
|
393
|
+
if (is_nan_or_inf(delayedR)) { delayedR = 0.0f; }
|
|
394
|
+
|
|
395
|
+
// Ping-pong cross-feedback
|
|
396
|
+
var nextL = sampleL + delayedR * effFeedback;
|
|
397
|
+
var nextR = sampleR + delayedL * effFeedback;
|
|
398
|
+
if (is_nan_or_inf(nextL)) { nextL = 0.0f; }
|
|
399
|
+
if (is_nan_or_inf(nextR)) { nextR = 0.0f; }
|
|
400
|
+
|
|
401
|
+
state[1u + writeIndex] = clamp(nextL, -4.0f, 4.0f);
|
|
402
|
+
state[bufferLength + 1u + writeIndex] = clamp(nextR, -4.0f, 4.0f);
|
|
403
|
+
|
|
404
|
+
var outL = effDry * sampleL + effWet * delayedL;
|
|
405
|
+
var outR = effDry * sampleR + effWet * delayedR;
|
|
406
|
+
if (is_nan_or_inf(outL)) { outL = 0.0f; }
|
|
407
|
+
if (is_nan_or_inf(outR)) { outR = 0.0f; }
|
|
408
|
+
|
|
409
|
+
if (outL > 1.0f) { outL = 1.0f; } else if (outL < -1.0f) { outL = -1.0f; }
|
|
410
|
+
else if (outL > 0.9f) { outL = 0.9f + 0.1f * tanh((outL - 0.9f) / 0.1f); }
|
|
411
|
+
else if (outL < -0.9f) { outL = -0.9f + 0.1f * tanh((outL + 0.9f) / 0.1f); }
|
|
412
|
+
|
|
413
|
+
if (outR > 1.0f) { outR = 1.0f; } else if (outR < -1.0f) { outR = -1.0f; }
|
|
414
|
+
else if (outR > 0.9f) { outR = 0.9f + 0.1f * tanh((outR - 0.9f) / 0.1f); }
|
|
415
|
+
else if (outR < -0.9f) { outR = -0.9f + 0.1f * tanh((outR + 0.9f) / 0.1f); }
|
|
416
|
+
|
|
417
|
+
outputChannels[index] = outL;
|
|
418
|
+
outputChannels[numSamples + index] = outR;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
writeIndex = (writeIndex + 1u) % bufferLength;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
state[0] = f32(writeIndex);
|
|
425
|
+
}
|
|
426
|
+
`;
|
|
427
|
+
const delayAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
|
|
428
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Audio Delay.");
|
|
429
|
+
const op = virtualMedia.operation || {};
|
|
430
|
+
const inputs = op.inputs || {};
|
|
431
|
+
const numChannels = channels.length;
|
|
432
|
+
if (numChannels === 0) return;
|
|
433
|
+
const numSamples = channels[0].length;
|
|
434
|
+
if (numSamples === 0) return;
|
|
435
|
+
const delaySec = Math.max(.001, Math.min(typeof op.delayTime === "number" ? op.delayTime : .25, 5));
|
|
436
|
+
const feedbackInternal = Math.max(0, Math.min(typeof op.feedback === "number" ? op.feedback : .4, .95));
|
|
437
|
+
const wetInternal = Math.max(0, Math.min(typeof op.wet === "number" ? op.wet : .3, 1));
|
|
438
|
+
const dryInternal = Math.max(0, Math.min(typeof op.dry === "number" ? op.dry : 1, 1));
|
|
439
|
+
const pingPongInternal = typeof op.pingPong === "boolean" ? op.pingPong : false;
|
|
440
|
+
const isHandleConnected = (handleIdKey) => {
|
|
441
|
+
const handleId = op[handleIdKey];
|
|
442
|
+
if (typeof handleId !== "string" || !handleId) return false;
|
|
443
|
+
const input = inputs[handleId];
|
|
444
|
+
return Boolean(input?.connectionValid && input.outputItem && (input.outputItem.type === "Signal" || input.outputItem.type === "Number"));
|
|
445
|
+
};
|
|
446
|
+
const hasDelayTimeSig = isHandleConnected("delayTimeHandleId") ? 1 : 0;
|
|
447
|
+
const hasFeedbackSig = isHandleConnected("feedbackHandleId") ? 1 : 0;
|
|
448
|
+
const hasWetSig = isHandleConnected("wetHandleId") ? 1 : 0;
|
|
449
|
+
const hasDrySig = isHandleConnected("dryHandleId") ? 1 : 0;
|
|
450
|
+
const bufferLength = Math.ceil(5 * sampleRate);
|
|
451
|
+
const nodeId = op.id || "audio-delay";
|
|
452
|
+
const frame = ctx.frame ?? 0;
|
|
453
|
+
const fps = ctx.fps ?? 24;
|
|
454
|
+
const stateFloatCount = numChannels * bufferLength + 1;
|
|
455
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, virtualMedia, frame, fps, DELAY_SHADER_TEMPLATE, () => [
|
|
456
|
+
sampleRate,
|
|
457
|
+
delaySec,
|
|
458
|
+
feedbackInternal,
|
|
459
|
+
wetInternal,
|
|
460
|
+
dryInternal,
|
|
461
|
+
pingPongInternal ? 1 : 0,
|
|
462
|
+
bufferLength,
|
|
463
|
+
hasDelayTimeSig,
|
|
464
|
+
hasFeedbackSig,
|
|
465
|
+
hasWetSig,
|
|
466
|
+
hasDrySig,
|
|
467
|
+
0,
|
|
468
|
+
frame,
|
|
469
|
+
numSamples,
|
|
470
|
+
numChannels,
|
|
471
|
+
0
|
|
472
|
+
], 16, stateFloatCount, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs, void 0, void 0, PARAM_ORDER$4);
|
|
473
|
+
};
|
|
474
|
+
var renderers_default$5 = defineRenderer({ audioProcessor: delayAudioProcessor });
|
|
475
|
+
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region ../../nodes/node-audio-fade/dist/renderers-HxDOLxTs.mjs
|
|
478
|
+
const PARAM_ORDER$3 = ["fadeInDuration", "fadeOutDuration"];
|
|
479
|
+
const FADE_SHADER_TEMPLATE = () => `
|
|
480
|
+
struct Uniforms {
|
|
481
|
+
sampleRate : f32,
|
|
482
|
+
fadeInDuration : f32,
|
|
483
|
+
fadeOutDuration : f32,
|
|
484
|
+
fadeInCurve : f32,
|
|
485
|
+
fadeOutCurve : f32,
|
|
486
|
+
clipDuration : f32,
|
|
487
|
+
hasFadeInDurationSig : f32,
|
|
488
|
+
hasFadeOutDurationSig : f32,
|
|
489
|
+
baseTime : f32,
|
|
490
|
+
frame : f32,
|
|
491
|
+
numSamples : f32,
|
|
492
|
+
numChannels : f32,
|
|
493
|
+
pad1 : f32,
|
|
494
|
+
pad2 : f32,
|
|
495
|
+
pad3 : f32,
|
|
496
|
+
pad4 : f32,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
500
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
501
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
502
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>; // state[0] = samplesProcessed
|
|
503
|
+
@group(0) @binding(4) var<storage, read> fadeInDurationSignal : array<f32>;
|
|
504
|
+
@group(0) @binding(5) var<storage, read> fadeOutDurationSignal : array<f32>;
|
|
505
|
+
|
|
506
|
+
fn applyCurve(p: f32, curve: f32) -> f32 {
|
|
507
|
+
let clampedP = max(0.0f, min(p, 1.0f));
|
|
508
|
+
if (curve == 1.0f) { // exponential
|
|
509
|
+
return clampedP * clampedP;
|
|
510
|
+
}
|
|
511
|
+
if (curve == 2.0f) { // scurve
|
|
512
|
+
return clampedP * clampedP * (3.0f - 2.0f * clampedP);
|
|
513
|
+
}
|
|
514
|
+
return clampedP; // linear
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
fn tanh(x: f32) -> f32 {
|
|
518
|
+
let exp2x = exp(2.0f * x);
|
|
519
|
+
return (exp2x - 1.0f) / (exp2x + 1.0f);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
fn is_nan_or_inf(v: f32) -> bool {
|
|
523
|
+
return (v != v) || (abs(v) > 3.402823466e+38f);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
@compute @workgroup_size(1)
|
|
527
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
528
|
+
if (gid.x != 0u) { return; }
|
|
529
|
+
|
|
530
|
+
let numSamples = u32(u.numSamples);
|
|
531
|
+
let numChannels = u32(u.numChannels);
|
|
532
|
+
|
|
533
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
534
|
+
let t = u.baseTime + f32(index) / u.sampleRate;
|
|
535
|
+
var gain = 1.0f;
|
|
536
|
+
|
|
537
|
+
var effFadeIn = u.fadeInDuration;
|
|
538
|
+
if (u.hasFadeInDurationSig > 0.5f) {
|
|
539
|
+
effFadeIn = fadeInDurationSignal[index];
|
|
540
|
+
}
|
|
541
|
+
effFadeIn = clamp(effFadeIn, 0.0f, 60.0f);
|
|
542
|
+
|
|
543
|
+
var effFadeOut = u.fadeOutDuration;
|
|
544
|
+
if (u.hasFadeOutDurationSig > 0.5f) {
|
|
545
|
+
effFadeOut = fadeOutDurationSignal[index];
|
|
546
|
+
}
|
|
547
|
+
effFadeOut = clamp(effFadeOut, 0.0f, 60.0f);
|
|
548
|
+
|
|
549
|
+
if (effFadeIn > 0.0f && t < effFadeIn) {
|
|
550
|
+
let p = t / effFadeIn;
|
|
551
|
+
gain = gain * applyCurve(p, u.fadeInCurve);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (effFadeOut > 0.0f && t > u.clipDuration - effFadeOut) {
|
|
555
|
+
let p = (u.clipDuration - t) / effFadeOut;
|
|
556
|
+
gain = gain * applyCurve(p, u.fadeOutCurve);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
gain = clamp(gain, 0.0f, 1.0f);
|
|
560
|
+
|
|
561
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
562
|
+
let sampleIndex = c * numSamples + index;
|
|
563
|
+
var inVal = inputChannels[sampleIndex];
|
|
564
|
+
if (is_nan_or_inf(inVal)) {
|
|
565
|
+
inVal = 0.0f;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
var outVal = inVal * gain;
|
|
569
|
+
if (is_nan_or_inf(outVal)) {
|
|
570
|
+
outVal = 0.0f;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (outVal > 1.0f) {
|
|
574
|
+
outVal = 1.0f;
|
|
575
|
+
} else if (outVal < -1.0f) {
|
|
576
|
+
outVal = -1.0f;
|
|
577
|
+
} else if (outVal > 0.9f) {
|
|
578
|
+
outVal = 0.9f + 0.1f * tanh((outVal - 0.9f) / 0.1f);
|
|
579
|
+
} else if (outVal < -0.9f) {
|
|
580
|
+
outVal = -0.9f + 0.1f * tanh((outVal + 0.9f) / 0.1f);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
outputChannels[sampleIndex] = outVal;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Dummy reference to keep state binding active
|
|
588
|
+
state[0] = state[0];
|
|
589
|
+
}
|
|
590
|
+
`;
|
|
591
|
+
const getCurveCode = (curve) => {
|
|
592
|
+
if (curve === "exponential") return 1;
|
|
593
|
+
if (curve === "scurve") return 2;
|
|
594
|
+
return 0;
|
|
595
|
+
};
|
|
596
|
+
const fadeAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
|
|
597
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Audio Fade.");
|
|
598
|
+
const op = virtualMedia.operation || {};
|
|
599
|
+
const inputs = op.inputs || {};
|
|
600
|
+
const numChannels = channels.length;
|
|
601
|
+
if (numChannels === 0) return;
|
|
602
|
+
const numSamples = channels[0].length;
|
|
603
|
+
if (numSamples === 0) return;
|
|
604
|
+
const fadeInDuration = Math.max(0, typeof op.fadeInDuration === "number" ? op.fadeInDuration : 0);
|
|
605
|
+
const fadeOutDuration = Math.max(0, typeof op.fadeOutDuration === "number" ? op.fadeOutDuration : 0);
|
|
606
|
+
const fadeInCurve = typeof op.fadeInCurve === "string" ? op.fadeInCurve : "linear";
|
|
607
|
+
const fadeOutCurve = typeof op.fadeOutCurve === "string" ? op.fadeOutCurve : "linear";
|
|
608
|
+
const clipDuration = (virtualMedia.metadata?.durationMs ?? 0) / 1e3;
|
|
609
|
+
let fadeIn = fadeInDuration;
|
|
610
|
+
let fadeOut = fadeOutDuration;
|
|
611
|
+
if (fadeIn + fadeOut > clipDuration) {
|
|
612
|
+
const half = clipDuration / 2;
|
|
613
|
+
fadeIn = Math.min(fadeIn, half);
|
|
614
|
+
fadeOut = Math.min(fadeOut, half);
|
|
615
|
+
}
|
|
616
|
+
const isHandleConnected = (handleIdKey) => {
|
|
617
|
+
const handleId = op[handleIdKey];
|
|
618
|
+
if (typeof handleId !== "string" || !handleId) return false;
|
|
619
|
+
const input = inputs[handleId];
|
|
620
|
+
return Boolean(input?.connectionValid && input.outputItem && (input.outputItem.type === "Signal" || input.outputItem.type === "Number"));
|
|
621
|
+
};
|
|
622
|
+
const hasFadeInDurationSig = isHandleConnected("fadeInDurationHandleId") ? 1 : 0;
|
|
623
|
+
const hasFadeOutDurationSig = isHandleConnected("fadeOutDurationHandleId") ? 1 : 0;
|
|
624
|
+
const nodeId = op.id || "audio-fade";
|
|
625
|
+
const frame = ctx.frame ?? 0;
|
|
626
|
+
const fps = ctx.fps ?? 24;
|
|
627
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, virtualMedia, frame, fps, FADE_SHADER_TEMPLATE, (chunkTimeSec) => {
|
|
628
|
+
const startFrame = typeof op.startFrame === "number" ? op.startFrame : frame;
|
|
629
|
+
const localTimeSec = ctx.elapsedMs !== void 0 ? ctx.elapsedMs / 1e3 + (chunkTimeSec - frame / fps) : Math.max(0, chunkTimeSec - startFrame / fps);
|
|
630
|
+
return [
|
|
631
|
+
sampleRate,
|
|
632
|
+
fadeIn,
|
|
633
|
+
fadeOut,
|
|
634
|
+
getCurveCode(fadeInCurve),
|
|
635
|
+
getCurveCode(fadeOutCurve),
|
|
636
|
+
clipDuration,
|
|
637
|
+
hasFadeInDurationSig,
|
|
638
|
+
hasFadeOutDurationSig,
|
|
639
|
+
localTimeSec,
|
|
640
|
+
frame,
|
|
641
|
+
numSamples,
|
|
642
|
+
numChannels,
|
|
643
|
+
0,
|
|
644
|
+
0,
|
|
645
|
+
0,
|
|
646
|
+
0
|
|
647
|
+
];
|
|
648
|
+
}, 16, 1, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs, void 0, void 0, PARAM_ORDER$3);
|
|
649
|
+
};
|
|
650
|
+
var renderers_default$4 = defineRenderer({ audioProcessor: fadeAudioProcessor });
|
|
651
|
+
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region ../../nodes/node-audio-noise-gate/dist/renderers-Dn1K3bcD.mjs
|
|
654
|
+
const PARAM_ORDER$2 = [
|
|
655
|
+
"threshold",
|
|
656
|
+
"attack",
|
|
657
|
+
"hold",
|
|
658
|
+
"release",
|
|
659
|
+
"range"
|
|
660
|
+
];
|
|
661
|
+
const NOISE_GATE_SHADER_TEMPLATE = () => `
|
|
662
|
+
struct Uniforms {
|
|
663
|
+
sampleRate : f32,
|
|
664
|
+
threshold : f32,
|
|
665
|
+
attack : f32,
|
|
666
|
+
hold : f32,
|
|
667
|
+
release : f32,
|
|
668
|
+
closedGain : f32,
|
|
669
|
+
windowLength : f32,
|
|
670
|
+
hasThresholdSig : f32,
|
|
671
|
+
hasAttackSig : f32,
|
|
672
|
+
hasHoldSig : f32,
|
|
673
|
+
hasReleaseSig : f32,
|
|
674
|
+
hasRangeSig : f32,
|
|
675
|
+
baseTime : f32,
|
|
676
|
+
frame : f32,
|
|
677
|
+
numSamples : f32,
|
|
678
|
+
numChannels : f32,
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
682
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
683
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
684
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>;
|
|
685
|
+
@group(0) @binding(4) var<storage, read> thresholdSignal : array<f32>;
|
|
686
|
+
@group(0) @binding(5) var<storage, read> attackSignal : array<f32>;
|
|
687
|
+
@group(0) @binding(6) var<storage, read> holdSignal : array<f32>;
|
|
688
|
+
@group(0) @binding(7) var<storage, read> releaseSignal : array<f32>;
|
|
689
|
+
@group(0) @binding(8) var<storage, read> rangeSignal : array<f32>;
|
|
690
|
+
|
|
691
|
+
fn log10(x: f32) -> f32 {
|
|
692
|
+
return log(x) * 0.4342944819032518f;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
fn tanh(x: f32) -> f32 {
|
|
696
|
+
let exp2x = exp(2.0f * x);
|
|
697
|
+
return (exp2x - 1.0f) / (exp2x + 1.0f);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
fn is_nan_or_inf(v: f32) -> bool {
|
|
701
|
+
return (v != v) || (abs(v) > 3.402823466e+38f);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
@compute @workgroup_size(1)
|
|
705
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
706
|
+
if (gid.x != 0u) { return; }
|
|
707
|
+
|
|
708
|
+
let numSamples = u32(u.numSamples);
|
|
709
|
+
let numChannels = u32(u.numChannels);
|
|
710
|
+
let windowLength = u32(u.windowLength);
|
|
711
|
+
|
|
712
|
+
var gateState = u32(state[0]); // 0=CLOSED, 1=ATTACK, 2=OPEN, 3=HOLD, 4=RELEASE
|
|
713
|
+
if (is_nan_or_inf(state[0])) { gateState = 0u; }
|
|
714
|
+
|
|
715
|
+
var currentGain = state[1];
|
|
716
|
+
if (is_nan_or_inf(currentGain)) { currentGain = 0.0f; }
|
|
717
|
+
|
|
718
|
+
var gainAtTransition = state[2];
|
|
719
|
+
if (is_nan_or_inf(gainAtTransition)) { gainAtTransition = 0.0f; }
|
|
720
|
+
|
|
721
|
+
var attackSamplesLeft = i32(state[3]);
|
|
722
|
+
var holdSamplesLeft = i32(state[4]);
|
|
723
|
+
var releaseSamplesLeft = i32(state[5]);
|
|
724
|
+
var writeIndex = u32(state[6]);
|
|
725
|
+
var sumSquares = state[7];
|
|
726
|
+
if (is_nan_or_inf(sumSquares)) { sumSquares = 0.0f; }
|
|
727
|
+
|
|
728
|
+
let sampleRate = u.sampleRate;
|
|
729
|
+
|
|
730
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
731
|
+
// 1. Dynamic Parameter Modulation & Anti-Explosion Clamping
|
|
732
|
+
var effThreshold = u.threshold;
|
|
733
|
+
if (u.hasThresholdSig > 0.5f) {
|
|
734
|
+
effThreshold = thresholdSignal[index];
|
|
735
|
+
}
|
|
736
|
+
effThreshold = clamp(effThreshold, -120.0f, 0.0f);
|
|
737
|
+
|
|
738
|
+
var effAttack = u.attack;
|
|
739
|
+
if (u.hasAttackSig > 0.5f) {
|
|
740
|
+
effAttack = attackSignal[index];
|
|
741
|
+
}
|
|
742
|
+
effAttack = clamp(effAttack, 0.0001f, 5.0f);
|
|
743
|
+
|
|
744
|
+
var effHold = u.hold;
|
|
745
|
+
if (u.hasHoldSig > 0.5f) {
|
|
746
|
+
effHold = holdSignal[index];
|
|
747
|
+
}
|
|
748
|
+
effHold = clamp(effHold, 0.001f, 10.0f);
|
|
749
|
+
|
|
750
|
+
var effRelease = u.release;
|
|
751
|
+
if (u.hasReleaseSig > 0.5f) {
|
|
752
|
+
effRelease = releaseSignal[index];
|
|
753
|
+
}
|
|
754
|
+
effRelease = clamp(effRelease, 0.01f, 10.0f);
|
|
755
|
+
|
|
756
|
+
var effRange = u.closedGain;
|
|
757
|
+
if (u.hasRangeSig > 0.5f) {
|
|
758
|
+
let rDb = clamp(rangeSignal[index], -120.0f, 0.0f);
|
|
759
|
+
effRange = select(0.0f, pow(10.0f, rDb / 20.0f), rDb > -96.0f);
|
|
760
|
+
}
|
|
761
|
+
effRange = clamp(effRange, 0.0f, 1.0f);
|
|
762
|
+
|
|
763
|
+
// 2. Peak & RMS Window Calculation
|
|
764
|
+
var peak = 0.0f;
|
|
765
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
766
|
+
var val = abs(inputChannels[c * numSamples + index]);
|
|
767
|
+
if (is_nan_or_inf(val)) { val = 0.0f; }
|
|
768
|
+
if (val > peak) {
|
|
769
|
+
peak = val;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
let inputPowerSq = peak * peak;
|
|
774
|
+
let oldestSq = state[8u + writeIndex];
|
|
775
|
+
state[8u + writeIndex] = inputPowerSq;
|
|
776
|
+
sumSquares = max(0.0f, sumSquares - oldestSq + inputPowerSq);
|
|
777
|
+
writeIndex = (writeIndex + 1u) % windowLength;
|
|
778
|
+
|
|
779
|
+
if (writeIndex == 0u) {
|
|
780
|
+
var sum = 0.0f;
|
|
781
|
+
for (var j = 0u; j < windowLength; j = j + 1u) {
|
|
782
|
+
sum = sum + state[8u + j];
|
|
783
|
+
}
|
|
784
|
+
sumSquares = sum;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
let rms = sqrt(sumSquares / f32(windowLength));
|
|
788
|
+
let db = 20.0f * log10(max(rms, 1e-9f));
|
|
789
|
+
|
|
790
|
+
let signalAboveThreshold = db >= effThreshold;
|
|
791
|
+
|
|
792
|
+
if (signalAboveThreshold) {
|
|
793
|
+
if (gateState == 0u || gateState == 4u) {
|
|
794
|
+
gateState = 1u; // ATTACK
|
|
795
|
+
gainAtTransition = currentGain;
|
|
796
|
+
var samples = i32(round(effAttack * sampleRate));
|
|
797
|
+
if (samples <= 0) { samples = 1; }
|
|
798
|
+
attackSamplesLeft = samples;
|
|
799
|
+
} else if (gateState == 3u) {
|
|
800
|
+
gateState = 2u; // OPEN
|
|
801
|
+
currentGain = 1.0f;
|
|
802
|
+
}
|
|
803
|
+
} else {
|
|
804
|
+
if (gateState == 2u || gateState == 1u) {
|
|
805
|
+
gateState = 3u; // HOLD
|
|
806
|
+
var samples = i32(round(effHold * sampleRate));
|
|
807
|
+
if (samples <= 0) { samples = 1; }
|
|
808
|
+
holdSamplesLeft = samples;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (gateState == 0u) {
|
|
813
|
+
currentGain = effRange;
|
|
814
|
+
} else if (gateState == 2u) {
|
|
815
|
+
currentGain = 1.0f;
|
|
816
|
+
} else if (gateState == 1u) {
|
|
817
|
+
var totalAttackSamples = i32(round(effAttack * sampleRate));
|
|
818
|
+
if (totalAttackSamples <= 0) { totalAttackSamples = 1; }
|
|
819
|
+
let progress = 1.0f - f32(attackSamplesLeft) / f32(totalAttackSamples);
|
|
820
|
+
currentGain = gainAtTransition + progress * (1.0f - gainAtTransition);
|
|
821
|
+
|
|
822
|
+
attackSamplesLeft = attackSamplesLeft - 1;
|
|
823
|
+
if (attackSamplesLeft <= 0) {
|
|
824
|
+
gateState = 2u;
|
|
825
|
+
currentGain = 1.0f;
|
|
826
|
+
}
|
|
827
|
+
} else if (gateState == 3u) {
|
|
828
|
+
currentGain = 1.0f;
|
|
829
|
+
holdSamplesLeft = holdSamplesLeft - 1;
|
|
830
|
+
if (holdSamplesLeft <= 0) {
|
|
831
|
+
gateState = 4u; // RELEASE
|
|
832
|
+
gainAtTransition = 1.0f;
|
|
833
|
+
var samples = i32(round(effRelease * sampleRate));
|
|
834
|
+
if (samples <= 0) { samples = 1; }
|
|
835
|
+
releaseSamplesLeft = samples;
|
|
836
|
+
}
|
|
837
|
+
} else if (gateState == 4u) {
|
|
838
|
+
var totalReleaseSamples = i32(round(effRelease * sampleRate));
|
|
839
|
+
if (totalReleaseSamples <= 0) { totalReleaseSamples = 1; }
|
|
840
|
+
let progress = 1.0f - f32(releaseSamplesLeft) / f32(totalReleaseSamples);
|
|
841
|
+
currentGain = gainAtTransition + progress * (effRange - gainAtTransition);
|
|
842
|
+
|
|
843
|
+
releaseSamplesLeft = releaseSamplesLeft - 1;
|
|
844
|
+
if (releaseSamplesLeft <= 0) {
|
|
845
|
+
gateState = 0u;
|
|
846
|
+
currentGain = effRange;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
currentGain = clamp(currentGain, 0.0f, 1.0f);
|
|
851
|
+
|
|
852
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
853
|
+
let sampleIndex = c * numSamples + index;
|
|
854
|
+
var inVal = inputChannels[sampleIndex];
|
|
855
|
+
if (is_nan_or_inf(inVal)) { inVal = 0.0f; }
|
|
856
|
+
|
|
857
|
+
var outVal = inVal * currentGain;
|
|
858
|
+
if (is_nan_or_inf(outVal)) { outVal = 0.0f; }
|
|
859
|
+
|
|
860
|
+
if (outVal > 1.0f) {
|
|
861
|
+
outVal = 1.0f;
|
|
862
|
+
} else if (outVal < -1.0f) {
|
|
863
|
+
outVal = -1.0f;
|
|
864
|
+
} else if (outVal > 0.9f) {
|
|
865
|
+
outVal = 0.9f + 0.1f * tanh((outVal - 0.9f) / 0.1f);
|
|
866
|
+
} else if (outVal < -0.9f) {
|
|
867
|
+
outVal = -0.9f + 0.1f * tanh((outVal + 0.9f) / 0.1f);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
outputChannels[sampleIndex] = outVal;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
state[0] = f32(gateState);
|
|
875
|
+
state[1] = currentGain;
|
|
876
|
+
state[2] = gainAtTransition;
|
|
877
|
+
state[3] = f32(attackSamplesLeft);
|
|
878
|
+
state[4] = f32(holdSamplesLeft);
|
|
879
|
+
state[5] = f32(releaseSamplesLeft);
|
|
880
|
+
state[6] = f32(writeIndex);
|
|
881
|
+
state[7] = sumSquares;
|
|
882
|
+
}
|
|
883
|
+
`;
|
|
884
|
+
const noiseGateAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
|
|
885
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Noise Gate.");
|
|
886
|
+
const op = virtualMedia.operation || {};
|
|
887
|
+
const inputs = op.inputs || {};
|
|
888
|
+
const threshold = typeof op.threshold === "number" ? op.threshold : -40;
|
|
889
|
+
const attack = typeof op.attack === "number" ? op.attack : .005;
|
|
890
|
+
const hold = typeof op.hold === "number" ? op.hold : .05;
|
|
891
|
+
const release = typeof op.release === "number" ? op.release : .1;
|
|
892
|
+
const range = typeof op.range === "number" ? op.range : -80;
|
|
893
|
+
const closedGain = range <= -96 ? 0 : 10 ** (range / 20);
|
|
894
|
+
const isHandleConnected = (handleIdKey) => {
|
|
895
|
+
const handleId = op[handleIdKey];
|
|
896
|
+
if (typeof handleId !== "string" || !handleId) return false;
|
|
897
|
+
const input = inputs[handleId];
|
|
898
|
+
return Boolean(input?.connectionValid && input.outputItem && (input.outputItem.type === "Signal" || input.outputItem.type === "Number"));
|
|
899
|
+
};
|
|
900
|
+
const hasThresholdSig = isHandleConnected("thresholdHandleId") ? 1 : 0;
|
|
901
|
+
const hasAttackSig = isHandleConnected("attackHandleId") ? 1 : 0;
|
|
902
|
+
const hasHoldSig = isHandleConnected("holdHandleId") ? 1 : 0;
|
|
903
|
+
const hasReleaseSig = isHandleConnected("releaseHandleId") ? 1 : 0;
|
|
904
|
+
const hasRangeSig = isHandleConnected("rangeHandleId") ? 1 : 0;
|
|
905
|
+
const numChannels = channels.length;
|
|
906
|
+
if (numChannels === 0) return;
|
|
907
|
+
const numSamples = channels[0].length;
|
|
908
|
+
if (numSamples === 0) return;
|
|
909
|
+
const windowLength = Math.max(1, Math.round(.01 * sampleRate));
|
|
910
|
+
const nodeId = op.id || "audio-noise-gate";
|
|
911
|
+
const frame = ctx.frame ?? 0;
|
|
912
|
+
const fps = ctx.fps ?? 24;
|
|
913
|
+
const stateFloatCount = 8 + windowLength;
|
|
914
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, virtualMedia, frame, fps, NOISE_GATE_SHADER_TEMPLATE, () => [
|
|
915
|
+
sampleRate,
|
|
916
|
+
threshold,
|
|
917
|
+
attack,
|
|
918
|
+
hold,
|
|
919
|
+
release,
|
|
920
|
+
closedGain,
|
|
921
|
+
windowLength,
|
|
922
|
+
hasThresholdSig,
|
|
923
|
+
hasAttackSig,
|
|
924
|
+
hasHoldSig,
|
|
925
|
+
hasReleaseSig,
|
|
926
|
+
hasRangeSig,
|
|
927
|
+
0,
|
|
928
|
+
frame,
|
|
929
|
+
numSamples,
|
|
930
|
+
numChannels
|
|
931
|
+
], 16, stateFloatCount, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs, void 0, void 0, PARAM_ORDER$2);
|
|
932
|
+
};
|
|
933
|
+
var renderers_default$3 = defineRenderer({ audioProcessor: noiseGateAudioProcessor });
|
|
934
|
+
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region ../../nodes/node-audio-parametric-eq/dist/renderers-CI8P2akC.mjs
|
|
937
|
+
const PARAM_ORDER$1 = [
|
|
938
|
+
"frequency",
|
|
939
|
+
"gain",
|
|
940
|
+
"q"
|
|
941
|
+
];
|
|
942
|
+
const PARAMETRIC_EQ_SHADER_TEMPLATE = () => `
|
|
943
|
+
struct Uniforms {
|
|
944
|
+
sampleRate : f32,
|
|
945
|
+
numBands : f32,
|
|
946
|
+
frequency : f32,
|
|
947
|
+
gain : f32,
|
|
948
|
+
q : f32,
|
|
949
|
+
bandType : f32,
|
|
950
|
+
hasFrequencySig : f32,
|
|
951
|
+
hasGainSig : f32,
|
|
952
|
+
hasQSig : f32,
|
|
953
|
+
baseTime : f32,
|
|
954
|
+
frame : f32,
|
|
955
|
+
numSamples : f32,
|
|
956
|
+
numChannels : f32,
|
|
957
|
+
pad1 : f32,
|
|
958
|
+
pad2 : f32,
|
|
959
|
+
pad3 : f32,
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
963
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
964
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
965
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>;
|
|
966
|
+
@group(0) @binding(4) var<storage, read> frequencySignal : array<f32>;
|
|
967
|
+
@group(0) @binding(5) var<storage, read> gainSignal : array<f32>;
|
|
968
|
+
@group(0) @binding(6) var<storage, read> qSignal : array<f32>;
|
|
969
|
+
|
|
970
|
+
const PI: f32 = 3.141592653589793f;
|
|
971
|
+
|
|
972
|
+
fn tanh(x: f32) -> f32 {
|
|
973
|
+
let exp2x = exp(2.0f * x);
|
|
974
|
+
return (exp2x - 1.0f) / (exp2x + 1.0f);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
fn is_nan_or_inf(v: f32) -> bool {
|
|
978
|
+
return (v != v) || (abs(v) > 3.402823466e+38f);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
@compute @workgroup_size(1)
|
|
982
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
983
|
+
if (gid.x != 0u) { return; }
|
|
984
|
+
|
|
985
|
+
let numSamples = u32(u.numSamples);
|
|
986
|
+
let numChannels = u32(u.numChannels);
|
|
987
|
+
let sampleRate = u.sampleRate;
|
|
988
|
+
let bandType = u.bandType; // 0=peak, 1=lowShelf, 2=highShelf, 3=lowPass, 4=highPass, 5=notch
|
|
989
|
+
|
|
990
|
+
let biquadStateOffset = 4u;
|
|
991
|
+
|
|
992
|
+
for (var c = 0u; c < numChannels; c = c + 1u) {
|
|
993
|
+
let offset = biquadStateOffset + c * 4u;
|
|
994
|
+
var x1 = state[offset];
|
|
995
|
+
var x2 = state[offset + 1u];
|
|
996
|
+
var y1 = state[offset + 2u];
|
|
997
|
+
var y2 = state[offset + 3u];
|
|
998
|
+
|
|
999
|
+
if (is_nan_or_inf(x1)) { x1 = 0.0f; }
|
|
1000
|
+
if (is_nan_or_inf(x2)) { x2 = 0.0f; }
|
|
1001
|
+
if (is_nan_or_inf(y1)) { y1 = 0.0f; }
|
|
1002
|
+
if (is_nan_or_inf(y2)) { y2 = 0.0f; }
|
|
1003
|
+
|
|
1004
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
1005
|
+
let sampleIndex = c * numSamples + index;
|
|
1006
|
+
var x = inputChannels[sampleIndex];
|
|
1007
|
+
if (is_nan_or_inf(x)) { x = 0.0f; }
|
|
1008
|
+
|
|
1009
|
+
// 1. Dynamic Parameter Signal Resolution & Clamping
|
|
1010
|
+
var effFreq = u.frequency;
|
|
1011
|
+
if (u.hasFrequencySig > 0.5f) {
|
|
1012
|
+
effFreq = frequencySignal[index];
|
|
1013
|
+
}
|
|
1014
|
+
effFreq = clamp(effFreq, 20.0f, sampleRate / 2.0f - 1.0f);
|
|
1015
|
+
|
|
1016
|
+
var effQ = u.q;
|
|
1017
|
+
if (u.hasQSig > 0.5f) {
|
|
1018
|
+
effQ = qSignal[index];
|
|
1019
|
+
}
|
|
1020
|
+
effQ = clamp(effQ, 0.01f, 10.0f);
|
|
1021
|
+
|
|
1022
|
+
var effGain = u.gain;
|
|
1023
|
+
if (u.hasGainSig > 0.5f) {
|
|
1024
|
+
effGain = gainSignal[index];
|
|
1025
|
+
}
|
|
1026
|
+
effGain = clamp(effGain, -24.0f, 24.0f);
|
|
1027
|
+
|
|
1028
|
+
if (bandType == 0.0f && abs(effGain) < 1e-5f) {
|
|
1029
|
+
// Bypass filter if peak gain is zero
|
|
1030
|
+
outputChannels[sampleIndex] = x;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// 2. Biquad Coefficients Calculation
|
|
1035
|
+
let w0 = (2.0f * PI * effFreq) / sampleRate;
|
|
1036
|
+
let cos_w0 = cos(w0);
|
|
1037
|
+
let sin_w0 = sin(w0);
|
|
1038
|
+
let A = clamp(pow(10.0f, effGain / 40.0f), 0.25f, 4.0f);
|
|
1039
|
+
let alpha = sin_w0 / (2.0f * effQ);
|
|
1040
|
+
|
|
1041
|
+
var b0 = 0.0f; var b1 = 0.0f; var b2 = 0.0f;
|
|
1042
|
+
var a0 = 0.0f; var a1 = 0.0f; var a2 = 0.0f;
|
|
1043
|
+
|
|
1044
|
+
if (bandType == 0.0f) { // peak
|
|
1045
|
+
b0 = 1.0f + alpha * A;
|
|
1046
|
+
b1 = -2.0f * cos_w0;
|
|
1047
|
+
b2 = 1.0f - alpha * A;
|
|
1048
|
+
a0 = 1.0f + alpha / A;
|
|
1049
|
+
a1 = -2.0f * cos_w0;
|
|
1050
|
+
a2 = 1.0f - alpha / A;
|
|
1051
|
+
} else if (bandType == 1.0f) { // lowShelf
|
|
1052
|
+
let sqrtA = sqrt(A);
|
|
1053
|
+
let twoSqrtAAlpha = 2.0f * sqrtA * alpha;
|
|
1054
|
+
b0 = A * (A + 1.0f - (A - 1.0f) * cos_w0 + twoSqrtAAlpha);
|
|
1055
|
+
b1 = 2.0f * A * (A - 1.0f - (A + 1.0f) * cos_w0);
|
|
1056
|
+
b2 = A * (A + 1.0f - (A - 1.0f) * cos_w0 - twoSqrtAAlpha);
|
|
1057
|
+
a0 = A + 1.0f + (A - 1.0f) * cos_w0 + twoSqrtAAlpha;
|
|
1058
|
+
a1 = -2.0f * (A - 1.0f + (A + 1.0f) * cos_w0);
|
|
1059
|
+
a2 = A + 1.0f + (A - 1.0f) * cos_w0 - twoSqrtAAlpha;
|
|
1060
|
+
} else if (bandType == 2.0f) { // highShelf
|
|
1061
|
+
let sqrtA = sqrt(A);
|
|
1062
|
+
let twoSqrtAAlpha = 2.0f * sqrtA * alpha;
|
|
1063
|
+
b0 = A * (A + 1.0f + (A - 1.0f) * cos_w0 + twoSqrtAAlpha);
|
|
1064
|
+
b1 = -2.0f * A * (A - 1.0f + (A + 1.0f) * cos_w0);
|
|
1065
|
+
b2 = A * (A + 1.0f + (A - 1.0f) * cos_w0 - twoSqrtAAlpha);
|
|
1066
|
+
a0 = A + 1.0f - (A - 1.0f) * cos_w0 + twoSqrtAAlpha;
|
|
1067
|
+
a1 = 2.0f * (A - 1.0f - (A + 1.0f) * cos_w0);
|
|
1068
|
+
a2 = A + 1.0f - (A - 1.0f) * cos_w0 - twoSqrtAAlpha;
|
|
1069
|
+
} else if (bandType == 3.0f) { // lowPass
|
|
1070
|
+
b0 = (1.0f - cos_w0) / 2.0f;
|
|
1071
|
+
b1 = 1.0f - cos_w0;
|
|
1072
|
+
b2 = (1.0f - cos_w0) / 2.0f;
|
|
1073
|
+
a0 = 1.0f + alpha;
|
|
1074
|
+
a1 = -2.0f * cos_w0;
|
|
1075
|
+
a2 = 1.0f - alpha;
|
|
1076
|
+
} else if (bandType == 4.0f) { // highPass
|
|
1077
|
+
b0 = (1.0f + cos_w0) / 2.0f;
|
|
1078
|
+
b1 = -(1.0f + cos_w0);
|
|
1079
|
+
b2 = (1.0f + cos_w0) / 2.0f;
|
|
1080
|
+
a0 = 1.0f + alpha;
|
|
1081
|
+
a1 = -2.0f * cos_w0;
|
|
1082
|
+
a2 = 1.0f - alpha;
|
|
1083
|
+
} else if (bandType == 5.0f) { // notch
|
|
1084
|
+
b0 = 1.0f;
|
|
1085
|
+
b1 = -2.0f * cos_w0;
|
|
1086
|
+
b2 = 1.0f;
|
|
1087
|
+
a0 = 1.0f + alpha;
|
|
1088
|
+
a1 = -2.0f * cos_w0;
|
|
1089
|
+
a2 = 1.0f - alpha;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (abs(a0) < 1e-9f) {
|
|
1093
|
+
outputChannels[sampleIndex] = x;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
let b0_norm = b0 / a0;
|
|
1098
|
+
let b1_norm = b1 / a0;
|
|
1099
|
+
let b2_norm = b2 / a0;
|
|
1100
|
+
let a1_norm = a1 / a0;
|
|
1101
|
+
let a2_norm = a2 / a0;
|
|
1102
|
+
|
|
1103
|
+
var y = b0_norm * x + b1_norm * x1 + b2_norm * x2 - a1_norm * y1 - a2_norm * y2;
|
|
1104
|
+
if (is_nan_or_inf(y)) { y = 0.0f; }
|
|
1105
|
+
|
|
1106
|
+
// Biquad State History Update with Soft Anti-Explosion Clamping
|
|
1107
|
+
x2 = x1;
|
|
1108
|
+
x1 = x;
|
|
1109
|
+
y2 = clamp(y1, -4.0f, 4.0f);
|
|
1110
|
+
y1 = clamp(y, -4.0f, 4.0f);
|
|
1111
|
+
|
|
1112
|
+
// Output Soft Clipping
|
|
1113
|
+
if (y > 1.0f) {
|
|
1114
|
+
y = 1.0f;
|
|
1115
|
+
} else if (y < -1.0f) {
|
|
1116
|
+
y = -1.0f;
|
|
1117
|
+
} else if (y > 0.9f) {
|
|
1118
|
+
y = 0.9f + 0.1f * tanh((y - 0.9f) / 0.1f);
|
|
1119
|
+
} else if (y < -0.9f) {
|
|
1120
|
+
y = -0.9f + 0.1f * tanh((y + 0.9f) / 0.1f);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
outputChannels[sampleIndex] = y;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
state[offset] = x1;
|
|
1127
|
+
state[offset + 1u] = x2;
|
|
1128
|
+
state[offset + 2u] = y1;
|
|
1129
|
+
state[offset + 3u] = y2;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
`;
|
|
1133
|
+
const getBandTypeVal = (type) => {
|
|
1134
|
+
if (type === "peak") return 0;
|
|
1135
|
+
if (type === "lowShelf") return 1;
|
|
1136
|
+
if (type === "highShelf") return 2;
|
|
1137
|
+
if (type === "lowPass") return 3;
|
|
1138
|
+
if (type === "highPass") return 4;
|
|
1139
|
+
if (type === "notch") return 5;
|
|
1140
|
+
return 0;
|
|
1141
|
+
};
|
|
1142
|
+
const parametricEqAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
|
|
1143
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Parametric EQ.");
|
|
1144
|
+
const op = virtualMedia.operation || {};
|
|
1145
|
+
const inputs = op.inputs || {};
|
|
1146
|
+
const config = {
|
|
1147
|
+
type: typeof op.type === "string" && [
|
|
1148
|
+
"lowShelf",
|
|
1149
|
+
"highShelf",
|
|
1150
|
+
"peak",
|
|
1151
|
+
"lowPass",
|
|
1152
|
+
"highPass",
|
|
1153
|
+
"notch"
|
|
1154
|
+
].includes(op.type) ? op.type : "peak",
|
|
1155
|
+
frequency: typeof op.frequency === "number" ? op.frequency : 1e3,
|
|
1156
|
+
frequencyHandleId: typeof op.frequencyHandleId === "string" ? op.frequencyHandleId : null,
|
|
1157
|
+
gain: typeof op.gain === "number" ? op.gain : 0,
|
|
1158
|
+
gainHandleId: typeof op.gainHandleId === "string" ? op.gainHandleId : null,
|
|
1159
|
+
q: typeof op.q === "number" ? op.q : 1,
|
|
1160
|
+
qHandleId: typeof op.qHandleId === "string" ? op.qHandleId : null
|
|
1161
|
+
};
|
|
1162
|
+
const isHandleConnected = (handleIdKey) => {
|
|
1163
|
+
const handleId = op[handleIdKey];
|
|
1164
|
+
if (typeof handleId !== "string" || !handleId) return false;
|
|
1165
|
+
const input = inputs[handleId];
|
|
1166
|
+
return Boolean(input?.connectionValid && input.outputItem && (input.outputItem.type === "Signal" || input.outputItem.type === "Number"));
|
|
1167
|
+
};
|
|
1168
|
+
const hasFrequencySig = isHandleConnected("frequencyHandleId") ? 1 : 0;
|
|
1169
|
+
const hasGainSig = isHandleConnected("gainHandleId") ? 1 : 0;
|
|
1170
|
+
const hasQSig = isHandleConnected("qHandleId") ? 1 : 0;
|
|
1171
|
+
const numChannels = channels.length;
|
|
1172
|
+
if (numChannels === 0) return;
|
|
1173
|
+
const numSamples = channels[0].length;
|
|
1174
|
+
if (numSamples === 0) return;
|
|
1175
|
+
const nodeId = op.id || "audio-parametric-eq";
|
|
1176
|
+
const frame = ctx.frame ?? 0;
|
|
1177
|
+
const fps = ctx.fps ?? 24;
|
|
1178
|
+
const stateFloatCount = 4 + numChannels * 4;
|
|
1179
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, virtualMedia, frame, fps, PARAMETRIC_EQ_SHADER_TEMPLATE, () => [
|
|
1180
|
+
sampleRate,
|
|
1181
|
+
1,
|
|
1182
|
+
config.frequency,
|
|
1183
|
+
config.gain,
|
|
1184
|
+
config.q,
|
|
1185
|
+
getBandTypeVal(config.type),
|
|
1186
|
+
hasFrequencySig,
|
|
1187
|
+
hasGainSig,
|
|
1188
|
+
hasQSig,
|
|
1189
|
+
0,
|
|
1190
|
+
frame,
|
|
1191
|
+
numSamples,
|
|
1192
|
+
numChannels,
|
|
1193
|
+
0,
|
|
1194
|
+
0,
|
|
1195
|
+
0
|
|
1196
|
+
], 16, stateFloatCount, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs, void 0, void 0, PARAM_ORDER$1);
|
|
1197
|
+
};
|
|
1198
|
+
var renderers_default$2 = defineRenderer({ audioProcessor: parametricEqAudioProcessor });
|
|
1199
|
+
|
|
1200
|
+
//#endregion
|
|
1201
|
+
//#region ../../nodes/node-audio-reverb/dist/renderers-Dz30LBKg.mjs
|
|
1202
|
+
const PARAM_ORDER = [
|
|
1203
|
+
"roomSize",
|
|
1204
|
+
"damping",
|
|
1205
|
+
"wet",
|
|
1206
|
+
"dry",
|
|
1207
|
+
"width"
|
|
1208
|
+
];
|
|
1209
|
+
const REVERB_SHADER_TEMPLATE = () => `
|
|
1210
|
+
struct Uniforms {
|
|
1211
|
+
sampleRate : f32,
|
|
1212
|
+
roomSize : f32,
|
|
1213
|
+
damping : f32,
|
|
1214
|
+
wet : f32,
|
|
1215
|
+
dry : f32,
|
|
1216
|
+
width : f32,
|
|
1217
|
+
hasRoomSizeSig : f32,
|
|
1218
|
+
hasDampingSig : f32,
|
|
1219
|
+
hasWetSig : f32,
|
|
1220
|
+
hasDrySig : f32,
|
|
1221
|
+
hasWidthSig : f32,
|
|
1222
|
+
baseTime : f32,
|
|
1223
|
+
frame : f32,
|
|
1224
|
+
numSamples : f32,
|
|
1225
|
+
numChannels : f32,
|
|
1226
|
+
pad1 : f32,
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
1230
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
1231
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
1232
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>;
|
|
1233
|
+
@group(0) @binding(4) var<storage, read> roomSizeSignal : array<f32>;
|
|
1234
|
+
@group(0) @binding(5) var<storage, read> dampingSignal : array<f32>;
|
|
1235
|
+
@group(0) @binding(6) var<storage, read> wetSignal : array<f32>;
|
|
1236
|
+
@group(0) @binding(7) var<storage, read> drySignal : array<f32>;
|
|
1237
|
+
@group(0) @binding(8) var<storage, read> widthSignal : array<f32>;
|
|
1238
|
+
|
|
1239
|
+
fn tanh(x: f32) -> f32 {
|
|
1240
|
+
let exp2x = exp(2.0f * x);
|
|
1241
|
+
return (exp2x - 1.0f) / (exp2x + 1.0f);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
fn is_nan_or_inf(v: f32) -> bool {
|
|
1245
|
+
return (v != v) || (abs(v) > 3.402823466e+38f);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
@compute @workgroup_size(1)
|
|
1249
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
1250
|
+
if (gid.x != 0u) { return; }
|
|
1251
|
+
|
|
1252
|
+
let numSamples = u32(u.numSamples);
|
|
1253
|
+
let numChannels = u32(u.numChannels);
|
|
1254
|
+
let fixedgain: f32 = 0.015f;
|
|
1255
|
+
|
|
1256
|
+
var bufIndices: array<u32, 24>;
|
|
1257
|
+
var filterStores: array<f32, 24>;
|
|
1258
|
+
var sizes: array<u32, 24>;
|
|
1259
|
+
var startOffsets: array<u32, 24>;
|
|
1260
|
+
|
|
1261
|
+
for (var f = 0u; f < 24u; f = f + 1u) {
|
|
1262
|
+
let metaOffset = f * 4u;
|
|
1263
|
+
sizes[f] = u32(state[metaOffset]);
|
|
1264
|
+
startOffsets[f] = u32(state[metaOffset + 1u]);
|
|
1265
|
+
bufIndices[f] = u32(state[metaOffset + 2u]);
|
|
1266
|
+
var fs = state[metaOffset + 3u];
|
|
1267
|
+
if (is_nan_or_inf(fs)) { fs = 0.0f; }
|
|
1268
|
+
filterStores[f] = fs;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
if (numChannels == 1u) {
|
|
1272
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
1273
|
+
// Dynamic parameter signal evaluation
|
|
1274
|
+
var effRoomSize = u.roomSize;
|
|
1275
|
+
if (u.hasRoomSizeSig > 0.5f) { effRoomSize = roomSizeSignal[index]; }
|
|
1276
|
+
effRoomSize = clamp(effRoomSize, 0.0f, 0.98f);
|
|
1277
|
+
|
|
1278
|
+
var effDamping = u.damping;
|
|
1279
|
+
if (u.hasDampingSig > 0.5f) { effDamping = dampingSignal[index]; }
|
|
1280
|
+
effDamping = clamp(effDamping, 0.0f, 1.0f) * 0.4f;
|
|
1281
|
+
|
|
1282
|
+
var effWet = u.wet;
|
|
1283
|
+
if (u.hasWetSig > 0.5f) { effWet = wetSignal[index]; }
|
|
1284
|
+
effWet = clamp(effWet, 0.0f, 1.0f) * 3.0f;
|
|
1285
|
+
|
|
1286
|
+
var effDry = u.dry;
|
|
1287
|
+
if (u.hasDrySig > 0.5f) { effDry = drySignal[index]; }
|
|
1288
|
+
effDry = clamp(effDry, 0.0f, 2.0f);
|
|
1289
|
+
|
|
1290
|
+
var x = inputChannels[index];
|
|
1291
|
+
if (is_nan_or_inf(x)) { x = 0.0f; }
|
|
1292
|
+
let inputVal = x * fixedgain;
|
|
1293
|
+
|
|
1294
|
+
var combSum = 0.0f;
|
|
1295
|
+
for (var j = 0u; j < 8u; j = j + 1u) {
|
|
1296
|
+
let size = sizes[j];
|
|
1297
|
+
let startOffset = startOffsets[j];
|
|
1298
|
+
let bufferIndex = bufIndices[j];
|
|
1299
|
+
var fs = filterStores[j];
|
|
1300
|
+
|
|
1301
|
+
var output = state[startOffset + bufferIndex];
|
|
1302
|
+
if (is_nan_or_inf(output)) { output = 0.0f; }
|
|
1303
|
+
|
|
1304
|
+
fs = output * (1.0f - effDamping) + fs * effDamping;
|
|
1305
|
+
var nextVal = inputVal + fs * effRoomSize;
|
|
1306
|
+
if (is_nan_or_inf(nextVal)) { nextVal = 0.0f; }
|
|
1307
|
+
state[startOffset + bufferIndex] = clamp(nextVal, -4.0f, 4.0f);
|
|
1308
|
+
|
|
1309
|
+
bufIndices[j] = (bufferIndex + 1u) % size;
|
|
1310
|
+
filterStores[j] = fs;
|
|
1311
|
+
combSum = combSum + output;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
var revOut = combSum;
|
|
1315
|
+
for (var j = 0u; j < 4u; j = j + 1u) {
|
|
1316
|
+
let f = 8u + j;
|
|
1317
|
+
let size = sizes[f];
|
|
1318
|
+
let startOffset = startOffsets[f];
|
|
1319
|
+
let bufferIndex = bufIndices[f];
|
|
1320
|
+
|
|
1321
|
+
var bufOut = state[startOffset + bufferIndex];
|
|
1322
|
+
if (is_nan_or_inf(bufOut)) { bufOut = 0.0f; }
|
|
1323
|
+
|
|
1324
|
+
var nextVal = revOut + bufOut * 0.5f;
|
|
1325
|
+
if (is_nan_or_inf(nextVal)) { nextVal = 0.0f; }
|
|
1326
|
+
state[startOffset + bufferIndex] = clamp(nextVal, -4.0f, 4.0f);
|
|
1327
|
+
|
|
1328
|
+
let output = bufOut - revOut;
|
|
1329
|
+
bufIndices[f] = (bufferIndex + 1u) % size;
|
|
1330
|
+
revOut = output;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
var outVal = x * effDry + revOut * effWet;
|
|
1334
|
+
if (is_nan_or_inf(outVal)) { outVal = 0.0f; }
|
|
1335
|
+
|
|
1336
|
+
if (outVal > 1.0f) { outVal = 1.0f; }
|
|
1337
|
+
else if (outVal < -1.0f) { outVal = -1.0f; }
|
|
1338
|
+
else if (outVal > 0.9f) { outVal = 0.9f + 0.1f * tanh((outVal - 0.9f) / 0.1f); }
|
|
1339
|
+
else if (outVal < -0.9f) { outVal = -0.9f + 0.1f * tanh((outVal + 0.9f) / 0.1f); }
|
|
1340
|
+
|
|
1341
|
+
outputChannels[index] = outVal;
|
|
1342
|
+
}
|
|
1343
|
+
} else {
|
|
1344
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
1345
|
+
// Dynamic parameter signal evaluation
|
|
1346
|
+
var effRoomSize = u.roomSize;
|
|
1347
|
+
if (u.hasRoomSizeSig > 0.5f) { effRoomSize = roomSizeSignal[index]; }
|
|
1348
|
+
effRoomSize = clamp(effRoomSize, 0.0f, 0.98f);
|
|
1349
|
+
|
|
1350
|
+
var effDamping = u.damping;
|
|
1351
|
+
if (u.hasDampingSig > 0.5f) { effDamping = dampingSignal[index]; }
|
|
1352
|
+
effDamping = clamp(effDamping, 0.0f, 1.0f) * 0.4f;
|
|
1353
|
+
|
|
1354
|
+
var effWet = u.wet;
|
|
1355
|
+
if (u.hasWetSig > 0.5f) { effWet = wetSignal[index]; }
|
|
1356
|
+
effWet = clamp(effWet, 0.0f, 1.0f) * 3.0f;
|
|
1357
|
+
|
|
1358
|
+
var effDry = u.dry;
|
|
1359
|
+
if (u.hasDrySig > 0.5f) { effDry = drySignal[index]; }
|
|
1360
|
+
effDry = clamp(effDry, 0.0f, 2.0f);
|
|
1361
|
+
|
|
1362
|
+
var effWidth = u.width;
|
|
1363
|
+
if (u.hasWidthSig > 0.5f) { effWidth = widthSignal[index]; }
|
|
1364
|
+
effWidth = clamp(effWidth, 0.0f, 1.0f);
|
|
1365
|
+
|
|
1366
|
+
let wet1 = effWet * (effWidth / 2.0f + 0.5f);
|
|
1367
|
+
let wet2 = effWet * ((1.0f - effWidth) / 2.0f);
|
|
1368
|
+
|
|
1369
|
+
var inputL = inputChannels[index];
|
|
1370
|
+
if (is_nan_or_inf(inputL)) { inputL = 0.0f; }
|
|
1371
|
+
var inputR = inputChannels[numSamples + index];
|
|
1372
|
+
if (is_nan_or_inf(inputR)) { inputR = 0.0f; }
|
|
1373
|
+
|
|
1374
|
+
let inputVal = (inputL + inputR) * fixedgain;
|
|
1375
|
+
|
|
1376
|
+
// Left channel (Filters 0-11)
|
|
1377
|
+
var combSumL = 0.0f;
|
|
1378
|
+
for (var j = 0u; j < 8u; j = j + 1u) {
|
|
1379
|
+
let size = sizes[j];
|
|
1380
|
+
let startOffset = startOffsets[j];
|
|
1381
|
+
let bufferIndex = bufIndices[j];
|
|
1382
|
+
var fs = filterStores[j];
|
|
1383
|
+
|
|
1384
|
+
var output = state[startOffset + bufferIndex];
|
|
1385
|
+
if (is_nan_or_inf(output)) { output = 0.0f; }
|
|
1386
|
+
|
|
1387
|
+
fs = output * (1.0f - effDamping) + fs * effDamping;
|
|
1388
|
+
var nextVal = inputVal + fs * effRoomSize;
|
|
1389
|
+
if (is_nan_or_inf(nextVal)) { nextVal = 0.0f; }
|
|
1390
|
+
state[startOffset + bufferIndex] = clamp(nextVal, -4.0f, 4.0f);
|
|
1391
|
+
|
|
1392
|
+
bufIndices[j] = (bufferIndex + 1u) % size;
|
|
1393
|
+
filterStores[j] = fs;
|
|
1394
|
+
combSumL = combSumL + output;
|
|
1395
|
+
}
|
|
1396
|
+
var revOutL = combSumL;
|
|
1397
|
+
for (var j = 0u; j < 4u; j = j + 1u) {
|
|
1398
|
+
let f = 8u + j;
|
|
1399
|
+
let size = sizes[f];
|
|
1400
|
+
let startOffset = startOffsets[f];
|
|
1401
|
+
let bufferIndex = bufIndices[f];
|
|
1402
|
+
|
|
1403
|
+
var bufOut = state[startOffset + bufferIndex];
|
|
1404
|
+
if (is_nan_or_inf(bufOut)) { bufOut = 0.0f; }
|
|
1405
|
+
|
|
1406
|
+
var nextVal = revOutL + bufOut * 0.5f;
|
|
1407
|
+
if (is_nan_or_inf(nextVal)) { nextVal = 0.0f; }
|
|
1408
|
+
state[startOffset + bufferIndex] = clamp(nextVal, -4.0f, 4.0f);
|
|
1409
|
+
|
|
1410
|
+
let output = bufOut - revOutL;
|
|
1411
|
+
bufIndices[f] = (bufferIndex + 1u) % size;
|
|
1412
|
+
revOutL = output;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Right channel (Filters 12-23)
|
|
1416
|
+
var combSumR = 0.0f;
|
|
1417
|
+
for (var j = 0u; j < 8u; j = j + 1u) {
|
|
1418
|
+
let f = 12u + j;
|
|
1419
|
+
let size = sizes[f];
|
|
1420
|
+
let startOffset = startOffsets[f];
|
|
1421
|
+
let bufferIndex = bufIndices[f];
|
|
1422
|
+
var fs = filterStores[f];
|
|
1423
|
+
|
|
1424
|
+
var output = state[startOffset + bufferIndex];
|
|
1425
|
+
if (is_nan_or_inf(output)) { output = 0.0f; }
|
|
1426
|
+
|
|
1427
|
+
fs = output * (1.0f - effDamping) + fs * effDamping;
|
|
1428
|
+
var nextVal = inputVal + fs * effRoomSize;
|
|
1429
|
+
if (is_nan_or_inf(nextVal)) { nextVal = 0.0f; }
|
|
1430
|
+
state[startOffset + bufferIndex] = clamp(nextVal, -4.0f, 4.0f);
|
|
1431
|
+
|
|
1432
|
+
bufIndices[f] = (bufferIndex + 1u) % size;
|
|
1433
|
+
filterStores[f] = fs;
|
|
1434
|
+
combSumR = combSumR + output;
|
|
1435
|
+
}
|
|
1436
|
+
var revOutR = combSumR;
|
|
1437
|
+
for (var j = 0u; j < 4u; j = j + 1u) {
|
|
1438
|
+
let f = 20u + j;
|
|
1439
|
+
let size = sizes[f];
|
|
1440
|
+
let startOffset = startOffsets[f];
|
|
1441
|
+
let bufferIndex = bufIndices[f];
|
|
1442
|
+
|
|
1443
|
+
var bufOut = state[startOffset + bufferIndex];
|
|
1444
|
+
if (is_nan_or_inf(bufOut)) { bufOut = 0.0f; }
|
|
1445
|
+
|
|
1446
|
+
var nextVal = revOutR + bufOut * 0.5f;
|
|
1447
|
+
if (is_nan_or_inf(nextVal)) { nextVal = 0.0f; }
|
|
1448
|
+
state[startOffset + bufferIndex] = clamp(nextVal, -4.0f, 4.0f);
|
|
1449
|
+
|
|
1450
|
+
let output = bufOut - revOutR;
|
|
1451
|
+
bufIndices[f] = (bufferIndex + 1u) % size;
|
|
1452
|
+
revOutR = output;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
var outL = inputL * effDry + revOutL * wet1 + revOutR * wet2;
|
|
1456
|
+
var outR = inputR * effDry + revOutR * wet1 + revOutL * wet2;
|
|
1457
|
+
if (is_nan_or_inf(outL)) { outL = 0.0f; }
|
|
1458
|
+
if (is_nan_or_inf(outR)) { outR = 0.0f; }
|
|
1459
|
+
|
|
1460
|
+
if (outL > 1.0f) { outL = 1.0f; } else if (outL < -1.0f) { outL = -1.0f; }
|
|
1461
|
+
else if (outL > 0.9f) { outL = 0.9f + 0.1f * tanh((outL - 0.9f) / 0.1f); }
|
|
1462
|
+
else if (outL < -0.9f) { outL = -0.9f + 0.1f * tanh((outL + 0.9f) / 0.1f); }
|
|
1463
|
+
|
|
1464
|
+
if (outR > 1.0f) { outR = 1.0f; } else if (outR < -1.0f) { outR = -1.0f; }
|
|
1465
|
+
else if (outR > 0.9f) { outR = 0.9f + 0.1f * tanh((outR - 0.9f) / 0.1f); }
|
|
1466
|
+
else if (outR < -0.9f) { outR = -0.9f + 0.1f * tanh((outR + 0.9f) / 0.1f); }
|
|
1467
|
+
|
|
1468
|
+
outputChannels[index] = outL;
|
|
1469
|
+
outputChannels[numSamples + index] = outR;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
for (var f = 0u; f < 24u; f = f + 1u) {
|
|
1474
|
+
let metaOffset = f * 4u;
|
|
1475
|
+
state[metaOffset + 2u] = f32(bufIndices[f]);
|
|
1476
|
+
state[metaOffset + 3u] = filterStores[f];
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
`;
|
|
1480
|
+
const leftCombLengths = [
|
|
1481
|
+
1116,
|
|
1482
|
+
1188,
|
|
1483
|
+
1277,
|
|
1484
|
+
1356,
|
|
1485
|
+
1422,
|
|
1486
|
+
1491,
|
|
1487
|
+
1557,
|
|
1488
|
+
1617
|
|
1489
|
+
];
|
|
1490
|
+
const leftAllpassLengths = [
|
|
1491
|
+
556,
|
|
1492
|
+
441,
|
|
1493
|
+
341,
|
|
1494
|
+
225
|
|
1495
|
+
];
|
|
1496
|
+
const reverbAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
|
|
1497
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Reverb.");
|
|
1498
|
+
const op = virtualMedia.operation || {};
|
|
1499
|
+
const inputs = op.inputs || {};
|
|
1500
|
+
const numChannels = channels.length;
|
|
1501
|
+
if (numChannels === 0) return;
|
|
1502
|
+
const numSamples = channels[0].length;
|
|
1503
|
+
if (numSamples === 0) return;
|
|
1504
|
+
const roomSizeInternal = Math.max(0, Math.min(typeof op.roomSize === "number" ? op.roomSize : .5, .98));
|
|
1505
|
+
const dampingInternal = Math.max(0, Math.min(typeof op.damping === "number" ? op.damping : .5, 1));
|
|
1506
|
+
const wetInternal = Math.max(0, Math.min(typeof op.wet === "number" ? op.wet : .3, 1));
|
|
1507
|
+
const dryInternal = Math.max(0, Math.min(typeof op.dry === "number" ? op.dry : 1, 1));
|
|
1508
|
+
const widthInternal = Math.max(0, Math.min(typeof op.width === "number" ? op.width : 1, 1));
|
|
1509
|
+
const isHandleConnected = (handleIdKey) => {
|
|
1510
|
+
const handleId = op[handleIdKey];
|
|
1511
|
+
if (typeof handleId !== "string" || !handleId) return false;
|
|
1512
|
+
const input = inputs[handleId];
|
|
1513
|
+
return Boolean(input?.connectionValid && input.outputItem && (input.outputItem.type === "Signal" || input.outputItem.type === "Number"));
|
|
1514
|
+
};
|
|
1515
|
+
const hasRoomSizeSig = isHandleConnected("roomSizeHandleId") ? 1 : 0;
|
|
1516
|
+
const hasDampingSig = isHandleConnected("dampingHandleId") ? 1 : 0;
|
|
1517
|
+
const hasWetSig = isHandleConnected("wetHandleId") ? 1 : 0;
|
|
1518
|
+
const hasDrySig = isHandleConnected("dryHandleId") ? 1 : 0;
|
|
1519
|
+
const hasWidthSig = isHandleConnected("widthHandleId") ? 1 : 0;
|
|
1520
|
+
const scale = sampleRate / 44100;
|
|
1521
|
+
const nodeId = op.id || "audio-reverb";
|
|
1522
|
+
const frame = ctx.frame ?? 0;
|
|
1523
|
+
const fps = ctx.fps ?? 24;
|
|
1524
|
+
const sizesL = [];
|
|
1525
|
+
const sizesR = [];
|
|
1526
|
+
leftCombLengths.forEach((len) => {
|
|
1527
|
+
sizesL.push(Math.round(len * scale));
|
|
1528
|
+
});
|
|
1529
|
+
leftCombLengths.forEach((len) => {
|
|
1530
|
+
sizesR.push(Math.round((len + 23) * scale));
|
|
1531
|
+
});
|
|
1532
|
+
leftAllpassLengths.forEach((len) => {
|
|
1533
|
+
sizesL.push(Math.round(len * scale));
|
|
1534
|
+
});
|
|
1535
|
+
leftAllpassLengths.forEach((len) => {
|
|
1536
|
+
sizesR.push(Math.round((len + 23) * scale));
|
|
1537
|
+
});
|
|
1538
|
+
const totalMetadataFloats = 96;
|
|
1539
|
+
let totalDelaySamples = 0;
|
|
1540
|
+
const filterSizes = [...sizesL, ...sizesR];
|
|
1541
|
+
for (const size of filterSizes) totalDelaySamples += size;
|
|
1542
|
+
const stateFloatCount = totalMetadataFloats + totalDelaySamples;
|
|
1543
|
+
const metaData = new Float32Array(totalMetadataFloats);
|
|
1544
|
+
let currentOffset = totalMetadataFloats;
|
|
1545
|
+
for (let f = 0; f < 24; f++) {
|
|
1546
|
+
const size = filterSizes[f];
|
|
1547
|
+
metaData[f * 4] = size;
|
|
1548
|
+
metaData[f * 4 + 1] = currentOffset;
|
|
1549
|
+
metaData[f * 4 + 2] = 0;
|
|
1550
|
+
metaData[f * 4 + 3] = 0;
|
|
1551
|
+
currentOffset += size;
|
|
1552
|
+
}
|
|
1553
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, virtualMedia, frame, fps, REVERB_SHADER_TEMPLATE, () => [
|
|
1554
|
+
sampleRate,
|
|
1555
|
+
roomSizeInternal,
|
|
1556
|
+
dampingInternal,
|
|
1557
|
+
wetInternal,
|
|
1558
|
+
dryInternal,
|
|
1559
|
+
widthInternal,
|
|
1560
|
+
hasRoomSizeSig,
|
|
1561
|
+
hasDampingSig,
|
|
1562
|
+
hasWetSig,
|
|
1563
|
+
hasDrySig,
|
|
1564
|
+
hasWidthSig,
|
|
1565
|
+
0,
|
|
1566
|
+
frame,
|
|
1567
|
+
numSamples,
|
|
1568
|
+
numChannels,
|
|
1569
|
+
0
|
|
1570
|
+
], 16, stateFloatCount, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs, metaData, void 0, PARAM_ORDER);
|
|
1571
|
+
};
|
|
1572
|
+
var renderers_default$1 = defineRenderer({ audioProcessor: reverbAudioProcessor });
|
|
1573
|
+
|
|
1574
|
+
//#endregion
|
|
1575
|
+
//#region ../../nodes/node-stereo-panning/dist/renderers-Csm8iHs7.mjs
|
|
1576
|
+
const PANNING_SHADER_TEMPLATE = () => `
|
|
1577
|
+
struct Uniforms {
|
|
1578
|
+
sampleRate : f32,
|
|
1579
|
+
pan : f32,
|
|
1580
|
+
baseTime : f32,
|
|
1581
|
+
frame : f32,
|
|
1582
|
+
numSamples : f32,
|
|
1583
|
+
numChannels : f32,
|
|
1584
|
+
pad1 : f32,
|
|
1585
|
+
pad2 : f32,
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
@group(0) @binding(0) var<uniform> u : Uniforms;
|
|
1589
|
+
@group(0) @binding(1) var<storage, read> inputChannels : array<f32>;
|
|
1590
|
+
@group(0) @binding(2) var<storage, read_write> outputChannels : array<f32>;
|
|
1591
|
+
@group(0) @binding(3) var<storage, read_write> state : array<f32>;
|
|
1592
|
+
|
|
1593
|
+
@compute @workgroup_size(1)
|
|
1594
|
+
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
|
1595
|
+
if (gid.x != 0u) { return; }
|
|
1596
|
+
|
|
1597
|
+
let numSamples = u32(u.numSamples);
|
|
1598
|
+
let numChannels = u32(u.numChannels);
|
|
1599
|
+
|
|
1600
|
+
let clampedPan = max(-1.0f, min(1.0f, u.pan));
|
|
1601
|
+
var leftGain = 1.0f;
|
|
1602
|
+
var rightGain = 1.0f;
|
|
1603
|
+
|
|
1604
|
+
if (clampedPan < 0.0f) {
|
|
1605
|
+
rightGain = 1.0f + clampedPan;
|
|
1606
|
+
} else if (clampedPan > 0.0f) {
|
|
1607
|
+
leftGain = 1.0f - clampedPan;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
for (var index = 0u; index < numSamples; index = index + 1u) {
|
|
1611
|
+
if (numChannels > 1u) {
|
|
1612
|
+
outputChannels[index] = inputChannels[index] * leftGain;
|
|
1613
|
+
outputChannels[numSamples + index] = inputChannels[numSamples + index] * rightGain;
|
|
1614
|
+
} else {
|
|
1615
|
+
outputChannels[index] = inputChannels[index] * leftGain;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Dummy reference to prevent state binding from being optimized out
|
|
1620
|
+
state[0] = state[0];
|
|
1621
|
+
}
|
|
1622
|
+
`;
|
|
1623
|
+
const stereoPanningAudioProcessor = async (channels, sampleRate, node, ctx) => {
|
|
1624
|
+
if (!ctx?.device) throw new Error("GPUDevice is required for WebGPU Stereo Panning.");
|
|
1625
|
+
const pan = node.operation?.pan ?? 0;
|
|
1626
|
+
const clampedPan = Math.max(-1, Math.min(1, pan));
|
|
1627
|
+
if (channels.length === 0) return;
|
|
1628
|
+
const numSamples = channels[0].length;
|
|
1629
|
+
if (numSamples === 0) return;
|
|
1630
|
+
if (channels.length === 1) {
|
|
1631
|
+
const rightChannel = new Float32Array(numSamples);
|
|
1632
|
+
rightChannel.set(channels[0]);
|
|
1633
|
+
channels.push(rightChannel);
|
|
1634
|
+
}
|
|
1635
|
+
const numChannels = channels.length;
|
|
1636
|
+
const nodeId = node.operation?.id || "stereo-panning";
|
|
1637
|
+
const frame = ctx.frame ?? 0;
|
|
1638
|
+
const fps = ctx.fps ?? 24;
|
|
1639
|
+
await WebGPUAudioProcessor.process(ctx.device, nodeId, channels, sampleRate, node, frame, fps, PANNING_SHADER_TEMPLATE, () => [
|
|
1640
|
+
sampleRate,
|
|
1641
|
+
clampedPan,
|
|
1642
|
+
0,
|
|
1643
|
+
frame,
|
|
1644
|
+
numSamples,
|
|
1645
|
+
numChannels,
|
|
1646
|
+
0,
|
|
1647
|
+
0
|
|
1648
|
+
], 8, 1, ctx?.renderId, true, ctx?.elapsedMs, ctx?.durationMs);
|
|
1649
|
+
};
|
|
1650
|
+
var renderers_default = defineRenderer({ audioProcessor: stereoPanningAudioProcessor });
|
|
1651
|
+
|
|
1652
|
+
//#endregion
|
|
1653
|
+
//#region src/audio/synth/sfx.ts
|
|
1654
|
+
const DEFAULT_DURATION_SEC = {
|
|
1655
|
+
whoosh: .7,
|
|
1656
|
+
riser: 2,
|
|
1657
|
+
impact: 1.8,
|
|
1658
|
+
downshifter: 1.2,
|
|
1659
|
+
glitch: .35
|
|
1660
|
+
};
|
|
1661
|
+
/** Small, fast, seedable PRNG returning values in [-1, 1). */
|
|
1662
|
+
function createNoise(seed) {
|
|
1663
|
+
let state = (seed ^ 2654435769) >>> 0;
|
|
1664
|
+
return () => {
|
|
1665
|
+
state = state + 1831565813 >>> 0;
|
|
1666
|
+
let t = state;
|
|
1667
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
1668
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
1669
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296 * 2 - 1;
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
/** Chamberlin state-variable filter; returns the band-pass output of one sample. */
|
|
1673
|
+
var BandPass = class {
|
|
1674
|
+
low = 0;
|
|
1675
|
+
band = 0;
|
|
1676
|
+
process(input, freq, q, sampleRate) {
|
|
1677
|
+
const f = 2 * Math.sin(Math.PI * Math.min(freq, sampleRate * .45) / sampleRate);
|
|
1678
|
+
this.low += f * this.band;
|
|
1679
|
+
const high = input - this.low - q * this.band;
|
|
1680
|
+
this.band += f * high;
|
|
1681
|
+
return this.band;
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
/** Exponential sweep from `a` to `b` as `x` moves 0 → 1. */
|
|
1685
|
+
const expSweep = (a, b, x) => a * (b / a) ** x;
|
|
1686
|
+
/** Fast rise, longer fall: a bell centred at `peak` in 0..1. */
|
|
1687
|
+
const bell = (x, peak) => x < peak ? Math.sin(x / peak * (Math.PI / 2)) ** 2 : Math.cos((x - peak) / (1 - peak) * (Math.PI / 2)) ** 2;
|
|
1688
|
+
function whoosh(n, sr, noise, reverse) {
|
|
1689
|
+
const left = new Float32Array(n);
|
|
1690
|
+
const right = new Float32Array(n);
|
|
1691
|
+
const filter = new BandPass();
|
|
1692
|
+
for (let i = 0; i < n; i++) {
|
|
1693
|
+
const x = i / n;
|
|
1694
|
+
const sweep = reverse ? 1 - x : x;
|
|
1695
|
+
const env = bell(x, reverse ? .35 : .6);
|
|
1696
|
+
const s = filter.process(noise(), expSweep(250, 7e3, sweep), .35, sr) * env * .9;
|
|
1697
|
+
const pan = (reverse ? 1 - x : x) * 2 - 1;
|
|
1698
|
+
left[i] = s * Math.sqrt((1 - pan) / 2) * 1.4;
|
|
1699
|
+
right[i] = s * Math.sqrt((1 + pan) / 2) * 1.4;
|
|
1700
|
+
}
|
|
1701
|
+
return [left, right];
|
|
1702
|
+
}
|
|
1703
|
+
function riser(n, sr, noise, reverse) {
|
|
1704
|
+
const left = new Float32Array(n);
|
|
1705
|
+
const right = new Float32Array(n);
|
|
1706
|
+
const air = new BandPass();
|
|
1707
|
+
let phase = 0;
|
|
1708
|
+
for (let i = 0; i < n; i++) {
|
|
1709
|
+
const x = i / n;
|
|
1710
|
+
const sweep = reverse ? 1 - x : x;
|
|
1711
|
+
const env = reverse ? (1 - x) ** 1.5 : x ** 2.2;
|
|
1712
|
+
const freq = expSweep(110, 880, sweep);
|
|
1713
|
+
phase += 2 * Math.PI * freq / sr;
|
|
1714
|
+
const s = ((Math.sin(phase) + .5 * Math.sin(phase * 2) + .25 * Math.sin(phase * 3)) * .3 + air.process(noise(), expSweep(300, 9e3, sweep), .5, sr) * 1.2) * env * .6;
|
|
1715
|
+
left[i] = s;
|
|
1716
|
+
right[i] = s;
|
|
1717
|
+
}
|
|
1718
|
+
return [left, right];
|
|
1719
|
+
}
|
|
1720
|
+
function impact(n, sr, noise) {
|
|
1721
|
+
const left = new Float32Array(n);
|
|
1722
|
+
const right = new Float32Array(n);
|
|
1723
|
+
const burst = new BandPass();
|
|
1724
|
+
let phase = 0;
|
|
1725
|
+
for (let i = 0; i < n; i++) {
|
|
1726
|
+
const t = i / sr;
|
|
1727
|
+
const freq = 38 + 62 * Math.exp(-t * 14);
|
|
1728
|
+
phase += 2 * Math.PI * freq / sr;
|
|
1729
|
+
const sub = Math.sin(phase) * Math.exp(-t * 2.6);
|
|
1730
|
+
const body = burst.process(noise(), 900 * Math.exp(-t * 6) + 120, .6, sr) * Math.exp(-t * 9) * 1.6;
|
|
1731
|
+
const click = i < 96 ? noise() * (1 - i / 96) * .5 : 0;
|
|
1732
|
+
const s = (sub * 1.1 + body + click) * .85;
|
|
1733
|
+
const tail = noise() * Math.exp(-t * 5) * .08;
|
|
1734
|
+
left[i] = s + tail;
|
|
1735
|
+
right[i] = s - tail;
|
|
1736
|
+
}
|
|
1737
|
+
return [left, right];
|
|
1738
|
+
}
|
|
1739
|
+
function glitch(n, sr, noise) {
|
|
1740
|
+
const left = new Float32Array(n);
|
|
1741
|
+
const right = new Float32Array(n);
|
|
1742
|
+
const slice = Math.max(1, Math.round(sr * .022));
|
|
1743
|
+
let held = 0;
|
|
1744
|
+
let gate = 1;
|
|
1745
|
+
let crush = 1;
|
|
1746
|
+
for (let i = 0; i < n; i++) {
|
|
1747
|
+
if (i % slice === 0) {
|
|
1748
|
+
gate = noise() > -.2 ? 1 : 0;
|
|
1749
|
+
crush = 2 + Math.floor((noise() + 1) * 6);
|
|
1750
|
+
}
|
|
1751
|
+
if (i % crush === 0) held = noise() > 0 ? 1 : -1;
|
|
1752
|
+
const env = 1 - i / n;
|
|
1753
|
+
const s = held * gate * env * .45;
|
|
1754
|
+
left[i] = s;
|
|
1755
|
+
right[i] = gate ? -s * .6 : 0;
|
|
1756
|
+
}
|
|
1757
|
+
return [left, right];
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Renders one trigger to stereo PCM positioned at `atBar` on the score timeline.
|
|
1761
|
+
* `atBar` is where the effect *starts*; a riser meant to land on a cut starts
|
|
1762
|
+
* `durationSec / secondsPerBar` bars before it.
|
|
1763
|
+
*/
|
|
1764
|
+
function renderSfx(trigger, ctx) {
|
|
1765
|
+
const durationSec = trigger.durationSec ?? DEFAULT_DURATION_SEC[trigger.type];
|
|
1766
|
+
const sr = ctx.sampleRate;
|
|
1767
|
+
const n = Math.max(1, Math.round(durationSec * sr));
|
|
1768
|
+
const noise = createNoise(ctx.seed + n);
|
|
1769
|
+
let channels;
|
|
1770
|
+
switch (trigger.type) {
|
|
1771
|
+
case "whoosh":
|
|
1772
|
+
channels = whoosh(n, sr, noise, false);
|
|
1773
|
+
break;
|
|
1774
|
+
case "riser":
|
|
1775
|
+
channels = riser(n, sr, noise, false);
|
|
1776
|
+
break;
|
|
1777
|
+
case "downshifter":
|
|
1778
|
+
channels = riser(n, sr, noise, true);
|
|
1779
|
+
break;
|
|
1780
|
+
case "impact":
|
|
1781
|
+
channels = impact(n, sr, noise);
|
|
1782
|
+
break;
|
|
1783
|
+
case "glitch":
|
|
1784
|
+
channels = glitch(n, sr, noise);
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
const gain = trigger.volume ?? .8;
|
|
1788
|
+
for (const channel of channels) for (let i = 0; i < n; i++) channel[i] = Math.tanh((channel[i] ?? 0) * gain);
|
|
1789
|
+
return {
|
|
1790
|
+
startSample: Math.max(0, Math.round(trigger.atBar * ctx.secondsPerBar * sr)),
|
|
1791
|
+
channels
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
/** Adds rendered effects into `target` in place, clipping to its length. */
|
|
1795
|
+
function mixSfxInto(target, effects) {
|
|
1796
|
+
for (const fx of effects) for (let c = 0; c < 2; c++) {
|
|
1797
|
+
const dst = target[c];
|
|
1798
|
+
const src = fx.channels[c];
|
|
1799
|
+
if (!dst || !src) continue;
|
|
1800
|
+
const limit = Math.min(src.length, dst.length - fx.startSample);
|
|
1801
|
+
for (let i = 0; i < limit; i++) {
|
|
1802
|
+
const val = dst[fx.startSample + i];
|
|
1803
|
+
const s = src[i];
|
|
1804
|
+
if (val !== void 0 && s !== void 0) dst[fx.startSample + i] = val + s;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Soft-knee limiter for sums that may exceed full scale (impacts stacked on a
|
|
1810
|
+
* loud mix): transparent below the knee, asymptotically bounded by 1.0 above it.
|
|
1811
|
+
*/
|
|
1812
|
+
function softLimit(target, knee = .7, ceiling = .97) {
|
|
1813
|
+
const headroom = ceiling - knee;
|
|
1814
|
+
for (const channel of target) for (let i = 0; i < channel.length; i++) {
|
|
1815
|
+
const x = channel[i] ?? 0;
|
|
1816
|
+
const a = Math.abs(x);
|
|
1817
|
+
if (a > knee) channel[i] = Math.sign(x) * (knee + headroom * Math.tanh((a - knee) / headroom));
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
//#endregion
|
|
1822
|
+
//#region src/audio/synth/wav-encoder.ts
|
|
1823
|
+
/**
|
|
1824
|
+
* Encodes stereo Float32Array PCM channels (-1.0 to +1.0) into a standard 16-bit RIFF/WAVE Buffer.
|
|
1825
|
+
*/
|
|
1826
|
+
function encodeStereoWav(channels, options = {}) {
|
|
1827
|
+
const sampleRate = options.sampleRate ?? 48e3;
|
|
1828
|
+
const numChannels = 2;
|
|
1829
|
+
const left = channels[0];
|
|
1830
|
+
const right = channels[1];
|
|
1831
|
+
const numSamples = Math.min(left.length, right.length);
|
|
1832
|
+
const blockAlign = numChannels * 2;
|
|
1833
|
+
const byteRate = sampleRate * blockAlign;
|
|
1834
|
+
const dataSize = numSamples * blockAlign;
|
|
1835
|
+
const totalSize = 44 + dataSize;
|
|
1836
|
+
const buffer = Buffer.alloc(totalSize);
|
|
1837
|
+
buffer.write("RIFF", 0, "ascii");
|
|
1838
|
+
buffer.writeUInt32LE(36 + dataSize, 4);
|
|
1839
|
+
buffer.write("WAVE", 8, "ascii");
|
|
1840
|
+
buffer.write("fmt ", 12, "ascii");
|
|
1841
|
+
buffer.writeUInt32LE(16, 16);
|
|
1842
|
+
buffer.writeUInt16LE(1, 20);
|
|
1843
|
+
buffer.writeUInt16LE(numChannels, 22);
|
|
1844
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
1845
|
+
buffer.writeUInt32LE(byteRate, 28);
|
|
1846
|
+
buffer.writeUInt16LE(blockAlign, 32);
|
|
1847
|
+
buffer.writeUInt16LE(16, 34);
|
|
1848
|
+
buffer.write("data", 36, "ascii");
|
|
1849
|
+
buffer.writeUInt32LE(dataSize, 40);
|
|
1850
|
+
let offset = 44;
|
|
1851
|
+
for (let i = 0; i < numSamples; i++) {
|
|
1852
|
+
const sl = Math.max(-1, Math.min(1, left[i] ?? 0));
|
|
1853
|
+
const intL = sl < 0 ? sl * 32768 : sl * 32767;
|
|
1854
|
+
buffer.writeInt16LE(Math.round(intL), offset);
|
|
1855
|
+
offset += 2;
|
|
1856
|
+
const sr = Math.max(-1, Math.min(1, right[i] ?? 0));
|
|
1857
|
+
const intR = sr < 0 ? sr * 32768 : sr * 32767;
|
|
1858
|
+
buffer.writeInt16LE(Math.round(intR), offset);
|
|
1859
|
+
offset += 2;
|
|
1860
|
+
}
|
|
1861
|
+
return buffer;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
//#endregion
|
|
1865
|
+
export { renderers_default as a, renderers_default$3 as c, renderers_default$6 as d, softLimit as i, renderers_default$4 as l, mixSfxInto as n, renderers_default$1 as o, renderSfx as r, renderers_default$2 as s, encodeStereoWav as t, renderers_default$5 as u };
|
|
1866
|
+
//# sourceMappingURL=audio-C4kiPbrV.mjs.map
|