@hraness/dawg 0.2.0 → 0.4.0
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/CHANGELOG.md +201 -0
- package/DAWG.md +762 -11
- package/README.md +79 -30
- package/core/chords.ts +1724 -0
- package/core/diff.ts +261 -0
- package/core/euclid.ts +670 -0
- package/core/fx.ts +1065 -0
- package/core/key.ts +43 -0
- package/core/kits.ts +320 -0
- package/core/params.ts +111 -0
- package/core/pitch.ts +60 -0
- package/core/rhythm.ts +287 -0
- package/core/score.ts +1054 -14
- package/core/sdk/eval-child.ts +125 -0
- package/core/sdk/eval.ts +260 -0
- package/core/sdk/print.ts +675 -0
- package/core/sdk/sync-chords.ts +54 -0
- package/core/sdk/v1.ts +4159 -0
- package/core/slug.ts +19 -0
- package/core/synth.ts +1001 -0
- package/package.json +8 -5
- package/src/agent/agent.ts +317 -16
- package/src/agent/brief.ts +50 -5
- package/src/agent/chord-tools.ts +357 -0
- package/src/agent/drum-tools.ts +135 -0
- package/src/agent/gateway.ts +215 -41
- package/src/agent/models.ts +633 -0
- package/src/agent/ops.ts +3 -18
- package/src/agent/pack-tools.ts +369 -0
- package/src/agent/planner.ts +55 -2
- package/src/agent/preview-tool.ts +270 -0
- package/src/agent/provider.ts +234 -106
- package/src/agent/rhythm-tools.ts +145 -0
- package/src/agent/sse.ts +31 -7
- package/src/agent/tools.ts +705 -14
- package/src/agent/usage.ts +296 -0
- package/src/agent/workspace.ts +683 -0
- package/src/agent/xcb-agent.ts +35 -14
- package/src/agent/xcb.ts +233 -21
- package/src/audio/audition.ts +93 -0
- package/src/audio/cache.ts +160 -0
- package/src/audio/click.ts +125 -0
- package/src/audio/effects/bus.ts +147 -0
- package/src/audio/effects/chain.ts +99 -0
- package/src/audio/effects/common.ts +251 -0
- package/src/audio/effects/convolution.ts +301 -0
- package/src/audio/effects/drive.ts +142 -0
- package/src/audio/effects/duck.ts +122 -0
- package/src/audio/effects/dynamics.ts +121 -0
- package/src/audio/effects/filter.ts +319 -0
- package/src/audio/effects/modulation.ts +174 -0
- package/src/audio/effects/space.ts +337 -0
- package/src/audio/engine.ts +430 -33
- package/src/audio/kits.ts +200 -0
- package/src/audio/live.ts +152 -0
- package/src/audio/packs.ts +1787 -0
- package/src/audio/player.ts +19 -4
- package/src/audio/preview.ts +470 -0
- package/src/audio/random.ts +15 -0
- package/src/audio/render-worker.ts +68 -0
- package/src/audio/renderer.ts +174 -0
- package/src/audio/sampler.ts +420 -0
- package/src/audio/samples.ts +1025 -0
- package/src/audio/synth/oscillators.ts +268 -0
- package/src/audio/synth/voice.ts +555 -0
- package/src/audio/synth/zzfx.ts +137 -0
- package/src/audio/wav.ts +550 -334
- package/src/audio/wavetable-maker.ts +717 -0
- package/src/audio/wavetable.ts +624 -0
- package/src/auth/cli.ts +146 -27
- package/src/auth/credentials.ts +167 -41
- package/src/auth/discover.ts +481 -0
- package/src/auth/login.ts +885 -128
- package/src/auth/openrouter.ts +206 -0
- package/src/auth/picker.ts +282 -0
- package/src/auth/runner.ts +25 -2
- package/src/auth/tui.ts +60 -43
- package/src/commands/drums.ts +299 -0
- package/src/commands/edit.ts +192 -0
- package/src/commands/fx.ts +360 -0
- package/src/commands/help.ts +535 -0
- package/src/commands/history.ts +32 -19
- package/src/commands/music.ts +34 -15
- package/src/commands/pack.ts +423 -0
- package/src/commands/rhythm.ts +230 -0
- package/src/commands/sample.ts +462 -0
- package/src/commands/synth.ts +229 -0
- package/src/commands/wavetable.ts +370 -0
- package/src/main.ts +2136 -170
- package/src/media/analyze.ts +364 -0
- package/src/media/backend.ts +253 -0
- package/src/media/cli.ts +186 -0
- package/src/media/download.ts +281 -0
- package/src/media/dsp.ts +281 -0
- package/src/media/import.ts +130 -0
- package/src/media/lyrics.ts +201 -0
- package/src/media/notes.ts +363 -0
- package/src/media/paths.ts +168 -0
- package/src/media/process.ts +226 -0
- package/src/media/registry.ts +9 -0
- package/src/media/sidecar.ts +72 -0
- package/src/media/stemdeck.ts +254 -0
- package/src/media/stems.ts +173 -0
- package/src/media/tools.ts +397 -0
- package/src/media/types.ts +92 -0
- package/src/media/vendor/basic-pitch.ts +261 -0
- package/src/media/vendor/drums.ts +817 -0
- package/src/media/vendor/grid.ts +203 -0
- package/src/media/vendor/util.ts +139 -0
- package/src/media/vendor/wav.ts +233 -0
- package/src/media/wavetable.ts +202 -0
- package/src/project/check.ts +80 -0
- package/src/project/init.ts +253 -0
- package/src/project/sync.ts +432 -0
- package/src/project/typecheck.ts +149 -0
- package/src/render.ts +49 -6
- package/src/session/attach.ts +3 -4
- package/src/session/daemon.ts +25 -8
- package/src/session/delta.ts +249 -0
- package/src/session/naming.ts +6 -38
- package/src/session/port.ts +26 -6
- package/src/session/rebase.ts +38 -8
- package/src/session/store.ts +116 -21
- package/src/tui/audition.ts +501 -0
- package/src/tui/euclid.ts +472 -0
- package/src/tui/menu.ts +2125 -0
- package/src/tui/play-chords.ts +538 -0
- package/src/tui/play-mode.ts +442 -0
- package/src/tui/play-session.ts +984 -0
- package/src/tui/sketch.ts +108 -0
- package/src/web/fetch.ts +340 -0
- package/src/web/http.ts +137 -0
- package/src/web/search.ts +684 -0
- package/tui/activity.ts +42 -3
- package/tui/app.ts +398 -33
- package/tui/drums.ts +44 -0
- package/tui/grammar.ts +281 -0
- package/tui/highway.ts +24 -2
- package/tui/layers.ts +14 -2
- package/tui/play-strip.ts +188 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send effects on the stereo pair: delay and reverb. Without their
|
|
3
|
+
* optional parameters both render exactly as the original renderer did.
|
|
4
|
+
*/
|
|
5
|
+
import type { Track } from "../../../core/score.ts";
|
|
6
|
+
import type { DecodedSample } from "../samples.ts";
|
|
7
|
+
import {
|
|
8
|
+
builtinImpulse,
|
|
9
|
+
convolveStereo,
|
|
10
|
+
sampleImpulse,
|
|
11
|
+
type Impulse,
|
|
12
|
+
} from "./convolution.ts";
|
|
13
|
+
import {
|
|
14
|
+
CONTROL_SAMPLES,
|
|
15
|
+
OnePole,
|
|
16
|
+
Param,
|
|
17
|
+
interpolateAutomation,
|
|
18
|
+
type EffectContext,
|
|
19
|
+
} from "./common.ts";
|
|
20
|
+
|
|
21
|
+
/** Delay length in seconds: `time` when set, otherwise `beats` at the tempo. */
|
|
22
|
+
export function delaySeconds(track: Track, tempoBpm: number): number {
|
|
23
|
+
const delay = track.delay;
|
|
24
|
+
if (!delay) return 0;
|
|
25
|
+
return delay.time !== undefined && delay.time > 0
|
|
26
|
+
? delay.time
|
|
27
|
+
: (delay.beats * 60) / tempoBpm;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Stereo feedback delay. Absent `pingpong`, each side echoes its own input
|
|
32
|
+
* and feeds back into the opposite side (the original sound). With
|
|
33
|
+
* `pingpong`, the mono sum enters the left line only and every repeat
|
|
34
|
+
* crosses over, so echoes alternate left, right, left. `highcut` runs a
|
|
35
|
+
* one-pole low-pass on everything written into the lines, so each repeat
|
|
36
|
+
* is darker than the last. Feedback and mix follow their lanes. `wetOnly`
|
|
37
|
+
* (a shared orbit bus) replaces the input with the repeats alone.
|
|
38
|
+
*/
|
|
39
|
+
export function applyDelay(
|
|
40
|
+
left: Float64Array,
|
|
41
|
+
right: Float64Array,
|
|
42
|
+
track: Track,
|
|
43
|
+
context: EffectContext,
|
|
44
|
+
wetOnly = false,
|
|
45
|
+
): void {
|
|
46
|
+
const delay = track.delay;
|
|
47
|
+
if (!delay) return;
|
|
48
|
+
const feedbackLane = track.delayFeedbackAutomation ?? [];
|
|
49
|
+
const mixLane = track.delayMixAutomation ?? [];
|
|
50
|
+
if (delay.mix <= 0 && mixLane.length === 0) return;
|
|
51
|
+
const { sampleRate, samplesPerTick } = context;
|
|
52
|
+
const length = Math.max(
|
|
53
|
+
1,
|
|
54
|
+
Math.round(delaySeconds(track, context.tempoBpm) * sampleRate),
|
|
55
|
+
);
|
|
56
|
+
const lineL = new Float64Array(length);
|
|
57
|
+
const lineR = new Float64Array(length);
|
|
58
|
+
let feedback = delay.feedback;
|
|
59
|
+
let mix = delay.mix;
|
|
60
|
+
const automated = feedbackLane.length > 0 || mixLane.length > 0;
|
|
61
|
+
const pingpong = delay.pingpong === true;
|
|
62
|
+
const highcut =
|
|
63
|
+
delay.highcut === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: [
|
|
66
|
+
new OnePole(delay.highcut, sampleRate),
|
|
67
|
+
new OnePole(delay.highcut, sampleRate),
|
|
68
|
+
];
|
|
69
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
70
|
+
if (automated && index % CONTROL_SAMPLES === 0) {
|
|
71
|
+
const tick = index / samplesPerTick;
|
|
72
|
+
feedback = interpolateAutomation(feedbackLane, tick, delay.feedback);
|
|
73
|
+
mix = interpolateAutomation(mixLane, tick, delay.mix);
|
|
74
|
+
}
|
|
75
|
+
const slot = index % length;
|
|
76
|
+
const wetL = lineL[slot]!;
|
|
77
|
+
const wetR = lineR[slot]!;
|
|
78
|
+
const dryL = left[index]!;
|
|
79
|
+
const dryR = right[index]!;
|
|
80
|
+
let writeL: number;
|
|
81
|
+
let writeR: number;
|
|
82
|
+
if (pingpong) {
|
|
83
|
+
writeL = (dryL + dryR) * 0.5 + wetR * feedback;
|
|
84
|
+
writeR = wetL * feedback;
|
|
85
|
+
} else {
|
|
86
|
+
writeL = dryL + wetR * feedback;
|
|
87
|
+
writeR = dryR + wetL * feedback;
|
|
88
|
+
}
|
|
89
|
+
if (highcut) {
|
|
90
|
+
writeL = highcut[0]!.process(writeL);
|
|
91
|
+
writeR = highcut[1]!.process(writeR);
|
|
92
|
+
}
|
|
93
|
+
lineL[slot] = writeL;
|
|
94
|
+
lineR[slot] = writeR;
|
|
95
|
+
left[index] = (wetOnly ? 0 : dryL) + wetL * mix;
|
|
96
|
+
right[index] = (wetOnly ? 0 : dryR) + wetR * mix;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Seconds until the track's delay decays below -60 dB, bounded. */
|
|
101
|
+
export function delayTailFor(track: Track, tempoBpm: number): number {
|
|
102
|
+
if (!track.delay) return 0;
|
|
103
|
+
const feedback = Math.max(
|
|
104
|
+
track.delay.feedback,
|
|
105
|
+
...(track.delayFeedbackAutomation ?? []).map((point) => point.value),
|
|
106
|
+
);
|
|
107
|
+
const repeats =
|
|
108
|
+
feedback <= 0.001 ? 1 : Math.min(64, Math.log(0.001) / Math.log(feedback));
|
|
109
|
+
return delaySeconds(track, tempoBpm) * (repeats + 1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Freeverb comb and allpass tunings at 44.1 kHz; the right side is spread. */
|
|
113
|
+
const COMB_TUNING = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617] as const;
|
|
114
|
+
const ALLPASS_TUNING = [556, 441, 341, 225] as const;
|
|
115
|
+
const STEREO_SPREAD = 23;
|
|
116
|
+
const REVERB_INPUT_GAIN = 0.015;
|
|
117
|
+
const REVERB_WET_SCALE = 3;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convolution wet level: an energy-normalized impulse on the summed pair,
|
|
121
|
+
* scaled so `mix` sounds as loud as the algorithmic tail at the same mix.
|
|
122
|
+
*/
|
|
123
|
+
const IR_WET_SCALE = 2.1;
|
|
124
|
+
|
|
125
|
+
/** Impulses decoded from samples, by sha256 and rate (small: a few IRs). */
|
|
126
|
+
const SAMPLE_IMPULSES = new Map<string, Impulse>();
|
|
127
|
+
const MAX_SAMPLE_IMPULSES = 8;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The impulse a track's `reverb.ir` names at this rate: a generated
|
|
131
|
+
* built-in, or the decoded sample in `irs`. Undefined when the track has no
|
|
132
|
+
* `ir` or its sample did not load (the algorithmic reverb then plays).
|
|
133
|
+
*/
|
|
134
|
+
export function reverbImpulse(
|
|
135
|
+
track: Track,
|
|
136
|
+
sampleRate: number,
|
|
137
|
+
irs?: ReadonlyMap<string, DecodedSample>,
|
|
138
|
+
): Impulse | undefined {
|
|
139
|
+
const ir = track.reverb?.ir;
|
|
140
|
+
if (!ir) return undefined;
|
|
141
|
+
if (ir.src.startsWith("builtin:"))
|
|
142
|
+
return builtinImpulse(ir.src.slice("builtin:".length), sampleRate);
|
|
143
|
+
const sample = irs?.get(track.id);
|
|
144
|
+
if (!sample) return undefined;
|
|
145
|
+
const key = `${sample.sha256}@${sampleRate}`;
|
|
146
|
+
let impulse = SAMPLE_IMPULSES.get(key);
|
|
147
|
+
if (!impulse) {
|
|
148
|
+
impulse = sampleImpulse(
|
|
149
|
+
`${ir.src}#${sample.sha256}`,
|
|
150
|
+
sample.mono,
|
|
151
|
+
sample.sampleRate,
|
|
152
|
+
sampleRate,
|
|
153
|
+
);
|
|
154
|
+
if (SAMPLE_IMPULSES.size >= MAX_SAMPLE_IMPULSES)
|
|
155
|
+
SAMPLE_IMPULSES.delete(SAMPLE_IMPULSES.keys().next().value!);
|
|
156
|
+
SAMPLE_IMPULSES.set(key, impulse);
|
|
157
|
+
}
|
|
158
|
+
return impulse;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Seconds of reverb tail a render keeps for this track. */
|
|
162
|
+
export function reverbTailFor(
|
|
163
|
+
track: Track,
|
|
164
|
+
sampleRate = 44_100,
|
|
165
|
+
irs?: ReadonlyMap<string, DecodedSample>,
|
|
166
|
+
): number {
|
|
167
|
+
const reverb = track.reverb;
|
|
168
|
+
const lane = track.fxAutomation?.["reverb-mix"] ?? [];
|
|
169
|
+
if (!reverb || (reverb.mix <= 0 && lane.length === 0)) return 0;
|
|
170
|
+
const impulse = reverbImpulse(track, sampleRate, irs);
|
|
171
|
+
const decay = impulse
|
|
172
|
+
? impulse.left.length / sampleRate
|
|
173
|
+
: (reverb.fade ?? 1 + 3 * reverb.size);
|
|
174
|
+
return Math.min(20, decay + (reverb.predelay ?? 0));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Schroeder–Moorer reverb in the Freeverb arrangement: eight damped
|
|
179
|
+
* feedback combs then four allpasses per side, the right side detuned.
|
|
180
|
+
* `size` sets comb feedback and damping; `fade` instead sets each comb's
|
|
181
|
+
* feedback for an exact -60 dB decay time; `dim` sets the in-loop damping
|
|
182
|
+
* frequency, `lowpass` filters the input and `predelay` delays it.
|
|
183
|
+
* `wetOnly` (a shared orbit bus) replaces the input with the tail alone.
|
|
184
|
+
*/
|
|
185
|
+
export function applyReverb(
|
|
186
|
+
left: Float64Array,
|
|
187
|
+
right: Float64Array,
|
|
188
|
+
track: Track,
|
|
189
|
+
context: EffectContext,
|
|
190
|
+
wetOnly = false,
|
|
191
|
+
): void {
|
|
192
|
+
const reverb = track.reverb;
|
|
193
|
+
if (!reverb) return;
|
|
194
|
+
const mixParam = new Param(
|
|
195
|
+
reverb.mix,
|
|
196
|
+
track.fxAutomation?.["reverb-mix"],
|
|
197
|
+
context.samplesPerTick,
|
|
198
|
+
);
|
|
199
|
+
if (reverb.mix <= 0 && !mixParam.automated) return;
|
|
200
|
+
const { sampleRate } = context;
|
|
201
|
+
const impulse = reverbImpulse(track, sampleRate, context.irs);
|
|
202
|
+
if (impulse) {
|
|
203
|
+
applyConvolutionReverb(
|
|
204
|
+
left,
|
|
205
|
+
right,
|
|
206
|
+
track,
|
|
207
|
+
context,
|
|
208
|
+
impulse,
|
|
209
|
+
mixParam,
|
|
210
|
+
wetOnly,
|
|
211
|
+
);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const scale = sampleRate / 44_100;
|
|
215
|
+
const sizeFeedback = 0.7 + 0.28 * reverb.size;
|
|
216
|
+
const damp =
|
|
217
|
+
reverb.dim === undefined
|
|
218
|
+
? 0.2 + 0.3 * reverb.size
|
|
219
|
+
: Math.exp(
|
|
220
|
+
(-2 * Math.PI * Math.min(reverb.dim, sampleRate * 0.45)) / sampleRate,
|
|
221
|
+
);
|
|
222
|
+
let wet = reverb.mix * REVERB_WET_SCALE;
|
|
223
|
+
const channel = (spread: number) => ({
|
|
224
|
+
combs: COMB_TUNING.map((tuning) => {
|
|
225
|
+
const length = Math.max(1, Math.round((tuning + spread) * scale));
|
|
226
|
+
return {
|
|
227
|
+
buffer: new Float64Array(length),
|
|
228
|
+
index: 0,
|
|
229
|
+
store: 0,
|
|
230
|
+
feedback:
|
|
231
|
+
reverb.fade === undefined
|
|
232
|
+
? sizeFeedback
|
|
233
|
+
: Math.min(0.995, 10 ** ((-3 * length) / sampleRate / reverb.fade)),
|
|
234
|
+
};
|
|
235
|
+
}),
|
|
236
|
+
allpasses: ALLPASS_TUNING.map((tuning) => ({
|
|
237
|
+
buffer: new Float64Array(
|
|
238
|
+
Math.max(1, Math.round((tuning + spread) * scale)),
|
|
239
|
+
),
|
|
240
|
+
index: 0,
|
|
241
|
+
})),
|
|
242
|
+
});
|
|
243
|
+
const sides = [channel(0), channel(STEREO_SPREAD)] as const;
|
|
244
|
+
const process = (side: (typeof sides)[number], input: number): number => {
|
|
245
|
+
let output = 0;
|
|
246
|
+
for (const comb of side.combs) {
|
|
247
|
+
const delayed = comb.buffer[comb.index]!;
|
|
248
|
+
comb.store = delayed * (1 - damp) + comb.store * damp;
|
|
249
|
+
comb.buffer[comb.index] = input + comb.store * comb.feedback;
|
|
250
|
+
comb.index = comb.index + 1 === comb.buffer.length ? 0 : comb.index + 1;
|
|
251
|
+
output += delayed;
|
|
252
|
+
}
|
|
253
|
+
for (const allpass of side.allpasses) {
|
|
254
|
+
const delayed = allpass.buffer[allpass.index]!;
|
|
255
|
+
allpass.buffer[allpass.index] = output + delayed * 0.5;
|
|
256
|
+
output = delayed - output;
|
|
257
|
+
allpass.index =
|
|
258
|
+
allpass.index + 1 === allpass.buffer.length ? 0 : allpass.index + 1;
|
|
259
|
+
}
|
|
260
|
+
return output;
|
|
261
|
+
};
|
|
262
|
+
const inputFilter =
|
|
263
|
+
reverb.lowpass === undefined
|
|
264
|
+
? undefined
|
|
265
|
+
: new OnePole(reverb.lowpass, sampleRate);
|
|
266
|
+
const predelaySamples = Math.round((reverb.predelay ?? 0) * sampleRate);
|
|
267
|
+
const predelay =
|
|
268
|
+
predelaySamples > 0 ? new Float64Array(predelaySamples) : undefined;
|
|
269
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
270
|
+
if (mixParam.automated && index % CONTROL_SAMPLES === 0)
|
|
271
|
+
wet = mixParam.at(index) * REVERB_WET_SCALE;
|
|
272
|
+
let input = (left[index]! + right[index]!) * REVERB_INPUT_GAIN;
|
|
273
|
+
if (inputFilter) input = inputFilter.process(input);
|
|
274
|
+
if (predelay) {
|
|
275
|
+
const slot = index % predelaySamples;
|
|
276
|
+
const delayed = predelay[slot]!;
|
|
277
|
+
predelay[slot] = input;
|
|
278
|
+
input = delayed;
|
|
279
|
+
}
|
|
280
|
+
if (wetOnly) {
|
|
281
|
+
left[index] = process(sides[0], input) * wet;
|
|
282
|
+
right[index] = process(sides[1], input) * wet;
|
|
283
|
+
} else {
|
|
284
|
+
left[index]! += process(sides[0], input) * wet;
|
|
285
|
+
right[index]! += process(sides[1], input) * wet;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* `reverb.ir`: the summed pair (through `lowpass` and `predelay`, as the
|
|
292
|
+
* algorithmic reverb) convolved with the impulse and added at `mix`.
|
|
293
|
+
* `size`, `fade` and `dim` describe the algorithmic tail and do nothing
|
|
294
|
+
* here: the impulse is the room.
|
|
295
|
+
*/
|
|
296
|
+
function applyConvolutionReverb(
|
|
297
|
+
left: Float64Array,
|
|
298
|
+
right: Float64Array,
|
|
299
|
+
track: Track,
|
|
300
|
+
context: EffectContext,
|
|
301
|
+
impulse: Impulse,
|
|
302
|
+
mixParam: Param,
|
|
303
|
+
wetOnly: boolean,
|
|
304
|
+
): void {
|
|
305
|
+
const reverb = track.reverb!;
|
|
306
|
+
const { sampleRate } = context;
|
|
307
|
+
const input = new Float64Array(left.length);
|
|
308
|
+
const inputFilter =
|
|
309
|
+
reverb.lowpass === undefined
|
|
310
|
+
? undefined
|
|
311
|
+
: new OnePole(reverb.lowpass, sampleRate);
|
|
312
|
+
const offset = Math.round((reverb.predelay ?? 0) * sampleRate);
|
|
313
|
+
for (let index = 0; index + offset < left.length; index += 1) {
|
|
314
|
+
let value = (left[index]! + right[index]!) * 0.5;
|
|
315
|
+
if (inputFilter) value = inputFilter.process(value);
|
|
316
|
+
input[index + offset] = value;
|
|
317
|
+
}
|
|
318
|
+
if (wetOnly) {
|
|
319
|
+
left.fill(0);
|
|
320
|
+
right.fill(0);
|
|
321
|
+
}
|
|
322
|
+
let wet = reverb.mix * IR_WET_SCALE;
|
|
323
|
+
convolveStereo(
|
|
324
|
+
input,
|
|
325
|
+
impulse,
|
|
326
|
+
left,
|
|
327
|
+
right,
|
|
328
|
+
wet,
|
|
329
|
+
mixParam.automated
|
|
330
|
+
? (index) => {
|
|
331
|
+
if (index % CONTROL_SAMPLES === 0)
|
|
332
|
+
wet = mixParam.at(index) * IR_WET_SCALE;
|
|
333
|
+
return wet;
|
|
334
|
+
}
|
|
335
|
+
: undefined,
|
|
336
|
+
);
|
|
337
|
+
}
|