1068 lines · 43.7 KB · JavaScript
RawDownload
// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------- wasm-audio-helper.js ------------------------- // /** * 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. */ // Basic byte unit of WASM heap. (16 bit = 2 bytes)const BYTES_PER_UNIT = Uint16Array.BYTES_PER_ELEMENT; // Byte per audio sample. (32 bit float)const BYTES_PER_SAMPLE = Float32Array.BYTES_PER_ELEMENT; // The max audio channel on Chrome is 32.const MAX_CHANNEL_COUNT = 32; // WebAudio's render quantum size.const RENDER_QUANTUM_FRAMES = 128;  /** * A WASM HEAP wrapper for AudioBuffer class. This breaks down the AudioBuffer * into an Array of Float32Array for the convinient WASM opearion. * * @class * @dependency Module A WASM module generated by the emscripten glue code. */class HeapAudioBuffer {  /**   * @constructor   * @param  {object} wasmModule WASM module generated by Emscripten.   * @param  {number} length Buffer frame length.   * @param  {number} channelCount Number of channels.   * @param  {number=} maxChannelCount Maximum number of channels.   */  constructor(wasmModule, length, channelCount, maxChannelCount) {    // The |channelCount| must be greater than 0, and less than or equal to    // the maximum channel count.    this._isInitialized = false;    this._module = wasmModule;    this._length = length;    this._maxChannelCount = maxChannelCount        ? Math.min(maxChannelCount, MAX_CHANNEL_COUNT)        : channelCount;    this._channelCount = channelCount;    this._allocateHeap();    this._isInitialized = true;  }   /**   * Allocates memory in the WASM heap and set up Float32Array views for the   * channel data.   *   * @private   */  _allocateHeap() {    const channelByteSize = this._length * BYTES_PER_SAMPLE;    const dataByteSize = this._channelCount * channelByteSize;    this._dataPtr = this._module._malloc(dataByteSize);    this._channelData = [];    for (let i = 0; i < this._channelCount; ++i) {      let startByteOffset = this._dataPtr + i * channelByteSize;      let endByteOffset = startByteOffset + channelByteSize;      // Get the actual array index by dividing the byte offset by 2 bytes.      this._channelData[i] =          this._module.HEAPF32.subarray(startByteOffset >> BYTES_PER_UNIT,                                        endByteOffset >> BYTES_PER_UNIT);    }  }   /**   * Adapt the current channel count to the new input buffer.   *   * @param  {number} newChannelCount The new channel count.   */  adaptChannel(newChannelCount) {    if (newChannelCount < this._maxChannelCount) {      this._channelCount = newChannelCount;    }    // reform subArrays just in case memory has grown since we last did this    const channelByteSize = this._length * BYTES_PER_SAMPLE;    for (let i = 0; i < this._channelCount; ++i)     {      let startByteOffset = this._dataPtr + i * channelByteSize;      let endByteOffset = startByteOffset + channelByteSize;      // Get the actual array index by dividing the byte offset by 2 bytes.      this._channelData[i] =          this._module.HEAPF32.subarray(startByteOffset >> BYTES_PER_UNIT,                                        endByteOffset >> BYTES_PER_UNIT);    }  }   /**   * Getter for the buffer length in frames.   *   * @return {?number} Buffer length in frames.   */  get length() {    return this._isInitialized ? this._length : null;  }   /**   * Getter for the number of channels.   *   * @return {?number} Buffer length in frames.   */  get numberOfChannels() {    return this._isInitialized ? this._channelCount : null;  }   /**   * Getter for the maxixmum number of channels allowed for the instance.   *   * @return {?number} Buffer length in frames.   */  get maxChannelCount() {    return this._isInitialized ? this._maxChannelCount : null;  }   /**   * Returns a Float32Array object for a given channel index. If the channel   * index is undefined, it returns the reference to the entire array of channel   * data.   *   * @param  {number|undefined} channelIndex Channel index.   * @return {?Array} a channel data array or an   * array of channel data.   */  getChannelData(channelIndex) {    if (channelIndex >= this._channelCount) {      return null;    }    return typeof channelIndex === 'undefined'        ? this._channelData : this._channelData[channelIndex];  }   /**   * Returns the base address of the allocated memory space in the WASM heap.   *   * @return {number} WASM Heap address.   */  getHeapAddress() {    return this._dataPtr;  }   /**   * Frees the allocated memory space in the WASM heap.   */  free() {    this._isInitialized = false;    this._module._free(this._dataPtr);    this._module._free(this._pointerArrayPtr);    this._channelData = null;  }} // class HeapAudioBuffer  /** * A JS FIFO implementation for the AudioWorklet. 3 assumptions for the * simpler operation: *  1. the push and the pull operation are done by 128 frames. (Web Audio *    API's render quantum size in the speficiation) *  2. the channel count of input/output cannot be changed dynamically. *    The AudioWorkletNode should be configured with the `.channelCount = k` *    (where k is the channel count you want) and *    `.channelCountMode = explicit`. *  3. This is for the single-thread operation. (obviously) * * @class */class RingBuffer {  /**   * @constructor   * @param  {number} length Buffer length in frames.   * @param  {number} channelCount Buffer channel count.   */  constructor(length, channelCount) {    this._readIndex = 0;    this._writeIndex = 0;    this._framesAvailable = 0;     this._channelCount = channelCount;    this._length = length;    this._channelData = [];    for (let i = 0; i < this._channelCount; ++i) {      this._channelData[i] = new Float32Array(length);    }  }   /**   * Getter for Available frames in buffer.   *   * @return {number} Available frames in buffer.   */  get framesAvailable() {    return this._framesAvailable;  }   /**   * Push a sequence of Float32Arrays to buffer.   *   * @param  {array} arraySequence A sequence of Float32Arrays.   */  push(arraySequence) {    // The channel count of arraySequence and the length of each channel must    // match with this buffer obejct.     // Transfer data from the |arraySequence| storage to the internal buffer.    let sourceLength = arraySequence[0].length;    for (let i = 0; i < sourceLength; ++i) {      let writeIndex = (this._writeIndex + i) % this._length;      for (let channel = 0; channel < this._channelCount; ++channel) {        this._channelData[channel][writeIndex] = arraySequence[channel][i];      }    }     this._writeIndex += sourceLength;    if (this._writeIndex >= this._length) {      this._writeIndex = 0;    }     // For excessive frames, the buffer will be overwritten.    this._framesAvailable += sourceLength;    if (this._framesAvailable > this._length) {      this._framesAvailable = this._length;    }  }   /**   * Pull data out of buffer and fill a given sequence of Float32Arrays.   *   * @param  {array} arraySequence An array of Float32Arrays.   */  pull(arraySequence) {    // The channel count of arraySequence and the length of each channel must    // match with this buffer obejct.     // If the FIFO is completely empty, do nothing.    if (this._framesAvailable === 0) {      return;    }     let destinationLength = arraySequence[0].length;     // Transfer data from the internal buffer to the |arraySequence| storage.    for (let i = 0; i < destinationLength; ++i) {      let readIndex = (this._readIndex + i) % this._length;      for (let channel = 0; channel < this._channelCount; ++channel) {        arraySequence[channel][i] = this._channelData[channel][readIndex];      }    }     this._readIndex += destinationLength;    if (this._readIndex >= this._length) {      this._readIndex = 0;    }     this._framesAvailable -= destinationLength;    if (this._framesAvailable < 0) {      this._framesAvailable = 0;    }  }} // class RingBuffer // ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //    // ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// ------------------------------------------------------------------------ //// --------------------------- chucknode.js ------------------------------- // var globalInit = false;var globalPromise = undefined;var chucks = {}; var setDataDir, setLogLevel, initChuckInstance,     clearChuckInstance, clearGlobals, cleanupChuckInstance,    cleanRegisteredChucks,         runChuckCode, runChuckCodeWithReplacementDac, runChuckFile,    runChuckFileWithReplacementDac, runChuckFileWithArgs,    runChuckFileWithArgsWithReplacementDac,        replaceChuckCode, replaceChuckCodeWithReplacementDac,    replaceChuckFile, replaceChuckFileWithReplacementDac,    replaceChuckFileWithArgs, replaceChuckFileWithArgsWithReplacementDac,        isShredActive,    removeShred,     // don't care about these because emscripten has another mechanism    // for rerouting print statements    // setChoutCallback =     // setCherrCallback =     // setStdoutCallback =     // setStderrCallback =      // set/get global int, float, string    setChuckInt, getChuckInt, setChuckFloat, getChuckFloat,    setChuckString, getChuckString,     // global Events    signalChuckEvent, broadcastChuckEvent, listenForChuckEventOnce,    startListeningForChuckEvent, stopListeningForChuckEvent,     // note: array is SAMPLE == Float32    // NOTE ALSO anything using arrays cannot use cwrap; use in similar manner to Module._chuckManualAudioCallback    // TODO    // getGlobalUGenSamples = Module.cwrap( 'getGlobalUGenSamples', 'number', ['number', 'string', 'array', 'number'] );     // set/get global int arrays    // note: anything using arrays cannot use cwrap (since 'array' is an Int64Array)    //  called manually with heap manipulation below    //  setGlobalIntArray = Module.cwrap( 'setGlobalIntArray', 'number', ['number', 'string', 'array', 'number'] );    getGlobalIntArray, setGlobalIntArrayValue, getGlobalIntArrayValue,    setGlobalAssociativeIntArrayValue, getGlobalAssociativeIntArrayValue,     // set/get global float arrays    // note: anything using arrays cannot use cwrap. called manually with heap manipulation below    //  setGlobalFloatArray = Module.cwrap( 'setGlobalFloatArray', 'number', ['number', 'string', 'array', 'number'] );    getGlobalFloatArray, setGlobalFloatArrayValue, getGlobalFloatArrayValue,    setGlobalAssociativeFloatArrayValue, getGlobalAssociativeFloatArrayValue; var initGlobals = function( Module ){    // metaproperties (e.g. setDataDir must be called before init_chuck    setDataDir = Module.cwrap( 'setDataDir', 'number', ['string'] );    setLogLevel = Module.cwrap( 'setLogLevel', 'number', ['number'] );     // initialize a VM    initChuckInstance = Module.cwrap( 'initChuckInstance', 'number', ['number', 'number', 'number', 'number'] );                // cleanup     clearChuckInstance = Module.cwrap( 'clearChuckInstance', 'number', ['number'] );    clearGlobals = Module.cwrap( 'clearGlobals', 'number', ['number'] );    cleanupChuckInstance = Module.cwrap( 'cleanupChuckInstance', 'number', ['number'] );    cleanRegisteredChucks = Module.cwrap( 'cleanRegisteredChucks', null );     // running code     runChuckCode = Module.cwrap( 'runChuckCode', 'number', ['number', 'string'] );    runChuckCodeWithReplacementDac = Module.cwrap( 'runChuckCodeWithReplacementDac', 'number', ['number', 'string', 'string'] );    runChuckFile = Module.cwrap( 'runChuckFile', 'number', ['number', 'string'] );    runChuckFileWithReplacementDac = Module.cwrap( 'runChuckFileWithReplacementDac', 'number', ['number', 'string', 'string'] );    runChuckFileWithArgs = Module.cwrap( 'runChuckFileWithArgs', 'number', ['number', 'string', 'string'] );    runChuckFileWithArgsWithReplacementDac = Module.cwrap( 'runChuckFileWithArgsWithReplacementDac', 'number', ['number', 'string', 'string', 'string'] );        replaceChuckCode = Module.cwrap( 'replaceChuckCode', 'number', ['number', 'number', 'string'] );    replaceChuckCodeWithReplacementDac = Module.cwrap( 'replaceChuckCodeWithReplacementDac', 'number', ['number', 'number', 'string', 'string'] );    replaceChuckFile = Module.cwrap( 'replaceChuckFile', 'number', ['number', 'number', 'string'] );    replaceChuckFileWithReplacementDac = Module.cwrap( 'replaceChuckFileWithReplacementDac', 'number', ['number', 'number', 'string', 'string'] );    replaceChuckFileWithArgs = Module.cwrap( 'replaceChuckFileWithArgs', 'number', ['number', 'number', 'string', 'string'] );    replaceChuckFileWithArgsWithReplacementDac = Module.cwrap( 'replaceChuckFileWithArgsWithReplacementDac', 'number', ['number', 'number', 'string', 'string', 'string'] );            isShredActive = Module.cwrap( 'isShredActive', 'number', ['number', 'number'] );    removeShred = Module.cwrap( 'removeShred', 'number', ['number', 'number'] );     // don't care about these because emscripten has another mechanism    // for rerouting print statements    // setChoutCallback =     // setCherrCallback =     // setStdoutCallback =     // setStderrCallback =      // set/get global int, float, string    setChuckInt = Module.cwrap( 'setChuckInt', 'number', ['number', 'string', 'number'] );    getChuckInt = Module.cwrap( 'getChuckInt', 'number', ['number', 'string'] );    setChuckFloat = Module.cwrap( 'setChuckFloat', 'number', ['number', 'string', 'number'] );    getChuckFloat = Module.cwrap( 'getChuckFloat', 'number', ['number', 'string'] );    setChuckString = Module.cwrap( 'setChuckString', 'number', ['number', 'string', 'string'] );    getChuckString = Module.cwrap( 'getChuckString', 'number', ['number', 'string', 'number'] );     // global Events    signalChuckEvent = Module.cwrap( 'signalChuckEvent', 'number', ['number', 'string'] );    broadcastChuckEvent = Module.cwrap( 'broadcastChuckEvent', 'number', ['number', 'string'] );    listenForChuckEventOnce = Module.cwrap( 'listenForChuckEventOnce', 'number', ['number', 'string', 'number'] );    startListeningForChuckEvent = Module.cwrap( 'startListeningForChuckEvent', 'number', ['number', 'string', 'number']);    stopListeningForChuckEvent = Module.cwrap( 'stopListeningForChuckEvent', 'number', ['number', 'string', 'number']);     // note: array is SAMPLE == Float32    // NOTE ALSO anything using arrays cannot use cwrap; use in similar manner to Module._chuckManualAudioCallback    // TODO    // getGlobalUGenSamples = Module.cwrap( 'getGlobalUGenSamples', 'number', ['number', 'string', 'array', 'number'] );     // set/get global int arrays    // note: anything using arrays cannot use cwrap (since 'array' is an Int64Array)    //  called manually with heap manipulation below    //  setGlobalIntArray = Module.cwrap( 'setGlobalIntArray', 'number', ['number', 'string', 'array', 'number'] );    getGlobalIntArray = Module.cwrap( 'getGlobalIntArray', 'number', ['number', 'string', 'number'] );    setGlobalIntArrayValue = Module.cwrap( 'setGlobalIntArrayValue', 'number', ['number', 'string', 'number', 'number'] );    getGlobalIntArrayValue = Module.cwrap( 'getGlobalIntArrayValue', 'number', ['number', 'string', 'number'] );    setGlobalAssociativeIntArrayValue = Module.cwrap( 'setGlobalAssociativeIntArrayValue', 'number', ['number', 'string', 'string', 'number'] );    getGlobalAssociativeIntArrayValue = Module.cwrap( 'getGlobalAssociativeIntArrayValue', 'number', ['number', 'string', 'string'] );     // set/get global float arrays    // note: anything using arrays cannot use cwrap. called manually with heap manipulation below    //  setGlobalFloatArray = Module.cwrap( 'setGlobalFloatArray', 'number', ['number', 'string', 'array', 'number'] );    getGlobalFloatArray = Module.cwrap( 'getGlobalFloatArray', 'number', ['number', 'string', 'number'] );    setGlobalFloatArrayValue = Module.cwrap( 'setGlobalFloatArrayValue', 'number', ['number', 'string', 'number', 'number'] );    getGlobalFloatArrayValue = Module.cwrap( 'getGlobalFloatArrayValue', 'number', ['number', 'string', 'number'] );    setGlobalAssociativeFloatArrayValue = Module.cwrap( 'setGlobalAssociativeFloatArrayValue', 'number', ['number', 'string', 'string', 'number'] );    getGlobalAssociativeFloatArrayValue = Module.cwrap( 'getGlobalAssociativeFloatArrayValue', 'number', ['number', 'string', 'string'] );     // set data dir to "/" for embedded files    setDataDir( "/" );} class ChuckNode extends AudioWorkletProcessor{    constructor( options )     {        super();                this.srate = options.processorOptions.srate;        this.inChannels = options.outputChannelCount[0];        this.outChannels = options.outputChannelCount[0];                this.myID = options.processorOptions.chuckID;                chucks[this.myID] = this;                // do this in response to an incoming message        this.port.onmessage = this.handle_message.bind(this);                            this.myPointers = {}        this.myActiveShreds = [];        this.haveInit = false;         this.init( options.processorOptions.preloadedFiles,                    options.processorOptions.wasm );    }        init( preloadedFiles, wasm )    {        if( !globalInit )        {            var PreModule = {                wasmBinary: wasm,                print: (function( self )                     {                        return function( text )                        {                            console.log( text );                            self.port.postMessage( { type: "console print", message: text } );                        }                    })( this ),                printErr: (function( self )                    {                        return function( text )                        {                            console.error( text );                            self.port.postMessage( { type: "console print", message: text } );                        }                    })( this ),                // don't try to decode audio files; I'm really truly trying to copy the binaries only I promise                noAudioDecoding: true,                // don't try to decode images either, just so it will stop printing warnings it can't do it                noImageDecoding: true            };                PreModule[ "preRun" ] = [                (function( filesToPreload, Module )                {                    return function()                    {                        for( var i = 0; i < filesToPreload.length; i++ )                        {                            Module["FS_createPreloadedFile"]( "/",                                 filesToPreload[i].filename,                                 filesToPreload[i].data, true, true );                        }                    }                })( preloadedFiles, PreModule )            ]            

Lines 1–500 of 1068.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t