/* loudness.c — EBU R128 / ITU-R BS.1770 K-weighting + 100 ms energy steps. * * The heavy per-sample part of integrated-loudness measurement, compiled to * WebAssembly (scripts/build-loudness-wasm.sh → frontend/loudness-wasm.js). * JavaScript computes the filter coefficients for the sample rate, feeds the * decoded audio in chunks through inbuf(), then reads the per-step energies * and applies the 400 ms block gating (absolute -70 LUFS, relative -10 LU). * No libc: only multiply-adds here. */ #define CHUNK 65536 #define MAXSTEPS 400000 /* 100 ms steps: ~11 hours */ static float in0[CHUNK], in1[CHUNK]; static double stepE[MAXSTEPS]; static int nsteps, nch, stepLen, stepPos; static double acc; static double k1[5], k2[5]; /* b0 b1 b2 a1 a2 for each stage */ static double st[2][8]; /* per channel: stage1 x1 x2 y1 y2, stage2 x1 x2 y1 y2 */ float *inbuf(int ch) { return ch ? in1 : in0; } double *steps(void) { return stepE; } int count(void) { return nsteps; } void setup(int channels, int steplen, double a0, double a1, double a2, double a3, double a4, double b0, double b1, double b2, double b3, double b4) { nch = channels > 1 ? 2 : 1; stepLen = steplen > 0 ? steplen : 1; stepPos = 0; nsteps = 0; acc = 0; k1[0] = a0; k1[1] = a1; k1[2] = a2; k1[3] = a3; k1[4] = a4; k2[0] = b0; k2[1] = b1; k2[2] = b2; k2[3] = b3; k2[4] = b4; for (int c = 0; c < 2; c++) for (int j = 0; j < 8; j++) st[c][j] = 0; } void feed(int n) { if (n > CHUNK) n = CHUNK; for (int i = 0; i < n; i++) { double e = 0; for (int c = 0; c < nch; c++) { double *s = st[c]; double x = c ? in1[i] : in0[i]; double y = k1[0] * x + k1[1] * s[0] + k1[2] * s[1] - k1[3] * s[2] - k1[4] * s[3]; s[1] = s[0]; s[0] = x; s[3] = s[2]; s[2] = y; double z = k2[0] * y + k2[1] * s[4] + k2[2] * s[5] - k2[3] * s[6] - k2[4] * s[7]; s[5] = s[4]; s[4] = y; s[7] = s[6]; s[6] = z; e += z * z; } acc += e; if (++stepPos == stepLen) { if (nsteps < MAXSTEPS) stepE[nsteps++] = acc; acc = 0; stepPos = 0; } } }