Source: CsoundObj.js

/*
 * C S O U N D
 *
 * L I C E N S E
 *
 * This software is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 */


/** Csound global AudioContext
 */
var CSOUND_AUDIO_CONTEXT = CSOUND_AUDIO_CONTEXT || 
    (function() {

        try {
            var AudioContext = window.AudioContext || window.webkitAudioContext;
            return new AudioContext();      
        }
        catch(error) {

            console.log('Web Audio API is not supported in this browser');
        }
        return null;
    }());


// Global singleton variables
var AudioWorkletGlobalScope = AudioWorkletGlobalScope || {};
var CSOUND_NODE_SCRIPT;

/* SETUP NODE TYPE */
if(typeof AudioWorkletNode !== 'undefined' &&
   CSOUND_AUDIO_CONTEXT.audioWorklet !== null) {
    console.log("Using WASM + AudioWorklet Csound implementation");
    CSOUND_NODE_SCRIPT = 'CsoundNode.js';
    CSOUND_AUDIO_CONTEXT.hasAudioWorklet = true;
} else {
    console.log("Using WASM + ScriptProcessorNode Csound implementation");
    CSOUND_NODE_SCRIPT = 'CsoundScriptProcessorNode.js';
    CSOUND_AUDIO_CONTEXT.hasAudioWorklet = false;  
}


const csound_load_script = function(src, callback) {
    var script = document.createElement('script');
    script.src = src;
    script.onload = callback;
    document.head.appendChild(script);
}


/** This ES6 Class provides an interface to the Csound
 * engine running on a node (an AudioWorkletNode where available,
 * ScriptProcessorNode elsewhere)
 * This class is designed to be compatible with
 * the previous ScriptProcessorNode-based CsoundObj
 */
class CsoundObj {
    /** Create a CsoundObj
     * @constructor 
     */
    constructor() {
        this.audioContext = CSOUND_AUDIO_CONTEXT;

        // exposes node as property, user may access to set port onMessage callback
        // or we can add a setOnMessage(cb) method on CsoundObj...
        this.node = CsoundObj.createNode();
        this.node.connect(this.audioContext.destination);
        this.microphoneNode = null;
    }

    /** Returns the underlying Csound node
        running the Csound engine.
    */
    getNode() {
        return this.node;
    }

    /** Writes data to a file in the WASM filesystem for
     *  use with csound.
     *
     * @param {string} filePath A string containing the path to write to.
     * @param {blob}   blobData The data to write to file.
     */
    writeToFS(filePath, blobData) {
        this.node.writeToFS(filePath, blobData);
    }
    
    /** Compiles a CSD, which may be given as a filename in the
     *  WASM filesystem or a string containing the code
     *
     * @param {string} csd A string containing the CSD filename or the CSD code.
     */
    compileCSD(csd) {
        this.node.compileCSD(csd);
    }

    /** Compiles Csound orchestra code.
     *
     * @param {string} orcString A string containing the orchestra code.
     */
    compileOrc(orcString) {
        this.node.compileOrc(orcString);
    }

    /** Sets a Csound engine option (flag)
     *  
     *
     * @param {string} option The Csound engine option to set. This should
     * not contain any whitespace.
     */
    setOption(option) {
        this.node.setOption(option);  
    }

    render(filePath) {
    }
    
    /** Evaluates Csound orchestra code.
     *
     * @param {string} codeString A string containing the orchestra code.
     */   
    evaluateCode(codeString) {
        this.node.evaluateCode(codeString);
    }

    /** Reads a numeric score string.
     *
     * @param {string} scoreString A string containing a numeric score.
     */     
    readScore(scoreString) {
        this.node.readScore(scoreString);
    }

    /** Sets the value of a control channel in the software bus
     *
     * @param {string} channelName A string containing the channel name.
     * @param {number} value The value to be set.
     */   
    setControlChannel(channelName, value) {
        this.node.setControlChannel(channelName, value);
    }

    /** Sets the value of a string channel in the software bus
     *
     * @param {string} channelName A string containing the channel name.
     * @param {string} stringValue The string to be set.
     */     
    setStringChannel(channelName, stringValue) {
        this.node.setStringChannel(channelName, stringValue);
    }

    /** Starts the node containing the Csound engine.
     */
    start() {
        if(this.microphoneNode != null) {
            this.microphoneNode.connect(this.node);
        }
        this.node.start();
    }

    /** Resets the Csound engine.
     */
    reset() {
        this.node.reset();
    }

    destroy() {
    }

    /** Starts performance, same as start()
     */
    play() {
        this.node.play();
    }

    /** Stops (pauses) performance
     */
    stop() {
        this.node.stop();
    }

    /** Sets a callback to process Csound console messages.
     *
     * @param {function} msgCallback A callback to process messages 
     * with signature function(message), where message is a string
     * from Csound.
     */    
    setMessageCallback(msgCallback) {
        this.node.setMessageCallback(msgCallback);
    }

    /** Sends a MIDI channel message to Csound
     *
     * @param {number} byte1 MIDI status byte
     * @param {number} byte2 MIDI data byte 1
     * @param {number} byte1 MIDI data byte 2
     *
     */
    midiMessage(byte1, byte2, byte3) {
        this.node.midiMessage(byte1, byte2, byte3);
    }

    /** Enables microphone (external audio) input in browser
     *
     * @param {function} audioInputCallback A callback with a signature
     * function(result), with result set to true in the event of success
     * or false if the microphone cannot be enabled
     */ 
    enableAudioInput(audioInputCallback) {

        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || null;
        let that = this;

        if (navigator.getUserMedia === null) {
            //Module['print']("Audio Input not supported in this browser");
            audioInputCallback(false);
        } else {
            let onSuccess = function(stream) {
                that.microphoneNode = CSOUND_AUDIO_CONTEXT.createMediaStreamSource(stream);
                audioInputCallback(true);
            };

            let onFailure = function(error) {
                that.microphoneNode = null;
                audioInputCallback(false);
                //Module['print']("Could not initialise audio input, error:" + error);
            };
            navigator.getUserMedia({
                audio: true, 
                video: false
            }, onSuccess, onFailure);
        }
    }

    
    /** 
     * This static method is used to asynchronously setup the Csound
     *  engine node.
     *
     * @param {string} script_base A string containing the base path to scripts
     */
    static importScripts(script_base='./') {
        return new Promise((resolve) => {
            csound_load_script(script_base + CSOUND_NODE_SCRIPT, () => {
                if(CSOUND_AUDIO_CONTEXT.hasAudioWorklet) CSOUND_AUDIO_CONTEXT.factory = CsoundNodeFactory;
                else CSOUND_AUDIO_CONTEXT.factory = CsoundScriptProcessorNodeFactory;
                CSOUND_AUDIO_CONTEXT.factory.importScripts(script_base).then(() => {
                    resolve();
                })
            })
        }) 
    }

    /** 
     * This static method creates a new Csound Engine node unattached
     * to a CsoundObj object. It can be used in scenarios where 
     * CsoundObj is not needed (ie. WebAudio API programming)
     *
     *  @param {number} InputChannelCount number of input channels
     *  @param {number} OutputChannelCount number of output channels
     *  @return A new Csound Engine Node (CsoundNode or CsoundScriptProcessorNode)
     */
    static createNode(inputChannelCount=1, outputChannelCount=2) {
        return CSOUND_AUDIO_CONTEXT.factory.createNode(inputChannelCount,outputChannelCount);
    }

}