refactor and make work annotsroll
authorymh <ymh.work@gmail.com>
Tue, 20 Jan 2015 11:57:44 +0100
changeset 98 72d767c5142d
parent 97 545803e685e0
child 100 0d7dac03c1a0
refactor and make work annotsroll
client/annotviz/app/annotsroll.html
client/annotviz/app/index.html
client/annotviz/app/js/annotsroll.js
client/annotviz/app/js/annotstimeline.js
client/annotviz/app/js/annotsvizview.js
client/annotviz/app/js/doubleroll.js
client/annotviz/app/js/logger.js
client/annotviz/app/js/main.js
client/annotviz/app/js/pianoroll.js
client/annotviz/app/js/wswrapper.js
client/annotviz/app/pianoroll_h.html
client/annotviz/app/pianoroll_v.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/annotsroll.html	Tue Jan 20 11:57:44 2015 +0100
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <meta name="description" content="">
+    <meta name="author" content="I.R.I">
+    <link rel="shortcut icon" href="/img/favicon.ico">
+
+    <title>AnnotsRoll</title>
+
+    <!-- Custom styles for this template -->
+    <link href="/css/annotviz.css" rel="stylesheet">
+</head>
+
+<body>
+    <h1>Piano Roll vertical</h1>
+    <noscript>You must enable JavaScript</noscript>
+    <div id="canvasContainer"></div>
+    <p>
+        <a href="#" onclick="stop(); return false;">stop intervals</a> -
+        <a href="#" onclick="start(); return false;">start intervals</a> -
+        temps écoulé : <span id="timeStarted"></span>
+    </p>
+    <!--pre id="log"></pre-->
+    <script src="/js/libs-annotviz.js"></script>
+    <script src="/js/annotviz.js"></script>
+    <script>
+        var pianorollChannel  = 'PIANOROLL';
+        var annotationChannel = 'ANNOT';
+        var eventCode = 'test_1';
+        var wsUri;
+        if (window.location.protocol === 'file:') {
+            wsUri = 'ws://127.0.0.1:8090/broadcast';
+        }
+        else {
+            wsUri = 'ws://' + window.location.hostname + ':8090/broadcast';
+        }
+        wsUriPianoroll = wsUri + '?channel=' + pianorollChannel + '&event_code=' + eventCode;
+        wsUriAnnotation = wsUri + '?channel=' + annotationChannel + '&event_code=' + eventCode;
+
+
+
+        var logger = new annotviz.ConsoleLogger(true);
+
+        var doubleroll = new annotviz.DoubleRoll({
+            logger: logger,
+            externalRefresh: true,
+            ws: new annotviz.WsWrapper(wsUriPianoroll, logger),
+            orientation: 'vertical',
+            sceneHeight: 1080,
+            pianorolls : [
+                {
+                    height: 435,
+                    timeWidth: 60,
+                    lineInterval: 5000,
+                    noteHeight: undefined
+                },
+            ]
+        });
+
+
+        var annotsroll = new annotviz.AnnotsRoll({
+            logger: logger,
+            externalRefresh: true,
+            ws: new annotviz.WsWrapper(wsUriAnnotation, logger),
+            parentContainer: doubleroll.stage,
+            xInit: 1920 - 435 - 300,
+            yInit: 1080,
+            width: 1920,
+            height: 1080,
+            widthRoll: 300,
+            framerate: doubleroll.framerate,
+            pixelsPerSecond: Math.floor(1920 / 60),
+            annotColors: [{ts: 0, colors: {'ntm' : '#cdc83f'}}],
+        });
+
+        var refreshInterval;
+
+        function stop() {
+            doubleroll.stop();
+            annotsroll.stop();
+            window.clearInterval(refreshInterval);
+        }
+        function start() {
+            doubleroll.start();
+            annotsroll.start();
+            refreshInterval = setInterval(function() { doubleroll.refresh(); annotsroll.refresh(); }, 1000/doubleroll.framerate);
+        }
+
+        window.onload = function() {
+            doubleroll.init();
+            annotsroll.init();
+            start();
+        }
+
+
+var sessionJson =
+    {
+        "categories": [
+    {
+        "color": "rgb(205,200,63)",
+        "subcategories": [
+    {
+        "color": "rgb(205,200,63)",
+        "code": "ntm",
+        "full_label": "sous-catégorie 1-1",
+        "label": "Batterie"
+    },
+{
+    "color": "rgb(205,200,63)",
+    "code": "iam",
+    "full_label": "sous-catégorie 1-2",
+    "label": "Clavier"
+},
+{
+    "color": "rgb(205,200,63)",
+    "code": "hip",
+    "full_label": "sous-catégorie 1-3",
+    "label": "Guitare"
+},
+{
+    "color": "rgb(205,200,63)",
+    "code": "hop",
+    "full_label": "sous-catégorie 1-4",
+    "label": "Trombone"
+}
+],
+"full_label": "Liste des instruments disponibles",
+"label": "Instruments"
+},
+{
+    "color": "rgb(222,139,83)",
+    "subcategories": [
+{
+    "color": "rgb(222,139,83)",
+    "code": "rock",
+    "full_label": "sous-catégorie 2-1",
+    "label": "Accélération"
+},
+{
+    "color": "rgb(222,139,83)",
+    "code": "rap",
+    "full_label": "sous-catégorie 2-2",
+    "label": "Décélération"
+},
+{
+    "color": "rgb(222,139,83)",
+    "code": "classic",
+    "full_label": "sous-catégorie 2-3",
+    "label": "Pause"
+}
+],
+"full_label": "catégorie 2",
+"label": "Rythmique"
+},
+{
+    "color": "rgb(197,163,202)",
+    "subcategories": [
+    {
+        "color": "rgb(197,163,202)",
+        "code": "drums",
+        "full_label": "sous-catégorie 3-1",
+        "label": "sub cat 3-1"
+    },
+    {
+        "color": "rgb(197,163,202)",
+        "code": "guitar",
+        "full_label": "sous-catégorie 3-2",
+        "label": "sub cat 3-2"
+    }
+    ],
+    "full_label": "catégorie 3",
+    "label": "Une 3ème cat"
+},
+{
+    "color": "rgb(121,187,146)",
+    "code": "bass",
+    "full_label": "catégorie 4",
+    "label": "Annot'direct"
+}
+]
+};
+    </script>
+</body>
+</html>
--- a/client/annotviz/app/index.html	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/index.html	Tue Jan 20 11:57:44 2015 +0100
@@ -19,6 +19,7 @@
         <ul>
             <li><a href="pianoroll_h.html">Horizontal Pianoroll</a></li>
             <li><a href="pianoroll_v.html">Vertical Pianoroll</a></li>
