1
0
mirror of https://gitlab.com/80486DX2-66/gists synced 2024-09-20 09:15:41 +05:30
gists/js-programming/bytebeat-render.js

113 lines
3.1 KiB
JavaScript

/*
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 => {
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))
}
execSync(`ffmpeg -f u8 -ar ${FINAL_SAMPLE_RATE} -ac ${CHANNELS} -i ${fileName} output_${+new Date()}.wav`)
unlinkSync(fileName)