FileSystem not working in html code from seperate file - javascript

So I'm trying to make a website that will play music notes from A-G at random and your goal is to guess them, I'm planning on storing the note mp3s in a separate folder, but at the moment I'm just trying to get it to take one song from the said folder, but it's not working, it can take mp3s from it if inputted manually, but not automatically.
Doesn't work:
const fs = require(`fs`);
let tones = fs.readdirSync(`./tones/`).filter((f) => { return f.endsWith(`.mp3`); });
let i = Math.floor(Math.random() * tones.length); //This is always equal to zero because tones only has one file
function start() {
let audio = new Audio(`./tones/${tones[i]}`);
audio.play();
};
Does work:
const fs = require(`fs`);
let tones = fs.readdirSync(`./tones/`).filter((f) => { return f.endsWith(`.mp3`); });
let i = Math.floor(Math.random() * tones.length);
function start() {
let audio = new Audio(`./tones/Sample.mp3`);
audio.play();
};
Any ideas?

Related

How can I store a JSON file in Javascript to use continuously?

I'm making a website and on one of my pages I have this button that when clicked, should take a haiku from a JSON file that contains over 5000 and display it to the user. Currently I do this the following way.
<script>
const haikuUrl = 'http://mywebsite/haikus.json';
async function getHaiku() {
const num = Math.floor(Math.random() * 5145);
const response = await fetch(haikuUrl);
const data = await response.json();
const {line1, line2, line3} = data[num];
document.getElementById('line1').textContent = line1;
document.getElementById('line2').textContent = line2;
document.getElementById('line3').textContent = line3;
}
document.getElementById('haikuButton').addEventListener('click', () => {
getHaiku();
});
</script>
I'm pretty much new to JS, and after looking around and watching some videos this was the only way I could get this to work but I'm pretty much sure there has to be a better way to do this. Right now, I'm having to load the whole file every time just to get 1 random object from the JSON. What I'd like to have is a constant or a variable that just sits there waiting for the user to ask for a random one. Something like this:
<script>
const data= [{},{},{}...{}]; //an array of all the haikus loaded once at the beginning
function getHaiku() {
const num = Math.floor(Math.random() * 5145);
const {line1, line2, line3} = data[num];
document.getElementById('line1').textContent = line1;
document.getElementById('line2').textContent = line2;
document.getElementById('line3').textContent = line3;
}
document.getElementById('haikuButton').addEventListener('click', () => {
getHaiku();
});
</script>
So pretty much the same just without the having to fetch the whole data every time.
I guess one option could be to hardcode the whole dataset into the js file into a variable, but something tells me there's got to be a better way.
I would first fetch the data, then choose a random haiku by using the randomly generated number as an index of the data array. Something like below:
I have not tested the code below so I am not sure if it works, but hopefully this nudges you in the right direction.
let data;
let button = document.getElementById('haikuButton');
let randomHaiku = '';
// Fetch data
async function getHaikus(){
const response = await fetch('http://mywebsite/haikus.json')
data = await response.json();
}
// Generate random number
function generateRandomNumber(array){
return Math.floor(Math.random() * array.length)
}
// get random haiku
button.addEventListener('click', ()=>{
let index = generateRandomNumber(data)
randomHaiku = data[index]
console.log(randomHaiku);
}, false)
getHaikus();
If I understand correctly your question, one optimization you can do is as follows:
const haikuUrl = 'http://mywebsite/haikus.json';
let haikus = null;
async function getHaiku() {
const num = Math.floor(Math.random() * 5145);
if (!haikus) {
const response = await fetch(haikuUrl);
haikus = await response.json();
}
const {line1, line2, line3} = haikus[num];
[...]
}
So it would download the file the first time the user clicks on the button, but not download it again if the user clicks on the button again (unless they close the webpage in between).
You should also definitely use the length of the array instead of hardcoding 5145, as mentioned in other answers.
It is also certainly possible to embed the data directly in the JS file, but it won't make much difference, it will just make the JS file (which must be downloaded as well) much bigger. So it wouldn't solve the problem of needing to download all the haikus when you need just one.
Making it so that it really only downloads one haiku is a bit more complicated. You could have a backend that the frontend requests a single haiku to (but that probably increases complexity significantly if you currently don't have a backend). You could also store all haikus in separate files with predictable names (for instance haikus/42.txt) and fetch only a single such file.

Sending audio numpy array to front end Javascript to play sound?

Alright so I have this block of code here
ipd.Audio(audio[0].data.cpu().numpy(), rate=hparams.sampling_rate)
I am trying to use the audio[0].data.cpu().numpy() part which contains the audio array data.
I want to send it to the front-end, which I know how. But the problem is I don't know what to do with the data. I have done some research on converting numpy to other forms of data but still pretty lost on how to go about this.
What can I do in the front using JavaScript to turn it into audio. Or better yet using a flask server to redirect it to a get route that returns a mp3 file.
I would start looking into Audio Buffers.
Here is an example I copied from here.
This creates white noise, since we are pushing random values into the audio buffer. Here you have to use your numeric values. Please make sure how to set up the sample rate (should be defined in your python tool)
<body>
<h1>AudioBuffer example</h1>
<button>Make white noise</button>
<script>
const button = document.querySelector('button');
const myScript = document.querySelector('script');
let AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx;
// Stereo
let channels = 2;
function init() {
audioCtx = new AudioContext();
}
button.onclick = function() {
if(!audioCtx) {
init();
}
// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
let frameCount = audioCtx.sampleRate * 2.0;
let myArrayBuffer = audioCtx.createBuffer(channels, frameCount, audioCtx.sampleRate);
// Fill the buffer with white noise;
//just random values between -1.0 and 1.0
for (let channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
let nowBuffering = myArrayBuffer.getChannelData(channel);
for (let i = 0; i < frameCount; i++) {
// Math.random() is in [0; 1.0]
// audio needs to be in [-1.0; 1.0]
nowBuffering[i] = Math.random() * 2 - 1;
}
}
// Get an AudioBufferSourceNode.
// This is the AudioNode to use when we want to play an AudioBuffer
let source = audioCtx.createBufferSource();
// set the buffer in the AudioBufferSourceNode
source.buffer = myArrayBuffer;
// connect the AudioBufferSourceNode to the
// destination so we can hear the sound
source.connect(audioCtx.destination);
// start the source playing
source.start();
source.onended = () => {
console.log('White noise finished');
}
}
</script>
</body>

Making byte-beats using WebAudio

Byte-beats are a fun way of making lo-fi music. I want to make some music myself using the WebAudio API. Here's my current code:
const sampleRate = 8000;
const frameCount = sampleRate * 5;
const audioCtx = new AudioContext({ sampleRate: sampleRate });
const src = audioCtx.createBufferSource();
const buf = audioCtx.createBuffer(1, frameCount, sampleRate);
buf.getChannelData(0).set(buf.getChannelData(0).map((_, t) => {
return (Math.sin(t / 10 + Math.sin(t * Math.pow(2, t >> 10)))) * 64 + 128;
}));
src.buffer = buf;
src.connect(audioCtx.destination);
src.start(0, 0, 100);
console.log('Reached the end :/');
My issue with this solution is that I've to create an huge buffer which has to be kept in memory. I was hoping that there would be a dynamic way of setting the sound's amplitude to save memory.
The byte-beats will be entire music compositions and can be pretty long. So, the frame counts can become pretty huge.
Can anyone please suggest me how to do this? Using other libraries is an option but I would prefer avoiding that.
That sounds like a good use case for an AudioWorklet. When using an AudioWorklet you only have to provide 128 samples at a time. It runs on another thread for performance reasons. That makes it a bit more complicated to code. Here is a basic example which uses a dynamically created URL to load the code for the AudioWorklet.
const play = async () => {
const audioContext = new AudioContext({ sampleRate: 8000 });
const source = `registerProcessor(
'byte-beats-processor',
class extends AudioWorkletProcessor {
process (_, [ output ]) {
for (let i = 0; i < 128; i += 1) {
const t = currentFrame + i;
output[0][i] = Math.sin(t);
}
return true;
}
}
);`
const blob = new Blob([ source ], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
await audioContext.audioWorklet.addModule(url);
const audioWorkletNode = new AudioWorkletNode(audioContext, 'byte-beats-processor');
audioWorkletNode.connect(audioContext.destination);
};
play();
Of course using Math.sin(t) is only an example. You probably want to replace that with something more interesting.
The AudioWorklet is currently only available in Chrome. That means you still need to use the deprecated ScriptProcessorNode for other browsers or you can use a polyfill such as standardized-audio-context which allows you to use the same code for all browsers.

Random, shuffle JavaScript playlist

I know this has been asked before, but I am new to JavaScript and after having read other answers I can't understand specifically why my method isn't working. The first track that plays is random, but then when the song ends, the same track repeats over and over again instead of choosing a different random track. If audio.play chooses a random track the first time, why doesn't it choose a random track again when the song ends, but instead loops the same track? Help appreciated:
var audio_files = [
"TRACKS/1.mp3",
"TRACKS/2.mp3",
"TRACKS/3.mp3"
]
var random_file = audio_files[Math.floor(Math.random() * audio_files.length)];
var audio = new Audio(random_file);
audio.play();
audio.addEventListener('ended', function(){
audio.play();
}
It would be easier to shuffle the array and simply iterate on it to get each file. With this solution, you will not get the same file twice because of the random solution.
Once you get to the end, do the same thing, shuffle the array and iterate again. Like this, the list will change giving the impression to selecting a different file in a random manner (but truly simply iterating).
Here is a pseudo code to it
function readFiles(){
array = shuffle(array);
for(var i = 0; i < array.length; ++i){
play(array[i]);
}
readFiles(); //to read undefinitly
}
Here and here, you will find a great threads to shuffle the array. I will not do it again, just follow the answer there.
In your case, you don't want a loop but you will use a counter the get the next file and shuffle the array again once you get to the end
function getNextFile(){
if(currentFile >= array.length){ //reach the end
shuffle(array);
currentFile = 0;
}
return array[currentFile+];
}
Your code need to be like this:
var audio_files = [
"TRACKS/1.mp3",
"TRACKS/2.mp3",
"TRACKS/3.mp3"
]
function random_file(){
return audio_files[Math.floor(Math.random() * audio_files.length)];
}
var audio = new Audio( random_file() );
audio.play();
audio.addEventListener('ended', function(){
audio.play( random_file() );
}
Your listner may be like this if player have another way to specify file for existing payer
audio.addEventListener('ended', function(){
audio.src = random_file();
audio.play();
}
or if your player have no such api method the only way is
function listner() {
audio = new Audio( random_file() );
audio.play();
audio.addEventListener('ended', listner)
}
audio.addEventListener('ended', listner)
Just install the famous underscoreJS module on client or server side with Node.js and invoke the sample function;
var random_file = _.sample(audio_files);
Open demo in codepen
To play the files in the playlist infinitely with the help of the cool underscore module, you can do the following
If you are a nodejs developer you can install the module using the following command
npm i underscore
To use it in nodejs you can do the following
const _ = require("underscore")
To use it on the frontend you can put the following script just before the closing tag of the html body tag
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.2/underscore-min.js"></script>
Now lets get started with the logic
Here is my html file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>SHUFFLED PLAYLIST</title>
</head>
<body>
<button id="playsong">play playlist</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.2/underscore-min.js"></script>
</body>
</html>
Here is my javascript file
let played_files = [];
let playsong = document.querySelector("#playsong");
let audio = new Audio();
let audio_files = [
"songs/song_num1.mp3",
"songs/song_num2.mp3",
"songs/song_num3.m4a",
"songs/song_num4.mp3",
];
function random_file() {
let file = _.sample(audio_files);
let allSongsPlayed = _.size(played_files) === _.size(audio_files);
if (_.contains(played_files, file) || allSongsPlayed) {
if (allSongsPlayed) {
played_files = [];
}
return random_file();
} else {
played_files.push(file);
return file;
}
}
function playSong() {
let file = random_file();
audio.src = file;
audio.play();
}
playsong.addEventListener("click", () => {
playSong();
});
audio.addEventListener("ended", playSong);

Play Audio through JS with Overlapping

I have the following JS code for a canvas based game.
var EXPLOSION = "sounds/explosion.wav";
function playSound(str, vol) {
var snd = new Audio();
snd.src = str;
snd.volume = vol;
snd.play();
}
function createExplosion() {
playSound(EXPLOSION, 0.5);
}
This works, however it sends a server request to download the sound file every time it is called. Alternatively, if I declare the Audio object beforehand:
var snd = new Audio();
snd.src = EXPLOSION;
snd.volume = 0.5;
function createExplosion() {
snd.play();
}
This works, however if the createExplosion function is called before the sound is finished playing, it does not play the sound at all. This means that only a single playthrough of the sound file is allowed at a time - and in scenarios that multiple explosions are taking place it doesn't work at all.
Is there any way to properly play an audio file multiple times overlapping with itself?
I was looking for this for ages in a tetris game i'm building and I think this solution is the best.
function playSoundMove() {
var sound = document.getElementById("move");
sound.load();
sound.play();
}
just have it loaded and ready to go.
You could just duplicate the node with cloneNode() and play() that duplicate node.
My audio element looks like this:
<audio id="knight-audio" src="knight.ogg" preload="auto"></audio>
and I have an onClick listener that does just that:
function click() {
const origAudio = document.getElementById("knight-audio");
const newAudio = origAudio.cloneNode()
newAudio.play()
}
And since the audio element isn't going to be displayed, you don't actually have to attach the node to anything.
I verified client-side and server-side that Chrome only tries to download the audio file once.
Caveats: I'm not sure about performance impacts, since this on my site this clip doesn't get played more than ~40x maximum for a page. You might have to clean up the audio nodes if you're doing something much larger than that?
Try this:
(function() {
var snds = {};
window.playSound(str,vol) {
if( !snds[str]) (snds[str] = new Audio()).src = str;
snds[str].volume = vol;
snds[str].play();
}
})();
Then the first time you call it it will fetch the sound, but every time after that it will reuse the same sound object.
EDIT: You can also preload with duplicates to allow the sound to play more than once at a time:
(function() {
var snds = {}
window.playSound = function(str,vol) {
if( !snds[str]) {
snds[str] = [new Audio()];
snds[str][0].src = str;
}
var snd = snds[str], pointer = 0;
while( snd[pointer].playing) {
pointer++;
if( pointer >= snd.length) {
snd.push(new Audio());
snd[pointer].src = str;
}
}
snd[pointer].volume = vol;
snd[pointer].play();
};
})();
Note that this will send multiple requests if you play the sound overlapping itself too much, but it should return Not Modified very quickly and will only do so if you play it more times than you have previously.
In my game i'm using preoading but after the sound is initiated (its not so smart to not preload at all or preload everything on page load, some sound hasn't played in some gameplay at all, why to load them)
const audio {};
audio.dataload = {'entity':false,'entityes':[],'n':0};
audio.dataload.ordernum = function() {
audio.dataload.n = (audio.dataload.n + 1)%10;
return audio.dataload.n;
}
audio.dataload.play = function() {
audio.dataload.entity = new Audio('/some.mp3');
for (let i = 0; i<10;i++) {
audio.dataload.entityes.push(audio.dataload.entity.cloneNode());
}
audio.dataload.entityes[audio.dataload.ordernum()].play();
}
audio.dataload.play() // plays sound and preload sounds to memory when it isn't
I've created a class that allows for layered audio. This is very similar to other answers where it creates another node with the same src, but this class will only do that if necessary. If it has created a node already that has been completed, it will replay that existing node.
Another tweak to this is that initially fetch the audio and use the URL of the blob. I do this for efficiency; so the src doesn't have to be fetched externally every single time a new node is created.
class LayeredAudio {
url;
samples = [];
constructor(src){
fetch(src)
.then(response => response.blob())
.then((blob) => {
this.url = URL.createObjectURL(blob);
this.samples[0] = new Audio(this.url);
});
}
play(){
if(!this.samples.find(e => e.paused)?.play()){
this.samples.push(new Audio(this.url))
this.samples[this.samples.length - 1].play()
}
}
}
const aud = new LayeredAudio("URL");
aud.play()
Relying more on memory than process time, we can make an array of multiple clones of the Audio and then play them by order:
function gameSnd() {
tick_wav = new Audio('sounds/tick.wav');
victory_wav = new Audio('sounds/victory.wav');
counter = 0;
ticks = [];
for (var i = 0; i<10;i++)
ticks.push(tick_wav.cloneNode());
tick = function(){
counter = (counter + 1)%10;
ticks[counter].play();
}
victory = function(){
victory_wav.play();
}
}
When I tried some of the other solutions there was some delay, but I may have found a better alternative. This will plow through a good chunk of memory if you make the audio array's length high. I doubt you will need to play the same audio more than 10 times at the same time, but if you do just make the array length longer.
var audio = new Array(10);
// The length of the audio array is how many times
// the audio can overlap
for (var i = 0; i < audio.length; i++) {
audio[i] = new Audio("your audio");
}
function PlayAudio() {
// Whenever you want to play it call this function
audio[audioIndex].play();
audioIndex++;
if(audioIndex > audio.length - 1) {
audioIndex = 0;
}
}
I have found this to be the simples way to overlap the same audio over itself
<button id="btn" onclick="clickMe()">ding</button>
<script>
function clickMe() {
const newAudio = new Audio("./ding.mp3")
newAudio.play()
}

Categories

Resources