/** A Reverb (#328): the impulse response it makes for itself. */ import { describe, expect, it } from 'vitest'; import { REVERB_MAX_DECAY, REVERB_PRESETS, reverbImpulse } from '../../src/core/soundfx/reverb'; const RATE = 48000; /** The RMS of a stretch of samples, in dB. */ function db(data: Float32Array, from: number, to: number): number { let sum = 0; for (let i = from; i < to; i++) sum += data[i]! ** 2; return 10 * Math.log10(sum / (to - from)); } /** The share of a stretch's energy that is above a few kHz: what is left after a two-tap average. */ function highShare(data: Float32Array, from: number, to: number): number { let all = 0; let high = 0; for (let i = from + 1; i < to; i++) { all += data[i]! ** 2; high += ((data[i]! - data[i - 1]!) / 2) ** 2; } return high / all; } describe('the impulse response of a Reverb', () => { it('lasts its decay and falls by 60 dB over it, two channels of their own noise', () => { const [left, right] = reverbImpulse(RATE, 2, 0); expect(left!.length).toBe(2 * RATE); expect(left).not.toEqual(right); // A tenth of the way at the start and at the end: 54 dB apart, 6 dB a tenth. expect(db(left!, 0, RATE / 5) - db(left!, (9 * RATE) / 5, 2 * RATE)).toBeCloseTo(54, 0); }); it('is the same every time, and never longer than 10 s', () => { expect(reverbImpulse(RATE, 1, 0.5)).toEqual(reverbImpulse(RATE, 1, 0.5)); expect(reverbImpulse(RATE, 60, 0)[0]!.length).toBe(REVERB_MAX_DECAY * RATE); }); it('loses its highs faster with damping', () => { const [open] = reverbImpulse(RATE, 1, 0); const [damped] = reverbImpulse(RATE, 1, 1); const late = [0.8 * RATE, RATE] as const; expect(highShare(damped!, ...late)).toBeLessThan(highShare(open!, ...late) / 4); // At the start both are open. expect(highShare(damped!, 0, RATE / 20)).toBeCloseTo(highShare(open!, 0, RATE / 20), 1); }); it('has four spaces from a small room to a cave', () => { expect(Object.keys(REVERB_PRESETS)).toEqual(['room', 'hall', 'cave', 'plate']); expect(REVERB_PRESETS.room.decay).toBeLessThan(REVERB_PRESETS.hall.decay); expect(REVERB_PRESETS.hall.decay).toBeLessThan(REVERB_PRESETS.cave.decay); }); });