diff --git a/assets/18-machinae_supremacy-lord_krutors_dominion.mp3 b/assets/18-machinae_supremacy-lord_krutors_dominion.mp3 new file mode 100644 index 00000000..fb88296f Binary files /dev/null and b/assets/18-machinae_supremacy-lord_krutors_dominion.mp3 differ diff --git a/assets/18-machinae_supremacy-lord_krutors_dominion.ogg b/assets/18-machinae_supremacy-lord_krutors_dominion.ogg new file mode 100644 index 00000000..a0a0d2fe Binary files /dev/null and b/assets/18-machinae_supremacy-lord_krutors_dominion.ogg differ diff --git a/assets/GU-StealDaisy.mp3 b/assets/GU-StealDaisy.mp3 new file mode 100644 index 00000000..d9429774 Binary files /dev/null and b/assets/GU-StealDaisy.mp3 differ diff --git a/assets/GU-StealDaisy.ogg b/assets/GU-StealDaisy.ogg new file mode 100644 index 00000000..676cbff1 Binary files /dev/null and b/assets/GU-StealDaisy.ogg differ diff --git a/assets/Game-Break.mp3 b/assets/Game-Break.mp3 new file mode 100644 index 00000000..05873abf Binary files /dev/null and b/assets/Game-Break.mp3 differ diff --git a/assets/Game-Break.ogg b/assets/Game-Break.ogg new file mode 100644 index 00000000..2f6bb0b4 Binary files /dev/null and b/assets/Game-Break.ogg differ diff --git a/assets/Game-Death.mp3 b/assets/Game-Death.mp3 new file mode 100644 index 00000000..aca1a707 Binary files /dev/null and b/assets/Game-Death.mp3 differ diff --git a/assets/Game-Death.ogg b/assets/Game-Death.ogg new file mode 100644 index 00000000..9f2fdc38 Binary files /dev/null and b/assets/Game-Death.ogg differ diff --git a/assets/Game-Shot.mp3 b/assets/Game-Shot.mp3 new file mode 100644 index 00000000..36c394ae Binary files /dev/null and b/assets/Game-Shot.mp3 differ diff --git a/assets/Game-Shot.ogg b/assets/Game-Shot.ogg new file mode 100644 index 00000000..05f31359 Binary files /dev/null and b/assets/Game-Shot.ogg differ diff --git a/assets/Game-Spawn.mp3 b/assets/Game-Spawn.mp3 new file mode 100644 index 00000000..a51469e5 Binary files /dev/null and b/assets/Game-Spawn.mp3 differ diff --git a/assets/Game-Spawn.ogg b/assets/Game-Spawn.ogg new file mode 100644 index 00000000..e0aae1bb Binary files /dev/null and b/assets/Game-Spawn.ogg differ diff --git a/assets/Humm.mp3 b/assets/Humm.mp3 new file mode 100644 index 00000000..6b806fec Binary files /dev/null and b/assets/Humm.mp3 differ diff --git a/assets/Humm.ogg b/assets/Humm.ogg new file mode 100644 index 00000000..fdad7a9a Binary files /dev/null and b/assets/Humm.ogg differ diff --git a/assets/M-GameBG.mp3 b/assets/M-GameBG.mp3 new file mode 100644 index 00000000..0d3ea944 Binary files /dev/null and b/assets/M-GameBG.mp3 differ diff --git a/assets/M-GameBG.ogg b/assets/M-GameBG.ogg new file mode 100644 index 00000000..e7426b99 Binary files /dev/null and b/assets/M-GameBG.ogg differ diff --git a/assets/R-Damage.mp3 b/assets/R-Damage.mp3 new file mode 100644 index 00000000..2dfbbbc4 Binary files /dev/null and b/assets/R-Damage.mp3 differ diff --git a/assets/R-Damage.ogg b/assets/R-Damage.ogg new file mode 100644 index 00000000..20afd2d7 Binary files /dev/null and b/assets/R-Damage.ogg differ diff --git a/assets/S-Damage.mp3 b/assets/S-Damage.mp3 new file mode 100644 index 00000000..eadeccbd Binary files /dev/null and b/assets/S-Damage.mp3 differ diff --git a/assets/S-Damage.ogg b/assets/S-Damage.ogg new file mode 100644 index 00000000..2a156e74 Binary files /dev/null and b/assets/S-Damage.ogg differ diff --git a/assets/Thunder1.mp3 b/assets/Thunder1.mp3 new file mode 100644 index 00000000..baf3d927 Binary files /dev/null and b/assets/Thunder1.mp3 differ diff --git a/assets/Thunder1.ogg b/assets/Thunder1.ogg new file mode 100644 index 00000000..132d3729 Binary files /dev/null and b/assets/Thunder1.ogg differ diff --git a/assets/ToneWobble.mp3 b/assets/ToneWobble.mp3 new file mode 100644 index 00000000..0b140413 Binary files /dev/null and b/assets/ToneWobble.mp3 differ diff --git a/assets/ToneWobble.ogg b/assets/ToneWobble.ogg new file mode 100644 index 00000000..717bce49 Binary files /dev/null and b/assets/ToneWobble.ogg differ diff --git a/assets/U-CabinBoy3.mp3 b/assets/U-CabinBoy3.mp3 new file mode 100644 index 00000000..5c3846d0 Binary files /dev/null and b/assets/U-CabinBoy3.mp3 differ diff --git a/assets/U-CabinBoy3.ogg b/assets/U-CabinBoy3.ogg new file mode 100644 index 00000000..6141282a Binary files /dev/null and b/assets/U-CabinBoy3.ogg differ diff --git a/assets/music.mp3 b/assets/music.mp3 new file mode 100755 index 00000000..5beeca81 Binary files /dev/null and b/assets/music.mp3 differ diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 00000000..b247d8ea --- /dev/null +++ b/css/styles.css @@ -0,0 +1,73 @@ +body { + font-family: "Times New Roman", Times, serif; + font-size: 14px; + background:url(../img/bg.gif) fixed; + background-repeat: repeat; + text-align: center; +} +.content { + background-color: #FFF; + border: 12px solid #000; + margin-top: 50px; + width: 600px; + margin-left: auto; + margin-right: auto; + margin-bottom: 50px; + -webkit-box-shadow: 0px 3px 16px #333; + -moz-box-shadow: 0px 2px 16px #333; + box-shadow: 0px 2px 16px #333; + padding: 50px; +} +.callout { + background-image: url(../img/callout_bg.gif); + background-repeat: repeat; + padding: 6px; + color: #000; +} +a { + color: #568; +} +a:hover { + color: #000; +} +p, ul { + color: #333; + text-align: justify; + line-height: 1.15; +} +ul { + padding-left: 25px; +} +h1 { + font-size: 24px; + font-weight: normal; + margin-top: 10px; +} +h3 { + text-align: left; + margin-bottom: -1em; + font-size: 14px; + color: #000; +} +footer p { + text-align:center; + margin-bottom:0px; + font-size: 11px; + color: #777; + font-family: Arial, Helvetica, sans-serif; +} +p.active{ + font-weight:bold; +} +.demoWrap{ + background:url(../img/callout_bg.gif); + border:1px solid #fff; + border:1px solid #cccccc; + width: auto; + height: auto; + padding: 10px; + position:relative; + font-family:Tahoma, Geneva, sans-serif; + font-size:12px; line-height:14px; + z-index:100; +} diff --git a/examples/TestSuite.html b/examples/TestSuite.html new file mode 100644 index 00000000..e9e60a20 --- /dev/null +++ b/examples/TestSuite.html @@ -0,0 +1,301 @@ + + + + + + + +SoundJS Test Suit + + + + +
+ +
+ Fork me on GitHub + A javascript library for working with html5 audio +

Test Suite

+ +
+

This test suite loads and plays a number of sounds, defined in the source. Playing sounds can be controlled, stopped, etc

+ +
+

Select a sound to play it. Each sound shows how many instances it can play at once.

+
+ + + + + +
+ + +
+
+
+ +
+

Select a playing sound to stop it, or control its volume. When a sound completes, it is removed from this list.

+ +
+ + +
+ +
+
+

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game , + Test Suite +

+ +
+ + diff --git a/examples/convolution.html b/examples/convolution.html new file mode 100644 index 00000000..288c09b9 --- /dev/null +++ b/examples/convolution.html @@ -0,0 +1,171 @@ + + + + + + +SoundJS: Example - Convolution + + + + + + + + + + +
+
+ Fork me on GitHub + A javascript library for working with html5 audio +

Example: Convolution Test

+ +
+ +

Click "Run Demo" to see how SoundJS handles a variety of common sound playing situations.

+
+ + +

0ms - Quiet note, repeating

+

0ms - Gull

+

150ms - Gull interrupting previous gull

+

1000ms - Rat

+

1050ms - Rat overlapping prev rat

+

2500ms - Note single

+

3500ms - Note single overlapping

+

4000ms - Pause mid notes

+

4500ms - Note, (fails: instances used up)

+

8000ms - Resume

+

9000ms - Note single

+

10000ms - Stop repeating note, letting previous note fade out

+ +
+ +

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game, + Test Suite +

+ +
+ + \ No newline at end of file diff --git a/examples/dynamicInstance.html b/examples/dynamicInstance.html new file mode 100644 index 00000000..cb2663d3 --- /dev/null +++ b/examples/dynamicInstance.html @@ -0,0 +1,200 @@ + + + + + + +SoundJS: Example - Dynamic Instances + + + + + + + + + + + +
+
+ Fork me on GitHub + A javascript library for working with html5 audio +

Example: Dynamic Instances

+ +
+ +

This a test board where you can play multiple looping sounds. The code is an example of how a game would divvy up instances using an on-demand approach rather than set numbers. Additionally it can be used to test audio fidelity and the capabilities of target devices.

+
+
+
Click and hold to pause, Active instances:
+
0
+
+ +
+ +

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game, + Test Suite +

+ +
+ + \ No newline at end of file diff --git a/examples/game/Ship.js b/examples/game/Ship.js new file mode 100644 index 00000000..e0a24b79 --- /dev/null +++ b/examples/game/Ship.js @@ -0,0 +1,128 @@ +(function(window) { + +// +function Ship() { + this.initialize(); +} +var p = Ship.prototype = new Container(); + +// public properties: + Ship.TOGGLE = 60; + Ship.MAX_THRUST = 2; + Ship.MAX_VELOCITY = 5; + +// public properties: + p.shipFlame; + p.shipBody; + + p.timeout; + p.thrust; + + p.vX; + p.vY; + + p.bounds; + p.hit; + +// constructor: + p.Container_initialize = p.initialize; //unique to avoid overiding base class + + p.initialize = function() { + this.Container_initialize(); + + this.shipFlame = new Shape(); + this.shipBody = new Shape(); + + this.addChild(this.shipFlame); + this.addChild(this.shipBody); + + this.makeShape(); + this.timeout = 0; + this.thrust = 0; + this.vX = 0; + this.vY = 0; + } + +// public methods: + p.makeShape = function() { + //draw ship body + var g = this.shipBody.graphics; + g.clear(); + g.beginStroke("#FFFFFF"); + + g.moveTo(0, 10); //nose + g.lineTo(5, -6); //rfin + g.lineTo(0, -2); //notch + g.lineTo(-5, -6); //lfin + g.closePath(); // nose + + + //draw ship flame + var o = this.shipFlame; + o.scaleX = 0.5; + o.scaleY = 0.5; + o.y = -5; + + g = o.graphics; + g.clear(); + g.beginStroke("#FFFFFF"); + + + g.moveTo(2, 0); //ship + g.lineTo(4, -3); //rpoint + g.lineTo(2, -2); //rnotch + g.lineTo(0, -5); //tip + g.lineTo(-2, -2); //lnotch + g.lineTo(-4, -3); //lpoint + g.lineTo(-2, -0); //ship + + //furthest visual element + this.bounds = 10; + this.hit = this.bounds; + } + + p.tick = function() { + //move by velocity + this.x += this.vX; + this.y += this.vY; + + //with thrust flicker a flame every Ship.TOGGLE frames, attenuate thrust + if(this.thrust > 0) { + this.timeout++; + this.shipFlame.alpha = 1; + + if(this.timeout > Ship.TOGGLE) { + this.timeout = 0; + if(this.shipFlame.scaleX == 1) { + this.shipFlame.scaleX = 0.5; + this.shipFlame.scaleY = 0.5; + } else { + this.shipFlame.scaleX = 1; + this.shipFlame.scaleY = 1; + } + } + this.thrust -= 0.5; + } else { + this.shipFlame.alpha = 0; + this.thrust = 0; + } + } + + p.accelerate = function() { + //increase push ammount for acceleration + this.thrust += this.thrust + 0.6; + if(this.thrust >= Ship.MAX_THRUST) { + this.thrust = Ship.MAX_THRUST; + } + + //accelerate + this.vX += Math.sin(this.rotation*(Math.PI/-180))*this.thrust; + this.vY += Math.cos(this.rotation*(Math.PI/-180))*this.thrust; + + //cap max speeds + this.vX = Math.min(Ship.MAX_VELOCITY, Math.max(-Ship.MAX_VELOCITY, this.vX)); + this.vY = Math.min(Ship.MAX_VELOCITY, Math.max(-Ship.MAX_VELOCITY, this.vY)); + } + +window.Ship = Ship; +}(window)); \ No newline at end of file diff --git a/examples/game/SpaceRock.js b/examples/game/SpaceRock.js new file mode 100644 index 00000000..71e75ba8 --- /dev/null +++ b/examples/game/SpaceRock.js @@ -0,0 +1,139 @@ +(function(window) { + +// +function SpaceRock(size) { + this.initialize(size); +} + +var p = SpaceRock.prototype = new Shape(); + +// static properties: + SpaceRock.LRG_ROCK = 40; + SpaceRock.MED_ROCK = 20; + SpaceRock.SML_ROCK = 10; + +// public properties: + + p.bounds; //visual radial size + p.hit; //average radial disparity + + p.size; //size value itself + p.spin; //spin ammount + p.score; //score value + + p.vX; //velocity X + p.vY; //velocity Y + + p.active; //is it active + +// constructor: + p.Shape_initialize = p.initialize; //unique to avoid overiding base class + + p.initialize = function(size) { + this.Shape_initialize(); // super call + + this.activate(size); + } + +// public methods: + //handle drawing a spaceRock + p.getShape = function(size) { + var angle = 0; + var radius = size; + + this.size = size; + this.hit = size; + this.bounds = 0; + + //setup + this.graphics.clear(); + this.graphics.beginStroke("#FFFFFF"); + + this.graphics.moveTo(0, size); + //draw spaceRock + while(angle < (Math.PI*2 - .5)) { + angle += .25+(Math.random()*100)/500; + radius = size+(size/2*Math.random()); + this.graphics.lineTo(Math.sin(angle)*radius, Math.cos(angle)*radius); + + //track visual depiction for interaction + if(radius > this.bounds) { this.bounds = radius; } //furthest point + this.hit = (this.hit + radius) / 2; //running average + } + + this.graphics.closePath(); // draw the last line segment back to the start point. + this.hit *= 1.1; //pad a bit + } + + //handle reinit for poolings sake + p.activate = function(size) { + this.getShape(size); + + //pick a random direction to move in and base the rotation off of speed + var angle = Math.random()*(Math.PI*2); + this.vX = Math.sin(angle)*(5 - size/10); + this.vY = Math.cos(angle)*(5 - size/10); + this.spin = (Math.random() + 0.2 )* this.vX; + + //associate score with size + this.score = (5 - size/10) * 100; + this.active = true; + } + + //handle what a spaceRock does to itself every frame + p.tick = function() { + this.rotation += this.spin; + this.x += this.vX; + this.y += this.vY; + } + + //position the spaceRock so it floats on screen + p.floatOnScreen = function(width, height) { + //base bias on real estate and pick a side or top/bottom + if(Math.random()*(width+height) > width) { + //side; ensure velocity pushes it on screen + if(this.vX > 0) { + this.x = -2 * this.bounds; + } else { + this.x = 2 * this.bounds + width; + } + //randomly position along other dimension + if(this.vY > 0) { + this.y = Math.random()*height*0.5; + } else { + this.y = Math.random()*height*0.5 + 0.5*height; + } + } else { + //top/bottom; ensure velocity pushes it on screen + if(this.vY > 0) { + this.y = -2 * this.bounds; + } else { + this.y = 2 * this.bounds + height; + } + //randomly position along other dimension + if(this.vX > 0) { + this.x = Math.random()*width*0.5; + } else { + this.x = Math.random()*width*0.5 + 0.5*width; + } + } + } + + p.hitPoint = function(tX, tY) { + return this.hitRadius(tX, tY, 0); + } + + p.hitRadius = function(tX, tY, tHit) { + //early returns speed it up + if(tX - tHit > this.x + this.hit) { return; } + if(tX + tHit < this.x - this.hit) { return; } + if(tY - tHit > this.y + this.hit) { return; } + if(tY + tHit < this.y - this.hit) { return; } + + //now do the circle distance test + return this.hit + tHit > Math.sqrt(Math.pow(Math.abs(this.x - tX),2) + Math.pow(Math.abs(this.y - tY),2)); + } + + +window.SpaceRock = SpaceRock; +}(window)); \ No newline at end of file diff --git a/examples/game/game.html b/examples/game/game.html new file mode 100644 index 00000000..f9f97cc5 --- /dev/null +++ b/examples/game/game.html @@ -0,0 +1,483 @@ + + + + +EaselJS Example: Building a game + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Fork me on GitHub + A javascript library for working with html5 audio +

Example: A Game

+ +
+ +
+ + +

Example use of SoundJS for a game, game example taken from EaselJS a canvas management tool. Music Copyright © 2010 Machinae Supremacy - Lord Krutors Dominion. Used with permission for non commercial use.

+
+ +
+
+ + +

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game, + Test Suite +

+ +
+ + \ No newline at end of file diff --git a/examples/interrupt.html b/examples/interrupt.html new file mode 100644 index 00000000..9ddf1adb --- /dev/null +++ b/examples/interrupt.html @@ -0,0 +1,165 @@ + + + + + + +SoundJS: Example - Audio Spike + + + + + + + + + + + + +
+
+ Fork me on GitHub + A javascript library for working with html5 audio +

Example: Interruption

+ +
+ +

This a test designed to illustrate exactly how interrupt modes function.

+

Browsers limit the total number of instances available for sound. If all the available instances are in use, SoundJS provides an interrupt mechanism to determine which playing sound to interrupt. Every Time the border changes, the following code is run using the interrupt value from the dropdown: SoundJS.play("music", interrupt); Specific details on the interrupt modes and the play() function can be found in the documentation. The return value from each play() command will indicate the instance used.

+
+
+ +
+
0
+
1
+

+
+ +

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game, + Test Suite +

+ + +
+ + \ No newline at end of file diff --git a/examples/masterVolume.html b/examples/masterVolume.html new file mode 100644 index 00000000..d5a85fd0 --- /dev/null +++ b/examples/masterVolume.html @@ -0,0 +1,110 @@ + + + + + + +SoundJS: Example - Master Volume + + + + + + + + + +
+
+ Fork me on GitHub + A javascript library for working with html5 audio +

Example: Master Volume

+ +
+ +

This is a demonstration of how master volume works, including independantly set volumes. There are two sounds playing: a rat squeak, and a single even tone. The volume of the tone is modified by a sine wave every tick and the rat alternates between high and low volume.

+
+ Volume: + +
+ +

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game, + Test Suite +

+ +
+ + \ No newline at end of file diff --git a/img/bg.gif b/img/bg.gif new file mode 100644 index 00000000..0aa8abda Binary files /dev/null and b/img/bg.gif differ diff --git a/img/callout_bg.gif b/img/callout_bg.gif new file mode 100644 index 00000000..7eaf1dc9 Binary files /dev/null and b/img/callout_bg.gif differ diff --git a/img/flourish.gif b/img/flourish.gif new file mode 100644 index 00000000..0629624f Binary files /dev/null and b/img/flourish.gif differ diff --git a/img/flourish2.gif b/img/flourish2.gif new file mode 100644 index 00000000..e11ba74d Binary files /dev/null and b/img/flourish2.gif differ diff --git a/img/logo.gif b/img/logo.gif new file mode 100644 index 00000000..fe8fbccc Binary files /dev/null and b/img/logo.gif differ diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 00000000..61a8c5ff Binary files /dev/null and b/img/logo.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..cefe5710 --- /dev/null +++ b/index.html @@ -0,0 +1,72 @@ + + + + +SoundJS: A Javascript Library for Working with HTML5 Audio. + + + +
+
+

Fork me on GitHub + A javascript library for working with html5 audio

+

 

+

+ a javascript library for working with
+ html5 audio +

+ +
+
+ +

Audio in HTML is promising, but can be quite difficult to work with. Each browser has a similar but different approach, which requires a lot of guess and test, and edge cases, which can add up to a lot of time spent. The SoundJS JavaScript library provides a simple API, and some powerful features to make working with audio a breeze.

+

examples: + Convolution, + Interrupt, + Dynamic Instances, + Master Volume, + A Game, + Test Suite +

+

For an example of SoundJS being used in a large-scale project, see Pirates Love Daisies, which uses techniques and approaches that were eventually rolled into SoundJS.

+

version 0.1: + download source, + view API documentation, + provide feedback, + get updates on Twitter +

+

SoundJS was built by gskinner.com, and is released under the MIT license, which means you can use it freely for almost any purpose (including commercial projects). We appreciate credit where appropriate, but it is not a requirement.

+

SoundJS is currently in early alpha. We will be making significant improvements to the library, samples, and documentation over the coming weeks. Please be aware that this may necessitate changes to the existing API.

+

In our preliminary testing, SoundJS appears to be fully compatible with Android, and all major desktop browsers that support the canvas element.

+ +

The key features are:

+

Channel Management

+

Deals with the maximums in different browsers, as well as helping determine the best channel to interrupt when dealing with multiple sounds of the same kind.

+

Batch Loading

+

Manages the preloading of a sound queue.

+

Master Volume Control

+

Control individual sound volume, or the volume of all sound channels at once, including muting.

+ +

Have a look at the full API documentation for more info.

+ +

Planned updates

+

SoundJS will be updated as time permits. We're not making any promises, but here are some of the things we're thinking of doing:

+ +
+ +
+ + \ No newline at end of file diff --git a/soundjs/SoundJS.js b/soundjs/SoundJS.js new file mode 100644 index 00000000..59d35dcd --- /dev/null +++ b/soundjs/SoundJS.js @@ -0,0 +1,1105 @@ +/* +* SoundJS by Grant Skinner. May 25, 2011 +* Visit http://www.gskinner.com/ for documentation, updates and examples. +* +* +* Copyright (c) 2011 Grant Skinner +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** +* SoundJS is a utility for managing HTML5 audio elements. +* The SoundJS class uses a static API and can't be instantiated directly. +* @module SoundJS +* +*/ +(function(window) { + + /** + * @class SoundJS + * @static + */ + function SoundJS() { + throw "SoundJS cannot be instantiated"; + } + + /** + * The duration in milliseconds to wait before declaring a timeout when loading a sound. + * + * @property AUDIO_TIMEOUT + * + * @static + * @default 8000 + * @type Number + */ + SoundJS.AUDIO_TIMEOUT = 8000; + + /** + * The interrupt mode to use when all instances are in use. Interrupt the the first instance. + * + * @property INTERRUPT_ANY + * + * @static + * @default 1 + * @type Number + * + */ + SoundJS.INTERRUPT_ANY = 1; + + /** + * The interrupt mode to use when all instances are in use. Interrupt the sound that is at the earliest position. + * + * @property INTERRUPT_EARLY + * + * @static + * @default 2 + * @type Number + */ + SoundJS.INTERRUPT_EARLY = 2; + + /** + * The interrupt mode to use when all instances are in use. Interrupt the sound that is at the latest position. + * + * @property INTERRUPT_LATE + * + * @static + * @default 3 + * @type Number + * + */ + SoundJS.INTERRUPT_LATE = 3; + + /** + * The interrupt mode to use when all instances are in use. Do not interrupt any sounds. + * + * @property INTERRUPT_NONE + * + * @static + * @default 4 + * @type Number + */ + SoundJS.INTERRUPT_NONE = 4; + + /** + * The function to call whenever progress changes. + * + * @static + * @type Function + */ + SoundJS.onProgress = null; + + /** + * The function to call when a loading sound times out. The timeout duration is determined by the AUDIO_TIMEOUT parameter. + * + * @static + * @type Function + */ + SoundJS.onSoundTimeout = null; + + /** + * The function to call when an error occurs loading a sound file. + * + * @static + * @type Function + */ + SoundJS.onSoundLoadError = null; + + /** + * The function to call when a sound has finished loading. + * + * @static + * @type Function + */ + SoundJS.onSoundLoadComplete = null; + + /** + * The function to call when all sounds in the preload queue have finished loading. + * + * @static + * @type Function + */ + SoundJS.onLoadQueueComplete = null; + + /** + * The function to call when a sound finishes playing. + * + * @static + * @type Function + */ + SoundJS.onSoundComplete = null; + + + /** + * An array containing all of the sound objects created by this manager. + * + * @property soundHash + * @type Array + * @protected + * + */ + SoundJS.soundHash = []; + + /** + * An array containing all of the sounds waiting to be loaded. + * + * @property loadQueue + * @type Array + * @protected + * + */ + SoundJS.loadQueue = []; + + /** + * The number of items left in loading queue. + * + * @property itemsToLoad + * @type Number + * @private + * + */ + SoundJS.itemsToLoad = 0; + + /** + * The number of currently playing instances. + * + * @property instanceCount + * @type Number + * @private + * + */ + SoundJS.instanceCount = 0; + + + /** + * The assumed maximum number of sounds, based on some of the browser limits we have encountered. Currently, this can not be determined via code or APIs. + * + * @private + * @static + * + */ + SoundJS.INST_MAX = 35; + + /** + * Float error tolerance. + * + * @private + * + */ + SoundJS.FT = 0.001; + + /** + * The name of the JavaScript event dispatched by an audio tag when an error occurs. + * + * @private + * + */ + SoundJS.AUDIO_ERROR = "error"; + + /** + * The name of the JavaScript event dispatched by an audio tag when loading progress changes. + * + * @private + * + */ + SoundJS.AUDIO_PROGRESS = "progress"; + + /** + * The name of the JavaScript event dispatched by an audio tag when an a sound has finished loading. + * + * @private + * + */ + SoundJS.AUDIO_COMPLETE = "canplaythrough"; + + /** + * The name of the JavaScript event dispatched by an audio tag when an a sound has finished playing. + * + * @private + * + */ + SoundJS.AUDIO_ENDED = "ended"; + + /** + * The name of the JavaScript event dispatched by an audio tag when an a sound has stalled + * + * @private + * + */ + SoundJS.AUDIO_STALLED = "stalled"; + + /** + * Master volume value. + * + * @private + * + */ + SoundJS._master = 1; + + /** + * Current Load progress. + * + * @private + * + */ + SoundJS._currentLoad = 0; + + /** + * Manage, load, and perform DOM injection of a sound. If the load queue is empty, it will trigger an immediate load, + * otherwise it will be added to the end of the loading queue and loaded in sequence. If instances of a sound by that + * name already exist, the number of instances is increased and the element 'src' is ignored. + * + * @method add + * + * @param {String} name : The reference to this sound effect + * @param {String} src : The filepath to load from + * @param {Number} instances : The number of simultaneous versions of this sound you wish to play (max sounds limited by Browser and Computer). + * + */ + SoundJS.add = function(name, src, instances) { + SoundJS.loadQueue.push({name:name, src:src, instances:instances}); + + // start loading if the queue is empty + if (SoundJS.loadQueue.length == 1) { + SoundJS.itemsToLoad = 1; + SoundJS.loadNext(); + } else { + SoundJS.itemsToLoad++; + } + }; + + /** + * Manage, load, and perform DOM injections of a sound, or set of sounds. + * This method is identical to the SoundJS.add() method, except: + * 1) An array of objects representing the parameters for each add call is required. + * [{name:'soundName', src:'sound.mp3', instances:1}, {name:'soundName', src:'sound.ogg', instances:4}] + * + * 2) It waits until all items are added to the queue before beginning the load operation, which can + * even out erroneous finished loading calls in high speed environments such as local environments + * or with cached sounds. + * + * @method addBatch + * + * @param {Array} params : Array of 'name:, src:, instances:' objects. + * + * @static + * + */ + SoundJS.addBatch = function(params) { + var l = params.length; + while(params.length) { + SoundJS.loadQueue.push(params.shift()); + } + + // start loading the queue is empty + if (SoundJS.loadQueue.length == l) { + SoundJS.loadNext(); + SoundJS.itemsToLoad = l; + } else { + SoundJS.itemsToLoad++; + } + }; + + /** + * Play the a sound file with the defined properties. + * + * The function returns one of three things; + * 1) The instance number played if sound was played successfully + * 2) 'NaN' if an erroneous parameter is passed, or the interrupt method forbids playing the sound + * 3) -1 on a delayed call, as the instance being played cannot be determined till the delay is over + * + * If the instance is needed on a delayed call, the best practice is to delay the call like so: + * e.g. setTimeout(function(){ myVar = SoundJS.play("sound")}, 3000); + * Note: this closure is needed for full functionality cross browsers. + * + * @method play + * + * @param {String} name : Specifies the url to the sound file to play (as defined in the add functions). + * @param {Number} interrupt = SoundJS.INTERRUPT_NONE: (optional) Defines the interrupt behavior if all instances are currently used. + * @param {Number} volume = 1: (optional) Specifies to volume at which to play that sound instance. + * @param {Boolean} loop = false: (optional) Sets the loop parameter on the sound instance. + * @param {Number} delay = 0: (optional) Specifies how long to wait before looking for a sound instance to play the sound. + * + * @return {Number} The index of the sound played, NaN, or -1. + * + * @static + * + */ + SoundJS.play = function(name, interrupt, volume, loop, delay) { + // deal with illegal input + if (name == null || SoundJS.soundHash[name] == null || SoundJS.soundHash[name].length == 0 || + (interrupt != SoundJS.INTERRUPT_ANY && interrupt != SoundJS.INTERRUPT_EARLY && interrupt != SoundJS.INTERRUPT_LATE + && interrupt != SoundJS.INTERRUPT_NONE && interrupt != null)) { return NaN; } + + // assign default values if null and correct inputs + if (interrupt == null) { interrupt = SoundJS.INTERRUPT_NONE; } + if (loop == null) { loop = false; } + if (!delay) { delay = 0; } + if (volume == null || volume > 1) { volume = 1; } + else if (volume < 0) { volume = 0; } + + // move onto playing + if (delay > 0) { + setTimeout(function(){SoundJS.beginPlaying(name, interrupt, volume, loop);}, delay); // closure to fix issues in IE9 + } else { + return SoundJS.beginPlaying(name, interrupt, volume, loop); + } + + return -1; + }; + + /** + * Get the current master volume level + * + * @method getMasterVolume + * + * @static + * + */ + SoundJS.getMasterVolume = function() { return SoundJS._master; } + + + /** + * Change the master volume level + * + * @method setMasterVolume + * + * @param {Number} value = null: (optional) New master volume. + * + * @static + * + */ + SoundJS.setMasterVolume = function(value) { + // don't deal with no volume + if (Number(value) == null) { return; } + + // fix out of range + if (value < 0) { value = 0; } + else if (value > 1) { value = 1; } + + var i, l, o, old, sound; + + old = SoundJS._master; + SoundJS._master = value; + + if (SoundJS._master != old) { + // adjust all + for(sound in SoundJS.soundHash) { + o = SoundJS.soundHash[sound]; + l = o.length; + for(i = 0; i < l; i++) { + o[i].volume = o[i].storedVolume * SoundJS._master; + } + } + } + }; + + /** + * Remove the specified number of sound instances from the DOM. WARNING: remove ignores interrupt + * modes and current actions, and will remove the last instances regardless of their status. + * + * @method remove + * + * @param {String} name = null: (optional) Note that the function removes all sounds if it is null. + * @param {Number} count = null: (optional) Note that the function removes all instances if it is null. + * + * @return {Boolean} Success of operation. + * + * @static + * + */ + SoundJS.remove = function(name, count) { + var i, l, o, sound; + + // global remove + if (name == null){ + for(sound in SoundJS.soundHash) { + o = SoundJS.soundHash[sound]; + l = o.length; + do { + SoundJS.stop(sound, l-1); + o[l-1].currentSrc = ""; + document.body.removeChild(o[l-1]); + o.pop(); + l = o.length; + SoundJS.instanceCount--; + } while(l); + } + } else { + o = SoundJS.soundHash[name]; + if (o == null) { return false; } + l = o.length; + + // sound remove + if (count == null) { + do { + SoundJS.stop(name, l-1); + o[l-1].currentSrc = ""; + document.body.removeChild(o[l-1]); + o.pop(); + l = o.length; + SoundJS.instanceCount--; + } while(l); + // count remove + } else { + if (count <= 0 || l <= 0) { return false; } + l--; + + for(i = 0; i <= l && i < count; i++) { + SoundJS.stop(name, l-i); + o[l-i].currentSrc = ""; + document.body.removeChild(o[l-i]); + o.pop(); + SoundJS.instanceCount--; + } + } + } + + return true; + }; + + /** + * Adjust the volume of specified instance(s) of this sound. + * + * @method setVolume + * + * @param {String} value : The volume to set the target to. + * @param {String} name = null: (optional) If null the function adjusts all sounds. + * @param {Number} instance = null: (optional) If null the function adjusts all instances. + * + * @return {Boolean} Success of operation. + * + * @static + * + */ + SoundJS.setVolume = function(value, name, instance) { + var i, l, o, sound, stored; + + // don't deal with no volume + if (value == null) { return false; } + stored = value; + value = value * SoundJS._master; + + // global volume adjust + if (name == null){ + for(sound in SoundJS.soundHash) { + o = SoundJS.soundHash[sound]; + l = o.length; + for(i = 0; i < l; i++) { + o[i].storedVolume = value; + o[i].volume = value; + } + } + } else { + o = SoundJS.soundHash[name]; + if (o == null) { return false; } + l = o.length; + + // sound volume adjust + if (instance == null) { + for(i = 0; i < l; i++) { + o[i].storedVolume = value; + o[i].volume = value; + } + // instance volume adjust + } else { + if (l <= instance) { return false; } + o[instance].storedVolume = value; + o[instance].volume = value; + } + } + + return true; + }; + + /** + * Get the volume of a sound instance. If no instance is provided the volume of the first instance will be returned. + * + * @method getVolume + * + * @param {String} name = null: (optional) If null the function adjusts all sounds. + * @param {Number} instance = null: (optional) If null the function gets the first instance. + * + * @return {Number} the volume of the specified instance or -1 if the instance does not exist. + * + * @static + */ + SoundJS.getVolume = function(name, instance) { + var o = SoundJS.soundHash[name]; + if (o == null || o.length == 0) { return -1; } + if (instance == null) { + return o[1].storedVolume; + } else { + if (o.length < instance) { return -1; } + return o[instance].storedVolume; + } + } + + /** + * Specify the mute value of specified instance(s) of this sound. + * + * @method setMute + * + * @param {String} isMuted : Whether or not the target is muted. + * @param {String} name = null: (optional) Note: The function adjusts all sounds if the value is null. + * @param {Array} instance = null: (optional) Note: The function adjusts all instances if the value is null. + * + * @return {Boolean} Success of operation. + * + * @static + * + */ + SoundJS.setMute = function(isMuted, name, instance) { + var i, l, o, sound; + + // don't deal with no volume + if (isMuted == null) { return false; } + + // global volume adjust + if (name == null){ + for(sound in SoundJS.soundHash) { + o = SoundJS.soundHash[sound]; + l = o.length; + for(i = 0; i < l; i++) { + o[i].muted = isMuted; + } + } + } else { + o = SoundJS.soundHash[name]; + if (o == null) { return false; } + l = o.length; + + // sound volume adjust + if (instance == null) { + for (i = 0; i < l; i++) { + o[i].muted = isMuted; + } + // instance volume adjust + } else { + if (l <= instance) { return false; } + o[instance].muted = isMuted; + } + } + + return true; + }; + + /** + * Pause specified instance(s) of this sound. + * + * @method pause + * + * @param {String} name = null: (optional) If null the function adjusts all sounds. + * @param {Number} instance = null: (optional) If null the function adjusts all instances. + * + * @return {Boolean} Success of operation. + * + * @static + * + */ + SoundJS.pause = function(name, instance) { + var i, l, o, sound; + + // global pause + if (name == null){ + for(sound in SoundJS.soundHash) { + o = SoundJS.soundHash[sound]; + l = o.length; + for(i = 0; i < l; i++) { + o[i].pause(); + } + } + } else { + o = SoundJS.soundHash[name]; + if (o == null) { return false; } + l = o.length; + + // sound pause + if (instance == null) { + for(i = 0; i < l; i++) { + o[i].pause(); + } + // instance pause + } else { + if (l <= instance) { return false; } + o[instance].pause(); + } + } + + return true; + }; + + /** + * Resume specified instance(s) of this sound. + * + * @method resume + * + * @param {String} name = null: (optional) If null the function adjusts all sounds. + * @param {String} instance = null: (optional) If null the function adjusts all instances. + * + * @return {Boolean} Success of operation. + * + * @static + * + */ + SoundJS.resume = function(name, instance) { + var i, l, o, sound; + + // global resume + if (name == null){ + for(sound in SoundJS.soundHash) { + l = SoundJS.soundHash[sound].length; + for(i = 0; i < l; i++) { + o = SoundJS.soundHash[sound][i]; + if (o.loop || (o.currentTime != o.duration && o.currentTime != 0)){ + o.play(); + } + } + } + } else { + if (SoundJS.soundHash[name] == null) { return false; } + l = SoundJS.soundHash[name].length; + + // sound resume + if (instance == null) { + for(i = 0; i < l; i++) { + o = SoundJS.soundHash[name][i]; + if (o.loop || (o.currentTime != o.duration && o.currentTime != 0)){ + o.play(); + } + } + // instance resume + } else { + if (l <= instance) { return false; } + o = SoundJS.soundHash[name][instance]; + if (o.loop || (o.currentTime != o.duration && o.currentTime != 0)){ + o.play(); + } + } + } + + return true; + }; + + /** + * Stop specified instance(s) of this sound. + * + * @method stop + * + * @param {String} name = null: (optional) If null the function adjusts all sounds. + * @param {String} instance = null: (optional) If null the function adjusts all instances. + * + * @return {Boolean} Success of operation. + * + * @static + * + */ + SoundJS.stop = function(name, instance) { + var i, l, o, sound; + + // global stop + if (name == null){ + for(sound in SoundJS.soundHash) { + l = SoundJS.soundHash[sound].length; + for(i = 0; i < l; i++) { + o = SoundJS.soundHash[sound][i]; + try { o.currentTime = 0; } catch(e) {} // Firefox chokes on this for stopped sounds. + o.pause(); + } + } + } else { + if (SoundJS.soundHash[name] == null) { return false; } + l = SoundJS.soundHash[name].length; + + // sound stop + if (instance == null) { + for(i = 0; i < l; i++) { + o = SoundJS.soundHash[name][i]; + o.currentTime = 0; + o.pause(); + } + // instance stop + } else { + if (l <= instance) { return false; } + o = SoundJS.soundHash[name][instance]; + o.currentTime = 0; + o.pause(); + } + } + + return true; + }; + + /** + * Check to see if specified sound is loaded/valid. + * If the value of the name is null, check all sounds are loaded. + * + * @method isLoaded + * + * @param {String} name = null: (optional) The sound to check. + * + * @return {Boolean} Indicating if the specified sound is valid & loaded + * + * @static + * + */ + SoundJS.isLoaded = function(name) { + var result = true; + var sound; + + if (name == null) { + // global isLoaded + for(sound in SoundJS.soundHash) { + result = result && SoundJS.soundHash[sound] && SoundJS.soundHash[sound][0] && SoundJS.soundHash[sound][0].loaded; + if (!result) { return result; } + } + } else { + // sound isLoaded + return SoundJS.soundHash[name] && SoundJS.soundHash[name][0] && SoundJS.soundHash[name][0].loaded; + } + + return result; + }; + + /** + * List the number of instances used by a specific sound. + * If the value of the name is null, check the entire system + * + * @method getNumInstances + * + * @param {String} name = null: (optional) Name of the sound to lookup. + * + * @return {Number} The number of instances currently instantiated or -1 if an invalid name is passed in. + * + * @static + * + */ + SoundJS.getNumInstances = function(name) { + var sound; + + if (name == null) { + return instanceCount; + } else if (SoundJS.soundHash[name]){ + return SoundJS.soundHash[name].length; + } else { + return -1; + } + }; + + /** + * Get the maximum number of instances the browser can run. + * + * @method getMaxInstances + * + * @return {Number} Assumption of maximum instances the browser can run, + * the actual number varies from browser to browser and machine to machine, and + * is largely based on the sound card. + * + * @static + * + */ + SoundJS.getMaxInstances = function() { + return SoundJS.INST_MAX; + }; + + /** + * Get the current load progress for the sound queue. + * + * @method getCurrentLoadProgress + * + * @return {Number} A number 0-1 indicating percentage loaded of the current loading queue. + * + * @static + * + */ + SoundJS.getCurrentLoadProgress = function() { + //return (SoundJS.itemsToLoad - SoundJS.loadQueue.length - SoundJS._currentLoad) / SoundJS.itemsToLoad; + //TD: Fix + return (SoundJS.itemsToLoad - SoundJS.loadQueue.length - (1 - SoundJS._currentLoad)) / SoundJS.itemsToLoad; + }; + + /** + * Get a reference to the DOM sound object by name and instance. + * + * @method getInstance + * + * @param {String} name : The sound name to find the instance in. + * @param {String} instance : The specific instance to grab. + * + * @return {Sound object} The Sound Element specified by name and instance or null. + * + * @static + * + */ + SoundJS.getInstance = function(name, instance) { + if (name == null || instance < 0 || !SoundJS.soundHash[name] || !SoundJS.soundHash[name][instance]) { return null; } + + return SoundJS.soundHash[name][instance]; + }; + + /** + * Internal function to actual begin playing a sound. + * Use .play() instead for type checking and default paramters, calling this directly may crash. + * + * @private + * + */ + SoundJS.beginPlaying = function(name, interrupt, volume, loop) { + // find correct instance + var i, result, target, examine; + var shouldReplace = false; + var l = SoundJS.soundHash[name].length; + + for(i = 0; i < l; i++) { + // init + examine = SoundJS.soundHash[name][i]; + if (target == null && interrupt != SoundJS.INTERRUPT_ANY && interrupt != SoundJS.INTERRUPT_NONE) { + target = examine; + result = i; + } + + // check interrupt types + if ((examine.currentTime >= (examine.duration - SoundJS.FT) && !examine.loop) || + (examine.currentTime == 0 && examine.paused)) { + // unused + shouldReplace = true; + l = i; + } else { + // compare + if ((interrupt == SoundJS.INTERRUPT_EARLY && examine.currentTime < target.currentTime) || + (interrupt == SoundJS.INTERRUPT_LATE && examine.currentTime > target.currentTime)){ + shouldReplace = true; + } + } + + // use marked item instead + if (shouldReplace) { + target = examine; + result = i; + } + } + + // if nothing is found replace the first one in the list + if (interrupt == SoundJS.INTERRUPT_ANY && !target){ + target = SoundJS.soundHash[name][0]; + result = 0; + } + + if (target) { + // play sound instance + target.loop = loop; + target.storedVolume = volume; + target.volume = volume * SoundJS._master; + target.currentTime = 0; + if (target.paused){ target.play(); } + + return result; + } + + // indicate no available instance + return -1; + }; + + /** + * Advances the loading queue. Do not call manually, as it assumes the previous item has loaded succesfully + * and may corrupt the loading procedure. Call .add() or .addBatch() instead. + * + * @private + * + */ + SoundJS.loadNext = function() { + // validate + if (SoundJS.loadQueue.length <= 0) { + if (SoundJS.onLoadQueueComplete) { SoundJS.onLoadQueueComplete(); } + return; + } + + // take data from next item + var o = SoundJS.loadQueue.shift(); + var instances = o.instances || 1; + var name = o.name; + var src = o.src; + + // build hash, and extend already existing names + var hash = SoundJS.soundHash[name] + if (hash == null) { hash = SoundJS.soundHash[name] = []; } + else if (hash.length) { src = hash[0].src; } + var init = hash.length; + + for(var i=init, l=init+instances; i