Sound effects in JavaScript / HTML5 - javascript

I'm using HTML5 to program games; the obstacle I've run into now is how to play sound effects.
The specific requirements are few in number:
Play and mix multiple sounds,
Play the same sample multiple times, possibly overlapping playbacks,
Interrupt playback of a sample at any point,
Preferably play WAV files containing (low quality) raw PCM, but I can convert these, of course.
My first approach was to use the HTML5 <audio> element and define all sound effects in my page. Firefox plays the WAV files just peachy, but calling #play multiple times doesn't really play the sample multiple times. From my understanding of the HTML5 spec, the <audio> element also tracks playback state, so that explains why.
My immediate thought was to clone the audio elements, so I created the following tiny JavaScript library to do that for me (depends on jQuery):
var Snd = {
init: function() {
$("audio").each(function() {
var src = this.getAttribute('src');
if (src.substring(0, 4) !== "snd/") { return; }
// Cut out the basename (strip directory and extension)
var name = src.substring(4, src.length - 4);
// Create the helper function, which clones the audio object and plays it
var Constructor = function() {};
Constructor.prototype = this;
Snd[name] = function() {
var clone = new Constructor();
clone.play();
// Return the cloned element, so the caller can interrupt the sound effect
return clone;
};
});
}
};
So now I can do Snd.boom(); from the Firebug console and play snd/boom.wav, but I still can't play the same sample multiple times. It seems that the <audio> element is really more of a streaming feature rather than something to play sound effects with.
Is there a clever way to make this happen that I'm missing, preferably using only HTML5 and JavaScript?
I should also mention that, my test environment is Firefox 3.5 on Ubuntu 9.10. The other browsers I've tried - Opera, Midori, Chromium, Epiphany - produced varying results. Some don't play anything, and some throw exceptions.

HTML5 Audio objects
You don't need to bother with <audio> elements. HTML 5 lets you access Audio objects directly:
var snd = new Audio("file.wav"); // buffers automatically when created
snd.play();
There's no support for mixing in current version of the spec.
To play same sound multiple times, create multiple instances of the Audio object. You could also set snd.currentTime=0 on the object after it finishes playing.
Since the JS constructor doesn't support fallback <source> elements, you should use
(new Audio()).canPlayType("audio/ogg; codecs=vorbis")
to test whether the browser supports Ogg Vorbis.
If you're writing a game or a music app (more than just a player), you'll want to use more advanced Web Audio API, which is now supported by most browsers.

Web Audio API
Edit: As of December 2021, Web Audio API is essentially supported in Chrome, Firefox, Safari and all the other major browsers (excluding IE, of course).
As of July 2012, the Web Audio API is now supported in Chrome, and at least partly supported in Firefox, and is slated to be added to IOS as of version 6.
Although the Audio element is robust enough to be used programmatically for basic tasks, it was never meant to provide full audio support for games and other complex applications. It was designed to allow a single piece of media to be embedded in a page, similar to an img tag. There are a lot of issues with trying to use the it for games:
Timing slips are common with Audio elements
You need an Audio element for each instance of a sound
Load events aren't totally reliable, yet
No common volume controls, no fading, no filters/effects
Here are some good resources to get started with the Web Audio API:
MDN documentaion
Getting Started With WebAudio article
The FieldRunners WebAudio Case Study is also a good read

