//-----------------------------------------------------------------------------
// name: webchuck_host.js
// desc: webchuck host code connecting WebChucK/wasm to WebAudio
//       it also provides API to call ChucK inside the WASM
//
// author: Jack Atherton
//   date: 2017 (original implementation)
//-----------------------------------------------------------------------------
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = undefined;
var theChuck = undefined;
var theChuckAlmostReady = defer();
var theChuckReady = defer();
var filesToPreload = [];
var whereIsChuck = whereIsChuck || "./js";
var currentChuckID = 1;

// global variables (clearly marked since globals can get messy fast)
// these are provided to be used by external .js files (e.g., for visualization)
var GLOBAL_audioSend;
var GLOBAL_audioContext;

var chuckPrint = function( text )
{
    // override me!
}

// src: http://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/
function defer()
{
    var res, rej;

    var promise = new Promise((resolve, reject) => {
        res = resolve;
        rej = reject;
    });

    promise.resolve = res;
    promise.reject = rej;

    return promise;
}

// START taken from emscripten source
var readAsync = function( url, onload, onerror )
{
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'arraybuffer';
    xhr.onload = function xhr_onload() 
    {
        if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) // file URLs can return 0
        { 
            onload(xhr.response);
            return;
        }
        onerror();
    };
    xhr.onerror = onerror;
    xhr.send(null);
};

var asyncLoadFile = function( url, onload, onerror ) 
{
    readAsync(url, function(arrayBuffer) {
        onload(new Uint8Array(arrayBuffer));
    }, function(event) {
        if (onerror) {
            onerror();
        } else {
            throw 'Loading data file "' + url + '" failed.';
        }
    });
}
// END taken from emscripten source

var loadWasm = new Promise( function( resolve, reject )
{
    asyncLoadFile( whereIsChuck + '/webchuck.wasm', resolve, reject ); 
});

var preloadFilenames = async function( filenamesToPreload )
{
    var promises = [];
    for( var i = 0; i < filenamesToPreload.length; i++ )
    {
        (function( filenameToPreload )
        {
            promises.push( new Promise( function( resolve, reject )
            {
                asyncLoadFile( filenameToPreload.serverFilename, function( byteArray )
                {
                    filesToPreload.push({
                        filename: filenameToPreload.virtualFilename,
                        data: byteArray
                    });
                    resolve();
                }, function()
                {
                    console.error( "Error fetching file:", filenameToPreload.serverFilename );
                    //reject( e );
                    resolve();
                });
            }) );
        })( filenamesToPreload[i] );
        
    }
    await Promise.all( promises );
}

var theWasm;
var startAudioContext = async function()
{
    theWasm = await loadWasm;
    audioContext = new AudioContext({
        //sampleRate: 48000
    });
    await audioContext.resume();
    await audioContext.audioWorklet.addModule( whereIsChuck + '/webchuck.js');
}

var startChuck = async function() 
{
    if( audioContext === undefined )
    {
        // set up audio context
        await startAudioContext();
        
        // keep track of chuck ID
        var newID = currentChuckID;
        // increment chuck ID
        currentChuckID++;

        // create a node between ChucK and the output
        GLOBAL_audioSend = new GainNode( audioContext, {gain: 1} );
        // set the global audio context variable
        GLOBAL_audioContext = audioContext;

        // instantiate a chuck
        theChuck = await createAChuck( newID, theChuckReady );
        // connect to audioContext directly
        theChuck.connect( audioContext.destination );
        // connect to our global audio send, to be used elsewhere
        theChuck.connect( GLOBAL_audioSend );

        theChuckAlmostReady.resolve();
    }
};

