mirror of
https://github.com/wassname/sound.js.git
synced 2026-06-27 17:50:17 +08:00
First commit
This commit is contained in:
@@ -1,4 +1,13 @@
|
|||||||
soundForGames
|
Sound for games
|
||||||
=============
|
===============
|
||||||
|
|
||||||
A micro-library to load, play and generate sound effects and music for games and interactive applications
|
"Sound for Games" is micro-library to load, play and generate sound effects and music for
|
||||||
|
games and interactive applications. At it's heart it's composed of
|
||||||
|
just two, short functions. The `makeSound` function helps you
|
||||||
|
load and play sound files (mp3, wav, ogg, and webm). The `soundEffect`
|
||||||
|
function helps you generate a wide range of sound and music effects.
|
||||||
|
These two functions are completely modular free of dependencies, so
|
||||||
|
you can
|
||||||
|
copy and paste whichever parts of them you need into your own
|
||||||
|
projects. All the code is in the `sound.js` file. Take a look at the
|
||||||
|
`index.html` file for a working example of all the features.
|
||||||
|
|||||||
+203
@@ -0,0 +1,203 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Sound for games</title>
|
||||||
|
<script src="sound.js"></script>
|
||||||
|
<body>
|
||||||
|
<p>Press the number keys to control the sounds
|
||||||
|
<br>
|
||||||
|
<br>Sounds from loaded sound files:
|
||||||
|
<br>
|
||||||
|
<br>a - Shoot sound
|
||||||
|
<br>b - Play music
|
||||||
|
<br>c - Pause music
|
||||||
|
<br>d - Restart music
|
||||||
|
<br>e - Play music from the 10 second point
|
||||||
|
<br>f - Play the bounce sound with an echo effect
|
||||||
|
<br>
|
||||||
|
<br>Generated sound effects
|
||||||
|
<br>
|
||||||
|
<br>g - Shoot
|
||||||
|
<br>h - Jump
|
||||||
|
<br>i - Explosion
|
||||||
|
<br>j - Bonus
|
||||||
|
</p>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Part 1 - Working with sound files
|
||||||
|
=================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Load the sounds
|
||||||
|
sounds.load([
|
||||||
|
"sounds/shoot.wav",
|
||||||
|
"sounds/music.wav",
|
||||||
|
"sounds/bounce.mp3",
|
||||||
|
]);
|
||||||
|
sounds.whenLoaded = setup;
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
console.log("sounds loaded");
|
||||||
|
|
||||||
|
//Create the sounds
|
||||||
|
var shoot = sounds["sounds/shoot.wav"],
|
||||||
|
music = sounds["sounds/music.wav"],
|
||||||
|
bounce = sounds["sounds/bounce.mp3"];
|
||||||
|
|
||||||
|
//Pan the shoot sound to the right
|
||||||
|
shoot.pan = 0.8;
|
||||||
|
|
||||||
|
//Make the music loop
|
||||||
|
music.loop = true;
|
||||||
|
|
||||||
|
//Set the pan to the left
|
||||||
|
music.pan = -0.8;
|
||||||
|
|
||||||
|
//Set the music volume
|
||||||
|
music.volume = 0.7;
|
||||||
|
|
||||||
|
//Set a reverb effect on the bounce sound
|
||||||
|
//arguments: duration, decay, reverse?
|
||||||
|
//music.setReverb(2, 2, false);
|
||||||
|
|
||||||
|
//Set the sound's `reverb` property to `false` to turn it off
|
||||||
|
//music.reverb = false;
|
||||||
|
|
||||||
|
//Add an echo effect to the bounce sound
|
||||||
|
//arguments: delay time, feedback time, optional frequency filtering
|
||||||
|
bounce.setEcho(0.2, 0.3, 1000);
|
||||||
|
|
||||||
|
//Set `echo` to false to turn it off
|
||||||
|
//bounce.echo = false;
|
||||||
|
|
||||||
|
//Optionally set the music playback rate to half speed
|
||||||
|
//music.playbackRate = 0.5;
|
||||||
|
|
||||||
|
|
||||||
|
//Capture the keyboard events
|
||||||
|
var a = keyboard(65),
|
||||||
|
b = keyboard(66),
|
||||||
|
c = keyboard(67),
|
||||||
|
d = keyboard(68),
|
||||||
|
e = keyboard(69),
|
||||||
|
f = keyboard(70);
|
||||||
|
|
||||||
|
//Control the sounds based on which keys are pressed
|
||||||
|
|
||||||
|
//Play the loaded shoot sound
|
||||||
|
a.press = function() { shoot.play() };
|
||||||
|
|
||||||
|
//Play the loaded music sound
|
||||||
|
b.press = function() {
|
||||||
|
if (!music.isPlaying) music.play();
|
||||||
|
console.log("music playing");
|
||||||
|
};
|
||||||
|
|
||||||
|
//Pause the music
|
||||||
|
c.press = function() {
|
||||||
|
music.pause();
|
||||||
|
console.log("music paused");
|
||||||
|
};
|
||||||
|
|
||||||
|
//Restart the music
|
||||||
|
d.press = function() {
|
||||||
|
music.restart();
|
||||||
|
console.log("music restarted");
|
||||||
|
};
|
||||||
|
|
||||||
|
//Play the music from the 10 second mark
|
||||||
|
e.press = function() {
|
||||||
|
music.playFrom(10);
|
||||||
|
console.log("music start point changed");
|
||||||
|
};
|
||||||
|
|
||||||
|
//Play the bounce sound
|
||||||
|
f.press = function() { bounce.play() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Part 2 - Working with sound effects
|
||||||
|
===================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
var g = keyboard(71),
|
||||||
|
h = keyboard(72),
|
||||||
|
i = keyboard(73),
|
||||||
|
j = keyboard(74);
|
||||||
|
|
||||||
|
g.press = function(){ shootSound() };
|
||||||
|
h.press = function(){ jumpSound() };
|
||||||
|
i.press = function(){ explosionSound() };
|
||||||
|
j.press = function(){ bonusSound() };
|
||||||
|
|
||||||
|
//The sound effect functions
|
||||||
|
|
||||||
|
//The shoot sound
|
||||||
|
function shootSound() {
|
||||||
|
soundEffect(
|
||||||
|
1046.5, //frequency
|
||||||
|
0, //attack
|
||||||
|
0.3, //decay
|
||||||
|
"sawtooth", //waveform
|
||||||
|
1, //Volume
|
||||||
|
-0.8, //pan
|
||||||
|
0, //wait before playing
|
||||||
|
1200, //pitch bend amount
|
||||||
|
false, //reverse bend
|
||||||
|
0, //random pitch range
|
||||||
|
25, //dissonance
|
||||||
|
[0.2, 0.2, 2000], //echo: [delay, feedback, filter]
|
||||||
|
undefined //reverb: [duration, decay, reverse?]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The jump sound
|
||||||
|
function jumpSound() {
|
||||||
|
soundEffect(
|
||||||
|
523.25, //frequency
|
||||||
|
0.05, //attack
|
||||||
|
0.2, //decay
|
||||||
|
"sine", //waveform
|
||||||
|
3, //volume
|
||||||
|
0.8, //pan
|
||||||
|
0, //wait before playing
|
||||||
|
600, //pitch bend amount
|
||||||
|
true, //reverse
|
||||||
|
100, //random pitch range
|
||||||
|
0, //dissonance
|
||||||
|
undefined, //echo: [delay, feedback, filter]
|
||||||
|
undefined //reverb: [duration, decay, reverse?]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The explosion sound
|
||||||
|
function explosionSound() {
|
||||||
|
soundEffect(
|
||||||
|
16, //frequency
|
||||||
|
0, //attack
|
||||||
|
1, //decay
|
||||||
|
"sawtooth", //waveform
|
||||||
|
1, //volume
|
||||||
|
0, //pan
|
||||||
|
0, //wait before playing
|
||||||
|
0, //pitch bend amount
|
||||||
|
false, //reverse
|
||||||
|
0, //random pitch range
|
||||||
|
50, //dissonance
|
||||||
|
undefined, //echo: [delay, feedback, filter]
|
||||||
|
undefined //reverb: [duration, decay, reverse?]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The bonus points sound
|
||||||
|
function bonusSound() {
|
||||||
|
//D
|
||||||
|
soundEffect(587.33, 0, 0.2, "square", 1, 0, 0);
|
||||||
|
//A
|
||||||
|
soundEffect(880, 0, 0.2, "square", 1, 0, 0.1);
|
||||||
|
//High D
|
||||||
|
soundEffect(1174.66, 0, 0.3, "square", 1, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
@@ -0,0 +1,798 @@
|
|||||||
|
/*
|
||||||
|
Prologue: Fixing the WebAudio API
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The WebAudio API is so new that it's API is not consistently implemented properly across
|
||||||
|
all modern browsers. Thankfully, Chris Wilson's Audio Context Monkey Patch script
|
||||||
|
normalizes the API for maximum compatibility.
|
||||||
|
|
||||||
|
https://github.com/cwilso/AudioContext-MonkeyPatch/blob/gh-pages/AudioContextMonkeyPatch.js
|
||||||
|
|
||||||
|
It's included here.
|
||||||
|
Thank you, Chris!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (global, exports, perf) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function fixSetTarget(param) {
|
||||||
|
if (!param) // if NYI, just return
|
||||||
|
return;
|
||||||
|
if (!param.setTargetAtTime)
|
||||||
|
param.setTargetAtTime = param.setTargetValueAtTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.hasOwnProperty('webkitAudioContext') &&
|
||||||
|
!window.hasOwnProperty('AudioContext')) {
|
||||||
|
window.AudioContext = webkitAudioContext;
|
||||||
|
|
||||||
|
if (!AudioContext.prototype.hasOwnProperty('createGain'))
|
||||||
|
AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
|
||||||
|
if (!AudioContext.prototype.hasOwnProperty('createDelay'))
|
||||||
|
AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
|
||||||
|
if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor'))
|
||||||
|
AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode;
|
||||||
|
|
||||||
|
AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain;
|
||||||
|
AudioContext.prototype.createGain = function() {
|
||||||
|
var node = this.internal_createGain();
|
||||||
|
fixSetTarget(node.gain);
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay;
|
||||||
|
AudioContext.prototype.createDelay = function(maxDelayTime) {
|
||||||
|
var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay();
|
||||||
|
fixSetTarget(node.delayTime);
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource;
|
||||||
|
AudioContext.prototype.createBufferSource = function() {
|
||||||
|
var node = this.internal_createBufferSource();
|
||||||
|
if (!node.start) {
|
||||||
|
node.start = function ( when, offset, duration ) {
|
||||||
|
if ( offset || duration )
|
||||||
|
this.noteGrainOn( when, offset, duration );
|
||||||
|
else
|
||||||
|
this.noteOn( when );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!node.stop)
|
||||||
|
node.stop = node.noteOff;
|
||||||
|
fixSetTarget(node.playbackRate);
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor;
|
||||||
|
AudioContext.prototype.createDynamicsCompressor = function() {
|
||||||
|
var node = this.internal_createDynamicsCompressor();
|
||||||
|
fixSetTarget(node.threshold);
|
||||||
|
fixSetTarget(node.knee);
|
||||||
|
fixSetTarget(node.ratio);
|
||||||
|
fixSetTarget(node.reduction);
|
||||||
|
fixSetTarget(node.attack);
|
||||||
|
fixSetTarget(node.release);
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter;
|
||||||
|
AudioContext.prototype.createBiquadFilter = function() {
|
||||||
|
var node = this.internal_createBiquadFilter();
|
||||||
|
fixSetTarget(node.frequency);
|
||||||
|
fixSetTarget(node.detune);
|
||||||
|
fixSetTarget(node.Q);
|
||||||
|
fixSetTarget(node.gain);
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (AudioContext.prototype.hasOwnProperty( 'createOscillator' )) {
|
||||||
|
AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator;
|
||||||
|
AudioContext.prototype.createOscillator = function() {
|
||||||
|
var node = this.internal_createOscillator();
|
||||||
|
if (!node.start)
|
||||||
|
node.start = node.noteOn;
|
||||||
|
if (!node.stop)
|
||||||
|
node.stop = node.noteOff;
|
||||||
|
fixSetTarget(node.frequency);
|
||||||
|
fixSetTarget(node.detune);
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(window));
|
||||||
|
|
||||||
|
//Define the audio context
|
||||||
|
var actx = new AudioContext();
|
||||||
|
|
||||||
|
/*
|
||||||
|
# sounds
|
||||||
|
All the loaded sound files are stored in this object. It has
|
||||||
|
a `load` method that manages asset loading. You can load sounds at
|
||||||
|
any time during the game by using the `sounds.load` method.
|
||||||
|
|
||||||
|
Here's how to load three sound files from the `sounds` folder and
|
||||||
|
call a `setup` method when they're finished loading:
|
||||||
|
|
||||||
|
sounds.load([
|
||||||
|
"sounds/shoot.wav",
|
||||||
|
"sounds/music.wav",
|
||||||
|
"sounds/bounce.mp3"
|
||||||
|
]);
|
||||||
|
sounds.whenLoaded = setup;
|
||||||
|
|
||||||
|
You can now acess these loaded sounds like this:
|
||||||
|
|
||||||
|
var shoot = sounds["sounds/shoot.wav"],
|
||||||
|
music = sounds["sounds/music.wav"],
|
||||||
|
bounce = sounds["sounds/bounce.mp3"];
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var sounds = {
|
||||||
|
//Properties to help track the assets being loaded.
|
||||||
|
toLoad: 0,
|
||||||
|
loaded: 0,
|
||||||
|
|
||||||
|
//File extensions for different types of sounds.
|
||||||
|
audioExtensions: ["mp3", "ogg", "wav", "webm"],
|
||||||
|
|
||||||
|
//The callback function that should run when all assets have loaded.
|
||||||
|
//Assign this when you load the fonts, like this: `assets.whenLoaded = makeSprites;`.
|
||||||
|
whenLoaded: undefined,
|
||||||
|
|
||||||
|
//The load method creates and loads all the assets. Use it like this:
|
||||||
|
//`assets.load(["images/anyImage.png", "fonts/anyFont.otf"]);`.
|
||||||
|
|
||||||
|
load: function(sources) {
|
||||||
|
console.log("Loading sounds..");
|
||||||
|
//Get a reference to this asset object so we can
|
||||||
|
//refer to it in the `forEach` loop ahead.
|
||||||
|
var self = this;
|
||||||
|
//Find the number of files that need to be loaded.
|
||||||
|
self.toLoad = sources.length;
|
||||||
|
sources.forEach(function(source){
|
||||||
|
//Find the file extension of the asset.
|
||||||
|
var extension = source.split('.').pop();
|
||||||
|
|
||||||
|
//#### Sounds
|
||||||
|
//Load audio files that have file extensions that match
|
||||||
|
//the `audioExtensions` array.
|
||||||
|
if (self.audioExtensions.indexOf(extension) !== -1) {
|
||||||
|
//Create a sound sprite.
|
||||||
|
var soundSprite = makeSound(source, self.loadHandler.bind(self));
|
||||||
|
//Get the sound file name.
|
||||||
|
soundSprite.name = source;
|
||||||
|
//If you just want to extract the file name with the
|
||||||
|
//extension, you can do it like this:
|
||||||
|
//soundSprite.name = source.split("/").pop();
|
||||||
|
//Assign the sound as a property of the assets object so
|
||||||
|
//we can access it like this: `assets["sounds/sound.mp3"]`.
|
||||||
|
self[soundSprite.name] = soundSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Display a message if the file type isn't recognized.
|
||||||
|
else {
|
||||||
|
console.log("File type not recognized: " + source);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//#### loadHandler
|
||||||
|
//The `loadHandler` will be called each time an asset finishes loading.
|
||||||
|
loadHandler: function () {
|
||||||
|
var self = this;
|
||||||
|
self.loaded += 1;
|
||||||
|
console.log(self.loaded);
|
||||||
|
//Check whether everything has loaded.
|
||||||
|
if (self.toLoad === self.loaded) {
|
||||||
|
//If it has, run the callback function that was assigned to the `whenLoaded` property
|
||||||
|
console.log("Sounds finished loading");
|
||||||
|
//Reset `loaded` and `toLoaded` so we can load more assets
|
||||||
|
//later if we want to.
|
||||||
|
self.toLoad = 0;
|
||||||
|
self.loaded = 0;
|
||||||
|
self.whenLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
#makeSound
|
||||||
|
`makeSound` creates and returns and WebAudio sound sprite.
|
||||||
|
You can use it to load a sound like this:
|
||||||
|
|
||||||
|
var anySound = makeSound("sounds/anySound.mp3", loadHandler);
|
||||||
|
|
||||||
|
(However, it's more convenient to load the sound file using
|
||||||
|
the `sounds.load` method described above.)
|
||||||
|
|
||||||
|
After the sound has been loaded you can access and use it like this:
|
||||||
|
|
||||||
|
function loadHandler() {
|
||||||
|
anySound.loop = true;
|
||||||
|
anySound.pan = 0.8;
|
||||||
|
anySound.volume = 0.5;
|
||||||
|
anySound.play();
|
||||||
|
anySound.pause();
|
||||||
|
anySound.playFrom(second);
|
||||||
|
anySound.restart();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function makeSound(source, loadHandler) {
|
||||||
|
|
||||||
|
//The sound object that this function returns.
|
||||||
|
var o = {};
|
||||||
|
|
||||||
|
//Set the default properties.
|
||||||
|
o.volumeNode = actx.createGain();
|
||||||
|
o.panNode = actx.createPanner();
|
||||||
|
o.delayNode = actx.createDelay();
|
||||||
|
o.feedbackNode = actx.createGain();
|
||||||
|
o.filterNode = actx.createBiquadFilter();
|
||||||
|
o.convolverNode = actx.createConvolver();
|
||||||
|
o.soundNode = null;
|
||||||
|
o.buffer = null;
|
||||||
|
o.source = null;
|
||||||
|
o.loop = false;
|
||||||
|
o.isPlaying = false;
|
||||||
|
|
||||||
|
//The function that should run when the sound is loaded.
|
||||||
|
o.loadHandler = undefined;
|
||||||
|
|
||||||
|
//Values for the `pan` and `volume` getters/setters.
|
||||||
|
o.panValue = 0;
|
||||||
|
o.volumeValue = 1;
|
||||||
|
|
||||||
|
//Values to help track and set the start and pause times.
|
||||||
|
o.startTime = 0;
|
||||||
|
o.startOffset = 0;
|
||||||
|
|
||||||
|
//Set the playback rate.
|
||||||
|
o.playbackRate = 1;
|
||||||
|
|
||||||
|
//Echo properties.
|
||||||
|
o.echo = false;
|
||||||
|
o.delayValue = 0.3;
|
||||||
|
o.feebackValue = 0.3;
|
||||||
|
o.filterValue = 0;
|
||||||
|
|
||||||
|
//Reverb properties
|
||||||
|
o.reverb = false;
|
||||||
|
o.reverbImpulse = null;
|
||||||
|
|
||||||
|
//The sound object's methods.
|
||||||
|
o.play = function() {
|
||||||
|
|
||||||
|
//Set the start time (it will be `0` when the sound
|
||||||
|
//first starts.
|
||||||
|
o.startTime = actx.currentTime;
|
||||||
|
|
||||||
|
//Create a sound node.
|
||||||
|
o.soundNode = actx.createBufferSource();
|
||||||
|
|
||||||
|
//Set the sound node's buffer property to the loaded sound.
|
||||||
|
o.soundNode.buffer = o.buffer;
|
||||||
|
|
||||||
|
//Set the playback rate
|
||||||
|
o.soundNode.playbackRate.value = this.playbackRate;
|
||||||
|
|
||||||
|
//Connect the sound to the pan, connect the pan to the
|
||||||
|
//volume, and connect the volume to the destination.
|
||||||
|
o.soundNode.connect(o.volumeNode);
|
||||||
|
|
||||||
|
//If there's no reverb, bypass the convolverNode
|
||||||
|
if (o.reverb === false) {
|
||||||
|
o.volumeNode.connect(o.panNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If there is reverb, connect the `convolverNode` and apply
|
||||||
|
//the impulse response
|
||||||
|
else {
|
||||||
|
o.volumeNode.connect(o.convolverNode);
|
||||||
|
o.convolverNode.connect(o.panNode);
|
||||||
|
o.convolverNode.buffer = o.reverbImpulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connect the `panNode` to the destination to complete the chain.
|
||||||
|
o.panNode.connect(actx.destination);
|
||||||
|
|
||||||
|
//Add optional echo.
|
||||||
|
if (o.echo) {
|
||||||
|
|
||||||
|
//Set the values.
|
||||||
|
o.feedbackNode.gain.value = o.feebackValue;
|
||||||
|
o.delayNode.delayTime.value = o.delayValue;
|
||||||
|
o.filterNode.frequency.value = o.filterValue;
|
||||||
|
|
||||||
|
//Create the delay loop, with optional filtering.
|
||||||
|
o.delayNode.connect(o.feedbackNode);
|
||||||
|
if (o.filterValue > 0) {
|
||||||
|
o.feedbackNode.connect(o.filterNode);
|
||||||
|
o.filterNode.connect(o.delayNode);
|
||||||
|
} else {
|
||||||
|
o.feedbackNode.connect(o.delayNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Capture the sound from the main node chain, send it to the
|
||||||
|
//delay loop, and send the final echo effect to the `panNode` which
|
||||||
|
//will then route it to the destination.
|
||||||
|
o.volumeNode.connect(o.delayNode);
|
||||||
|
o.delayNode.connect(o.panNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Will the sound loop? This can be `true` or `false`.
|
||||||
|
o.soundNode.loop = o.loop;
|
||||||
|
|
||||||
|
//Finally, use the `start` method to play the sound.
|
||||||
|
//The start time will either be `0`,
|
||||||
|
//or a later time if the sound was paused.
|
||||||
|
o.soundNode.start(
|
||||||
|
0, o.startOffset % o.buffer.duration
|
||||||
|
);
|
||||||
|
|
||||||
|
//Set `isPlaying` to `true` to help control the
|
||||||
|
//`pause` and `restart` methods.
|
||||||
|
o.isPlaying = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
o.pause = function() {
|
||||||
|
//Pause the sound if it's playing, and calculate the
|
||||||
|
//`startOffset` to save the current position.
|
||||||
|
if (o.isPlaying) {
|
||||||
|
o.soundNode.stop(0);
|
||||||
|
o.startOffset += actx.currentTime - o.startTime;
|
||||||
|
o.isPlaying = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
o.restart = function() {
|
||||||
|
//Stop the sound if it's playing, reset the start and offset times,
|
||||||
|
//then call the `play` method again.
|
||||||
|
if (o.isPlaying) {
|
||||||
|
o.soundNode.stop(0);
|
||||||
|
}
|
||||||
|
o.startOffset = 0;
|
||||||
|
o.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
o.playFrom = function(value) {
|
||||||
|
if (o.isPlaying) {
|
||||||
|
o.soundNode.stop(0);
|
||||||
|
}
|
||||||
|
o.startOffset = value;
|
||||||
|
o.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
o.setEcho = function(delayValue, feedbackValue, filterValue) {
|
||||||
|
if (delayValue === undefined) delayValue = 0.3;
|
||||||
|
if (feedbackValue === undefined) feedbackValue = 0.3;
|
||||||
|
if (filterValue === undefined) filterValue = 0;
|
||||||
|
o.delayValue = delayValue;
|
||||||
|
o.feebackValue = feedbackValue;
|
||||||
|
o.filterValue = filterValue;
|
||||||
|
o.echo = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
o.setReverb = function(duration, decay, reverse) {
|
||||||
|
if (duration === undefined) duration = 2;
|
||||||
|
if (decay === undefined) decay = 2;
|
||||||
|
if (reverse === undefined) reverse = false;
|
||||||
|
o.reverbImpulse = impulseResponse(duration, decay, reverse, actx);
|
||||||
|
o.reverb = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Volume and pan getters/setters.
|
||||||
|
Object.defineProperties(o, {
|
||||||
|
volume: {
|
||||||
|
get: function() {
|
||||||
|
return o.volumeValue;
|
||||||
|
},
|
||||||
|
set: function(value) {
|
||||||
|
o.volumeNode.gain.value = value;
|
||||||
|
o.volumeValue = value;
|
||||||
|
},
|
||||||
|
enumerable: true, configurable: true
|
||||||
|
},
|
||||||
|
pan: {
|
||||||
|
get: function() {
|
||||||
|
return o.panValue;
|
||||||
|
},
|
||||||
|
set: function(value) {
|
||||||
|
//Panner objects accept x, y and z coordinates for 3D
|
||||||
|
//sound. However, because we're only doing 2D left/right
|
||||||
|
//panning we're only interested in the x coordinate,
|
||||||
|
//the first one. However, for a natural effect, the z
|
||||||
|
//value also has to be set proportionately.
|
||||||
|
var x = value,
|
||||||
|
y = 0,
|
||||||
|
z = 1 - Math.abs(x);
|
||||||
|
o.panNode.setPosition(x, y, z);
|
||||||
|
o.panValue = value;
|
||||||
|
},
|
||||||
|
enumerable: true, configurable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//The `load` method. It will call the `loadHandler` passed
|
||||||
|
//that was passed as an argument when the sound has loaded.
|
||||||
|
o.load = function() {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
//Use xhr to load the sound file.
|
||||||
|
xhr.open("GET", source, true);
|
||||||
|
xhr.responseType = "arraybuffer";
|
||||||
|
xhr.addEventListener("load", function() {
|
||||||
|
|
||||||
|
//Decode the sound and store a reference to the buffer.
|
||||||
|
actx.decodeAudioData(
|
||||||
|
xhr.response,
|
||||||
|
function(buffer) {
|
||||||
|
o.buffer = buffer;
|
||||||
|
o.hasLoaded = true;
|
||||||
|
|
||||||
|
//This next bit is optional, but important.
|
||||||
|
//If you have a load manager in your game, call it here so that
|
||||||
|
//the sound is registered as having loaded.
|
||||||
|
if (loadHandler) {
|
||||||
|
loadHandler();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//Throw an error if the sound can't be decoded.
|
||||||
|
function(error) {
|
||||||
|
throw new Error("Audio could not be decoded: " + error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Send the request to load the file.
|
||||||
|
xhr.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Load the sound.
|
||||||
|
o.load();
|
||||||
|
|
||||||
|
//Return the sound object.
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//The `soundEffect` function that makes all these sounds
|
||||||
|
function soundEffect(
|
||||||
|
frequencyValue, //The sound's fequency pitch in Hertz
|
||||||
|
attack, //The time, in seconds, to fade the sound in
|
||||||
|
decay, //The time, in seconds, to fade the sound out
|
||||||
|
type, //waveform type: "sine", "triangle", "square", "sawtooth"
|
||||||
|
volumeValue, //The sound's maximum volume
|
||||||
|
panValue, //The speaker pan. left: -1, middle: 0, right: 1
|
||||||
|
wait, //The time, in seconds, to wait before playing the sound
|
||||||
|
pitchBendAmount, //The number of Hz in which to bend the sound's pitch down
|
||||||
|
reverse, //If `reverse` is true the pitch will bend up
|
||||||
|
randomValue, //A range, in Hz, within which to randomize the pitch
|
||||||
|
dissonance, //A value in Hz. It creates 2 dissonant frequencies above below the target pitch
|
||||||
|
echo, //An array: [delayTimeInSeconds, feedbackTimeInSeconds, filterValueInHz]
|
||||||
|
reverb //An array: [durationInSeconds, decayRateInSeconds, reverse]
|
||||||
|
) {
|
||||||
|
|
||||||
|
//Set the default values
|
||||||
|
if (frequencyValue === undefined) frequencyValue = 200;
|
||||||
|
if (attack === undefined) attack = 0;
|
||||||
|
if (decay === undefined) decay = 1;
|
||||||
|
if (type === undefined) type = "sine";
|
||||||
|
if (volumeValue === undefined) volumeValue = 1;
|
||||||
|
if (panValue === undefined) panValue = 0;
|
||||||
|
if (wait === undefined) wait = 0;
|
||||||
|
if (pitchBendAmount === undefined) pitchBendAmount = 0;
|
||||||
|
if (reverse === undefined) reverse = false;
|
||||||
|
if (randomValue === undefined) randomValue = 0;
|
||||||
|
if (dissonance === undefined) dissonance = 0;
|
||||||
|
if (echo === undefined) echo = undefined;
|
||||||
|
if (reverb === undefined) reverb = undefined;
|
||||||
|
|
||||||
|
//Create an oscillator, gain and pan nodes, and connect them
|
||||||
|
//together to the destination
|
||||||
|
var oscillator = actx.createOscillator(),
|
||||||
|
volume = actx.createGain(),
|
||||||
|
pan = actx.createPanner();
|
||||||
|
oscillator.connect(volume);
|
||||||
|
volume.connect(pan);
|
||||||
|
pan.connect(actx.destination);
|
||||||
|
|
||||||
|
//Set the supplied values
|
||||||
|
volume.gain.value = volumeValue;
|
||||||
|
pan.setPosition(panValue, 0, 1 - Math.abs(panValue));
|
||||||
|
oscillator.type = type;
|
||||||
|
|
||||||
|
//Optionally randomize the pitch. If the `randomValue` is greater
|
||||||
|
//than zero, a random pitch is selected that's within the range
|
||||||
|
//specified by `frequencyValue`. The random pitch will be either
|
||||||
|
//above or below the target frequency.
|
||||||
|
var frequency;
|
||||||
|
var randomInt = function(min, max){
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
|
};
|
||||||
|
if (randomValue > 0) {
|
||||||
|
frequency = randomInt(
|
||||||
|
frequencyValue - randomValue / 2,
|
||||||
|
frequencyValue + randomValue / 2
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
frequency = frequencyValue;
|
||||||
|
}
|
||||||
|
oscillator.frequency.value = frequency;
|
||||||
|
|
||||||
|
//Apply effects
|
||||||
|
if (attack > 0) fadeIn(volume);
|
||||||
|
fadeOut(volume);
|
||||||
|
if (pitchBendAmount > 0) pitchBend(oscillator);
|
||||||
|
if (echo) addEcho(volume);
|
||||||
|
if (reverb) addReverb(volume);
|
||||||
|
if (dissonance > 0) addDissonance();
|
||||||
|
|
||||||
|
//Play the sound
|
||||||
|
play(oscillator);
|
||||||
|
|
||||||
|
//The helper functions:
|
||||||
|
|
||||||
|
function addReverb(volumeNode) {
|
||||||
|
var convolver = actx.createConvolver();
|
||||||
|
convolver.buffer = impulseResponse(reverb[0], reverb[1], reverb[2], actx);
|
||||||
|
volumeNode.connect(convolver);
|
||||||
|
convolver.connect(pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEcho(volumeNode) {
|
||||||
|
|
||||||
|
//Create the nodes
|
||||||
|
var feedback = actx.createGain(),
|
||||||
|
delay = actx.createDelay(),
|
||||||
|
filter = actx.createBiquadFilter();
|
||||||
|
|
||||||
|
//Set their values (delay time, feedback time and filter frequency)
|
||||||
|
delay.delayTime.value = echo[0];
|
||||||
|
feedback.gain.value = echo[1];
|
||||||
|
if (echo[2]) filter.frequency.value = echo[2];
|
||||||
|
|
||||||
|
//Create the delay feedback loop, with
|
||||||
|
//optional filtering
|
||||||
|
delay.connect(feedback);
|
||||||
|
if (echo[2]) {
|
||||||
|
feedback.connect(filter);
|
||||||
|
filter.connect(delay);
|
||||||
|
} else {
|
||||||
|
feedback.connect(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connect the delay loop to the oscillator's volume
|
||||||
|
//node, and then to the destination
|
||||||
|
volumeNode.connect(delay);
|
||||||
|
|
||||||
|
//Connect the delay loop to the main sound chain's
|
||||||
|
//pan node, so that the echo effect is directed to
|
||||||
|
//the correct speaker
|
||||||
|
delay.connect(pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The `fadeIn` function
|
||||||
|
function fadeIn(volumeNode) {
|
||||||
|
|
||||||
|
//Set the volume to 0 so that you can fade
|
||||||
|
//in from silence
|
||||||
|
volumeNode.gain.value = 0;
|
||||||
|
|
||||||
|
volumeNode.gain.linearRampToValueAtTime(
|
||||||
|
0, actx.currentTime + wait
|
||||||
|
);
|
||||||
|
volumeNode.gain.linearRampToValueAtTime(
|
||||||
|
volumeValue, actx.currentTime + wait + attack
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The `fadeOut` function
|
||||||
|
function fadeOut(volumeNode) {
|
||||||
|
volumeNode.gain.linearRampToValueAtTime(
|
||||||
|
volumeValue, actx.currentTime + attack + wait
|
||||||
|
);
|
||||||
|
volumeNode.gain.linearRampToValueAtTime(
|
||||||
|
0, actx.currentTime + wait + attack + decay
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The `pitchBend` function
|
||||||
|
function pitchBend(oscillatorNode) {
|
||||||
|
//If `reverse` is true, make the note drop in frequency. Useful for
|
||||||
|
//shooting sounds
|
||||||
|
|
||||||
|
//Get the frequency of the current oscillator
|
||||||
|
var frequency = oscillatorNode.frequency.value;
|
||||||
|
|
||||||
|
//If `reverse` is true, make the sound drop in pitch
|
||||||
|
if (!reverse) {
|
||||||
|
oscillatorNode.frequency.linearRampToValueAtTime(
|
||||||
|
frequency,
|
||||||
|
actx.currentTime + wait
|
||||||
|
);
|
||||||
|
oscillatorNode.frequency.linearRampToValueAtTime(
|
||||||
|
frequency - pitchBendAmount,
|
||||||
|
actx.currentTime + wait + attack + decay
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If `reverse` is false, make the note rise in pitch. Useful for
|
||||||
|
//jumping sounds
|
||||||
|
else {
|
||||||
|
oscillatorNode.frequency.linearRampToValueAtTime(
|
||||||
|
frequency,
|
||||||
|
actx.currentTime + wait
|
||||||
|
);
|
||||||
|
oscillatorNode.frequency.linearRampToValueAtTime(
|
||||||
|
frequency + pitchBendAmount,
|
||||||
|
actx.currentTime + wait + attack + decay
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//The `addDissonance` function
|
||||||
|
function addDissonance() {
|
||||||
|
|
||||||
|
//Create two more oscillators and gain nodes
|
||||||
|
var d1 = actx.createOscillator(),
|
||||||
|
d2 = actx.createOscillator(),
|
||||||
|
d1Volume = actx.createGain(),
|
||||||
|
d2Volume = actx.createGain();
|
||||||
|
|
||||||
|
//Set the volume to the `volumeValue`
|
||||||
|
d1Volume.gain.value = volumeValue;
|
||||||
|
d2Volume.gain.value = volumeValue;
|
||||||
|
|
||||||
|
//Connect the oscillators to the gain and destination nodes
|
||||||
|
d1.connect(d1Volume);
|
||||||
|
d1Volume.connect(actx.destination);
|
||||||
|
d2.connect(d2Volume);
|
||||||
|
d2Volume.connect(actx.destination);
|
||||||
|
|
||||||
|
//Set the waveform to "sawtooth" for a harsh effect
|
||||||
|
d1.type = "sawtooth";
|
||||||
|
d2.type = "sawtooth";
|
||||||
|
|
||||||
|
//Make the two oscillators play at frequencies above and
|
||||||
|
//below the main sound's frequency. Use whatever value was
|
||||||
|
//supplied by the `dissonance` argument
|
||||||
|
d1.frequency.value = frequency + dissonance;
|
||||||
|
d2.frequency.value = frequency - dissonance;
|
||||||
|
|
||||||
|
//Fade in/out, pitch bend and play the oscillators
|
||||||
|
//to match the main sound
|
||||||
|
if (attack > 0) {
|
||||||
|
fadeIn(d1Volume);
|
||||||
|
fadeIn(d2Volume);
|
||||||
|
}
|
||||||
|
if (decay > 0) {
|
||||||
|
fadeOut(d1Volume);
|
||||||
|
fadeOut(d2Volume);
|
||||||
|
}
|
||||||
|
if (pitchBendAmount > 0) {
|
||||||
|
pitchBend(d1);
|
||||||
|
pitchBend(d2);
|
||||||
|
}
|
||||||
|
if (echo) {
|
||||||
|
addEcho(d1Volume);
|
||||||
|
addEcho(d2Volume);
|
||||||
|
}
|
||||||
|
if (reverb) {
|
||||||
|
addReverb(d1Volume);
|
||||||
|
addReverb(d2Volume);
|
||||||
|
}
|
||||||
|
play(d1);
|
||||||
|
play(d2);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The `play` function
|
||||||
|
function play(node) {
|
||||||
|
node.start(actx.currentTime + wait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
# impulseResponse
|
||||||
|
The `makeSound` and `soundEffect` functions uses `impulseResponse` to help create an optional reverb effect.
|
||||||
|
It simulates a model of sound reverberation in an acoustic space which
|
||||||
|
a convolver node can blend with the source sound.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function impulseResponse(duration, decay, reverse, actx) {
|
||||||
|
|
||||||
|
//The length of the buffer.
|
||||||
|
var length = actx.sampleRate * duration;
|
||||||
|
|
||||||
|
//Create an audio buffer (an empty sound container) to store the reverb effect.
|
||||||
|
var impulse = actx.createBuffer(2, length, actx.sampleRate);
|
||||||
|
|
||||||
|
//Use `getChannelData` to initialize empty arrays to store sound data for
|
||||||
|
//the left and right channels.
|
||||||
|
var left = impulse.getChannelData(0),
|
||||||
|
right = impulse.getChannelData(1);
|
||||||
|
|
||||||
|
//Loop through each sample-frame and fill the channel
|
||||||
|
//data with random noise.
|
||||||
|
for (var i = 0; i < length; i++){
|
||||||
|
|
||||||
|
//Apply the reverse effect, if `reverse` is `true`.
|
||||||
|
var n;
|
||||||
|
if (reverse) {
|
||||||
|
n = length - i;
|
||||||
|
} else {
|
||||||
|
n = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fill the left and right channels with random white noise which
|
||||||
|
//decays exponentially.
|
||||||
|
left[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
|
||||||
|
right[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return the `impulse`.
|
||||||
|
return impulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
# keyboard
|
||||||
|
The `keyboard` helper function creates `key` objects
|
||||||
|
that listen for keyboard events. Create a new key object like
|
||||||
|
this:
|
||||||
|
|
||||||
|
var keyObject = g.keyboard(asciiKeyCodeNumber);
|
||||||
|
|
||||||
|
Then assign `press` and `release` methods like this:
|
||||||
|
|
||||||
|
keyObject.press = function() {
|
||||||
|
//key object pressed
|
||||||
|
};
|
||||||
|
keyObject.release = function() {
|
||||||
|
//key object released
|
||||||
|
};
|
||||||
|
|
||||||
|
Keyboard objects also have `isDown` and `isUp` Booleans that you can check.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function keyboard(keyCode) {
|
||||||
|
var key = {};
|
||||||
|
key.code = keyCode;
|
||||||
|
key.isDown = false;
|
||||||
|
key.isUp = true;
|
||||||
|
key.press = undefined;
|
||||||
|
key.release = undefined;
|
||||||
|
//The `downHandler`
|
||||||
|
key.downHandler = function(event) {
|
||||||
|
if (event.keyCode === key.code) {
|
||||||
|
if (key.isUp && key.press) key.press();
|
||||||
|
key.isDown = true;
|
||||||
|
key.isUp = false;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
//The `upHandler`
|
||||||
|
key.upHandler = function(event) {
|
||||||
|
if (event.keyCode === key.code) {
|
||||||
|
if (key.isDown && key.release) key.release();
|
||||||
|
key.isDown = false;
|
||||||
|
key.isUp = true;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Attach event listeners
|
||||||
|
window.addEventListener(
|
||||||
|
"keydown", key.downHandler.bind(key), false
|
||||||
|
);
|
||||||
|
window.addEventListener(
|
||||||
|
"keyup", key.upHandler.bind(key), false
|
||||||
|
);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user