UNPKG

node-web-audio-api

Version:
202 lines (169 loc) 7.4 kB
import conversions from 'webidl-conversions'; import nativeBinding from '../load-native.js'; import { isFunction, kEnumerableProperty, } from './lib/utils.js'; import { throwSanitizedError, } from './lib/errors.js'; import { kNapiObj, } from './lib/symbols.js'; import { propagateEvent, } from './lib/events.js'; import { AudioNode } from './AudioNode.js'; import { BaseAudioContext } from './BaseAudioContext.js'; import { AudioBuffer } from './AudioBuffer.js'; import { AudioProcessingEvent } from './Events.js'; export class ScriptProcessorNode extends AudioNode { #onaudioprocess = null; constructor(context, options) { if (arguments.length < 1) { throw new TypeError(`Failed to construct 'ScriptProcessorNode': 1 argument required, but only ${arguments.length} present`); } if (!(context instanceof BaseAudioContext)) { throw new TypeError(`Failed to construct 'ScriptProcessorNode': argument 1 is not of type BaseAudioContext`); } const parsedOptions = {}; if (options && typeof options !== 'object') { throw new TypeError('Failed to construct \'ScriptProcessorNode\': argument 2 is not of type \'ScriptProcessorNodeOptions\''); } // IDL defines bufferSize default value as 0 // cf. https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createscriptprocessor // > If it’s not passed in, or if the value is 0, then the implementation // > will choose the best buffer size for the given environment, which will // > be constant power of 2 throughout the lifetime of the node. if (options && options.bufferSize !== undefined && options.bufferSize !== 0) { parsedOptions.bufferSize = conversions['unsigned long'](options.bufferSize, { enforceRange: true, context: `Failed to construct 'ScriptProcessorNode': Failed to read the 'bufferSize' property from ScriptProcessorNodeOptions: The provided value '${options.bufferSize}'`, }); } else { parsedOptions.bufferSize = 256; } if (options && options.numberOfInputChannels !== undefined) { parsedOptions.numberOfInputChannels = conversions['unsigned long'](options.numberOfInputChannels, { enforceRange: true, context: `Failed to construct 'ScriptProcessorNode': Failed to read the 'numberOfInputChannels' property from ScriptProcessorNodeOptions: The provided value '${options.numberOfInputChannels}'`, }); } else { parsedOptions.numberOfInputChannels = 2; } if (options && options.numberOfOutputChannels !== undefined) { parsedOptions.numberOfOutputChannels = conversions['unsigned long'](options.numberOfOutputChannels, { enforceRange: true, context: `Failed to construct 'ScriptProcessorNode': Failed to read the 'numberOfOutputChannels' property from ScriptProcessorNodeOptions: The provided value '${options.numberOfOutputChannels}'`, }); } else { parsedOptions.numberOfOutputChannels = 2; } if (options && options.channelCount !== undefined) { parsedOptions.channelCount = conversions['unsigned long'](options.channelCount, { enforceRange: true, context: `Failed to construct 'ScriptProcessorNode': Failed to read the 'channelCount' property from ScriptProcessorNodeOptions: The provided value '${options.channelCount}'`, }); } if (options && options.channelCountMode !== undefined) { parsedOptions.channelCountMode = conversions['DOMString'](options.channelCountMode, { context: `Failed to construct 'ScriptProcessorNode': Failed to read the 'channelCount' property from ScriptProcessorNodeOptions: The provided value '${options.channelCountMode}'`, }); } if (options && options.channelInterpretation !== undefined) { parsedOptions.channelInterpretation = conversions['DOMString'](options.channelInterpretation, { context: `Failed to construct 'ScriptProcessorNode': Failed to read the 'channelInterpretation' property from ScriptProcessorNodeOptions: The provided value '${options.channelInterpretation}'`, }); } let napiObj; try { napiObj = new nativeBinding.NapiScriptProcessorNode(context[kNapiObj], parsedOptions); } catch (err) { throwSanitizedError(err); } super(context, { [kNapiObj]: napiObj, }); const numberOfInputChannels = parsedOptions.numberOfInputChannels; const numberOfOutputChannels = parsedOptions.numberOfOutputChannels; const bufferSize = parsedOptions.bufferSize; const inputBuffer = new AudioBuffer({ numberOfChannels: numberOfInputChannels, length: bufferSize, sampleRate: context.sampleRate, }); const outputBuffer = new AudioBuffer({ numberOfChannels: numberOfOutputChannels, length: bufferSize, sampleRate: context.sampleRate, }); this[kNapiObj].onaudioprocess((function(e) { // retrieve the ArrayBuffer from Buffer; const inputArrayBuffer = e.buffer; // first 8 bits are playbackTime const playbackTime = new Float64Array(inputArrayBuffer, 0, 8)[0]; // unpack channels data for (let channelNumber = 0; channelNumber < numberOfInputChannels; channelNumber++) { const offset = (channelNumber * bufferSize * 4) + 8; const channelData = new Float32Array(inputArrayBuffer, offset, bufferSize); inputBuffer.copyToChannel(channelData, channelNumber); } const audioProcessingEventInit = { playbackTime, inputBuffer, outputBuffer, }; const event = new AudioProcessingEvent('audioprocess', audioProcessingEventInit); propagateEvent(this, event); let channels = new Array(numberOfOutputChannels); for (let channelNumber = 0; channelNumber < numberOfOutputChannels; channelNumber++) { const channelBuffer = outputBuffer.getChannelData(channelNumber).buffer; const bufferView = new Uint8Array(channelBuffer); channels[channelNumber] = bufferView; } // pack output buffer data into raw Buffer const outputArrayBuffer = Buffer.concat(channels); return outputArrayBuffer; }).bind(this)); } get bufferSize() { if (!(this instanceof ScriptProcessorNode)) { throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'ScriptProcessorNode\''); } return this[kNapiObj].bufferSize; } get onaudioprocess() { if (!(this instanceof ScriptProcessorNode)) { throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'ScriptProcessorNode\''); } return this.#onaudioprocess; } set onaudioprocess(value) { if (!(this instanceof ScriptProcessorNode)) { throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'ScriptProcessorNode\''); } if (isFunction(value) || value === null) { this.#onaudioprocess = value; } } } Object.defineProperties(ScriptProcessorNode, { length: { __proto__: null, writable: false, enumerable: false, configurable: true, value: 0, }, }); Object.defineProperties(ScriptProcessorNode.prototype, { [Symbol.toStringTag]: { __proto__: null, writable: false, enumerable: false, configurable: true, value: 'ScriptProcessorNode', }, bufferSize: kEnumerableProperty, onaudioprocess: kEnumerableProperty, });