var createAChuck = function( chuckID, initPromise )
{
    // important: "number of inputs / outputs" is like an aggregate source
    // most of the time, you only want one input source and one output
    // source, but each one has multiple channels
    var numInOutChannels = 2;
    
    var aChuck = new AudioWorkletNode( audioContext, 'chuck-node', { 
        numberOfInputs: 1,
        numberOfOutputs: 1,
        outputChannelCount: [numInOutChannels],
        processorOptions: {
            chuckID: chuckID,
            srate: audioContext.sampleRate,
            preloadedFiles: filesToPreload,
            wasm: theWasm
        }
    } );
    
    aChuck.chuckID = chuckID;
    
    (function( self )
    {
        self.eventCallbacks = {};
        self.deferredPromises = {};
        self.eventCallbackCounter = 0;
        self.deferredPromiseCounter = 0;
        self.amReady = initPromise;
        
        self.onprocessorerror = function( e )
        {
            console.log( e );
        }
        
        // respond to messages
        self.port.onmessage = function( event )
        {
            switch( event.data.type ) 
            {
                case "initCallback":
                    if( self.amReady )
                    {
                        self.amReady.resolve();
                    }
                    break;
                case "console print":
                    chuckPrint( event.data.message );
                    break;
                case "eventCallback":
                    if( event.data.callback in self.eventCallbacks )
                    {
                        self.eventCallbacks[event.data.callback]();
                    }
                    break;
                case "intCallback":
                case "floatCallback":
                case "stringCallback":
                case "intArrayCallback":
                case "floatArrayCallback":
                    if( event.data.callback in self.deferredPromises )
                    {
                        self.deferredPromises[event.data.callback].resolve(event.data.result);
                        delete self.deferredPromises[event.data.callback];
                    }
                    break;
                case "newShredCallback":
                    if( event.data.callback in self.deferredPromises )
                    {
                        if( event.data.shred > 0 )
                        {
                            self.deferredPromises[event.data.callback].resolve(event.data.shred);
                        }
                        else
                        {
                            self.deferredPromises[event.data.callback].reject("Running code failed");
                        }
                    }
                    break;
                case "replacedShredCallback":
                    if( event.data.callback in self.deferredPromises )
                    {
                        if( event.data.newShred > 0 )
                        {
                            self.deferredPromises[event.data.callback].resolve({
                                newShred: event.data.newShred,
                                oldShred: event.data.oldShred
                            });
                        }
                        else
                        {
                            self.deferredPromises[event.data.callback].reject("Replacing code failed");
                        }
                    }
                    break;
                case "removedShredCallback":
                    if( event.data.callback in self.deferredPromises )
                    {
                        if( event.data.shred > 0 )
                        {
                            self.deferredPromises[event.data.callback].resolve(event.data.shred);
                        }
                        else
                        {
                            self.deferredPromises[event.data.callback].reject("Removing code failed");
                        }
                    }
                    break;
                default:
                    break;
            }
        }
        self.nextDeferID = function()
        {
            var callbackID = self.deferredPromiseCounter++;
            self.deferredPromises[callbackID] = defer();
            return callbackID;
        }
        // ================== Filesystem ===================== //
        self.createFile = function( directory, filename, data )
        {
            self.port.postMessage( {
                type: 'loadFile',
                directory: directory,
                filename: filename,
                data: data
            } );
        }
        // ================== Run / Compile ================== //
        self.runCode = function( code )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "runChuckCode", 
                code: code,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.runCodeWithReplacementDac = function( code, dac_name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "runChuckCodeWithReplacementDac", 
                code: code,
                dac_name: dac_name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.runFile = function( filename )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: "runChuckFile",
                filename: filename,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.runFileWithReplacementDac = function( filename, dac_name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "runChuckFileWithReplacementDac", 
                filename: filename,
                dac_name: dac_name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.runFileWithArgs = function( filename, colon_separated_args )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "runChuckFileWithArgs", 
                filename: filename,
                colon_separated_args: colon_separated_args,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.runFileWithArgsWithReplacementDac = function( filename, colon_separated_args, dac_name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "runChuckFileWithArgs", 
                filename: filename,
                colon_separated_args: colon_separated_args,
                dac_name: dac_name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.replaceCode = function( code )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: "replaceChuckCode",
                code: code,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.replaceCodeWithReplacementDac = function( code, dac_name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "replaceChuckCodeWithReplacementDac", 
                code: code,
                dac_name: dac_name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.replaceFile = function( filename )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: "replaceChuckFile",
                filename: filename,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.replaceFileWithReplacementDac = function( filename, dac_name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "replaceChuckFileWithReplacementDac", 
                filename: filename,
                dac_name: dac_name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.replaceFileWithArgs = function( filename, colon_separated_args )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "replaceChuckFileWithArgs", 
                filename: filename,
                colon_separated_args: colon_separated_args,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.replaceFileWithArgsWithReplacementDac = function( filename, colon_separated_args, dac_name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( { 
                type: "replaceChuckFileWithArgs", 
                filename: filename,
                colon_separated_args: colon_separated_args,
                dac_name: dac_name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.removeLastCode = function()
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: "removeLastCode",
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.removeShred = function( shred )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: "removeShred",
                shred: shred,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.isShredActive = function( shred )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: "isShredActive",
                shred: shred,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        // ================== Int, Float, String ============= //
        self.setInt = function( variable, value )
        {
            self.port.postMessage( { 
                type: 'setChuckInt', 
                variable: variable,
                value: value
            } );
        }
        self.getInt = function( variable )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getChuckInt',
                variable: variable,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setFloat = function( variable, value )
        {
            self.port.postMessage( { 
                type: 'setChuckFloat', 
                variable: variable,
                value: value
            } );
        }
        self.getFloat = function( variable )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getChuckFloat',
                variable: variable,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setString = function( variable, value )
        {
            self.port.postMessage( { 
                type: 'setChuckString', 
                variable: variable,
                value: value
            } );
        }
        self.getString = function( variable )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getChuckString',
                variable: variable,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        // ================== Event =================== //
        self.signalEvent = function( variable )
        {
            self.port.postMessage( { type: 'signalChuckEvent', variable: variable } );
        }
        self.broadcastEvent = function( variable )
        {
            self.port.postMessage( { type: 'broadcastChuckEvent', variable: variable } );
        }
        self.listenForEventOnce = function( variable, callback )
        {
            var callbackID = self.eventCallbackCounter++;
            self.eventCallbacks[callbackID] = callback;
            self.port.postMessage( { 
                type: 'listenForChuckEventOnce',
                variable: variable,
                callback: callbackID
            } );
        }
        self.startListeningForEvent = function( variable, callback )
        {
            var callbackID = self.eventCallbackCounter++;
            self.eventCallbacks[callbackID] = callback;
            self.port.postMessage( { 
                type: 'startListeningForChuckEvent',
                variable: variable,
                callback: callbackID
            } );
            return callbackID;
        }
        self.stopListeningForEvent = function( variable, callbackID )
        {
            self.port.postMessage( { 
                type: 'stopListeningForChuckEvent',
                variable: variable,
                callback: callbackID
            } );
        }
        // ================== Int[] =================== //
        self.setIntArray = function( variable, values )
        {
            self.port.postMessage( {
                type: 'setGlobalIntArray',
                variable: variable,
                values: values
            } );
        }
        self.getIntArray = function( variable )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getGlobalIntArray',
                variable: variable,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setIntArrayValue = function( variable, index, value )
        {
            self.port.postMessage( {
                type: 'setGlobalIntArrayValue',
                variable: variable,
                index: index,
                value: value
            } );
        }
        self.getIntArrayValue = function( variable, index )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getGlobalIntArrayValue',
                variable: variable,
                index: index,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setAssociativeIntArrayValue = function( variable, key, value )
        {
            self.port.postMessage( {
                type: 'setGlobalAssociativeIntArrayValue',
                variable: variable,
                key: key,
                value: value
            } );
        }
        self.getAssociativeIntArrayValue = function( variable, key )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getGlobalAssociativeIntArrayValue',
                variable: variable,
                key: key,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        // ================== Float[] =================== //
        self.setFloatArray = function( variable, values )
        {
            self.port.postMessage( {
                type: 'setGlobalFloatArray',
                variable: variable,
                values: values
            } );
        }
        self.getFloatArray = function( variable )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getGlobalFloatArray',
                variable: variable,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        };
        self.setFloatArrayValue = function( variable, index, value )
        {
            self.port.postMessage( {
                type: 'setGlobalFloatArrayValue',
                variable: variable,
                index: index,
                value: value
            } );
        }
        self.getFloatArrayValue = function( variable, index )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getGlobalFloatArrayValue',
                variable: variable,
                index: index,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setAssociativeFloatArrayValue = function( variable, key, value )
        {
            self.port.postMessage( {
                type: 'setGlobalAssociativeFloatArrayValue',
                variable: variable,
                key: key,
                value: value
            } );
        }
        self.getAssociativeFloatArrayValue = function( variable, key )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getGlobalAssociativeFloatArrayValue',
                variable: variable,
                key: key,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        // ================== ChucK VM parameters ============= //
        self.setParamInt = function( name, value )
        {
            self.port.postMessage( {
                type: 'setParamInt',
                name: name,
                value: value
            } );
        }
        self.getParamInt = function( name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getParamInt',
                name: name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setParamFloat = function( name, value )
        {
            self.port.postMessage( {
                type: 'setParamFloat',
                name: name,
                value: value
            } );
        }
        self.getParamFloat = function( name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getParamFloat',
                name: name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.setParamString = function( name, value )
        {
            self.port.postMessage( {
                type: 'setParamString',
                name: name,
                value: value
            } );
        }
        self.getParamString = function( name )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getParamString',
                name: name,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        // ================= VM ====================== //
        self.now = function()
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'getChuckNow',
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        self.clearChuckInstance = function()
        {
            self.port.postMessage( { type: 'clearChuckInstance' } );
        }
        self.clearGlobals = function()
        {
            self.port.postMessage( { type: 'clearGlobals' } );
        }
        // ================= chugin import API ====================== //
        self.now = function( path )
        {
            var callbackID = self.nextDeferID();
            self.port.postMessage( {
                type: 'importChugin',
                path: path,
                callback: callbackID
            } );
            return self.deferredPromises[callbackID];
        }
        
    })( aChuck );
    
    return aChuck;
}

var createASubChuck = function( chuck, dacName, initPromise )
{
    var aSubChuck = new AudioWorkletNode( audioContext, 'chuck-sub-node',
    { 
        numberOfInputs: 1,
        numberOfOutputs: 1,
        outputChannelCount: [1],
        processorOptions: {
            chuckID: chuck.chuckID,
            dac: dacName
        }
    } );
    
    (function( self )
    {
        self.myChuck = chuck;
        self.dac = dacName;
        self.amReady = initPromise;
        
        // respond to messages
        self.port.onmessage = function( event )
        {
            switch( event.data.type ) 
            {
                case "initCallback":
                    if( self.amReady )
                    {
                        self.amReady.resolve();
                    }
                    break;
                default:
                    break;
            }
        }
        // ================== Filesystem ===================== //
        self.createFile = function( directory, filename, data )
        {
            return self.myChuck.createFile( directory, filename, data );
        }
        // ================== Run / Compile ================== //
        self.runCode = function( code )
        {
            // N.B. self.dac
            return self.myChuck.runCodeWithReplacementDac( code, self.dac );
        }
        self.runCodeWithReplacementDac = function( code, dac_name )
        {
            return self.myChuck.runCodeWithReplacementDac( code, dac_name );
        }
        self.runFile = function( filename )
        {
            // N.B. self.dac
            return self.myChuck.runFileWithReplacementDac( filename, self.dac );
        }
        self.runFileWithReplacementDac = function( filename, dac_name )
        {
            return self.myChuck.runFileWithReplacementDac( filename, dac_name );
        }
        self.runFileWithArgs = function( filename, colon_separated_args )
        {
            // N.B. self.dac
            return self.myChuck.runFileWithArgsWithReplacementDac( filename, colon_separated_args, self.dac );
        }
        self.runFileWithArgsWithReplacementDac = function( filename, colon_separated_args, dac_name )
        {
            return self.myChuck.runFileWithArgsWithReplacementDac( filename, colon_separated_args, dac_name );
        }
        self.replaceCode = function( code )
        {
            // N.B. self.dac
            return self.myChuck.replaceCodeWithReplacementDac( code, self.dac );
        }
        self.replaceCodeWithReplacementDac = function( code, dac_name )
        {
            return self.myChuck.replaceCodeWithReplacementDac( code, dac_name );
        }
        self.replaceFile = function( filename )
        {
            // N.B. self.dac
            return self.myChuck.replaceFileWithReplacementDac( filename, self.dac );
        }
        self.replaceFileWithReplacementDac = function( filename, dac_name )
        {
            return self.myChuck.replaceFileWithReplacementDac( filename, dac_name );
        }
        self.replaceFileWithArgs = function( filename, colon_separated_args )
        {
            // N.B. self.dac
            return self.myChuck.replaceFileWithArgsWithReplacementDac( filename, colon_separated_args, self.dac );
        }
        self.replaceFileWithArgsWithReplacementDac = function( filename, colon_separated_args, dac_name )
        {
            return self.myChuck.replaceFileWithArgsWithReplacementDac( filename, colon_separated_args, dac_name );
        }
        self.removeLastCode = function()
        {
            return self.myChuck.removeLastCode();
        }
        self.removeShred = function( shred )
        {
            return self.myChuck.removeShred( shred );
        }
        self.isShredActive = function( shred )
        {
            return self.myChuck.isShredActive( shred );
        }
        // ================== Int, Float, String ============= //
        self.setInt = function( variable, value )
        {
            return self.myChuck.setInt( variable, value );
        }
        self.getInt = function( variable )
        {
            return self.myChuck.getInt( variable );
        }
        self.setFloat = function( variable, value )
        {
            return self.myChuck.setFloat( variable, value );
        }
        self.getFloat = function( variable )
        {
            return self.myChuck.getFloat( variable );
        }
        self.setString = function( variable, value )
        {
            return self.myChuck.setString( variable, value );
        }
        self.getString = function( variable )
        {
            return self.myChuck.getString( variable );
        }
        // ================== Event =================== //
        self.signalEvent = function( variable )
        {
            return self.myChuck.signalEvent( variable );
        }
        self.broadcastEvent = function( variable )
        {
            return self.myChuck.broadcastEvent( variable );
        }
        self.listenForEventOnce = function( variable, callback )
        {
            return self.myChuck.listenForEventOnce( variable, callback );
        }
        self.startListeningForEvent = function( variable, callback )
        {
            return self.myChuck.startListeningForEvent( variable, callback );
        }
        self.stopListeningForEvent = function( variable, callbackID )
        {
            return self.myChuck.stopListeningForEvent( variable, callbackID );
        }
        // ================== Int[] =================== //
        self.setIntArray = function( variable, values )
        {
            return self.myChuck.setIntArray( variable, values );
        }
        self.getIntArray = function( variable )
        {
            return self.myChuck.getIntArray( variable );
        }
        self.setIntArrayValue = function( variable, index, value )
        {
            return self.myChuck.setIntArrayValue( variable, index, value );
        }
        self.getIntArrayValue = function( variable, index )
        {
            return self.myChuck.getIntArrayValue( variable, index );
        }
        self.setAssociativeIntArrayValue = function( variable, key, value )
        {
            return self.myChuck.setAssociativeIntArrayValue( variable, key, value );
        }
        self.getAssociativeIntArrayValue = function( variable, key )
        {
            return self.myChuck.getAssociativeIntArrayValue( variable, key );
        }
        // ================== Float[] =================== //
        self.setFloatArray = function( variable, values )
        {
            return self.myChuck.setFloatArray( variable, values );
        }
        self.getFloatArray = function( variable )
        {
            return self.myChuck.getFloatArray( variable );
        };
        self.setFloatArrayValue = function( variable, index, value )
        {
            return self.myChuck.setFloatArrayValue( variable, index, value );
        }
        self.getFloatArrayValue = function( variable, index )
        {
            return self.myChuck.getFloatArrayValue( variable, index );
        }
        self.setAssociativeFloatArrayValue = function( variable, key, value )
        {
            return self.myChuck.setAssociativeFloatArrayValue( variable, key, value );
        }
        self.getAssociativeFloatArrayValue = function( variable, key )
        {
            return self.myChuck.getAssociativeFloatArrayValue( variable, key );
        }
        // ================== VM Params ============= //
        self.setParamInt = function( name, value )
        {
            return self.myChuck.setParamInt( name, value );
        }
        self.getParamInt = function( name )
        {
            return self.myChuck.getParamInt( name );
        }
        self.setParamFloat = function( name, value )
        {
            return self.myChuck.setParamFloat( name, value );
        }
        self.getParamFloat = function( name )
        {
            return self.myChuck.getParamFloat( name );
        }
        self.setParamString = function( name, value )
        {
            return self.myChuck.setParamString( name, value );
        }
        self.getParamString = function( name )
        {
            return self.myChuck.getParamString( name );
        }
        // ================= VM ====================== //
        self.now = function()
        {
            return self.myChuck.now();
        }
        self.clearChuckInstance = function()
        {
            return self.myChuck.clearChuckInstance();
        }
        self.clearGlobals = function()
        {
            return self.myChuck.clearGlobals();
        }

    })( aSubChuck );
    
    return aSubChuck;
}
