<html>
<head>
    <style>
        html, body{
            margin:0;
        }
    </style>
</head>
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r50/three.min.js"></script>
<script src="jsRocket.js"></script>

<script>
    var Demo = (function () {

        var BPM = 120,
            ROWS_PER_BEAT = 8,
            ROW_RATE = BPM / 60 * ROWS_PER_BEAT;
            //------------------------------------------
        var _demoMode = true, //Set to true for preview
            //------------------------------------------
            _syncDevice = new JSRocket.SyncDevice(),
            _row = 0,
            _previousIntRow;

        //THREE variables
        var WIDTH = document.body.clientWidth,
            HEIGHT = document.body.clientHeight,
            FOV = 50,
            _audio = new Audio(),
            _renderer = new THREE.WebGLRenderer(),
            _camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT),
            _scene = new THREE.Scene(),
            _cube;

        //scene variables | things you set through jsRocket
        var _cameraRotation = 45,
            _cameraDistance = 400,
            _clearR = 51,
            _clearG = 41,
            _clearB = 44,
            _fov = FOV;
        
        (function init() {
            prepareScene();
            prepareSync();
        }());
        
        function prepareScene() {

            addCube();

            _renderer.setSize(WIDTH, HEIGHT);
            _scene.add(_camera);
            document.body.appendChild(_renderer.domElement);
        }

        function addCube() {

            var _diameter = 100,
                _materials = [],
                _colors = [0x540024, 0x7A0538, 0xBA0032, 0xE9001C, 0xFF3700, 0x2e0014];

            for (var i = 0; i < 6; i++) {
                _materials.push(new THREE.MeshBasicMaterial({ color:_colors[i] }));
            }

            _cube = new THREE.Mesh(
                    new THREE.CubeGeometry(_diameter, _diameter, _diameter, 1, 1, 1, _materials),
                    new THREE.MeshFaceMaterial());

            _scene.add(_cube);
        }

        function prepareSync() {

            if (_demoMode) {
                _syncDevice.setConfig({'rocketXML':'cube.rocket'});
                _syncDevice.init("demo");
            } else {
                _syncDevice.init();
            }

            _syncDevice.on('ready', onSyncReady);
            _syncDevice.on('update', onSyncUpdate);
            _syncDevice.on('play', onPlay);
            _syncDevice.on('pause', onPause);
        }

        function onSyncReady() {
            
            _clearR = _syncDevice.getTrack('clearR');
            _clearG = _syncDevice.getTrack('clearG');
            _clearB = _syncDevice.getTrack('clearB');
            _cameraRotation = _syncDevice.getTrack('rotation');
            _cameraDistance = _syncDevice.getTrack('distance');
            _fov = _syncDevice.getTrack('FOV');

            prepareAudio();
        }
        
        function prepareAudio() {
            //Alpha_C just whipped this up for jsRocket demo purposes
            _audio.src = "https://raw.github.com/mog/jsRocket/master/example/threeJS_Cube_with_sound/alpha_c_-_euh.ogg";
            _audio.load();
            _audio.preload = true;
            _audio.addEventListener('canplay', onAudioReady);
        }
        
        function onAudioReady() {

            if(_demoMode) { 
                render();
                _audio.play();
            } else {
                _audio.pause();
                _audio.currentTime = _row / ROW_RATE;
            }
        }

        function onSyncUpdate(row) {

            if (!isNaN(row)) {
                _row = row;
                _audio.currentTime = _row / ROW_RATE;
            }
            render();
        }

        function onPlay() {

            _audio.currentTime = _row / ROW_RATE;
            _audio.play();
            render();
        }

        function onPause() {

            _row = _audio.currentTime * ROW_RATE;
            window.cancelAnimationFrame(render, document);
            _audio.pause();
        }

        function render() {

            if(_audio.paused === false) {
                //otherwise we may jump into a point in the audio where there's
                //no timeframe, resulting in Rocket setting row 2 and we report
                //row 1 back - thus Rocket spasming out
                _row = _audio.currentTime * ROW_RATE;
            }
            
            // this informs Rocket where we are
            _syncDevice.update(_row);
              
            var rot = (_cameraRotation.getValue(_row) || 0) / 180 * Math.PI,
                color = new THREE.Color();

            //update Field Of View
            _camera.fov = (_fov.getValue(_row) || FOV);
            //_camera.aspect = WIDTH / HEIGHT;
            _camera.updateProjectionMatrix();

            //rotate that cam
            _camera.position.x = Math.cos(rot) * (_cameraDistance.getValue(_row) || 0);
            _camera.position.z = Math.sin(rot) * (_cameraDistance.getValue(_row) || 0);
            _camera.lookAt(_scene.position);

            //the background color
            color.setRGB((_clearR.getValue(_row) || 0) / 255,
                        (_clearG.getValue(_row) || 0) / 255,
                        (_clearB.getValue(_row) || 0) / 255);
            _renderer.setClearColor(color);

            _renderer.render(_scene, _camera);
            
            if((_demoMode === true)  || (_audio.paused === false))
                window.requestAnimationFrame(render, document);
            else
                window.cancelAnimationFrame(render, document);
        }
    }());
