JavaScript Web Audio: cannot properly decode audio data? - javascript

I'm trying to use the Web Audio API in JavaScript to load a sound into a buffer and play it. Unfortunately it doesn't work and I get the following error:
Uncaught TypeError: Failed to set the 'buffer' property on 'AudioBufferSourceNode':
The provided value is not of type 'AudioBuffer'.
I can pinpoint which line is giving me the error, but I don't know why. Here is the relevant code if it helps:
var audioContext;
var playSoundBuffer;
function init() {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
audioContext = new AudioContext();
loadNote();
}
function loadNote() {
var request = new XMLHttpRequest();
request.open("GET", "./sounds/topE.wav", true);
request.responseType = "arraybuffer";
request.onload = function() {
audioContext.decodeAudioData(request.response, function(buffer) {
playSoundBuffer = buffer;
}, function(error) {
console.error("decodeAudioData error", error);
});
};
request.send();
playSound();
}
function playSound() {
var source = audioContext.createBufferSource();
source.buffer = playSoundBuffer; // This is the line that generates the error
source.connect(audioContext.destination);
source.start(0);
}
I believe the decodeAudioData method returns an AudioBuffer to its first callback function (its second parameter). I tried to save this AudioBuffer to the "playSoundBuffer" and then play it, but I get that error and I'm not sure why. Any help would be greatly appreciated.

The reason you get that error is because you are ignoring the asynchronous nature of your code and treat it as if it were synchronous. If you always log the contents of all relevant parts as the first step in debugging you will realize that at the time you try to process your buffer it's undefined and not an AudioBuffer at all. Tip: Always console.log everything until you know exactly how it behaves at any point.
function loadNote() {
var request = new XMLHttpRequest();
request.open("GET", "./sounds/topE.wav", true);
request.responseType = "arraybuffer";
request.onload = function() {
audioContext.decodeAudioData(request.response, function(buffer) {
playSoundBuffer = buffer;
playSound(); // don't start processing it before the response is there!
}, function(error) {
console.error("decodeAudioData error", error);
});
};
request.send();//start doing something async
}

Related

javascript audio api play blob url

I worked out a testing function for Web Audio API to play url blob:
// trigger takes a sound play function
function loadSound(url, trigger) {
let context = new (window.AudioContext || window.webkitAudioContext)();
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
trigger(()=>{
// play sound
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start();
});
}, e=>{
console.log(e);
});
}
request.send();
}
loadSound(url, fc=>{
window.addEventListener('click', fc);
});
this is just for test, actually, I need a function to call to directly play the sound from url, if any current playing, quit it.
let ac;
function playSound(url) {
if(ac){ac.suspend()}
let context = new (window.AudioContext || window.webkitAudioContext)();
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
// play sound
let source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
// source.noteOn(0); // play the source now
ac = context;
source.start();
}, e=>{
console.log(e);
});
}
request.send();
}
window.addEventListener('click',()=>{
playSound(url);
});
I did not do much modification, however, the second version, triggers works fine, but always produces no sound.
I suspect it may be this variable scope issue, I will be very glad if you can help me debug it.
since the blob url is too long, I put two versions in code pen.
working version
not working version
Instead of calling supsend on the stored AudioContext, save a reference to the AudioBufferSourceNode that is currently playing. Then check if the reference exits and call stop() whenever you play a new sound.
const context = new AudioContext();
let bufferSource = null;
function playSound(url) {
if (bufferSource !== null) {
bufferSource.stop();
bufferSource = null;
}
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, (buffer) => {
bufferSource = context.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.connect(context.destination);
bufferSource.start();
bufferSource.addEventListener('ended', () => {
bufferSource = null;
});
}, (error) => {
console.log(error);
});
}
request.send();
}
window.addEventListener('click', () => {
playSound(url);
});

Web Audio API source.start() only plays the second time it is called (in safari)

