JS Audio API - Oscillator inside function doesn't play sound - javascript

Using Opera 44.0, I was fiddling around with the Audio API and tried a simple example:
var ac = new AudioContext();
var osc = ac.createOscillator();
osc.connect(ac.destination);
osc.start();
osc.stop(2);
It works as expected, the sound is played for 2 seconds and then it stops.
Then I tried to step up a little, playing a sound when a button is clicked:
function play(){
var osc = ac.createOscillator();
osc.connect(ac.destination);
osc.start();
osc.stop(2);
}
var ac = new AudioContext();
var playBtn = document.querySelector("#play");
playBtn.addEventListener("click", play);
It doesn't work. The function is called when I click the button (I checked using console.log() inside the function), but no sound is played. I tried refreshing the page, restarting the browser.. nothing.
After some research I found out that the oscillator is thrown away when stop() is called, so I have to create a new oscillator every time. Pretty much all the examples I found revolve around that concept, which is why I am creating it inside the function. But with no errors whatsoever, I can't figure out why it's not working.
So, where is the problem here?

Digging through the documentation, I managed to solve the problem.
Originally, I assumed that the argument passed to osc.stop(2) was the playing time in seconds, something like "play for 2 seconds, then stop".
That's not correct though: the argument is "...the audio context time when the oscillator should stop".
By logging ac.currentTime inside the play() function, the value returned when I clicked on the button was ~5. So, what happened is that by passing 2 to osc.stop() i'm telling the osc to stop when the context time is 2, which is already passed!
The solution is simple:
function play(){
var osc = ac.createOscillator();
osc.connect(ac.destination);
osc.start();
//take into account the current time of the context
osc.stop(ac.currentTime + 2);
}
Words of wisdom now echo into my brain... R....T.....F...M...

Related

I'm having trouble playing audio in javascript at the same time

So far, I have tried using this function to create a new audio element every time it is called:
function createAudio(src, type){
var sound = new Audio();
var source = document.createElement('source');
source.type = type;
source.src = src;
sound.appendChild('source');
sound.play();
};
That way, if you want to play a single audio file called, for example, "gunshot.mp3", but multiple times you can use this line of code:
createSound('gunshot.mp3','audio/mpeg');
This works ok, but what will happen is that when the sound is loaded a bunch of times, it will just stop running entirely. Every time that you reload the game its like rolling dice to see which sounds will or won't run, and even when it does run its patchy and inconsistent.
I'm guessing that it has something do with the html page being overloaded with new elements, so is there maybe a way to clear the elements every once and a while, like clearing a particle array so it doesn't get too long? Or is it just that my function is not correct in the first place?
Thanks!

Web Audio API demo doesn't work on iOS