</script>
</body>
<tracks rows="128">
  <track name="clearR">
		<key row="0" value="45.000000" interpolation="0"/>
		<key row="11" value="45.000000" interpolation="2"/>
		<key row="14" value="242.000000" interpolation="0"/>
		<key row="25" value="255.000000" interpolation="3"/>
		<key row="29" value="255.000000" interpolation="3"/>
		<key row="34" value="51.000000" interpolation="0"/>
		<key row="48" value="51.000000" interpolation="2"/>
		<key row="50" value="255.000000" interpolation="3"/>
		<key row="51" value="51.000000" interpolation="0"/>
		<key row="52" value="255.000000" interpolation="3"/>
		<key row="53" value="51.000000" interpolation="0"/>
		<key row="58" value="51.000000" interpolation="2"/>
		<key row="60" value="255.000000" interpolation="3"/>
		<key row="62" value="51.000000" interpolation="0"/>
		<key row="90" value="255.000000" interpolation="3"/>
		<key row="94" value="255.000000" interpolation="3"/>
		<key row="99" value="51.000000" interpolation="0"/>
		<key row="113" value="51.000000" interpolation="2"/>
		<key row="115" value="255.000000" interpolation="3"/>
		<key row="116" value="51.000000" interpolation="0"/>
		<key row="117" value="255.000000" interpolation="3"/>
		<key row="118" value="51.000000" interpolation="0"/>
		<key row="123" value="51.000000" interpolation="2"/>
		<key row="125" value="255.000000" interpolation="3"/>
		<key row="127" value="51.000000" interpolation="0"/>
	</track>
	<track name="clearG">
		<key row="0" value="166.000000" interpolation="0"/>
		<key row="11" value="166.000000" interpolation="2"/>
		<key row="14" value="242.000000" interpolation="0"/>
		<key row="25" value="98.000000" interpolation="3"/>
		<key row="29" value="179.000000" interpolation="3"/>
		<key row="34" value="41.000000" interpolation="0"/>
		<key row="48" value="41.000000" interpolation="2"/>
		<key row="50" value="255.000000" interpolation="3"/>
		<key row="51" value="41.000000" interpolation="0"/>
		<key row="52" value="255.000000" interpolation="3"/>
		<key row="53" value="41.000000" interpolation="0"/>
		<key row="58" value="41.000000" interpolation="2"/>
		<key row="60" value="255.000000" interpolation="3"/>
		<key row="62" value="41.000000" interpolation="0"/>
		<key row="90" value="98.000000" interpolation="3"/>
		<key row="94" value="179.000000" interpolation="3"/>
		<key row="99" value="41.000000" interpolation="0"/>
		<key row="113" value="41.000000" interpolation="2"/>
		<key row="115" value="255.000000" interpolation="3"/>
		<key row="116" value="41.000000" interpolation="0"/>
		<key row="117" value="255.000000" interpolation="3"/>
		<key row="118" value="41.000000" interpolation="0"/>
		<key row="123" value="41.000000" interpolation="2"/>
		<key row="125" value="255.000000" interpolation="3"/>
		<key row="127" value="41.000000" interpolation="0"/>
	</track>
	<track name="clearB">
		<key row="0" value="154.000000" interpolation="0"/>
		<key row="11" value="154.000000" interpolation="2"/>
		<key row="14" value="242.000000" interpolation="0"/>
		<key row="25" value="0.000000" interpolation="0"/>
		<key row="29" value="0.000000" interpolation="3"/>
		<key row="34" value="44.000000" interpolation="0"/>
		<key row="48" value="44.000000" interpolation="2"/>
		<key row="50" value="255.000000" interpolation="3"/>
		<key row="51" value="44.000000" interpolation="0"/>
		<key row="52" value="255.000000" interpolation="3"/>
		<key row="53" value="44.000000" interpolation="0"/>
		<key row="58" value="44.000000" interpolation="2"/>
		<key row="60" value="255.000000" interpolation="3"/>
		<key row="62" value="44.000000" interpolation="0"/>
		<key row="90" value="0.000000" interpolation="0"/>
		<key row="94" value="0.000000" interpolation="3"/>
		<key row="99" value="44.000000" interpolation="0"/>
		<key row="113" value="44.000000" interpolation="2"/>
		<key row="115" value="255.000000" interpolation="3"/>
		<key row="116" value="44.000000" interpolation="0"/>
		<key row="117" value="255.000000" interpolation="3"/>
		<key row="118" value="44.000000" interpolation="0"/>
		<key row="123" value="44.000000" interpolation="2"/>
		<key row="125" value="255.000000" interpolation="3"/>
		<key row="127" value="44.000000" interpolation="0"/>
	</track>
	<track name="rotation">
		<key row="0" value="0.000000" interpolation="2"/>
		<key row="5" value="45.000000" interpolation="0"/>
		<key row="11" value="20.000000" interpolation="3"/>
		<key row="14" value="122.000000" interpolation="0"/>
		<key row="25" value="496.000000" interpolation="2"/>
		<key row="29" value="315.000000" interpolation="0"/>
		<key row="34" value="315.000000" interpolation="3"/>
		<key row="38" value="300.000000" interpolation="0"/>
		<key row="46" value="300.000000" interpolation="3"/>
		<key row="48" value="330.000000" interpolation="0"/>
		<key row="68" value="40.000000" interpolation="2"/>
		<key row="72" value="45.000000" interpolation="0"/>
		<key row="76" value="50.000000" interpolation="2"/>
		<key row="80" value="64.000000" interpolation="0"/>
		<key row="90" value="496.000000" interpolation="2"/>
		<key row="94" value="315.000000" interpolation="0"/>
		<key row="99" value="315.000000" interpolation="3"/>
		<key row="103" value="300.000000" interpolation="0"/>
		<key row="111" value="300.000000" interpolation="3"/>
		<key row="113" value="330.000000" interpolation="0"/>
		<key row="133" value="40.000000" interpolation="2"/>
		<key row="137" value="45.000000" interpolation="0"/>
		<key row="141" value="50.000000" interpolation="2"/>
		<key row="145" value="64.000000" interpolation="0"/>
	</track>
	<track name="distance">
		<key row="0" value="400.000000" interpolation="0"/>
		<key row="5" value="130.000000" interpolation="0"/>
		<key row="11" value="400.000000" interpolation="3"/>
		<key row="14" value="130.000000" interpolation="0"/>
	</track>
	<track name="FOV">
		<key row="0" value="35.000000" interpolation="3"/>
		<key row="5" value="90.000000" interpolation="0"/>
		<key row="11" value="35.000000" interpolation="3"/>
		<key row="14" value="90.000000" interpolation="0"/>
		<key row="25" value="120.000000" interpolation="2"/>
		<key row="46" value="90.000000" interpolation="2"/>
		<key row="90" value="120.000000" interpolation="2"/>
		<key row="111" value="90.000000" interpolation="2"/>
	</track>
