mirror of
https://github.com/IllusionMan1212/adventures-of-kuroneko.git
synced 2026-10-24 02:12:36 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,516 @@
|
||||
ig.module(
|
||||
'impact.sound'
|
||||
)
|
||||
.defines(function(){ "use strict";
|
||||
|
||||
ig.SoundManager = ig.Class.extend({
|
||||
clips: {},
|
||||
volume: 1,
|
||||
format: null,
|
||||
|
||||
init: function() {
|
||||
// Quick sanity check if the Browser supports the Audio tag
|
||||
if( !ig.Sound.enabled || !window.Audio ) {
|
||||
ig.Sound.enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Probe sound formats and determine the file extension to load
|
||||
var probe = new Audio();
|
||||
for( var i = 0; i < ig.Sound.use.length; i++ ) {
|
||||
var format = ig.Sound.use[i];
|
||||
if( probe.canPlayType(format.mime) ) {
|
||||
this.format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No compatible format found? -> Disable sound
|
||||
if( !this.format ) {
|
||||
ig.Sound.enabled = false;
|
||||
}
|
||||
|
||||
// Create WebAudio Context
|
||||
if( ig.Sound.enabled && ig.Sound.useWebAudio ) {
|
||||
this.audioContext = new AudioContext();
|
||||
this.boundWebAudioUnlock = this.unlockWebAudio.bind(this);
|
||||
document.addEventListener('touchstart', this.boundWebAudioUnlock, false);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
unlockWebAudio: function() {
|
||||
document.removeEventListener('touchstart', this.boundWebAudioUnlock, false);
|
||||
|
||||
// create empty buffer
|
||||
var buffer = this.audioContext.createBuffer(1, 1, 22050);
|
||||
var source = this.audioContext.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
|
||||
source.connect(this.audioContext.destination);
|
||||
source.start(0);
|
||||
},
|
||||
|
||||
load: function( path, multiChannel, loadCallback ) {
|
||||
if( multiChannel && ig.Sound.useWebAudio ) {
|
||||
// Requested as Multichannel and we're using WebAudio?
|
||||
return this.loadWebAudio( path, multiChannel, loadCallback );
|
||||
}
|
||||
else {
|
||||
// Oldschool HTML5 Audio - always used for Music
|
||||
return this.loadHTML5Audio( path, multiChannel, loadCallback );
|
||||
}
|
||||
},
|
||||
|
||||
loadWebAudio: function( path, multiChannel, loadCallback ) {
|
||||
// Path to the soundfile with the right extension (.ogg or .mp3)
|
||||
var realPath = ig.prefix + path.replace(/[^\.]+$/, this.format.ext) + ig.nocache;
|
||||
|
||||
if( this.clips[path] ) {
|
||||
return this.clips[path];
|
||||
}
|
||||
|
||||
var audioSource = new ig.Sound.WebAudioSource()
|
||||
this.clips[path] = audioSource;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', realPath, true);
|
||||
request.responseType = 'arraybuffer';
|
||||
|
||||
|
||||
var that = this;
|
||||
request.onload = function(ev) {
|
||||
that.audioContext.decodeAudioData(request.response,
|
||||
function(buffer) {
|
||||
audioSource.buffer = buffer;
|
||||
loadCallback( path, true, ev );
|
||||
},
|
||||
function(ev) {
|
||||
loadCallback( path, false, ev );
|
||||
}
|
||||
);
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
loadCallback( path, false, ev );
|
||||
};
|
||||
request.send();
|
||||
|
||||
return audioSource;
|
||||
},
|
||||
|
||||
loadHTML5Audio: function( path, multiChannel, loadCallback ) {
|
||||
|
||||
// Path to the soundfile with the right extension (.ogg or .mp3)
|
||||
var realPath = ig.prefix + path.replace(/[^\.]+$/, this.format.ext) + ig.nocache;
|
||||
|
||||
// Sound file already loaded?
|
||||
if( this.clips[path] ) {
|
||||
// Loaded as WebAudio, but now requested as HTML5 Audio? Probably Music?
|
||||
if( this.clips[path] instanceof ig.Sound.WebAudioSource ) {
|
||||
return this.clips[path];
|
||||
}
|
||||
|
||||
// Only loaded as single channel and now requested as multichannel?
|
||||
if( multiChannel && this.clips[path].length < ig.Sound.channels ) {
|
||||
for( var i = this.clips[path].length; i < ig.Sound.channels; i++ ) {
|
||||
var a = new Audio( realPath );
|
||||
a.load();
|
||||
this.clips[path].push( a );
|
||||
}
|
||||
}
|
||||
return this.clips[path][0];
|
||||
}
|
||||
|
||||
var clip = new Audio( realPath );
|
||||
if( loadCallback ) {
|
||||
|
||||
// The canplaythrough event is dispatched when the browser determines
|
||||
// that the sound can be played without interuption, provided the
|
||||
// download rate doesn't change.
|
||||
// Mobile browsers stubbornly refuse to preload HTML5, so we simply
|
||||
// ignore the canplaythrough event and immediately "fake" a successful
|
||||
// load callback
|
||||
if( ig.ua.mobile ) {
|
||||
setTimeout(function(){
|
||||
loadCallback( path, true, null );
|
||||
}, 0);
|
||||
}
|
||||
else {
|
||||
clip.addEventListener( 'canplaythrough', function cb(ev){
|
||||
clip.removeEventListener('canplaythrough', cb, false);
|
||||
loadCallback( path, true, ev );
|
||||
}, false );
|
||||
|
||||
clip.addEventListener( 'error', function(ev){
|
||||
loadCallback( path, false, ev );
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
clip.preload = 'auto';
|
||||
clip.load();
|
||||
|
||||
|
||||
this.clips[path] = [clip];
|
||||
if( multiChannel ) {
|
||||
for( var i = 1; i < ig.Sound.channels; i++ ) {
|
||||
var a = new Audio(realPath);
|
||||
a.load();
|
||||
this.clips[path].push( a );
|
||||
}
|
||||
}
|
||||
|
||||
return clip;
|
||||
},
|
||||
|
||||
|
||||
get: function( path ) {
|
||||
// Find and return a channel that is not currently playing
|
||||
var channels = this.clips[path];
|
||||
|
||||
// Is this a WebAudio source? We only ever have one for each Sound
|
||||
if( channels && channels instanceof ig.Sound.WebAudioSource ) {
|
||||
return channels;
|
||||
}
|
||||
|
||||
// Oldschool HTML5 Audio - find a channel that's not currently
|
||||
// playing or, if all are playing, rewind one
|
||||
for( var i = 0, clip; clip = channels[i++]; ) {
|
||||
if( clip.paused || clip.ended ) {
|
||||
if( clip.ended ) {
|
||||
clip.currentTime = 0;
|
||||
}
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
|
||||
// Still here? Pause and rewind the first channel
|
||||
channels[0].pause();
|
||||
channels[0].currentTime = 0;
|
||||
return channels[0];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.Music = ig.Class.extend({
|
||||
tracks: [],
|
||||
namedTracks: {},
|
||||
currentTrack: null,
|
||||
currentIndex: 0,
|
||||
random: false,
|
||||
|
||||
_volume: 1,
|
||||
_loop: false,
|
||||
_fadeInterval: 0,
|
||||
_fadeTimer: null,
|
||||
_endedCallbackBound: null,
|
||||
|
||||
|
||||
init: function() {
|
||||
this._endedCallbackBound = this._endedCallback.bind(this);
|
||||
|
||||
Object.defineProperty(this,"volume", {
|
||||
get: this.getVolume.bind(this),
|
||||
set: this.setVolume.bind(this)
|
||||
});
|
||||
|
||||
Object.defineProperty(this,"loop", {
|
||||
get: this.getLooping.bind(this),
|
||||
set: this.setLooping.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
add: function( music, name ) {
|
||||
if( !ig.Sound.enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var path = music instanceof ig.Sound ? music.path : music;
|
||||
|
||||
var track = ig.soundManager.load(path, false);
|
||||
|
||||
// Did we get a WebAudio Source? This is suboptimal; Music should be loaded
|
||||
// as HTML5 Audio so it can be streamed
|
||||
if( track instanceof ig.Sound.WebAudioSource ) {
|
||||
// Since this error will likely occour at game start, we stop the game
|
||||
// to not produce any more errors.
|
||||
ig.system.stopRunLoop();
|
||||
throw(
|
||||
"Sound '"+path+"' loaded as Multichannel but used for Music. " +
|
||||
"Set the multiChannel param to false when loading, e.g.: new ig.Sound(path, false)"
|
||||
);
|
||||
}
|
||||
|
||||
track.loop = this._loop;
|
||||
track.volume = this._volume;
|
||||
track.addEventListener( 'ended', this._endedCallbackBound, false );
|
||||
this.tracks.push( track );
|
||||
|
||||
if( name ) {
|
||||
this.namedTracks[name] = track;
|
||||
}
|
||||
|
||||
if( !this.currentTrack ) {
|
||||
this.currentTrack = track;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
next: function() {
|
||||
if( !this.tracks.length ) { return; }
|
||||
|
||||
this.stop();
|
||||
this.currentIndex = this.random
|
||||
? Math.floor(Math.random() * this.tracks.length)
|
||||
: (this.currentIndex + 1) % this.tracks.length;
|
||||
this.currentTrack = this.tracks[this.currentIndex];
|
||||
this.play();
|
||||
},
|
||||
|
||||
|
||||
pause: function() {
|
||||
if( !this.currentTrack ) { return; }
|
||||
this.currentTrack.pause();
|
||||
},
|
||||
|
||||
|
||||
stop: function() {
|
||||
if( !this.currentTrack ) { return; }
|
||||
this.currentTrack.pause();
|
||||
this.currentTrack.currentTime = 0;
|
||||
},
|
||||
|
||||
|
||||
play: function( name ) {
|
||||
// If a name was provided, stop playing the current track (if any)
|
||||
// and play the named track
|
||||
if( name && this.namedTracks[name] ) {
|
||||
var newTrack = this.namedTracks[name];
|
||||
if( newTrack != this.currentTrack ) {
|
||||
this.stop();
|
||||
this.currentTrack = newTrack;
|
||||
}
|
||||
}
|
||||
else if( !this.currentTrack ) {
|
||||
return;
|
||||
}
|
||||
this.currentTrack.play();
|
||||
},
|
||||
|
||||
|
||||
getLooping: function() {
|
||||
return this._loop;
|
||||
},
|
||||
|
||||
|
||||
setLooping: function( l ) {
|
||||
this._loop = l;
|
||||
for( var i in this.tracks ) {
|
||||
this.tracks[i].loop = l;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getVolume: function() {
|
||||
return this._volume;
|
||||
},
|
||||
|
||||
|
||||
setVolume: function( v ) {
|
||||
this._volume = v.limit(0,1);
|
||||
for( var i in this.tracks ) {
|
||||
this.tracks[i].volume = this._volume;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
fadeOut: function( time ) {
|
||||
if( !this.currentTrack ) { return; }
|
||||
|
||||
clearInterval( this._fadeInterval );
|
||||
this.fadeTimer = new ig.Timer( time );
|
||||
this._fadeInterval = setInterval( this._fadeStep.bind(this), 50 );
|
||||
},
|
||||
|
||||
|
||||
_fadeStep: function() {
|
||||
var v = this.fadeTimer.delta()
|
||||
.map(-this.fadeTimer.target, 0, 1, 0)
|
||||
.limit( 0, 1 )
|
||||
* this._volume;
|
||||
|
||||
if( v <= 0.01 ) {
|
||||
this.stop();
|
||||
this.currentTrack.volume = this._volume;
|
||||
clearInterval( this._fadeInterval );
|
||||
}
|
||||
else {
|
||||
this.currentTrack.volume = v;
|
||||
}
|
||||
},
|
||||
|
||||
_endedCallback: function() {
|
||||
if( this._loop ) {
|
||||
this.play();
|
||||
}
|
||||
else {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
ig.Sound = ig.Class.extend({
|
||||
path: '',
|
||||
volume: 1,
|
||||
currentClip: null,
|
||||
multiChannel: true,
|
||||
_loop: false,
|
||||
|
||||
|
||||
init: function( path, multiChannel ) {
|
||||
this.path = path;
|
||||
this.multiChannel = (multiChannel !== false);
|
||||
|
||||
Object.defineProperty(this,"loop", {
|
||||
get: this.getLooping.bind(this),
|
||||
set: this.setLooping.bind(this)
|
||||
});
|
||||
|
||||
this.load();
|
||||
},
|
||||
|
||||
getLooping: function() {
|
||||
return this._loop;
|
||||
},
|
||||
|
||||
setLooping: function( loop ) {
|
||||
this._loop = loop;
|
||||
|
||||
if( this.currentClip ) {
|
||||
this.currentClip.loop = loop;
|
||||
}
|
||||
},
|
||||
|
||||
load: function( loadCallback ) {
|
||||
if( !ig.Sound.enabled ) {
|
||||
if( loadCallback ) {
|
||||
loadCallback( this.path, true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( ig.ready ) {
|
||||
ig.soundManager.load( this.path, this.multiChannel, loadCallback );
|
||||
}
|
||||
else {
|
||||
ig.addResource( this );
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
play: function() {
|
||||
if( !ig.Sound.enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentClip = ig.soundManager.get( this.path );
|
||||
this.currentClip.loop = this._loop;
|
||||
this.currentClip.volume = ig.soundManager.volume * this.volume;
|
||||
this.currentClip.play();
|
||||
},
|
||||
|
||||
|
||||
stop: function() {
|
||||
if( this.currentClip ) {
|
||||
this.currentClip.pause();
|
||||
this.currentClip.currentTime = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Sound.WebAudioSource = ig.Class.extend({
|
||||
sources: [],
|
||||
gain: null,
|
||||
buffer: null,
|
||||
_loop: false,
|
||||
|
||||
init: function() {
|
||||
this.gain = ig.soundManager.audioContext.createGain();
|
||||
this.gain.connect(ig.soundManager.audioContext.destination);
|
||||
|
||||
Object.defineProperty(this,"loop", {
|
||||
get: this.getLooping.bind(this),
|
||||
set: this.setLooping.bind(this)
|
||||
});
|
||||
|
||||
Object.defineProperty(this,"volume", {
|
||||
get: this.getVolume.bind(this),
|
||||
set: this.setVolume.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
play: function() {
|
||||
if( !this.buffer ) { return; }
|
||||
var source = ig.soundManager.audioContext.createBufferSource();
|
||||
source.buffer = this.buffer;
|
||||
source.connect(this.gain);
|
||||
source.loop = this._loop;
|
||||
|
||||
// Add this new source to our sources array and remove it again
|
||||
// later when it has finished playing.
|
||||
var that = this;
|
||||
this.sources.push(source);
|
||||
source.onended = function(){ that.sources.erase(source); }
|
||||
|
||||
source.start(0);
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
for( var i = 0; i < this.sources.length; i++ ) {
|
||||
try{
|
||||
this.sources[i].stop();
|
||||
} catch(err){}
|
||||
}
|
||||
},
|
||||
|
||||
getLooping: function() {
|
||||
return this._loop;
|
||||
},
|
||||
|
||||
setLooping: function( loop ) {
|
||||
this._loop = loop;
|
||||
|
||||
for( var i = 0; i < this.sources.length; i++ ) {
|
||||
this.sources[i].loop = loop;
|
||||
}
|
||||
},
|
||||
|
||||
getVolume: function() {
|
||||
return this.gain.gain.value;
|
||||
},
|
||||
|
||||
setVolume: function( volume ) {
|
||||
this.gain.gain.value = volume;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ig.Sound.FORMAT = {
|
||||
MP3: {ext: 'mp3', mime: 'audio/mpeg'},
|
||||
M4A: {ext: 'm4a', mime: 'audio/mp4; codecs=mp4a'},
|
||||
OGG: {ext: 'ogg', mime: 'audio/ogg; codecs=vorbis'},
|
||||
WEBM: {ext: 'webm', mime: 'audio/webm; codecs=vorbis'},
|
||||
CAF: {ext: 'caf', mime: 'audio/x-caf'}
|
||||
};
|
||||
ig.Sound.use = [ig.Sound.FORMAT.OGG, ig.Sound.FORMAT.MP3];
|
||||
ig.Sound.channels = 4;
|
||||
ig.Sound.enabled = true;
|
||||
|
||||
ig.normalizeVendorAttribute(window, 'AudioContext');
|
||||
ig.Sound.useWebAudio = !!window.AudioContext && !window.nwf;
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user