UNPKG

@free-side/audioworklet-polyfill

Version:

AudioWorklet polyfill using the legacy ScriptProcessor API.

126 lines (111 loc) 4.13 kB
/** * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ import { Realm } from './realm'; const PARAMS = []; let nextPort; if (typeof AudioWorkletNode !== 'function' || !('audioWorklet' in AudioContext.prototype)) { self.AudioWorkletNode = function AudioWorkletNode (context, name, options) { const processor = getProcessorsForContext(context)[name]; const outputChannels = options && options.outputChannelCount ? options.outputChannelCount[0] : 2; const scriptProcessor = context.createScriptProcessor(undefined, 2, outputChannels); scriptProcessor.parameters = new Map(); if (processor.properties) { for (let i = 0; i < processor.properties.length; i++) { const prop = processor.properties[i]; const node = context.createGain().gain; node.value = prop.defaultValue; // @TODO there's no good way to construct the proxy AudioParam here scriptProcessor.parameters.set(prop.name, node); } } const mc = new MessageChannel(); nextPort = mc.port2; const inst = new processor.Processor(options || {}); nextPort = null; scriptProcessor.port = mc.port1; scriptProcessor.processor = processor; scriptProcessor.instance = inst; scriptProcessor.onaudioprocess = onAudioProcess; return scriptProcessor; }; Object.defineProperty((self.AudioContext || self.webkitAudioContext).prototype, 'audioWorklet', { get () { return this.$$audioWorklet || (this.$$audioWorklet = new self.AudioWorklet(this)); } }); self.AudioWorklet = class AudioWorklet { constructor (audioContext) { this.$$context = audioContext; } addModule (url, options) { return fetch(url).then(r => { if (!r.ok) throw Error(r.status); return r.text(); }).then(code => { const context = { sampleRate: this.$$context.sampleRate, currentTime: this.$$context.currentTime, AudioWorkletProcessor () { this.port = nextPort; }, registerProcessor: (name, Processor) => { const processors = getProcessorsForContext(this.$$context); processors[name] = { realm, context, Processor, properties: Processor.parameterDescriptors || [] }; } }; context.self = context; const realm = new Realm(context, document.documentElement); realm.exec(((options && options.transpile) || String)(code)); return null; }); } }; } function onAudioProcess (e) { const parameters = {}; let index = -1; this.parameters.forEach((value, key) => { const arr = PARAMS[++index] || (PARAMS[index] = new Float32Array(this.bufferSize)); // @TODO proper values here if possible arr.fill(value.value); parameters[key] = arr; }); this.processor.realm.exec( 'self.sampleRate=sampleRate=' + this.context.sampleRate + ';' + 'self.currentTime=currentTime=' + this.context.currentTime ); const inputs = channelToArray(e.inputBuffer); const outputs = channelToArray(e.outputBuffer); this.instance.process([inputs], [outputs], parameters); // @todo - keepalive // let ret = this.instance.process([inputs], [outputs], parameters); // if (ret === true) { } } function channelToArray (ch) { const out = []; for (let i = 0; i < ch.numberOfChannels; i++) { out[i] = ch.getChannelData(i); } return out; } function getProcessorsForContext (audioContext) { return audioContext.$$processors || (audioContext.$$processors = {}); }