/*
CsoundObj.js
Copyright (C) 2018 Steven Yi, Victor Lazzarini
This file is part of Csound.
The Csound Library 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.
Csound 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with Csound; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA
*/
/** 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 !== undefined) {
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.createElementNS("http://www.w3.org/1999/xhtml", "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);
}
/** Request the data from a control channel
*
* @param {string} channelName A string containing the channel name.
* @param {function} callback An optional callback to be called when
* the requested data is available. This can be set once for all
* subsequent requests.
*/
requestControlChannel(channelName, callback = null) {
this.node.requestControlChannel(channelName, callback);
}
/** Request the string data from a control channel
*
* @param {string} channelName A string containing the channel name.
* @param {function} callback An optional callback to be called when
* the requested data is available. This can be set once for all
* subsequent requests.
*/
requestStringChannel(channelName, callback = null) {
this.node.requestStringChannel(channelName, callback);
}
/** Get the latest requested control channel data
*
* @param {string} channelName A string containing the channel name.
* @returns {(number)} The latest channel value requested.
*/
getControlChannel(channelName) {
return this.node.getControlChannel(channelName);
}
/** Get the latest requested string channel data
*
* @param {string} channelName A string containing the channel name.
* @returns {(number|string)} The latest channel value requested.
*/
getStringChannel(channelName) {
return this.node.getStringChannel(channelName);
}
/** Request the data from a Csound function table
*
* @param {number} number The function table number
* @param {function} callback An optional callback to be called when
* the requested data is available. This can be set once for all
* subsequent requests.
*/
requestTable(number, callback = null) {
this.node.requestTable(number, callback);
}
/** Get the requested table number
*
* @param {number} number The function table number
* @returns {Float32Array} The table as a typed array.
*/
getTable(number) {
return this.node.getTable(number);
}
/** Set a specific table position
*
* @param {number} number The function table number
* @param {number} index The index of the position to be set
* @param {number} value The value to set
*/
setTableValue(number, index, value) {
this.node.setTableValue(number, index, value);
}
/** Set a table with data from an array
*
* @param {number} number The function table number
* @param {Float32Array} table The source data for the table
*/
setTable(number, table) {
this.node.setTable(number, table);
}
/** 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) {
console.log("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;
console.log("Could not initialise audio input, error:" + error);
audioInputCallback(false);
};
navigator.getUserMedia({
audio: true,
video: false
}, onSuccess, onFailure);
}
}
enableMidiInput(midiInputCallback) {
const handleMidiInput = (evt) => {
this.midiMessage(evt.data[0], evt.data[1], evt.data[2]);
};
const midiSuccess = function(midiInterface) {
const inputs = midiInterface.inputs.values();
for (let input = inputs.next(); input && !input.done; input = inputs.next()) {
input = input.value;
input.onmidimessage = handleMidiInput;
}
if (midiInputCallback) {
midiInputCallback(true);
}
};
const midiFail = function(error) {
console.log("MIDI failed to start, error:" + error);
if (midiInputCallback) {
midiInputCallback(false);
}
};
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then(midiSuccess, midiFail);
} else {
console.log("MIDI not supported in this browser");
if (midiInputCallback) {
midiInputCallback(false);
}
}
}
/**
* 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);
}
}