<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
};
};