import { describe, expect, it } from 'vitest';
import { renderSpec, type SoundSpec } from '../SoundSpec';
const same = (a: Float32Array, b: Float32Array): boolean =>
a.length === b.length && a.every((v, i) => v === b[i]);
const peak = (samples: Float32Array): number => Math.max(...[...samples].map(Math.abs));
describe('renderSpec — детерминизм', () => {
it('одинаковые спеки → одинаковые сэмплы', () => {
const spec: SoundSpec = { kind: 'hit', dur: 0.2, seed: 42 };
expect(same(renderSpec(spec), renderSpec(spec))).toBe(true);
});
it('другой сид → другой шум (но та же форма затухания)', () => {
const a = renderSpec({ kind: 'hit', dur: 0.2, seed: 1 });
const b = renderSpec({ kind: 'hit', dur: 0.2, seed: 2 });
expect(same(a, b)).toBe(false);
expect(peak(a)).toBeCloseTo(peak(b), 5); // нормализация по одному peak
});
});
describe('renderSpec — kinds', () => {
it('hit/chime/scrape нормализуются к peak, немые буферы исключены', () => {
for (const kind of ['hit', 'chime', 'scrape'] as const) {
const samples = renderSpec({ kind, dur: 0.3, peak: 0.6 });
expect(peak(samples)).toBeCloseTo(0.6, 2);
expect(peak(samples)).toBeGreaterThan(0);
}
});
it('длительности: без лупа — ровно dur×RATE; луп сшит (короче на фейд)', () => {
const hit = renderSpec({ kind: 'hit', dur: 0.4 });
expect(hit.length).toBe(8820); // 0.4 с × 22050
const hum = renderSpec({ kind: 'hum', dur: 2 }); // луп по умолчанию
expect(hum.length).toBeLessThan(44100); // срезан кроссфейд-хвост
expect(hum.length).toBeGreaterThanOrEqual(44100 - 11025); // фейд ≤ 0.5 с
});
it('hum: заданный loop=false не сшивает; hit с loop=true сшивает', () => {
const humRaw = renderSpec({ kind: 'hum', dur: 1, loop: false });
expect(humRaw.length).toBe(22050);
const hitLoop = renderSpec({ kind: 'hit', dur: 1, loop: true });
expect(hitLoop.length).toBeLessThan(22050);
});
it('клэмп длительности: 0.01 → мин 0.05 с, hum 100 → макс 8 с', () => {
expect(renderSpec({ kind: 'hit', dur: 0.01 }).length).toBe(1102); // 0.05 с
expect(renderSpec({ kind: 'hum', dur: 100, loop: false }).length).toBe(176400); // 8 с
});
it('параметры доезжают: freq меняет тембр звона, tone — подложку удара', () => {
const low = renderSpec({ kind: 'chime', dur: 0.3, freq: 220, loop: false });
const high = renderSpec({ kind: 'chime', dur: 0.3, freq: 880, loop: false });
expect(same(low, high)).toBe(false);
const plain = renderSpec({ kind: 'hit', dur: 0.3, seed: 5, loop: false });
const toned = renderSpec({ kind: 'hit', dur: 0.3, seed: 5, tone: 120, loop: false });
expect(same(plain, toned)).toBe(false);
});
});