1ee087eCymbal designer: scripted design, thickness field, mount, single strikeJeremy Magland 1// Playback level: absolute (fixed pascal-to-full-scale mapping with a user gain),
2// loudness normalization, or peak normalization. A soft clipper bounds the output.
4export type LevelMode = "absolute" | "loudness" | "peak";
6/** Linear gain applied to the pressure signal [Pa] for the given mode. */
7export function levelGain(samples: Float32Array, fs: number, mode: LevelMode, gainDb: number, peak = 0.95): number {
8 if (samples.length === 0) return 0;
9 if (mode === "absolute") {
10 // 0 dBFS corresponds to 1 Pa at 0 dB gain.
11 return 10 ** (gainDb / 20);
12 }
13 if (mode === "peak") {
14 let max = 0;
15 for (let k = 0; k < samples.length; k++) max = Math.max(max, Math.abs(samples[k]));
16 return max > 0 ? peak / max : 0;
17 }
18 // Loudness: scale the RMS of the sustained part (after the first 50 ms) to -16 dBFS.
19 const start = Math.min(samples.length - 1, Math.round(0.05 * fs));
20 let sum = 0;
21 for (let k = start; k < samples.length; k++) sum += samples[k] * samples[k];
22 const rms = Math.sqrt(sum / Math.max(1, samples.length - start));
23 return rms > 0 ? 10 ** (-16 / 20) / rms : 0;
24}
26/** Peak level [dBFS] the signal would reach before clipping. */
27export function peakDbfs(samples: Float32Array, gain: number): number {
28 let max = 0;
29 for (let k = 0; k < samples.length; k++) max = Math.max(max, Math.abs(samples[k]));
30 return max > 0 ? 20 * Math.log10(max * gain) : -Infinity;
31}
33/** Returns a copy of `samples` scaled for playback or export, soft-clipped to [-peak, peak]. */
34export function normalize(samples: Float32Array, fs: number, mode: LevelMode, gainDb: number, peak = 0.95): Float32Array {
35 const out = new Float32Array(samples.length);
36 const g = levelGain(samples, fs, mode, gainDb, peak);
37 if (mode === "peak") {
38 for (let k = 0; k < samples.length; k++) out[k] = samples[k] * g;
39 return out;
40 }
41 for (let k = 0; k < samples.length; k++) out[k] = peak * Math.tanh((samples[k] * g) / peak);
42 return out;
43}