/* Node.JS: Bytebeat generator Note: Calls `ffmpeg` to convert output raw audio to a WAVE file Warning: The current result is quick and dirty. Not for educational or production purposes. To-Do: Fix signed bytebeat and floatbeat */ const { appendFileSync, unlinkSync, writeFileSync } = require("fs") const { tmpdir } = require("os") const { execSync } = require("child_process") const { basename } = require("path") let BUFFER_SIZE = 65536 // feel free to change this const LIGHTNING_MODE = false // disables sequential file write optimization, // feel free to change this const SAMPLE_RATE = 8000 // feel free to change this const SECONDS = 30 // feel free to change this const CHANNELS = 1 // feel free to change this const FINAL_SAMPLE_RATE = 44100 // feel free to change this const FINAL_SAMPLE_RATE_CONVERSION = SAMPLE_RATE / FINAL_SAMPLE_RATE / CHANNELS const SAMPLES = SECONDS * FINAL_SAMPLE_RATE // feel free to change this const PRODUCT = SAMPLES * CHANNELS BUFFER_SIZE = LIGHTNING_MODE ? PRODUCT : BUFFER_SIZE const TYPE_BYTEBEAT = 0 const TYPE_SIGNED_BYTEBEAT = 1 const TYPE_FLOATBEAT = 2 const SELECTED_TYPE = TYPE_BYTEBEAT // feel free to change this // for bytebeat const int = x => Math.floor(x) const abs = Math.abs const cbrt = Math.cbrt const cos = Math.cos const cosh = Math.cosh const floor = Math.floor const log = Math.log const log2 = Math.log2 const PI = Math.PI const pow = Math.pow const random = Math.random const sin = Math.sin const sinh = Math.sinh const sqrt = Math.sqrt const tan = Math.tan const tanh = Math.tanh const generateAudio = t => { return t&t>>8 } const constrainValue = sample => { switch (SELECTED_TYPE) { case TYPE_BYTEBEAT: return sample & 255 case TYPE_SIGNED_BYTEBEAT: return ((sample + 127) & 255) - 128 case TYPE_FLOATBEAT: return floor(sample * 127) + 127 } } const random_choice = choices => choices[Math.floor(Math.random() * choices.length)] const randomFileNameAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" const generateRandomFileName = () => { let res = tmpdir() + "/" + basename(__filename) + "_" for (let i = 0; i < 64; i++) res += random_choice(randomFileNameAlphabet) return res } let t = 0 let fileName = generateRandomFileName() writeFileSync(fileName, Buffer.alloc(0)) // the loop of sequential file writing, created to ease load on RAM for (let buffer = 0; t < PRODUCT; buffer++) { let audioData = new Uint8Array(BUFFER_SIZE) let idx = 0 for (; idx < BUFFER_SIZE && t < PRODUCT; idx++) { let sample = generateAudio(t * FINAL_SAMPLE_RATE_CONVERSION) if (sample.constructor === Array) sample.forEach((sample, index) => { audioData[CHANNELS * idx + index] = constrainValue(sample) }) else audioData[idx] = constrainValue(sample) t++ } if (t >= PRODUCT) { let truncatedArray = new Uint8Array(idx) truncatedArray.set(audioData.subarray(0, idx)) audioData = truncatedArray } appendFileSync(fileName, Buffer.from(audioData.buffer)) audioData = null } execSync( `ffmpeg -f u8 -ar ${FINAL_SAMPLE_RATE} -ac ${CHANNELS} ` + `-i ${fileName} output_${+new Date()}.wav`) unlinkSync(fileName)