/*
CsoundNode.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
*/
// Setup a single global AudioContext object if not already defined
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;
}());
/** This ES6 Class defines a Custom Node as an AudioWorkletNode
* that holds a Csound engine.
*/
class CsoundNode extends AudioWorkletNode {
/**
*
* @constructor
* @param {AudioContext} context AudioContext in which this node will run
* @param {object} options Configuration options, holding numberOfInputs,
* numberOfOutputs
* @returns {object} A new CsoundNode
*/
constructor(context, options) {
options = options || {};
super(context, 'Csound', options);
this.msgCallback = (msg) => { console.log(msg); }
this.port.start();
this.channels = {};
this.channelCallbacks = {};
this.stringChannels = {};
this.stringChannelCallbacks = {};
this.table = {};
this.tableCallbacks = {};
this.port.onmessage = (event) => {
let data = event.data;
switch(data[0]) {
case "log":
this.msgCallback(data[1]);
break;
case "control":
this.channels[data[1]] = data[2];
if (typeof this.channelCallbacks[data[1]] != 'undefined')
this.channelCallbacks[data[1]]();
break;
case "stringChannel":
this.stringChannels[data[1]] = data[2];
if (typeof this.stringChannelCallbacks[data[1]] != 'undefined')
this.stringChannelCallbacks[data[1]]();
break;
case "table":
this.table[data[1]] = data[2];
if (typeof this.tableCallbacks[data[1]] != 'undefined')
this.tableCallbacks[data[1]]();
break;
default:
console.log('[CsoundNode] Invalid Message: "' + event.data);
}
};
}
/** 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.port.postMessage(["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(filePath) {
this.port.postMessage(["compileCSD", filePath]);
}
/** Compiles Csound orchestra code.
*
* @param {string} orcString A string containing the orchestra code.
*/
compileOrc(orcString) {
this.port.postMessage(["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.port.postMessage(["setOption", option]);
}
render(filePath) {
}
/** Evaluates Csound orchestra code.
*
* @param {string} codeString A string containing the orchestra code.
*/
evaluateCode(codeString) {
this.port.postMessage(["evalCode", codeString]);
}
/** Reads a numeric score string.
*
* @param {string} scoreString A string containing a numeric score.
*/
readScore(scoreString) {
this.port.postMessage(["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.port.postMessage(["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, value) {
this.port.postMessage(["setStringChannel",
channelName, value]);
}
/** 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.port.postMessage(["getControlChannel",
channelName]);
if (callback !== null)
this.channelCallbacks[channelName] = callback;
}
/** Request the data from a String 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.port.postMessage(["getStringChannel",
channelName]);
if (callback !== null)
this.stringChannelCallbacks[channelName] = callback;
}
/** Get the latest requested channel data
*
* @param {string} channelName A string containing the channel name.
* @returns {number} The latest channel value requested.
*/
getControlChannel(channelName) {
return this.channels[channelName];
}
/** Get the latest requested string channel data
*
* @param {string} channelName A string containing the channel name.
* @returns {string} The latest channel value requested.
*/
getStringChannel(channelName) {
return this.stringChannels[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.port.postMessage(["getTable", number]);
if (callback !== null)
this.tableCallbacks[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.table[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.port.postMessage(["setTableAtIndex", 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.port.postMessage(["setTable", number, table]);
}
/** Starts processing in this node
*/
start() {
this.port.postMessage(["start"]);
}
/** Resets the Csound engine.
*/
reset() {
this.port.postMessage(["reset"]);
}
destroy() {
}
/** Starts performance, same as start()
*/
play() {
this.port.postMessage(["play"]);
}
/** Stops (pauses) performance
*/
stop() {
this.port.postMessage(["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.msgCallback = 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.port.postMessage(["midiMessage", byte1, byte2, byte3]);
}
}
/** This E6 class is used to setup scripts and
allow the creation of new CsoundNode objects
@hideconstructor
*/
class CsoundNodeFactory {
/**
* This static method is used to asynchronously setup scripts for AudioWorklet Csound
*
* @param {string} script_base A string containing the base path to scripts
*/
static importScripts(script_base='./') {
let actx = CSOUND_AUDIO_CONTEXT;
return new Promise( (resolve) => {
actx.audioWorklet.addModule(script_base + 'CsoundProcessor.js').then(() => {
resolve();
})
})
}
/**
* This static method creates a new CsoundNode.
* @param {number} InputChannelCount number of input channels
* @param {number} OutputChannelCount number of output channels
* @returns {object}
*/
static createNode(inputChannelCount=1, outputChannelCount=2) {
var options = {};
options.numberOfInputs = inputChannelCount;
options.numberOfOutputs = 1;
options.outputChannelCount = [ outputChannelCount ];
return new CsoundNode(CSOUND_AUDIO_CONTEXT, options);
}
}