+            <li><a href="annotsroll.html">AnnotsRoll</a></li>
         </ul>
     </p>
 </body>
--- a/client/annotviz/app/js/annotsroll.js	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/js/annotsroll.js	Tue Jan 20 11:57:44 2015 +0100
@@ -8,56 +8,162 @@
 'use strict';
 
 var PIXI = require('pixi');
-var randomColor = require('randomColor');
+var _ = require('lodash');
 
-function AnnotsRoll(parentContainer, xInit, yInit, width, height, widthRoll, pixelsPerSecond, annotColors, lineInterval){
+//
+//
+//
+// options = {
+//     parentContainer:,
+//     externalRefresh: true/false
+//     ws:,
+//     xInit:,
+//     yInit:,
+//     width:,
+//     height:,
+//     widthRoll:,
+//     pixelsPerSecond:,
+//     framerate:,
+//     annotColors: [{ts: , colors: {code1 : '#dshdsj', code2: 'sdasd', 'default': 'dsadas'}}],
+//     defaultColor: default,
+//     annotStyles: {
+//         'label': {font:, fill:},
+//         'text':{font:, fill:},
+//         'user':{font:, fill:},
+//     }
+// }
+var DEFAULT_ANNOT_COLOR = '#bababa';
+
+var defaultAnnotStyles = {
+    'label': { font: '26pt Arial Bold', fill: '#65A954' },
+    'text' : { font: '20pt Arial Regular', fill: '#444444' },
+    'user' : { font: '22pt Arial regular', fill: '#444444' },
+};
+
+var defaultOptions = {
+    externalRefresh: false,
+    defaultColor: DEFAULT_ANNOT_COLOR,
+    annotStyles: defaultAnnotStyles
+};
+
+function AnnotsRoll(options) {
+
+//parentContainer, xInit, yInit, width, height, widthRoll, pixelsPerSecond, annotColors
     var _this = this;
+    var opts = _(options).defaults(defaultOptions).value();
+
+
     this.container = new PIXI.DisplayObjectContainer();
-    this.container.position.x = xInit;
-    this.container.position.y = yInit;
-    this.container.width = width;
-    parentContainer.addChild(this.container);
+    this.container.x = opts.xInit;
+    this.container.y = opts.yInit;
+    this.container.width = opts.width;
+    opts.parentContainer.addChild(this.container);
+
+    this.height = opts.height;
+    this.width = opts.width;
+    this.widthRoll = opts.widthRoll;
+    this.pixelsPerSecond = opts.pixelsPerSecond;
+    this.annotColors = opts.annotColors;
+    this.startTs = options.startTs || Date.now();
 
-    this.height = height;
-    this.width = width;
-    this.widthRoll = widthRoll;
-    this.pixelsPerSecond = pixelsPerSecond;
-    this.lineInterval = lineInterval;
+    var yInit = opts.yInit;
+    var annotStyles = _(opts.annotStyles).defaults(defaultAnnotStyles).value();
+    var started = false;
+    var ws = opts.ws;
+    var externalRefresh = opts.externalRefresh;
+
+
+    var isHidden = function(child) {
+        // TODO: the origin point is an approximation. Should refine this
+        var globalPos = child.toGlobal(new PIXI.Point(0,0));
+        return ((globalPos.x + child.width) < 0) || ((globalPos.y + child.height) < 0) ;
+    };
+
+    this.addAnnots = function(data) {
+
+        //var title = data.content.category.label;
+        //var user = data.content.user;
+        //Test cat and color
+        //var colorAnnot = 0x65A954;
+        var category = data.content.category.label,
+            text     = data.content.text,
+            user     = data.content.user,
+            ts       = Date.parse(data.ts),
+            color    = this.getColor(ts, data.content.category.code);
+
+        this.addAnnot(category, text, user, color, ts);
+    };
 
-    this.addAnnot = function(category, user, catColor){
-    	var graphics = new PIXI.Graphics();
-    	var color = catColor;
-    	var x = 0;
-    	var y = -this.container.y;
-        graphics.beginFill(color);
-        graphics.drawRect(x, y, 10, 3);
-        graphics.endFill();
-        
+    this.getColor = function(ts, code) {
+        var colorsDef;
+        _(this.annotColors).eachRight(function(cdef) {
+            console.log("cDef", cdef);
+            console.log("cDef ts", cdef.ts, ts);
+            if(cdef.ts < ts) {
+                colorsDef = cdef.colors;
+                return false;
+            }
+        });
+        var resColor;
+        console.log("colorsDef", colorsDef);
+        if(colorsDef) {
+            resColor = colorsDef[code];
+        }
+        if(!resColor) {
+            resColor = DEFAULT_ANNOT_COLOR;
+        }
+        return resColor;
+    }
+
+    this.addAnnot = function(category, text, user, catColor, ts){
+
+        var color = catColor ? catColor : DEFAULT_ANNOT_COLOR;
+        var x = 0;
+        var y = (ts-this.startTs) * this.pixelsPerSecond / 1000 + yInit;
+
+        var colorHex = parseInt(color.replace(/^#/, ''), 16);
+
+        var graphics = new PIXI.Graphics()
+            .beginFill(colorHex)
+            .drawRect(x, y, 10, 3)
+            .endFill();
+
         this.container.addChild(graphics);
 
-        var catText = new PIXI.Text(category, { font: '16pt Arial', fill: '#65A954' });
-        catText.x = x + 20;
-        catText.y = y - 23;
-        this.container.addChild(catText);
-        
-        var userText = new PIXI.Text(user, { font: '10pt Arial', fill: '#444444' });
-        userText.x = x + 20;
-        userText.y = y + 2;
-        this.container.addChild(userText);
-        
-        this.addAnnotLine(color)
+        var catLabel = new PIXI.Text(
+            category,
+            _(annotStyles.label).extend({fill: color}).value()
+        );
+        catLabel.x = x + 20;
+        catLabel.y = y - 23;
+        this.container.addChild(catLabel);
+
+        var textHeight = 0;
+        if(text) {
+            var catText = new PIXI.Text(text, annotStyles.text);
+            catText.x = x + 20;
+            catText.y = y + 2;
+            this.container.addChild(catText);
+            textHeight += (catText.height + 2);
+        }
+
+        var catUser = new PIXI.Text(user, annotStyles.user);
+        catUser.x = x + 20;
+        catUser.y = y + 2 + textHeight;
+        this.container.addChild(catUser);
+
+        this.addAnnotLine(colorHex, y);
     };
 
-    this.addAnnotLine = function(color){
-    	var x = this.widthRoll;
-    	var y = -this.container.y;
-    	
-    	var graphics = new PIXI.Graphics();
-    	
-        graphics.beginFill(color);
-        graphics.drawRect(x, y, this.width - x, 3);
-        graphics.endFill();
-        
+    this.addAnnotLine = function(color, y) {
+        var x = this.widthRoll;
+
+
+        var graphics = new PIXI.Graphics()
+            .beginFill(color)
+            .drawRect(x, y, this.width - x, 3)
+            .endFill();
+
         this.container.addChild(graphics);
     };
 
@@ -65,29 +171,51 @@
     	this.container.y = Math.floor(diffTime*this.pixelsPerSecond);
     };
 
+    this.move = this.refresh = function() {
+        var diff = (this.startTs - Date.now())/1000;
+        this.moveTo(diff);
+    };
+
     this.removePassedObjets = function(){
-        var nbChilds = _this.container.children.length;
-        var i = 0, childIsNowDisplayed = false, childrenToRemove = [];
-        while(i<nbChilds && !childIsNowDisplayed){
-            var child = _this.container.children[i++];
-            
-            if(typeof(child) == 'undefined') {
-                continue;
-            }
-        	if((child.y + child.height) < (Math.abs(_this.container.y) - _this.height)){
-                childrenToRemove.push(child);
-            }
-            else {
-                childIsNowDisplayed = true;
-            }
-        }
+        var childrenToRemove = [];
+        _(_this.container.children).forEach(function(child) {
+            return typeof(child) === 'undefined' ||
+                (isHidden(child) && childrenToRemove.push(child));
+        });
         childrenToRemove.forEach(function(child) {
             _this.container.removeChild(child);
         });
     };
 
-	window.setInterval(this.removePassedObjets, 1000 * this.height / this.pixelsPerSecond );
+    this.init = function() {
+
+        ws.message(function(data) {
+            _this.addAnnots(data);
+        });
+
+    };
+
+
+    this.start = function() {
+        if(!started) {
+            this.startTs = Date.now();
+            started = true;
+        }
+        this.cleanInterval = setInterval(function () { _this.removePassedObjets(); }, 1000 * this.height / this.pixelsPerSecond );
+        if(!externalRefresh) {
+            this.refreshInterval = setInterval(function() {_this.move();}, 1000/this.framerate);
+        }
+    };
+
+    this.stop = function() {
+        clearInterval(this.cleanInterval);
+        if(!externalRefresh) {
+            clearInterval(this.refreshInterval);
+        }
+    };
 
 }
 
-module.exports = AnnotsRoll;
+module.exports = {
+    AnnotsRoll: AnnotsRoll,
+};
--- a/client/annotviz/app/js/annotstimeline.js	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/js/annotstimeline.js	Tue Jan 20 11:57:44 2015 +0100
@@ -1,18 +1,17 @@
 /**
-* js/generalView
+* js/annotsTimeline
 *
-* generalView basic component
+* annotsTimeline basic component
 *
 */
 
 'use strict';
 
 var PIXI = require('pixi');
-var randomColor = require('randomColor');
 
-function GeneralView(parentContainer, xInit, yInit, width, height, timeBegin, timeEnd, intervalSize){
-	console.log(width);
-    var _this = this;
+function AnnotsTimeline(parentContainer, xInit, yInit, width, height, timeBegin, timeEnd, intervalSize){
+
+//    var _this = this;
     this.container = new PIXI.DisplayObjectContainer();
     this.container.position.x = xInit;
     this.container.position.y = yInit;
@@ -22,7 +21,7 @@
 
     this.timeBegin = timeBegin;
     this.timeEnd = timeEnd;
-    this.duration = (timeEnd - timeBegin)/1000
+    this.duration = (timeEnd - timeBegin)/1000;
     this.width = width;
     this.height = height;
     //define interval corresponding to the time of one step
@@ -33,7 +32,7 @@
 
   //Initialise the list of step
     //each cell will contain the list categories and the number of annotations per categories
-    this.cells = []
+    this.cells = [];
 
 
     // draw temp line to locate the middle of the container
@@ -62,7 +61,7 @@
 //    		if (c)
 //    	}
 
-    	console.log("x : " + x + " | y : " + y);
+    	console.log('x : ' + x + ' | y : ' + y);
 
     	// We draw the rectangle
         var graphics = new PIXI.Graphics();
@@ -75,4 +74,6 @@
 
 }
 
-module.exports = GeneralView;
+module.exports = {
+	AnnotsTimeline : AnnotsTimeline
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/js/annotsvizview.js	Tue Jan 20 11:57:44 2015 +0100
@@ -0,0 +1,6 @@
+/**
+* js/annotsvizview.js
+*
+* This is the starting point for your application.
+* Take a look at http://browserify.org/ for more info
+*/
--- a/client/annotviz/app/js/doubleroll.js	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/js/doubleroll.js	Tue Jan 20 11:57:44 2015 +0100
@@ -5,10 +5,7 @@
 * Take a look at http://browserify.org/ for more info
 */
 
-/* global window: false */
 /* global document: false */
-/* global WebSocket: false */
-/* global MozWebSocket: false */
 
 'use strict';
 
@@ -17,11 +14,10 @@
 var _ = require('lodash');
 var PianoRoll = require('./pianoroll.js');
 
-var NTP_EPOCH_DELTA = 2208988800; //c.f. RFC 868
-
 var defaultConfig = {
     orientation: 'horizontal',
-    logger: false,
+    externalRefresh: false,
+    logger: undefined,
     sceneWidth: 1920,
     pianorolls : [
       {
@@ -44,13 +40,11 @@
     lineFillColor: 0xFFFF00,
     noteColors: [0xB90000, 0x4BDD71, 0xAF931E, 0x1C28BA, 0x536991],
     canvasContainer: 'canvasContainer',
-    logContainer: 'log',
     timeContainer: 'timeStarted',
     noteHeight: undefined,
     zeroShift: 0.9,
     timeWidth: 60,
     lineInterval: 5000,
-    annotationChannel: 'PIANOROLL'
 //    wsUri: undefined,
 //    eventCode: undefined
 
@@ -63,6 +57,7 @@
 
     var orientation = opts.orientation;
     var isHorizontal = (orientation !== 'vertical');
+    var externalRefresh = opts.externalRefresh;
 
     this.logger = opts.logger;
     this.lineColor = opts.lineColor;
@@ -80,36 +75,23 @@
 
     var sceneWidth = opts.sceneWidth;
     var canvasContainer = opts.canvasContainer;
-    var logContainer = opts.logContainer;
     var timeContainer = opts.timeContainer;
 
     var zeroShift = opts.zeroShift;
 
-    var eventCode = opts.eventCode;
-    var annotationChannel = opts.annotationChannel;
-    var wsUri = opts.wsUri;
-    if(!wsUri) {
-        if (window.location.protocol === 'file:') {
-            wsUri = 'ws://127.0.0.1:8090/broadcast';
-        }
-        else {
-            wsUri = 'ws://' + window.location.hostname + ':8090/broadcast';
-        }
-        wsUri += '?channel='+annotationChannel+'&event_code='+eventCode;
-    }
-
+    var ws = opts.ws;
 
     var colorsReg = {};
 
     //create an new instance of a pixi stage
-    var stage = new PIXI.Stage(sceneBgColor);
+    this.stage = new PIXI.Stage(sceneBgColor);
     //create a renderer instance.
     var renderer = PIXI.autoDetectRenderer(sceneWidth, sceneHeight);
 
     var uberContainer = new PIXI.DisplayObjectContainer();
     uberContainer.x = Math.floor(sceneWidth*zeroShift);
     uberContainer.y = 0;
-    stage.addChild(uberContainer);
+    this.stage.addChild(uberContainer);
 
     var pianorollList = [];
 
@@ -164,76 +146,31 @@
         if(typeof(canvasContainer) === 'string') {
             canvasContainer = document.getElementById(canvasContainer);
         }
-        if(typeof(logContainer) === 'string') {
-            logContainer = document.getElementById(logContainer);
-        }
         if(typeof(timeContainer) === 'string') {
             timeContainer = document.getElementById(timeContainer);
         }
 
-
-        if(!this.logger){
-            document.body.removeChild(logContainer);
-            logContainer = undefined;
-        }
-        var sock;
-
         canvasContainer.appendChild(renderer.view);
 
-        if ('WebSocket' in window) {
-            sock = new WebSocket(wsUri);
-        } else if ('MozWebSocket' in window) {
-            sock = new MozWebSocket(wsUri);
-        } else {
-            this.log('Browser does not support WebSocket!');
-            window.location = 'http://autobahn.ws/unsupportedbrowser';
-        }
-
-        if (!sock) {
-            return;
-        }
-        sock.onopen = function(){
-            if(_this.logger){
-                _this.log('Connected to ' + _this.wsUri);
-            }
-        };
-
-        sock.onclose = function(e) {
-            if(_this.logger){
-                _this.log('Connection closed (wasClean = ' + e.wasClean + ', code = ' + e.code + ', reason = \'' + e.reason + '\')');
-            }
-            sock = null;
-        };
-
-        sock.onmessage = function(e) {
-            var dataJson = JSON.parse(e.data);
-            if(_this.logger){
-                var dataDate = new Date((dataJson.content[0]-NTP_EPOCH_DELTA)*1000);
-                _this.log('Got message: ' + e.data + ' - ' + dataDate.toISOString());
-            }
-            _this.addNotes(dataJson);
-        };
+        ws.message(function(data) {
+            _this.addNotes(data);
+        });
 
     };
 
 
     this.addNotes = function(data) {
-        var note = data.content[3];
-        var velocity = data.content[4];
-        var ts = (data.content[0] - NTP_EPOCH_DELTA)*1000;
-        var channel = data.content[2];
-        var sessionTs = data.content[1];
 
         pianorollList.forEach(function(c) {
-            c.addNote(note, ts, sessionTs, velocity, channel, 0);
+            c.addNoteRaw(data);
         });
     };
 
-    this.refreshStage = function() {
+    this.refreshStage = this.refresh = function() {
         pianorollList.forEach(function(c) {
             c.move();
         });
-        renderer.render(stage);
+        renderer.render(this.stage);
     };
 
     // Init page and intervals
@@ -253,16 +190,20 @@
     this.start = function() {
 
         startTs = Date.now();
-        refreshInterval = window.setInterval(function() {_this.refreshStage();}, 1000/this.framerate);
-        refreshTimeInterval = window.setInterval(function() {_this.updateTime();}, 1000);
+        if(!externalRefresh) {
+            refreshInterval = setInterval(function() {_this.refreshStage();}, 1000/this.framerate);
+        }
+        refreshTimeInterval = setInterval(function() {_this.updateTime();}, 1000);
         pianorollList.forEach(function(c) {
             c.start();
         });
     };
 
     this.stop = function() {
-        window.clearInterval(refreshInterval);
-        window.clearInterval(refreshTimeInterval);
+        if(!externalRefresh) {
+            clearInterval(refreshInterval);
+        }
+        clearInterval(refreshTimeInterval);
         pianorollList.forEach(function(c) {
             c.stop();
         });
@@ -270,17 +211,12 @@
 
 
     this.log = function(m) {
-        if(this.logger){
-            this.logContainer.innerHTML += m + '\n';
-            this.logContainer.scrollTop = logContainer.scrollHeight;
+        if(this.logger) {
+            this.logger.log(m);
         }
     };
 
 
-    window.onload = function() {
-        _this.init();
-        _this.start();
-    };
 
     return this;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/js/logger.js	Tue Jan 20 11:57:44 2015 +0100
@@ -0,0 +1,45 @@
+/**
+* js/wswrapper.js
+*
+* simple logger service
+*
+*/
+
+/* global document: false */
+
+'use strict';
+
+function HtmlLogger(doLog, container) {
+
+    var logContainer = container;
+    if(typeof(container) === 'string') {
+        logContainer = document.getElementById(container);
+    }
+    if(!doLog) {
+        document.body.removeChild(logContainer);
+        logContainer = undefined;
+    }
+
+
+    this.log = function(msg) {
+        if(doLog && logContainer) {
+            logContainer.innerHTML += msg + '\n';
+            logContainer.scrollTop = logContainer.scrollHeight;
+        }
+    };
+}
+
+function ConsoleLogger(doLog) {
+
+    this.log = function(msg) {
+        if(doLog) {
+            console.log(msg);
+        }
+    }
+
+}
+
+module.exports = {
+    HtmlLogger: HtmlLogger,
+    ConsoleLogger: ConsoleLogger
+};
--- a/client/annotviz/app/js/main.js	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/js/main.js	Tue Jan 20 11:57:44 2015 +0100
@@ -7,8 +7,16 @@
 
 'use strict';
 
-var doubleroll = require('./doubleroll.js');
+var doubleroll = require('./doubleroll');
+var annotroll = require('./annotsroll');
+var wswrapper = require('./wswrapper');
+var logger = require('./logger');
+
+var _ = require('lodash');
 
-module.exports = {
-    DoubleRoll: doubleroll.DoubleRoll
-};
+module.exports = _({})
+    .extend(doubleroll)
+    .extend(annotroll)
+    .extend(wswrapper)
+    .extend(logger)
+    .value();
--- a/client/annotviz/app/js/pianoroll.js	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/js/pianoroll.js	Tue Jan 20 11:57:44 2015 +0100
@@ -5,7 +5,6 @@
 *
 */
 
-/* global window: false */
 'use strict';
 
 
@@ -13,6 +12,7 @@
 var randomColor = require('randomColor');
 var _ = require('lodash');
 
+var NTP_EPOCH_DELTA = 2208988800; //c.f. RFC 868
 
 function PianoRoll(options) {
     var _this = this;
@@ -72,6 +72,16 @@
         return graphics;
     };
 
+    this.addNoteRaw = function(data) {
+        var note = data.content[3];
+        var velocity = data.content[4];
+        var ts = (data.content[0] - NTP_EPOCH_DELTA)*1000;
+        var channel = data.content[2];
+        var sessionTs = data.content[1];
+
+        this.addNote(note, ts, sessionTs, velocity, channel, 0);
+    };
+
     this.addNote = function(note, startTime, sessionTs, velocity, channel, duration) {
 
         var ts = startTime;
@@ -186,11 +196,10 @@
     };
 
     this.removePassedObjets = function(){
-        var nbChilds = _this.container.children.length;
         var childrenToRemove = [];
         _(_this.container.children).forEach(function(child) {
-            return typeof(child) === 'undefined'
-                || (isHidden(child) && childrenToRemove.push(child));
+            return typeof(child) === 'undefined' ||
+                (isHidden(child) && childrenToRemove.push(child));
         });
         childrenToRemove.forEach(function(child) {
             _this.container.removeChild(child);
@@ -201,16 +210,16 @@
         if(!started) {
             this.startTs = Date.now();
             this.addLine();
+            started = true;
         }
-        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.verticalLinesInterval = setInterval(function() { _this.addLine(); }, this.lineInterval);
+        this.cleanInterval = setInterval(function () { _this.removePassedObjets(); }, 1000 * this.width / this.pixelsPerSecond );
     };
 
     this.stop = function() {
         //window.clearInterval(this.moveInterval);
-        window.clearInterval(this.verticalLinesInterval);
-        window.clearInterval(this.cleanInterval);
+        clearInterval(this.verticalLinesInterval);
+        clearInterval(this.cleanInterval);
     };
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/js/wswrapper.js	Tue Jan 20 11:57:44 2015 +0100
@@ -0,0 +1,53 @@
+/**
+* js/wswrapper.js
+*
+* simple webservice wrapper to register callbacks on onmessage
+*
+*/
+
+/* global WebSocket: false */
+
+'use strict';
+
+function WsWrapper(wsurl, logger) {
+
+    var url = wsurl;
+    var sock = new WebSocket(url);
+    var loggerObj = logger;
+
+    var log = function(msg) {
+        if(loggerObj) {
+            loggerObj.log(msg);
+        }
+    };
+
+    var handlers = [];
+
+    sock.onopen = function() {
+        log('Connected to ' + url);
+    };
+
+    sock.onclose = function(e) {
+        log('Connection closed (wasClean = ' + e.wasClean + ', code = ' + e.code + ', reason = \'' + e.reason + '\')');
+        sock = null;
+    };
+
+    sock.onmessage = function(e) {
+        log('received ' + e.data);
+        var data = JSON.parse(e.data);
+        handlers.forEach(function(handler) {
+            handler(data);
+        });
+    };
+
+    this.message = function(handler) {
+        if(handler) {
+            handlers.push(handler);
+        }
+    };
+
+}
+
+module.exports = {
+    WsWrapper: WsWrapper
+};
--- a/client/annotviz/app/pianoroll_h.html	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/pianoroll_h.html	Tue Jan 20 11:57:44 2015 +0100
@@ -7,14 +7,14 @@
     <meta name="author" content="I.R.I">
     <link rel="shortcut icon" href="/img/favicon.ico">
 
-    <title>Piano Roll</title>
+    <title>Horizontal Piano Roll</title>
 
     <!-- Custom styles for this template -->
     <link href="/css/annotviz.css" rel="stylesheet">
 </head>
 
 <body>
-    <h1>Piano Roll</h1>
+    <h1>Horizontal Piano Roll</h1>
     <noscript>You must enable JavaScript</noscript>
     <div id="canvasContainer"></div>
     <p>
@@ -26,11 +26,30 @@
     <script src="/js/libs-annotviz.js"></script>
     <script src="/js/annotviz.js"></script>
     <script>
-        var doubleroll = annotviz.DoubleRoll({
-            eventCode: "test_1"
+        var annotationChannel = 'PIANOROLL';
+        var eventCode = 'test_1';
+        var wsUri;
+        if (window.location.protocol === 'file:') {
+            wsUri = 'ws://127.0.0.1:8090/broadcast';
+        }
+        else {
+            wsUri = 'ws://' + window.location.hostname + ':8090/broadcast';
+        }
+        wsUri += '?channel='+annotationChannel+'&event_code='+eventCode;
+
+
+        var doubleroll = new annotviz.DoubleRoll({
+            logger: new annotviz.HtmlLogger(false, 'log'),
+            ws: new annotviz.WsWrapper(wsUri)
         });
+
         function stop() { doubleroll.stop(); }
         function start() { doubleroll.start(); }
+
+        window.onload = function() {
+            doubleroll.init();
+            start();
+        }
     </script>
 </body>
 </html>
--- a/client/annotviz/app/pianoroll_v.html	Mon Jan 19 16:44:59 2015 +0100
+++ b/client/annotviz/app/pianoroll_v.html	Tue Jan 20 11:57:44 2015 +0100
@@ -7,14 +7,14 @@
     <meta name="author" content="I.R.I">
     <link rel="shortcut icon" href="/img/favicon.ico">
 
-    <title>Piano Roll Vertical</title>
+    <title>Vertical Piano Roll</title>
 
     <!-- Custom styles for this template -->
     <link href="/css/annotviz.css" rel="stylesheet">
 </head>
 
 <body>
-    <h1>Piano Roll vertical</h1>
+    <h1>Vertical Piano Roll</h1>
     <noscript>You must enable JavaScript</noscript>
     <div id="canvasContainer"></div>
     <p>
@@ -26,12 +26,31 @@
     <script src="/js/libs-annotviz.js"></script>
     <script src="/js/annotviz.js"></script>
     <script>
+        var annotationChannel = 'PIANOROLL';
+        var eventCode = 'test_1';
+        var wsUri;
+        if (window.location.protocol === 'file:') {
+            wsUri = 'ws://127.0.0.1:8090/broadcast';
+        }
+        else {
+            wsUri = 'ws://' + window.location.hostname + ':8090/broadcast';
+        }
+        wsUri += '?channel='+annotationChannel+'&event_code='+eventCode;
+
+
         var doubleroll = annotviz.DoubleRoll({
-            eventCode: 'test_1',
             orientation: 'vertical',
+            logger: new annotviz.HtmlLogger(false, 'log'),
+            ws: new annotviz.WsWrapper(wsUri)
         });
         function stop() { doubleroll.stop(); }
         function start() { doubleroll.start(); }
+
+        window.onload = function() {
+            doubleroll.init();
+            start();
+        };
+
     </script>
 </body>
 </html>