2017-11-17 13:17:59 +00:00
|
|
|
var Class = require('../../utils/Class');
|
|
|
|
var BaseSound = require('../BaseSound');
|
2017-11-14 15:27:22 +00:00
|
|
|
// Phaser.Sound.WebAudioSound
|
2017-11-17 16:07:04 +00:00
|
|
|
// TODO support webkitAudioContext implementation differences
|
2017-11-17 16:02:11 +00:00
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
|
2017-11-14 15:27:22 +00:00
|
|
|
var WebAudioSound = new Class({
|
|
|
|
Extends: BaseSound,
|
|
|
|
initialize: function WebAudioSound(manager, key, config) {
|
2017-11-22 16:58:05 +00:00
|
|
|
if (config === void 0) { config = {}; }
|
2017-11-14 18:30:51 +00:00
|
|
|
/**
|
|
|
|
* [description]
|
|
|
|
*
|
2017-12-04 21:09:41 +00:00
|
|
|
* @private
|
2017-11-14 18:30:51 +00:00
|
|
|
* @property {AudioBuffer} audioBuffer
|
|
|
|
*/
|
2017-11-14 19:09:44 +00:00
|
|
|
this.audioBuffer = manager.game.cache.audio.get(key);
|
2017-11-14 18:30:51 +00:00
|
|
|
if (!this.audioBuffer) {
|
|
|
|
console.error('No audio loaded in cache with key: \'' + key + '\'!');
|
|
|
|
return;
|
|
|
|
}
|
2017-11-15 15:55:13 +00:00
|
|
|
/**
|
|
|
|
* [description]
|
|
|
|
*
|
2017-12-04 21:09:41 +00:00
|
|
|
* @private
|
2017-11-15 15:55:13 +00:00
|
|
|
* @property {AudioBufferSourceNode} source
|
|
|
|
*/
|
|
|
|
this.source = null;
|
2017-11-15 14:42:37 +00:00
|
|
|
/**
|
|
|
|
* [description]
|
|
|
|
*
|
2017-12-04 21:09:41 +00:00
|
|
|
* @private
|
2017-11-15 16:37:22 +00:00
|
|
|
* @property {GainNode} muteNode
|
2017-11-15 14:42:37 +00:00
|
|
|
*/
|
2017-11-15 16:37:22 +00:00
|
|
|
this.muteNode = manager.context.createGain();
|
2017-11-15 15:02:11 +00:00
|
|
|
/**
|
|
|
|
* [description]
|
|
|
|
*
|
2017-12-04 21:09:41 +00:00
|
|
|
* @private
|
2017-11-15 16:37:22 +00:00
|
|
|
* @property {GainNode} volumeNode
|
2017-11-15 15:02:11 +00:00
|
|
|
*/
|
2017-11-15 16:37:22 +00:00
|
|
|
this.volumeNode = manager.context.createGain();
|
2017-11-17 13:57:57 +00:00
|
|
|
/**
|
2017-12-13 21:36:57 +00:00
|
|
|
* The time at which the sound should have started from the beginning.
|
|
|
|
* Based on BaseAudioContext.currentTime value.
|
2017-11-17 13:57:57 +00:00
|
|
|
*
|
2017-12-04 21:09:41 +00:00
|
|
|
* @private
|
2017-12-13 21:32:34 +00:00
|
|
|
* @property {number} playTime
|
2017-11-17 13:57:57 +00:00
|
|
|
*/
|
2017-12-13 21:32:34 +00:00
|
|
|
this.playTime = 0;
|
2017-12-13 21:39:24 +00:00
|
|
|
/**
|
2017-12-13 21:48:05 +00:00
|
|
|
* The time at which the sound source should actually start playback.
|
|
|
|
* Based on BaseAudioContext.currentTime value.
|
2017-12-13 21:39:24 +00:00
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @property {number} startTime
|
|
|
|
*/
|
|
|
|
this.startTime = 0;
|
2017-11-26 16:03:36 +00:00
|
|
|
/**
|
2017-12-05 18:09:34 +00:00
|
|
|
* An array where we keep track of all rate updates during playback.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @property {{ time: number, rate: number }[]} rateUpdates
|
|
|
|
*/
|
|
|
|
this.rateUpdates = [];
|
|
|
|
/**
|
2017-11-26 16:03:36 +00:00
|
|
|
* Used for keeping track when sound source playback has ended
|
|
|
|
* so it's state can be updated accordingly.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @property {boolean} hasEnded
|
|
|
|
*/
|
|
|
|
this.hasEnded = false;
|
2017-11-15 16:37:22 +00:00
|
|
|
this.muteNode.connect(this.volumeNode);
|
|
|
|
this.volumeNode.connect(manager.destination);
|
2017-11-22 16:54:00 +00:00
|
|
|
this.duration = this.audioBuffer.duration;
|
|
|
|
this.totalDuration = this.audioBuffer.duration;
|
2017-12-06 17:06:39 +00:00
|
|
|
BaseSound.call(this, manager, key, config);
|
2017-11-14 18:35:18 +00:00
|
|
|
},
|
2017-11-22 17:06:21 +00:00
|
|
|
play: function (markerName, config) {
|
|
|
|
if (!BaseSound.prototype.play.call(this, markerName, config)) {
|
2017-11-16 13:19:04 +00:00
|
|
|
return null;
|
2017-11-14 18:35:18 +00:00
|
|
|
}
|
2017-12-05 18:51:15 +00:00
|
|
|
// \/\/\/ isPlaying = true, isPaused = false \/\/\/
|
2017-11-17 17:38:23 +00:00
|
|
|
this.stopAndRemoveBufferSource();
|
2017-12-05 19:17:14 +00:00
|
|
|
this.createAndStartBufferSource();
|
2017-11-14 18:35:18 +00:00
|
|
|
return this;
|
2017-11-16 13:23:04 +00:00
|
|
|
},
|
|
|
|
pause: function () {
|
2017-12-13 21:43:42 +00:00
|
|
|
if (this.manager.context.currentTime < this.startTime) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-11-17 16:17:06 +00:00
|
|
|
if (!BaseSound.prototype.pause.call(this)) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-12-05 18:51:15 +00:00
|
|
|
// \/\/\/ isPlaying = false, isPaused = true \/\/\/
|
2017-12-05 19:20:33 +00:00
|
|
|
this.currentConfig.seek = this.getCurrentTime(); // Equivalent to setting paused time
|
2017-12-07 19:13:52 +00:00
|
|
|
this.stopAndRemoveBufferSource();
|
2017-11-17 16:17:06 +00:00
|
|
|
return true;
|
2017-11-16 13:23:04 +00:00
|
|
|
},
|
|
|
|
resume: function () {
|
2017-12-13 21:44:59 +00:00
|
|
|
if (this.manager.context.currentTime < this.startTime) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-11-17 16:17:06 +00:00
|
|
|
if (!BaseSound.prototype.resume.call(this)) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-12-05 18:51:15 +00:00
|
|
|
// \/\/\/ isPlaying = true, isPaused = false \/\/\/
|
2017-12-05 19:17:14 +00:00
|
|
|
this.createAndStartBufferSource();
|
2017-11-17 16:17:06 +00:00
|
|
|
return true;
|
2017-11-16 13:23:04 +00:00
|
|
|
},
|
|
|
|
stop: function () {
|
2017-11-17 16:17:06 +00:00
|
|
|
if (!BaseSound.prototype.stop.call(this)) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-12-05 18:51:15 +00:00
|
|
|
// \/\/\/ isPlaying = false, isPaused = false \/\/\/
|
2017-11-17 16:07:04 +00:00
|
|
|
this.stopAndRemoveBufferSource();
|
2017-11-17 16:17:06 +00:00
|
|
|
return true;
|
2017-11-16 13:23:04 +00:00
|
|
|
},
|
2017-11-17 16:01:12 +00:00
|
|
|
/**
|
|
|
|
* Used internally to do what the name says.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
2017-12-05 19:17:14 +00:00
|
|
|
createAndStartBufferSource: function () {
|
2017-12-12 19:13:58 +00:00
|
|
|
var _this = this;
|
2017-12-05 19:17:14 +00:00
|
|
|
var seek = this.currentConfig.seek;
|
2017-12-13 21:29:55 +00:00
|
|
|
var delay = this.currentConfig.delay;
|
|
|
|
var when = this.manager.context.currentTime + delay;
|
2017-12-05 19:17:14 +00:00
|
|
|
var offset = (this.currentMarker ? this.currentMarker.start : 0) + seek;
|
|
|
|
var duration = this.duration - seek;
|
2017-12-13 21:41:33 +00:00
|
|
|
this.playTime = when - seek;
|
|
|
|
this.startTime = when;
|
2017-12-12 19:13:58 +00:00
|
|
|
this.source = this.manager.context.createBufferSource();
|
|
|
|
this.source.buffer = this.audioBuffer;
|
|
|
|
this.source.connect(this.muteNode);
|
|
|
|
this.source.onended = function (ev) {
|
|
|
|
if (ev.target === _this.source) {
|
|
|
|
// sound ended
|
2017-12-12 19:17:42 +00:00
|
|
|
if (_this.currentConfig.loop) {
|
2017-12-12 19:18:51 +00:00
|
|
|
_this.resetConfig();
|
2017-12-12 19:17:42 +00:00
|
|
|
_this.createAndStartBufferSource();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_this.hasEnded = true;
|
|
|
|
}
|
2017-12-12 19:13:58 +00:00
|
|
|
}
|
|
|
|
// else was stopped
|
|
|
|
};
|
2017-12-05 18:36:59 +00:00
|
|
|
this.applyConfig();
|
2017-12-13 21:29:55 +00:00
|
|
|
this.source.start(Math.max(0, when), Math.max(0, offset), Math.max(0, duration));
|
2017-12-05 18:41:07 +00:00
|
|
|
this.resetConfig();
|
2017-11-17 16:01:12 +00:00
|
|
|
},
|
2017-11-17 16:07:04 +00:00
|
|
|
/**
|
|
|
|
* Used internally to do what the name says.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
stopAndRemoveBufferSource: function () {
|
2017-11-17 17:38:23 +00:00
|
|
|
if (this.source) {
|
2017-11-26 15:35:13 +00:00
|
|
|
this.source.stop();
|
2017-11-17 17:38:23 +00:00
|
|
|
this.source = null;
|
|
|
|
}
|
2017-12-13 21:32:34 +00:00
|
|
|
this.playTime = 0;
|
2017-12-13 21:42:16 +00:00
|
|
|
this.startTime = 0;
|
2017-11-17 16:07:04 +00:00
|
|
|
},
|
2017-12-07 19:14:58 +00:00
|
|
|
/**
|
|
|
|
* @protected
|
|
|
|
*/
|
2017-12-05 18:35:26 +00:00
|
|
|
applyConfig: function () {
|
|
|
|
this.rateUpdates.length = 0;
|
|
|
|
this.rateUpdates.push({
|
|
|
|
time: 0,
|
|
|
|
rate: 1
|
|
|
|
});
|
|
|
|
BaseSound.prototype.applyConfig.call(this);
|
|
|
|
},
|
2017-11-26 16:25:01 +00:00
|
|
|
/**
|
|
|
|
* Update method called on every game step.
|
|
|
|
*
|
|
|
|
* @param {number} time - The current timestamp as generated by the Request Animation Frame or SetTimeout.
|
|
|
|
* @param {number} delta - The delta time elapsed since the last frame.
|
|
|
|
*/
|
|
|
|
update: function (time, delta) {
|
2017-11-26 16:05:24 +00:00
|
|
|
if (this.hasEnded) {
|
2017-11-26 16:07:56 +00:00
|
|
|
this.hasEnded = false;
|
2017-11-26 16:05:24 +00:00
|
|
|
this.stop();
|
|
|
|
}
|
2017-11-16 13:23:04 +00:00
|
|
|
},
|
|
|
|
destroy: function () {
|
2017-12-10 12:17:39 +00:00
|
|
|
BaseSound.prototype.destroy.call(this);
|
|
|
|
this.audioBuffer = null;
|
|
|
|
this.stopAndRemoveBufferSource();
|
|
|
|
this.muteNode.disconnect();
|
|
|
|
this.muteNode = null;
|
|
|
|
this.volumeNode.disconnect();
|
|
|
|
this.volumeNode = null;
|
|
|
|
this.rateUpdates = null;
|
2017-11-27 15:44:23 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
setRate: function () {
|
2017-12-01 14:41:24 +00:00
|
|
|
BaseSound.prototype.setRate.call(this);
|
2017-11-27 15:48:20 +00:00
|
|
|
if (this.source) {
|
2017-12-01 14:41:24 +00:00
|
|
|
this.source.playbackRate.setValueAtTime(this.totalRate, 0);
|
2017-11-27 15:48:20 +00:00
|
|
|
}
|
2017-12-05 18:13:09 +00:00
|
|
|
if (this.isPlaying) {
|
2017-12-14 13:04:05 +00:00
|
|
|
var time = void 0;
|
2017-12-13 21:49:13 +00:00
|
|
|
if (this.manager.context.currentTime < this.startTime) {
|
2017-12-14 13:04:05 +00:00
|
|
|
time = this.startTime - this.playTime;
|
2017-12-13 21:49:13 +00:00
|
|
|
}
|
|
|
|
else {
|
2017-12-14 13:04:05 +00:00
|
|
|
time = this.manager.context.currentTime - this.playTime;
|
2017-12-13 21:49:13 +00:00
|
|
|
}
|
2017-12-14 13:04:05 +00:00
|
|
|
this.rateUpdates.push({
|
|
|
|
time: time,
|
|
|
|
rate: this.totalRate
|
|
|
|
});
|
2017-12-05 18:13:09 +00:00
|
|
|
}
|
2017-12-05 18:43:08 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
getCurrentTime: function () {
|
|
|
|
var currentTime = 0;
|
|
|
|
for (var i = 0; i < this.rateUpdates.length; i++) {
|
|
|
|
var nextTime = void 0;
|
|
|
|
if (i < this.rateUpdates.length - 1) {
|
|
|
|
nextTime = this.rateUpdates[i + 1].time;
|
|
|
|
}
|
|
|
|
else {
|
2017-12-13 21:32:34 +00:00
|
|
|
nextTime = this.manager.context.currentTime - this.playTime;
|
2017-12-05 18:43:08 +00:00
|
|
|
}
|
|
|
|
currentTime += (nextTime - this.rateUpdates[i].time) * this.rateUpdates[i].rate;
|
|
|
|
}
|
|
|
|
return currentTime;
|
2017-11-14 15:27:22 +00:00
|
|
|
}
|
|
|
|
});
|
2017-11-15 14:58:38 +00:00
|
|
|
/**
|
2017-11-15 16:37:22 +00:00
|
|
|
* Mute setting.
|
|
|
|
* @property {boolean} mute
|
2017-11-15 14:58:38 +00:00
|
|
|
*/
|
2017-11-15 16:37:22 +00:00
|
|
|
Object.defineProperty(WebAudioSound.prototype, 'mute', {
|
2017-11-15 14:58:38 +00:00
|
|
|
get: function () {
|
2017-11-15 16:37:22 +00:00
|
|
|
return this.muteNode.gain.value === 0;
|
2017-11-15 14:58:38 +00:00
|
|
|
},
|
|
|
|
set: function (value) {
|
2017-11-16 12:09:43 +00:00
|
|
|
this.currentConfig.mute = value;
|
2017-11-21 18:47:27 +00:00
|
|
|
this.muteNode.gain.setValueAtTime(value ? 0 : 1, 0);
|
2017-11-15 14:58:38 +00:00
|
|
|
}
|
|
|
|
});
|
2017-11-15 15:14:04 +00:00
|
|
|
/**
|
2017-11-15 16:37:22 +00:00
|
|
|
* Volume setting.
|
|
|
|
* @property {number} volume
|
2017-11-15 15:14:04 +00:00
|
|
|
*/
|
2017-11-15 16:37:22 +00:00
|
|
|
Object.defineProperty(WebAudioSound.prototype, 'volume', {
|
2017-11-15 15:14:04 +00:00
|
|
|
get: function () {
|
2017-11-15 16:37:22 +00:00
|
|
|
return this.volumeNode.gain.value;
|
2017-11-15 15:14:04 +00:00
|
|
|
},
|
|
|
|
set: function (value) {
|
2017-11-16 12:09:43 +00:00
|
|
|
this.currentConfig.volume = value;
|
2017-11-21 18:47:27 +00:00
|
|
|
this.volumeNode.gain.setValueAtTime(value, 0);
|
2017-11-15 15:14:04 +00:00
|
|
|
}
|
|
|
|
});
|
2017-11-16 14:21:57 +00:00
|
|
|
/**
|
|
|
|
* Playback rate.
|
|
|
|
* @property {number} rate
|
|
|
|
*/
|
|
|
|
Object.defineProperty(WebAudioSound.prototype, 'rate', {
|
|
|
|
get: function () {
|
|
|
|
return this.currentConfig.rate;
|
|
|
|
},
|
|
|
|
set: function (value) {
|
|
|
|
this.currentConfig.rate = value;
|
2017-11-27 15:48:20 +00:00
|
|
|
this.setRate();
|
2017-11-16 14:21:57 +00:00
|
|
|
}
|
|
|
|
});
|
2017-11-16 16:25:06 +00:00
|
|
|
/**
|
|
|
|
* Detuning of sound.
|
|
|
|
* @property {number} detune
|
|
|
|
*/
|
|
|
|
Object.defineProperty(WebAudioSound.prototype, 'detune', {
|
|
|
|
get: function () {
|
|
|
|
return this.currentConfig.detune;
|
|
|
|
},
|
|
|
|
set: function (value) {
|
|
|
|
this.currentConfig.detune = value;
|
2017-11-27 15:48:20 +00:00
|
|
|
this.setRate();
|
2017-11-16 16:25:06 +00:00
|
|
|
}
|
|
|
|
});
|
2017-12-05 18:07:40 +00:00
|
|
|
/**
|
|
|
|
* Current position of playing sound.
|
|
|
|
* @property {number} seek
|
|
|
|
*/
|
|
|
|
Object.defineProperty(WebAudioSound.prototype, 'seek', {
|
|
|
|
get: function () {
|
2017-12-05 18:47:41 +00:00
|
|
|
if (this.isPlaying) {
|
2017-12-13 21:47:39 +00:00
|
|
|
if (this.manager.context.currentTime < this.startTime) {
|
|
|
|
return this.startTime - this.playTime;
|
|
|
|
}
|
2017-12-05 18:47:41 +00:00
|
|
|
return this.getCurrentTime();
|
|
|
|
}
|
2017-12-07 19:58:07 +00:00
|
|
|
else if (this.isPaused) {
|
2017-12-05 18:48:52 +00:00
|
|
|
return this.currentConfig.seek;
|
|
|
|
}
|
2017-12-07 19:58:07 +00:00
|
|
|
else {
|
|
|
|
return 0;
|
|
|
|
}
|
2017-12-05 18:07:40 +00:00
|
|
|
},
|
|
|
|
set: function (value) {
|
2017-12-13 21:46:02 +00:00
|
|
|
if (this.manager.context.currentTime < this.startTime) {
|
|
|
|
return;
|
|
|
|
}
|
2017-12-07 19:58:54 +00:00
|
|
|
if (this.isPlaying || this.isPaused) {
|
|
|
|
value = Math.min(Math.max(0, value), this.duration);
|
|
|
|
this.currentConfig.seek = value;
|
|
|
|
}
|
2017-12-05 19:22:49 +00:00
|
|
|
if (this.isPlaying) {
|
|
|
|
this.stopAndRemoveBufferSource();
|
|
|
|
this.createAndStartBufferSource();
|
|
|
|
}
|
2017-12-05 18:07:40 +00:00
|
|
|
}
|
|
|
|
});
|
2017-12-12 18:42:21 +00:00
|
|
|
/**
|
|
|
|
* Property indicating whether or not
|
|
|
|
* the sound or current sound marker will loop.
|
|
|
|
*
|
|
|
|
* @property {boolean} loop
|
|
|
|
*/
|
|
|
|
Object.defineProperty(WebAudioSound.prototype, 'loop', {
|
|
|
|
get: function () {
|
2017-12-12 18:43:21 +00:00
|
|
|
return this.currentConfig.loop;
|
2017-12-12 18:42:21 +00:00
|
|
|
},
|
|
|
|
set: function (value) {
|
2017-12-12 18:44:41 +00:00
|
|
|
this.currentConfig.loop = value;
|
2017-12-12 18:42:21 +00:00
|
|
|
}
|
|
|
|
});
|
2017-11-14 15:27:22 +00:00
|
|
|
module.exports = WebAudioSound;
|