/** * GPU smoke test for the placeholder instrawave adapter, on desktop * WebGPU (Google Dawn) through instrawave's node shim: bake the drum once * at the placeholder accuracy, render two notes through the midiboot * Instrument interface, and check the result is shaped like audio. Run * with `npm run test:gpu`; exits 0 with a note when no GPU is available. */ import { installWebGpu, errMsg } from 'instrawave/scripts/nodeWebGpu.ts'; try { console.log(await installWebGpu()); } catch (e) { console.log(`skipped: ${errMsg(e)}`); process.exit(0); } const { createInstrawaveInstruments } = await import('../src/instruments/instrawave/adapter.ts'); let failures = 0; function check(cond: boolean, msg: string): void { console.log(`${cond ? 'ok ' : 'FAIL'} ${msg}`); if (!cond) failures++; } const drum = createInstrawaveInstruments().find((i) => i.id === 'iw-drum')!; const SR = 44100; const len = Math.round(SR * 2.0); const t0 = performance.now(); const out = (await drum.render({ notes: [ { timeSec: 0.1, durationSec: 0.25, pitch: 48, velocity: 0.9 }, { timeSec: 0.9, durationSec: 0.25, pitch: 55, velocity: 0.6 }, ], sampleRate: SR, lengthSamples: len, })) as Float32Array; console.log(`bake + render in ${((performance.now() - t0) / 1000).toFixed(1)} s`); check(out.length === len, `trace length ${out.length} = ${len}`); let finite = true; for (let i = 0; i < out.length; i++) if (!Number.isFinite(out[i])) { finite = false; break; } check(finite, 'all samples finite'); const seg = (a: number, b: number) => { let p = 0; for (let i = Math.round(a * SR); i < Math.round(b * SR); i++) p = Math.max(p, Math.abs(out[i])); return p; }; // The raw trace is at mic-pressure scale (peaks around 1e-5); the app // normalizes per track, so the checks here are relative to the peak. const peakAll = seg(0, 2.0); check(peakAll > 1e-7, `trace is not silent (peak ${peakAll.toExponential(2)})`); check(seg(0, 0.09) < peakAll * 1e-3, 'silent before the first strike'); check(seg(0.12, 0.8) > peakAll * 0.1, 'first strike sounds'); check(seg(0.92, 1.8) > peakAll * 0.05, 'second strike sounds'); // The render is deterministic against the same bake. const again = (await drum.render({ notes: [{ timeSec: 0.1, durationSec: 0.25, pitch: 48, velocity: 0.9 }], sampleRate: SR, lengthSamples: SR, })) as Float32Array; const again2 = (await drum.render({ notes: [{ timeSec: 0.1, durationSec: 0.25, pitch: 48, velocity: 0.9 }], sampleRate: SR, lengthSamples: SR, })) as Float32Array; let same = again.length === again2.length; if (same) for (let i = 0; i < again.length; i++) if (again[i] !== again2[i]) { same = false; break; } check(same, 'repeat render is bit-identical'); console.log(failures === 0 ? 'GPU smoke test passed' : `${failures} failure(s)`); process.exit(failures > 0 ? 1 : 0);