|
@@ -0,0 +1,172 @@
|
|
|
+/**
|
|
|
+ * @author alteredq / http://alteredqualia.com/
|
|
|
+ *
|
|
|
+ * AudioObject
|
|
|
+ *
|
|
|
+ * - 3d spatialized sound with Doppler-shift effect
|
|
|
+ *
|
|
|
+ * - uses Audio API (currently supported in WebKit-based browsers)
|
|
|
+ * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
|
|
|
+ *
|
|
|
+ * - based on Doppler effect demo from Chromium
|
|
|
+ * http://chromium.googlecode.com/svn/trunk/samples/audio/doppler.html
|
|
|
+ *
|
|
|
+ * - parameters
|
|
|
+ *
|
|
|
+ * - listener
|
|
|
+ * dopplerFactor // A constant used to determine the amount of pitch shift to use when rendering a doppler effect.
|
|
|
+ * speedOfSound // The speed of sound used for calculating doppler shift. The default value is 343.3 meters / second.
|
|
|
+ *
|
|
|
+ * - panner
|
|
|
+ * refDistance // A reference distance for reducing volume as source move further from the listener.
|
|
|
+ * maxDistance // The maximum distance between source and listener, after which the volume will not be reduced any further.
|
|
|
+ * rolloffFactor // Describes how quickly the volume is reduced as source moves away from listener.
|
|
|
+ * coneInnerAngle // An angle inside of which there will be no volume reduction.
|
|
|
+ * coneOuterAngle // An angle outside of which the volume will be reduced to a constant value of coneOuterGain.
|
|
|
+ * coneOuterGain // Amount of volume reduction outside of the coneOuterAngle.
|
|
|
+ */
|
|
|
+
|
|
|
+THREE.AudioObject = function ( url, volume, playbackRate, loop ) {
|
|
|
+
|
|
|
+ THREE.Object3D.call( this );
|
|
|
+
|
|
|
+ if ( playbackRate === undefined ) playbackRate = 1;
|
|
|
+ if ( volume === undefined ) volume = 1;
|
|
|
+ if ( loop === undefined ) loop = true;
|
|
|
+
|
|
|
+ if ( ! this.context ) {
|
|
|
+
|
|
|
+ try {
|
|
|
+
|
|
|
+ this.context = new webkitAudioContext();
|
|
|
+
|
|
|
+ } catch( error ) {
|
|
|
+
|
|
|
+ console.warn( "THREE.AudioObject: webkitAudioContext not found" );
|
|
|
+ return this;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.directionalSource = false;
|
|
|
+
|
|
|
+ this.listener = this.context.listener;
|
|
|
+ this.panner = this.context.createPanner();
|
|
|
+ this.source = this.context.createBufferSource();
|
|
|
+
|
|
|
+ this.masterGainNode = this.context.createGainNode();
|
|
|
+ this.dryGainNode = this.context.createGainNode();
|
|
|
+
|
|
|
+ // Setup initial gains
|
|
|
+
|
|
|
+ this.masterGainNode.gain.value = volume;
|
|
|
+ this.dryGainNode.gain.value = 3.0;
|
|
|
+
|
|
|
+ // Connect dry mix
|
|
|
+
|
|
|
+ this.source.connect( this.panner );
|
|
|
+ this.panner.connect( this.dryGainNode );
|
|
|
+ this.dryGainNode.connect( this.masterGainNode );
|
|
|
+
|
|
|
+ // Connect master gain
|
|
|
+
|
|
|
+ this.masterGainNode.connect( this.context.destination );
|
|
|
+
|
|
|
+ // Set source parameters and load sound
|
|
|
+
|
|
|
+ this.source.playbackRate.value = playbackRate;
|
|
|
+ this.source.loop = loop;
|
|
|
+
|
|
|
+ loadBufferAndPlay( url );
|
|
|
+
|
|
|
+ // private properties
|
|
|
+
|
|
|
+ var soundPosition = new THREE.Vector3(),
|
|
|
+ cameraPosition = new THREE.Vector3(),
|
|
|
+ oldSoundPosition = new THREE.Vector3(),
|
|
|
+ oldCameraPosition = new THREE.Vector3(),
|
|
|
+
|
|
|
+ soundDelta = new THREE.Vector3(),
|
|
|
+ cameraDelta = new THREE.Vector3(),
|
|
|
+
|
|
|
+ soundFront = new THREE.Vector3(),
|
|
|
+ cameraFront = new THREE.Vector3(),
|
|
|
+ soundUp = new THREE.Vector3(),
|
|
|
+ cameraUp = new THREE.Vector3();
|
|
|
+
|
|
|
+ var _this = this;
|
|
|
+
|
|
|
+ // API
|
|
|
+
|
|
|
+ this.setVolume = function ( volume ) {
|
|
|
+
|
|
|
+ this.masterGainNode.gain.value = volume;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ this.update = function ( camera ) {
|
|
|
+
|
|
|
+ oldSoundPosition.copy( soundPosition );
|
|
|
+ oldCameraPosition.copy( cameraPosition );
|
|
|
+
|
|
|
+ soundPosition.copy( this.matrixWorld.getPosition() );
|
|
|
+ cameraPosition.copy( camera.matrixWorld.getPosition() );
|
|
|
+
|
|
|
+ soundDelta.sub( soundPosition, oldSoundPosition );
|
|
|
+ cameraDelta.sub( cameraPosition, oldCameraPosition );
|
|
|
+
|
|
|
+ cameraUp.copy( camera.up );
|
|
|
+
|
|
|
+ cameraFront.set( 0, 0, -1 );
|
|
|
+ camera.matrixWorld.rotateAxis( cameraFront );
|
|
|
+ cameraFront.normalize();
|
|
|
+
|
|
|
+ this.listener.setPosition( cameraPosition.x, cameraPosition.y, cameraPosition.z );
|
|
|
+ this.listener.setVelocity( cameraDelta.x, cameraDelta.y, cameraDelta.z );
|
|
|
+ this.listener.setOrientation( cameraFront.x, cameraFront.y, cameraFront.z, cameraUp.x, cameraUp.y, cameraUp.z );
|
|
|
+
|
|
|
+ this.panner.setPosition( soundPosition.x, soundPosition.y, soundPosition.z );
|
|
|
+ this.panner.setVelocity( soundDelta.x, soundDelta.y, soundDelta.z );
|
|
|
+
|
|
|
+ if ( this.directionalSource ) {
|
|
|
+
|
|
|
+ soundFront.set( 0, 0, -1 );
|
|
|
+ this.matrixWorld.rotateAxis( soundFront );
|
|
|
+ soundFront.normalize();
|
|
|
+
|
|
|
+ soundUp.copy( this.up );
|
|
|
+ this.panner.setOrientation( soundFront.x, soundFront.y, soundFront.z, soundUp.x, soundUp.y, soundUp.z );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ function loadBufferAndPlay( url ) {
|
|
|
+
|
|
|
+ // Load asynchronously
|
|
|
+
|
|
|
+ var request = new XMLHttpRequest();
|
|
|
+ request.open( "GET", url, true );
|
|
|
+ request.responseType = "arraybuffer";
|
|
|
+
|
|
|
+ request.onload = function() {
|
|
|
+
|
|
|
+ _this.source.buffer = _this.context.createBuffer( request.response, true );
|
|
|
+ _this.source.noteOn( 0 );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ request.send();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+THREE.AudioObject.prototype = new THREE.Object3D();
|
|
|
+THREE.AudioObject.prototype.constructor = THREE.AudioObject;
|
|
|
+
|
|
|
+THREE.AudioObject.prototype.context = null;
|
|
|
+THREE.AudioObject.prototype.type = null;
|
|
|
+
|