I am learning how to use the Web Audio API and I am experiencing a problem that I am not sure how to resolve. I am not sure if it is because I am a beginner at this, or there is something going on that I don't understand.
Basically I am creating a function that plays a short mp3. It should http GET to load MP3 into the audio buffer the first time the audio is played. (Subsequently requests for the same audio don't need to re-fetch the mp3)
The code below works completely correctly in Chrome. However in Safari playAudio('filename') will not play audio the very first time that it is invoked. Every single time after that it works fine. Any expert advise from someone more experienced than me would be very much appreciated.
<script>
var context;
function init() {
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
context = new AudioContext();
}
catch(e) {
alert("Your browser doesn't support Web Audio API");
}
}
window.addEventListener('load', init);
function loadAudio(w) {
var audioURL='https://biblicaltext.com/audio/' + w + '.mp3';
var request = new XMLHttpRequest();
request.open("GET", audioURL, true);
request.responseType = 'arraybuffer';
request.onload = function() {
//console.log("update", request.readyState, request.status)
if (request.readyState === 4) {
if (request.status === 200) {
//take the audio from http request and decode it in an audio buffer
context.decodeAudioData(request.response, function(buffer) {
if(buffer) {
console.log(w, buffer);
playAudioBuffer(w, buffer);
} else {
console.log("audio buffer decoding failed")
}
}, function(ec) {
console.log("http request buffer decoding failed")
});
} else {
console.log("get", audioURL, "failed", request.status)
}
}
}
request.send();
}
var audioBuffer;
var currentWord;
function playAudio(w) {
if (typeof audioBuffer === 'undefined' || audioBuffer == null || currentWord != w) {
audioBuffer = null
currentWord = ""
console.log("reqest load of different word", w)
loadAudio(w)
} else {
playAudioBuffer(w, audioBuffer)
}
}
function playAudioBuffer(w, buffer) {
audioBuffer = buffer
currentWord = w
var source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.start();
console.log('playing', currentWord);
}
</script>
Play ἦν
<br>
Play ὥστε
<br>
Play οὐρανός
<br>
Play υἱός
<br>
Play εἷς
<br>
Am I hitting some kind of weird situation where Safari is blocking sound because the clicking of the link is disconnected from the loading/playing of the audio by a fraction of a second?
It turns out the way to 'unlock' audio is to play a silent/hidden sound when the user first interacts with the page to make sure future requests for audio will function correctly.
https://paulbakaus.com/tutorials/html5/web-audio-on-ios/
Here is what I used:
window.addEventListener('touchstart', function() {
// create empty buffer
var buffer = myContext.createBuffer(1, 1, 22050);
var source = myContext.createBufferSource();
source.buffer = buffer;
// connect to output (your speakers)
source.connect(myContext.destination);
// play the file
source.noteOn(0);
}, false);
I realize I am probably not the first person to have asked this on SO, however the search keywords (safari play first time vs second time) don't turn up anything on SO, so I am inclined to answer and leave this up. If the community things deleting my noob question would be better then I'm happy to do that.

Web Audio API Unable to decode audio data

Got following error while trying to load mp3 audio file in Google Chrome: Uncaught (in promise) DOMException: Unable to decode audio data. Here's my code:
loadSong(url) {
var AudioCtx = new AudioContext();
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = "arraybuffer";
request.onload = function () {
console.log(request.response);
AudioCtx.decodeAudioData(
request.response,
function (buffer) {
var currentSong = AudioCtx.createBufferSource();
currentSong.buffer = buffer;
currentSong.connect(AudioCtx.destination);
currentSong.start(0);
},
function (e) {
alert("error: " + e.err);
}
);
};
request.send();
}
Here's console.log of request.response:
ArrayBuffer(153) {}
byteLength:0
__proto__:ArrayBuffer
byteLength:0
constructor:ƒ ArrayBuffer()
slice:ƒ slice()
Symbol(Symbol.toStringTag):"ArrayBuffer"
get byteLength:ƒ byteLength()
__proto__:Object
In Firefox I got error The buffer passed to decodeAudioData contains an unknown type of content.. Same error with OGG files

XMLHttpRequest detecting 404 (Not Found)

