/** * What a streamed sound (#349) is played from on the audio thread: the samples in order, read at the * rate of the context, silent where they come late and skipped once they are there. */ import { describe, expect, it } from 'vitest'; import { STREAM_RATE, StreamQueue } from '../../src/core/music/streamQueue'; const ramp = (from: number, count: number) => Float32Array.from({ length: count }, (_, i) => from + i); const block = (size = 128) => [new Float32Array(size), new Float32Array(size)]; describe('the queue of a streamed sound', () => { it('plays what it is sent sample for sample at the rate of the stream, across its chunks', () => { const queue = new StreamQueue(2, 1); queue.push([ramp(0, 100), ramp(1000, 100)]); queue.push([ramp(100, 100), ramp(1100, 100)]); queue.end(); const out = block(); expect(queue.render(out)).toBe(true); expect(Array.from(out[0]!.subarray(98, 102))).toEqual([98, 99, 100, 101]); expect(Array.from(out[1]!.subarray(0, 2))).toEqual([1000, 1001]); expect(queue.render(out)).toBe(false); expect(Array.from(out[0]!.subarray(70, 74))).toEqual([198, 199, 0, 0]); expect(queue.used).toBe(200); }); it('plays a mono stream on both sides', () => { const queue = new StreamQueue(1, 1); queue.push([ramp(5, 300)]); const out = block(); queue.render(out); expect(out[1]![3]).toBe(8); }); it('reads it at the rate of a context running at 44.1 kHz along a smooth curve', () => { const step = STREAM_RATE / 44100; const queue = new StreamQueue(1, step); const sine = (i: number) => Math.sin((2 * Math.PI * 440 * i) / STREAM_RATE); for (let at = 0; at < 2 * STREAM_RATE; at += 4800) queue.push([Float32Array.from({ length: 4800 }, (_, i) => sine(at + i))]); const out = [new Float32Array(44100)]; queue.render(out); let worst = 0; for (let i = 4; i < 44000; i++) worst = Math.max(worst, Math.abs(out[0]![i]! - sine(i * step))); expect(worst).toBeLessThan(1e-4); expect(queue.used).toBe(Math.floor(44100 * step)); }); it('is silent while what is due has not come, and skips as much once it has, so it keeps time', () => { const queue = new StreamQueue(1, 1); queue.push([ramp(0, 64)]); const out = [new Float32Array(128)]; queue.render(out); // The last two it had are held for the curve and come late with the rest. expect(out[0]![61]).toBe(61); expect(out[0]![100]).toBe(0); queue.push([ramp(64, 1000)]); expect(queue.used).toBe(128); expect(queue.skipped).toBe(66); queue.render(out); expect(out[0]![0]).toBe(128); }); it('says it has played out once it has ended and all of it has been read', () => { const queue = new StreamQueue(2, 1); queue.push([ramp(0, 300), ramp(0, 300)]); queue.end(); expect(queue.render(block())).toBe(true); expect(queue.render(block())).toBe(true); const out = block(); expect(queue.render(out)).toBe(false); expect(Array.from(out[0]!.subarray(42, 46))).toEqual([298, 299, 0, 0]); expect(queue.done).toBe(true); }); it('skips what came too late even when nothing more comes after it', () => { const queue = new StreamQueue(1, 1); queue.push([ramp(0, 10)]); queue.render(block()); queue.end(); expect(queue.done).toBe(true); expect(queue.render(block())).toBe(false); }); });