1
0
mirror of https://gitlab.com/80486DX2-66/gists synced 2024-11-10 06:42:02 +05:30
gists/js-programming/bytebeat-render.js

123 lines
3.2 KiB
JavaScript
Raw Normal View History

2023-12-29 22:25:56 +05:30
/*
2024-01-25 23:03:58 +05:30
bytebeat-render.js
2023-12-29 22:25:56 +05:30
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
2024-01-25 23:03:58 +05:30
Author: Intel A80486DX2-66
License: Creative Commons Zero 1.0 Universal
2023-12-29 22:25:56 +05:30
*/
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
2024-01-01 04:12:17 +05:30
const LIGHTNING_MODE = false // disables sequential file write optimization,
// feel free to enable this
2023-12-29 22:25:56 +05:30
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)]
2024-01-01 04:12:17 +05:30
const randomFileNameAlphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
2023-12-29 22:25:56 +05:30
const generateRandomFilePath = () => {
2023-12-29 22:25:56 +05:30
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))
2023-12-29 22:25:56 +05:30
// 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)
2023-12-29 22:25:56 +05:30
if (sample.constructor === Array)
sample.forEach((sample, index) => {
audioData[CHANNELS * idx + index] = constrainValue(sample)
2023-12-29 22:25:56 +05:30
})
else
audioData[idx] = constrainValue(sample)
t++
2023-12-29 22:25:56 +05:30
}
if (t >= PRODUCT) {
let truncatedArray = new Uint8Array(idx)
truncatedArray.set(audioData.subarray(0, idx))
audioData = truncatedArray
}
appendFileSync(filePath, Buffer.from(audioData.buffer))
2024-01-01 04:04:18 +05:30
audioData = null
2023-12-29 22:25:56 +05:30
}
2024-01-01 04:12:17 +05:30
execSync(
`ffmpeg -f u8 -ar ${FINAL_SAMPLE_RATE} -ac ${CHANNELS} ` +
`-i ${filePath} output_${+new Date()}.wav`)
unlinkSync(filePath)