js/Mobilizing/renderer/audio/Renderer.js
import Component from '../../core/Component';
//Used for singletonize this class instance (avoid mutilple rendrer)
let instance = null;
/**
* Renderer is a Web Audio API based renderer for playing sound.
*/
export default class Renderer extends Component {
/**
* @example
* //to do
* @constructor
* @extends Component
* @param {Object} params Parameters object, given by the constructor.
* @param {Boolean} [params.listenerAutoUpdateRotation=true] Set the update of the forward and up vector of this audio listener to be automatic
*/
constructor({
listenerAutoUpdateRotation = true
} = {}) {
//if an instance already exists return it
if (instance) {
console.warn("an instance of audio Renderer already exists!");
return instance;
}
super(...arguments);
instance = this;
this.listenerAutoUpdateRotation = listenerAutoUpdateRotation;
this.audioContext = undefined;
//Get the main audio context of Web Audio
if (window.AudioContext !== undefined) {
this.audioContext = new window.AudioContext();
}
else if (window.webkitAudioContext !== undefined) {
this.audioContext = new window.webkitAudioContext();
}
//hack to reset the SR
if (this.audioContext) {
if (this.audioContext.sampleRate !== 44100) {
const buffer = this.audioContext.createBuffer(1, 1, 44100);
const dummy = this.audioContext.createBufferSource();
dummy.buffer = buffer;
dummy.connect(this.audioContext.destination);
dummy.start(0);
dummy.disconnect();
this.audioContext.close();
//Get the main audio context of Web Audio
if (window.AudioContext !== undefined) {
this.audioContext = new window.AudioContext();
}
else if (window.webkitAudioContext !== undefined) {
this.audioContext = new window.webkitAudioContext();
}
}
this.masterGain = this.audioContext.createGain();
this.masterGain.connect(this.audioContext.destination);
}
this.listener = this.audioContext.listener;
//adjust the strategy to what the browser supports
if (this.listener.forwardX) {
this.listener.forwardX.value = 0;
this.listener.forwardY.value = 0;
this.listener.forwardZ.value = -1;
this.listener.upX.value = 0;
this.listener.upY.value = 1;
this.listener.upZ.value = 0;
}
else {
this.listener.setOrientation(0, 0, -1, 0, 1, 0);
}
//keep an array of connected sources for easy duplication for offlineContext
this.sources = [];
}
/**
* Maintains an array of all the sources attached to this renderer
* @param {Source} source
*/
registerNode(source) {
this.sources.push(source);
}
/**
* @return {Number} the current time of this audio renderer
*/
getCurrentTime() {
return this.audioContext.currentTime;
}
/**
* Set this audioContext's gain volume, use a 1 sec linear ramp to avoid clicks
* @param {Number} val
*/
setMasterGain(val) {
let value = val;
if (val === 0) {
value = 0.001;
}
//this.masterGain.gain.value = val;
this.masterGain.gain.linearRampToValueAtTime(value, this.getCurrentTime() + 1);
}
/**
* Get this audioContext's gain volume
* @param {Number} val
*/
getMasterGain() {
return this.masterGain.gain.value;
}
/**
* creates a webaudioAPI analyzer to extract frequencies from the audio source used in this renderer
* @param {Number} fftSize this size of this fft in memory
* @param {Number} smoothingTimeConstant
*/
createAnalyzer(fftSize = 256, smoothingTimeConstant = 0.2) {
/**
* @property analyser
* audio analyser
*/
this.analyser = this.audioContext.createAnalyser();
const _fftSize = fftSize;
this.analyser.fftSize = _fftSize;
this.analyser.smoothingTimeConstant = smoothingTimeConstant;
const bufferSize = this.analyser.frequencyBinCount;
/**
* @property analyserArray
* Uint8Array for accessing frequencies
*/
this.analyserTimeArray = new Uint8Array(bufferSize);
this.analyserFrequencyArray = new Uint8Array(bufferSize);
this.analyserFloatTimeArray = new Float32Array(bufferSize);
this.analyserFloatFrequencyArray = new Float32Array(bufferSize);
//this.masterGain.connect(this.analyser);
}
beep(frequency) {
const freq = (frequency) ? frequency : 440;
const osc = this.audioContext.createOscillator();
osc.connect(this.masterGain);
osc.frequency.value = freq;
osc.start(0);
osc.stop(this.audioContext.currentTime + 0.2);
}
/**
* Attach a Transform object, usually coming from a 3D graphical object, to this renderer listener (the "ears"). The listener position and orientation (rotation) will be automatically updated against the given Transform.
* @param {Transform} transform
*/
setListenerTransform(transform) {
this.listener.transform = transform;
}
/**
* Set the update of the forward and up vector of this audio listener to be automatic (computed from the given 3D object transform) or not (will stay at (0, 0, -1, 0, 1, 0))
* @param {Boolean} value
*/
setListenerAutoUpdateRotation(value) {
this.listenerAutoUpdateRotation = value;
}
/**
* Set the position of the listner in 3D space ONLY when transform from a 3D object has not been attached to the listener (cf setListenerTransform())
* @param {Object|Vector3} pos An object containing x,y,z coordinates in space (can be a Vector3 from the 3D renderer)
*/
setListenerPosition(pos) {
this.listener.setPosition(pos.x, pos.y, pos.z);
}
/**
* Set the listener orientation through the given Mobilizing/Three.js quaternion. Directions (i.e forwardVector and upVector) are extracted by the Quaternion Class.
* @param {Quaternion} quaternion Mobilizing/Three.js quaternion to use for this listener orientation
*/
setListenerQuaternion(quaternion) {
const directions = quaternion.getDirections();
if (this.listener.forwardX) {
this.listener.forwardX.value = directions.forwardVector.x;
this.listener.forwardY.value = directions.forwardVector.y;
this.listener.forwardZ.value = directions.forwardVector.z;
this.listener.upX.value = directions.upVector.x;
this.listener.upY.value = directions.upVector.y;
this.listener.upZ.value = directions.upVector.z;
}
else {
this.listener.setOrientation(
directions.forwardVector.x,
directions.forwardVector.y,
directions.forwardVector.z,
directions.upVector.x,
directions.upVector.y,
directions.upVector.z);
}
}
update() {
super.update();
if (this.listener.transform) {
const pos = this.listener.transform.getLocalPosition();
this.listener.setPosition(pos.x, pos.y, pos.z);
const quaternion = this.listener.transform.getLocalQuaternion();
if (this.listenerAutoUpdateRotation) {
this.setListenerQuaternion(quaternion);
}
}
}
}