124 lines
3.6 KiB
JavaScript
124 lines
3.6 KiB
JavaScript
|
const fs = require("fs");
|
||
|
const wav = require("node-wav");
|
||
|
const path = require("path");
|
||
|
|
||
|
const tempDir = "./temp";
|
||
|
const soundDir = "./sounds";
|
||
|
|
||
|
const soundPaths = fs.readdirSync("sounds");
|
||
|
let loadedSounds = [];
|
||
|
let offset = 0;
|
||
|
for (let p in soundPaths)
|
||
|
{
|
||
|
const soundPath = path.join(soundDir, soundPaths[p]);
|
||
|
if (fs.statSync(soundPath).isDirectory())
|
||
|
{
|
||
|
offset++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
let buffer = fs.readFileSync(soundPath);
|
||
|
let decoded = wav.decode(buffer);
|
||
|
loadedSounds[p - offset] = decoded;
|
||
|
}
|
||
|
|
||
|
// somehow this works
|
||
|
// idk how to merge audio of different sample rates
|
||
|
// i tried looking it up and didn't find anything
|
||
|
// so i just made sure the sound files were exported with 48000 bytes per second
|
||
|
async function mergeBuffers (buffers, playtime = -1)
|
||
|
{
|
||
|
// for the final length, either use the specified playtime, or the length of the first sound
|
||
|
let length = playtime > 0 ? playtime * 48000 : buffers[0].length;
|
||
|
let finalBuffer = [];
|
||
|
for (let f_nr = 0; f_nr < length; f_nr++)
|
||
|
{
|
||
|
// literally add all the sound bytes together
|
||
|
// yeah, this works??
|
||
|
let value = 0;
|
||
|
for (let buffer of buffers)
|
||
|
if (buffer[f_nr]) value += buffer[f_nr];
|
||
|
finalBuffer.push(value);
|
||
|
}
|
||
|
return Float32Array.from(finalBuffer);
|
||
|
}
|
||
|
|
||
|
class MemeAudio
|
||
|
{
|
||
|
constructor()
|
||
|
{
|
||
|
this.tracks = [];
|
||
|
}
|
||
|
|
||
|
playSound (soundId, position = 0, track = -1)
|
||
|
{
|
||
|
// track not specified, go up one from previous
|
||
|
if (track < 0)
|
||
|
track = this.tracks.length;
|
||
|
|
||
|
// track doesn't exist, initialize a new one
|
||
|
let stereoTrackBuffer = this.tracks[track];
|
||
|
if (!this.tracks[track])
|
||
|
stereoTrackBuffer = [[], []];
|
||
|
|
||
|
// get the data of the sound to play
|
||
|
let sound = loadedSounds[soundId];
|
||
|
for (let ch in stereoTrackBuffer) // should just be left/right audio channels
|
||
|
{
|
||
|
let loadedChannel = sound.channelData[ch];
|
||
|
let byteArray = [];
|
||
|
|
||
|
// add empty space until the sound should play
|
||
|
for (let i = 0; i < position * 48000; i++)
|
||
|
byteArray[i] = 0;
|
||
|
|
||
|
// copy audio data from sound's channel
|
||
|
for (let byte of loadedChannel)
|
||
|
byteArray.push(byte);
|
||
|
|
||
|
// convert to Float32array
|
||
|
stereoTrackBuffer[ch] = Float32Array.from(byteArray);
|
||
|
}
|
||
|
|
||
|
// add to timeline
|
||
|
this.tracks[track] = stereoTrackBuffer;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
async encode (duration)
|
||
|
{
|
||
|
let ac = { sampleRate: 48000, float: true, bitDepth: 32 }; // encoding config
|
||
|
|
||
|
// make an array of all left channel audio and all right channel audio
|
||
|
let combinedTrack = [[], []];
|
||
|
for (let track in this.tracks)
|
||
|
{
|
||
|
combinedTrack[0].push(this.tracks[track][0]);
|
||
|
combinedTrack[1].push(this.tracks[track][1]);
|
||
|
}
|
||
|
|
||
|
// make a single stereo track
|
||
|
let mergedTrack = await Promise.all([mergeBuffers(combinedTrack[0], duration), mergeBuffers(combinedTrack[1], duration)]);
|
||
|
|
||
|
let encoded = wav.encode(mergedTrack, ac);
|
||
|
return encoded;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function generateAudio (seconds)
|
||
|
{
|
||
|
let audio = new MemeAudio();
|
||
|
let pos = 0;
|
||
|
while (pos < seconds)
|
||
|
{
|
||
|
audio.playSound(Math.floor(Math.random() * loadedSounds.length), pos);
|
||
|
pos += (Math.random() * 1) - 0.25;
|
||
|
}
|
||
|
let encoded = await audio.encode(seconds);
|
||
|
resultPath = path.join(tempDir, `snd_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}.wav`);
|
||
|
await fs.promises.writeFile(resultPath, encoded);
|
||
|
return resultPath;
|
||
|
}
|
||
|
|
||
|
module.exports = { generateAudio };
|