/**
* This module attempts to load, and to use, in order of decreasing
* preference:
* (1) Injected csound (e.g. Csound for Android or CsoundQt).
* (2) csound.node.
* (3) Csound for WebAssembly (csound.js, creates CsoudObj).
* (4) Csound for WebAssembly (CsoundAudioNode.js, based on AudioWorklet).
*
* Please note, for WebAudio, code is asynchronous but is wrapped in promises
* using the async keyword to make calls behave synchronously; the calling
* code (e.g. "Play" button handlers) must therefore also be declared async.
* Not only that, but for other platforms handlers should also be declared
* async.
*
* To use this script, include it at the top of the body of your Web page and
* and then call `let csound_ = async get_csound(csound_message_callback).`
* The result will be a live csound_ object. Call `get_csound` in this way
* in every block of code that will call Csound methods.
*
* Please note, the Csound performance should (and sometimes must) be
* configured with sr, ksmps, nchnls, nchnls_i, and sample word format
* matching the host platform.
*
* On Linux, PulseAudio may cause problems. It is better to disable PulseAudio
* and use a low-level ALSA configuration.
*
* On Android, csound.SetMessageCallback does not work. Instead, assign the
* message callback function to console.log.
*/
// These are globals:
csound_injected = null;
csound_node = null;
csound_obj = null;
csound_audio_node = null;
csound_is_loaded = false;
var get_operating_system = function() {
let operating_system = "unknown";
let platform = navigator.platform;
if (platform.startsWith('Mac')) {
return 'Macintosh';
}
if (platform.startsWith('Win')) {
return 'Windows';
}
if (platform.startsWith('Linux')) {
return 'Linux';
}
let userAgent = navigator.userAgent || navigator.vendor || window.opera;
console.log("userAgent: " + userAgent + "\n");
// Windows Phone must come first because its UA also contains "Android"
if (/windows phone/i.test(userAgent)) {
operating_system = "Windows Phone";
console.log("operating_system: " + operating_system + "\n");
return operating_system;
}
if (/android/i.test(userAgent)) {
operating_system = "Android";
console.log("operating_system: " + operating_system + "\n");
return operating_system;
}
// iOS detection from: http://stackoverflow.com/a/9039885/177710
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
operating_system = "iOS";
console.log("operating_system: " + operating_system + "\n");
return operating_system;
}
if (/Macintosh/.test(userAgent)) {
operating_system = "Macintosh";
console.log("operating_system: " + operating_system + "\n");
return operating_system;
}
console.log("operating_system: " + operating_system + "\n");
return operating_system;
}
/**
* There is an issue on Android in that csound may be undefined when the page is
* first rendered, and be defined only when the user plays the piece.
*/
var load_csound = async function(csound_message_callback_) {
let operating_system = get_operating_system();
if (operating_system === "Android" && typeof csound === 'undefined') {
csound_message_callback("Operating system is Android, but Csound is not yet defined.\n");
// On Android, Csound uses only stdout for messages; this becomes
// console.log, so we assign our "csound message callback" to
// console.log.
return;
}
if (typeof csound !== 'undefined') {
if (csound != null) {
csound_injected = csound;
csound_is_loaded = true;
console.log = csound_message_callback;
csound_message_callback_("Csound is already defined in this JavaScript context.\n");
return;
}
}
try {
csound_message_callback_("Trying to load csound.node...\n");
csound_node = await require('csound.node');
var nwgui = await require('nw.gui');
nw_window = await nwgui.Window.get();
nw_window.on('close', function() {
csound_message_callback_('Closing down...\n');
this.close(true);
});
csound_is_loaded = true;
csound_message_callback_("csound.node is available in this JavaScript context.\n");
return;
} catch (e) {
csound_message_callback_(e + '\n');
}
try {
csound_message_callback_("Trying to load csound.js...\n");
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext_ = new AudioContext();
let url = './csound.js';
const { Csound } = await import(url);
csound_obj = await Csound({audioContext: audioContext_});
csound_is_loaded = true;
csound_message_callback_("CsoundObj (AudioWorklet) is available in this JavaScript context.\n");
return;
} catch (e) {
csound_message_callback_(e + '\n');
}
try {
csound_message_callback_("Trying to load CsoundAudioNode.js...\n");
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext_ = new AudioContext();
await audioContext_.audioWorklet.addModule('CsoundAudioProcessor.js').then(function() {
csound_message_callback_("Creating CsoundAudioNode...\n");
csound_audio_node = new CsoundAudioNode(audioContext_, csound_message_callback_);
csound_is_loaded = true;
csound_message_callback_("CsoundAudioNode (AudioWorklet) is available in this JavaScript context.\n");
return;
}, function(error) {
csound_message_callback_(error + '\n');
});
} catch (e) {
csound_message_callback_(e + '\n');
}
}
/**
* Returns a singleton instance of Csound, if one is available in the
* JavaScript environment. If Csound has not been loaded, attempts are made
* to load it from various sources. The csound_message_callback parameter is
* required, but console.log can be passed.
*/
var get_csound = async function(csound_message_callback_) {
if (csound_is_loaded === false) {
await load_csound(csound_message_callback_);
}
if (csound_injected != null) {
csound = csound_injected;
return csound_injected;
} else if (csound_node != null) {
csound = csound_node;
csound.setMessageCallback(csound_message_callback_);
return csound_node;
} else if (csound_obj != null) {
csound = csound_obj;
await csound.on("message", csound_message_callback_);
return csound_obj;
} else if (csound_audio_node != null) {
csound = csound_audio_node;
csound.setMessageCallback(csound_message_callback_);
return csound_audio_node;
} else {
csound_message_callback_("Csound is still loading, wait a bit...\n");
}
}