/** * The seed's sounds, built here instead of shipped or taken from the system: unlike the DejaVu * typefaces there is no audio file the host and the CI image are guaranteed to share, and a binary * in the repo would be a binary in the repo. WAV is the one format that can be written by hand. * * The bytes are a pure function of the arguments, so storage stays content-addressed: re-seeding * writes the same hash and the checks can name a file by its exact size. */ const SAMPLE_RATE = 8000; const BITS = 16; const CHANNELS = 1; /** * A mono 16-bit PCM sine, faded in and out over a millisecond so it neither clicks on start nor on * stop – a decoder is happy either way, but a human listening to the editor's preview is not. */ export function sineWav(seconds, frequency = 440) { const frames = Math.round(SAMPLE_RATE * seconds); const data = Buffer.alloc(frames * 2); const fade = Math.max(1, Math.round(SAMPLE_RATE * 0.001)); for (let i = 0; i < frames; i++) { const envelope = Math.min(1, i / fade, (frames - 1 - i) / fade); const value = Math.sin((2 * Math.PI * frequency * i) / SAMPLE_RATE) * envelope * 0.6; data.writeInt16LE(Math.round(value * 32767), i * 2); } return withRiffHeader(data); } function withRiffHeader(data) { const byteRate = (SAMPLE_RATE * CHANNELS * BITS) / 8; const header = Buffer.alloc(44); header.write('RIFF', 0); header.writeUInt32LE(36 + data.length, 4); header.write('WAVE', 8); header.write('fmt ', 12); header.writeUInt32LE(16, 16); // PCM header length header.writeUInt16LE(1, 20); // 1 = uncompressed PCM header.writeUInt16LE(CHANNELS, 22); header.writeUInt32LE(SAMPLE_RATE, 24); header.writeUInt32LE(byteRate, 28); header.writeUInt16LE((CHANNELS * BITS) / 8, 32); // block align header.writeUInt16LE(BITS, 34); header.write('data', 36); header.writeUInt32LE(data.length, 40); return Buffer.concat([header, data]); }