/** * The level meters of the Song tab (#359): the dB scale, how a level and its peak rise and fall, and * the clip lamp that stays lit. */ import { describe, expect, it } from 'vitest'; import { gainToDb, hushMeter, METER_FALL, meterClipped, meterShare, meterZone, PEAK_HOLD, peakText, QUIET_METER, QUIET_SIDE, stepMeter, stepSide, } from '../../src/editor/song/meterView'; describe('meter scale', () => { it('runs from -48 to +6 dBFS, full scale at 0 dB', () => { expect(gainToDb(1)).toBe(0); expect(gainToDb(0.5)).toBeCloseTo(-6.02, 2); expect(gainToDb(0)).toBe(-Infinity); expect(meterShare(-48)).toBe(0); expect(meterShare(-Infinity)).toBe(0); expect(meterShare(6)).toBe(1); expect(meterShare(0)).toBeCloseTo(48 / 54, 6); }); it('is green, yellow from -6 dB and red past 0 dB', () => { expect(meterZone(-6.1)).toBe('ok'); expect(meterZone(-6)).toBe('warm'); expect(meterZone(0)).toBe('warm'); expect(meterZone(0.1)).toBe('hot'); }); it('writes a peak to a tenth of a dB', () => { expect(peakText(-Infinity)).toBe('-inf'); expect(peakText(-3.24)).toBe('-3.2'); expect(peakText(1.46)).toBe('+1.5'); expect(peakText(-0.01)).toBe('0.0'); }); }); describe('a side of a meter', () => { it('jumps up at once and falls back slowly', () => { const up = stepSide(QUIET_SIDE, 1, 0, 0.016); expect(up.level).toBe(0); const later = stepSide(up, 0, 0.5, 0.5); expect(later.level).toBeCloseTo(-METER_FALL * 0.5, 6); const louder = stepSide(later, 0.5, 0.516, 0.016); expect(louder.level).toBeCloseTo(-6.02, 2); }); it('holds its peak, then lets it fall', () => { const hit = stepSide(QUIET_SIDE, 1, 10, 0.016); const held = stepSide(hit, 0.01, 10 + PEAK_HOLD - 0.1, 0.016); expect(held.peak).toBe(0); const falling = stepSide(held, 0.01, 10 + PEAK_HOLD + 0.1, 0.2); expect(falling.peak).toBeCloseTo(-METER_FALL * 0.2, 6); const gone = stepSide(falling, 0, 20, 3); expect(gone.peak).toBe(-Infinity); expect(gone.level).toBe(-Infinity); }); it('lights the clip lamp past full scale and keeps it lit', () => { expect(stepSide(QUIET_SIDE, 1, 0, 0).clipped).toBe(false); const clipped = stepSide(QUIET_SIDE, 1.01, 0, 0); expect(clipped.clipped).toBe(true); expect(stepSide(clipped, 0, 30, 30).clipped).toBe(true); }); it('keeps the two sides apart: a sound panned hard clips one side only', () => { const meter = stepMeter(QUIET_METER, [2, 0], 0, 0); expect(meter[0].clipped).toBe(true); expect(meter[1].clipped).toBe(false); expect(meter[0].level).toBeCloseTo(6.02, 2); expect(meter[1].level).toBe(-Infinity); expect(meterClipped(meter)).toBe(true); }); it('goes dark when nothing plays, its clip lamp still lit', () => { const hushed = hushMeter(stepMeter(QUIET_METER, [2, 0.5], 0, 0)); expect(hushed.map((side) => [side.level, side.peak])).toEqual([ [-Infinity, -Infinity], [-Infinity, -Infinity], ]); expect(hushed.map((side) => side.clipped)).toEqual([true, false]); }); });