--- a/annot-server/websockets.py Tue Jan 13 15:27:20 2015 +0100
+++ b/annot-server/websockets.py Fri Jan 16 03:16:19 2015 +0100
@@ -1,4 +1,3 @@
-
#
# See LICENCE for detail
# Copyright (c) 2014 IRI
@@ -50,8 +49,8 @@
def register(self, client):
if not client in self.clients:
- print("registered client {}".format(client.peer))
- self.clients.append(client)
+ print("registered client {}".format(client.peer))
+ self.clients.append(client)
def unregister(self, client):
if client in self.clients:
@@ -60,11 +59,11 @@
if client in self.filters:
self.filters.pop(client, None)
- def broadcast(self, msg, filter):
+ def broadcast(self, msg, filter_list):
print("broadcasting prepared message '{}' ..".format(msg))
preparedMsg = self.prepareMessage(msg)
for c in self.clients:
- if all([ (k in filter and filter[k] in v) for k,v in self.filters.get(c, {}).items()]):
+ if all([ (k in filter_list and filter_list[k] in v) for k,v in self.filters.get(c, {}).items()]):
c.sendPreparedMessage(preparedMsg)
print("prepared message sent to {}".format(c.peer))
--- a/client/annotviz/app/index.html Tue Jan 13 15:27:20 2015 +0100
+++ b/client/annotviz/app/index.html Fri Jan 16 03:16:19 2015 +0100
@@ -18,8 +18,9 @@
<noscript>You must enable JavaScript</noscript>
<div id="canvasContainer"></div>
<p>
- <a href="#" onclick="clearInterval(moveInterval); clearInterval(verticalLinesInterval); return false;">stop intervals</a>
- - temps écoulé : <span id="myspan"></span>
+ <a href="#" onclick="stop(); return false;">stop intervals</a> -
+ <a href="#" onclick="start(); return false;">start intervals</a> -
+ temps écoulé : <span id="myspan"></span>
</p>
<pre id="log"></pre>
<script src="/js/libs-annotviz.js"></script>
@@ -28,8 +29,8 @@
</script>
<script src="/js/annotviz.js"></script>
<script>
- var moveInterval = annotviz.moveInterval;
- var verticalLinesInterval = annotviz.verticalLinesInterval;
+ var stop = annotviz.stop;
+ var start = annotviz.start;
</script>
</body>
</html>
--- a/client/annotviz/app/js/main.js Tue Jan 13 15:27:20 2015 +0100
+++ b/client/annotviz/app/js/main.js Fri Jan 16 03:16:19 2015 +0100
@@ -5,11 +5,20 @@
* Take a look at http://browserify.org/ for more info
*/
+ /* global window: false */
+ /* global document: false */
+ /* global WebSocket: false */
+ /* global MozWebSocket: false */
+
+ /* global eventCode: false */
'use strict';
var PIXI = require('pixi');
+var _ = require('lodash');
+
+var NTP_EPOCH_DELTA = 2208988800; //c.f. RFC 868
// Config vars
var logger = false;
var sceneWidth = 1920;
@@ -22,14 +31,10 @@
var manualFramerate = pixelsPerSecond1 / 4;
var pixelsPerSecond2 = Math.floor(sceneWidth / 60); // nb of pixels per second
var lineInterval = 5000; // means line every 5 seconds
-var nbLines = -1;
-var noteHeight = 110;
+//var noteHeight = 110;
var noteColors = [0xB90000, 0x4BDD71, 0xAF931E, 0x1C28BA, 0x536991];
var colorsReg = {};
-// Vars
-var noteDict = [];
// Timecode method
-var timePageLoaded = Date.now();
var offsetMusic = false;
@@ -47,12 +52,38 @@
uberContainer.position.y = 0;
stage.addChild(uberContainer);
-var PianoRoll = require('./pianoroll.js')
+var PianoRoll = require('./pianoroll.js');
// Init containers
var containerList = [];
-containerList.push(new PianoRoll(uberContainer, 0, 0, prHeight1, true, pixelsPerSecond1, sceneWidth, noteColors, colorsReg, lineColor, lineInterval, offsetMusic, prHeight1 / 128));
-containerList.push(new PianoRoll(uberContainer, 0, prHeight1, prHeight2, false, pixelsPerSecond2, sceneWidth, noteColors, colorsReg, lineColor, lineInterval, offsetMusic, prHeight2 / 128));
+
+var pianorollOptions = {
+ parentContainer: uberContainer,
+ xInit: 0,
+ width: sceneWidth,
+ noteColors: noteColors,
+ colorsReg: colorsReg,
+ lineColor: lineColor,
+ lineInterval: lineInterval,
+ offsetMusic: offsetMusic
+};
+
+
+containerList.push(new PianoRoll(_.extend(_.clone(pianorollOptions), {
+ yInit: 0,
+ height: prHeight1,
+ linesDown: true,
+ pixelsPerSecond: pixelsPerSecond1,
+ noteHeight: prHeight1 / 128
+})));
+containerList.push(new PianoRoll(_.extend(_.clone(pianorollOptions), {
+ yInit: prHeight1,
+ height: prHeight2,
+ linesDown: false,
+ pixelsPerSecond: pixelsPerSecond2,
+ noteHeight: prHeight2 / 128
+})));
+
// Line between two containers
var graphics = new PIXI.Graphics();
@@ -64,52 +95,16 @@
stage.addChild(graphics);
-function replaceContainers(){
- var diff = (Date.now() - timePageLoaded)/1000;// nb of seconds since page loaded
- //console.log("replace ! diff1 = ", container1.x - Math.floor(-diff*pixelsPerSecond1), ", diff 2 = ", container2.x - Math.floor(-diff*pixelsPerSecond2));
- for(var i=0;i<containerList.length;i++){
- containerList[i].moveTo(-diff);
- }
- renderer.render(stage);
-}
-
function addNotes(data){
- if(!offsetMusic){
- // get difference between the current note timecode and my zero to set the difference between the canvas's zero and the music's zero
- // in order to place in real time
- var now = Date.now();
- var timeBetweenNowAndStart = now - timePageLoaded;
- offsetMusic = timeBetweenNowAndStart - data.content[1];
- //console.log("start: ", timePageLoaded, ", now: ", now, ", timeBetweenNowAndStart = ", timeBetweenNowAndStart, ", offsetMusic = ", offsetMusic);
- }
var note = data.content[3];
var velocity = data.content[4];
- if(velocity===0){
- if(typeof noteDict[data.content[2]][note]!=='undefined'){
- // We close the note in container one
- //console.log("coucou 2", data);
- var duration = data.content[1] - noteDict[data.content[2]][note].ts;
- for(var i=0;i<containerList.length;i++){
- // addNote(note, startTime, duration, velocity, canal)
- containerList[i].addNote(note, noteDict[data.content[2]][note].ts, duration, noteDict[data.content[2]][note].velocity, data.content[2]);
- }
- // delete entry
- delete noteDict[data.content[2]][note];
- }
- }
- else{
- if(typeof noteDict[data.content[2]]==='undefined'){
- noteDict[data.content[2]] = {};
- }
- noteDict[data.content[2]][note] = {ts: data.content[1], velocity:velocity};
- }
-}
+ var ts = (data.content[0] - NTP_EPOCH_DELTA)*1000;
+ var channel = data.content[2];
+ var sessionTs = data.content[1];
-function addLine(){
- var ts = new Date();
- for(var i=0;i<containerList.length;i++){
- containerList[i].addLine(ts);
- }
+ containerList.forEach(function(c) {
+ c.addNote(note, ts, sessionTs, velocity, channel, 0);
+ });
}
@@ -164,21 +159,42 @@
};
sock.onmessage = function(e) {
+ var dataJson = JSON.parse(e.data);
+ var dataDate = new Date((dataJson.content[0]-NTP_EPOCH_DELTA)*1000);
if(logger){
- log('Got message: ' + e.data);
+ log('Got message: ' + e.data + ' - ' + dataDate.toISOString());
}
- addNotes(JSON.parse(e.data));
+ addNotes(dataJson);
};
}
};
+function refreshStage() {
+ containerList.forEach(function(c) {
+ c.move();
+ });
+ renderer.render(stage);
+}
+
// Init page and intervals
-addLine();
-var moveInterval = window.setInterval(replaceContainers, 1000/manualFramerate);
-var verticalLinesInterval = window.setInterval(addLine, lineInterval);
+var refreshInterval;
+
+function start() {
+ refreshInterval = window.setInterval(refreshStage, 1000/manualFramerate);
+ containerList.forEach(function(c) {
+ c.start();
+ });
+}
-// Little inteval to show time
+function stop() {
+ window.clearInterval(refreshInterval);
+ containerList.forEach(function(c) {
+ c.stop();
+ });
+}
+
+// Little interval to show time
var nbSec = 0;
var mySpan = document.getElementById('myspan');
function updateTime(){
@@ -189,10 +205,11 @@
var timeStr = (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
mySpan.innerHTML = timeStr;
}
-var secondInterval = window.setInterval(updateTime, 1000);
+window.setInterval(updateTime, 1000);
+
+start();
module.exports = {
- moveInterval: moveInterval,
- verticalLinesInterval: verticalLinesInterval,
- secondInterval: secondInterval
+ start: start,
+ stop: stop,
};
--- a/client/annotviz/app/js/pianoroll.js Tue Jan 13 15:27:20 2015 +0100
+++ b/client/annotviz/app/js/pianoroll.js Fri Jan 16 03:16:19 2015 +0100
@@ -5,40 +5,38 @@
*
*/
+/* global window: false */
'use strict';
+
var PIXI = require('pixi');
var randomColor = require('randomColor');
+var _ = require('lodash');
-function PianoRoll(parentContainer, xInit, yInit, height, linesDown, pixelsPerSecond, width, noteColors, colorsReg, lineColor, lineInterval, offsetMusic, noteHeight){
+
+function PianoRoll(options) {
var _this = this;
this.container = new PIXI.DisplayObjectContainer();
- this.container.position.x = xInit;
- this.container.position.y = yInit;
- parentContainer.addChild(this.container);
+ this.container.position.x = options.xInit;
+ this.container.position.y = options.yInit;
+ options.parentContainer.addChild(this.container);
- this.linesDown = linesDown;
- this.height = height;
- this.pixelsPerSecond = pixelsPerSecond;
- this.width = width;
- this.noteColors = noteColors;
- this.colorsReg = colorsReg || {};
- this.lineColor = lineColor;
- this.lineInterval = lineInterval;
- this.offsetMusic = offsetMusic || 0;
- this.noteHeight = noteHeight;
+ this.linesDown = options.linesDown;
+ this.height = options.height;
+ this.pixelsPerSecond = options.pixelsPerSecond;
+ this.width = options.width;
+ this.noteColors = options.noteColors;
+ this.colorsReg = options.colorsReg || {};
+ this.lineColor = options.lineColor;
+ this.lineInterval = options.lineInterval;
+ this.offsetMusic = options.offsetMusic || false;
+ this.noteHeight = options.noteHeight;
+ this.noteDict = {};
+ this.startTs = options.startTs || Date.now();
- this.addNote = function(note, startTime, duration, velocity, canal){
- //console.log("coucou 1", note, timeFromZero, ts, velocity, pixelsPerSecond, container, prHeight);
- var beginX = (this.offsetMusic + startTime) * this.pixelsPerSecond / 1000;
- var width = duration * this.pixelsPerSecond / 1000;
- if((beginX+width) < (Math.abs(this.container.x) - this.width)) {
- // not visible. do nothing
- return;
- }
- // We draw the rectangle
- var graphics = new PIXI.Graphics();
- //console.log("beginX = ", beginX, "canal = ", canal, "color = ", noteColor[canal], "width = ", width, "note = ", note, "velocity = ", velocity);
+
+ //TODO: I do not like the "regColor" object. This should not be global, but local
+ this.getColor = function(canal) {
var color = this.colorsReg[canal];
if(typeof(color) === 'undefined') {
var colorsRegSize = Object.keys(this.colorsReg).length;
@@ -49,15 +47,85 @@
color = this.colorsReg[canal] = parseInt(randomColor({ luminosity: 'light', hue: 'random', format:'hex'}).replace(/^#/, ''), 16);
}
}
- graphics.beginFill(color, (velocity / 128));
- var y = (128-note) * this.height / 128; // (128-note) because y = 0 is for note = 128 and y = 128 for note = 0
- graphics.drawRect(0, Math.floor(y - (noteHeight/2) + ((this.height / 128)/2)), width, noteHeight);
+ return color;
+ };
+
+ this.getNoteRect = function(x, y, color, alpha, width, height) {
+ var graphics = new PIXI.Graphics();
+ //console.log("beginX = ", beginX, "canal = ", canal, "color = ", noteColor[canal], "width = ", width, "note = ", note, "velocity = ", velocity);
+ graphics.beginFill(color, alpha);
+ graphics.drawRect(0, 0, width, height);
graphics.endFill();
- graphics.x = beginX;
- this.container.addChild(graphics);
+ graphics.x = x;
+ graphics.y = y;
+ graphics.width = width;
+ graphics.height = height;
+ return graphics;
+ };
+
+ this.addNote = function(note, startTime, sessionTs, velocity, channel, duration) {
+
+ var ts = startTime;
+ if(this.offsetMusic) {
+ ts = this.startTs + sessionTs;
+ }
+
+ var noteDuration = duration;
+ var noteVelocity = velocity;
+ var graphics;
+ if(!duration) {
+ if(typeof this.noteDict[channel]==='undefined'){
+ this.noteDict[channel] = {};
+ }
+ if(velocity===0) {
+ if(typeof this.noteDict[channel][note] !== 'undefined') {
+ var noteDef = this.noteDict[channel][note];
+ delete this.noteDict[channel][note];
+ noteDuration = sessionTs - noteDef.sessionTs;
+ graphics = noteDef.graphics;
+ noteVelocity = noteDef.velocity;
+ ts = noteDef.ts;
+ }
+ }
+ else {
+ noteDuration = Date.now() - ts;
+ this.noteDict[channel][note] = { ts: ts, velocity: velocity, sessionTs: sessionTs};
+ }
+ }
+
+
+ if(!this.offsetMusic || velocity===0) {
+
+ var width = noteDuration * this.pixelsPerSecond / 1000;
+ if(!graphics) {
+ var x = (ts-this.startTs) * this.pixelsPerSecond / 1000;
+ if((x+width) < (Math.abs(this.container.x) - this.width)) {
+ // not visible. do nothing
+ return;
+ }
+ var y = Math.floor((128-note+0.5) * this.height / 128 - (this.noteHeight/2));
+ var color = this.getColor(channel);
+ var alpha = (noteVelocity / 128);
+
+ graphics = this.getNoteRect(x, y, color, alpha, width, this.noteHeight);
+ this.container.addChild(graphics);
+ //console.log(color, alpha, graphics.lineColor, graphics.fillAlpha);
+ }
+ else {
+ graphics.width = width;
+ }
+
+ if(!duration && velocity) {
+ this.noteDict[channel][note].graphics = graphics;
+ }
+ }
};
this.addLine = function(ts){
+
+ if(typeof(ts) === 'undefined') {
+ ts = new Date();
+ }
var graphics = new PIXI.Graphics();
var x = -this.container.x;
graphics.beginFill(0xFFFF00);
@@ -82,7 +150,21 @@
};
this.moveTo = function(diffTime){
+ var oldX = this.container.x;
this.container.x = Math.floor(diffTime*this.pixelsPerSecond);
+ var deltaX = Math.abs(oldX-this.container.x);
+ _.forOwn(this.noteDict, function(channelDict) {
+ _.forOwn(channelDict, function(noteDef) {
+ if(noteDef.graphics) {
+ noteDef.graphics.width = noteDef.graphics.width + deltaX;
+ }
+ });
+ });
+ };
+
+ this.move = function() {
+ var diff = (this.startTs - Date.now())/1000;
+ this.moveTo(diff);
};
this.removePassedObjets = function(){
@@ -91,7 +173,7 @@
while(i<nbChilds && !childIsNowDisplayed){
var child = _this.container.children[i++];
//console.log("remove ? ", child.x, child.width, ((child.x + child.width) < (Math.abs(_this.container.x) - _this.width)));
- if(typeof(child) == 'undefined') {
+ if(typeof(child) === 'undefined') {
continue;
}
if((child.x + child.width) < (Math.abs(_this.container.x) - _this.width)){
@@ -109,9 +191,23 @@
//console.log("before : ", nbChilds, ", after : ", _this.container.children.length);
};
- // remove notes each scene width
- //var removeInterval = window.setInterval(this.removePassedObjets, 1000 * sceneWidth / this.pixelsPerSecond );
- window.setInterval(this.removePassedObjets, 1000 * this.width / this.pixelsPerSecond );
+ this.start = function() {
+ if(typeof(this.startTs) === 'undefined') {
+ console.log('Container Started');
+ this.startTs = Date.now();
+ this.addLine();
+ }
+ var _this = this;
+ this.verticalLinesInterval = window.setInterval(function() { _this.addLine(); }, this.lineInterval);
+ this.cleanInterval = window.setInterval(function () { _this.removePassedObjets(); }, 1000 * this.width / this.pixelsPerSecond );
+ };
+
+ this.stop = function() {
+ //window.clearInterval(this.moveInterval);
+ window.clearInterval(this.verticalLinesInterval);
+ window.clearInterval(this.cleanInterval);
+ };
+
}
--- a/client/annotviz/bower.json Tue Jan 13 15:27:20 2015 +0100
+++ b/client/annotviz/bower.json Fri Jan 16 03:16:19 2015 +0100
@@ -3,6 +3,7 @@
"version": "0.0.0",
"dependencies": {
"randomColor": "davidmerfield/randomColor#~0.1.1",
- "pixi": "~2.2.3"
+ "pixi": "~2.2.3",
+ "lodash": "~2.4.1"
}
}
--- a/client/annotviz/gulp/tasks/browserify.js Tue Jan 13 15:27:20 2015 +0100
+++ b/client/annotviz/gulp/tasks/browserify.js Fri Jan 16 03:16:19 2015 +0100
@@ -15,6 +15,7 @@
return browserify({debug: true})
.require('./app/lib/pixi/bin/pixi.js', { expose: 'pixi' })
.require('./app/lib/randomColor/randomColor.js', {expose: 'randomColor'})
+ .require('./app/lib/lodash/dist/lodash.js', {expose: 'lodash'})
.bundle()
.pipe(source('libs-'+p.name+'.js'))
.pipe(gulp.dest(config.dist + '/js/'));
@@ -26,6 +27,7 @@
.add('./app/js/main.js')
.external('pixi')
.external('randomColor')
+ .external('lodash')
.transform(partialify) // Transform to allow requireing of templates
.bundle()
.pipe(source(p.name+'.js'))
--- a/client/pianoroll/app/js/main.js Tue Jan 13 15:27:20 2015 +0100
+++ b/client/pianoroll/app/js/main.js Fri Jan 16 03:16:19 2015 +0100
@@ -84,7 +84,7 @@
}
var note = data.content[3];
var velocity = data.content[4];
- if(velocity===0){
+ if(velocity===0) {
if(typeof noteDict[data.content[2]][note]!=='undefined'){
// We close the note in container one
//console.log("coucou 2", data);
--- a/utils/pianoroll-client.py Tue Jan 13 15:27:20 2015 +0100
+++ b/utils/pianoroll-client.py Fri Jan 16 03:16:19 2015 +0100
@@ -7,6 +7,7 @@
import argparse
import csv
+import signal
import time
import ntplib
@@ -19,7 +20,7 @@
"""
Example that sends UDP messages.
"""
- def __init__(self, port, host, address, rows, shift):
+ def __init__(self, port, host, address, rows, shift, token):
self.port = port
self.host = host
self.client = async.DatagramClientProtocol()
@@ -27,6 +28,7 @@
self.rows = rows
self.address = address
self.shift = shift
+ self.token = token
reactor.callLater(0, self.send_messages)
def _send(self, element):
@@ -36,19 +38,31 @@
def send_messages(self):
t0 = time.time()
+ #tc = 0
for row in self.rows:
+ if not self.token.running:
+ break
if self.shift:
- row[0] = ntplib.system_to_ntp_time(t0 + float(row[1])/10**3)
+ row[0] = ntplib.system_to_ntp_time(t0 + float(row[1])/1000.0)
row_conv = [ osc.TimeTagArgument(float(row[0]))] + [osc.IntArgument(int(a)) for a in row[1:]]
- #time.sleep((row_conv[1].value-tc)/10**3)
- time.sleep(0.1)
- #tc = row_conv[1].value
+ #time.sleep((row_conv[1].value-tc)/1000.0)
+ sleep_time = t0+float(row[1])/1000.0-time.time()
+ if sleep_time > 0:
+ time.sleep(sleep_time)
+ #time.sleep(0.1)
+ tc = row_conv[1].value
self._send(osc.Message(self.address,*row_conv))
print("Goodbye.")
reactor.callLater(0.1, reactor.stop)
+class Token(object):
+ def __init__(self):
+ self.running = True
+
if __name__ == "__main__":
+ token = Token()
+
parser = argparse.ArgumentParser(description='Simulate an (osc) pianoroll client.')
parser.add_argument('datafile', metavar='DATAFILE', help='The file containing the pianoroll data (CSV).')
parser.add_argument('-e', '--event', dest='event', metavar='EVENT', required=True, help='the event code.')
@@ -56,8 +70,16 @@
args = parser.parse_args()
+ def customHandler(signum, _):
+ print("Got signal: %s" % signum)
+ token.running = False
+ if reactor.running:
+ reactor.callFromThread(reactor.stop) # to stop twisted code when in the reactor loop
+ signal.signal(signal.SIGINT, customHandler)
+
+
with open(args.datafile, 'rU') as datafile:
reader = csv.reader(datafile, delimiter=' ')
- app = UDPSenderApplication(9090, "127.0.0.1", "/pianoroll/%s/" % args.event, list(reader), args.shift)
+ app = UDPSenderApplication(9090, "127.0.0.1", "/pianoroll/%s/" % args.event, list(reader), args.shift, token)
reactor.run()