123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837 |
- "use strict";
- define( [ "module", "vwf/model" ], function( module, model ) {
-
- var context;
- var soundData = {};
- var soundGroups = {};
- var masterVolume = 1.0;
- var logger;
- var soundDriver;
- var startTime = Date.now();
- var driver = model.load( module, {
- initialize: function() {
-
-
- this.state.soundManager = {};
- soundDriver = this;
- logger = this.logger;
- try {
-
-
-
-
- if ( !window.AudioContext ) {
- window.AudioContext = window.webkitAudioContext;
- }
- context = new AudioContext();
- }
- catch( e ) {
-
- logger.warnx( "initialize", "Web Audio API is not supported in this browser." );
- }
- },
- callingMethod: function( nodeID, methodName, params ) {
- if ( nodeID !== this.state.soundManager.nodeID ) {
- return undefined;
- }
- if ( !context ) {
- return undefined;
- }
-
-
- var soundDefinition, successCallback, failureCallback, exitCallback;
- var soundName, soundNames, soundDatum, soundDefinition, soundInstance;
- var instanceIDs, instanceID, i, volume, fadeTime, fadeMethod, instanceHandle;
- var soundGroup, groupName;
- switch( methodName ) {
-
- case "loadSound":
- soundDefinition = params[ 0 ];
- successCallback = params[ 1 ];
- failureCallback = params[ 2 ];
- if ( soundDefinition === undefined ) {
- logger.errorx( "loadSound", "The 'loadSound' method requires " +
- "a definition for the sound." );
- return undefined;
- }
- soundName = soundDefinition.soundName;
- if ( soundName === undefined ) {
- logger.errorx( "loadSound", "The sound definition must contain soundName." );
- return undefined;
- }
- if ( soundData[ soundName ] !== undefined ) {
- logger.errorx( "loadSound", "Duplicate sound named '" + soundName + "'." );
- return undefined;
- }
- soundData[ soundName ] = new SoundDatum( soundDefinition,
- successCallback,
- failureCallback );
- return;
-
-
- case "isReady":
- soundDatum = getSoundDatum( params[ 0 ] );
- return soundDatum !== undefined ? !!soundDatum.buffer : false;
-
-
-
-
- case "playSound":
- soundName = params[ 0 ];
- soundDatum = getSoundDatum( soundName );
- exitCallback = params[ 1 ];
- return soundDatum ? soundDatum.playSound( exitCallback )
- : { soundName: soundName, instanceID: -1 };
- case "playSequence":
- soundNames = params;
- soundDatum = getSoundDatum( soundNames[ 0 ] );
- var playNext = function ( soundNames, current ){
- if (current !== soundNames.length){
- soundDatum = getSoundDatum( soundNames[ current ] );
- soundDatum.playSound( playNext ( soundNames, current + 1 ) );
- }
- }
- return soundDatum ? soundDatum.playSound( playNext ( soundNames, 0 ) )
- : { soundName: soundName, instanceID: - 1 };
-
-
- case "isSoundPlaying":
- soundDatum = getSoundDatum( params[ 0 ] );
- return soundDatum ? soundDatum.isPlaying() : false;
-
-
- case "isInstancePlaying":
- return getSoundInstance( params[ 0 ] ) !== undefined;
-
- case "setVolume":
- instanceHandle = params [ 0 ];
- soundDatum = getSoundDatum( instanceHandle.soundName );
- if ( soundDatum ){
- soundDatum.setVolume ( params [ 0 ], params [ 1 ], params [ 2 ], params [ 3 ] );
- }
-
-
- case "setMasterVolume":
- masterVolume = params [ 0 ];
- for ( soundName in soundData ){
- soundDatum = soundData[ soundName ];
- if ( soundDatum ) {
- soundDatum.resetOnMasterVolumeChange();
- }
- }
-
- case "hasSubtitle":
- instanceHandle = params [ 0 ];
- soundDatum = getSoundDatum( instanceHandle.soundName );
- return soundDatum ? !!soundDatum.subtitle : undefined;
-
- case "getSubtitle":
- instanceHandle = params [ 0 ];
- soundDatum = getSoundDatum( instanceHandle.soundName );
- return soundDatum ? soundDatum.subtitle : undefined;
-
-
- case "getDuration":
- instanceHandle = params[ 0 ];
- soundDatum = getSoundDatum( instanceHandle.soundName );
- return soundDatum && soundDatum.buffer ? soundDatum.buffer.duration : undefined;
-
- case "stopSoundInstance":
- instanceHandle = params[ 0 ];
-
- if ( !instanceHandle.soundName ){
- soundName = params[ 0 ];
- soundDatum = getSoundDatum( soundName );
- soundDatum && soundDatum.stopDatumSoundInstances();
- } else {
-
- soundDatum = getSoundDatum( instanceHandle.soundName );
- soundDatum && soundDatum.stopInstance( instanceHandle );
- }
- return;
-
- case "stopSoundGroup":
- groupName = params[ 0 ];
- soundGroup = soundGroups[ groupName ];
- soundGroup && soundGroup.clearQueue();
- soundGroup && soundGroup.stopPlayingSound();
- return;
-
- case "stopAllSoundInstances":
- for ( groupName in soundGroups ) {
- soundGroup = soundGroups[ groupName ];
- soundGroup && soundGroup.clearQueue();
- }
- for ( soundName in soundData ) {
- soundDatum = soundData[ soundName ];
- soundDatum && soundDatum.stopDatumSoundInstances();
- }
- return undefined;
-
- case "getSoundDefinition":
- soundDatum = getSoundDatum( params[ 0 ] );
- return soundDatum ? soundDatum.soundDefinition : undefined;
-
- }
- return undefined;
- }
- } );
- function SoundDatum( soundDefinition, successCallback, failureCallback ) {
- this.initialize( soundDefinition, successCallback, failureCallback );
- return this;
- }
- SoundDatum.prototype = {
- constructor: SoundDatum,
-
- name: "",
-
- buffer: null,
-
- playingInstances: null,
-
- initialVolume: 1.0,
- isLooping: false,
- allowMultiplay: false,
- soundDefinition: null,
- playOnLoad: false,
- subtitle: undefined,
- soundGroup: undefined,
- groupReplacementMethod: undefined,
- queueDelayTime: undefined,
-
- instanceIDCounter: 0,
- initialize: function( soundDefinition, successCallback, failureCallback ) {
- this.name = soundDefinition.soundName;
- this.playingInstances = {};
- this.soundDefinition = soundDefinition;
- if ( this.soundDefinition.isLooping !== undefined ) {
- this.isLooping = soundDefinition.isLooping;
- }
- if ( this.soundDefinition.allowMultiplay !== undefined ) {
- this.allowMultiplay = soundDefinition.allowMultiplay;
- }
- if (this.soundDefinition.initialVolume !== undefined ) {
- this.initialVolume = soundDefinition.initialVolume;
- }
- if ( this.soundDefinition.playOnLoad !== undefined ) {
- this.playOnLoad = soundDefinition.playOnLoad;
- }
- this.subtitle = this.soundDefinition.subtitle;
- var soundGroupName = this.soundDefinition.soundGroup;
- if ( soundGroupName ) {
- if ( !soundGroups[ soundGroupName ] ) {
- soundGroups[ soundGroupName ] =
- new SoundGroup( soundGroupName );
- }
- this.soundGroup = soundGroups[ soundGroupName ];
- }
- this.groupReplacementMethod = this.soundDefinition.groupReplacementMethod;
- if ( this.groupReplacementMethod && !this.soundGroup ) {
- logger.warnx( "SoundDatum.initialize",
- "You defined a replacement method but not a sound " +
- "group. Replacement is only done when you replace " +
- "another sound in the same group!" );
- }
- if ( this.soundDefinition.queueDelayTime !== undefined ) {
- this.queueDelayTime = this.soundDefinition.queueDelayTime;
- if ( this.groupReplacementMethod !== "queue" ) {
- logger.warnx( "SoundDatum.initialize",
- "You defined a queue delay time, but " +
- "the replacement method is not 'queue'.");
- }
- } else {
- this.queueDelayTime =
- this.groupReplacementMethod === "queue" ? 0.8 : 0;
- }
-
- var request = new XMLHttpRequest();
- request.open( 'GET', soundDefinition.soundURL, true );
- request.responseType = 'arraybuffer';
- var thisSoundDatum = this;
- request.onload = function() {
- context.decodeAudioData(
- request.response,
- function( buffer ) {
- thisSoundDatum.buffer = buffer;
- if ( thisSoundDatum.playOnLoad === true ) {
- thisSoundDatum.playSound( null, true );
- }
- successCallback && successCallback();
- },
- function() {
- logger.warnx( "SoundDatum.initialize", "Failed to load sound: '" +
- thisSoundDatum.name + "'." );
- delete soundData[ thisSoundDatum.name ];
- failureCallback && failureCallback();
- }
- );
- }
- request.send();
- },
- playSound: function( exitCallback ) {
- if ( !this.buffer ) {
- logger.errorx( "SoundDatum.playSound", "Sound '" + this.name + "' hasn't finished " +
- "loading, or loaded improperly." );
- return { soundName: this.name, instanceID: -1 };
- }
- if ( !this.allowMultiplay && this.isPlaying() ) {
- return { soundName: this.name,
- instanceID: this.playingInstances[ 0 ] };
- }
- var id = this.instanceIDCounter;
- ++this.instanceIDCounter;
- this.playingInstances[ id ] = new PlayingInstance( this, id, exitCallback );
- return { soundName: this.name, instanceID: id };
- },
- stopInstance: function( instanceHandle ) {
- var soundInstance = this.playingInstances[ instanceHandle.instanceID ];
- soundInstance && soundInstance.stop();
- },
- stopDatumSoundInstances: function () {
- for ( var instanceID in this.playingInstances ) {
- var soundInstance = this.playingInstances[ instanceID ];
- soundInstance && soundInstance.stop();
- }
-
-
-
- },
- resetOnMasterVolumeChange: function () {
- for ( var instanceID in this.playingInstances ) {
- var soundInstance = this.playingInstances[ instanceID ];
- if ( soundInstance ) {
- soundInstance.resetVolume();
- }
- }
- },
- setVolume: function ( instanceHandle, volume, fadeTime, fadeMethod ) {
-
- var soundInstance = getSoundInstance( instanceHandle );
- soundInstance && soundInstance.setVolume( volume, fadeTime, fadeMethod );
- },
- isPlaying: function() {
- var instanceIDs = Object.keys( this.playingInstances );
- return instanceIDs.length > 0;
- },
- }
- function PlayingInstance( soundDatum, id, exitCallback, successCallback ) {
- this.initialize( soundDatum, id, exitCallback, successCallback );
- return this;
- }
- PlayingInstance.prototype = {
- constructor: PlayingInstance,
-
- id: undefined,
-
- soundDatum: null,
-
- sourceNode: undefined,
- gainNode: undefined,
-
-
- localVolume$: undefined,
-
- playStatus: undefined,
- initialize: function( soundDatum, id, exitCallback, successCallback ) {
-
-
-
-
-
-
-
-
-
- this.id = id;
- this.soundDatum = soundDatum;
- this.playStatus = "stopped";
- this.sourceNode = context.createBufferSource();
- this.sourceNode.buffer = this.soundDatum.buffer;
- this.sourceNode.loop = this.soundDatum.isLooping;
- this.localVolume$ = this.soundDatum.initialVolume;
- this.gainNode = context.createGain();
- this.sourceNode.connect( this.gainNode );
- this.gainNode.connect( context.destination );
- this.resetVolume();
- var group = soundDatum.soundGroup;
-
-
- var thisInstance = this;
- this.sourceNode.onended = function() {
- thisInstance.playStatus = "stopped";
- fireSoundEvent( "soundFinished", thisInstance );
-
-
-
- if ( group ) {
- group.soundFinished( thisInstance );
- var nextInstance = group.unQueueSound();
- if ( nextInstance ) {
- var delaySeconds = nextInstance.soundDatum.queueDelayTime;
-
-
-
-
-
- if ( delaySeconds > 0 ) {
- nextInstance.startDelayed( delaySeconds );
- } else {
- nextInstance.start();
- }
- }
- }
- delete soundDatum.playingInstances[ id ];
- exitCallback && exitCallback();
- }
- if ( group ) {
- switch ( soundDatum.groupReplacementMethod ) {
- case "queue":
-
-
-
- if ( group.getPlayingSound() ) {
- group.queueSound( this );
- } else {
- this.start();
- }
- break;
- case "replace":
- group.clearQueue();
- group.stopPlayingSound();
- this.start();
- break;
- default:
- logger.errorx( "PlayingInstance.initialize",
- "This sound ('" +
- thisInstance.soundDatum.name +
- "') is in a group, but doesn't " +
- "have a valid replacement method!" );
- group.clearQueue();
- group.stopPlayingSound();
- this.start();
- }
- } else {
- this.start();
- }
- },
- getVolume: function() {
- return this.localVolume$ * ( masterVolume !== undefined ? masterVolume : 1.0 );
- },
- setVolume: function( volume, fadeTime, fadeMethod ) {
- if ( !volume ) {
- logger.errorx( "PlayingInstance.setVolume", "The 'setVolume' " +
- "method requires a volume." );
- return;
- }
- this.localVolume$ = volume;
- if ( !fadeTime || ( fadeTime <= 0 ) ) {
- fadeMethod = "immediate";
- }
- var now = context.currentTime;
- this.gainNode.gain.cancelScheduledValues( now );
- switch( fadeMethod ) {
- case "linear":
- var endTime = now + fadeTime;
- this.gainNode.gain.linearRampToValueAtTime( this.getVolume(),
- endTime );
- break;
- case "exponential":
- case undefined:
- this.gainNode.gain.setTargetValueAtTime( this.getVolume(),
- now, fadeTime );
- break;
- case "immediate":
- this.gainNode.gain.value = this.getVolume();
- break;
- default:
- logger.errorx( "PlayingInstance.setVolume", "Unknown fade method: '" +
- fadeMethod + "'. Using an exponential " +
- "fade." );
- this.gainNode.gain.setTargetValueAtTime( this.getVolume(),
- now, fadeTime );
- }
- },
- resetVolume: function() {
- this.setVolume(this.localVolume$);
- },
- start: function() {
- switch ( this.playStatus ) {
- case "playing":
- logger.warnx( "PlayingInstance.start",
- "Duplicate call to start. Sound: '" +
- this.soundDatum.name + "'." );
- break;
- case "stopping":
- logger.warnx( "PlayingInstance.start", "Start is being " +
- "called, but we're not done stopping yet. " +
- "Is that bad?" );
-
- case "delayed":
- case "stopped":
- var group = this.soundDatum.soundGroup;
- var playingSound = group ? group.getPlayingSound()
- : undefined;
- if ( !group ||
- !playingSound ||
- ( ( playingSound === this ) &&
- ( this.playStatus !== "stopping" ) ) ) {
- this.playStatus = "playing";
- group && group.setPlayingSound( this );
- this.sourceNode.start( 0 );
-
-
-
- fireSoundEvent( "soundStarted", this );
- } else {
- if ( ( playingSound !== this ) &&
- ( playingSound.playStatus != "stopping" ) ) {
- logger.errorx( "PlayingInstance.start",
- "We are trying to start a sound " +
- "('" + this.soundDatum.name +
- "') that is in a sound group, " +
- "but the currently playing sound " +
- "in that group ('" +
- playingSound.soundDatum.name +
- "') isn't in the process of " +
- "stopping. This is probably bad." );
- }
-
-
-
-
- group.jumpQueue( this );
- }
- break;
- case "delayCancelled":
-
-
-
-
-
-
-
- this.playStatus = "stopped";
- break;
- default:
- logger.errorx( "PlayingInstance.start", "Invalid " +
- "playStatus: '" + this.playStatus + "'!" );
- }
- },
- startDelayed: function( delaySeconds ) {
- var group = this.soundDatum.soundGroup;
- if ( group ) {
- if ( group.getPlayingSound() ) {
- logger.errorx( "PlayingInstance.startDelayed",
- "How is there already a sound playing " +
- "when startDelayed() is called?" );
- return;
- }
- group.setPlayingSound( this );
- }
- this.playStatus = "delayed";
- setTimeout( this.start.bind(this), delaySeconds * 1000 );
- },
- stop: function() {
- switch ( this.playStatus ) {
- case "playing":
- this.playStatus = "stopping";
- this.sourceNode.stop();
- break;
- case "delayed":
- this.playStatus = "delayCancelled";
- var group = this.soundDatum.soundGroup;
- if ( group ) {
- group.soundFinished( this );
- }
- break;
- case "delayCancelled":
- case "stopping":
- case "stopped":
- logger.warnx( "PlayingInstance.stop", "Duplicate call " +
- "to stop (or it was never started). " +
- "Sound: '" + this.soundDatum.name + "'." );
- break;
- default:
- logger.errorx( "PlayingInstance.stop", "Invalid " +
- "playStatus: '" + this.playStatus + "'!" );
- }
- },
- }
- function SoundGroup( groupName ) {
- this.initialize( groupName );
- return this;
- }
- SoundGroup.prototype = {
- constructor: SoundGroup,
-
-
- name$: "PROTOTYPE",
- queue$: "PROTOTYPE",
- playingSound$: "PROTOTYPE",
- initialize: function( groupName ) {
- this.name$ = groupName;
- this.queue$ = [];
- this.playingSound$ = undefined;
- },
- getPlayingSound: function() {
- return this.playingSound$;
- },
- setPlayingSound: function( playingInstance ) {
- if ( this.playingSound$ ) {
- if ( this.playingSound$ !== playingInstance ) {
- logger.errorx( "SoundGroup.setPlayingSound",
- "Trying to set playingSound to '" +
- playingInstance.soundDatum.name +
- "', but it is already set to '" +
- this.playingSound$.soundDatum.name + "'!");
- } else if ( !this.playingSound$.playStatus !== "delayed" ) {
- logger.errorx("SoundGroup.setPlayingSound",
- "How are we re-setting the playing sound " +
- "when we're not in a delay? Sound: '" +
- this.playingSound$.soundDatum.name + "'." );
- }
- } else {
- this.playingSound$ = playingInstance;
- }
- },
- soundFinished: function( playingInstance ) {
- if ( playingInstance !== this.playingSound$ ) {
- logger.errorx( "SoundGroup.soundFinished", "'" +
- playingInstance.soundDatum.name + "' just " +
- "repored that it is finished, but we thought " +
- "that '" + this.playingSound$.soundDatum.name +
- "' was playing!");
- return;
- }
- this.playingSound$ = undefined;
- },
- stopPlayingSound: function() {
- this.playingSound$ && this.playingSound$.stop();
- },
-
- queueSound: function( playingInstance ) {
- this.queue$.unshift( playingInstance );
- },
-
- jumpQueue: function( playingInstance ) {
- this.queue$.push( playingInstance );
- },
- unQueueSound: function() {
- return this.queue$.pop();
- },
- clearQueue: function() {
- this.queue$.length = 0;
- },
- hasQueuedSounds: function() {
- return queue$.length > 0;
- },
- }
- function getSoundDatum( soundName ) {
- if ( soundName === undefined ) {
- logger.errorx( "getSoundDatum", "The 'getSoundDatum' method " +
- "requires the sound name." );
- return undefined;
- }
- var soundDatum = soundData[ soundName ];
- if ( soundDatum === undefined ) {
- logger.errorx( "getSoundDatum", "Sound '" + soundName +
- "' not found." );
- return undefined;
- }
- return soundDatum;
- }
- function getSoundInstance( instanceHandle ) {
- if ( instanceHandle === undefined ) {
- logger.errorx( "getSoundInstance", "The 'GetSoundInstance' " +
- "method requires the instance ID." );
- return undefined;
- }
- if ( ( instanceHandle.soundName === undefined ) ||
- ( instanceHandle.instanceID === undefined ) ) {
- logger.errorx( "getSoundInstance", "The instance handle must " +
- "contain soundName and instanceID values");
- return undefined;
- }
- var soundDatum = getSoundDatum( instanceHandle.soundName );
- if ( soundDatum.isLayered === true ) {
- return soundDatum;
- } else {
- return soundDatum ? soundDatum.playingInstances[ instanceHandle.instanceID ]
- : undefined;
- }
- }
- function fireSoundEvent( eventString, instance ) {
- vwf_view.kernel.fireEvent( soundDriver.state.soundManager.nodeID,
- eventString,
- [ { soundName: instance.soundDatum.name,
- instanceID: instance.id } ] );
- }
- function timestamp() {
- var delta = Date.now() - startTime;
- var minutes = Math.floor( delta / 60000 );
- var seconds = ( delta % 60000 ) / 1000;
- return "" + minutes + ":" + seconds;
- }
- return driver;
- } );
|