I'm trying to get the sample rate information of a file before uploading it to server, need to access this for some validation purpose on browser.
What I did so far
let reader = new FileReader();
reader.readAsArrayBuffer(e.files[0]);
reader.onload = function (event) {
let buffer = reader.result;
let AudioContext = window.AudioContext || window.webkitAudioContext;
let context = new AudioContext();
context.decodeAudioData(buffer, function (buffer1) {
console.log(buffer1.sampleRate);
});
}
But this always gives some constant result regardless of file selected. Can anyone suggest what is wrong here or any other idea to achieve the same?
Related
Alright, so I'm trying to determine the intensity (in dB) on samples of an audio file which is recorded by the user's browser.
I have been able to record it and play it through an HTML element.
But when I try to use this element as a source and connect it to an AnalyserNode, AnalyserNode.getFloatFrequencyData always returns an array full of -Infinity, getByteFrequencyData always returns zeroes, getByteTimeDomainData is full of 128.
Here's my code:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source;
var analyser = audioCtx.createAnalyser();
var bufferLength = analyser.frequencyBinCount;
var data = new Float32Array(bufferLength);
mediaRecorder.onstop = function(e) {
var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
chunks = [];
var audioURL = window.URL.createObjectURL(blob);
// audio is an HTML audio element
audio.src = audioURL;
audio.addEventListener("canplaythrough", function() {
source = audioCtx.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioCtx.destination);
analyser.getFloatFrequencyData(data);
console.log(data);
});
}
Any idea why the AnalyserNode behaves like the source is empty/mute? I also tried to put the stream as source while recording, with the same result.
I ran into the same issue, thanks to some of your code snippets, I made it work on my end (the code bellow is typescript and will not work in the browser at the moment of writing):
audioCtx.decodeAudioData(this.result as ArrayBuffer).then(function (buffer: AudioBuffer) {
soundSource = audioCtx.createBufferSource();
soundSource.buffer = buffer;
//soundSource.connect(audioCtx.destination); //I do not need to play the sound
soundSource.connect(analyser);
soundSource.start(0);
setInterval(() => {
calc(); //In here, I will get the analyzed data with analyser.getFloatFrequencyData
}, 300); //This can be changed to 0.
// The interval helps with making sure the buffer has the data
Some explanation (I'm still a beginner when it comes to the Web Audio API, so my explanation might be wrong or incomplete):
An analyzer needs to be able to analyze a specific part of your sound file. In this case I create a AudioBufferSoundNode that contains the buffer that I got from decoding the audio data. I feed the buffer to the source, which eventually will be able to be copied inside the Analyzer. However, without the interval callback, the buffer never seems to be ready and the analysed data contains -Inifinity (which I assume is the absence of any sound, as it has nothing to read) at every index of the array. Which is why the interval is there. It analyses the data every 300ms.
Hope this helps someone!
You need to fetch the audio file and decode the audio buffer.
The url to the audio source must also be on the same domain or have have the correct CORS headers as well (as mentioned by Anthony).
Note: Replace <FILE-URI> with the path to your file in the example below.
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source;
var analyser = audioCtx.createAnalyser();
var button = document.querySelector('button');
var freqs;
var times;
button.addEventListener('click', (e) => {
fetch("<FILE-URI>", {
headers: new Headers({
"Content-Type" : "audio/mpeg"
})
}).then(function(response){
return response.arrayBuffer()
}).then((ab) => {
audioCtx.decodeAudioData(ab, (buffer) => {
source = audioCtx.createBufferSource();
source.connect(audioCtx.destination)
source.connect(analyser);
source.buffer = buffer;
source.start(0);
viewBufferData();
});
});
});
// Watch the changes in the audio buffer
function viewBufferData() {
setInterval(function(){
freqs = new Uint8Array(analyser.frequencyBinCount);
times = new Uint8Array(analyser.frequencyBinCount);
analyser.smoothingTimeConstant = 0.8;
analyser.fftSize = 2048;
analyser.getByteFrequencyData(freqs);
analyser.getByteTimeDomainData(times);
console.log(freqs)
console.log(times)
}, 1000)
}
If the source file from a different domain? That would fail in createMediaElementSource.
I'm currently playing around with the Web Audio API in Chrome (60.0.3112.90) to possibly build a sound wave of a given file via FilerReader, AudioContext, createScriptProcessor, and createAnalyser. I have the following code:
const visualize = analyser => {
analyser.fftSize = 256;
let bufferLength = analyser.frequencyBinCount;
let dataArray = new Float32Array(bufferLength);
analyser.getFloatFrequencyData(dataArray);
}
loadAudio(file){
// creating FileReader to convert audio file to an ArrayBuffer
const fileReader = new FileReader();
navigator.getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
fileReader.addEventListener('loadend', () => {
const fileArrayBuffer = fileReader.result;
let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let processor = audioCtx.createScriptProcessor(4096, 1, 1);
let analyser = audioCtx.createAnalyser();
analyser.connect(processor);
let data = new Float32Array(analyser.frequencyBinCount);
let soundBuffer;
let soundSource = audioCtx.createBufferSource();
// loading audio track into buffer
audioCtx.decodeAudioData(
fileArrayBuffer,
buffer => {
soundBuffer = buffer;
soundSource.buffer = soundBuffer;
soundSource.connect(analyser);
soundSource.connect(audioCtx.destination);
processor.onaudioprocess = () => {
// data becomes array of -Infinity values after call below
analyser.getFloatFrequencyData(data);
};
visuaulize(analyser);
},
error => 'error with decoding audio data: ' + error.err
);
});
fileReader.readAsArrayBuffer(file);
}
Upon loading a file, I get all the way to analyser.getFloatFrequencyData(data). Upon reading the Web audio API docs, it says that the parameter is:
The Float32Array that the frequency domain data will be copied to.
For any sample which is silent, the value is -Infinity.
In my case, I have both an mp3 and wav file I'm using to test this and after invoking analyser.getFloatFrequency(data), both files end up giving me data which becomes an array of `-Infinity' values.
This may be due to my ignorance with Web Audio's API, but my question is why are both files, which contain loud audio, giving me an array that represents silent samples?
The Web Audio AnalyserNode is only designed to work in realtime. (It used to be called RealtimeAnalyser.) Web Audio doesn't have the ability to do analysis on buffers; take a look at another library, like DSP.js.
Instead of:
soundSource.connect(analyser);
soundSource.connect(audioCtx.destination);
try:
soundSource.connect(analyser);
analyser.connect(audioCtx.destination);
Realising I sould do a source ==> anlalsyser ==>> destination chain solved this problem when I encountered it.
I've been trying to follow the steps in some tutorials for playback of a simple, encoded local wav or mp3 file with the web Audio API using a button. My code is the following (testAudioAPI.js):
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var myBuffer;
clickme = document.getElementById('clickme');
clickme.addEventListener('click',clickHandler);
var request = new XMLHttpRequest();
request.open('GET', 'WoodeBlock_SMan_B.wav', true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(theBuffer) {
myBuffer = theBuffer;
}, onError);
}
request.send();
function playSound(buffer) {
var source = context.createBufferSource(), g = context.createGain();
source.buffer = buffer;
source.start(0);
g.gain.value = 0.5;
source.connect(g);
g.connect(context.destination);
}
function clickHandler(e) {
playSound(myBuffer);
}
And the HTML file would look like this:
<!doctype html>
<html>
<body>
<button id="clickme">Play</button>
<script src='testAudioAPI.js'></script>
</body>
</html>
However, no sound is achieved whatsoever. I've tried several snippets but I still can't figure it out. When I try to generate a sound by synthesizing it by creating an oscillator node, I do get sound, but not with buffers from local files. What would be the problem here? Thank you all.
minimalistic approach to modern ES6.
new AudioContext();
context.createBufferSource();
source.buffer = audioBuffer;
audioBuffer requests ArrayBuffer data by fetch, and then decodeAudioData decodes to AudioBuffer.
source.start()
<button id="start">playSound</button>
const audioPlay = async url => {
const context = new AudioContext();
const source = context.createBufferSource();
const audioBuffer = await fetch(url)
.then(res => res.arrayBuffer())
.then(ArrayBuffer => context.decodeAudioData(ArrayBuffer));
source.buffer = audioBuffer;
source.connect(context.destination);
source.start();
};
document.querySelector('#start').onclick = () => audioPlay('music/music.mp3');
stop play: source.stop();
The Web Audio API cannot be played automatically, you need to be triggered by an event.
Creating multiple AudioContext objects will cause an error, you should log out and then create them.
Failed to construct 'AudioContext': number of hardware contexts reached maximum
const audioPlay = (() => {
let context = null;
return async url => {
if (context) context.close();
context = new AudioContext();
const source = context.createBufferSource();
source.buffer = await fetch(url)
.then(res => res.arrayBuffer())
.then(arrayBuffer => context.decodeAudioData(arrayBuffer));
source.connect(context.destination);
source.start();
};
})();
document.querySelector('#start').onclick = () => audioPlay('music/music.mp3');
Local files, huh? Are you just grabbing them from the filesystem (e.g. "file://"), or do you have a local web server running?
From what I recall, serving directly from the filesystem violates CORS in most (all?) browsers – which will prevent your AJAX request from succeeding.
Maybe try serving the page with python -m SimpleHTTPServer or similar, and then access it at http://localhost:8000.
From what I can tell, all of your code looks fine.
You have a call to onError which isn't defined.
Apart from that it should be working, but not with Chrome because Chrome blocks all XMLHttpRequest to local files. But with Firefox for example it should work once you define (or get rid of) your call to onError.
You can create a URL reference to play a local file with two lines of code:
const sound = new URL('../assets/sound.mp3', import.meta.url)
new Audio(sound.href).play()
The href is only needed to satisfy Typescript, otherwise you can omit that suffix.
I'm trying to make a webpage in html5 which stores sample-data from a wav-file in an array. Is there any way to get the sample-data with javascript?
I'm using a file-input to select the wav-file.
In the javascript I already added:
document.getElementById('fileinput').addEventListener('change', readFile, false);
but I have no idea what to do in readFile.
EDIT:
I tried to get the file in an ArrayBuffer, pass it to the decodeAudioData method and get a typedArraybuffer out of it.
This is my code:
var openFile = function(event) {
var input = event.target;
var audioContext = new AudioContext();
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = reader.result;
console.log("arrayBuffer:");
console.log(arrayBuffer);
audioContext.decodeAudioData(arrayBuffer, decodedDone);
};
reader.readAsArrayBuffer(input.files[0]);
};
function decodedDone(decoded) {
var typedArray = new Uint32Array(decoded, 1, decoded.length);
console.log("decoded");
console.log(decoded);
console.log("typedArray");
console.log(typedArray);
for (i=0; i<10; i++)
{
console.log(typedArray[i]);
}
}
The elements of typedArray are all 0. Is my way of creating the typedArray wrong or did I do something else wrong on?
EDIT:
I finally got it. This is my code:
var openFile = function(event) {
var input = event.target;
var audioContext = new AudioContext();
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = reader.result;
console.log("arrayBuffer:");
console.log(arrayBuffer);
audioContext.decodeAudioData(arrayBuffer, decodedDone);
};
reader.readAsArrayBuffer(input.files[0]);
};
function decodedDone(decoded) {
var typedArray = new Float32Array(decoded.length);
typedArray=decoded.getChannelData(0);
console.log("typedArray:");
console.log(typedArray);
}
Thanks for the answers!
You'll need to learn a lot about Web APIs to accomplish that, but in the end it's quite simple.
Get the file contents in an ArrayBuffer with the File API
Pass it to the Web Audio API's decodeAudioData method.
Get a typed ArrayBuffer with the raw samples you wanted.
Edit: If you want to implement an equalizer, you're wasting your time, there's a native equalizer node in the Audio API. Depending on the length of your wave file it might be better not to load it all in memory and instead to just create a source that reads from it and connect that source to an equalizer node.
Here's a simple code example to get a Float32Array from a wav audio file in JavaScript:
let audioData = await fetch("https://example.com/test.wav").then(r => r.arrayBuffer());
let audioCtx = new AudioContext({sampleRate:44100});
let decodedData = await audioCtx.decodeAudioData(audioData); // audio is resampled to the AudioContext's sampling rate
console.log(decodedData.length, decodedData.duration, decodedData.sampleRate, decodedData.numberOfChannels);
let float32Data = decodedData.getChannelData(0); // Float32Array for channel 0
I would like to know if there is a way to select a pdf file using input type="file" and open it using PDFJS
You should be able to use a FileReader to get the contents of a file object as a typed array, which pdfjs accepts (https://mozilla.github.io/pdf.js/examples/)
//Step 1: Get the file from the input element
inputElement.onchange = function(event) {
var file = event.target.files[0];
//Step 2: Read the file using file reader
var fileReader = new FileReader();
fileReader.onload = function() {
//Step 4:turn array buffer into typed array
var typedarray = new Uint8Array(this.result);
//Step 5:pdfjs should be able to read this
const loadingTask = pdfjsLib.getDocument(typedarray);
loadingTask.promise.then(pdf => {
// The document is loaded here...
});
};
//Step 3:Read the file as ArrayBuffer
fileReader.readAsArrayBuffer(file);
}
Edit: The pdfjs API changed at some point since I wrote this first answer in 2015. Updating to reflect the new API as of 2021(thanks to #Chiel) for the updated answer
If getDocument().then is not a function:
I reckon I have managed to solve the new problem with the new API. As mentioned in this GitHub issue, the getDocument function now has an promise added to itself.
In short, this:
PDFJS.getDocument(typedarray).then(function(pdf) {
// The document is loaded here...
});
became this:
const loadingTask = pdfjsLib.getDocument(typedarray);
loadingTask.promise.then(pdf => {
// The document is loaded here...
});
Adapting the older answer to the new api to comply to the bounty gives the following result:
//Step 1: Get the file from the input element
inputElement.onchange = function(event) {
//It is important that you use the file and not the filepath (The file path won't work because of security issues)
var file = event.target.files[0];
var fileReader = new FileReader();
fileReader.onload = function() {
var typedarray = new Uint8Array(this.result);
//replaced the old function with the new api
const loadingTask = pdfjsLib.getDocument(typedarray);
loadingTask.promise.then(pdf => {
// The document is loaded here...
});
};
//Step 3:Read the file as ArrayBuffer
fileReader.readAsArrayBuffer(file);
}
I have created an example below with the official releases of the source code below to show that it is working.
/*Offical release of the pdfjs worker*/
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.5.207/pdf.worker.js';
document.getElementById('file').onchange = function(event) {
var file = event.target.files[0];
var fileReader = new FileReader();
fileReader.onload = function() {
var typedarray = new Uint8Array(this.result);
console.log(typedarray);
const loadingTask = pdfjsLib.getDocument(typedarray);
loadingTask.promise.then(pdf => {
// The document is loaded here...
//This below is just for demonstration purposes showing that it works with the moderen api
pdf.getPage(1).then(function(page) {
console.log('Page loaded');
var scale = 1.5;
var viewport = page.getViewport({
scale: scale
});
var canvas = document.getElementById('pdfCanvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function() {
console.log('Page rendered');
});
});
//end of example code
});
}
fileReader.readAsArrayBuffer(file);
}
<html>
<head>
<!-- The offical release-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.5.207/pdf.js"> </script>
</head>
<body>
<input type="file" id="file">
<h2>Rendered pdf:</h2>
<canvas id="pdfCanvas" width="300" height="300"></canvas>
</body>
</html>
Hope this helps! If not, please comment.
Note:
This might not work in jsFiddle.
I adopted your code and it worked! Then I was browsing for more tips here and there, then I learned there is an even more convenient method.
You can get the URL of client-loaded file with
URL.createObjectURL()
It reduces nesting by one level and you don't need to read the file, convert it to array, etc.