UNPKG

web-audio-api

Version:
104 lines (91 loc) 3.69 kB
import AudioNode from './AudioNode.js' import AudioParam from './AudioParam.js' import AudioBuffer from 'audio-buffer' import { BLOCK_SIZE } from './constants.js' import { DOMErr } from './errors.js' // W3C spec equal-power panning: // For mono input: outputL = input * cos(x), outputR = input * sin(x) // where x = (pan + 1) / 2 * π/2 // For stereo input (continuous at pan=0, identity at pan=0): // If pan < 0: x = -pan * π/2; outputL = inputL + inputR * sin(x), outputR = inputR * cos(x) // If pan >= 0: x = pan * π/2; outputL = inputL * cos(x), outputR = inputR + inputL * sin(x) class StereoPannerNode extends AudioNode { #pan get pan() { return this.#pan } constructor(context, options) { options = AudioNode._checkOpts(options) super(context, 1, 1, 2, 'clamped-max', 'speakers') this.#pan = new AudioParam(this.context, options.pan ?? 0, 'a', -1, 1) this._outBuf = new AudioBuffer(2, BLOCK_SIZE, context.sampleRate) this._applyOpts(options) } _validateChannelCount(val) { if (val > 2) throw DOMErr('channelCount cannot be greater than 2', 'NotSupportedError') } _validateChannelCountMode(val) { if (val === 'max') throw DOMErr("channelCountMode cannot be 'max'", 'NotSupportedError') } _tick() { super._tick() let inBuf = this._inputs[0]._tick() let panArr = this.#pan._tick() let outL = this._outBuf.getChannelData(0) let outR = this._outBuf.getChannelData(1) let inCh = inBuf.numberOfChannels let PI2 = Math.PI / 2 // Fast path: constant pan across block — compute trig once let isConst = panArr[0] === panArr[BLOCK_SIZE - 1] if (inCh === 1) { let inp = inBuf.getChannelData(0) if (isConst) { let p = Math.max(-1, Math.min(1, panArr[0])) let x = (p + 1) / 2 * PI2 let cosX = Math.cos(x), sinX = Math.sin(x) for (let i = 0; i < BLOCK_SIZE; i++) { outL[i] = inp[i] * cosX outR[i] = inp[i] * sinX } } else { for (let i = 0; i < BLOCK_SIZE; i++) { let p = Math.max(-1, Math.min(1, panArr[i])) let x = (p + 1) / 2 * PI2 outL[i] = inp[i] * Math.cos(x) outR[i] = inp[i] * Math.sin(x) } } } else { let inL = inBuf.getChannelData(0), inR = inBuf.getChannelData(1) if (isConst) { let p = Math.max(-1, Math.min(1, panArr[0])) if (p <= -1) { for (let i = 0; i < BLOCK_SIZE; i++) { outL[i] = inL[i] + inR[i]; outR[i] = 0 } } else if (p >= 1) { for (let i = 0; i < BLOCK_SIZE; i++) { outL[i] = 0; outR[i] = inR[i] + inL[i] } } else if (p < 0) { let x = -p * PI2, sinX = Math.sin(x), cosX = Math.cos(x) for (let i = 0; i < BLOCK_SIZE; i++) { outL[i] = inL[i] + inR[i] * sinX; outR[i] = inR[i] * cosX } } else { let x = p * PI2, cosX = Math.cos(x), sinX = Math.sin(x) for (let i = 0; i < BLOCK_SIZE; i++) { outL[i] = inL[i] * cosX; outR[i] = inR[i] + inL[i] * sinX } } } else { for (let i = 0; i < BLOCK_SIZE; i++) { let p = Math.max(-1, Math.min(1, panArr[i])) if (p <= -1) { outL[i] = inL[i] + inR[i]; outR[i] = 0 } else if (p >= 1) { outL[i] = 0; outR[i] = inR[i] + inL[i] } else if (p < 0) { let x = -p * PI2 outL[i] = inL[i] + inR[i] * Math.sin(x); outR[i] = inR[i] * Math.cos(x) } else { let x = p * PI2 outL[i] = inL[i] * Math.cos(x); outR[i] = inR[i] + inL[i] * Math.sin(x) } } } } return this._outBuf } } export default StereoPannerNode