1
0
mirror of https://gitlab.com/80486DX2-66/gists synced 2024-12-26 11:30:03 +05:30
gists/js-programming/bytebeat-render.js

121 lines
3.4 KiB
JavaScript

/*
* bytebeat-render.js [Node]
*
* 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.
*
* TODO: Fix signed bytebeat and floatbeat
*
* Author: Intel A80486DX2-66
* License: Creative Commons Zero 1.0 Universal
*/
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 enable 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 generateRandomFilePath = () => {
let res = tmpdir() + "/" + basename(__filename) + "_"
for (let i = 0; i < 64; i++)
res += random_choice(randomFileNameAlphabet)
return res
}
let t = 0
let filePath = generateRandomFilePath()
writeFileSync(filePath, Buffer.alloc(0))
// the loop of sequential file writing, created to ease load on RAM
const max = Math.floor((PRODUCT + (BUFFER_SIZE - 1)) / BUFFER_SIZE),
needTwoBuffers = max > 1, needSingleBuffer = !needTwoBuffers
let audioData = new Uint8Array(needSingleBuffer ? PRODUCT : BUFFER_SIZE)
for (let seq = 0; seq < max; seq++) {
if (needTwoBuffers && (t + BUFFER_SIZE) >= PRODUCT) {
let calculatedSize = PRODUCT - t
audioData = new Uint8Array(calculatedSize)
}
for (let idx = 0; t < PRODUCT && idx < BUFFER_SIZE; idx++, t++) {
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)
}
appendFileSync(filePath, Buffer.from(audioData.buffer))
}
execSync(
`ffmpeg -f u8 -ar ${FINAL_SAMPLE_RATE} -ac ${CHANNELS} ` +
`-i ${filePath} output_${+new Date()}.wav`)
unlinkSync(filePath)