</tracks>
var JSRocket = {};
JSRocket.SyncData = function () {

    "use strict";

    var _track = [];

    function getTrack(index) {
        return _track[index];
    }

    function getIndexForName(name) {
        for (var i = 0; i < _track.length; i++) {

            if (_track[i].name === name) {
                return i;
            }
        }

        return -1;
    }

    function getTrackLength() {
        return _track.length;
    }

    function createIndex(varName) {
        var track = new JSRocket.Track();
        track.name = varName;

        _track.push(track);
    }

    return {
        getTrack       :getTrack,
        getIndexForName:getIndexForName,
        getTrackLength :getTrackLength,
        createIndex    :createIndex
    };
};
JSRocket.Track = function () {

    "use strict";

    var STEP = 0,
        LINEAR = 1,
        SMOOTH = 2,
        RAMP = 3;

    var _track = [],
        _index = [];

    function getValue(row) {
        var intRow = Math.floor(row),
            bound = getBound(intRow),
            lower = bound.low,
            upper = bound.high,
            v;

        if (isNaN(lower)) {

            return NaN;

        } else if ((isNaN(upper)) || (_track[lower].interpolation === STEP)) {

            return _track[lower].value;

        } else {

            switch (_track[lower].interpolation) {

                case LINEAR:
                    v = (row - lower) / (upper - lower);
                    return _track[lower].value + (_track[upper].value - _track[lower].value) * v;

                case SMOOTH:
                    v = (row - lower) / (upper - lower);
                    v = v * v * (3 - 2 * v);
                    return (_track[upper].value * v) + (_track[lower].value * (1 - v));

                case RAMP:
                    v = Math.pow((row - lower) / (upper - lower), 2);
                    return _track[lower].value + (_track[upper].value - _track[lower].value) * v;
            }
        }

        return NaN;
    }

    function getBound(rowIndex) {
        var lower = NaN,
            upper = NaN;

        for (var i = 0; i < _index.length; i++) {

            if (_index[i] <= rowIndex) {

                lower = _index[i];

            } else if (_index[i] >= rowIndex) {

                upper = _index[i];
                break;
            }
        }

        return {"low":lower, "high":upper};
    }

    function add(row, value, interpolation, delaySort) {

        remove(row);

        //index lookup table
        _index.push(row);
        _track[row] = { "value"         :value,
                        "interpolation" :interpolation};

        //parser calls this quite often, so we sort later
        if(delaySort !== true) {
            sortIndex();
        }
    }

    function sortIndex() {

        _index = _index.sort(function (a, b) {
            return a - b;
        });
    }

    function remove(row) {
        if (_index.indexOf(row) > -1) {
            _index.splice(_index.indexOf(row), 1);
            delete _track[row];
        }
    }

    return {
        getValue:getValue,
        sortIndex:sortIndex,
        add     :add,
        remove  :remove
    };
};
JSRocket.SyncDevicePlayer = function (cfg) {

    "use strict";

    var _urlRequest,
        _syncData = new JSRocket.SyncData(),
        _eventHandler = {
            'ready':function () {
            },
            'error':function () {
            }
        };

    function load(url) {

        _urlRequest = new XMLHttpRequest();

        if (_urlRequest === null) {
            _eventHandler.error();
            return;
        }

        _urlRequest.open('GET', url, true);
        _urlRequest.onreadystatechange = urlRequestHandler;

        _urlRequest.send();
    }

    function urlRequestHandler() {

        if (_urlRequest.readyState === 4) {
            if (_urlRequest.status < 300) {
                readXML(_urlRequest.responseText);
            } else {
                _eventHandler.error();
            }
        }
    }
 
    function readXML(xmlString) {
        var key,
            t = 0, tLen, k = 0, kLen,
            xml = (new DOMParser()).parseFromString(xmlString, 'text/xml'),
            tracks = xml.getElementsByTagName('tracks');

        //<tracks>
        var trackList = tracks[0].getElementsByTagName('track');

        for (t, tLen = trackList.length; t < tLen; t++) {

            var track = getTrack(trackList[t].getAttribute('name')),
                keyList = trackList[t].getElementsByTagName('key');

            for (k = 0, kLen = keyList.length; k < kLen; k++) {
                key = keyList[k];
                track.add(parseInt(key.getAttribute('row'), 10),
                    parseFloat(key.getAttribute('value')),
                    parseInt(key.getAttribute('interpolation'), 10),
                    true);

            }
            track.sortIndex();
        }

        _eventHandler.ready();
    }
    
    function getTrack(name) {

        var index = _syncData.getIndexForName(name);

        if (index > -1) {
            return _syncData.getTrack(index);
        }

        _syncData.createIndex(name);
        return _syncData.getTrack(_syncData.getTrackLength() - 1);
    }

    function setEvent(evt, handler) {
        _eventHandler[evt] = handler;
    }

    function nop() {

    }

    if (cfg.rocketXML === "" || cfg.rocketXML === undefined || cfg.rocketXML === undefined) {
        throw("[jsRocket] rocketXML is not set, try _syncDevice.setConfig({'rocketXML':'url/To/RocketXML.rocket'})");
    } else {
        load(cfg.rocketXML);
    }

    return {
        load    :load,
        getTrack:getTrack,
        update  :nop,
        on      :setEvent
    };
};
JSRocket.SyncDeviceClient = function (cfg) {

    "use strict";

    var CMD_SET_KEY = 0,
        CMD_DELETE_KEY = 1,
        CMD_GET_TRACK = 2,
        CMD_SET_ROW = 3,
        CMD_PAUSE = 4,
        CMD_SAVE_TRACKS = 5;

    var _ws = new WebSocket(cfg.socketURL),
        _syncData = new JSRocket.SyncData(),
        _eventHandler = {
            'ready' :function () {
            },
            'update':function () {
            },
            'play'  :function () {
            },
            'pause' :function () {
            },
            'save' :function () {
            }
        };

    function onOpen() {

        _ws.binaryType = "arraybuffer";
        _ws.send('hello, synctracker!');
    }

    function onMessage(e) {

        var queue = (new Uint8Array(e.data)),
            cmd = queue[0],
            track, row, value, interpolation;

        //Handshake
        if (cmd === 104) {

            _eventHandler.ready();

        //PAUSE
        } else if (CMD_PAUSE === cmd) {

            if( queue[1] === 1) {
                _eventHandler.pause();
            } else {
                _eventHandler.play();
            }

        //SET_ROW
        } else if (CMD_SET_ROW === cmd) {

            row = toInt(queue.subarray(1, 5));

            _eventHandler.update(row);

        //SET_KEY
        } else if (CMD_SET_KEY === cmd) {

            track = toInt(queue.subarray(1, 5));
            row = toInt(queue.subarray(5, 9));

            //value = Math.round(toFloat(queue.subarray(9, 13)) * 100) / 100; //round to what's seen in Rocket tracks
            value = toFloat(queue.subarray(9, 13)); //use the values you see in Rocket statusbar

            interpolation = toInt(queue.subarray(13, 14));
            _syncData.getTrack(track).add(row, value, interpolation);

            //don't set row, as this could also be a interpolation change
            _eventHandler.update();

        //DELETE
        } else if (CMD_DELETE_KEY === cmd) {

            track = toInt(queue.subarray(1, 5));
            row = toInt(queue.subarray(5, 9));

            _syncData.getTrack(track).remove(row);

            _eventHandler.update();

        //SAVE
        } else if (CMD_SAVE_TRACKS === cmd) {
            _eventHandler.save();
        }
    }

    function onClose(e) {
        console.warn(">> connection closed", e);
    }

    function onError(e) {
        console.error(">> connection error'd", e);
    }

    _ws.onopen = onOpen;
    _ws.onmessage = onMessage;
    _ws.onclose = onClose;
    _ws.onerror = onError;

    function getTrack(name) {

        var index = _syncData.getIndexForName(name);

        if (index > -1) {
            return _syncData.getTrack(index);
        }

        _ws.send(new Uint8Array([CMD_GET_TRACK, 0, 0,  0, name.length]).buffer);
        _ws.send(name);

        _syncData.createIndex(name);

        return _syncData.getTrack(_syncData.getTrackLength() - 1);
    }

    function setRow(row) {

        var streamInt = [(row >> 24) & 0xFF,
                        (row >> 16) & 0xFF,
                        (row >> 8) & 0xFF,
                        (row      ) & 0xFF];

        _ws.send(new Uint8Array([CMD_SET_ROW, streamInt[0], streamInt[1], streamInt[2], streamInt[3]]).buffer);
    }

    function toInt(arr) {

        var i = 0,
            view = new DataView(new ArrayBuffer(arr.length));

        for(;i < arr.length; i++) {
            view.setUint8(i, arr[i]);
        }

        if(view.byteLength === 1) {
            return view.getInt8(0);
        } else {
            return view.getInt32(0);
        }
    }

    function toFloat(arr) {
        var view = new DataView(new ArrayBuffer(4));
        view.setUint8(0, arr[0]);
        view.setUint8(1, arr[1]);
        view.setUint8(2, arr[2]);
        view.setUint8(3, arr[3]);

        return view.getFloat32(0);
    }

    function setEvent(evt, handler) {
        _eventHandler[evt] = handler;
    }

    return {
        getTrack:getTrack,
        update  :setRow,
        on      :setEvent
    };
};

