UNPKG

audio2d

Version:

Easy to use API to add the power of web audio to your game.

802 lines (690 loc) 18.2 kB
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var classCallCheck = _classCallCheck; function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var createClass = _createClass; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var defineProperty = _defineProperty; /** * Provides an interface for adding web audio nodes. */ var Nodes = /*#__PURE__*/function () { /** * A reference to the AudioContext. * * @private * * @property {AudioContext} */ /** * @param {AudioContext} ctx A reference to the AudioContext. */ function Nodes(ctx) { classCallCheck(this, Nodes); defineProperty(this, "_ctx", void 0); this._ctx = ctx; } /** * Creates a biquadFilter node. * * @returns {Node} */ createClass(Nodes, [{ key: "biquadFilter", value: function biquadFilter() { return { name: 'biquadFilter', instance: this._ctx.createBiquadFilter() }; } /** * Creates a gain node. * * @returns {Node} */ }, { key: "gain", value: function gain() { return { name: 'gain', instance: this._ctx.createGain() }; } }]); return Nodes; }(); /** * The states that an audio clip can be in. */ var AudioClipState; (function (AudioClipState) { AudioClipState["STOPPED"] = "STOPPED"; AudioClipState["PLAYING"] = "PLAYING"; AudioClipState["PAUSED"] = "PAUSED"; })(AudioClipState || (AudioClipState = {})); /** * An audio clip represents a piece of audio, which is either an audio html element or an audio boffer, as * a playable clip with extra properties. */ var AudioClip = /*#__PURE__*/function () { /** * The name of the audio clip. * * @private * * @property {string} */ /** * A reference to the audio to play. * * @private * * @property */ /** * The audio buffer source of the clip. * * @private * * @property {AudioBufferSourceNode} */ /** * A reference to the options for this audio clip. * * @private * * @property {AudioClipOptions} */ /** * A reference to the gain node for this clip. * * @private * * @property {GainNode} */ /** * The current state of this clip. * * @private * * @property {AudioClipState} */ /** * The number of times this clip has been played. * * @private * * @property */ /** * The time that this clip start being played at. * * @private * * @property {number} */ /** * When the clip is paused, this will keep track of when it was paused so it can be resumed at that time. * * @private * * @property {number} */ /** * The current time of the clip. * * @private * * @property {number} */ /** * The duration of the clip. * * @private * * @property {number} */ /** * The volume of this audio clip. * * @private * * @property {number} * * @default 100 */ /** * Keeps track of the previous volume of the clip. * * @private * * @property {number} */ /** * A reference to the nodes that have been added for this clip. * * @private * * @property {Array<Node>} */ /** * A reference to the nodes that have been added in a way that allows them to be retrieved easily. * * @private * * @property {*} */ /** * Indicates whether this audio clip is played on a loop or not. * * @property {boolean} * * @default false */ /** * @param {string} name The name of the audio clip. * @param {AudioBuffer} audio The AudioBuffer that contains the audio of the clip. * @param {AudioClipOptions} [options] The options passed to this audio clip. */ function AudioClip(name, audio, options) { classCallCheck(this, AudioClip); defineProperty(this, "_name", void 0); defineProperty(this, "_audio", void 0); defineProperty(this, "_source", void 0); defineProperty(this, "_options", void 0); defineProperty(this, "_gain", void 0); defineProperty(this, "_state", AudioClipState.STOPPED); defineProperty(this, "_timesPlayed", 0); defineProperty(this, "_timeStartedAt", 0); defineProperty(this, "_timePausedAt", 0); defineProperty(this, "_currentTime", 0); defineProperty(this, "_duration", void 0); defineProperty(this, "_volume", 100); defineProperty(this, "_previousVolume", 1); defineProperty(this, "_nodesref", []); defineProperty(this, "_nodes", {}); defineProperty(this, "loop", false); this._name = name; this._audio = audio; this._duration = audio.duration; this._options = options; this._gain = this._options.ctx.createGain(); this._gain.connect(this._options.ctx.destination); if (this._options.trigger) this._setupTrigger(); if (!this._options.markers) this._options.markers = []; } /** * Gets the name of the audio clip. * * @returns {string} */ createClass(AudioClip, [{ key: "addNode", /** * Adds a custom node from `app.nodes[nodeName]`. * * @param {Node} node The node to add to this clip. * * @example * * const track = a2d.addAudio('track-1', track1Buffer); * * const bf = a2d.nodes.biquadFilter(); * track.addNode(bf); */ value: function addNode(node) { this._nodes[node.name] = node.instance; this._nodesref.push(node); if (this._nodesref.length === 1) return; var latestNode = this._nodesref[this._nodesref.length - 2]; node.instance.connect(latestNode.instance); } /** * Plays this audio clip. * * @param {string} marker The name of the marker of the part of the clip to play. * * @example * * const sfxMarkers = [{ name: 'walk', start: 1500, duration: 1000 }, { name: 'fall': start: 2500, duration: 1500 }]; * const sfx = a2d.addAudio('sfx', sfxBuffer, { markers: sxfMarkers }); * * // play just the falling sound. * sfx.play('fall'); */ }, { key: "play", value: function play(marker) { var offset = this._timePausedAt; this._source = this._options.ctx.createBufferSource(); this._source.buffer = this._audio; this._source.loop = this.loop; this._connectNodes(); this._oncomplete(); if (marker) { var _this$_options$marker; var clipMarker = (_this$_options$marker = this._options.markers) === null || _this$_options$marker === void 0 ? void 0 : _this$_options$marker.find(function (m) { return m.name === marker; }); if (!clipMarker) return; this._source.start(0, clipMarker.start / 1000, clipMarker.duration ? clipMarker.duration / 1000 : undefined); this._resetA2DMarkers(clipMarker); } else { this._source.start(); } this._timeStartedAt = this._options.ctx.currentTime - offset; this._timePausedAt = 0; this._state = AudioClipState.PLAYING; this._timesPlayed++; } /** * Pause the currently playing audio. * * @example * * const sfx = a2d.addAudio('sfx', sfxBuffer); * sfx.play(); * * setTimeout(() => { * sfx.pause(); * }, 1000); */ }, { key: "pause", value: function pause() { var _this$_options$marker2; var elapsed = this._options.ctx.currentTime - this._timeStartedAt; this.stop(); this._timePausedAt = elapsed; (_this$_options$marker2 = this._options.markers) === null || _this$_options$marker2 === void 0 ? void 0 : _this$_options$marker2.push({ name: 'a2d-pause', start: this._timePausedAt * 1000 }); this._state = AudioClipState.PAUSED; } /** * Resumes playing this clip from when it was paused. * * @example * * const sfx = a2d.addAudio('sfx', sfxBuffer); * sfx.play(); * sfx.pause(); * * setTimeout(() => { * sfx.resume(); * }, 1000); */ }, { key: "resume", value: function resume() { this.play('a2d-pause'); } /** * Stops the playback of this audio. * * @returns {AudioClip} Returns this for chaining. * * @example * * const sfx = a2d.addAudio('sfx', sfxBuffer); * * sfx.play(); * sfx.stop(); */ }, { key: "stop", value: function stop() { this._source.disconnect(); this._source = this._options.ctx.createBufferSource(); this._timePausedAt = 0; this._timeStartedAt = 0; this._state = AudioClipState.STOPPED; } /** * Seeks to a specific time in the clip. * * @param {number} time The time, in milliseconds, to seek to. */ }, { key: "seek", value: function seek(time) { var _this$_options$marker3; if (!time) return; if (time > this.duration * 1000) { console.warn('The time to seek to is greater than the duration of the clip.'); return; } if (this._state === AudioClipState.PLAYING) this.stop(); (_this$_options$marker3 = this._options.markers) === null || _this$_options$marker3 === void 0 ? void 0 : _this$_options$marker3.push({ name: 'a2d-seek', start: time }); this.play('a2d-seek'); } /** * Mutes this clip. * * @example * * const sfx = a2d.addAudio('sfx', sfxBuffer); * * sfx.play(); * sfx.mute(); */ }, { key: "mute", value: function mute() { this._previousVolume = this.volume; this.volume = 0; } /** * Puts the volume back to the value it was at before the clip was muted. * * @example * * const sfx = a2d.addAudio('sfx', sfxBuffer); * sfx.play(); * * sfx.mute(); * sfx.unmute(); */ }, { key: "unmute", value: function unmute() { this.volume = this._previousVolume; } /** * Sets up an onclick event on a trigger element if one was provided in the options. * * @private */ }, { key: "_setupTrigger", value: function _setupTrigger() { var _this = this; var el = document.querySelector(this._options.trigger); if (!el) return; el.addEventListener('click', function () { return _this.play(); }); } /** * Connects the nodes that have been added through `addNode`. * * @private */ }, { key: "_connectNodes", value: function _connectNodes() { if (this._nodesref.length > 0) { var firstNode = this._nodesref[0]; var latestNode = this._nodesref[this._nodesref.length - 1]; this._source.connect(latestNode.instance); firstNode.instance.connect(this._gain); } else { this._source.connect(this._gain); } } /** * Specify what happens when a clip is finished playing. * * @private */ }, { key: "_oncomplete", value: function _oncomplete() { var _this2 = this; this._source.onended = function () { _this2._state = AudioClipState.STOPPED; _this2._source.onended = null; }; } /** * Resets any markers set internally. * * @private * * @param {Marker} clipMarker The marker to check if should be removed. */ }, { key: "_resetA2DMarkers", value: function _resetA2DMarkers(clipMarker) { if (clipMarker.name.includes('a2d')) { var _this$_options$marker4; this._options.markers = (_this$_options$marker4 = this._options.markers) === null || _this$_options$marker4 === void 0 ? void 0 : _this$_options$marker4.filter(function (marker) { !marker.name.includes('a2d'); }); } } }, { key: "name", get: function get() { return this._name; } /** * Gets the current state of the audio clip. * * @returns {string} */ }, { key: "state", get: function get() { return this._state; } /** * Gets the number of times that this clip has been played. * * @returns {number} */ }, { key: "timesPlayed", get: function get() { return this._timesPlayed; } /** * Gets the current time of the clip. * * @returns {number} */ }, { key: "currentTime", get: function get() { if (this._state === AudioClipState.PAUSED) return this._timePausedAt; if (this._state === AudioClipState.PLAYING) return this._options.ctx.currentTime - this._timeStartedAt; return 0; } /** * Gets the duration of the clip. * * @returns {number} */ }, { key: "duration", get: function get() { return this._duration; } /** * Gets the volume of this clip. * * @returns {number} */ }, { key: "volume", get: function get() { return this._volume; } /** * Sets the volume of this clip. * * @param {number} vol The new volume of the clip. */ , set: function set(vol) { this._volume = vol; this._gain.gain.setValueAtTime(this._volume / 100, this._options.ctx.currentTime); } /** * Gets the created nodes. * * @returns {*} */ }, { key: "nodes", get: function get() { return this._nodes; } }]); return AudioClip; }(); /** * Audio2D is a web audio helper for adding sound/music to your JavaScript games. */ var Audio2D = /*#__PURE__*/function () { function Audio2D() { classCallCheck(this, Audio2D); defineProperty(this, "_ctx", new AudioContext()); defineProperty(this, "_clips", []); defineProperty(this, "_nodes", new Nodes(this._ctx)); } createClass(Audio2D, [{ key: "addAudio", /** * Adds audio to the media library. * * @param {string} name The name of this audio clip used to reference it. * @param {AudioBuffer} buffer A reference to the audio buffer for this audio. * @param {Object} [options] * @param {Array<Marker>} [options.markers] A breakdown of the audio into individual parts that can be used independently. * @param {string} [options.trigger] An id or classname of a dom element that when clicked will trigger the clip to play. * * @example * * // Adding an audio clip with no markers. * const levelUp = a2d.addAudio('level-up', levelUpBuffer); * * // Adding an audio clip with markers. * const sfxMarkers = [ * { name: 'walk', start: 1500, duration: 1000 }, * { name: 'fall': start: 2500, duration: 1500 }, * ]; * const sfx = a2d.addAudio('sfx', sfxBuffer, { markers: sxfMarkers }); */ value: function addAudio(name, audio) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; options.ctx = this._ctx; var clip = new AudioClip(name, audio, options); this._clips.push(clip); return clip; } /** * Gets an audio clip from the media library. * * @param {string} name The name of the audio clip to get. * * @returns {AudioClip|undefined} Returns the audio clip if its found or undefined otherwise. * * @example * * a2d.addAudio('track1', buffer); * * const clip = a2d.getAudio('track1'); */ }, { key: "getAudio", value: function getAudio(name) { return this._clips.find(function (clip) { return clip.name === name; }); } /** * Removes an audio clip from the media library. * * @param {string} name The name of the audio clip to remove. * * @returns {Audio2D} Returns this for chaining. * * @example * * a2d.addAudio('track1', buffer); * * a2d.removeAudio('track1'); */ }, { key: "removeAudio", value: function removeAudio(name) { this._clips = this.clips.filter(function (clip) { return clip.name !== name; }); return this; } /** * Removes all audio clips from the media library. * * @returns {Audio2D} Returns this for chaining. * * @example * * a2d.addAudio('track1', buffer1); * a2d.addAudio('track2', buffer2); * * a2d.removeAllAudio(); */ }, { key: "removeAllAudio", value: function removeAllAudio() { this._clips = []; return this; } }, { key: "clips", /** * Returns the created audio clips. * * @returns {Array<AudioClip>} */ get: function get() { return this._clips; } /** * Returns the nodes module to use for creating nodes and adding them to clips. * * @returns {Nodes} */ }, { key: "nodes", get: function get() { return this._nodes; } }]); return Audio2D; }(); export { Audio2D };