howler.js
For game authoring, one of the best solutions is to use a library which solves the many problems we face when writing code for the web, such as howler.js. howler.js abstracts the great (but low-level) Web Audio API into an easy to use framework. It will attempt to fall back to HTML5 Audio Element if Web Audio API is unavailable.
var sound = new Howl({
urls: ['sound.mp3', 'sound.ogg']
}).play();
// it also provides calls for spatial/3d audio effects (most browsers)
sound.pos3d(0.1,0.3,0.5);
wad.js
Another great library is wad.js, which is especially useful for producing synth audio, such as music and effects. For example:
var saw = new Wad({source : 'sawtooth'})
saw.play({
volume : 0.8,
wait : 0, // Time in seconds between calling play() and actually triggering the note.
loop : false, // This overrides the value for loop on the constructor, if it was set.
pitch : 'A4', // A4 is 440 hertz.
label : 'A', // A label that identifies this note.
env : {hold : 9001},
panning : [1, -1, 10],
filter : {frequency : 900},
delay : {delayTime : .8}
})
Sound for Games
Another library similar to Wad.js is "Sound for Games", it has more focus on effects production, while providing a similar set of functionality through a relatively distinct (and perhaps more concise feeling) API:
function shootSound() {
soundEffect(
1046.5, //frequency
0, //attack
0.3, //decay
"sawtooth", //waveform
1, //Volume
-0.8, //pan
0, //wait before playing
1200, //pitch bend amount
false, //reverse bend
0, //random pitch range
25, //dissonance
[0.2, 0.2, 2000], //echo array: [delay, feedback, filter]
undefined //reverb array: [duration, decay, reverse?]
);
}
Summary
Each of these libraries are worth a look, whether you need to play back a single sound file, or perhaps create your own html-based music editor, effects generator, or video game.

You may also want to use this to detect HTML 5 audio in some cases:
http://diveintohtml5.ep.io/everything.html
HTML 5 JS Detect function
function supportsAudio()
{
var a = document.createElement('audio');
return !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
}

Here's one method for making it possible to play even same sound simultaneously. Combine with preloader, and you're all set. This works with Firefox 17.0.1 at least, haven't tested it with anything else yet.
// collection of sounds that are playing
var playing={};
// collection of sounds
var sounds={step:"knock.ogg",throw:"swing.ogg"};
// function that is used to play sounds
function player(x)
{
var a,b;
b=new Date();
a=x+b.getTime();
playing[a]=new Audio(sounds[x]);
// with this we prevent playing-object from becoming a memory-monster:
playing[a].onended=function(){delete playing[a]};
playing[a].play();
}
Bind this to a keyboard key, and enjoy:
player("step");

To play the same sample multiple times, wouldn't it be possible to do something like this:
e.pause(); // Perhaps optional
e.currentTime = 0;
e.play();
(e is the audio element)
Perhaps I completely misunderstood your problem, do you want the sound effect to play multiple times at the same time? Then this is completely wrong.

Sounds like what you want is multi-channel sounds. Let's suppose you have 4 channels (like on really old 16-bit games), I haven't got round to playing with the HTML5 audio feature yet, but don't you just need 4 <audio> elements, and cycle which is used to play the next sound effect? Have you tried that? What happens? If it works: To play more sounds simultaneously, just add more <audio> elements.
I have done this before without the HTML5 <audio> element, using a little Flash object from http://flash-mp3-player.net/ - I wrote a music quiz (http://webdeavour.appspot.com/) and used it to play clips of music when the user clicked the button for the question. Initially I had one player per question, and it was possible to play them over the top of each other, so I changed it so there was only one player, which I pointed at different music clips.

Have a look at the jai (-> mirror) (javascript audio interface) site. From looking at their source, they appear to be calling play() repeatedly, and they mention that their library might be appropriate for use in HTML5-based games.
You can fire multiple audio events
simultaneously, which could be used
for creating Javascript games, or
having a voice speaking over some
background music

Here's an idea. Load all of your audio for a certain class of sounds into a single individual audio element where the src data is all of your samples in a contiguous audio file (probably want some silence between so you can catch and cut the samples with a timeout with less risk of bleeding to the next sample). Then, seek to the sample and play it when needed.
If you need more than one of these to play you can create an additional audio element with the same src so that it is cached. Now, you effectively have multiple "tracks". You can utilize groups of tracks with your favorite resource allocation scheme like Round Robin etc.
You could also specify other options like queuing sounds into a track to play when that resource becomes available or cutting a currently playing sample.

