From 1606eb57d2f7d70fa899f338c354d28ef1df49a7 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Fri, 29 Dec 2023 19:55:56 +0300 Subject: [PATCH] add js-programming/bytebeat-render.js --- js-programming/bytebeat-render.js | 113 ++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 js-programming/bytebeat-render.js diff --git a/js-programming/bytebeat-render.js b/js-programming/bytebeat-render.js new file mode 100644 index 0000000..3ac94d2 --- /dev/null +++ b/js-programming/bytebeat-render.js @@ -0,0 +1,113 @@ +/* +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 +To-Do: (cosmetic) Wrap the code before 81th column +*/ + +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 => { + t *= FINAL_SAMPLE_RATE_CONVERSION + 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) + 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)) +} + +execSync(`ffmpeg -f u8 -ar ${FINAL_SAMPLE_RATE} -ac ${CHANNELS} -i ${fileName} output_${+new Date()}.wav -y`) +unlinkSync(fileName)