If the URL is correct (file.dat exists), this works great (the file length matches). If it is wrong I will see a very small file length and I will not see the xhr.onerror.
How can I detect that the URL was incorrect?
var xhr = new XMLHttpRequest()
xhr.responseType = "blob"
xhr.onload = ()=> {
var reader = new FileReader()
reader.onload = evt => {
var contents = new Buffer(evt.target.result, 'binary')
console.log('file len',contents.length)
}
reader.readAsBinaryString(xhr.response)
}
xhr.addEventListener("error", () => { console.error('xhr.onerror',e) })
xhr.open("GET", "file.dat")
xhr.send()
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
I do see a stacktrace in the console pointing to xhr.send()
GET http://localhost:8080/file.dat 404 (Not Found)
A try catch around both open and send does not catch any exceptions.
Files are served by WebpackDevServer (I hope that should not matter though).
You can check the status of the response object.
// Not using arrow function because I don't want the lexical `this`
xhr.onload = function() {
if (this.status === 404) {
// not found, add some error handling
return;
}
var reader = new FileReader()
reader.onload = evt => {
var contents = new Buffer(evt.target.result, 'binary')
console.log('file len',contents.length)
}
reader.readAsBinaryString(xhr.response)
}
Credit to https://developer.appcelerator.com/question/129410/xhr-request-cant-check-for-error-for-404-page-or-other-errors
Using https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-status:
XmlHttpRequest objects (you have one in the variable xhr) have a read-only property status that you can use to get the status text once it's loaded.

Web Audio Api - Download edited MP3

I'm currently editing my mp3 file with multiple effects like so
var mainVerse = document.getElementById('audio1');
var s = source;
source.disconnect(audioCtx.destination);
for (var i in filters1) {
s.connect(filters1[i]);
s = filters1[i];
}
s.connect(audioCtx.destination);
The mp3 plays accordingly on the web with the filters on it. Is it possible to create and download a new mp3 file with these new effects, using web audio api or any writing to mp3 container javascript library ? If not whats the best to solve this on the web ?
UPDATE - Using OfflineAudioContext
Using the sample code from https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext/oncomplete
I've tried using the offline node like so;
var audioCtx = new AudioContext();
var offlineCtx = new OfflineAudioContext(2,44100*40,44100);
osource = offlineCtx.createBufferSource();
function getData() {
request = new XMLHttpRequest();
request.open('GET', 'Song1.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function() {
var audioData = request.response;
audioCtx.decodeAudioData(audioData, function(buffer) {
myBuffer = buffer;
osource.buffer = myBuffer;
osource.connect(offlineCtx.destination);
osource.start();
//source.loop = true;
offlineCtx.startRendering().then(function(renderedBuffer) {
console.log('Rendering completed successfully');
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var song = audioCtx.createBufferSource();
song.buffer = renderedBuffer;
song.connect(audioCtx.destination);
song.start();
rec = new Recorder(song, {
workerPath: 'Recorderjs/recorderWorker.js'
});
rec.exportWAV(function(e){
rec.clear();
Recorder.forceDownload(e, "filename.wav");
});
}).catch(function(err) {
console.log('Rendering failed: ' + err);
// Note: The promise should reject when startRendering is called a second time on an OfflineAudioContext
});
});
}
request.send();
}
// Run getData to start the process off
getData();
Still getting the recorder to download an empty file, I'm using the song source as the source for the recorder. The song plays and everything with his code but recorder doesn't download it
Use https://github.com/mattdiamond/Recorderjs to record a .wav file. Then use https://github.com/akrennmair/libmp3lame-js to encode it to .mp3.
There's a nifty guide here, if you need a hand: http://audior.ec/blog/recording-mp3-using-only-html5-and-javascript-recordmp3-js/
UPDATE
Try moving
rec = new Recorder(song, {
workerPath: 'Recorderjs/recorderWorker.js'
});
so that it is located above the call to start rendering, and connect it to osource instead, like so:
rec = new Recorder(osource, {
workerPath: 'Recorderjs/recorderWorker.js'
});
osource.connect(offlineCtx.destination);
osource.start();
offlineCtx.startRendering().then(function(renderedBuffer) {
.....

Categories

Resources