UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

167 lines (164 loc) 6.02 kB
import { Debug } from '../../core/debug.js'; import { math } from '../../core/math/math.js'; import { Vec3 } from '../../core/math/vec3.js'; import { DISTANCE_INVERSE, DISTANCE_LINEAR, DISTANCE_EXPONENTIAL } from './constants.js'; import { hasAudioContext } from './capabilities.js'; import { Channel } from './channel.js'; /** * @import { SoundManager } from '../sound/manager.js' * @import { Sound } from '../sound/sound.js' */ // default maxDistance, same as Web Audio API var MAX_DISTANCE = 10000; /** * 3D audio channel. * * @ignore */ class Channel3d extends Channel { getPosition() { return this.position; } setPosition(position) { this.position.copy(position); var panner = this.panner; if ('positionX' in panner) { panner.positionX.value = position.x; panner.positionY.value = position.y; panner.positionZ.value = position.z; } else if (panner.setPosition) { panner.setPosition(position.x, position.y, position.z); } } getVelocity() { Debug.warn('Channel3d#getVelocity is not implemented.'); return this.velocity; } setVelocity(velocity) { Debug.warn('Channel3d#setVelocity is not implemented.'); this.velocity.copy(velocity); } getMaxDistance() { return this.panner.maxDistance; } setMaxDistance(max) { this.panner.maxDistance = max; } getMinDistance() { return this.panner.refDistance; } setMinDistance(min) { this.panner.refDistance = min; } getRollOffFactor() { return this.panner.rolloffFactor; } setRollOffFactor(factor) { this.panner.rolloffFactor = factor; } getDistanceModel() { return this.panner.distanceModel; } setDistanceModel(distanceModel) { this.panner.distanceModel = distanceModel; } /** * Create the buffer source and connect it up to the correct audio nodes. * * @private */ _createSource() { var context = this.manager.context; this.source = context.createBufferSource(); this.source.buffer = this.sound.buffer; // Connect up the nodes this.source.connect(this.panner); this.panner.connect(this.gain); this.gain.connect(context.destination); if (!this.loop) { // mark source as paused when it ends this.source.onended = this.pause.bind(this); } } /** * Create a new Channel3d instance. * * @param {SoundManager} manager - The SoundManager instance. * @param {Sound} sound - The sound to playback. * @param {object} [options] - Optional options object. * @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. */ constructor(manager, sound, options){ super(manager, sound, options); this.position = new Vec3(); this.velocity = new Vec3(); if (hasAudioContext()) { this.panner = manager.context.createPanner(); } else { this.maxDistance = MAX_DISTANCE; this.minDistance = 1; this.rollOffFactor = 1; this.distanceModel = DISTANCE_INVERSE; } } } if (!hasAudioContext()) { // temp vector storage var 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 var fallOff = function fallOff(posOne, posTwo, refDistance, maxDistance, rolloffFactor, distanceModel) { offset = offset.sub2(posOne, posTwo); var distance = offset.length(); if (distance < refDistance) { return 1; } else if (distance > maxDistance) { return 0; } var 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.assign(Channel3d.prototype, { setPosition: function setPosition(position) { this.position.copy(position); if (this.source) { var listener = this.manager.listener; var lpos = listener.getPosition(); var factor = fallOff(lpos, this.position, this.minDistance, this.maxDistance, this.rollOffFactor, this.distanceModel); var v = this.getVolume(); this.source.volume = v * factor; } }, getMaxDistance: function getMaxDistance() { return this.maxDistance; }, setMaxDistance: function setMaxDistance(max) { this.maxDistance = max; }, getMinDistance: function getMinDistance() { return this.minDistance; }, setMinDistance: function setMinDistance(min) { this.minDistance = min; }, getRollOffFactor: function getRollOffFactor() { return this.rollOffFactor; }, setRollOffFactor: function setRollOffFactor(factor) { this.rollOffFactor = factor; }, getDistanceModel: function getDistanceModel() { return this.distanceModel; }, setDistanceModel: function setDistanceModel(distanceModel) { this.distanceModel = distanceModel; } }); } export { Channel3d };