diff options
| author | Jan Tuomi <jans.tuomi@gmail.com> | 2015-11-16 16:58:18 +0200 |
|---|---|---|
| committer | Jan Tuomi <jans.tuomi@gmail.com> | 2015-11-16 16:58:18 +0200 |
| commit | 13c5dc317abe0fb4458e703f2ab9397a7c24c859 (patch) | |
| tree | 1410e5e6d250cbcadc29a1b66a851473e15da8b6 /howler.js-2.0.0-beta4 | |
Rengöring
Diffstat (limited to 'howler.js-2.0.0-beta4')
| -rw-r--r-- | howler.js-2.0.0-beta4/CHANGELOG.md | 254 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/LICENSE.md | 20 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/README.md | 315 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/bower.json | 6 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/howler.core.min.js | 2 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/howler.effects.min.js | 2 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/howler.min.js | 4 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/package.json | 31 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/src/howler.core.js | 1808 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/src/plugins/howler.effects.js | 603 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/tests/sound1.mp3 | bin | 0 -> 1016939 bytes | |||
| -rw-r--r-- | howler.js-2.0.0-beta4/tests/sound1.ogg | bin | 0 -> 1038603 bytes | |||
| -rw-r--r-- | howler.js-2.0.0-beta4/tests/sound2.mp3 | bin | 0 -> 254895 bytes | |||
| -rw-r--r-- | howler.js-2.0.0-beta4/tests/sound2.ogg | bin | 0 -> 169096 bytes | |||
| -rw-r--r-- | howler.js-2.0.0-beta4/tests/tests.html | 66 | ||||
| -rw-r--r-- | howler.js-2.0.0-beta4/tests/tests.js | 452 |
16 files changed, 3563 insertions, 0 deletions
diff --git a/howler.js-2.0.0-beta4/CHANGELOG.md b/howler.js-2.0.0-beta4/CHANGELOG.md new file mode 100644 index 0000000..c601fc5 --- /dev/null +++ b/howler.js-2.0.0-beta4/CHANGELOG.md @@ -0,0 +1,254 @@ +## 2.0.0 (TBD) +This major release contains breaking changes (migration guide), but should be very familiar. Howler.js has been rewritten from the ground up using the knowledge and work since the initial release. There's a long list of additions and improvements, which I urge you to read through. + +The biggest change is how you should think about your audio when using howler.js. There is now the concept of global (`Howler`), group (`Howl`) and single sound (`Sound`). Each sound that is played gets its own `Sound` object that can be manipulated, giving much greater control over playback, whether using sprites or not. `Howl` method calls can then apply to one sound or all in the group. + +``` +Howler (global) -> + Howl (group) -> + Sound (single) +``` + +Howler.js now also has the concept of plugins. The core represents 100% compatibility across hTML5 Audio and Web Audio, adhering to the initial goals of the library. There is also a new Effects Plugin that adds advanced features only available in the Web Audio API. + +- `ADDED`: Lots of general code cleanup, simplification and reogranziation. +- `ADDED`: Howler.js is now modularized. The core represents the initial goal for howler.js with 100% compatibility across hTML5 Audio and Web Audio. The effects plugin adds many of the advanced features provided by the Web Audio API. +- `ADDED`: The new structure allows for full control of sprite playback (this was buggy or didn't work at all before). +- `ADDED`: New `once` method to setup event listeners that will automatically remove themselves once fired. +- `ADDED`: New `playing` method that will return `true` if the specified sound is currently playing. +- `ADDED`: New `duration` method that will return the duration of the audio source. +- `ADDED`: New `preload` property to allow disabling the auto-preload functionality. +- `ADDED`: New `faded` event that fires at the completion of a fade-in or fade-out. +- `ADDED`: New `stop` event that fires when `stop` is called, but not when the sound ends (`end` event already exists for that). +- `ADDED`: New `pool` property to allow setting the inactive sound pool size (for advanced use, still defaults to 5). +- `ADDED`: Third parameter to `on`, `once` and `off` to allow listening or removing events for only a specific sound id. +- `ADDED`: The following methods now alter all sounds within a `Howl` group when no `id` is passed: `pause`, `stop`, `volume`, `fade`, `mute`, `loop`. +- `ADDED`: The `rate` property now changes the playback rate on both Web Audio and HTML5 Audio. +- `ADDED`: New `rate` method that allows changing playback rate at runtime. +- `ADDED`: New global `unload` method. +- `ADDED`: Support for .webm extension in addition to .weba. +- `ADDED`: New codec recommendations and notes have been added to the documentation. +- `ADDED`: (Effects) New `Howler` listener methods `pos`, `orientation`, `velocity` and `listenerAttr`. +- `ADDED`: (Effects) New `Howl` methods `pos`, `orientation`, `velocity` and `pannerAttr` to control spatial audio of single sounds or groups of sounds. +- `ADDED`: (Effects) `pannerAttr` allows for control of `coneInnerAngle`, `coneOUterAngle`, `coneOuterGain`, `distanceModel`, `maxDistance`, `panningModel`, `refDistance` and `rolloffFactor`. +- `UPDATED`: (Effects) When using Web Audio, a panner node is only added when spatial audio is used. +- `UPDATED`: The `play` method no longer takes a callback and immediately returns the playback sound id (this means you can no longer chain onto the `play` method, but all others work the same). +- `UPDATED`: Changed property names `buffer` to `html5` and `pos` to `seek`. +- `UPDATED`: The global, group and single sound `mute` and `unmute` methods have been combined into a single `mute` method. +- `UPDATED`: The AMD definition is now namespaced to `howler`. +- `UPDATED`: The deprecated `fadeIn` and `fadeOut` methods have been removed in favor of the single `fade` method. +- `UPDATED`: Improved the `ext` property and made it especially usefully for playing streams (for example, SoundCloud). +- `UPDATED`: The `fade` method now only uses timeouts as a fallback with HTML5 Audio. +- `UPDATED`: Moved any needed try/catch statements into own methods to prevent de-optimization in V8 and others. +- `UPDATED`: Updated and improved overall documentation. +- `UPDATED`: Fades are now automatically stopped when a new one is started, volume is changed or the sound is paused/stopped. +- `UPDATED`: Automatically checks for disabled audio in Internet Explorer. +- `FIXED`: The event system has been overhauled to be more reliable. +- `FIXED`: Methods called before a sound has loaded no longer cause events to stick in the queue. +- `FIXED`: The `end` event correctly fires at the end of each loop when using Web Audio. +- `FIXED`: Fixed several issues with playback of sprites. +- `FIXED`: Fixed several issues with playback timing after pausing sounds. +- `FIXED`: Improved support for seeking a sound while it is playing. +- `FIXED`: When playback rate is changed, the `end` event now fires at the correct time. +- `FIXED`: Fixed a potential memory leak when using the `unload` method. +- `FIXED`: Calling `pause` on a sound that hasn't yet loaded now works correctly. +- `FIXED`: Muting a sound while it is fading now works. +- `FIXED`: Playback of base64 encoded sounds in Internet Explorer 9. +- `FIXED`: MIME check for some base64 encoded MP3's. +- `FIXED`: Now tries to automatically unlock audio on mobile browsers besides Mobile Safari. + +## 1.1.25 (July 29, 2014) +- `ADDED`: The `AudioContext` is now available on the global `Howler` object (thanks Matt DesLauriers). +- `FIXED`: When falling back to HTML5 Audio due to XHR error, delete cache for source file to prevent multi-playback issues. + +## 1.1.24 (July 20, 2014) +- `FIXED`: Improved performance of loading files using data URIs (thanks Rob Wu). +- `FIXED`: Data URIs now work with Web Audio API (thanks Rob Wu). +- `FIXED`: Omitting the second parameter of the `off` method now correctly clears all events by that name (thanks Gabriel Munteanu). +- `FIXED`: Fire `end` event when unloading playing sounds. +- `FIXED`: Small error fix in iOS check. + +## 1.1.23 (July 2, 2014) +- `FIXED`: Playing multiple sprites rapdily with HTML5 Audio cause the sprite to break due to a v1.1.22 update. +- `FIXED`: Don't run the iOS test if there is no audio context, which prevents a breaking error. + +## 1.1.22 (June 28, 2014) +- `ADDED`: Howler will now automatically attempt to unlock audio on iOS (thanks Federico Brigante). +- `ADDED`: New `codecs` global Howler method to check for codec support in the current browser (thanks Jay Oster). +- `FIXED`: End timers are now correctly cleaned up when a sound naturally completes rather than being forced to stop. + +## 1.1.21 (May 28, 2014) +- `ADDED`: Support for npm and bower (thanks Morantron). +- `ADDED`: Support for audio/aac, audio/m4a and audio/mp4 mime types (thanks Federico Brigante). +- `FIXED`: Fixed calculation of duration after pausing a sprite that was sometimes causing unexpected behavior. +- `FIXED`: Clear the event listener when creating a new HTML5 Audio node. + +## 1.1.20 (April 18, 2014) +- `ADDED`: When using Web Audio API, the panningModel now defaults to 'equalpower' to give higher quality sound. It then automatically switches to 'HRTF' when using 3D sound. This can also be overridden with the new `model` property. +- `FIXED`: Fixed another bug causing issues in CocoonJS (thanks Olivier Biot). +- `FIXED`: Fixed an issue that could have caused invalid state errors and a memory leak when unloading in Internet Explorer. +- `FIXED`: The documentation has been updated to include the `rate` property. + +## 1.1.19 (April 14, 2014) +- `ADDED`: Added CocoonJS support (thanks Olivier Biot). +- `FIXED`: Fixed several issues with pausing sprite instances by overhauling how end timers are tracked and cleared internally. +- `FIXED`: Prevent error when using a server-side require where window is absent (thanks AlexMost). + +## 1.1.18 (March 23, 2014) +- `FIXED`: Muting a looping sound now correctly keeps the sound muted when using HTML5 Audio. +- `FIXED`: Wrap AudioContext creation in try/catch to gracefully handle browser bugs: [Chromium issue](https://code.google.com/p/chromium/issues/detail?id=308784) (thanks Chris Buckley). +- `FIXED`: Listen for HTML5 Audio errors and fire `loaderror` if any are encountered (thanks digitaltonic). + +## 1.1.17 (February 5, 2014) +- `FIXED`: Fix another bug in Chrome that would throw an error when pausing/stopping when a source is already stopped. +- `ADDED`: CommonJS support for things like Browserify (thanks Michal Kuklis). +- `ADDED`: Support for playback mp4 files. +- `ADDED`: Expose the `noAudio` variable to the global `Howler` object. +- `FIXED`: Fix a rounding error that was causing HTML5 Audio to cut off early on some environments. +- `FIXED`: The `onend` callback now correctly fires when changing the pos of a sound after it has started playing and when it is using HTML5 Audio. + +## 1.1.16 (January 8, 2014) +- `FIXED`: Prevent InvalidStateError when unloading a sound that has already been stopped. +- `FIXED`: Fix bug in unload method that prevented the first sound from being unloaded. + +## 1.1.15 (December 28, 2013) +- `FIXED`: Fix bug that prevented master volume from being set to 0. +- `FIXED`: Fix bug that prevented initial volume from being set to 0. +- `FIXED`: Update the README to accurately show `autoplay` as defaulting to `false`. +- `FIXED`: Call `loaderror` when decodeAudioData fails. +- `FIXED`: Fix bug in setting position on an active playing WebAudio node through 'pos(position, id)' (thanks Arjun Mehta). +- `FIXED`: Fix an issue with looping after resuming playback when in WebAudio playback (thanks anzev). + +## 1.1.14 (October 18, 2013) +- `FIXED`: Critical bug fix that was breaking support on some browsers and some codecs. + +## 1.1.13 (October 17, 2013) +- `FIXED`: Code cleanup by removing redundant `canPlay` object (thanks Fabien). +- `FIXED`: File extensions are now detected correctly if there is a query string with dots in the filename (thanks theshock). +- `FIXED`: Fire `onloaderror` if a bad filename is passed with the `urls` property. + +## 1.1.12 (September 12, 2013) +- `UPDATED`: Changed AMD definition to anonymous module and define it as global always (thanks Fabien). +- `ADDED`: Added the `rate` property to `Howl` object creation, allowing you to specify the playback rate. This only works when using Web Audio (thanks Qqwy). +- `FIXED`: Prevent some instances of IE9 from throwing "Not Implemented" error (thanks Tero Tilus). + +## 1.1.11 (July 28, 2013) +- `FIXED`: Fix bug caused by trying to disconnect audio node when using HTML5 Audio. +- `FIXED`: Correctly return the sound's position when it is paused. +- `FIXED`: Fix another bug that caused looping sounds to not always correctly resume after a pause. + +## 1.1.10 (July 26, 2013) +- `ADDED`: New `unload` method to destroy a Howl object. This will stop all associated sounds instantly and remove the sound from the cache. +- `FIXED`: When using Web Audio, loop from the correct position after pausing the sound halfway through. +- `FIXED`: Always return a number when getting a sound's position with the `pos` method, and always return the reference to the sound when setting a sound that hasn't loaded. + +## 1.1.9 (July 11, 2013) +- `FIXED`: Fixed issue where calling the `volume` method before a sound had loaded prevented the volume from being changed. + +## 1.1.8 (July 10, 2013) +- `FIXED`: `urls` method now works again, and can take a string rather than an array if only one url is being passed. +- `FIXED`: Make `node.play` async when not using webAudio (thanks Alex Dong). + +## 1.1.7 (May 30, 2013) +- `FIXED`: Hotfix for a missing parameter that somehow missed the 1.1.6 commit in global muting. + +## 1.1.6 (May 30, 2013) +- `ADDED`: A general `fade` method that allows a playing sound to be faded from one volume to another. +- `DEPRECATED`: The `fadeIn` and `fadeOut` methods should no longer be used and have been deprecated. These will be removed in a future major release. +- `FIXED`: No longer require the sprite parameter to be passed into the `play` method when just passing a callback function. +- `FIXED`: Cleaned up global muting code. (thanks arnorhs). + +## 1.1.5 (May 3, 2013) +- `ADDED`: Support for the Ogg Opus codec (thanks Andrew Carpenter). +- `ADDED`: Semver tags for easy package management (thanks Martin Reurings). +- `ADDED`: Improve style/readability of code that discovers which audio file extension to use (thanks Fabien). +- `ADDED`: The `onend` event now passes the soundId back as the 2nd parameter of the callback (thanks Ross Cairns). +- `FIXED`: A few small typos in the comments. (thanks VAS). + +## 1.1.4 (April 28, 2013) +- `FIXED`: A few small bugs that broke global mute and unmute when using HTML5 Audio. + +## 1.1.3 (April 27, 2013) +- `FIXED`: Bug that prevented global mute from working 100% of the time when using HTML5 Audio. + +## 1.1.2 (April 24, 2013) +- `FIXED`: Calling `volume` before `play` now works as expected. +- `FIXED`: Edge case issue with cache cleaning. +- `FIXED`: Load event didn't fire when new URLs were loaded after the initial load. + +## 1.1.1 (April 17, 2013) +- `ADDED`: `onloaderror` event fired when sound fails to load (thanks Thiago de Barros Laceda). +- `ADDED`: `format` property that overrides the URL extraction of the file format (thanks Kenan Shifflett). +- `FIXED`: AMD implementation now only defines one module and removes global scope (thanks Kenan Shifflett). +- `FIXED`: Broken chaining with `play` method. + +## 1.1.0 (April 11, 2013) +- `ADDED:` New `pos3d` method that allows for positional audio (Web Audio API only). +- `ADDED:` Multi-playback control system that allows for control of specific play instances when sprites are used. A callback has been added to the `play` method that returns the `soundId` for the playback instance. This can then be passed as the optional last parameter to other methods to control that specific playback instead of the whole sound object. +- `ADDED:` Pass the `Howl` object reference as the first parameter in the custom event callbacks. +- `ADDED:` New optional parameter in sprite defintions to define a sprite as looping rather than the whole track. In the sprite definition array, set the 3rd value to true for looping (`spriteName: [pos, duration, loop]`). +- `FIXED:` Now all audio acts as a sound sprite internally, which helps to fix several lingering bugs (doesn't affect the API at all). +- `FIXED:` Improved implementation of Web Audio API looping. +- `FIXED:` Improved implementation of HTML5 Audio looping. +- `FIXED:` Issue that caused the fallback to not work when testing locally. +- `FIXED:` Fire `onend` event at the end of `fadeOut`. +- `FIXED:` Prevent errors from being thrown on browsers that don't support HTML5 Audio. +- `FIXED:` Various code cleanup and optimizations. + +## 1.0.13 (March 20, 2013) +- `ADDED:` Support for AMD loading as a module (thanks @mostlygeek). + +## 1.0.12 (March 28, 2013) +- `ADDED:` Automatically switch to HTML5 Audio if there is an error due to CORS. +- `FIXED:` Check that only numbers get passed into volume methods. + +## 1.0.11 (March 8, 2013) +- `ADDED:` Exposed `usingWebAudio` value through the global `Howler` object. +- `FIXED:` Issue with non-sprite HTML5 Audio clips becoming unplayable (thanks Paul Morris). + +## 1.0.10 (March 1, 2013) +- `FIXED:` Issue that caused simultaneous playback of audio sprites to break while using HTML5 Audio. + +## 1.0.9 (March 1, 2013) +- `ADDED:` Spec-implementation detection to cover new and deprecated Web Audio API methods (thanks @canuckistani). + +## 1.0.8 (February 25, 2013) +- `ADDED:` New `onplay` event. +- `ADDED:` Support for playing audio from base64 encoded strings. +- `FIXED:` Issue with soundId not being unique when multiple sounds were played simultaneously. +- `FIXED:` Verify that an HTML5 Audio Node is ready to play before playing it. +- `FIXED:` Issue with `onend` timer not getting cleared all the time. + +## 1.0.7 (February 18, 2013) +- `FIXED:` Cancel the correct timer when multiple HTML5 Audio sounds are played at the same time. +- `FIXED:` Make sure howler.js is future-compatible with UglifyJS 2. +- `FIXED:` Duration now gets set correctly when pulled from cache. +- `FIXED:` Tiny typo in README.md (thanks @johnfn). + +## 1.0.6 (February 8, 2013) +- `FIXED:` Issue with global mute calls happening before an HTML5 Audio element is loaded. + +## 1.0.5 (February 7, 2013) +- `FIXED:` Global mute now also mutes all future sounds that are played until `unmute` is called. + +## 1.0.4 (February 6, 2013) +- `ADDED:` Support for WebM audio. +- `FIXED:` Issue with volume changes when on HTML5 Audio. +- `FIXED:` Round volume values to fix inconsistencies in fade in/out methods. + +## 1.0.3 (February 2, 2013) +- `FIXED:` Make sure `self` is always defined before returning it. + +## 1.0.2 (February 1, 2013) +- `ADDED:` New `off` method that allows for the removal of custom events. +- `FIXED:` Issue with chaining the `on` method. +- `FIXED:` Small typo in documentation. + +## 1.0.1 (January 30, 2013) +- `ADDED:` New `buffer` property that allows you to force the use of HTML5 on specific sounds to allow streaming of large audio files. +- `ADDED:` Support for multiple events per event type. +- `FIXED:` Issue with method chaining before a sound was ready to play. +- `FIXED:` Use `self` everywhere instead of `this` to maintain consistency. + +## 1.0.0 (January 28, 2013) +- First commit
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/LICENSE.md b/howler.js-2.0.0-beta4/LICENSE.md new file mode 100644 index 0000000..cc37b73 --- /dev/null +++ b/howler.js-2.0.0-beta4/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2013-2014 James Simpson and GoldFire Studios, Inc. + +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.
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/README.md b/howler.js-2.0.0-beta4/README.md new file mode 100644 index 0000000..57ab23d --- /dev/null +++ b/howler.js-2.0.0-beta4/README.md @@ -0,0 +1,315 @@ + + +## Description +[**howler.js**](http://howlerjs.com) is an audio library for the modern web. It defaults to [Web Audio API](http://webaudio.github.io/web-audio-api/) and falls back to [HTML5 Audio](http://www.whatwg.org/specs/web-apps/current-work/#the-audio-element). + +More documentation, examples and demos can be found at **[howlerjs.com](http://howlerjs.com)**. + +### Features +* Defaults to Web Audio API +* Falls back to HTML5 Audio +* Supports multiple file formats to support all browsers +* Automatic caching for Web Audio API +* Implements cache pool for HTML5 Audio +* Per-sound and global mute/unmute and volume control +* Playback of multiple sounds at the same time +* Easy sound sprite definition and playback +* Fade in/out sounds +* Supports Web Audio 3D sound positioning +* Methods can be chained +* Uses no outside libraries, just pure Javascript +* Lightweight, 9kb filesize (3kb gzipped) + +### Browser Compatibility +Tested in the following browsers/versions: +* Google Chrome 7.0+ +* Internet Explorer 9.0+ +* Firefox 4.0+ +* Safari 5.1.4+ +* Mobile Safari 6.0+ (after user input) +* Opera 12.0+ + +## Documentation + +### Examples + +##### Most basic, play an MP3: +```javascript +var sound = new Howl({ + src: ['sound.mp3'] +}); + +sound.play(); +``` + +##### More playback options: +```javascript +var sound = new Howl({ + src: ['sound.ogg', 'sound.mp3', 'sound.wav'], + autoplay: true, + loop: true, + volume: 0.5, + onend: function() { + console.log('Finished!'); + } +}); +``` + +##### Define and play a sound sprite: +```javascript +var sound = new Howl({ + src: ['sounds.ogg', 'sounds.mp3'], + sprite: { + blast: [0, 1000], + laser: [2000, 3000], + winner: [4000, 7500] + } +}); + +// shoot the laser! +sound.play('laser'); +``` + + +### Core Properties +#### src `Array` `[]` *`required`* +The sources to the track(s) to be loaded for the sound (URLs or base64 data URIs). These should be in order of preference, howler.js will automatically load the first one that is compatible with the current browser. If your files have no extensions, you will need to explicitly specify the extension using the `ext` property. +#### volume `Number` `1.0` +The volume of the specific track, from `0.0` to `1.0`. +#### html5 `Boolean` `false` +Set to `true` to force HTML5 Audio. This should be used for large audio files so that you don't have to wait for the full file to be downloaded and decoded before playing. +#### loop `Boolean` `false` +Set to `true` to automatically loop the sound forever. +#### preload `Boolean` `true` +Automatically begin downloading the audio file when the `Howl` is defined. +#### autoplay `Boolean` `false` +Set to `true` to automatically start playback when sound is loaded. +#### mute `Boolean` `false` +Set to `true` to load the audio muted. +#### sprite `Object` `{}` +Define a sound sprite for the sound. The offset and duration are defined in milliseconds. A third (optional) parameter is available to set a sprite as looping. An easy way to generate compatible sound sprites is with [audiosprite](https://github.com/tonistiigi/audiosprite). +```javascript +{ + key: [offset, duration, (loop)] +} +``` +#### rate `Number` `1.0` +The rate of playback. 0.5 to 4.0, with 1.0 being normal speed. +#### pool `Number` `5` +The size of the inactive sounds pool. Once sounds are stopped or finish playing, they are marked as ended and ready for cleanup. We keep a pool of these to recycle for improved performance. Generally this doesn't need to be changed. It is important to keep in mind that when a sound is paused, it won't be removed from the pool and will still be considered active so that it can be resumed later. +#### ext `Array` `[]` +howler.js automatically detects your file format from the extension, but you may also specify a format in situations where extraction won't work (such as with a SoundCloud stream). +#### onload `Function` +Fires when the sound is loaded. +#### onloaderror `Function` +Fires when the sound is unable to load. The first parameter is the ID of the sound (if it exists) and the second is the error message/code. +#### onplay `Function` +Fires when the sound begins playing. The first parameter is the ID of the sound. +#### onend `Function` +Fires when the sound finishes playing (if it is looping, it'll fire at the end of each loop). The first parameter is the ID of the sound. +#### onpause `Function` +Fires when the sound has been paused. The first parameter is the ID of the sound. +#### onstop `Function` +Fires when the sound has been stopped. The first parameter is the ID of the sound. +#### onfaded `Function` +Fires when the current sound finishes fading in/out. The first parameter is the ID of the sound. + + +### Core Methods +#### play([sprite/id]) +Begins playback of a sound. Returns the sound id to be used with other methods. Only method that can't be chained. +* **sprite/id**: `String/Number` `optional` Takes one parameter that can either be a sprite or sound ID. If a sprite is passed, a new sound will play based on the sprite's definition. If a sound ID is passed, the previously played sound will be played (for example, after puasing it). However, if an ID of a sound that has been drained from the pool is passed, nothing will play. + +#### pause([id]) +Pauses playback of sound or group, saving the `seek` of playback. +* **id**: `Number` `optional` The sound ID. If none is passed, all sounds in group are puased. + +#### stop([id]) +Stops playback of sound, resetting `seek` to `0`. +* **id**: `Number` `optional` The sound ID. If none is passed, all sounds in group are stopped. + +#### mute([muted], [id]) +Mutes the sound, but doesn't pause the playback. +* **muted**: `Boolean` `optional` True to mute and false to unmute. +* **id**: `Number` `optional` The sound ID. If none is passed, all sounds in group are stopped. + +#### volume([volume], [id]) +Get/set volume of this sound or the group. This method optionally takes 0, 1 or 2 arguments. +* **volume**: `Number` `optional` Volume from `0.0` to `1.0`. +* **id**: `Number` `optional` The sound ID. If none is passed, all sounds in group have volume altered relative to their own volume. + +#### fade(from, to, duration, [id]) +Fade a currently playing sound between two volumes. Fires the `faded` event when complete. +* **from**: `Number` Volume to fade from (`0.0` to `1.0`). +* **to**: `Number` Volume to fade to (`0.0` to `1.0`). +* **duration**: `Number` Time in milliseconds to fade. +* **id**: `Number` `optional` The sound ID. If none is passed, all sounds in group will fade. + +#### rate([rate], [id]) +Get/set the rate of playback for a sound. This method optionally takes 0, 1 or 2 arguments. +* **rate**: `Number` `optional` The rate of playback. 0.5 to 4.0, with 1.0 being normal speed. +* **id**: `Number` `optional` The sound ID. If none is passed, playback rate of all sounds in group will change. + +#### seek([seek], [id]) +Get/set the position of playback for a sound. This method optionally takes 0, 1 or 2 arguments. +* **seek**: `Number` `optional` The position to move current playback to (in seconds). +* **id**: `Number` `optional` The sound ID. If none is passed, the first sound will seek. + +#### loop([loop], [id]) +Get/set whether to loop the sound or group. This method can optionally take 0, 1 or 2 arguments. +* **loop**: `Boolean` `optional` To loop or not to loop, that is the question. +* **id**: `Number` `optional` The sound ID. If none is passed, all sounds in group will have their `loop` property updated. + +#### playing(id) +Check if a sound is currently playing or not, returns a `Boolean`. +* **id**: `Number` The sound ID to check. + +#### duration() +Get the duration of the audio source. Will return 0 until after the `load` event fires. + +#### on(event, function, [id]) +Listen for events. Multiple events can be added by calling this multiple times. +* **event**: `String` Name of event to fire/set (`load`, `loaderror`, `play`, `end`, `pause`, `stop`, `faded`). +* **function**: `Function` Define function to fire on event. +* **id**: `Number` `optional` Only listen to events for this sound id. + +#### once(event, function, [id]) +Same as `on`, but it removes itself after the callback is fired. +* **event**: `String` Name of event to fire/set (`load`, `loaderror`, `play`, `end`, `pause`, `stop`, `faded`). +* **function**: `Function` Define function to fire on event. +* **id**: `Number` `optional` Only listen to events for this sound id. + +#### off(event, [function], [id]) +Remove event listener that you've set. +* **event**: `String` Name of event (`load`, `loaderror`, `play`, `end`, `pause`, `stop`, `faded`). +* **function**: `Function` `optional` The listener to remove. Omit this to remove all events of type. +* **id**: `Number` `optional` Only remove events for this sound id. + +#### load() +This is called by default, but if you set `preload` to false, you must call `load` before you can play any sounds. + +#### unload() +Unload and destroy a Howl object. This will immediately stop all sounds attached to this sound and remove it from the cache. + +### Global Core Methods +The following methods are used to modify all sounds globally, and are called from the `Howler` object. +#### mute(muted) +Mute or unmute all sounds. +* **muted**: `Boolean` True to mute and false to unmute. + +#### volume([volume]) +Get/set the global volume for all sounds, relative to their own volume. +* **volume**: `Number` `optional` Volume from `0.0` to `1.0`. + +#### codecs(ext) +Check supported audio codecs. Returns `true` if the codec is supported in the current browser. +* **ext**: `String` File extension. One of: "mp3", "opus", "ogg", "wav", "aac", "m4a", "mp4", "weba". + +#### unload() +Unload and destroy all currently loaded Howl objects. This will immediately stop all sounds and remove them from cache. + + +### Global Core Properties +#### usingWebAudio `Boolean` +`true` if the Web Audio API is available. +#### noAudio `Boolean` +`true` if any audio is available. +#### mobileAutoEnable `Boolean` `true` +Automatically attempts to enable audio on mobile (iOS, Android, etc) devices. +#### ctx `Boolean` *`Web Audio Only`* +Exposes the `AudioContext` with Web Audio API. + + +### Plugin: Effects Methods +#### pos(x, y, z, [id]) +Get/set the 3D spatial position of the audio source for this sound or group. The most common usage is to set the `x` position for left/right panning. Setting any value higher than `1.0` will begin to decrease the volume of the sound as it moves further away. +* **x**: `Number` The x-position of the audio from `-1000.0` to `1000.0`. +* **y**: `Number` The y-position of the audio from `-1000.0` to `1000.0`. +* **z**: `Number` The z-position of the audio from `-1000.0` to `1000.0`. +* **id**: `Number` `optional` The sound ID. If none is passed, all in group will be updated. + +#### orientation(x, y, z, [id]) +Get/set the direction the audio source is pointing in the 3D cartesian coordinate space. Depending on how direction the sound is, based on the `cone` attributes, a sound pointing away from the listener can be quiet or silent. +* **x**: `Number` The x-orientation of the source. +* **y**: `Number` The y-orientation of the source. +* **z**: `Number` The z-orientation of the source. +* **id**: `Number` `optional` The sound ID. If none is passed, all in group will be updated. + +#### velocity(x, y, z, [id]) +Get/set the velocity vector of the audio source or group. This controls both direction and speed in 3D space and is relative to the listener's velocity. The units are meters/second and are independent of position and orientation. +* **x**: `Number` The x-velocity of the source. +* **y**: `Number` The y-velocity of the source. +* **z**: `Number` The z-velocity of the source. +* **id**: `Number` `optional` The sound ID. If none is passed, all in group will be updated. + +#### pannerAttr(o, [id]) +Get/set the panner node's attributes for a sound or group of sounds. This method can optionall take 0, 1 or 2 arguments. +* **o**: `Object` All values to update. + * **coneInnerAngle** `360` There will be no volume reduction inside this angle. + * **coneOUterAngle** `360` The volume will be reduced to a constant value of `coneOuterGain` outside this angle. + * **coneOuterGain** `0` The amount of volume reduction outside of `coneOuterAngle`. + * **distanceModel** `inverse` Determines algorithm to use to reduce volume as audio moves away from listener. Can be `linear`, `inverse` or `exponential. + * **maxDistance** `10000` Volume won't reduce between source/listener beyond this distance. + * **panningModel** `HRTF` Determines which spatialization algorithm is used to position audio. Can be `HRTF` or `equalpower`. + * **refDistance** `1` A reference distance for reducing volume as the source moves away from the listener. + * **rolloffFactor** `1` How quickly the volume reduces as source moves from listener. +* **id**: `Number` `optional` The sound ID. If none is passed, all in group will be updated. + + +### Plugin: Effects Properties +#### orientation `Array` `[1, 0, 0]` +Sets the direction the audio source is pointing in the 3D cartesian coordinate space. Depending on how direction the sound is, based on the `cone` attributes, a sound pointing away from the listener can be quiet or silent. +#### pos `Array` `null` +Sets the 3D spatial position of the audio source for this sound or group. The most common usage is to set the `x` position for left/right panning. Setting any value higher than `1.0` will begin to decrease the volume of the sound as it moves further away. +#### velocity `Array` `[0, 0, 0]` +Sets the velocity vector of the audio source or group. This controls both direction and speed in 3D space and is relative to the listener's velocity. The units are meters/second and are independent of position and orientation. +#### pannerAttr `Object` +Sets the panner node's attributes for a sound or group of sounds. See the `pannerAttr` method for all available options. + + +### Plugin: Global Effects Methods +#### pos(x, y, z) +Get/set the position of the listener in 3D cartesian space. Sounds using 3D position will be relative to the listener's position. +* **x**: `Number` The x-position of the listener. +* **y**: `Number` The y-position of the listener. +* **z**: `Number` The z-position of the listener. + +#### orientation(x, y, z, xUp, yUp, zUp) +Get/set the direction the listener is pointing in the 3D cartesian space. A front and up vector must be provided. The front is the direction the face of the listener is pointing, and up is the direction the top of the listener is pointing. Thus, these values are expected to be at right angles from each other. +* **x**: `Number` The x-orientation of listener. +* **y**: `Number` The y-orientation of listener. +* **z**: `Number` The z-orientation of listener. +* **xUp**: `Number` The x-orientation of the top of the listener. +* **yUp**: `Number` The y-orientation of the top of the listener. +* **zUp**: `Number` The z-orientation of the top of the listener. + +#### velocity(x, y, z) +Get/set the velocity vector of the listener. This controls both direction and speed in 3D space, and is combined relative to a sound's velocity to determine how much doppler shift (pitch change) to apply. +* **x**: `Number` The x-velocity of listener. +* **y**: `Number` The y-velocity of listener. +* **z**: `Number` The z-velocity of listener. + +#### pannerAttr(o) +Get/set the audio listener attributes. +* **o**: `Object` All values to update. + * **dopplerFactor** `1` Determines the amount of pitch shift from doppler effect. + * **speedOfSound** `343.3` Speed of sound used to calculate doppler shift. + + +### Mobile Playback +By default, audio on iOS, Android, etc is locked until a sound is played within a user interaction, and then it plays normally the rest of the page session ([Apple documentation](https://developer.apple.com/library/safari/documentation/audiovideo/conceptual/using_html5_audio_video/PlayingandSynthesizingSounds/PlayingandSynthesizingSounds.html)). The default behavior of howler.js is to attempt to silently unlock audio playback by playing an empty buffer on the first `touchend` event. This behavior can be disabled by calling: + +```javascript +Howler.mobileAutoEnable = false; +``` + +### Format Recommendations +Howler.js supports a wide array of audio codecs that have varying browser support ("mp3", "opus", "ogg", "wav", "aac", "m4a", "mp4", "weba", ...), but if you want full browser coverage you still need to use at least two of them. If your goal is to have the best balance of small filesize and high quality, based on extensive production testing, your best bet is to default to `ogg/webm` and fallback to `mp3`. Both `ogg` and `webm` have nearly full browser coverage with a great combination of compression and quality. You'll need the `mp3` fallback for Internet Explorer. + +It is important to remember that howler.js selects the first compatible sound from your array of sources. So if you want `ogg` or `webm` to be used before `mp3`, you need to put the sources in that order. + +### License + +Copyright (c) 2013-2015 James Simpson and GoldFire Studios, Inc. + +Released under the MIT License.
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/bower.json b/howler.js-2.0.0-beta4/bower.json new file mode 100644 index 0000000..0ee4978 --- /dev/null +++ b/howler.js-2.0.0-beta4/bower.json @@ -0,0 +1,6 @@ +{ + "name": "howler.js", + "version": "2.0.0-beta4", + "description": "Javascript audio library for the modern web.", + "main": "howler.min.js" +} diff --git a/howler.js-2.0.0-beta4/howler.core.min.js b/howler.js-2.0.0-beta4/howler.core.min.js new file mode 100644 index 0000000..272bd61 --- /dev/null +++ b/howler.js-2.0.0-beta4/howler.core.min.js @@ -0,0 +1,2 @@ +/*! howler.js v2.0.0-beta4 | (c) 2013-2015, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";function e(){try{"undefined"!=typeof AudioContext?n=new AudioContext:"undefined"!=typeof webkitAudioContext?n=new webkitAudioContext:o=!1}catch(e){o=!1}if(!o)if("undefined"!=typeof Audio)try{new Audio}catch(e){t=!0}else t=!0;try{var r=new Audio;r.muted&&(t=!0)}catch(e){}}var n=null,o=!0,t=!1;if(e(),o){var r="undefined"==typeof n.createGain?n.createGainNode():n.createGain();r.gain.value=1,r.connect(n.destination)}var d=function(){this.init()};d.prototype={init:function(){var e=this||u;return e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e.mobileAutoEnable=!0,e.noAudio=t,e.usingWebAudio=o,e.ctx=n,t||e._setupCodecs(),e},volume:function(e){var n=this||u;if(e=parseFloat(e),"undefined"!=typeof e&&e>=0&&1>=e){n._volume=e,o&&(r.gain.value=e);for(var t=0;t<n._howls.length;t++)if(!n._howls[t]._webAudio)for(var d=n._howls[t]._getSoundIds(),a=0;a<d.length;a++){var i=n._howls[t]._soundById(d[a]);i&&i._node&&(i._node.volume=i._volume*e)}return n}return n._volume},mute:function(e){var n=this||u;n._muted=e,o&&(r.gain.value=e?0:n._volume);for(var t=0;t<n._howls.length;t++)if(!n._howls[t]._webAudio)for(var d=n._howls[t]._getSoundIds(),a=0;a<d.length;a++){var i=n._howls[t]._soundById(d[a]);i&&i._node&&(i._node.muted=e?!0:i._muted)}return n},unload:function(){for(var e=this||u,n=e._howls.length-1;n>=0;n--)e._howls[n].unload();return e},codecs:function(e){return(this||u)._codecs[e]},_setupCodecs:function(){var e=this||u,n=new Audio,o=n.canPlayType("audio/mpeg;").replace(/^no$/,""),t=/OPR\//.test(navigator.userAgent);return e._codecs={mp3:!(t||!o&&!n.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!o,opus:!!n.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!n.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),aac:!!n.canPlayType("audio/aac;").replace(/^no$/,""),m4a:!!(n.canPlayType("audio/x-m4a;")||n.canPlayType("audio/m4a;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(n.canPlayType("audio/x-mp4;")||n.canPlayType("audio/mp4;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),webm:!!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")},e},_enableMobileAudio:function(){var e=this||u,o=/iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk/i.test(navigator.userAgent),t=!!("ontouchend"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0);if(!n||!e._mobileEnabled&&o&&t){e._mobileEnabled=!1;var r=function(){var o=n.createBuffer(1,1,22050),t=n.createBufferSource();t.buffer=o,t.connect(n.destination),"undefined"==typeof t.start?t.noteOn(0):t.start(0),t.onended=function(){e._mobileEnabled=!0,e.mobileAutoEnable=!1,document.removeEventListener("touchend",r,!0)}};return document.addEventListener("touchend",r,!0),e}}};var u=new d,a=function(e){var n=this;return e.src&&0!==e.src.length?void n.init(e):void console.error("An array of source files must be passed with any new Howl.")};a.prototype={init:function(e){var t=this;return t._autoplay=e.autoplay||!1,t._ext=e.ext||null,t._html5=e.html5||!1,t._muted=e.mute||!1,t._loop=e.loop||!1,t._pool=e.pool||5,t._preload="boolean"==typeof e.preload?e.preload:!0,t._rate=e.rate||1,t._sprite=e.sprite||{},t._src="string"!=typeof e.src?e.src:[e.src],t._volume=void 0!==e.volume?e.volume:1,t._duration=0,t._loaded=!1,t._sounds=[],t._endTimers={},t._onend=e.onend?[{fn:e.onend}]:[],t._onfaded=e.onfaded?[{fn:e.onfaded}]:[],t._onload=e.onload?[{fn:e.onload}]:[],t._onloaderror=e.onloaderror?[{fn:e.onloaderror}]:[],t._onpause=e.onpause?[{fn:e.onpause}]:[],t._onplay=e.onplay?[{fn:e.onplay}]:[],t._onstop=e.onstop?[{fn:e.onstop}]:[],t._webAudio=o&&!t._html5,"undefined"!=typeof n&&n&&u.mobileAutoEnable&&u._enableMobileAudio(),u._howls.push(t),t._preload&&t.load(),t},load:function(){var e=this,n=null;if(t)return void e._emit("loaderror",null,"No audio support.");"string"==typeof e._src&&(e._src=[e._src]);for(var o=0;o<e._src.length;o++){var r,d;if(e._ext&&e._ext[o]?r=e._ext[o]:(d=e._src[o],r=/^data:audio\/([^;,]+);/i.exec(d),r||(r=/\.([^.]+)$/.exec(d.split("?",1)[0])),r&&(r=r[1].toLowerCase())),u.codecs(r)){n=e._src[o];break}}return n?(e._src=n,new i(e),e._webAudio&&s(e),e):void e._emit("loaderror",null,"No codec support for selected audio sources.")},play:function(e){var o=this,t=arguments,r=null;if("number"==typeof e)r=e,e=null;else if("undefined"==typeof e){e="__default";for(var d=0,a=0;a<o._sounds.length;a++)o._sounds[a]._paused&&!o._sounds[a]._ended&&(d++,r=o._sounds[a]._id);1===d?e=null:r=null}var i=r?o._soundById(r):o._inactiveSound();if(!i)return null;if(r&&!e&&(e=i._sprite||"__default"),!o._loaded&&!o._sprite[e])return o.once("load",function(){o.play(o._soundById(i._id)?i._id:void 0)}),i._id;if(r&&!i._paused)return i._id;var _=i._seek>0?i._seek:o._sprite[e][0]/1e3,s=(o._sprite[e][0]+o._sprite[e][1])/1e3-_,l=1e3*s/Math.abs(i._rate);o._endTimers[i._id]=setTimeout(o._ended.bind(o,i),l),i._paused=!1,i._ended=!1,i._sprite=e,i._seek=_,i._start=o._sprite[e][0]/1e3,i._stop=(o._sprite[e][0]+o._sprite[e][1])/1e3,i._loop=!(!i._loop&&!o._sprite[e][2]);var f=i._node;if(o._webAudio){var c=function(){o._refreshBuffer(i);var e=i._muted||o._muted?0:i._volume*u.volume();f.gain.setValueAtTime(e,n.currentTime),i._playStart=n.currentTime,"undefined"==typeof f.bufferSource.start?i._loop?f.bufferSource.noteGrainOn(0,_,86400):f.bufferSource.noteGrainOn(0,_,s):i._loop?f.bufferSource.start(0,_,86400):f.bufferSource.start(0,_,s),o._endTimers[i._id]||(o._endTimers[i._id]=setTimeout(o._ended.bind(o,i),l)),t[1]||setTimeout(function(){o._emit("play",i._id)},0)};o._loaded?c():(o.once("load",c),o._clearTimer(i._id))}else{var p=function(){f.currentTime=_,f.muted=i._muted||o._muted||u._muted||f.muted,f.volume=i._volume*u.volume(),f.playbackRate=i._rate,setTimeout(function(){f.play(),t[1]||o._emit("play",i._id)},0)};if(4===f.readyState||!f.readyState&&navigator.isCocoonJS)p();else{var m=function(){o._endTimers[i._id]=setTimeout(o._ended.bind(o,i),l),p(),f.removeEventListener("canplaythrough",m,!1)};f.addEventListener("canplaythrough",m,!1),o._clearTimer(i._id)}}return i._id},pause:function(e){var n=this;if(!n._loaded)return n.once("play",function(){n.pause(e)}),n;for(var o=n._getSoundIds(e),t=0;t<o.length;t++){n._clearTimer(o[t]);var r=n._soundById(o[t]);if(r&&!r._paused){if(r._seek=n.seek(o[t]),r._paused=!0,n._stopFade(o[t]),n._webAudio){if(!r._node.bufferSource)return n;"undefined"==typeof r._node.bufferSource.stop?r._node.bufferSource.noteOff(0):r._node.bufferSource.stop(0),r._node.bufferSource=null}else isNaN(r._node.duration)||r._node.pause();arguments[1]||n._emit("pause",r._id)}}return n},stop:function(e){var n=this;if(!n._loaded)return"undefined"!=typeof n._sounds[0]._sprite&&n.once("play",function(){n.stop(e)}),n;for(var o=n._getSoundIds(e),t=0;t<o.length;t++){n._clearTimer(o[t]);var r=n._soundById(o[t]);if(r&&!r._paused){if(r._seek=r._start||0,r._paused=!0,r._ended=!0,n._stopFade(o[t]),n._webAudio&&r._node){if(!r._node.bufferSource)return n;"undefined"==typeof r._node.bufferSource.stop?r._node.bufferSource.noteOff(0):r._node.bufferSource.stop(0),r._node.bufferSource=null}else r._node&&!isNaN(r._node.duration)&&(r._node.pause(),r._node.currentTime=r._start||0);n._emit("stop",r._id)}}return n},mute:function(e,o){var t=this;if(!t._loaded)return t.once("play",function(){t.mute(e,o)}),t;if("undefined"==typeof o){if("boolean"!=typeof e)return t._muted;t._muted=e}for(var r=t._getSoundIds(o),d=0;d<r.length;d++){var a=t._soundById(r[d]);a&&(a._muted=e,t._webAudio&&a._node?a._node.gain.setValueAtTime(e?0:a._volume*u.volume(),n.currentTime):a._node&&(a._node.muted=u._muted?!0:e))}return t},volume:function(){var e,o,t=this,r=arguments;if(0===r.length)return t._volume;if(1===r.length){var d=t._getSoundIds(),a=d.indexOf(r[0]);a>=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var i;if(!("undefined"!=typeof e&&e>=0&&1>=e))return i=o?t._soundById(o):t._sounds[0],i?i._volume:0;if(!t._loaded)return t.once("play",function(){t.volume.apply(t,r)}),t;"undefined"==typeof o&&(t._volume=e),o=t._getSoundIds(o);for(var _=0;_<o.length;_++)i=t._soundById(o[_]),i&&(i._volume=e,r[2]||t._stopFade(o[_]),t._webAudio&&i._node&&!i._muted?i._node.gain.setValueAtTime(e*u.volume(),n.currentTime):i._node&&!i._muted&&(i._node.volume=e*u.volume()));return t},fade:function(e,o,t,r){var d=this;if(!d._loaded)return d.once("play",function(){d.fade(e,o,t,r)}),d;d.volume(e,r);for(var u=d._getSoundIds(r),a=0;a<u.length;a++){var i=d._soundById(u[a]);if(i)if(d._webAudio&&!i._muted){var _=n.currentTime,s=_+t/1e3;i._volume=e,i._node.gain.setValueAtTime(e,_),i._node.gain.linearRampToValueAtTime(o,s),i._timeout=setTimeout(function(e,t){delete t._timeout,setTimeout(function(){t._volume=o,d._emit("faded",e)},s-n.currentTime>0?Math.ceil(1e3*(s-n.currentTime)):0)}.bind(d,u[a],i),t)}else{var l=Math.abs(e-o),f=e>o?"out":"in",c=l/.01,p=t/c;!function(){var n=e;i._interval=setInterval(function(e,t){n+="in"===f?.01:-.01,n=Math.max(0,n),n=Math.min(1,n),n=Math.round(100*n)/100,d.volume(n,e,!0),n===o&&(clearInterval(t._interval),delete t._interval,d._emit("faded",e))}.bind(d,u[a],i),p)}()}}return d},_stopFade:function(e){var o=this,t=o._soundById(e);return t._interval?(clearInterval(t._interval),delete t._interval,o._emit("faded",e)):t._timeout&&(clearTimeout(t._timeout),delete t._timeout,t._node.gain.cancelScheduledValues(n.currentTime),o._emit("faded",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return o=t._soundById(parseInt(r[0],10)),o?o._loop:!1;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var d=t._getSoundIds(n),u=0;u<d.length;u++)o=t._soundById(d[u]),o&&(o._loop=e,t._webAudio&&o._node&&o._node.bufferSource&&(o._node.bufferSource.loop=e));return t},rate:function(){var e,n,o=this,t=arguments;if(0===t.length)n=o._sounds[0]._id;else if(1===t.length){var r=o._getSoundIds(),d=r.indexOf(t[0]);d>=0?n=parseInt(t[0],10):e=parseFloat(t[0])}else 2===t.length&&(e=parseFloat(t[0]),n=parseInt(t[1],10));var u;if("number"!=typeof e)return u=o._soundById(n),u?u._rate:o._rate;if(!o._loaded)return o.once("load",function(){o.rate.apply(o,t)}),o;"undefined"==typeof n&&(o._rate=e),n=o._getSoundIds(n);for(var a=0;a<n.length;a++)if(u=o._soundById(n[a])){u._rate=e,o._webAudio&&u._node&&u._node.bufferSource?u._node.bufferSource.playbackRate.value=e:u._node&&(u._node.playbackRate=e);var i=o.seek(n[a]),_=(o._sprite[u._sprite][0]+o._sprite[u._sprite][1])/1e3-i,s=1e3*_/Math.abs(u._rate);o._clearTimer(n[a]),o._endTimers[n[a]]=setTimeout(o._ended.bind(o,u),s)}return o},seek:function(){var e,o,t=this,r=arguments;if(0===r.length)o=t._sounds[0]._id;else if(1===r.length){var d=t._getSoundIds(),u=d.indexOf(r[0]);u>=0?o=parseInt(r[0],10):(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if("undefined"==typeof o)return t;if(!t._loaded)return t.once("load",function(){t.seek.apply(t,r)}),t;var a=t._soundById(o);if(a){if(!(e>=0))return t._webAudio?a._seek+(t.playing(o)?n.currentTime-a._playStart:0):a._node.currentTime;var i=t.playing(o);i&&t.pause(o,!0),a._seek=e,t._clearTimer(o),i&&t.play(o,!0)}return t},playing:function(e){var n=this,o=n._soundById(e)||n._sounds[0];return o?!o._paused:!1},duration:function(){return this._duration},unload:function(){for(var e=this,n=e._sounds,o=0;o<n.length;o++){n[o]._paused||(e.stop(n[o]._id),e._emit("end",n[o]._id)),e._webAudio||(n[o]._node.src="",n[o]._node.removeEventListener("error",n[o]._errorFn,!1),n[o]._node.removeEventListener("canplaythrough",n[o]._loadFn,!1)),delete n[o]._node,e._clearTimer(n[o]._id);var t=u._howls.indexOf(e);t>=0&&u._howls.splice(t,1)}return _&&delete _[e._src],e=null,null},on:function(e,n,o,t){var r=this,d=r["_on"+e];return"function"==typeof n&&d.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e];if(n){for(var d=0;d<r.length;d++)if(n===r[d].fn&&o===r[d].id){r.splice(d,1);break}}else t["on"+e]=[];return t},once:function(e,n,o){var t=this;return t.on(e,n,o,1),t},_emit:function(e,n,o){for(var t=this,r=t["_on"+e],d=0;d<r.length;d++)r[d].id&&r[d].id!==n||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[d].fn),0),r[d].once&&t.off(e,r[d].fn,r[d].id));return t},_ended:function(e){var o=this,t=e._sprite,r=!(!e._loop&&!o._sprite[t][2]);if(o._emit("end",e._id),!o._webAudio&&r&&o.stop(e._id).play(e._id),o._webAudio&&r){o._emit("play",e._id),e._seek=e._start||0,e._playStart=n.currentTime;var d=1e3*(e._stop-e._start)/Math.abs(e._rate);o._endTimers[e._id]=setTimeout(o._ended.bind(o,e),d)}return o._webAudio&&!r&&(e._paused=!0,e._ended=!0,e._seek=e._start||0,o._clearTimer(e._id),e._node.bufferSource=null),o._webAudio||r||o.stop(e._id),o},_clearTimer:function(e){var n=this;return n._endTimers[e]&&(clearTimeout(n._endTimers[e]),delete n._endTimers[e]),n},_soundById:function(e){for(var n=this,o=0;o<n._sounds.length;o++)if(e===n._sounds[o]._id)return n._sounds[o];return null},_inactiveSound:function(){var e=this;e._drain();for(var n=0;n<e._sounds.length;n++)if(e._sounds[n]._ended)return e._sounds[n].reset();return new i(e)},_drain:function(){var e=this,n=e._pool,o=0,t=0;if(!(e._sounds.length<n)){for(t=0;t<e._sounds.length;t++)e._sounds[t]._ended&&o++;for(t=e._sounds.length-1;t>=0;t--){if(n>=o)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if("undefined"==typeof e){for(var o=[],t=0;t<n._sounds.length;t++)o.push(n._sounds[t]._id);return o}return[e]},_refreshBuffer:function(e){var o=this;return e._node.bufferSource=n.createBufferSource(),e._node.bufferSource.buffer=_[o._src],e._node.bufferSource.connect(e._panner?e._panner:e._node),e._node.bufferSource.loop=e._loop,e._loop&&(e._node.bufferSource.loopStart=e._start||0,e._node.bufferSource.loopEnd=e._stop),e._node.bufferSource.playbackRate.value=o._rate,o}};var i=function(e){this._parent=e,this.init()};if(i.prototype={init:function(){var e=this,n=e._parent;return e._muted=n._muted,e._loop=n._loop,e._volume=n._volume,e._muted=n._muted,e._rate=n._rate,e._seek=0,e._paused=!0,e._ended=!0,e._id=Math.round(Date.now()*Math.random()),n._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=u._muted||e._muted||e._parent._muted?0:e._volume*u.volume();return o._webAudio?(e._node="undefined"==typeof n.createGain?n.createGainNode():n.createGain(),e._node.gain.setValueAtTime(t,n.currentTime),e._node.paused=!0,e._node.connect(r)):(e._node=new Audio,e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener("canplaythrough",e._loadFn,!1),e._node.src=o._src,e._node.preload="auto",e._node.volume=t,e._node.load()),e},reset:function(){var e=this,n=e._parent;return e._muted=n._muted,e._loop=n._loop,e._volume=n._volume,e._muted=n._muted,e._rate=n._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite=null,e._id=Math.round(Date.now()*Math.random()),e},_errorListener:function(){var e=this;e._node.error&&4===e._node.error.code&&(u.noAudio=!0),e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorListener,!1)},_loadListener:function(){var e=this,n=e._parent;n._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(n._sprite).length&&(n._sprite={__default:[0,1e3*n._duration]}),n._loaded||(n._loaded=!0,n._emit("load")),n._autoplay&&n.play(),e._node.removeEventListener("canplaythrough",e._loadFn,!1)}},o)var _={},s=function(e){var n=e._src;if(_[n])return e._duration=_[n].duration,void c(e);if(/^data:[^;]+;base64,/.test(n)){window.atob=window.atob||function(e){for(var n,o,t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=String(e).replace(/=+$/,""),d=0,u=0,a="";o=r.charAt(u++);~o&&(n=d%4?64*n+o:o,d++%4)?a+=String.fromCharCode(255&n>>(-2*d&6)):0)o=t.indexOf(o);return a};for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),r=0;r<o.length;++r)t[r]=o.charCodeAt(r);f(t.buffer,e)}else{var d=new XMLHttpRequest;d.open("GET",n,!0),d.responseType="arraybuffer",d.onload=function(){f(d.response,e)},d.onerror=function(){e._webAudio&&(e._html5=!0,e._webAudio=!1,e._sounds=[],delete _[n],e.load())},l(d)}},l=function(e){try{e.send()}catch(n){e.onerror()}},f=function(e,o){n.decodeAudioData(e,function(e){e&&(_[o._src]=e,c(o,e))},function(){o._emit("loaderror",null,"Decoding audio data failed.")})},c=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),e._loaded||(e._loaded=!0,e._emit("load")),e._autoplay&&e.play()};"function"==typeof define&&define.amd&&define("howler",function(){return{Howler:u,Howl:a}}),"undefined"!=typeof exports&&(exports.Howler=u,exports.Howl=a),"undefined"!=typeof window&&(window.HowlerGlobal=d,window.Howler=u,window.Howl=a,window.Sound=i)}();
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/howler.effects.min.js b/howler.js-2.0.0-beta4/howler.effects.min.js new file mode 100644 index 0000000..e278759 --- /dev/null +++ b/howler.js-2.0.0-beta4/howler.effects.min.js @@ -0,0 +1,2 @@ +/*! howler.js v2.0.0-beta4 | Effects Plugin | (c) 2013-2015, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";HowlerGlobal.prototype.init=function(e){return function(){var n=this;return n._pos=[0,0,0],n._orientation=[0,0,-1,0,1,0],n._velocity=[0,0,0],n._listenerAttr={dopplerFactor:1,speedOfSound:343.3},e.call(this,o)}}(HowlerGlobal.prototype.init),HowlerGlobal.prototype.pos=function(e,n,t){var o=this;return o.ctx&&o.ctx.listener?(n="number"!=typeof n?o._pos[1]:n,t="number"!=typeof t?o._pos[2]:t,"number"!=typeof e?o._pos:(o._pos=[e,n,t],o.ctx.listener.setPosition(o._pos[0],o._pos[1],o._pos[2]),o)):o},HowlerGlobal.prototype.orientation=function(e,n,t,o,r,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var p=a._orientation;return n="number"!=typeof n?p[1]:n,t="number"!=typeof t?p[2]:t,o="number"!=typeof o?p[3]:o,r="number"!=typeof r?p[4]:r,i="number"!=typeof i?p[5]:i,"number"!=typeof e?p:(a._orientation=[e,n,t,o,r,i],a.ctx.listener.setOrientation(p[0],p[1],p[2],p[3],p[4],p[5]),a)},HowlerGlobal.prototype.velocity=function(e,n,t){var o=this;return o.ctx&&o.ctx.listener?(n="number"!=typeof n?o._velocity[1]:n,t="number"!=typeof t?o._velocity[2]:t,"number"!=typeof e?o._velocity:(o._velocity=[e,n,t],o.ctx.listener.setVelocity(o._velocity[0],o._velocity[1],o._velocity[2]),o)):o},HowlerGlobal.prototype.listenerAttr=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;var t=n._listenerAttr;return e?(n._listenerAttr={dopplerFactor:"undefined"!=typeof e.dopplerFactor?e.dopplerFactor:t.dopplerFactor,speedOfSound:"undefined"!=typeof e.speedOfSound?e.speedOfSound:t.speedOfSound},n.ctx.listener.dopplerFactor=t.dopplerFactor,n.ctx.listener.speedOfSound=t.speedOfSound,n):t},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._pos=n.pos||null,t._velocity=n.velocity||[0,0,0],t._pannerAttr={coneInnerAngle:"undefined"!=typeof n.coneInnerAngle?n.coneInnerAngle:360,coneOUterAngle:"undefined"!=typeof n.coneOUterAngle?n.coneOUterAngle:360,coneOuterGain:"undefined"!=typeof n.coneOuterGain?n.coneOuterGain:0,distanceModel:"undefined"!=typeof n.distanceModel?n.distanceModel:"inverse",maxDistance:"undefined"!=typeof n.maxDistance?n.maxDistance:1e4,panningModel:"undefined"!=typeof n.panningModel?n.panningModel:"HRTF",refDistance:"undefined"!=typeof n.refDistance?n.refDistance:1,rolloffFactor:"undefined"!=typeof n.rolloffFactor?n.rolloffFactor:1},e.call(this,n)}}(Howl.prototype.init),Howl.prototype.pos=function(n,t,o,r){var i=this;if(!i._webAudio)return i;if(!i._loaded)return i.once("play",function(){i.pos(n,t,o,r)}),i;if(t="number"!=typeof t?0:t,o="number"!=typeof o?-.5:o,"undefined"==typeof r){if("number"!=typeof n)return i._pos;i._pos=[n,t,o]}for(var a=i._getSoundIds(r),p=0;p<a.length;p++){var l=i._soundById(a[p]);if(l){if("number"!=typeof n)return l._pos;l._pos=[n,t,o],l._node&&(l._panner||e(l),l._panner.setPosition(n,t,o))}}return i},Howl.prototype.orientation=function(n,t,o,r){var i=this;if(!i._webAudio)return i;if(!i._loaded)return i.once("play",function(){i.orientation(n,t,o,r)}),i;if(t="number"!=typeof t?i._orientation[1]:t,o="number"!=typeof o?i._orientation[1]:o,"undefined"==typeof r){if("number"!=typeof n)return i._orientation;i._orientation=[n,t,o]}for(var a=i._getSoundIds(r),p=0;p<a.length;p++){var l=i._soundById(a[p]);if(l){if("number"!=typeof n)return l._orientation;l._orientation=[n,t,o],l._node&&(l._panner||e(l),l._panner.setOrientation(n,t,o))}}return i},Howl.prototype.velocity=function(n,t,o,r){var i=this;if(!i._webAudio)return i;if(!i._loaded)return i.once("play",function(){i.velocity(n,t,o,r)}),i;if(t="number"!=typeof t?i._velocity[1]:t,o="number"!=typeof o?i._velocity[1]:o,"undefined"==typeof r){if("number"!=typeof n)return i._velocity;i._velocity=[n,t,o]}for(var a=i._getSoundIds(r),p=0;p<a.length;p++){var l=i._soundById(a[p]);if(l){if("number"!=typeof n)return l._velocity;l._velocity=[n,t,o],l._node&&(l._panner||e(l),l._panner.setVelocity(n,t,o))}}return i},Howl.prototype.pannerAttr=function(){var n,t,o,r=this,i=arguments;if(!r._webAudio)return r;if(0===i.length)return r._pannerAttr;if(1===i.length){if("object"!=typeof i[0])return o=r._soundById(parseInt(i[0],10)),o?o._pannerAttr:r._pannerAttr;n=i[0],"undefined"==typeof t&&(r._pannerAttr={coneInnerAngle:"undefined"!=typeof n.coneInnerAngle?n.coneInnerAngle:r._coneInnerAngle,coneOUterAngle:"undefined"!=typeof n.coneOUterAngle?n.coneOUterAngle:r._coneOUterAngle,coneOuterGain:"undefined"!=typeof n.coneOuterGain?n.coneOuterGain:r._coneOuterGain,distanceModel:"undefined"!=typeof n.distanceModel?n.distanceModel:r._distanceModel,maxDistance:"undefined"!=typeof n.maxDistance?n.maxDistance:r._maxDistance,panningModel:"undefined"!=typeof n.panningModel?n.panningModel:r._panningModel,refDistance:"undefined"!=typeof n.refDistance?n.refDistance:r._refDistance,rolloffFactor:"undefined"!=typeof n.rolloffFactor?n.rolloffFactor:r._rolloffFactor})}else 2===i.length&&(n=i[0],t=parseInt(i[1],10));for(var a=r._getSoundIds(t),p=0;p<a.length;p++)if(o=r._soundById(a[p])){var l=o._pannerAttr;l={coneInnerAngle:"undefined"!=typeof n.coneInnerAngle?n.coneInnerAngle:l.coneInnerAngle,coneOUterAngle:"undefined"!=typeof n.coneOUterAngle?n.coneOUterAngle:l.coneOUterAngle,coneOuterGain:"undefined"!=typeof n.coneOuterGain?n.coneOuterGain:l.coneOuterGain,distanceModel:"undefined"!=typeof n.distanceModel?n.distanceModel:l.distanceModel,maxDistance:"undefined"!=typeof n.maxDistance?n.maxDistance:l.maxDistance,panningModel:"undefined"!=typeof n.panningModel?n.panningModel:l.panningModel,refDistance:"undefined"!=typeof n.refDistance?n.refDistance:l.refDistance,rolloffFactor:"undefined"!=typeof n.rolloffFactor?n.rolloffFactor:l.rolloffFactor};var c=o._panner;c?(c.coneInnerAngle=l.coneInnerAngle,c.coneOUterAngle=l.coneOUterAngle,c.coneOuterGain=l.coneOuterGain,c.distanceModel=l.distanceModel,c.maxDistance=l.maxDistance,c.panningModel=l.panningModel,c.refDistance=l.refDistance,c.rolloffFactor=l.rolloffFactor):(o._pos||(o._pos=r._pos||[0,0,-.5]),e(o))}return r},Sound.prototype.init=function(e){return function(){var n=this,t=n._parent;n._orientation=t._orientation,n._pos=t._pos,n._velocity=t._velocity,n._pannerAttr=t._pannerAttr,e.call(this),n._pos&&t.pos(n._pos[0],n._pos[1],n._pos[2],n._id)}}(Sound.prototype.init),Sound.prototype.reset=function(e){return function(){var n=this,t=n._parent;return n._orientation=t._orientation,n._pos=t._pos,n._velocity=t._velocity,n._pannerAttr=t._pannerAttr,e.call(this)}}(Sound.prototype.reset);var e=function(e){e._panner=Howler.ctx.createPanner(),e._panner.coneInnerAngle=e._pannerAttr.coneInnerAngle,e._panner.coneOUterAngle=e._pannerAttr.coneOUterAngle,e._panner.coneOuterGain=e._pannerAttr.coneOuterGain,e._panner.distanceModel=e._pannerAttr.distanceModel,e._panner.maxDistance=e._pannerAttr.maxDistance,e._panner.panningModel=e._pannerAttr.panningModel,e._panner.refDistance=e._pannerAttr.refDistance,e._panner.rolloffFactor=e._pannerAttr.rolloffFactor,e._panner.setPosition(e._pos[0],e._pos[1],e._pos[2]),e._panner.setOrientation(e._orientation[0],e._orientation[1],e._orientation[2]),e._panner.setVelocity(e._velocity[0],e._velocity[1],e._velocity[2]),e._panner.connect(e._node),e._paused||e._parent.pause(e._id).play(e._id)}}();
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/howler.min.js b/howler.js-2.0.0-beta4/howler.min.js new file mode 100644 index 0000000..745fd5d --- /dev/null +++ b/howler.js-2.0.0-beta4/howler.min.js @@ -0,0 +1,4 @@ +/*! howler.js v2.0.0-beta4 | (c) 2013-2015, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";function e(){try{"undefined"!=typeof AudioContext?n=new AudioContext:"undefined"!=typeof webkitAudioContext?n=new webkitAudioContext:o=!1}catch(e){o=!1}if(!o)if("undefined"!=typeof Audio)try{new Audio}catch(e){t=!0}else t=!0;try{var r=new Audio;r.muted&&(t=!0)}catch(e){}}var n=null,o=!0,t=!1;if(e(),o){var r="undefined"==typeof n.createGain?n.createGainNode():n.createGain();r.gain.value=1,r.connect(n.destination)}var d=function(){this.init()};d.prototype={init:function(){var e=this||u;return e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e.mobileAutoEnable=!0,e.noAudio=t,e.usingWebAudio=o,e.ctx=n,t||e._setupCodecs(),e},volume:function(e){var n=this||u;if(e=parseFloat(e),"undefined"!=typeof e&&e>=0&&1>=e){n._volume=e,o&&(r.gain.value=e);for(var t=0;t<n._howls.length;t++)if(!n._howls[t]._webAudio)for(var d=n._howls[t]._getSoundIds(),a=0;a<d.length;a++){var i=n._howls[t]._soundById(d[a]);i&&i._node&&(i._node.volume=i._volume*e)}return n}return n._volume},mute:function(e){var n=this||u;n._muted=e,o&&(r.gain.value=e?0:n._volume);for(var t=0;t<n._howls.length;t++)if(!n._howls[t]._webAudio)for(var d=n._howls[t]._getSoundIds(),a=0;a<d.length;a++){var i=n._howls[t]._soundById(d[a]);i&&i._node&&(i._node.muted=e?!0:i._muted)}return n},unload:function(){for(var e=this||u,n=e._howls.length-1;n>=0;n--)e._howls[n].unload();return e},codecs:function(e){return(this||u)._codecs[e]},_setupCodecs:function(){var e=this||u,n=new Audio,o=n.canPlayType("audio/mpeg;").replace(/^no$/,""),t=/OPR\//.test(navigator.userAgent);return e._codecs={mp3:!(t||!o&&!n.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!o,opus:!!n.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!n.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),aac:!!n.canPlayType("audio/aac;").replace(/^no$/,""),m4a:!!(n.canPlayType("audio/x-m4a;")||n.canPlayType("audio/m4a;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(n.canPlayType("audio/x-mp4;")||n.canPlayType("audio/mp4;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),webm:!!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")},e},_enableMobileAudio:function(){var e=this||u,o=/iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk/i.test(navigator.userAgent),t=!!("ontouchend"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0);if(!n||!e._mobileEnabled&&o&&t){e._mobileEnabled=!1;var r=function(){var o=n.createBuffer(1,1,22050),t=n.createBufferSource();t.buffer=o,t.connect(n.destination),"undefined"==typeof t.start?t.noteOn(0):t.start(0),t.onended=function(){e._mobileEnabled=!0,e.mobileAutoEnable=!1,document.removeEventListener("touchend",r,!0)}};return document.addEventListener("touchend",r,!0),e}}};var u=new d,a=function(e){var n=this;return e.src&&0!==e.src.length?void n.init(e):void console.error("An array of source files must be passed with any new Howl.")};a.prototype={init:function(e){var t=this;return t._autoplay=e.autoplay||!1,t._ext=e.ext||null,t._html5=e.html5||!1,t._muted=e.mute||!1,t._loop=e.loop||!1,t._pool=e.pool||5,t._preload="boolean"==typeof e.preload?e.preload:!0,t._rate=e.rate||1,t._sprite=e.sprite||{},t._src="string"!=typeof e.src?e.src:[e.src],t._volume=void 0!==e.volume?e.volume:1,t._duration=0,t._loaded=!1,t._sounds=[],t._endTimers={},t._onend=e.onend?[{fn:e.onend}]:[],t._onfaded=e.onfaded?[{fn:e.onfaded}]:[],t._onload=e.onload?[{fn:e.onload}]:[],t._onloaderror=e.onloaderror?[{fn:e.onloaderror}]:[],t._onpause=e.onpause?[{fn:e.onpause}]:[],t._onplay=e.onplay?[{fn:e.onplay}]:[],t._onstop=e.onstop?[{fn:e.onstop}]:[],t._webAudio=o&&!t._html5,"undefined"!=typeof n&&n&&u.mobileAutoEnable&&u._enableMobileAudio(),u._howls.push(t),t._preload&&t.load(),t},load:function(){var e=this,n=null;if(t)return void e._emit("loaderror",null,"No audio support.");"string"==typeof e._src&&(e._src=[e._src]);for(var o=0;o<e._src.length;o++){var r,d;if(e._ext&&e._ext[o]?r=e._ext[o]:(d=e._src[o],r=/^data:audio\/([^;,]+);/i.exec(d),r||(r=/\.([^.]+)$/.exec(d.split("?",1)[0])),r&&(r=r[1].toLowerCase())),u.codecs(r)){n=e._src[o];break}}return n?(e._src=n,new i(e),e._webAudio&&s(e),e):void e._emit("loaderror",null,"No codec support for selected audio sources.")},play:function(e){var o=this,t=arguments,r=null;if("number"==typeof e)r=e,e=null;else if("undefined"==typeof e){e="__default";for(var d=0,a=0;a<o._sounds.length;a++)o._sounds[a]._paused&&!o._sounds[a]._ended&&(d++,r=o._sounds[a]._id);1===d?e=null:r=null}var i=r?o._soundById(r):o._inactiveSound();if(!i)return null;if(r&&!e&&(e=i._sprite||"__default"),!o._loaded&&!o._sprite[e])return o.once("load",function(){o.play(o._soundById(i._id)?i._id:void 0)}),i._id;if(r&&!i._paused)return i._id;var _=i._seek>0?i._seek:o._sprite[e][0]/1e3,s=(o._sprite[e][0]+o._sprite[e][1])/1e3-_,l=1e3*s/Math.abs(i._rate);o._endTimers[i._id]=setTimeout(o._ended.bind(o,i),l),i._paused=!1,i._ended=!1,i._sprite=e,i._seek=_,i._start=o._sprite[e][0]/1e3,i._stop=(o._sprite[e][0]+o._sprite[e][1])/1e3,i._loop=!(!i._loop&&!o._sprite[e][2]);var f=i._node;if(o._webAudio){var c=function(){o._refreshBuffer(i);var e=i._muted||o._muted?0:i._volume*u.volume();f.gain.setValueAtTime(e,n.currentTime),i._playStart=n.currentTime,"undefined"==typeof f.bufferSource.start?i._loop?f.bufferSource.noteGrainOn(0,_,86400):f.bufferSource.noteGrainOn(0,_,s):i._loop?f.bufferSource.start(0,_,86400):f.bufferSource.start(0,_,s),o._endTimers[i._id]||(o._endTimers[i._id]=setTimeout(o._ended.bind(o,i),l)),t[1]||setTimeout(function(){o._emit("play",i._id)},0)};o._loaded?c():(o.once("load",c),o._clearTimer(i._id))}else{var p=function(){f.currentTime=_,f.muted=i._muted||o._muted||u._muted||f.muted,f.volume=i._volume*u.volume(),f.playbackRate=i._rate,setTimeout(function(){f.play(),t[1]||o._emit("play",i._id)},0)};if(4===f.readyState||!f.readyState&&navigator.isCocoonJS)p();else{var m=function(){o._endTimers[i._id]=setTimeout(o._ended.bind(o,i),l),p(),f.removeEventListener("canplaythrough",m,!1)};f.addEventListener("canplaythrough",m,!1),o._clearTimer(i._id)}}return i._id},pause:function(e){var n=this;if(!n._loaded)return n.once("play",function(){n.pause(e)}),n;for(var o=n._getSoundIds(e),t=0;t<o.length;t++){n._clearTimer(o[t]);var r=n._soundById(o[t]);if(r&&!r._paused){if(r._seek=n.seek(o[t]),r._paused=!0,n._stopFade(o[t]),n._webAudio){if(!r._node.bufferSource)return n;"undefined"==typeof r._node.bufferSource.stop?r._node.bufferSource.noteOff(0):r._node.bufferSource.stop(0),r._node.bufferSource=null}else isNaN(r._node.duration)||r._node.pause();arguments[1]||n._emit("pause",r._id)}}return n},stop:function(e){var n=this;if(!n._loaded)return"undefined"!=typeof n._sounds[0]._sprite&&n.once("play",function(){n.stop(e)}),n;for(var o=n._getSoundIds(e),t=0;t<o.length;t++){n._clearTimer(o[t]);var r=n._soundById(o[t]);if(r&&!r._paused){if(r._seek=r._start||0,r._paused=!0,r._ended=!0,n._stopFade(o[t]),n._webAudio&&r._node){if(!r._node.bufferSource)return n;"undefined"==typeof r._node.bufferSource.stop?r._node.bufferSource.noteOff(0):r._node.bufferSource.stop(0),r._node.bufferSource=null}else r._node&&!isNaN(r._node.duration)&&(r._node.pause(),r._node.currentTime=r._start||0);n._emit("stop",r._id)}}return n},mute:function(e,o){var t=this;if(!t._loaded)return t.once("play",function(){t.mute(e,o)}),t;if("undefined"==typeof o){if("boolean"!=typeof e)return t._muted;t._muted=e}for(var r=t._getSoundIds(o),d=0;d<r.length;d++){var a=t._soundById(r[d]);a&&(a._muted=e,t._webAudio&&a._node?a._node.gain.setValueAtTime(e?0:a._volume*u.volume(),n.currentTime):a._node&&(a._node.muted=u._muted?!0:e))}return t},volume:function(){var e,o,t=this,r=arguments;if(0===r.length)return t._volume;if(1===r.length){var d=t._getSoundIds(),a=d.indexOf(r[0]);a>=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var i;if(!("undefined"!=typeof e&&e>=0&&1>=e))return i=o?t._soundById(o):t._sounds[0],i?i._volume:0;if(!t._loaded)return t.once("play",function(){t.volume.apply(t,r)}),t;"undefined"==typeof o&&(t._volume=e),o=t._getSoundIds(o);for(var _=0;_<o.length;_++)i=t._soundById(o[_]),i&&(i._volume=e,r[2]||t._stopFade(o[_]),t._webAudio&&i._node&&!i._muted?i._node.gain.setValueAtTime(e*u.volume(),n.currentTime):i._node&&!i._muted&&(i._node.volume=e*u.volume()));return t},fade:function(e,o,t,r){var d=this;if(!d._loaded)return d.once("play",function(){d.fade(e,o,t,r)}),d;d.volume(e,r);for(var u=d._getSoundIds(r),a=0;a<u.length;a++){var i=d._soundById(u[a]);if(i)if(d._webAudio&&!i._muted){var _=n.currentTime,s=_+t/1e3;i._volume=e,i._node.gain.setValueAtTime(e,_),i._node.gain.linearRampToValueAtTime(o,s),i._timeout=setTimeout(function(e,t){delete t._timeout,setTimeout(function(){t._volume=o,d._emit("faded",e)},s-n.currentTime>0?Math.ceil(1e3*(s-n.currentTime)):0)}.bind(d,u[a],i),t)}else{var l=Math.abs(e-o),f=e>o?"out":"in",c=l/.01,p=t/c;!function(){var n=e;i._interval=setInterval(function(e,t){n+="in"===f?.01:-.01,n=Math.max(0,n),n=Math.min(1,n),n=Math.round(100*n)/100,d.volume(n,e,!0),n===o&&(clearInterval(t._interval),delete t._interval,d._emit("faded",e))}.bind(d,u[a],i),p)}()}}return d},_stopFade:function(e){var o=this,t=o._soundById(e);return t._interval?(clearInterval(t._interval),delete t._interval,o._emit("faded",e)):t._timeout&&(clearTimeout(t._timeout),delete t._timeout,t._node.gain.cancelScheduledValues(n.currentTime),o._emit("faded",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return o=t._soundById(parseInt(r[0],10)),o?o._loop:!1;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var d=t._getSoundIds(n),u=0;u<d.length;u++)o=t._soundById(d[u]),o&&(o._loop=e,t._webAudio&&o._node&&o._node.bufferSource&&(o._node.bufferSource.loop=e));return t},rate:function(){var e,n,o=this,t=arguments;if(0===t.length)n=o._sounds[0]._id;else if(1===t.length){var r=o._getSoundIds(),d=r.indexOf(t[0]);d>=0?n=parseInt(t[0],10):e=parseFloat(t[0])}else 2===t.length&&(e=parseFloat(t[0]),n=parseInt(t[1],10));var u;if("number"!=typeof e)return u=o._soundById(n),u?u._rate:o._rate;if(!o._loaded)return o.once("load",function(){o.rate.apply(o,t)}),o;"undefined"==typeof n&&(o._rate=e),n=o._getSoundIds(n);for(var a=0;a<n.length;a++)if(u=o._soundById(n[a])){u._rate=e,o._webAudio&&u._node&&u._node.bufferSource?u._node.bufferSource.playbackRate.value=e:u._node&&(u._node.playbackRate=e);var i=o.seek(n[a]),_=(o._sprite[u._sprite][0]+o._sprite[u._sprite][1])/1e3-i,s=1e3*_/Math.abs(u._rate);o._clearTimer(n[a]),o._endTimers[n[a]]=setTimeout(o._ended.bind(o,u),s)}return o},seek:function(){var e,o,t=this,r=arguments;if(0===r.length)o=t._sounds[0]._id;else if(1===r.length){var d=t._getSoundIds(),u=d.indexOf(r[0]);u>=0?o=parseInt(r[0],10):(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if("undefined"==typeof o)return t;if(!t._loaded)return t.once("load",function(){t.seek.apply(t,r)}),t;var a=t._soundById(o);if(a){if(!(e>=0))return t._webAudio?a._seek+(t.playing(o)?n.currentTime-a._playStart:0):a._node.currentTime;var i=t.playing(o);i&&t.pause(o,!0),a._seek=e,t._clearTimer(o),i&&t.play(o,!0)}return t},playing:function(e){var n=this,o=n._soundById(e)||n._sounds[0];return o?!o._paused:!1},duration:function(){return this._duration},unload:function(){for(var e=this,n=e._sounds,o=0;o<n.length;o++){n[o]._paused||(e.stop(n[o]._id),e._emit("end",n[o]._id)),e._webAudio||(n[o]._node.src="",n[o]._node.removeEventListener("error",n[o]._errorFn,!1),n[o]._node.removeEventListener("canplaythrough",n[o]._loadFn,!1)),delete n[o]._node,e._clearTimer(n[o]._id);var t=u._howls.indexOf(e);t>=0&&u._howls.splice(t,1)}return _&&delete _[e._src],e=null,null},on:function(e,n,o,t){var r=this,d=r["_on"+e];return"function"==typeof n&&d.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e];if(n){for(var d=0;d<r.length;d++)if(n===r[d].fn&&o===r[d].id){r.splice(d,1);break}}else t["on"+e]=[];return t},once:function(e,n,o){var t=this;return t.on(e,n,o,1),t},_emit:function(e,n,o){for(var t=this,r=t["_on"+e],d=0;d<r.length;d++)r[d].id&&r[d].id!==n||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[d].fn),0),r[d].once&&t.off(e,r[d].fn,r[d].id));return t},_ended:function(e){var o=this,t=e._sprite,r=!(!e._loop&&!o._sprite[t][2]);if(o._emit("end",e._id),!o._webAudio&&r&&o.stop(e._id).play(e._id),o._webAudio&&r){o._emit("play",e._id),e._seek=e._start||0,e._playStart=n.currentTime;var d=1e3*(e._stop-e._start)/Math.abs(e._rate);o._endTimers[e._id]=setTimeout(o._ended.bind(o,e),d)}return o._webAudio&&!r&&(e._paused=!0,e._ended=!0,e._seek=e._start||0,o._clearTimer(e._id),e._node.bufferSource=null),o._webAudio||r||o.stop(e._id),o},_clearTimer:function(e){var n=this;return n._endTimers[e]&&(clearTimeout(n._endTimers[e]),delete n._endTimers[e]),n},_soundById:function(e){for(var n=this,o=0;o<n._sounds.length;o++)if(e===n._sounds[o]._id)return n._sounds[o];return null},_inactiveSound:function(){var e=this;e._drain();for(var n=0;n<e._sounds.length;n++)if(e._sounds[n]._ended)return e._sounds[n].reset();return new i(e)},_drain:function(){var e=this,n=e._pool,o=0,t=0;if(!(e._sounds.length<n)){for(t=0;t<e._sounds.length;t++)e._sounds[t]._ended&&o++;for(t=e._sounds.length-1;t>=0;t--){if(n>=o)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if("undefined"==typeof e){for(var o=[],t=0;t<n._sounds.length;t++)o.push(n._sounds[t]._id);return o}return[e]},_refreshBuffer:function(e){var o=this;return e._node.bufferSource=n.createBufferSource(),e._node.bufferSource.buffer=_[o._src],e._node.bufferSource.connect(e._panner?e._panner:e._node),e._node.bufferSource.loop=e._loop,e._loop&&(e._node.bufferSource.loopStart=e._start||0,e._node.bufferSource.loopEnd=e._stop),e._node.bufferSource.playbackRate.value=o._rate,o}};var i=function(e){this._parent=e,this.init()};if(i.prototype={init:function(){var e=this,n=e._parent;return e._muted=n._muted,e._loop=n._loop,e._volume=n._volume,e._muted=n._muted,e._rate=n._rate,e._seek=0,e._paused=!0,e._ended=!0,e._id=Math.round(Date.now()*Math.random()),n._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=u._muted||e._muted||e._parent._muted?0:e._volume*u.volume();return o._webAudio?(e._node="undefined"==typeof n.createGain?n.createGainNode():n.createGain(),e._node.gain.setValueAtTime(t,n.currentTime),e._node.paused=!0,e._node.connect(r)):(e._node=new Audio,e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener("canplaythrough",e._loadFn,!1),e._node.src=o._src,e._node.preload="auto",e._node.volume=t,e._node.load()),e},reset:function(){var e=this,n=e._parent;return e._muted=n._muted,e._loop=n._loop,e._volume=n._volume,e._muted=n._muted,e._rate=n._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite=null,e._id=Math.round(Date.now()*Math.random()),e},_errorListener:function(){var e=this;e._node.error&&4===e._node.error.code&&(u.noAudio=!0),e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorListener,!1)},_loadListener:function(){var e=this,n=e._parent;n._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(n._sprite).length&&(n._sprite={__default:[0,1e3*n._duration]}),n._loaded||(n._loaded=!0,n._emit("load")),n._autoplay&&n.play(),e._node.removeEventListener("canplaythrough",e._loadFn,!1)}},o)var _={},s=function(e){var n=e._src;if(_[n])return e._duration=_[n].duration,void c(e);if(/^data:[^;]+;base64,/.test(n)){window.atob=window.atob||function(e){for(var n,o,t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=String(e).replace(/=+$/,""),d=0,u=0,a="";o=r.charAt(u++);~o&&(n=d%4?64*n+o:o,d++%4)?a+=String.fromCharCode(255&n>>(-2*d&6)):0)o=t.indexOf(o);return a};for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),r=0;r<o.length;++r)t[r]=o.charCodeAt(r);f(t.buffer,e)}else{var d=new XMLHttpRequest;d.open("GET",n,!0),d.responseType="arraybuffer",d.onload=function(){f(d.response,e)},d.onerror=function(){e._webAudio&&(e._html5=!0,e._webAudio=!1,e._sounds=[],delete _[n],e.load())},l(d)}},l=function(e){try{e.send()}catch(n){e.onerror()}},f=function(e,o){n.decodeAudioData(e,function(e){e&&(_[o._src]=e,c(o,e))},function(){o._emit("loaderror",null,"Decoding audio data failed.")})},c=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),e._loaded||(e._loaded=!0,e._emit("load")),e._autoplay&&e.play()};"function"==typeof define&&define.amd&&define("howler",function(){return{Howler:u,Howl:a}}),"undefined"!=typeof exports&&(exports.Howler=u,exports.Howl=a),"undefined"!=typeof window&&(window.HowlerGlobal=d,window.Howler=u,window.Howl=a,window.Sound=i)}(); +/*! Effects Plugin */ +!function(){"use strict";HowlerGlobal.prototype.init=function(e){return function(){var n=this;return n._pos=[0,0,0],n._orientation=[0,0,-1,0,1,0],n._velocity=[0,0,0],n._listenerAttr={dopplerFactor:1,speedOfSound:343.3},e.call(this,o)}}(HowlerGlobal.prototype.init),HowlerGlobal.prototype.pos=function(e,n,t){var o=this;return o.ctx&&o.ctx.listener?(n="number"!=typeof n?o._pos[1]:n,t="number"!=typeof t?o._pos[2]:t,"number"!=typeof e?o._pos:(o._pos=[e,n,t],o.ctx.listener.setPosition(o._pos[0],o._pos[1],o._pos[2]),o)):o},HowlerGlobal.prototype.orientation=function(e,n,t,o,r,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var p=a._orientation;return n="number"!=typeof n?p[1]:n,t="number"!=typeof t?p[2]:t,o="number"!=typeof o?p[3]:o,r="number"!=typeof r?p[4]:r,i="number"!=typeof i?p[5]:i,"number"!=typeof e?p:(a._orientation=[e,n,t,o,r,i],a.ctx.listener.setOrientation(p[0],p[1],p[2],p[3],p[4],p[5]),a)},HowlerGlobal.prototype.velocity=function(e,n,t){var o=this;return o.ctx&&o.ctx.listener?(n="number"!=typeof n?o._velocity[1]:n,t="number"!=typeof t?o._velocity[2]:t,"number"!=typeof e?o._velocity:(o._velocity=[e,n,t],o.ctx.listener.setVelocity(o._velocity[0],o._velocity[1],o._velocity[2]),o)):o},HowlerGlobal.prototype.listenerAttr=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;var t=n._listenerAttr;return e?(n._listenerAttr={dopplerFactor:"undefined"!=typeof e.dopplerFactor?e.dopplerFactor:t.dopplerFactor,speedOfSound:"undefined"!=typeof e.speedOfSound?e.speedOfSound:t.speedOfSound},n.ctx.listener.dopplerFactor=t.dopplerFactor,n.ctx.listener.speedOfSound=t.speedOfSound,n):t},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._pos=n.pos||null,t._velocity=n.velocity||[0,0,0],t._pannerAttr={coneInnerAngle:"undefined"!=typeof n.coneInnerAngle?n.coneInnerAngle:360,coneOUterAngle:"undefined"!=typeof n.coneOUterAngle?n.coneOUterAngle:360,coneOuterGain:"undefined"!=typeof n.coneOuterGain?n.coneOuterGain:0,distanceModel:"undefined"!=typeof n.distanceModel?n.distanceModel:"inverse",maxDistance:"undefined"!=typeof n.maxDistance?n.maxDistance:1e4,panningModel:"undefined"!=typeof n.panningModel?n.panningModel:"HRTF",refDistance:"undefined"!=typeof n.refDistance?n.refDistance:1,rolloffFactor:"undefined"!=typeof n.rolloffFactor?n.rolloffFactor:1},e.call(this,n)}}(Howl.prototype.init),Howl.prototype.pos=function(n,t,o,r){var i=this;if(!i._webAudio)return i;if(!i._loaded)return i.once("play",function(){i.pos(n,t,o,r)}),i;if(t="number"!=typeof t?0:t,o="number"!=typeof o?-.5:o,"undefined"==typeof r){if("number"!=typeof n)return i._pos;i._pos=[n,t,o]}for(var a=i._getSoundIds(r),p=0;p<a.length;p++){var l=i._soundById(a[p]);if(l){if("number"!=typeof n)return l._pos;l._pos=[n,t,o],l._node&&(l._panner||e(l),l._panner.setPosition(n,t,o))}}return i},Howl.prototype.orientation=function(n,t,o,r){var i=this;if(!i._webAudio)return i;if(!i._loaded)return i.once("play",function(){i.orientation(n,t,o,r)}),i;if(t="number"!=typeof t?i._orientation[1]:t,o="number"!=typeof o?i._orientation[1]:o,"undefined"==typeof r){if("number"!=typeof n)return i._orientation;i._orientation=[n,t,o]}for(var a=i._getSoundIds(r),p=0;p<a.length;p++){var l=i._soundById(a[p]);if(l){if("number"!=typeof n)return l._orientation;l._orientation=[n,t,o],l._node&&(l._panner||e(l),l._panner.setOrientation(n,t,o))}}return i},Howl.prototype.velocity=function(n,t,o,r){var i=this;if(!i._webAudio)return i;if(!i._loaded)return i.once("play",function(){i.velocity(n,t,o,r)}),i;if(t="number"!=typeof t?i._velocity[1]:t,o="number"!=typeof o?i._velocity[1]:o,"undefined"==typeof r){if("number"!=typeof n)return i._velocity;i._velocity=[n,t,o]}for(var a=i._getSoundIds(r),p=0;p<a.length;p++){var l=i._soundById(a[p]);if(l){if("number"!=typeof n)return l._velocity;l._velocity=[n,t,o],l._node&&(l._panner||e(l),l._panner.setVelocity(n,t,o))}}return i},Howl.prototype.pannerAttr=function(){var n,t,o,r=this,i=arguments;if(!r._webAudio)return r;if(0===i.length)return r._pannerAttr;if(1===i.length){if("object"!=typeof i[0])return o=r._soundById(parseInt(i[0],10)),o?o._pannerAttr:r._pannerAttr;n=i[0],"undefined"==typeof t&&(r._pannerAttr={coneInnerAngle:"undefined"!=typeof n.coneInnerAngle?n.coneInnerAngle:r._coneInnerAngle,coneOUterAngle:"undefined"!=typeof n.coneOUterAngle?n.coneOUterAngle:r._coneOUterAngle,coneOuterGain:"undefined"!=typeof n.coneOuterGain?n.coneOuterGain:r._coneOuterGain,distanceModel:"undefined"!=typeof n.distanceModel?n.distanceModel:r._distanceModel,maxDistance:"undefined"!=typeof n.maxDistance?n.maxDistance:r._maxDistance,panningModel:"undefined"!=typeof n.panningModel?n.panningModel:r._panningModel,refDistance:"undefined"!=typeof n.refDistance?n.refDistance:r._refDistance,rolloffFactor:"undefined"!=typeof n.rolloffFactor?n.rolloffFactor:r._rolloffFactor})}else 2===i.length&&(n=i[0],t=parseInt(i[1],10));for(var a=r._getSoundIds(t),p=0;p<a.length;p++)if(o=r._soundById(a[p])){var l=o._pannerAttr;l={coneInnerAngle:"undefined"!=typeof n.coneInnerAngle?n.coneInnerAngle:l.coneInnerAngle,coneOUterAngle:"undefined"!=typeof n.coneOUterAngle?n.coneOUterAngle:l.coneOUterAngle,coneOuterGain:"undefined"!=typeof n.coneOuterGain?n.coneOuterGain:l.coneOuterGain,distanceModel:"undefined"!=typeof n.distanceModel?n.distanceModel:l.distanceModel,maxDistance:"undefined"!=typeof n.maxDistance?n.maxDistance:l.maxDistance,panningModel:"undefined"!=typeof n.panningModel?n.panningModel:l.panningModel,refDistance:"undefined"!=typeof n.refDistance?n.refDistance:l.refDistance,rolloffFactor:"undefined"!=typeof n.rolloffFactor?n.rolloffFactor:l.rolloffFactor};var c=o._panner;c?(c.coneInnerAngle=l.coneInnerAngle,c.coneOUterAngle=l.coneOUterAngle,c.coneOuterGain=l.coneOuterGain,c.distanceModel=l.distanceModel,c.maxDistance=l.maxDistance,c.panningModel=l.panningModel,c.refDistance=l.refDistance,c.rolloffFactor=l.rolloffFactor):(o._pos||(o._pos=r._pos||[0,0,-.5]),e(o))}return r},Sound.prototype.init=function(e){return function(){var n=this,t=n._parent;n._orientation=t._orientation,n._pos=t._pos,n._velocity=t._velocity,n._pannerAttr=t._pannerAttr,e.call(this),n._pos&&t.pos(n._pos[0],n._pos[1],n._pos[2],n._id)}}(Sound.prototype.init),Sound.prototype.reset=function(e){return function(){var n=this,t=n._parent;return n._orientation=t._orientation,n._pos=t._pos,n._velocity=t._velocity,n._pannerAttr=t._pannerAttr,e.call(this)}}(Sound.prototype.reset);var e=function(e){e._panner=Howler.ctx.createPanner(),e._panner.coneInnerAngle=e._pannerAttr.coneInnerAngle,e._panner.coneOUterAngle=e._pannerAttr.coneOUterAngle,e._panner.coneOuterGain=e._pannerAttr.coneOuterGain,e._panner.distanceModel=e._pannerAttr.distanceModel,e._panner.maxDistance=e._pannerAttr.maxDistance,e._panner.panningModel=e._pannerAttr.panningModel,e._panner.refDistance=e._pannerAttr.refDistance,e._panner.rolloffFactor=e._pannerAttr.rolloffFactor,e._panner.setPosition(e._pos[0],e._pos[1],e._pos[2]),e._panner.setOrientation(e._orientation[0],e._orientation[1],e._orientation[2]),e._panner.setVelocity(e._velocity[0],e._velocity[1],e._velocity[2]),e._panner.connect(e._node),e._paused||e._parent.pause(e._id).play(e._id)}}();
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/package.json b/howler.js-2.0.0-beta4/package.json new file mode 100644 index 0000000..261341b --- /dev/null +++ b/howler.js-2.0.0-beta4/package.json @@ -0,0 +1,31 @@ +{ + "name": "howler", + "description": "Javascript audio library for the modern web.", + "homepage": "http://howlerjs.com", + "keywords": [ + "howler", + "howler.js", + "audio", + "webaudio", + "browser", + "html5" + ], + "author": "James Simpson <james@goldfirestudios.com> (http://goldfirestudios.com)", + "repository": { + "type": "git", + "url": "git://github.com/goldfire/howler.js.git#2.0" + }, + "main": "howler.min.js", + "version": "2.0.0-beta4", + "license": { + "type": "MIT", + "url": "https://raw.githubusercontent.com/goldfire/howler.js/master/LICENSE.md" + }, + "files": [ + "src", + "howler.min.js", + "howler.core.min.js", + "howler.effects.min.js", + "LICENSE.md" + ] +} diff --git a/howler.js-2.0.0-beta4/src/howler.core.js b/howler.js-2.0.0-beta4/src/howler.core.js new file mode 100644 index 0000000..3cdd18b --- /dev/null +++ b/howler.js-2.0.0-beta4/src/howler.core.js @@ -0,0 +1,1808 @@ +/*! + * howler.js v2.0.0-beta4 + * howlerjs.com + * + * (c) 2013-2015, James Simpson of GoldFire Studios + * goldfirestudios.com + * + * MIT License + */ + +(function() { + + 'use strict'; + + // Setup our audio context. + var ctx = null; + var usingWebAudio = true; + var noAudio = false; + setupAudioContext(); + + // Create a master gain node. + if (usingWebAudio) { + var masterGain = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain(); + masterGain.gain.value = 1; + masterGain.connect(ctx.destination); + } + + /** Global Methods **/ + /***************************************************************************/ + + /** + * Create the global controller. All contained methods and properties apply + * to all sounds that are currently playing or will be in the future. + */ + var HowlerGlobal = function() { + this.init(); + }; + HowlerGlobal.prototype = { + /** + * Initialize the global Howler object. + * @return {Howler} + */ + init: function() { + var self = this || Howler; + + // Internal properties. + self._codecs = {}; + self._howls = []; + self._muted = false; + self._volume = 1; + + // Set to false to disable the auto iOS enabler. + self.mobileAutoEnable = true; + + // No audio is available on this system if this is set to true. + self.noAudio = noAudio; + + // This will be true if the Web Audio API is available. + self.usingWebAudio = usingWebAudio; + + // Expose the AudioContext when using Web Audio. + self.ctx = ctx; + + // Check for supported codecs. + if (!noAudio) { + self._setupCodecs(); + } + + return self; + }, + + /** + * Get/set the global volume for all sounds. + * @param {Float} vol Volume from 0.0 to 1.0. + * @return {Howler/Float} Returns self or current volume. + */ + volume: function(vol) { + var self = this || Howler; + vol = parseFloat(vol); + + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + self._volume = vol; + + // When using Web Audio, we just need to adjust the master gain. + if (usingWebAudio) { + masterGain.gain.value = vol; + } + + // Loop through and change volume for all HTML5 audio nodes. + for (var i=0; i<self._howls.length; i++) { + if (!self._howls[i]._webAudio) { + // Get all of the sounds in this Howl group. + var ids = self._howls[i]._getSoundIds(); + + // Loop through all sounds and change the volumes. + for (var j=0; j<ids.length; j++) { + var sound = self._howls[i]._soundById(ids[j]); + + if (sound && sound._node) { + sound._node.volume = sound._volume * vol; + } + } + } + } + + return self; + } + + return self._volume; + }, + + /** + * Handle muting and unmuting globally. + * @param {Boolean} muted Is muted or not. + */ + mute: function(muted) { + var self = this || Howler; + + self._muted = muted; + + // With Web Audio, we just need to mute the master gain. + if (usingWebAudio) { + masterGain.gain.value = muted ? 0 : self._volume; + } + + // Loop through and mute all HTML5 Audio nodes. + for (var i=0; i<self._howls.length; i++) { + if (!self._howls[i]._webAudio) { + // Get all of the sounds in this Howl group. + var ids = self._howls[i]._getSoundIds(); + + // Loop through all sounds and mark the audio node as muted. + for (var j=0; j<ids.length; j++) { + var sound = self._howls[i]._soundById(ids[j]); + + if (sound && sound._node) { + sound._node.muted = (muted) ? true : sound._muted; + } + } + } + } + + return self; + }, + + /** + * Unload and destroy all currently loaded Howl objects. + * @return {Howler} + */ + unload: function() { + var self = this || Howler; + + for (var i=self._howls.length-1; i>=0; i--) { + self._howls[i].unload(); + } + + return self; + }, + + /** + * Check for codec support of specific extension. + * @param {String} ext Audio file extention. + * @return {Boolean} + */ + codecs: function(ext) { + return (this || Howler)._codecs[ext]; + }, + + /** + * Check for browser support for various codecs and cache the results. + * @return {Howler} + */ + _setupCodecs: function() { + var self = this || Howler; + var audioTest = new Audio(); + var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''); + var isOpera = /OPR\//.test(navigator.userAgent); + + self._codecs = { + mp3: !!(!isOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))), + mpeg: !!mpegTest, + opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''), + ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + wav: !!audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ''), + aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''), + m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + weba: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ''), + webm: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '') + }; + + return self; + }, + + /** + * Mobile browsers will only allow audio to be played after a user interaction. + * Attempt to automatically unlock audio on the first user interaction. + * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ + * @return {Howler} + */ + _enableMobileAudio: function() { + var self = this || Howler; + + // Only run this on iOS if audio isn't already eanbled. + var isMobile = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk/i.test(navigator.userAgent); + var isTouch = !!(('ontouchend' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)); + if (ctx && (self._mobileEnabled || !isMobile || !isTouch)) { + return; + } + + self._mobileEnabled = false; + + // Call this method on touch start to create and play a buffer, + // then check if the audio actually played to determine if + // audio has now been unlocked on iOS, Android, etc. + var unlock = function() { + // Create an empty buffer. + var buffer = ctx.createBuffer(1, 1, 22050); + var source = ctx.createBufferSource(); + source.buffer = buffer; + source.connect(ctx.destination); + + // Play the empty buffer. + if (typeof source.start === 'undefined') { + source.noteOn(0); + } else { + source.start(0); + } + + // Setup a timeout to check that we are unlocked on the next event loop. + source.onended = function() { + // Update the unlocked state and prevent this check from happening again. + self._mobileEnabled = true; + self.mobileAutoEnable = false; + + // Remove the touch start listener. + document.removeEventListener('touchend', unlock, true); + }; + }; + + // Setup a touch start listener to attempt an unlock in. + document.addEventListener('touchend', unlock, true); + + return self; + } + }; + + // Setup the global audio controller. + var Howler = new HowlerGlobal(); + + /** Group Methods **/ + /***************************************************************************/ + + /** + * Create an audio group controller. + * @param {Object} o Passed in properties for this group. + */ + var Howl = function(o) { + var self = this; + + // Throw an error if no source is provided. + if (!o.src || o.src.length === 0) { + console.error('An array of source files must be passed with any new Howl.'); + return; + } + + self.init(o); + }; + Howl.prototype = { + /** + * Initialize a new Howl group object. + * @param {Object} o Passed in properties for this group. + * @return {Howl} + */ + init: function(o) { + var self = this; + + // Setup user-defined default properties. + self._autoplay = o.autoplay || false; + self._ext = o.ext || null; + self._html5 = o.html5 || false; + self._muted = o.mute || false; + self._loop = o.loop || false; + self._pool = o.pool || 5; + self._preload = (typeof o.preload === 'boolean') ? o.preload : true; + self._rate = o.rate || 1; + self._sprite = o.sprite || {}; + self._src = (typeof o.src !== 'string') ? o.src : [o.src]; + self._volume = o.volume !== undefined ? o.volume : 1; + + // Setup all other default properties. + self._duration = 0; + self._loaded = false; + self._sounds = []; + self._endTimers = {}; + + // Setup event listeners. + self._onend = o.onend ? [{fn: o.onend}] : []; + self._onfaded = o.onfaded ? [{fn: o.onfaded}] : []; + self._onload = o.onload ? [{fn: o.onload}] : []; + self._onloaderror = o.onloaderror ? [{fn: o.onloaderror}] : []; + self._onpause = o.onpause ? [{fn: o.onpause}] : []; + self._onplay = o.onplay ? [{fn: o.onplay}] : []; + self._onstop = o.onstop ? [{fn: o.onstop}] : []; + + // Web Audio or HTML5 Audio? + self._webAudio = usingWebAudio && !self._html5; + + // Automatically try to enable audio on iOS. + if (typeof ctx !== 'undefined' && ctx && Howler.mobileAutoEnable) { + Howler._enableMobileAudio(); + } + + // Keep track of this Howl group in the global controller. + Howler._howls.push(self); + + // Load the source file unless otherwise specified. + if (self._preload) { + self.load(); + } + + return self; + }, + + /** + * Load the audio file. + * @return {Howler} + */ + load: function() { + var self = this; + var url = null; + + // If no audio is available, quit immediately. + if (noAudio) { + self._emit('loaderror', null, 'No audio support.'); + return; + } + + // Make sure our source is in an array. + if (typeof self._src === 'string') { + self._src = [self._src]; + } + + // Loop through the sources and pick the first one that is compatible. + for (var i=0; i<self._src.length; i++) { + var ext, str; + + if (self._ext && self._ext[i]) { + // If an extension was specified, use that instead. + ext = self._ext[i]; + } else { + // Extract the file extension from the URL or base64 data URI. + str = self._src[i]; + ext = /^data:audio\/([^;,]+);/i.exec(str); + if (!ext) { + ext = /\.([^.]+)$/.exec(str.split('?', 1)[0]); + } + + if (ext) { + ext = ext[1].toLowerCase(); + } + } + + // Check if this extension is available. + if (Howler.codecs(ext)) { + url = self._src[i]; + break; + } + } + + if (!url) { + self._emit('loaderror', null, 'No codec support for selected audio sources.'); + return; + } + + self._src = url; + + // Create a new sound object and add it to the pool. + new Sound(self); + + // Load and decode the audio data for playback. + if (self._webAudio) { + loadBuffer(self); + } + + return self; + }, + + /** + * Play a sound or resume previous playback. + * @param {String/Number} sprite Sprite name for sprite playback or sound id to continue previous. + * @return {Number} Sound ID. + */ + play: function(sprite) { + var self = this; + var args = arguments; + var id = null; + + // Determine if a sprite, sound id or nothing was passed + if (typeof sprite === 'number') { + id = sprite; + sprite = null; + } else if (typeof sprite === 'undefined') { + // Use the default sound sprite (plays the full audio length). + sprite = '__default'; + + // Check if there is a single paused sound that isn't ended. + // If there is, play that sound. If not, continue as usual. + var num = 0; + for (var i=0; i<self._sounds.length; i++) { + if (self._sounds[i]._paused && !self._sounds[i]._ended) { + num++; + id = self._sounds[i]._id; + } + } + + if (num === 1) { + sprite = null; + } else { + id = null; + } + } + + // Get the selected node, or get one from the pool. + var sound = id ? self._soundById(id) : self._inactiveSound(); + + // If the sound doesn't exist, do nothing. + if (!sound) { + return null; + } + + // Select the sprite definition. + if (id && !sprite) { + sprite = sound._sprite || '__default'; + } + + // If we have no sprite and the sound hasn't loaded, we must wait + // for the sound to load to get our audio's duration. + if (!self._loaded && !self._sprite[sprite]) { + self.once('load', function() { + self.play(self._soundById(sound._id) ? sound._id : undefined); + }); + return sound._id; + } + + // Don't play the sound if an id was passed and it is already playing. + if (id && !sound._paused) { + return sound._id; + } + + // Determine how long to play for and where to start playing. + var seek = sound._seek > 0 ? sound._seek : self._sprite[sprite][0] / 1000; + var duration = ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek; + + // Create a timer to fire at the end of playback or the start of a new loop. + var timeout = (duration * 1000) / Math.abs(sound._rate); + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + + // Update the parameters of the sound + sound._paused = false; + sound._ended = false; + sound._sprite = sprite; + sound._seek = seek; + sound._start = self._sprite[sprite][0] / 1000; + sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; + sound._loop = !!(sound._loop || self._sprite[sprite][2]); + + // Begin the actual playback. + var node = sound._node; + if (self._webAudio) { + // Fire this when the sound is ready to play to begin Web Audio playback. + var playWebAudio = function() { + self._refreshBuffer(sound); + + // Setup the playback params. + var vol = (sound._muted || self._muted) ? 0 : sound._volume * Howler.volume(); + node.gain.setValueAtTime(vol, ctx.currentTime); + sound._playStart = ctx.currentTime; + + // Play the sound using the supported method. + if (typeof node.bufferSource.start === 'undefined') { + sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration); + } else { + sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration); + } + + // Start a new timer if none is present. + if (!self._endTimers[sound._id]) { + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + if (!args[1]) { + setTimeout(function() { + self._emit('play', sound._id); + }, 0); + } + }; + + if (self._loaded) { + playWebAudio(); + } else { + // Wait for the audio to load and then begin playback. + self.once('load', playWebAudio); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } else { + // Fire this when the sound is ready to play to begin HTML5 Audio playback. + var playHtml5 = function() { + node.currentTime = seek; + node.muted = sound._muted || self._muted || Howler._muted || node.muted; + node.volume = sound._volume * Howler.volume(); + node.playbackRate = sound._rate; + setTimeout(function() { + node.play(); + if (!args[1]) { + self._emit('play', sound._id); + } + }, 0); + }; + + // Play immediately if ready, or wait for the 'canplaythrough'e vent. + if (node.readyState === 4 || !node.readyState && navigator.isCocoonJS) { + playHtml5(); + } else { + var listener = function() { + // Setup the new end timer. + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + + // Begin playback. + playHtml5(); + + // Clear this listener. + node.removeEventListener('canplaythrough', listener, false); + }; + node.addEventListener('canplaythrough', listener, false); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } + + return sound._id; + }, + + /** + * Pause playback and save current position. + * @param {Number} id The sound ID (empty to pause all in group). + * @return {Howl} + */ + pause: function(id) { + var self = this; + + // Wait for the sound to begin playing before pausing it. + if (!self._loaded) { + self.once('play', function() { + self.pause(id); + }); + + return self; + } + + // If no id is passed, get all ID's to be paused. + var ids = self._getSoundIds(id); + + for (var i=0; i<ids.length; i++) { + // Clear the end timer. + self._clearTimer(ids[i]); + + // Get the sound. + var sound = self._soundById(ids[i]); + + if (sound && !sound._paused) { + // Reset the seek position. + sound._seek = self.seek(ids[i]); + sound._paused = true; + + // Stop currently running fades. + self._stopFade(ids[i]); + + if (self._webAudio) { + // make sure the sound has been created + if (!sound._node.bufferSource) { + return self; + } + + if (typeof sound._node.bufferSource.stop === 'undefined') { + sound._node.bufferSource.noteOff(0); + } else { + sound._node.bufferSource.stop(0); + } + + // Clean up the buffer source. + sound._node.bufferSource = null; + } else if (!isNaN(sound._node.duration)) { + sound._node.pause(); + } + + // Fire the pause event, unless `true` is passed as the 2nd argument. + if (!arguments[1]) { + self._emit('pause', sound._id); + } + } + } + + return self; + }, + + /** + * Stop playback and reset to start. + * @param {Number} id The sound ID (empty to stop all in group). + * @return {Howl} + */ + stop: function(id) { + var self = this; + + // Wait for the sound to begin playing before stopping it. + if (!self._loaded) { + if (typeof self._sounds[0]._sprite !== 'undefined') { + self.once('play', function() { + self.stop(id); + }); + } + + return self; + } + + // If no id is passed, get all ID's to be stopped. + var ids = self._getSoundIds(id); + + for (var i=0; i<ids.length; i++) { + // Clear the end timer. + self._clearTimer(ids[i]); + + // Get the sound. + var sound = self._soundById(ids[i]); + + if (sound && !sound._paused) { + // Reset the seek position. + sound._seek = sound._start || 0; + sound._paused = true; + sound._ended = true; + + // Stop currently running fades. + self._stopFade(ids[i]); + + if (self._webAudio && sound._node) { + // make sure the sound has been created + if (!sound._node.bufferSource) { + return self; + } + + if (typeof sound._node.bufferSource.stop === 'undefined') { + sound._node.bufferSource.noteOff(0); + } else { + sound._node.bufferSource.stop(0); + } + + // Clean up the buffer source. + sound._node.bufferSource = null; + } else if (sound._node && !isNaN(sound._node.duration)) { + sound._node.pause(); + sound._node.currentTime = sound._start || 0; + } + + self._emit('stop', sound._id); + } + } + + return self; + }, + + /** + * Mute/unmute a single sound or all sounds in this Howl group. + * @param {Boolean} muted Set to true to mute and false to unmute. + * @param {Number} id The sound ID to update (omit to mute/unmute all). + * @return {Howl} + */ + mute: function(muted, id) { + var self = this; + + // Wait for the sound to begin playing before muting it. + if (!self._loaded) { + self.once('play', function() { + self.mute(muted, id); + }); + + return self; + } + + // If applying mute/unmute to all sounds, update the group's value. + if (typeof id === 'undefined') { + if (typeof muted === 'boolean') { + self._muted = muted; + } else { + return self._muted; + } + } + + // If no id is passed, get all ID's to be muted. + var ids = self._getSoundIds(id); + + for (var i=0; i<ids.length; i++) { + // Get the sound. + var sound = self._soundById(ids[i]); + + if (sound) { + sound._muted = muted; + + if (self._webAudio && sound._node) { + sound._node.gain.setValueAtTime(muted ? 0 : sound._volume * Howler.volume(), ctx.currentTime); + } else if (sound._node) { + sound._node.muted = Howler._muted ? true : muted; + } + } + } + + return self; + }, + + /** + * Get/set the volume of this sound or of the Howl group. This method can optionally take 0, 1 or 2 arguments. + * volume() -> Returns the group's volume value. + * volume(id) -> Returns the sound id's current volume. + * volume(vol) -> Sets the volume of all sounds in this Howl group. + * volume(vol, id) -> Sets the volume of passed sound id. + * @return {Howl/Number} Returns self or current volume. + */ + volume: function() { + var self = this; + var args = arguments; + var vol, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the value of the groups' volume. + return self._volume; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new volume. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + vol = parseFloat(args[0]); + } + } else if (args.length >= 2) { + vol = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the volume or return the current volume. + var sound; + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + // Wait for the sound to begin playing before changing the volume. + if (!self._loaded) { + self.once('play', function() { + self.volume.apply(self, args); + }); + + return self; + } + + // Set the group volume. + if (typeof id === 'undefined') { + self._volume = vol; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i<id.length; i++) { + // Get the sound. + sound = self._soundById(id[i]); + + if (sound) { + sound._volume = vol; + + // Stop currently running fades. + if (!args[2]) { + self._stopFade(id[i]); + } + + if (self._webAudio && sound._node && !sound._muted) { + sound._node.gain.setValueAtTime(vol * Howler.volume(), ctx.currentTime); + } else if (sound._node && !sound._muted) { + sound._node.volume = vol * Howler.volume(); + } + } + } + } else { + sound = id ? self._soundById(id) : self._sounds[0]; + return sound ? sound._volume : 0; + } + + return self; + }, + + /** + * Fade a currently playing sound between two volumes (if no id is passsed, all sounds will fade). + * @param {Number} from The value to fade from (0.0 to 1.0). + * @param {Number} to The volume to fade to (0.0 to 1.0). + * @param {Number} len Time in milliseconds to fade. + * @param {Number} id The sound id (omit to fade all sounds). + * @return {Howl} + */ + fade: function(from, to, len, id) { + var self = this; + + // Wait for the sound to play before fading. + if (!self._loaded) { + self.once('play', function() { + self.fade(from, to, len, id); + }); + + return self; + } + + // Set the volume to the start position. + self.volume(from, id); + + // Fade the volume of one or all sounds. + var ids = self._getSoundIds(id); + for (var i=0; i<ids.length; i++) { + // Get the sound. + var sound = self._soundById(ids[i]); + + // Create a linear fade or fall back to timeouts with HTML5 Audio. + if (sound) { + if (self._webAudio && !sound._muted) { + var currentTime = ctx.currentTime; + var end = currentTime + (len / 1000); + sound._volume = from; + sound._node.gain.setValueAtTime(from, currentTime); + sound._node.gain.linearRampToValueAtTime(to, end); + + // Fire the event when complete. + sound._timeout = setTimeout(function(id, sound) { + delete sound._timeout; + setTimeout(function() { + sound._volume = to; + self._emit('faded', id); + }, end - ctx.currentTime > 0 ? Math.ceil((end - ctx.currentTime) * 1000) : 0); + }.bind(self, ids[i], sound), len); + } else { + var diff = Math.abs(from - to); + var dir = from > to ? 'out' : 'in'; + var steps = diff / 0.01; + var stepLen = len / steps; + + (function() { + var vol = from; + sound._interval = setInterval(function(id, sound) { + // Update the volume amount. + vol += (dir === 'in' ? 0.01 : -0.01); + + // Make sure the volume is in the right bounds. + vol = Math.max(0, vol); + vol = Math.min(1, vol); + + // Round to within 2 decimal points. + vol = Math.round(vol * 100) / 100; + + // Change the volume. + self.volume(vol, id, true); + + // When the fade is complete, stop it and fire event. + if (vol === to) { + clearInterval(sound._interval); + delete sound._interval; + self._emit('faded', id); + } + }.bind(self, ids[i], sound), stepLen); + })(); + } + } + } + + return self; + }, + + /** + * Internal method that stops the currently playing fade when + * a new fade starts, volume is changed or the sound is stopped. + * @param {Number} id The sound id. + * @return {Howl} + */ + _stopFade: function(id) { + var self = this; + var sound = self._soundById(id); + + if (sound._interval) { + clearInterval(sound._interval); + delete sound._interval; + self._emit('faded', id); + } else if (sound._timeout) { + clearTimeout(sound._timeout); + delete sound._timeout; + sound._node.gain.cancelScheduledValues(ctx.currentTime); + self._emit('faded', id); + } + + return self; + }, + + /** + * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments. + * loop() -> Returns the group's loop value. + * loop(id) -> Returns the sound id's loop value. + * loop(loop) -> Sets the loop value for all sounds in this Howl group. + * loop(loop, id) -> Sets the loop value of passed sound id. + * @return {Howl/Boolean} Returns self or current loop value. + */ + loop: function() { + var self = this; + var args = arguments; + var loop, id, sound; + + // Determine the values for loop and id. + if (args.length === 0) { + // Return the grou's loop value. + return self._loop; + } else if (args.length === 1) { + if (typeof args[0] === 'boolean') { + loop = args[0]; + self._loop = loop; + } else { + // Return this sound's loop value. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._loop : false; + } + } else if (args.length === 2) { + loop = args[0]; + id = parseInt(args[1], 10); + } + + // If no id is passed, get all ID's to be looped. + var ids = self._getSoundIds(id); + for (var i=0; i<ids.length; i++) { + sound = self._soundById(ids[i]); + + if (sound) { + sound._loop = loop; + if (self._webAudio && sound._node && sound._node.bufferSource) { + sound._node.bufferSource.loop = loop; + } + } + } + + return self; + }, + + /** + * Get/set the playback rate of a sound. This method can optionally take 0, 1 or 2 arguments. + * rate() -> Returns the first sound node's current playback rate. + * rate(id) -> Returns the sound id's current playback rate. + * rate(rate) -> Sets the playback rate of all sounds in this Howl group. + * rate(rate, id) -> Sets the playback rate of passed sound id. + * @return {Howl/Number} Returns self or the current playback rate. + */ + rate: function() { + var self = this; + var args = arguments; + var rate, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current rate of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new rate value. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + rate = parseFloat(args[0]); + } + } else if (args.length === 2) { + rate = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the playback rate or return the current value. + var sound; + if (typeof rate === 'number') { + // Wait for the sound to load before changing the playback rate. + if (!self._loaded) { + self.once('load', function() { + self.rate.apply(self, args); + }); + + return self; + } + + // Set the group rate. + if (typeof id === 'undefined') { + self._rate = rate; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i<id.length; i++) { + // Get the sound. + sound = self._soundById(id[i]); + + if (sound) { + sound._rate = rate; + + // Change the playback rate. + if (self._webAudio && sound._node && sound._node.bufferSource) { + sound._node.bufferSource.playbackRate.value = rate; + } else if (sound._node) { + sound._node.playbackRate = rate; + } + + // Reset the timers. + var seek = self.seek(id[i]); + var duration = ((self._sprite[sound._sprite][0] + self._sprite[sound._sprite][1]) / 1000) - seek; + var timeout = (duration * 1000) / Math.abs(sound._rate); + + self._clearTimer(id[i]); + self._endTimers[id[i]] = setTimeout(self._ended.bind(self, sound), timeout); + } + } + } else { + sound = self._soundById(id); + return sound ? sound._rate : self._rate; + } + + return self; + }, + + /** + * Get/set the seek position of a sound. This method can optionally take 0, 1 or 2 arguments. + * seek() -> Returns the first sound node's current seek position. + * seek(id) -> Returns the sound id's current seek position. + * seek(seek) -> Sets the seek position of the first sound node. + * seek(seek, id) -> Sets the seek position of passed sound id. + * @return {Howl/Number} Returns self or the current seek position. + */ + seek: function() { + var self = this; + var args = arguments; + var seek, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current position of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new seek position. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + id = self._sounds[0]._id; + seek = parseFloat(args[0]); + } + } else if (args.length === 2) { + seek = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // If there is no ID, bail out. + if (typeof id === 'undefined') { + return self; + } + + // Wait for the sound to load before seeking it. + if (!self._loaded) { + self.once('load', function() { + self.seek.apply(self, args); + }); + + return self; + } + + // Get the sound. + var sound = self._soundById(id); + + if (sound) { + if (seek >= 0) { + // Pause the sound and update position for restarting playback. + var playing = self.playing(id); + if (playing) { + self.pause(id, true); + } + + // Move the position of the track and cancel timer. + sound._seek = seek; + self._clearTimer(id); + + // Restart the playback if the sound was playing. + if (playing) { + self.play(id, true); + } + } else { + if (self._webAudio) { + return (sound._seek + (self.playing(id) ? ctx.currentTime - sound._playStart : 0)); + } else { + return sound._node.currentTime; + } + } + } + + return self; + }, + + /** + * Check if a specific sound is currently playing or not. + * @param {Number} id The sound id to check. If none is passed, first sound is used. + * @return {Boolean} True if playing and false if not. + */ + playing: function(id) { + var self = this; + var sound = self._soundById(id) || self._sounds[0]; + + return sound ? !sound._paused : false; + }, + + /** + * Get the duration of this sound. + * @return {Number} Audio duration. + */ + duration: function() { + return this._duration; + }, + + /** + * Unload and destroy the current Howl object. + * This will immediately stop all sound instances attached to this group. + */ + unload: function() { + var self = this; + + // Stop playing any active sounds. + var sounds = self._sounds; + for (var i=0; i<sounds.length; i++) { + // Stop the sound if it is currently playing. + if (!sounds[i]._paused) { + self.stop(sounds[i]._id); + self._emit('end', sounds[i]._id); + } + + // Remove the source or disconnect. + if (!self._webAudio) { + // Set the source to an empty string to stop any downloading. + sounds[i]._node.src = ''; + + // Remove any event listeners. + sounds[i]._node.removeEventListener('error', sounds[i]._errorFn, false); + sounds[i]._node.removeEventListener('canplaythrough', sounds[i]._loadFn, false); + } + + // Empty out all of the nodes. + delete sounds[i]._node; + + // Make sure all timers are cleared out. + self._clearTimer(sounds[i]._id); + + // Remove the references in the global Howler object. + var index = Howler._howls.indexOf(self); + if (index >= 0) { + Howler._howls.splice(index, 1); + } + } + + // Delete this sound from the cache. + if (cache) { + delete cache[self._src]; + } + + // Clear out `self`. + self = null; + + return null; + }, + + /** + * Listen to a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to call. + * @param {Number} id (optional) Only listen to events for this sound. + * @param {Number} once (INTERNAL) Marks event to fire only once. + * @return {Howl} + */ + on: function(event, fn, id, once) { + var self = this; + var events = self['_on' + event]; + + if (typeof fn === 'function') { + events.push(once ? {id: id, fn: fn, once: once} : {id: id, fn: fn}); + } + + return self; + }, + + /** + * Remove a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to remove. Leave empty to remove all. + * @param {Number} id (optional) Only remove events for this sound. + * @return {Howl} + */ + off: function(event, fn, id) { + var self = this; + var events = self['_on' + event]; + + if (fn) { + // Loop through event store and remove the passed function. + for (var i=0; i<events.length; i++) { + if (fn === events[i].fn && id === events[i].id) { + events.splice(i, 1); + break; + } + } + } else { + // Clear out all events of this type. + self['on' + event] = []; + } + + return self; + }, + + /** + * Listen to a custom event and remove it once fired. + * @param {String} event Event name. + * @param {Function} fn Listener to call. + * @param {Number} id (optional) Only listen to events for this sound. + * @return {Howl} + */ + once: function(event, fn, id) { + var self = this; + + // Setup the event listener. + self.on(event, fn, id, 1); + + return self; + }, + + /** + * Emit all events of a specific type and pass the sound id. + * @param {String} event Event name. + * @param {Number} id Sound ID. + * @param {Number} msg Message to go with event. + * @return {Howl} + */ + _emit: function(event, id, msg) { + var self = this; + var events = self['_on' + event]; + + // Loop through event store and fire all functions. + for (var i=0; i<events.length; i++) { + if (!events[i].id || events[i].id === id) { + setTimeout(function(fn) { + fn.call(this, id, msg); + }.bind(self, events[i].fn), 0); + + // If this event was setup with `once`, remove it. + if (events[i].once) { + self.off(event, events[i].fn, events[i].id); + } + } + } + + return self; + }, + + /** + * Fired when playback ends at the end of the duration. + * @param {Sound} sound The sound object to work with. + * @return {Howl} + */ + _ended: function(sound) { + var self = this; + var sprite = sound._sprite; + + // Should this sound loop? + var loop = !!(sound._loop || self._sprite[sprite][2]); + + // Fire the ended event. + self._emit('end', sound._id); + + // Restart the playback for HTML5 Audio loop. + if (!self._webAudio && loop) { + self.stop(sound._id).play(sound._id); + } + + // Restart this timer if on a Web Audio loop. + if (self._webAudio && loop) { + self._emit('play', sound._id); + sound._seek = sound._start || 0; + sound._playStart = ctx.currentTime; + + var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + // Mark the node as paused. + if (self._webAudio && !loop) { + sound._paused = true; + sound._ended = true; + sound._seek = sound._start || 0; + self._clearTimer(sound._id); + + // Clean up the buffer source. + sound._node.bufferSource = null; + } + + // When using a sprite, end the track. + if (!self._webAudio && !loop) { + self.stop(sound._id); + } + + return self; + }, + + /** + * Clear the end timer for a sound playback. + * @param {Number} id The sound ID. + * @return {Howl} + */ + _clearTimer: function(id) { + var self = this; + + if (self._endTimers[id]) { + clearTimeout(self._endTimers[id]); + delete self._endTimers[id]; + } + + return self; + }, + + /** + * Return the sound identified by this ID, or return null. + * @param {Number} id Sound ID + * @return {Object} Sound object or null. + */ + _soundById: function(id) { + var self = this; + + // Loop through all sounds and find the one with this ID. + for (var i=0; i<self._sounds.length; i++) { + if (id === self._sounds[i]._id) { + return self._sounds[i]; + } + } + + return null; + }, + + /** + * Return an inactive sound from the pool or create a new one. + * @return {Sound} Sound playback object. + */ + _inactiveSound: function() { + var self = this; + + self._drain(); + + // Find the first inactive node to recycle. + for (var i=0; i<self._sounds.length; i++) { + if (self._sounds[i]._ended) { + return self._sounds[i].reset(); + } + } + + // If no inactive node was found, create a new one. + return new Sound(self); + }, + + /** + * Drain excess inactive sounds from the pool. + */ + _drain: function() { + var self = this; + var limit = self._pool; + var cnt = 0; + var i = 0; + + // If there are less sounds than the max pool size, we are done. + if (self._sounds.length < limit) { + return; + } + + // Count the number of inactive sounds. + for (i=0; i<self._sounds.length; i++) { + if (self._sounds[i]._ended) { + cnt++; + } + } + + // Remove excess inactive sounds, going in reverse order. + for (i=self._sounds.length - 1; i>=0; i--) { + if (cnt <= limit) { + return; + } + + if (self._sounds[i]._ended) { + // Disconnect the audio source when using Web Audio. + if (self._webAudio && self._sounds[i]._node) { + self._sounds[i]._node.disconnect(0); + } + + // Remove sounds until we have the pool size. + self._sounds.splice(i, 1); + cnt--; + } + } + }, + + /** + * Get all ID's from the sounds pool. + * @param {Number} id Only return one ID if one is passed. + * @return {Array} Array of IDs. + */ + _getSoundIds: function(id) { + var self = this; + + if (typeof id === 'undefined') { + var ids = []; + for (var i=0; i<self._sounds.length; i++) { + ids.push(self._sounds[i]._id); + } + + return ids; + } else { + return [id]; + } + }, + + /** + * Load the sound back into the buffer source. + * @param {Sound} sound The sound object to work with. + * @return {Howl} + */ + _refreshBuffer: function(sound) { + var self = this; + + // Setup the buffer source for playback. + sound._node.bufferSource = ctx.createBufferSource(); + sound._node.bufferSource.buffer = cache[self._src]; + + // Connect to the correct node. + if (sound._panner) { + sound._node.bufferSource.connect(sound._panner); + } else { + sound._node.bufferSource.connect(sound._node); + } + + // Setup looping and playback rate. + sound._node.bufferSource.loop = sound._loop; + if (sound._loop) { + sound._node.bufferSource.loopStart = sound._start || 0; + sound._node.bufferSource.loopEnd = sound._stop; + } + sound._node.bufferSource.playbackRate.value = self._rate; + + return self; + } + }; + + /** Single Sound Methods **/ + /***************************************************************************/ + + /** + * Setup the sound object, which each node attached to a Howl group is contained in. + * @param {Object} howl The Howl parent group. + */ + var Sound = function(howl) { + this._parent = howl; + this.init(); + }; + Sound.prototype = { + /** + * Initialize a new Sound object. + * @return {Sound} + */ + init: function() { + var self = this; + var parent = self._parent; + + // Setup the default parameters. + self._muted = parent._muted; + self._loop = parent._loop; + self._volume = parent._volume; + self._muted = parent._muted; + self._rate = parent._rate; + self._seek = 0; + self._paused = true; + self._ended = true; + + // Generate a unique ID for this sound. + self._id = Math.round(Date.now() * Math.random()); + + // Add itself to the parent's pool. + parent._sounds.push(self); + + // Create the new node. + self.create(); + + return self; + }, + + /** + * Create and setup a new sound object, whether HTML5 Audio or Web Audio. + * @return {Sound} + */ + create: function() { + var self = this; + var parent = self._parent; + var volume = (Howler._muted || self._muted || self._parent._muted) ? 0 : self._volume * Howler.volume(); + + if (parent._webAudio) { + // Create the gain node for controlling volume (the source will connect to this). + self._node = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain(); + self._node.gain.setValueAtTime(volume, ctx.currentTime); + self._node.paused = true; + self._node.connect(masterGain); + } else { + self._node = new Audio(); + + // Listen for errors (http://dev.w3.org/html5/spec-author-view/spec.html#mediaerror). + self._errorFn = self._errorListener.bind(self); + self._node.addEventListener('error', self._errorFn, false); + + // Listen for 'canplaythrough' event to let us know the sound is ready. + self._loadFn = self._loadListener.bind(self); + self._node.addEventListener('canplaythrough', self._loadFn, false); + + // Setup the new audio node. + self._node.src = parent._src; + self._node.preload = 'auto'; + self._node.volume = volume; + + // Begin loading the source. + self._node.load(); + } + + return self; + }, + + /** + * Reset the parameters of this sound to the original state (for recycle). + * @return {Sound} + */ + reset: function() { + var self = this; + var parent = self._parent; + + // Reset all of the parameters of this sound. + self._muted = parent._muted; + self._loop = parent._loop; + self._volume = parent._volume; + self._muted = parent._muted; + self._rate = parent._rate; + self._seek = 0; + self._paused = true; + self._ended = true; + self._sprite = null; + + // Generate a new ID so that it isn't confused with the previous sound. + self._id = Math.round(Date.now() * Math.random()); + + return self; + }, + + /** + * HTML5 Audio error listener callback. + */ + _errorListener: function() { + var self = this; + + if (self._node.error && self._node.error.code === 4) { + Howler.noAudio = true; + } + + // Fire an error event and pass back the code. + self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0); + + // Clear the event listener. + self._node.removeEventListener('error', self._errorListener, false); + }, + + /** + * HTML5 Audio canplaythrough listener callback. + */ + _loadListener: function() { + var self = this; + var parent = self._parent; + + // Round up the duration to account for the lower precision in HTML5 Audio. + parent._duration = Math.ceil(self._node.duration * 10) / 10; + + // Setup a sprite if none is defined. + if (Object.keys(parent._sprite).length === 0) { + parent._sprite = {__default: [0, parent._duration * 1000]}; + } + + if (!parent._loaded) { + parent._loaded = true; + parent._emit('load'); + } + + if (parent._autoplay) { + parent.play(); + } + + // Clear the event listener. + self._node.removeEventListener('canplaythrough', self._loadFn, false); + } + }; + + /** Helper Methods **/ + /***************************************************************************/ + + // Only define these methods when using Web Audio. + if (usingWebAudio) { + + var cache = {}; + + /** + * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API). + * @param {Howl} self + */ + var loadBuffer = function(self) { + var url = self._src; + + // Check if the buffer has already been cached and use it instead. + if (cache[url]) { + // Set the duration from the cache. + self._duration = cache[url].duration; + + // Load the sound into this Howl. + loadSound(self); + + return; + } + + if (/^data:[^;]+;base64,/.test(url)) { + // Setup polyfill for window.atob to support IE9. + // Modified from: https://github.com/davidchambers/Base64.js + window.atob = window.atob || function(input) { + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var str = String(input).replace(/=+$/, ''); + for ( + var bc = 0, bs, buffer, idx = 0, output = ''; + buffer = str.charAt(idx++); + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + buffer = chars.indexOf(buffer); + } + + return output; + }; + + // Decode the base64 data URI without XHR, since some browsers don't support it. + var data = atob(url.split(',')[1]); + var dataView = new Uint8Array(data.length); + for (var i=0; i<data.length; ++i) { + dataView[i] = data.charCodeAt(i); + } + + decodeAudioData(dataView.buffer, self); + } else { + // Load the buffer from the URL. + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function() { + decodeAudioData(xhr.response, self); + }; + xhr.onerror = function() { + // If there is an error, switch to HTML5 Audio. + if (self._webAudio) { + self._html5 = true; + self._webAudio = false; + self._sounds = []; + delete cache[url]; + self.load(); + } + }; + safeXhrSend(xhr); + } + }; + + /** + * Send the XHR request wrapped in a try/catch. + * @param {Object} xhr XHR to send. + */ + var safeXhrSend = function(xhr) { + try { + xhr.send(); + } catch (e) { + xhr.onerror(); + } + }; + + /** + * Decode audio data from an array buffer. + * @param {ArrayBuffer} arraybuffer The audio data. + * @param {Howl} self + */ + var decodeAudioData = function(arraybuffer, self) { + // Decode the buffer into an audio source. + ctx.decodeAudioData(arraybuffer, function(buffer) { + if (buffer) { + cache[self._src] = buffer; + loadSound(self, buffer); + } + }, function() { + self._emit('loaderror', null, 'Decoding audio data failed.'); + }); + }; + + /** + * Sound is now loaded, so finish setting everything up and fire the loaded event. + * @param {Howl} self + * @param {Object} buffer The decoded buffer sound source. + */ + var loadSound = function(self, buffer) { + // Set the duration. + if (buffer && !self._duration) { + self._duration = buffer.duration; + } + + // Setup a sprite if none is defined. + if (Object.keys(self._sprite).length === 0) { + self._sprite = {__default: [0, self._duration * 1000]}; + } + + // Fire the loaded event. + if (!self._loaded) { + self._loaded = true; + self._emit('load'); + } + + // Begin playback if specified. + if (self._autoplay) { + self.play(); + } + }; + + } + + /** + * Setup the audio context when available, or switch to HTML5 Audio mode. + */ + function setupAudioContext() { + try { + if (typeof AudioContext !== 'undefined') { + ctx = new AudioContext(); + } else if (typeof webkitAudioContext !== 'undefined') { + ctx = new webkitAudioContext(); + } else { + usingWebAudio = false; + } + } catch(e) { + usingWebAudio = false; + } + + if (!usingWebAudio) { + if (typeof Audio !== 'undefined') { + try { + new Audio(); + } catch(e) { + noAudio = true; + } + } else { + noAudio = true; + } + } + + // Test to make sure audio isn't disabled in Internet Explorer + try { + var test = new Audio(); + if (test.muted) { + noAudio = true; + } + } catch (e) {} + } + + // Add support for AMD (Asynchronous Module Definition) libraries such as require.js. + if (typeof define === 'function' && define.amd) { + define('howler', function() { + return { + Howler: Howler, + Howl: Howl + }; + }); + } + + // Add support for CommonJS libraries such as browserify. + if (typeof exports !== 'undefined') { + exports.Howler = Howler; + exports.Howl = Howl; + } + + // Define globally in case AMD is not available or unused. + if (typeof window !== 'undefined') { + window.HowlerGlobal = HowlerGlobal; + window.Howler = Howler; + window.Howl = Howl; + window.Sound = Sound; + } +})(); diff --git a/howler.js-2.0.0-beta4/src/plugins/howler.effects.js b/howler.js-2.0.0-beta4/src/plugins/howler.effects.js new file mode 100644 index 0000000..116a184 --- /dev/null +++ b/howler.js-2.0.0-beta4/src/plugins/howler.effects.js @@ -0,0 +1,603 @@ +/*! + * Effects Plugin - Adds advanced Web Audio API functionality. + * + * howler.js v2.0.0-beta4 + * howlerjs.com + * + * (c) 2013-2015, James Simpson of GoldFire Studios + * goldfirestudios.com + * + * MIT License + */ + +(function() { + + 'use strict'; + + /** Global Methods **/ + /***************************************************************************/ + + /** + * Add new properties to the core global init. + * @param {Function} _super Core global init method. + * @return {Howl} + */ + HowlerGlobal.prototype.init = (function(_super) { + return function() { + var self = this; + + // Setup default effects properties. + self._pos = [0, 0, 0]; + self._orientation = [0, 0, -1, 0, 1, 0]; + self._velocity = [0, 0, 0]; + self._listenerAttr = { + dopplerFactor: 1, + speedOfSound: 343.3 + }; + + // Complete initilization with howler.js core's global init function. + return _super.call(this, o); + }; + })(HowlerGlobal.prototype.init); + + /** + * Get/set the position of the listener in 3D cartesian space. Sounds using + * 3D position will be relative to the listener's position. + * @param {Number} x The x-position of the listener. + * @param {Number} y The y-position of the listener. + * @param {Number} z The z-position of the listener. + * @return {Howler/Array} Self or current listener position. + */ + HowlerGlobal.prototype.pos = function(x, y, z) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self.ctx || !self.ctx.listener) { + return self; + } + + // Set the defaults for optional 'y' & 'z'. + y = (typeof y !== 'number') ? self._pos[1] : y; + z = (typeof z !== 'number') ? self._pos[2] : z; + + if (typeof x === 'number') { + self._pos = [x, y, z]; + self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]); + } else { + return self._pos; + } + + return self; + }; + + /** + * Get/set the direction the listener is pointing in the 3D cartesian space. + * A front and up vector must be provided. The front is the direction the + * face of the listener is pointing, and up is the direction the top of the + * listener is pointing. Thus, these values are expected to be at right angles + * from each other. + * @param {Number} x The x-orientation of the listener. + * @param {Number} y The y-orientation of the listener. + * @param {Number} z The z-orientation of the listener. + * @param {Number} xUp The x-orientation of the top of the listener. + * @param {Number} yUp The y-orientation of the top of the listener. + * @param {Number} zUp The z-orientation of the top of the listener. + * @return {Howler/Array} Returns self or the current orientation vectors. + */ + HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self.ctx || !self.ctx.listener) { + return self; + } + + // Set the defaults for optional 'y' & 'z'. + var or = self._orientation; + y = (typeof y !== 'number') ? or[1] : y; + z = (typeof z !== 'number') ? or[2] : z; + xUp = (typeof xUp !== 'number') ? or[3] : xUp; + yUp = (typeof yUp !== 'number') ? or[4] : yUp; + zUp = (typeof zUp !== 'number') ? or[5] : zUp; + + if (typeof x === 'number') { + self._orientation = [x, y, z, xUp, yUp, zUp]; + self.ctx.listener.setOrientation(or[0], or[1], or[2], or[3], or[4], or[5]); + } else { + return or; + } + + return self; + }; + + /** + * Get/set the velocity vector of the listener. This controls both direction and speed + * in 3D space, and is combined relative to a sound's velocity to determine how much + * doppler shift (pitch change) to apply. + * @param {Number} x The x-velocity of the listener. + * @param {Number} y The y-velocity of the listener. + * @param {Number} z The z-velocity of the listener. + * @return {Howler/Array} Self or current listener velocity. + */ + HowlerGlobal.prototype.velocity = function(x, y, z) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self.ctx || !self.ctx.listener) { + return self; + } + + // Set the defaults for optional 'y' & 'z'. + y = (typeof y !== 'number') ? self._velocity[1] : y; + z = (typeof z !== 'number') ? self._velocity[2] : z; + + if (typeof x === 'number') { + self._velocity = [x, y, z]; + self.ctx.listener.setVelocity(self._velocity[0], self._velocity[1], self._velocity[2]); + } else { + return self._velocity; + } + + return self; + }; + + /** + * Get/set the audio listener attributes. + * Attributes: + * dopplerFactor - (`1` by default) Determines the amount of pitch shift from doppler effect. + * speedOfSound - (`343.3` by default) Speed of sound used to calculate doppler shift. + * @param {Object} o The attributes to set. + * @return {Howl/Object} Returns self or current listener attributes. + */ + HowlerGlobal.prototype.listenerAttr = function(o) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self.ctx || !self.ctx.listener) { + return self; + } + + var la = self._listenerAttr; + if (o) { + // Update the listener attribute values. + self._listenerAttr = { + dopplerFactor: typeof o.dopplerFactor !== 'undefined' ? o.dopplerFactor : la.dopplerFactor, + speedOfSound: typeof o.speedOfSound !== 'undefined' ? o.speedOfSound : la.speedOfSound + }; + + // Apply the new values. + self.ctx.listener.dopplerFactor = la.dopplerFactor; + self.ctx.listener.speedOfSound = la.speedOfSound; + } else { + return la; + } + + return self; + }; + + /** Group Methods **/ + /***************************************************************************/ + + /** + * Add new properties to the core init. + * @param {Function} _super Core init method. + * @return {Howl} + */ + Howl.prototype.init = (function(_super) { + return function(o) { + var self = this; + + // Setup user-defined default properties. + self._orientation = o.orientation || [1, 0, 0]; + self._pos = o.pos || null; + self._velocity = o.velocity || [0, 0, 0]; + self._pannerAttr = { + coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : 360, + coneOUterAngle: typeof o.coneOUterAngle !== 'undefined' ? o.coneOUterAngle : 360, + coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : 0, + distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : 'inverse', + maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : 10000, + panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : 'HRTF', + refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : 1, + rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : 1 + }; + + // Complete initilization with howler.js core's init function. + return _super.call(this, o); + }; + })(Howl.prototype.init); + + /** + * Get/set the 3D spatial position of the audio source for this sound or + * all in the group. The most common usage is to set the 'x' position for + * left/right panning. Setting any value higher than 1.0 will begin to + * decrease the volume of the sound as it moves further away. + * @param {Number} x The x-position of the audio from -1000.0 to 1000.0. + * @param {Number} y The y-position of the audio from -1000.0 to 1000.0. + * @param {Number} z The z-position of the audio from -1000.0 to 1000.0. + * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated. + * @return {Howl/Array} Returns self or the current 3D spatial position: [x, y, z]. + */ + Howl.prototype.pos = function(x, y, z, id) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self._webAudio) { + return self; + } + + // Wait for the sound to play before changing position. + if (!self._loaded) { + self.once('play', function() { + self.pos(x, y, z, id); + }); + + return self; + } + + // Set the defaults for optional 'y' & 'z'. + y = (typeof y !== 'number') ? 0 : y; + z = (typeof z !== 'number') ? -0.5 : z; + + // Setup the group's spatial position if no ID is passed. + if (typeof id === 'undefined') { + // Return the group's spatial position if no parameters are passed. + if (typeof x === 'number') { + self._pos = [x, y, z]; + } else { + return self._pos; + } + } + + // Change the spatial position of one or all sounds in group. + var ids = self._getSoundIds(id); + for (var i=0; i<ids.length; i++) { + // Get the sound. + var sound = self._soundById(ids[i]); + + if (sound) { + if (typeof x === 'number') { + sound._pos = [x, y, z]; + + if (sound._node) { + // Check if there is a panner setup and create a new one if not. + if (!sound._panner) { + setupPanner(sound); + } + + sound._panner.setPosition(x, y, z); + } + } else { + return sound._pos; + } + } + } + + return self; + }; + + /** + * Get/set the direction the audio source is pointing in the 3D cartesian coordinate + * space. Depending on how direction the sound is, based on the `cone` attributes, + * a sound pointing away from the listener can be quiet or silent. + * @param {Number} x The x-orientation of the source. + * @param {Number} y The y-orientation of the source. + * @param {Number} z The z-orientation of the source. + * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated. + * @return {Howl/Array} Returns self or the current 3D spatial orientation: [x, y, z]. + */ + Howl.prototype.orientation = function(x, y, z, id) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self._webAudio) { + return self; + } + + // Wait for the sound to play before changing orientation. + if (!self._loaded) { + self.once('play', function() { + self.orientation(x, y, z, id); + }); + + return self; + } + + // Set the defaults for optional 'y' & 'z'. + y = (typeof y !== 'number') ? self._orientation[1] : y; + z = (typeof z !== 'number') ? self._orientation[1] : z; + + // Setup the group's spatial orientation if no ID is passed. + if (typeof id === 'undefined') { + // Return the group's spatial orientation if no parameters are passed. + if (typeof x === 'number') { + self._orientation = [x, y, z]; + } else { + return self._orientation; + } + } + + // Change the spatial orientation of one or all sounds in group. + var ids = self._getSoundIds(id); + for (var i=0; i<ids.length; i++) { + // Get the sound. + var sound = self._soundById(ids[i]); + + if (sound) { + if (typeof x === 'number') { + sound._orientation = [x, y, z]; + + if (sound._node) { + // Check if there is a panner setup and create a new one if not. + if (!sound._panner) { + setupPanner(sound); + } + + sound._panner.setOrientation(x, y, z); + } + } else { + return sound._orientation; + } + } + } + + return self; + }; + + /** + * Get/set the velocity vector of the audio source or group. This controls both + * direction and speed in 3D space and is relative to the listener's velocity. + * The units are meters/second and are independent of position and orientation. + * @param {Number} x The x-velocity of the source. + * @param {Number} y The y-velocity of the source. + * @param {Number} z The z-velocity of the source. + * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated. + * @return {Howl/Array} Returns self or the current 3D spatial velocity: [x, y, z]. + */ + Howl.prototype.velocity = function(x, y, z, id) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self._webAudio) { + return self; + } + + // Wait for the sound to play before changing velocity. + if (!self._loaded) { + self.once('play', function() { + self.velocity(x, y, z, id); + }); + + return self; + } + + // Set the defaults for optional 'y' & 'z'. + y = (typeof y !== 'number') ? self._velocity[1] : y; + z = (typeof z !== 'number') ? self._velocity[1] : z; + + // Setup the group's spatial velocity if no ID is passed. + if (typeof id === 'undefined') { + // Return the group's spatial velocity if no parameters are passed. + if (typeof x === 'number') { + self._velocity = [x, y, z]; + } else { + return self._velocity; + } + } + + // Change the spatial velocity of one or all sounds in group. + var ids = self._getSoundIds(id); + for (var i=0; i<ids.length; i++) { + // Get the sound. + var sound = self._soundById(ids[i]); + + if (sound) { + if (typeof x === 'number') { + sound._velocity = [x, y, z]; + + if (sound._node) { + // Check if there is a panner setup and create a new one if not. + if (!sound._panner) { + setupPanner(sound); + } + + sound._panner.setVelocity(x, y, z); + } + } else { + return sound._velocity; + } + } + } + + return self; + }; + + /** + * Get/set the panner node's attributes for a sound or group of sounds. + * This method can optionall take 0, 1 or 2 arguments. + * pannerAttr() -> Returns the group's values. + * pannerAttr(id) -> Returns the sound id's values. + * pannerAttr(o) -> Set's the values of all sounds in this Howl group. + * pannerAttr(o, id) -> Set's the values of passed sound id. + * + * Attributes: + * coneInnerAngle - (360 by default) There will be no volume reduction inside this angle. + * coneOUterAngle - (360 by default) The volume will be reduced to a constant value of + * `coneOuterGain` outside this angle. + * coneOuterGain - (0 by default) The amount of volume reduction outside of `coneOuterAngle`. + * distanceModel - ('inverse' by default) Determines algorithm to use to reduce volume as audio moves + * away from listener. Can be `linear`, `inverse` or `exponential. + * maxDistance - (10000 by default) Volume won't reduce between source/listener beyond this distance. + * panningModel - ('HRTF' by default) Determines which spatialization algorithm is used to position audio. + * Can be `HRTF` or `equalpower`. + * refDistance - (1 by default) A reference distance for reducing volume as the source + * moves away from the listener. + * rolloffFactor - (1 by default) How quickly the volume reduces as source moves from listener. + * + * @return {Howl/Object} Returns self or current panner attributes. + */ + Howl.prototype.pannerAttr = function() { + var self = this; + var args = arguments; + var o, id, sound; + + // Stop right here if not using Web Audio. + if (!self._webAudio) { + return self; + } + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the group's panner attribute values. + return self._pannerAttr; + } else if (args.length === 1) { + if (typeof args[0] === 'object') { + o = args[0]; + + // Set the grou's panner attribute values. + if (typeof id === 'undefined') { + self._pannerAttr = { + coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : self._coneInnerAngle, + coneOUterAngle: typeof o.coneOUterAngle !== 'undefined' ? o.coneOUterAngle : self._coneOUterAngle, + coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : self._coneOuterGain, + distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : self._distanceModel, + maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : self._maxDistance, + panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : self._panningModel, + refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : self._refDistance, + rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : self._rolloffFactor + }; + } + } else { + // Return this sound's panner attribute values. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._pannerAttr : self._pannerAttr; + } + } else if (args.length === 2) { + o = args[0]; + id = parseInt(args[1], 10); + } + + // Update the values of the specified sounds. + var ids = self._getSoundIds(id); + for (var i=0; i<ids.length; i++) { + sound = self._soundById(ids[i]); + + if (sound) { + // Merge the new values into the sound. + var pa = sound._pannerAttr; + pa = { + coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : pa.coneInnerAngle, + coneOUterAngle: typeof o.coneOUterAngle !== 'undefined' ? o.coneOUterAngle : pa.coneOUterAngle, + coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : pa.coneOuterGain, + distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : pa.distanceModel, + maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : pa.maxDistance, + panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : pa.panningModel, + refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : pa.refDistance, + rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : pa.rolloffFactor + }; + + // Update the panner values or create a new panner if none exists. + var panner = sound._panner; + if (panner) { + panner.coneInnerAngle = pa.coneInnerAngle; + panner.coneOUterAngle = pa.coneOUterAngle; + panner.coneOuterGain = pa.coneOuterGain; + panner.distanceModel = pa.distanceModel; + panner.maxDistance = pa.maxDistance; + panner.panningModel = pa.panningModel; + panner.refDistance = pa.refDistance; + panner.rolloffFactor = pa.rolloffFactor; + } else { + // Make sure we have a position to setup the node with. + if (!sound._pos) { + sound._pos = self._pos || [0, 0, -0.5]; + } + + // Create a new panner node. + setupPanner(sound); + } + } + } + + return self; + }; + + /** Single Sound Methods **/ + /***************************************************************************/ + + /** + * Add new properties to the core Sound init. + * @param {Function} _super Core Sound init method. + * @return {Sound} + */ + Sound.prototype.init = (function(_super) { + return function() { + var self = this; + var parent = self._parent; + + // Setup user-defined default properties. + self._orientation = parent._orientation; + self._pos = parent._pos; + self._velocity = parent._velocity; + self._pannerAttr = parent._pannerAttr; + + // Complete initilization with howler.js core Sound's init function. + _super.call(this); + + // If a position was specified, set it up. + if (self._pos) { + parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id); + } + }; + })(Sound.prototype.init); + + /** + * Override the Sound.reset method to clean up properties from the effects plugin. + * @param {Function} _super Sound reset method. + * @return {Sound} + */ + Sound.prototype.reset = (function(_super) { + return function() { + var self = this; + var parent = self._parent; + + // Reset all effects module properties on this sound. + self._orientation = parent._orientation; + self._pos = parent._pos; + self._velocity = parent._velocity; + self._pannerAttr = parent._pannerAttr; + + // Complete resetting of the sound. + return _super.call(this); + }; + })(Sound.prototype.reset); + + /** Helper Methods **/ + /***************************************************************************/ + + /** + * Create a new panner node and save it on the sound. + * @param {Sound} sound Specific sound to setup panning on. + */ + var setupPanner = function(sound) { + // Create the new panner node. + sound._panner = Howler.ctx.createPanner(); + sound._panner.coneInnerAngle = sound._pannerAttr.coneInnerAngle; + sound._panner.coneOUterAngle = sound._pannerAttr.coneOUterAngle; + sound._panner.coneOuterGain = sound._pannerAttr.coneOuterGain; + sound._panner.distanceModel = sound._pannerAttr.distanceModel; + sound._panner.maxDistance = sound._pannerAttr.maxDistance; + sound._panner.panningModel = sound._pannerAttr.panningModel; + sound._panner.refDistance = sound._pannerAttr.refDistance; + sound._panner.rolloffFactor = sound._pannerAttr.rolloffFactor; + sound._panner.setPosition(sound._pos[0], sound._pos[1], sound._pos[2]); + sound._panner.setOrientation(sound._orientation[0], sound._orientation[1], sound._orientation[2]); + sound._panner.setVelocity(sound._velocity[0], sound._velocity[1], sound._velocity[2]); + sound._panner.connect(sound._node); + + // Update the connections. + if (!sound._paused) { + sound._parent.pause(sound._id).play(sound._id); + } + }; +})(); diff --git a/howler.js-2.0.0-beta4/tests/sound1.mp3 b/howler.js-2.0.0-beta4/tests/sound1.mp3 Binary files differnew file mode 100644 index 0000000..2f16532 --- /dev/null +++ b/howler.js-2.0.0-beta4/tests/sound1.mp3 diff --git a/howler.js-2.0.0-beta4/tests/sound1.ogg b/howler.js-2.0.0-beta4/tests/sound1.ogg Binary files differnew file mode 100644 index 0000000..1bcb546 --- /dev/null +++ b/howler.js-2.0.0-beta4/tests/sound1.ogg diff --git a/howler.js-2.0.0-beta4/tests/sound2.mp3 b/howler.js-2.0.0-beta4/tests/sound2.mp3 Binary files differnew file mode 100644 index 0000000..70aff5b --- /dev/null +++ b/howler.js-2.0.0-beta4/tests/sound2.mp3 diff --git a/howler.js-2.0.0-beta4/tests/sound2.ogg b/howler.js-2.0.0-beta4/tests/sound2.ogg Binary files differnew file mode 100644 index 0000000..9875401 --- /dev/null +++ b/howler.js-2.0.0-beta4/tests/sound2.ogg diff --git a/howler.js-2.0.0-beta4/tests/tests.html b/howler.js-2.0.0-beta4/tests/tests.html new file mode 100644 index 0000000..1ad77da --- /dev/null +++ b/howler.js-2.0.0-beta4/tests/tests.html @@ -0,0 +1,66 @@ +<!doctype html> +<html lang='en'> +<head> + <meta charset='utf-8'> + <title>howler.js tests</title> + <style type='text/css'> + html { + width: 100%; + height: 100%; + overflow: hidden; + } + + body { + width: 100%; + height: 100%; + overflow: hidden; + background: #e2e0d8; + border: 10px solid #bab6a7; + margin: 0; + padding: 0; + outline: 0; + font-family: 'Lucida Grande', Arial; + font-weight: bold; + font-size: 50px; + color: #bab6a7; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + } + + #container { + position: absolute; + width: 100%; + top: 50%; + margin-top: -30px; + text-align: center; + } + + #start { + background: #bab6a7; + border: none; + outline: 0; + padding: 20px 50px; + margin: 0; + font-family: 'Lucida Grande', Arial; + font-weight: bold; + font-size: 40px; + color: #e2e0d8; + border-radius: 5px; + cursor: pointer; + } + + #start:hover { + background: #a6a294; + } + </style> +</head> +<body> + <div id="container"> + <span id="label"></span> + <button id="start">BEGIN TESTS</button> + </div> + <script src='../src/howler.core.js'></script> + <script src='./tests.js'></script> +</body> +</html>
\ No newline at end of file diff --git a/howler.js-2.0.0-beta4/tests/tests.js b/howler.js-2.0.0-beta4/tests/tests.js new file mode 100644 index 0000000..ff6479b --- /dev/null +++ b/howler.js-2.0.0-beta4/tests/tests.js @@ -0,0 +1,452 @@ +// Cache the label for later use. +var label = document.getElementById('label'); + +// Setup the sounds to be used. +var sound1 = new Howl({ + src: ['sound1.ogg', 'sound1.mp3'] +}); + +var sound2 = new Howl({ + src: ['sound1.ogg', 'sound1.mp3'], + html5: true +}); + +var sound3 = new Howl({ + src: ['sound2.ogg', 'sound2.mp3'], + sprite: { + one: [0, 450], + two: [2000, 250], + three: [4000, 350], + four: [6000, 380], + five: [8000, 340], + beat: [10000, 11163] + } +}); + +var sound4 = new Howl({ + src: ['sound2.ogg', 'sound2.mp3'], + html5: true, + sprite: { + one: [0, 450], + two: [2000, 250], + three: [4000, 350], + four: [6000, 380], + five: [8000, 340], + beat: [10000, 11163] + } +}); + +// Define the tests to run. +var id; +var webaudio = [ + function(fn) { + sound1.once('play', function() { + label.innerHTML = 'PLAYING'; + setTimeout(fn, 2000); + }); + + id = sound1.play(); + }, + + function(fn) { + sound1.pause(id); + + label.innerHTML = 'PAUSED'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound1.play(id); + + label.innerHTML = 'RESUMING'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.stop(id); + + label.innerHTML = 'STOPPED'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound1.play(id); + + label.innerHTML = 'PLAY FROM START'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.fade(1, 0, 2000, id); + + label.innerHTML = 'FADE OUT'; + sound1.once('faded', function() { + fn(); + }, id); + }, + + function(fn) { + sound1.fade(0, 1, 2000, id); + + label.innerHTML = 'FADE IN'; + sound1.once('faded', function() { + fn(); + }, id); + }, + + function(fn) { + sound1.mute(true, id); + + label.innerHTML = 'MUTE'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound1.mute(false, id); + + label.innerHTML = 'UNMUTE'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.volume(0.5, id); + + label.innerHTML = 'HALF VOLUME'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.volume(1, id); + + label.innerHTML = 'FULL VOLUME'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.seek(0, id); + + label.innerHTML = 'SEEK TO START'; + setTimeout(fn, 2000); + }, + + function(fn) { + id = sound1.play(); + + label.innerHTML = 'PLAY 2ND'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.mute(true); + + label.innerHTML = 'MUTE GROUP'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound1.mute(false); + + label.innerHTML = 'UNMUTE GROUP'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.volume(0.5); + + label.innerHTML = 'HALF VOLUME GROUP'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound1.fade(0.5, 0, 2000); + + label.innerHTML = 'FADE OUT GROUP'; + sound1.once('faded', function() { + if (sound1._onfaded.length === 0) { + fn(); + } + }); + }, + + function(fn) { + sound1.fade(0, 1, 2000); + + label.innerHTML = 'FADE IN GROUP'; + sound1.once('faded', function() { + if (sound1._onfaded.length === 0) { + fn(); + } + }); + }, + + function(fn) { + sound1.stop(); + + label.innerHTML = 'STOP GROUP'; + setTimeout(fn, 1500); + }, + + function(fn) { + id = sound3.play('beat'); + + label.innerHTML = 'PLAY SPRITE'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound3.pause(id); + + label.innerHTML = 'PAUSE SPRITE'; + setTimeout(fn, 1000); + }, + + function(fn) { + sound3.play(id); + + label.innerHTML = 'RESUME SPRITE'; + setTimeout(fn, 1500); + }, + + function(fn) { + var sounds = ['one', 'two', 'three', 'four', 'five']; + for (var i=0; i<sounds.length; i++) { + setTimeout(function(i) { + sound3.play(sounds[i]); + }.bind(null, i), i * 500); + } + + label.innerHTML = 'MULTIPLE SPRITES'; + setTimeout(fn, 3000); + }, + + function(fn) { + var sprite = sound3.play('one'); + sound3.loop(true, sprite); + + label.innerHTML = 'LOOP SPRITE'; + setTimeout(function() { + sound3.loop(false, sprite); + fn(); + }, 3000); + }, + + function(fn) { + sound3.fade(1, 0, 2000, id); + + label.innerHTML = 'FADE OUT SPRITE'; + sound3.once('faded', function() { + fn(); + }); + } +]; + +var html5 = [ + function(fn) { + id = sound2.play(); + + label.innerHTML = 'PLAYING (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.pause(id); + + label.innerHTML = 'PAUSED (HTML5)'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound2.play(id); + + label.innerHTML = 'RESUMING (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.stop(id); + + label.innerHTML = 'STOPPED (HTML5)'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound2.play(id); + + label.innerHTML = 'PLAY FROM START (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.fade(1, 0, 2000, id); + + label.innerHTML = 'FADE OUT (HTML5)'; + sound2.once('faded', function() { + fn(); + }); + }, + + function(fn) { + sound2.fade(0, 1, 2000, id); + + label.innerHTML = 'FADE IN (HTML5)'; + sound2.once('faded', function() { + fn(); + }); + }, + + function(fn) { + sound2.mute(true, id); + + label.innerHTML = 'MUTE (HTML5)'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound2.mute(false, id); + + label.innerHTML = 'UNMUTE (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.volume(0.5, id); + + label.innerHTML = 'HALF VOLUME (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.volume(1, id); + + label.innerHTML = 'FULL VOLUME (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.seek(0, id); + + label.innerHTML = 'SEEK TO START (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + id = sound2.play(); + + label.innerHTML = 'PLAY 2ND (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.mute(true); + + label.innerHTML = 'MUTE GROUP (HTML5)'; + setTimeout(fn, 1500); + }, + + function(fn) { + sound2.mute(false); + + label.innerHTML = 'UNMUTE GROUP (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.volume(0.5); + + label.innerHTML = 'HALF VOLUME GROUP (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound2.fade(0.5, 0, 2000); + + label.innerHTML = 'FADE OUT GROUP (HTML5)'; + sound2.once('faded', function() { + if (sound2._onfaded.length === 0) { + fn(); + } + }); + }, + + function(fn) { + sound2.fade(0, 1, 2000); + + label.innerHTML = 'FADE IN GROUP (HTML5)'; + sound2.once('faded', function() { + if (sound2._onfaded.length === 0) { + fn(); + } + }); + }, + + function(fn) { + sound2.stop(); + + label.innerHTML = 'STOP GROUP (HTML5)'; + setTimeout(fn, 1500); + }, + + function(fn) { + id = sound4.play('beat'); + + label.innerHTML = 'PLAY SPRITE (HTML5)'; + setTimeout(fn, 2000); + }, + + function(fn) { + sound4.pause(id); + + label.innerHTML = 'PAUSE SPRITE (HTML5)'; + setTimeout(fn, 1000); + }, + + function(fn) { + sound4.play(id); + + label.innerHTML = 'RESUME SPRITE (HTML5)'; + setTimeout(fn, 1500); + }, + + function(fn) { + var sounds = ['one', 'two', 'three', 'four', 'five']; + for (var i=0; i<sounds.length; i++) { + setTimeout(function(i) { + sound4.play(sounds[i]); + }.bind(null, i), i * 500); + } + + label.innerHTML = 'MULTIPLE SPRITES (HTML5)'; + setTimeout(fn, 3000); + }, + + function(fn) { + sound4.fade(1, 0, 2000, id); + + label.innerHTML = 'FADE OUT SPRITE (HTML5)'; + sound4.once('faded', function() { + fn(); + }); + } +]; + +// If Web Audio is available, use both tets; otherwise, just hTML5. +var tests = Howler.usingWebAudio ? webaudio.concat(html5) : html5; + +// Create a method that will call the next in the series. +var chain = function(i) { + return function() { + if (tests[i]) { + tests[i](chain(++i)); + } else { + label.innerHTML = 'COMPLETE!'; + label.style.color = '#57c263'; + } + }; +}; + +// Listen to a click on the button to being the tests. +var start = document.getElementById('start'); +start.addEventListener('click', function() { + tests[0](chain(1)); + start.style.display = 'none'; +}, false);
\ No newline at end of file |