I'm currently working on adapting this web audio API demo for a project that I am working on, but there is no sound when I test on an iPhone. It works fine on the iPad.
I've searched for solutions and found this thread on StackOverflow with the following snippet of one of the answers:
Safari on iOS 6 effectively starts with the Web Audio API muted. It
will not unmute until you attempt to play a sound in a user input
event (create a buffer source, connect it to destination, and call
noteOn()). After this, it unmutes and audio plays unrestricted and as
it ought to. This is an undocumented aspect of how the Web Audio API
works on iOS 6 (Apple's doc is here, hopefully they update it with a
mention of this soon!)
The user input event should be the onclick event on the play button but changing to use noteOn() instead of start() still doesn't fix it.
Update: I've also tried binding the play button with the touchend event but to no avail.
Here is the function that uses noteOn():
function playNote(buffer, pan, x, y, z, sendGain, mainGain, playbackRate, noteTime) {
// Create the note
var voice = context.createBufferSource();
voice.buffer = buffer;
voice.playbackRate.value = playbackRate;
// Optionally, connect to a panner
var finalNode;
if (pan) {
var panner = context.createPanner();
panner.panningModel = "HRTF";
panner.setPosition(x, y, z);
voice.connect(panner);
finalNode = panner;
} else {
finalNode = voice;
}
// Connect to dry mix
var dryGainNode = context.createGain();
dryGainNode.gain.value = mainGain * effectDryMix;
finalNode.connect(dryGainNode);
dryGainNode.connect(masterGainNode);
// Connect to wet mix
var wetGainNode = context.createGain();
wetGainNode.gain.value = sendGain;
finalNode.connect(wetGainNode);
wetGainNode.connect(convolver);
if (iOS) {
voice.noteOn(noteTime);
}
else {
voice.start(noteTime);
}
}
Any suggestions would be greatly appreciated. Thanks.
I feel really stupid. Apparently, if you have your iPhone on vibrate mode, the sound doesn't play.
The start() method should work fine without the if else statements on iOS as long as you call the function with a user interaction event. Also flip the order you pass y and z to the panner cause z is second for some strange reason.
Here's a working example, change stuff in it to fit what you need, most isn't need and I've got others somewhere that use the dom to add event listeners
<script>
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.type = 'sine';
oscillator.frequency.value = 440;
gainNode.gain.value = 1;
</script>
<button onclick="oscillator.start();">play</button>
My own experience has been that sometimes the Web Audio API works on iPhones, sometimes it doesn't. Here is a page that worked 5 minutes ago on my iPhone 6s; 1 minute ago it didn't work; now it does again!
http://www.stephenandrewtaylor.net/dna-sonification/
Here is another one that works intermittently; it worked 2 minutes ago and now it doesn't (the animations work, there is just no audio).
http://www.stephenandrewtaylor.net/lie-sonification/
It might have to do with how many tabs are open in Safari; you could try closing some of your open tabs (right now I have 5 tabs, including the lie-sonificaton page which worked 2 minutes ago but now doesn't). I am also a novice programmer and I'm sure there are much better ways I could be writing the code.

Web Audio API and real current time when playing an audio file

I am having problems when I want to know the current time of a file playing using the Web Audio API. My code plays the file nicely and the current time returned by the getCurrentTime() function is accurate enough when it comes to short files which load fast.
But when I try to load big files, sometimes the current time returned by the getCurrentTime() function is accurate and sometimes not. Sometimes, after waiting for example for 20 seconds to hear the file playing, when it starts playing it says that the current time is about 20 seconds (which is not true because it is just playing the beginning of the file). It happens with any audio format (OGG, MP3, WAV...) but only sometimes.
I am using a slow system (Asus EEE PC 901 with an Intel Atom 1.60 Ghz and 2 GB RAM with Windows XP Home Edition and SP3) and Firefox 41.0.1.
I am not sure, but it seems that the source.start() method starts playing the sound way too late, so the line after calling that method, where I set the value for the startTime variable, is not the real starting time.
Here is the code (simplified):
var context, buffer, startTime, source;
var stopped = true;
function load(file, startAt)
{
//Here creates the AudioContext and loads the file through XHR (AJAX) and gets the buffer. All works fine.
//When it gots the buffer through XHR (AJAX) and all is fine, it calls play(startAt) function immediately.
//Note: normally, startAt is 0.
}
function play(startAt)
{
source = context.createBufferSource(); //Context created before.
source.buffer = buffer; //Buffer got before from XHR (AJAX).
//Creates a gain node to be able to set the volume later:
var gainNode = context.createGain();
source.connect(gainNode);
gainNode.connect(context.destination);
//Plays the sound:
source.loop = false;
source.start(startAt, 0, buffer.duration - 3); //I don't want the last 3 seconds.
//Stores the start time (useful for pause/resume):
startTime = context.currentTime - startAt; //Here I store the startTime but maybe the file has still not begun to play (should it be just startTime = context.currentTime?).
stopped = false;
}
function stop()
{
source.stop(0);
stopped = true;
}
function getCurrentTime()
{
return (stopped) ? 0 : context.currentTime - startTime;
}
How can I detect when exactly the source.start() method starts playing the file? So I can set the startTime variable value just at that moment, and never before.
Thank you very much in advance. I would really appreciate any kind of help.
From MDN (https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/start), about the first parameter of the start() function:
when (Optional)
The time, in seconds, at which the sound should begin to play, in the same time coordinate system used by the AudioContext. If when is less than (AudioContext.currentTime, or if it's 0, the sound begins to play at once. The default value is 0.
There is no evident issue with your code (although there is no example a call to play()): if you call play(0) or play(context.currentTime + someDelayInSeconds), start() should behave as expected. Unfortunately here the issue is that AudioBufferSource is not meant for big files. Again from the MDN doc of AudioBuffer (https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer):
Objects of these types are designed to hold small audio snippets, typically less than 45 s.
I suspect that for big something doesn't work very well with the "sound begins play at once" assumption (I also experienced it, although 20 seconds seems way too much...). Unfortunately there is no way to get the exact start time of AudioBufferSource in WebAudio yet.
If you don't have any real reason to load this big file with AudioBufferSource, I suggest you use a MediaElementSourceNode (https://developer.mozilla.org/en-US/docs/Web/API/MediaElementAudioSourceNode): as you can see from the example on the linked doc, it allows you to plug a simple HTML5 Audio element into the AudioContext. You then can have all usual control over the element itself, i.e. you also have access to the audioElement.currentTime property, which tells you the current playout time (in this case of the file itself, which is what you need). Additionally, you don't have to handle loading of the file in memory and could start playing as soon as some data is available.
context.currentTime starts counting the second you create the context object. That means if it takes 20 seconds for your audio to load, context.currentTime == 20.
To account for this delay, you can set a simple timer from the time that you create the context to the time that audio loading completes.
var context; //Create your context here
var audioLoadStart = new Date();
//Do audio load
var audioLoadOffset = (new Date() - audioLoadStart) / 1000;
currentTime = context.currentTime - audioLoadOffset - startTime;

Webaudio sound stops on Chrome for Android after about 2 minutes

I'm running into an issue with WebAudio on Chrome for Android.
I'm experiencing this on a Samsung Galaxy S3 (GT-I9300) with:
Chrome version 44.0.2403.133
Android 4.3.0
Here is the code I'm using to try and isolate the issue:
var audioContext;
if(window.AudioContext) {
audioContext = new AudioContext();
}
var startTime = Date.now();
var lastTrigger;
var gain = audioContext.createGain();
gain.gain.value = 1;
gain.connect(audioContext.destination);
var buttonTrigger = document.getElementById('trigger');
buttonTrigger.addEventListener('click', function(event) {
var oscillator = audioContext.createOscillator();
oscillator.type = "square";
oscillator.frequency.value = 100 + (Math.cos(audioContext.currentTime)*100);
oscillator.connect(gain);
oscillator.start(0);
oscillator.stop(audioContext.currentTime+0.1);
lastTrigger = Date.now();
});
var timer = document.getElementById('timer');
setInterval(function() {
if(lastTrigger) { timer.textContent = Date.now() - lastTrigger; }
}, 1000);
And here it is on jsfiddle
This simply creates an oscillator node and plays on clicking a button. On my phone, if I do not click the button for about a minute and a half or two minutes, I no longer get any sound.
There are no errors thrown.
Does anyone have any experience of this issue and a possible workaround?
This issue originally appeared in a much larger app using Phaser to play sounds from a m4a file, so this is not solely to do with the oscillator.
UPDATE
According to the Chromium bug ticket this issue has now been fixed.
After experiencing the same problem on Android. I found a better solution than playing a "dummy sound" every 30sec.
Just remember the time when you last played over your context:
var lastPlayed = new Date().getTime();
var audioSource = context.createBufferSource();
audioSource.connect( context.destination );
audioSource.buffer = sb;
audioSource.start( 0 );
The next time you play a sample/sound Just check the time passed and reset the AudioContext
if(new Date().getTime()-lastPlayed>30000){ // Time passed since last playing is greater than 30 secs
context.close();
context=new AudioContext();
}
For Android this works like charm.
I think what you're seeing is an auto-shutdown of Web Audio when there's no sound for a while. What happens if you click the button a second time, a second or so after the first? (Web Audio can take some time (order of tens of milliseconds, at least) to restart.)
The suspend()/resume() methods, and looking at the context.state, would be helpful here.
#RaymondToy's comment answers this question. There is a bug with Chrome on Android (at least for Samsung Galaxy S3/4). Webaudio stops playing sounds after a period of inactivity. Where inactivity is essentially silence.
The only work around I can find is to play some kind of sound at intervals. I have experimented with playing a sound every 30 seconds and that stopped the problem.
I also tried playing some kind of silent noise (silent audio buffer or silent part of an m4a audio file or muted sound), neither of which solved the problem.

Why isn't my audio rewinding?

I'm having a bit of trouble rewinding audio in Javascript. I basically have a countdown that beeps each second as it gets towards the end of the countdown.
I tried using;
var bip = new Audio("http://www.soundjay.com/button/beep-7.wav");
bip.play();
but it didn't beep every second which I'm guessing has something to do withit having to load a new sound every second. I then tried loading the sound externally and triggering it with the following code.
bip.pause();
bip.currentTime = 0;
console.log(bip.currentTime);
bip.play();
but this only plays the sound once then completely fails to rewind it (this is shown by the console logging a time of 0.19 seconds after the first playthrough).
Is there something I'm missing here?
In google chrome I noticed that it only works if you have the audio file in same domain. I have no problems if the audio file is in same domain. Event setting .currentTime works.
Cross-domain:
var bip = new Audio("http://www.soundjay.com/button/beep-7.wav");
bip.play();
bip.currentTime; //0.10950099676847458
bip.currentTime = 0;
bip.currentTime; //0.10950099676847458
Same-domain:
var bip = new Audio("beep-7.wav");
bip.play();
bip.currentTime; //0.10950099676847458
bip.currentTime = 0;
bip.currentTime; //0
I tried googling for a while and could find nothing in specs about this or even any discussion.
when I want rewind I simply load the audio again:
my_audio.load()
*btw, I also use a eventlistener for 'canplay' to trigger the my_audio.play(). It seems that this is necessary in android, and maybe other devices also*
To further dsdsdsdsd's answer with a bit of paint-by-numbers for the "whole shebang"
NOTE: In my app, loc1 is a dummy that refers to the song's stored location
// ... code before ...
tune = new Audio(loc1); // First, create audio event using js
// set up "song's over' event listener & action
tune.addEventListener('ended', function(){
tune.load(); //reload audio event (and reset currentTime!)
tune.play(); //play audio event for subsequent 'ended' events
},false);
tune.play(); // plays it the first time thru
// ... code after ...
I spent days and days trying to figure out what I was doing wrong, but it all works fine now... at least on the desktop browsers...
As of Chrome version 37.0.2062.120 m, the behaviour described by #Esailija has not changed.
I workaround this issue by encoding the audio data in base64 encoding and feed the data to Audio() using data: URL.
Test code:
snd = new Audio('data:audio/ogg;base64,[...base64 encoded data...]');
snd.onloadeddata = function() {
snd.currentTime = 0;
snd.play();
setTimeout(function() {
snd.currentTime = 0;
snd.play();
}, 200);
};
(I am surprised that there are no bug reports or references on this matter... or maybe my Google-fu is not strong enough.)

Categories

Resources