UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

247 lines (244 loc) 9.1 kB
import { Debug } from '../../core/debug.js'; import { math } from '../../core/math/math.js'; import { Vec3 } from '../../core/math/vec3.js'; import { DISTANCE_LINEAR, DISTANCE_INVERSE, DISTANCE_EXPONENTIAL } from './constants.js'; import { hasAudioContext } from './capabilities.js'; import { SoundInstance } from './instance.js'; /** * @import { SoundManager } from './manager.js' * @import { Sound } from './sound.js' */ // default maxDistance, same as Web Audio API const MAX_DISTANCE = 10000; /** * A SoundInstance3d plays a {@link Sound} in 3D. * * @category Sound */ class SoundInstance3d extends SoundInstance { /** * Create a new SoundInstance3d instance. * * @param {SoundManager} manager - The sound manager. * @param {Sound} sound - The sound to play. * @param {object} options - Options for the instance. * @param {number} [options.volume] - The playback volume, between 0 and 1. Defaults to 1. * @param {number} [options.pitch] - The relative pitch. Defaults to 1 (plays at normal pitch). * @param {boolean} [options.loop] - Whether the sound should loop when it reaches the end or * not. Defaults to false. * @param {number} [options.startTime] - The time from which the playback will start. Default * is 0 to start at the beginning. * @param {number} [options.duration] - The total time after the startTime when playback will * stop or restart if loop is true. * @param {Vec3} [options.position] - The position of the sound in 3D space. * @param {string} [options.distanceModel] - Determines which algorithm to use to reduce the * volume of the audio as it moves away from the listener. Can be: * * - {@link DISTANCE_LINEAR} * - {@link DISTANCE_INVERSE} * - {@link DISTANCE_EXPONENTIAL} * * Defaults to {@link DISTANCE_LINEAR}. * @param {number} [options.refDistance] - The reference distance for reducing volume as the * sound source moves further from the listener. Defaults to 1. * @param {number} [options.maxDistance] - The maximum distance from the listener at which * audio falloff stops. Note the volume of the audio is not 0 after this distance, but just * doesn't fall off anymore. Defaults to 10000. * @param {number} [options.rollOffFactor] - The factor used in the falloff equation. Defaults * to 1. */ constructor(manager, sound, options = {}){ super(manager, sound, options), /** * @type {Vec3} * @private */ this._position = new Vec3(), /** * @type {Vec3} * @private */ this._velocity = new Vec3(); if (options.position) { this.position = options.position; } this.maxDistance = options.maxDistance !== undefined ? Number(options.maxDistance) : MAX_DISTANCE; this.refDistance = options.refDistance !== undefined ? Number(options.refDistance) : 1; this.rollOffFactor = options.rollOffFactor !== undefined ? Number(options.rollOffFactor) : 1; this.distanceModel = options.distanceModel !== undefined ? options.distanceModel : DISTANCE_LINEAR; } /** * Allocate Web Audio resources for this instance. * * @private */ _initializeNodes() { this.gain = this._manager.context.createGain(); this.panner = this._manager.context.createPanner(); this.panner.connect(this.gain); this._inputNode = this.panner; this._connectorNode = this.gain; this._connectorNode.connect(this._manager.context.destination); } /** * Sets the position of the sound in 3D space. * * @type {Vec3} */ set position(value) { this._position.copy(value); const panner = this.panner; if ('positionX' in panner) { panner.positionX.value = value.x; panner.positionY.value = value.y; panner.positionZ.value = value.z; } else if (panner.setPosition) { panner.setPosition(value.x, value.y, value.z); } } /** * Gets the position of the sound in 3D space. * * @type {Vec3} */ get position() { return this._position; } set velocity(velocity) { Debug.warn('SoundInstance3d#velocity is not implemented.'); this._velocity.copy(velocity); } get velocity() { Debug.warn('SoundInstance3d#velocity is not implemented.'); return this._velocity; } /** * Sets the maximum distance from the listener at which audio falloff stops. Note that the * volume of the audio is not 0 after this distance, but just doesn't fall off anymore. * * @type {number} */ set maxDistance(value) { this.panner.maxDistance = value; } /** * Gets the maximum distance from the listener at which audio falloff stops. * * @type {number} */ get maxDistance() { return this.panner.maxDistance; } /** * Sets the reference distance for reducing volume as the sound source moves further from the * listener. * * @type {number} */ set refDistance(value) { this.panner.refDistance = value; } /** * Gets the reference distance for reducing volume as the sound source moves further from the * listener. * * @type {number} */ get refDistance() { return this.panner.refDistance; } /** * Sets the factor used in the falloff equation. * * @type {number} */ set rollOffFactor(value) { this.panner.rolloffFactor = value; } /** * Gets the factor used in the falloff equation. * * @type {number} */ get rollOffFactor() { return this.panner.rolloffFactor; } /** * Sets which algorithm to use to reduce the volume of the audio as it moves away from * the listener. Can be: * * - {@link DISTANCE_LINEAR} * - {@link DISTANCE_INVERSE} * - {@link DISTANCE_EXPONENTIAL} * * Default is {@link DISTANCE_LINEAR}. * * @type {string} */ set distanceModel(value) { this.panner.distanceModel = value; } /** * Gets which algorithm to use to reduce the volume of the audio as it moves away from * the listener. * * @type {string} */ get distanceModel() { return this.panner.distanceModel; } } if (!hasAudioContext()) { // temp vector storage let offset = new Vec3(); // Fall off function which should be the same as the one in the Web Audio API // Taken from https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/distanceModel const fallOff = function(posOne, posTwo, refDistance, maxDistance, rollOffFactor, distanceModel) { offset = offset.sub2(posOne, posTwo); const distance = offset.length(); if (distance < refDistance) { return 1; } else if (distance > maxDistance) { return 0; } let result = 0; if (distanceModel === DISTANCE_LINEAR) { result = 1 - rollOffFactor * (distance - refDistance) / (maxDistance - refDistance); } else if (distanceModel === DISTANCE_INVERSE) { result = refDistance / (refDistance + rollOffFactor * (distance - refDistance)); } else if (distanceModel === DISTANCE_EXPONENTIAL) { result = Math.pow(distance / refDistance, -rollOffFactor); } return math.clamp(result, 0, 1); }; Object.defineProperty(SoundInstance3d.prototype, 'position', { get: function() { return this._position; }, set: function(position) { this._position.copy(position); if (this.source) { const listener = this._manager.listener; const lpos = listener.getPosition(); const factor = fallOff(lpos, this._position, this.refDistance, this.maxDistance, this.rollOffFactor, this.distanceModel); const v = this.volume; this.source.volume = v * factor * this._manager.volume; } } }); Object.defineProperty(SoundInstance3d.prototype, 'maxDistance', { get: function() { return this._maxDistance; }, set: function(value) { this._maxDistance = value; } }); Object.defineProperty(SoundInstance3d.prototype, 'refDistance', { get: function() { return this._refDistance; }, set: function(value) { this._refDistance = value; } }); Object.defineProperty(SoundInstance3d.prototype, 'rollOffFactor', { get: function() { return this._rollOffFactor; }, set: function(value) { this._rollOffFactor = value; } }); Object.defineProperty(SoundInstance3d.prototype, 'distanceModel', { get: function() { return this._distanceModel; }, set: function(value) { this._distanceModel = value; } }); } export { SoundInstance3d };