http://robert.ocallahan.org/2011/11/latency-of-html5-sounds.html
http://people.mozilla.org/~roc/audio-latency-repeating.html
Works OK in Firefox and Chrome for me.
To stop a sound that you started, do
var sound = document.getElementById("shot").cloneNode(true);
sound.play();
and later
sound.pause();

I would recommend using SoundJS, a library I've help develop. It allows you to write a single code base that works everywhere, with SoundJS picking web audio, html audio, or flash audio as appropriate.
It will allow you to do all of the thing you want:
Play and mix multiple sounds,
Play the same sample multiple times, possibly overlapping playbacks
Interrupt playback of a sample at any point
play WAV files containing (depending on browser support)
Hope that helps.

It's not possible to do multi-shot playing with a single <audio> element. You need to use multiple elements for this.

I ran into this while programming a musicbox card generator. Started with different libraries but everytime there was a glitch somehow. The lag on normal audio implementation was bad, no multiple plays... eventually ended up using lowlag library + soundmanager:
http://lowlag.alienbill.com/
and
http://www.schillmania.com/projects/soundmanager2/
You can check out the implementation here:
http://musicbox.grit.it/
I generated wav + ogg files for multi browser plays. This musicbox player works responsive on ipad, iphone, Nexus, mac, pc,... works for me.

var AudioContextFunc = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContextFunc();
var player=new WebAudioFontPlayer();
var instrumVox,instrumApplause;
var drumClap,drumLowTom,drumHighTom,drumSnare,drumKick,drumCrash;
loadDrum(21,function(s){drumClap=s;});
loadDrum(30,function(s){drumLowTom=s;});
loadDrum(50,function(s){drumHighTom=s;});
loadDrum(15,function(s){drumSnare=s;});
loadDrum(5,function(s){drumKick=s;});
loadDrum(70,function(s){drumCrash=s;});
loadInstrument(594,function(s){instrumVox=s;});
loadInstrument(1367,function(s){instrumApplause=s;});
function loadDrum(n,callback){
var info=player.loader.drumInfo(n);
player.loader.startLoad(audioContext, info.url, info.variable);
player.loader.waitLoad(function () {callback(window[info.variable])});
}
function loadInstrument(n,callback){
var info=player.loader.instrumentInfo(n);
player.loader.startLoad(audioContext, info.url, info.variable);
player.loader.waitLoad(function () {callback(window[info.variable])});
}
function uhoh(){
var when=audioContext.currentTime;
var b=0.1;
player.queueWaveTable(audioContext, audioContext.destination, instrumVox, when+b*0, 60, b*1);
player.queueWaveTable(audioContext, audioContext.destination, instrumVox, when+b*3, 56, b*4);
}
function applause(){
player.queueWaveTable(audioContext, audioContext.destination, instrumApplause, audioContext.currentTime, 54, 3);
}
function badumtss(){
var when=audioContext.currentTime;
var b=0.11;
player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*0, drumSnare.zones[0].keyRangeLow, 3.5);
player.queueWaveTable(audioContext, audioContext.destination, drumLowTom, when+b*0, drumLowTom.zones[0].keyRangeLow, 3.5);
player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*1, drumSnare.zones[0].keyRangeLow, 3.5);
player.queueWaveTable(audioContext, audioContext.destination, drumHighTom, when+b*1, drumHighTom.zones[0].keyRangeLow, 3.5);
player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*3, drumSnare.zones[0].keyRangeLow, 3.5);
player.queueWaveTable(audioContext, audioContext.destination, drumKick, when+b*3, drumKick.zones[0].keyRangeLow, 3.5);
player.queueWaveTable(audioContext, audioContext.destination, drumCrash, when+b*3, drumCrash.zones[0].keyRangeLow, 3.5);
}
<script src='https://surikov.github.io/webaudiofont/npm/dist/WebAudioFontPlayer.js'></script>
<button onclick='badumtss();'>badumtss</button>
<button onclick='uhoh();'>uhoh</button>
<button onclick='applause();'>applause</button>
<br/><a href='https://github.com/surikov/webaudiofont'>More sounds</a>

