import { afterEach, describe, expect, it, vi } from 'vitest'; import { buildCymbal, buildCymbalExcitation, buildMic, deriveCymbal } from '../src/instruments/bead-cymbal/model'; import { CYMBAL_PRESETS } from '../src/instruments/bead-cymbal/presets'; import { simulate } from '../src/sim/solver'; import type { SimResult } from '../src/sim/types'; describe('partial results', () => { afterEach(() => vi.restoreAllMocks()); it('are prefixes of the finished run (delayed pickup, mallet)', () => { const p = { ...CYMBAL_PRESETS[0].params, rings: 6, duration: 0.01 }; const d = deriveCymbal(p); const mic = buildMic(p, d); const opt = { duration: p.duration, fs: p.fs, cfl: p.cfl, frameRate: p.frameRate, pickup: mic.weights, pickupDelay: mic.delay }; // a clock that runs fast, so partial results come often let now = 0; vi.spyOn(performance, 'now').mockImplementation(() => (now += 500)); const parts: SimResult[] = []; const r = simulate(buildCymbal(d), buildCymbalExcitation(p, d), opt, undefined, (x) => parts.push(x)); expect(parts.length).toBeGreaterThan(3); let prev = 0; for (const x of parts) { const S = x.audio.length; const F = x.frameTimes.length; expect(S).toBeGreaterThan(prev); expect(S).toBeLessThan(r.audio.length); prev = S; expect(F).toBe(Math.floor((S - 1) / Math.round(p.fs / p.frameRate)) + 1); expect(x.audio).toEqual(r.audio.subarray(0, S)); expect(x.contactForce).toEqual(r.contactForce!.subarray(0, S)); expect(x.frames).toEqual(r.frames.subarray(0, 3 * d.n * F)); expect(x.energy).toEqual(r.energy.subarray(0, F)); expect(x.balance).toEqual(r.balance.subarray(0, F)); expect(x.malletPos).toEqual(r.malletPos!.subarray(0, 3 * F)); } }); });