JSRocket.SyncDevice = function () {

    "use strict";

    var _connected = false,
        _device,
        _previousIntRow,
        _config = {
            "socketURL":"ws://localhost:1338",
            "rocketXML":""
        },
        _eventHandler = {
            'ready' :function () {
            },
            'update':function () {
            },
            'play'  :function () {
            },
            'pause' :function () {
            }
        };

    function init(mode) {
        if (mode === "demo") {
            _device = new JSRocket.SyncDevicePlayer(_config);
        } else {
            _device = new JSRocket.SyncDeviceClient(_config);
        }

        _device.on('ready', deviceReady);
        _device.on('update', deviceUpdate);
        _device.on('play', devicePlay);
        _device.on('pause', devicePause);
    }

    function getConfig() {
        return _config;
    }

    function setConfig(cfg) {
        for (var option in cfg) {
            if (cfg.hasOwnProperty(option)) {
                _config[option] = cfg[option];
            }
        }

        return _config;
    }

    function deviceReady() {
        _connected = true;
        _eventHandler.ready();
    }

    function deviceUpdate(row) {
        _eventHandler.update(row);
    }

    function devicePlay() {
        _eventHandler.play();
    }

    function devicePause() {
        _eventHandler.pause();
    }

    function getTrack(name) {
        if (_connected) {
            return _device.getTrack(name);
        } else {
            return null;
        }
    }

    function update(row) {
        //no need to update rocket on float rows
        if (Math.floor(row) !== _previousIntRow) {
            _previousIntRow = Math.floor(row);
            _device.update(_previousIntRow);
        }
    }

    function setEvent(evt, handler) {
        _eventHandler[evt] = handler;
    }

    return {
        init     :init,
        setConfig:setConfig,
        getConfig:getConfig,
        getTrack :getTrack,
        update   :update,
        on       :setEvent
    };
};