I know this is a total hack but thought I should add this sample open source audio library I put on github awhile ago...
https://github.com/run-time/jThump
After clicking the link below, type on the home row keys to play a blues riff (also type multiple keys at the same time etc.)
Sample using jThump library >> http://davealger.com/apps/jthump/
It basically works by making invisible <iframe> elements that load a page that plays a sound onReady().
This is certainly not ideal but you could +1 this solution based on creativity alone (and the fact that it is open source and works in any browser that I've tried it on) I hope this gives someone else searching some ideas at least.
:)

You can always try AudioContext it has limited support but it's a part of the web audio api working draft. It might be worth it if you are planing to release something in the future. And if you are only programing for chrome and Firefox you're golden.

Web Audio API is right tool for this job. There is little bit of work involved in loading sounds files and playing it. Luckily there are plenty of libraries out there that simplify the job. Being interested in sounds I also created a library called musquito you can check out that as well.
Currently it supports only fading sound effect and I'm working on other things like 3D spatialization.

The selected answer will work in everything except IE. I wrote a tutorial on how to make it work cross browser. Here is the function I wrote:
function playSomeSounds(soundPath) {
var trident = !!navigator.userAgent.match(/Trident\/7.0/);
var net = !!navigator.userAgent.match(/.NET4.0E/);
var IE11 = trident && net
var IEold = (navigator.userAgent.match(/MSIE/i) ? true : false);
if (IE11 || IEold) {
document.all.sound.src = soundPath;
} else {
var snd = new Audio(soundPath); // buffers automatically when created
snd.play();
}
}
You also need to add the following tag to the html page:
<bgsound id="sound">
Finally you can call the function and simply pass through the path here:
playSomeSounds("sounds/welcome.wav");

Related

Is there a way to get OscillatorNode to produce a sound on an iOS device?

I'm working on a music web app that has a piano keyboard. When a user presses a piano key, I'm using OscillatorNode to play a brief tone corresponding to the key:
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playNote(note) {
let oscillator;
let freq = notes[note];
console.debug(note + " (" + freq + " Hz)");
oscillator = audioCtx.createOscillator(); // create Oscillator node
oscillator.type = wavetypeEl.val(); // triangle wave by default
oscillator.frequency.setValueAtTime(freq, audioCtx.currentTime); // freq = value in hertz
oscillator.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + 0.5);
}
$('#keyboard button').on('click', (e) => {
playNote(e.target.dataset.note);
});
This works on all the desktop and Android browsers I've tried, but iOS stubbornly refuses to play any sound. I see that I need a user interaction to "unlock" an AudioContext on iOS, but I would have thought calling playNote() from my click function would have done the trick.
According to Apple, I should be able to use noteOn() on my oscillator object, instead of oscillator.start() the way I've got it in my example. But that doesn't seem to be a valid method.
I must be missing something simple here. Anybody know?
If everything seems to be working fine it could be that the device itself is on mute. For some reason (or for no reason at all) Safari doesn't play any sound coming from the Web Audio API when the device is muted. But it plays everything else.
There are some hacky ways to circumvent this bug which basically work by playing something with an audio element first before using the Web Audio API.
unmute-ios-audio is for example a library which implements the hack mentioned above.
I have an iPhone XS Max and the demo works on it producing sound as you have it right now... I've also read that for iOS the element needs both onClick handler and a style of {cursor: pointer} set to work properly.(as of a few years ago) seems like it's working though.

preloadjs + howler audio doesn't play

I was trying to make my first website.
I'm working on local, so if you need an online website for debug it, I can upload it.
I want to create a preload section on the first visit where show a preload bar/% progress and load all elements before show the page.
I'm doing the audio part and the preloader of the website.
To accomplish this part, I'm using howler.js for audio management and preloadjs.js for the preloader part.
The problem that I can not solve is to start the mp3 at the complete function of the mp3 load.
Below is the code I used.
This is the music part with howler.
var baseurl = document.location.origin;
var soundfolder = baseurl+'/alink/wp-content/themes/alink/media/sounds/';
//SOUNDS EFFECTS
var bgmusic = [soundfolder+'background.mp3', soundfolder+'background.ogg'];
//Music background
var musicbg = new Howl({
src: [bgmusic[0], bgmusic[1]],
loop: true,
volume: 0.5,
preload: false,
});
This is the preloader part with prealodjs.
//PRELOADER
var manifest;
var preload;
function setupManifest() {
manifest = [{
src: baseurl+"/alink/wp-content/themes/alink/media/sounds/background.mp3",
id: "mp3file"
}
];
}
function startPreload() {
preload = new createjs.LoadQueue(true);
preload.on("fileload", handleFileLoad);
preload.on("progress", handleFileProgress);
preload.on("complete", loadComplete);
preload.on("error", loadError);
preload.loadManifest(manifest);
}
function handleFileLoad(event) {
console.log("A file has loaded of type: " + event.item.type);
}
function loadError(evt) {
console.log("Error!",evt.text);
}
function handleFileProgress(event) {
}
function loadComplete(event) {
console.log("Finished Loading Assets");
musicbg.play();
}
setupManifest();
startPreload();
Following some tutorials and js library guidelines, in the howler call I entered the "preload: false" parameter.
Without the preload part and without the "preload: false" parameter, the musical part works perfectly.
By inserting the parameter and the code part of the preloader, when the loadComplete() function is called, the music does not start. (in the console the function is called correctly.
I really can not figure out where the problem is, so I ask you what I'm doing wrong. I think there is a missing part where the file loaded from preloadjs functions is not related to howler call and it can't find the mp3 file in the cache.
Thank you very much for your help.
if you load audio using PreloadJS without SoundJS "installed" to assist with preloading, the audio can only be loaded as HTMLAudio, which is pretty limited. I am fairly certain Howler uses the WebAudio context, so it would just re-load the audio when it needs it.
PreloadJS is really only designed to load WebAudio sounds to be played with SoundJS. In fact, it actually uses a lot of shared code to download and prepare audio for playback. This is not necessarily by design (to prevent usage of other libraries), but WebAudio uses a shared audio context when preloading audio buffers, and PreloadJS knows how to share that context with SoundJS. Howler is probably similar, where it preloads using an audio context it knows how to work with.
As a maintainer of the CreateJS libraries I am certainly biased, but if you want to preload audio with PreloadJS, then SoundJS is a better option than Howler.
var queue = new createjs.LoadQueue();
queue.installPlugin(createjs.Sound); // Tell PreloadJS to use SoundJS to load audio
// ... other queue stuff
// After loading is done:
createjs.Sound.play("soundId");
Hope that helps.

JavaScript play() function does not working in Chrome

I created an audio object and want to play it when user leave the window. So, my code is here:
$('body').on('mouseleave', function(){
var audio = new Audio( 'quite-impressed.mp3' )
audio.play()
})
It works well in Firefox. It also works in Chrome if I click in the page and then leave mouse outside of the body. But, when I leave the mouse without clicking in the page an error showed in the console and the audio does not play
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first. https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
But, in this example site it works fine without interacting with the page. How can I make it possible? Thanks in advance.
It seems they use an AudioContext to play that sound.
Chrome did came back a few months ago about blocking the AudioContext API, because a lot of fair uses were not prepared for such restriction and thus got broken by it.
But M71, which will get released in December 2018 will reenable that restriction, you can read about it here: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
// this will work in Chrome < 70 but not af
onmouseout = e => {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
osc.connect(ctx.destination);
osc.start(0);
osc.stop(1);
}
Outsourced live example, since Stacksnippets are always granted the user gesture anyway: https://jsfiddle.net/zy3ev8ka/
Try this:
window.audio = new Audio( 'quite-impressed.mp3' )
$('body').on('mouseleave', function(){
audio.play()
})
This worked for me on Chrome 77:
On address bar: chrome://settings/content/sound
Turn off "Allow sites to play sound (recommended)"
Turn it on again
if your js play function is not running and if your code is correct then this may help,you have to allow your browser to get that sound file
just goto settings =>privacy and security => site-settings =>sound.
and then add your local url at add to play section..

PreloadJS cannot find audio

I'm working on hobby project. I'm using createjs. But, of course, I have a problem. Since I update the code to use Preloadjs, the browser can no longer find the audio files. Here is my loading code:
function load (canvasToShowProgress) {
canvas = canvasToShowProgress;
loadingStage = new createjs.Stage();
loadQueue = new createjs.LoadQueue(true);
progessText = new createjs.Text("Loading... 0%");
loadingStage.addChild(loadingStage);
loadingStage.update();
//start loading
loadQueue.installPlugin(createjs.Sound);
loadQueue.setMaxConnections(4); // Allow 4 concurrent loads
loadQueue.loadManifest("configuration/GameAssets.json");
loadQueue.on("progress", handleProgress);
loadQueue.on("complete",handleComplete);
}
My manifest GameAssets.json looks like this:
{"manifest":
[ {"src":"images/game/street.png", "id":"street"},
{"id":"Default", "src":"sounds/game/background.ogg"},
{"id":"Psychic", "src":"sounds/game/ps.ogg"},
{"id":"Nitro", "src":"sounds/game/ntr.ogg"}
]
}
By the way, the image is loaded perfectly. In the music player class, I call the audio by simply doing soundInstance = createjs.Sound.play(soundIds[currentPlayIndex]);. (The soundIds is temporary a hardcoded array with the ids. Where is my mistake?
First, I just want to confirm you are using the latest 0.6.0 releases of both SoundJS and PreloadJS (they need to be used together).
Currently only Firefox and Chrome support Ogg, so if you are using another browser you would need to provide and alternate format (mp3 is currently the most broadly supported) and set it up using SoundJS.alternateExtensions.
If you are still having loading errors, you'd likely need to dig into the errors a little more to determine the cause.
Hope that helps.
Your soundIds array declare like this
var soundIds=["Default","Psychic","Nitro"]
then call it like that
createjs.Sound.play(soundIds[0]);

How to avoid playing back the sound that is being recorded

I'm talking about feedback - when you make a simple javascript application that opens a stream from the user and reads the frequency analysis (or whatever is it) it thorws all received data back to the headphones in both Google Chrome and Opera. Firefox is silent most of the time and randomly creates a huge mess with unstable feedback - it also closes the stream after few seconds. Generally the thing doesn't work in Firefox yet.
I created a fiddle. If your browser doesn't support it you'll just get error in the console I assume.
The critical part of the code is the function that is called when user accepts the request for the microphone access:
//Not sure why do I do this
var inputPoint = context.createGain();
// Create an AudioNode from the stream.
var source = context.createMediaStreamSource(stream);
source.connect(inputPoint);
//Analyser - this converts raw data into spectral analysis
window.analyser = context.createAnalyser();
//Mores stuff I know nothing about
analyser.fftSize = 2048;
//Sounds much like connecting nodes in MatLab, doesn't it?
inputPoint.connect(analyser);
analyser.connect(context.destination);
///THIS should probably make the sound silent (gain:0) but it doesn't
var zeroGain = context.createGain();
zeroGain.gain.value = 0.0;
//More connecting... are you already lost which node is which? Because I am.
inputPoint.connect(zeroGain);
zeroGain.connect(context.destination);
Zero gain idea is not mine, I have stolen it from simple sound recorder demo. But what works for them doesn't work for me.
The demo has also no problems in Firefox, like I do.
in function mediaGranted(stream) {...
comment out
..
Fiddle line #46: //analyser.connect(context.destination);
..
more info https://mdn.mozillademos.org/files/5081/WebAudioBasics.png
nice demo: http://mdn.github.io/voice-change-o-matic/

Categories

Resources