add new visualization + small corrections
authorymh <ymh.work@gmail.com>
Tue, 13 Jan 2015 10:46:05 +0100
changeset 85 eff9460bd4f2
parent 84 d7c5bffdd2d8
child 86 e944c017b8c8
add new visualization + small corrections
.hgignore
client/annotviz/.bowerrc
client/annotviz/.editorconfig
client/annotviz/.gitattributes
client/annotviz/.jshintrc
client/annotviz/README.md
client/annotviz/app/img/favicon.ico
client/annotviz/app/index.html
client/annotviz/app/js/main.js
client/annotviz/app/js/pianoroll.js
client/annotviz/app/less/app.less
client/annotviz/app/less/components/base.less
client/annotviz/app/less/variables.less
client/annotviz/bower.json
client/annotviz/gulp/config.js
client/annotviz/gulp/index.js
client/annotviz/gulp/tasks/browserify.js
client/annotviz/gulp/tasks/build.js
client/annotviz/gulp/tasks/clean.js
client/annotviz/gulp/tasks/default.js
client/annotviz/gulp/tasks/dev.js
client/annotviz/gulp/tasks/dist.js
client/annotviz/gulp/tasks/html.js
client/annotviz/gulp/tasks/images.js
client/annotviz/gulp/tasks/jshint.js
client/annotviz/gulp/tasks/less.js
client/annotviz/gulp/tasks/serve.js
client/annotviz/gulp/tasks/watch.js
client/annotviz/gulp/util/scriptFilter.js
client/annotviz/gulpfile.js
client/annotviz/package.json
client/pianoroll/app/index.html
client/pianoroll/app/js/app.js
client/pianoroll/app/js/main.js
client/pianoroll/app/js/pianoroll.js
utils/pianoroll-client.py
--- a/.hgignore	Mon Jan 12 17:23:05 2015 +0100
+++ b/.hgignore	Tue Jan 13 10:46:05 2015 +0100
@@ -21,8 +21,10 @@
 \.orig$
 ^client/pianoroll/dist
 ^client/pianoroll/.tmp
-^client/pianoroll/.sass-cache
 ^client/pianoroll/app/lib
+^client/annotviz/dist
+^client/annotviz/.tmp
+^client/annotviz/app/lib
 
 node_modules
 .DS_Store
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/.bowerrc	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,3 @@
+{
+  "directory": "app/lib"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/.editorconfig	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/.gitattributes	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,1 @@
+* text=auto
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/.jshintrc	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,21 @@
+{
+  "node": true,
+  "esnext": true,
+  "bitwise": true,
+  "camelcase": true,
+  "curly": true,
+  "eqeqeq": true,
+  "immed": true,
+  "indent": 4,
+  "latedef": true,
+  "newcap": true,
+  "noarg": true,
+  "quotmark": "single",
+  "regexp": true,
+  "undef": true,
+  "unused": true,
+  "strict": true,
+  "trailing": true,
+  "smarttabs": true,
+  "white": false
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/README.md	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,1 @@
+# Mons Pianoroll
Binary file client/annotviz/app/img/favicon.ico has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/index.html	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,35 @@
+<!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>Piano Roll</title>
+
+    <!-- Custom styles for this template -->
+    <link href="/css/pianoroll.css" rel="stylesheet">
+</head>
+
+<body>
+    <h1>Piano Roll</h1>
+    <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>
+    </p>
+    <pre id="log"></pre>
+    <script src="/js/libs-pianoroll.js"></script>
+    <script>
+    var eventCode = "test_1";
+    </script>
+    <script src="/js/pianoroll.js"></script>
+    <script>
+    var moveInterval = pianoroll.moveInterval;
+    var verticalLinesInterval = pianoroll.verticalLinesInterval;
+    </script>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/js/main.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,198 @@
+/**
+ * scripts/main.js
+ *
+ * This is the starting point for your application.
+ * Take a look at http://browserify.org/ for more info
+ */
+
+'use strict';
+
+
+var PIXI = require('pixi');
+
+// Config vars
+var logger = false;
+var sceneWidth = 1920;
+var prHeight1 = 435;
+var prHeight2 = 645;
+var sceneHeight = prHeight1 + prHeight2;
+var sceneBgColor = 0xFFFFFF;
+var lineColor = 0x444444;
+var pixelsPerSecond1 = Math.floor(sceneWidth / 10); // nb of pixels per second
+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 noteColors = [0xB90000, 0x4BDD71, 0xAF931E, 0x1C28BA, 0x536991];
+var colorsReg = {};
+// Vars
+var noteDict = [];
+// Timecode method
+var timePageLoaded = Date.now();
+var offsetMusic = false;
+
+
+//create an new instance of a pixi stage
+var stage = new PIXI.Stage(sceneBgColor);
+
+//create a renderer instance.
+var renderer = PIXI.autoDetectRenderer(sceneWidth, sceneHeight);
+
+//add the renderer view element to the DOM
+document.getElementById('canvasContainer').appendChild(renderer.view);
+
+var uberContainer = new PIXI.DisplayObjectContainer();
+uberContainer.position.x = Math.floor(sceneWidth*9/10);
+uberContainer.position.y = 0;
+stage.addChild(uberContainer);
+
+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));
+
+// Line between two containers
+var graphics = new PIXI.Graphics();
+graphics.beginFill(0xFFFF00);
+graphics.lineStyle(1, lineColor);
+graphics.moveTo(0, prHeight1);
+graphics.lineTo(sceneWidth, prHeight1);
+graphics.endFill();
+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};
+    }
+}
+
+function addLine(){
+    ts = new Date();
+    for(var i=0;i<containerList.length;i++){
+        containerList[i].addLine(ts);
+    }
+}
+
+
+
+// Socket management
+var sock = null;
+var ellog = null;
+function log(m) {
+    if(logger){
+        ellog.innerHTML += m + '\n';
+        ellog.scrollTop = ellog.scrollHeight;
+    }
+}
+window.onload = function(){
+
+    if(logger){
+        ellog = document.getElementById('log');
+    }
+    else{
+        document.body.removeChild(document.getElementById('log'));
+    }
+
+    var wsuri;
+    if (window.location.protocol === 'file:') {
+        wsuri = 'ws://127.0.0.1:8090/broadcast';
+    } else {
+        wsuri = 'ws://' + window.location.hostname + ':8090/broadcast';
+    }
+    wsuri = wsuri + '?channel=PIANOROLL&event_code='+eventCode;
+
+    if ('WebSocket' in window) {
+        sock = new WebSocket(wsuri);
+    } else if ('MozWebSocket' in window) {
+        sock = new MozWebSocket(wsuri);
+    } else {
+        log('Browser does not support WebSocket!');
+        window.location = 'http://autobahn.ws/unsupportedbrowser';
+    }
+
+    if (sock) {
+        sock.onopen = function(){
+            if(logger){
+                log('Connected to ' + wsuri);
+            }
+        };
+
+        sock.onclose = function(e) {
+            if(logger){
+                log('Connection closed (wasClean = ' + e.wasClean + ', code = ' + e.code + ', reason = \'' + e.reason + '\')');
+            }
+            sock = null;
+        };
+
+        sock.onmessage = function(e) {
+            if(logger){
+                log('Got message: ' + e.data);
+            }
+            addNotes(JSON.parse(e.data));
+        };
+    }
+};
+
+
+// Init page and intervals
+addLine();
+var moveInterval = window.setInterval(replaceContainers, 1000/manualFramerate);
+var verticalLinesInterval = window.setInterval(addLine, lineInterval);
+
+// Little inteval to show time
+var nbSec = 0;
+var mySpan = document.getElementById('myspan');
+function updateTime(){
+    nbSec++;
+    var hours = parseInt( nbSec / 3600 ) % 24;
+    var minutes = parseInt( nbSec / 60 ) % 60;
+    var seconds = nbSec % 60;
+    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);
+
+module.exports = {
+    moveInterval: moveInterval,
+    verticalLinesInterval: verticalLinesInterval,
+    secondInterval: secondInterval
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/js/pianoroll.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,118 @@
+/**
+* js/pianoroll.js
+*
+* pianoroll basic component
+*
+*/
+
+'use strict';
+
+var PIXI = require('pixi');
+var randomColor = require('randomColor');
+
+function PianoRoll(parentContainer, xInit, yInit, height, linesDown, pixelsPerSecond, width, noteColors, colorsReg, lineColor, lineInterval, offsetMusic, noteHeight){
+    var _this = this;
+    this.container = new PIXI.DisplayObjectContainer();
+    this.container.position.x = xInit;
+    this.container.position.y = yInit;
+    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.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);
+        var color = this.colorsReg[canal];
+        if(typeof(color) === 'undefined') {
+            var colorsRegSize = Object.keys(this.colorsReg).length;
+            if(colorsRegSize < this.noteColors.length) {
+                color = this.colorsReg[canal] = this.noteColors[colorsRegSize];
+            }
+            else {
+                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);
+        graphics.endFill();
+        graphics.x = beginX;
+        this.container.addChild(graphics);
+    };
+
+    this.addLine = function(ts){
+        var graphics = new PIXI.Graphics();
+        var x = -this.container.x;
+        graphics.beginFill(0xFFFF00);
+        graphics.lineStyle(1, this.lineColor);
+        var y = this.linesDown ? this.height - 20 : 0;
+        graphics.moveTo(x, y);
+        graphics.lineTo(x, y + 20);
+        graphics.endFill();
+        this.container.addChild(graphics);
+        // Add text
+        //var totalSec = lineNb * this.lineInterval / 1000;
+        var hours = ts.getHours();
+        var minutes =ts.getMinutes();
+        var seconds = ts.getSeconds();
+        var timeStr = (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds  < 10 ? '0' + seconds : seconds);
+
+        var fontObj = { font: '10pt Arial', fill: '#444444' };
+        var t = new PIXI.Text(timeStr, fontObj);
+        t.x = x + 2;
+        t.y = this.linesDown ? this.height - 15 : 2;
+        this.container.addChild(t);
+    };
+
+    this.moveTo = function(diffTime){
+        this.container.x = Math.floor(diffTime*this.pixelsPerSecond);
+    };
+
+    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++];
+            //console.log("remove ? ", child.x, child.width, ((child.x + child.width) < (Math.abs(_this.container.x) - _this.width)));
+            if(typeof(child) == 'undefined') {
+                continue;
+            }
+            if((child.x + child.width) < (Math.abs(_this.container.x) - _this.width)){
+                childrenToRemove.push(child);
+                //console.log("    remove !!!");
+            }
+            else {
+                childIsNowDisplayed = true;
+                //console.log("    childIsNowDisplayed");
+            }
+        }
+        childrenToRemove.forEach(function(child) {
+            _this.container.removeChild(child);
+        });
+        //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 );
+
+}
+
+module.exports = PianoRoll;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/less/app.less	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,13 @@
+/* ==========================================================================
+   App Stylesheet
+   app.less
+   ========================================================================== */
+
+
+/* Mixins (User defined mixins)
+   ======================================================================== */
+
+
+/* Components
+   ======================================================================== */
+@import "components/base.less";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/less/components/base.less	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,15 @@
+/* ==========================================================================
+   Base Stylesheet
+   base.less
+   ========================================================================== */
+body {
+   margin: 0;
+   padding: 0;
+   background-color: #FFF;
+}
+#log {
+   height: 20em;
+   overflow-y: scroll;
+   background-color: #faa;
+}
+   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/app/less/variables.less	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,5 @@
+/* ==========================================================================
+   You can copy the variables file here or simply add new overrides here
+   and import the bootstrap variables file.
+   variables.less
+   ========================================================================== */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/bower.json	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,8 @@
+{
+  "name": "annotviz",
+  "version": "0.0.0",
+  "dependencies": {
+    "randomColor": "davidmerfield/randomColor#~0.1.1",
+    "pixi": "~2.2.3"
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/config.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,12 @@
+var path = require('path');
+var production = (process.env.NODE_ENV === 'production');
+
+module.exports = {
+    bower: 'app/lib',
+    dist: production ? 'dist' : '.tmp',
+    livereloadPort: 35729,
+    port: 8888,
+    root: path.resolve('./'),
+    browser: "FirefoxDeveloperEdition",
+    serverStatic: path.resolve('../../annot-server/static')
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/index.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,7 @@
+var fs = require('fs');
+var onlyScripts = require('./util/scriptFilter');
+var tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts);
+
+tasks.forEach(function(task) {
+	require('./tasks/' + task);
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/browserify.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,41 @@
+'use strict';
+
+var browserify = require('browserify');
+var config = require('../config');
+var partialify = require('partialify');
+var gulp = require('gulp');
+var debug = require('gulp-debug');
+var rename = require('gulp-rename');
+var source = require('vinyl-source-stream');
+var p = require('../../package.json')
+var uglify = require('gulp-uglify');
+
+// Vendor
+gulp.task('vendor', function() {
+  return browserify({debug: true})
+    .require('./app/lib/pixi/bin/pixi.js', { expose: 'pixi' })
+    .require('./app/lib/randomColor/randomColor.js', {expose: 'randomColor'})
+    .bundle()
+    .pipe(source('libs-pianoroll.js'))
+    .pipe(gulp.dest(config.dist + '/js/'));
+});
+
+// Browserify
+gulp.task('browserify', function() {
+  return browserify({debug: true, standalone: 'pianoroll'})
+    .add('./app/js/main.js')
+    .external('pixi')
+    .external('randomColor')
+    .transform(partialify) // Transform to allow requireing of templates
+    .bundle()
+    .pipe(source(p.name+'.js'))
+    .pipe(gulp.dest(config.dist + '/js/'));
+});
+
+// Script Dist
+gulp.task('scripts:dist', function() {
+  return gulp.src([config.dist + '/js/*.js', '!'+config.dist + '/js/*.min.js'], {base: config.dist})
+    .pipe(uglify())
+    .pipe(rename({extname: '.min.js'}))
+    .pipe(gulp.dest(config.dist));
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/build.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,8 @@
+'use strict';
+
+var gulp = require('gulp');
+
+// Build
+gulp.task('build', ['html', 'styles', 'vendor', 'browserify', 'images'], function() {
+
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/clean.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,9 @@
+'use strict';
+
+var gulp = require('gulp');
+var rimraf = require('gulp-rimraf');
+
+// Clean
+gulp.task('clean', function () {
+    return gulp.src(['.tmp', 'dist/css', 'dist/js', 'dist/img'], {read: false}).pipe(rimraf());
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/default.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,8 @@
+'use strict';
+
+var gulp = require('gulp');
+
+// Default task
+gulp.task('default', ['clean'], function () {
+    gulp.start('dev');
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/dev.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,6 @@
+'use strict';
+
+var gulp = require('gulp');
+
+// Dev Server
+gulp.task('dev', ['html', 'styles', 'vendor', 'browserify', 'images', 'watch']);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/dist.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,21 @@
+'use strict';
+
+var gulp = require('gulp');
+var runSequence = require('run-sequence');
+var config = require('../config');
+
+// Build
+gulp.task('dist', function() {
+    runSequence(['html', 'images:dist','styles', 'styles:dist', 'vendor', 'browserify'], 'scripts:dist', function() {
+        return gulp.src(['.tmp/css/*.css', '.tmp/js/*.js', '.tmp/img/*'], {base: '.tmp'})
+          .pipe(gulp.dest('dist'));
+    });
+});
+
+gulp.task('server', function() {
+    runSequence(['html', 'images:dist','styles', 'styles:dist', 'vendor', 'browserify'], 'scripts:dist', function() {
+        return gulp.src(['.tmp/css/*.css', '.tmp/js/*.js', '.tmp/img/*'], {base: '.tmp'})
+            .pipe(gulp.dest('dist'))
+            .pipe(gulp.dest(config.serverStatic));
+    });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/html.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,11 @@
+'use strict';
+
+var config = require('../config');
+var gulp = require('gulp');
+var size = require('gulp-size');
+
+gulp.task('html', function () {
+  return gulp.src('app/*.html')
+    .pipe(gulp.dest(config.dist))
+    .pipe(size());
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/images.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,37 @@
+'use strict';
+
+var config = require('../config');
+var path = require('path');
+var gulp = require('gulp');
+var cache = require('gulp-cache');
+var changed = require('gulp-changed');
+var imagemin = require('gulp-imagemin');
+var size = require('gulp-size');
+
+// Images
+gulp.task('images', function() {
+	var dest = config.dist + '/img';
+
+	return gulp.src('app/img/**/*')
+		.pipe(changed(dest)) // Ignore unchanged files
+		.pipe(imagemin()) // Optimize
+		.pipe(gulp.dest(dest));
+});
+
+
+// Images Dist
+gulp.task('images:dist', ['images'], function () {
+  var dest = config.dist + '/img';
+  return gulp.src(['app/img/**/*'], {base: path.resolve('app')+"/img"})
+    // Commenting out the cache section for now.
+    // .pipe(gulp.dest('dist'))
+    // .pipe(rev())
+    // .pipe(cache(imagemin({
+    //   optimizationLevel: 3,
+    //   progressive: true,
+    //   interlaced: false
+    // })))
+    .pipe(imagemin()) // Optimize
+    .pipe(size())
+	.pipe(gulp.dest(dest));
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/jshint.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,11 @@
+'use strict';
+
+var gulp = require('gulp');
+var jshint = require('gulp-jshint');
+
+// JSHint
+gulp.task('jshint', function () {
+  return gulp.src('app/js/**/*.js')
+    .pipe(jshint('.jshintrc'))
+    .pipe(jshint.reporter(require('jshint-stylish')));
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/less.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,43 @@
+'use strict';
+
+var config = require('../config');
+var p = require('../../package.json')
+var gulp = require('gulp');
+// var prefix = require('gulp-autoprefixer');
+var csso = require('gulp-csso');
+var less = require('gulp-less');
+var rename = require('gulp-rename');
+var size = require('gulp-size');
+var fs = require('fs')
+
+// Styles
+gulp.task('styles', function () {
+  return gulp.src('app/less/app.less')
+    // Leaving out recess support due to string interpolation missing in less v1.3 (which recess is dependent on)
+    // .pipe(recess())
+    .pipe(less({
+      style: 'expanded',
+      loadPath: [config.bower]
+    }))
+    .pipe(rename(p.name+'.css'))
+    .pipe(gulp.dest(config.dist + '/css'))
+    .pipe(size());
+});
+
+
+// Styles Dist
+gulp.task('styles:dist', function () {
+
+  return gulp.src('app/less/app.less')
+    // Leaving out recess support due to string interpolation missing in less v1.3 (which recess is dependent on)
+    // .pipe(recess())
+    .pipe(less({
+      style: 'expanded',
+      loadPath: [config.bower]
+    }))
+    // .pipe(prefix('last 1 version'))  // add vendor prefixes if necessary
+    .pipe(rename(p.name+'.min.css'))
+    .pipe(csso())  // minify css
+    .pipe(gulp.dest(config.dist + '/css'))
+    .pipe(size());
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/serve.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,27 @@
+'use strict';
+
+var config = require('../config');
+var gulp = require('gulp');
+
+// Connect
+gulp.task('connect', function () {
+  var connect = require('connect');
+  var app = connect()
+    .use(require('connect-livereload')({ port: config.livereloadPort }))
+    .use('/', connect.static('.tmp'))
+    .use('/', connect.static('app'))
+    // paths to bower_components should be relative to the current file
+    // e.g. in app/index.html you should use ../bower_components
+    .use('/lib', connect.static('lib'))
+    .use(connect.directory('app'));
+
+  require('http').createServer(app)
+    .listen(config.port)
+    .on('listening', function () {
+      console.log('Started connect web server on http://localhost:' + config.port);
+    });
+});
+
+gulp.task('serve', ['connect', 'styles'], function () {
+  require('opn')('http://localhost:' + config.port, config.browser);
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/tasks/watch.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,33 @@
+'use strict';
+
+// var config = require('../config.js');
+var gulp = require('gulp');
+var livereload = require('gulp-livereload');
+
+// Watch
+gulp.task('watch', ['connect', 'serve'], function () {
+  var server = livereload();
+    // Watch for changes in `app` folder
+    gulp.watch([
+        // 'app/jade/**/*.jade',
+        // 'app/*.html',
+        // 'app/scss/**/*.scss',
+        // 'app/scripts/**/*.js',
+        // 'app/images/**/*',
+        '.tmp/**/*'
+    ]).on('change', function(file) {
+      server.changed(file.path);
+    });
+
+    // Watch .less files
+    gulp.watch('app/less/**/*.less', ['styles']);
+
+    // Watch .js files
+    gulp.watch('app/js/**/*.js', ['browserify']);
+
+    // Watch image files
+    gulp.watch('app/img/**/*', ['images']);
+
+    // Watch .html files
+    gulp.watch('app/**/*.html', ['html']);
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulp/util/scriptFilter.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,7 @@
+var path = require('path');
+
+// Filters out non .js files. Prevents
+// accidental inclusion of possible hidden files
+module.exports = function(name) {
+    return /(\.(js)$)/i.test(path.extname(name));
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/gulpfile.js	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,15 @@
+'use strict';
+// Generated on 2015-01-11 using generator-browserify 0.4.1
+
+/**
+ * gulpfile.js
+ * ===========
+ * Rather than manage one giant configuration file responsible
+ * for creating multiple tasks, each task has been broken out into
+ * its own file in gulp/tasks. Any file in that folder gets automatically
+ * required by the loop in ./gulp/index.js (required below).
+
+ * To add a new task, simply add a new task file to gulp/tasks.
+ */
+
+require('./gulp');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/annotviz/package.json	Tue Jan 13 10:46:05 2015 +0100
@@ -0,0 +1,45 @@
+{
+  "name": "annotviz",
+  "description": "Mons annotviz",
+  "repository": "https://www.iri.centrepompidou.fr/dev/hg/mons",
+  "version": "0.0.0",
+  "engines": {
+    "node": ">=0.10.0"
+  },
+  "scripts": {
+    "dist": "NODE_ENV=production gulp dist"
+  },
+  "devDependencies": {
+    "bower": "^1.3.12",
+    "browserify": "^5.12.1",
+    "browserify-shim": "^3.7.0",
+    "connect": "^2.25.9",
+    "connect-livereload": "^0.4.0",
+    "debowerify": "^0.8.1",
+    "gulp": "3.8.7",
+    "gulp-autoprefixer": "^0.0.10",
+    "gulp-cache": "^0.2.2",
+    "gulp-changed": "^1.0.0",
+    "gulp-concat": "^2.3.5",
+    "gulp-csso": "^0.2.9",
+    "gulp-debug": "^1.0.1",
+    "gulp-imagemin": "^1.0.1",
+    "gulp-jshint": "^1.8.4",
+    "gulp-less": "^1.3.5",
+    "gulp-livereload": "^2.1.1",
+    "gulp-load-plugins": "^0.6.0",
+    "gulp-rename": "^1.2.0",
+    "gulp-rimraf": "^0.1.0",
+    "gulp-size": "^1.1.0",
+    "gulp-uglify": "^1.0.1",
+    "gulp-util": "^3.0.1",
+    "jshint-stylish": "^0.4.0",
+    "mocha": "^1.21.4",
+    "opn": "^1.0.0",
+    "partialify": "^3.1.1",
+    "run-sequence": "^1.0.2",
+    "should": "^4.0.4",
+    "vinyl-source-stream": "^0.1.1"
+  },
+  "dependencies": {}
+}
--- a/client/pianoroll/app/index.html	Mon Jan 12 17:23:05 2015 +0100
+++ b/client/pianoroll/app/index.html	Tue Jan 13 10:46:05 2015 +0100
@@ -24,7 +24,7 @@
     <pre id="log"></pre>
     <script src="/js/libs-pianoroll.js"></script>
     <script>
-    var eventCode = "mons_samedi25";
+    var eventCode = "test_1";
     </script>
     <script src="/js/pianoroll.js"></script>
     <script>
--- a/client/pianoroll/app/js/app.js	Mon Jan 12 17:23:05 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-/**
- * js/app.js
- *
- * This is a sample CommonJS module.
- * Take a look at http://browserify.org/ for more info
- */
-
-'use strict';
-
-function App() {
-  console.log('app initialized');
-}
-
-module.exports = App;
-
-App.prototype.beep = function () {
-  console.log('boop');
-};
--- a/client/pianoroll/app/js/main.js	Mon Jan 12 17:23:05 2015 +0100
+++ b/client/pianoroll/app/js/main.js	Tue Jan 13 10:46:05 2015 +0100
@@ -51,8 +51,8 @@
 
 // Init containers
 var containerList = [];
-containerList.push(new PianoRoll(uberContainer, 0, 0, prHeight1, true, pixelsPerSecond1, sceneWidth, noteColors, colorsReg, lineColor, lineInterval));
-containerList.push(new PianoRoll(uberContainer, 0, prHeight1, prHeight2, false, pixelsPerSecond2, sceneWidth, noteColors, colorsReg, lineColor, lineInterval));
+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));
 
 // Line between two containers
 var graphics = new PIXI.Graphics();
--- a/client/pianoroll/app/js/pianoroll.js	Mon Jan 12 17:23:05 2015 +0100
+++ b/client/pianoroll/app/js/pianoroll.js	Tue Jan 13 10:46:05 2015 +0100
@@ -10,7 +10,7 @@
 var PIXI = require('pixi');
 var randomColor = require('randomColor');
 
-function PianoRoll(parentContainer, xInit, yInit, height, linesDown, pixelsPerSecond, width, noteColors, colorsReg, lineColor, lineInterval){
+function PianoRoll(parentContainer, xInit, yInit, height, linesDown, pixelsPerSecond, width, noteColors, colorsReg, lineColor, lineInterval, offsetMusic, noteHeight){
     var _this = this;
     this.container = new PIXI.DisplayObjectContainer();
     this.container.position.x = xInit;
@@ -25,11 +25,17 @@
     this.colorsReg = colorsReg || {};
     this.lineColor = lineColor;
     this.lineInterval = lineInterval;
+    this.offsetMusic = offsetMusic || 0;
+    this.noteHeight = noteHeight;
 
     this.addNote = function(note, startTime, duration, velocity, canal){
         //console.log("coucou 1", note, timeFromZero, ts, velocity, pixelsPerSecond, container, prHeight);
-        var beginX = (offsetMusic + startTime) * this.pixelsPerSecond / 1000;
+        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);
@@ -80,20 +86,25 @@
 
     this.removePassedObjets = function(){
         var nbChilds = _this.container.children.length;
-        var i = 0, childIsNowDisplayed = false;
+        var i = 0, childIsNowDisplayed = false, childrenToRemove = [];
         while(i<nbChilds && !childIsNowDisplayed){
-            var child = _this.container.children[0];
+            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') {
+                continue;
+            }
             if((child.x + child.width) < (Math.abs(_this.container.x) - _this.width)){
-                _this.container.removeChild(child);
+                childrenToRemove.push(child);
                 //console.log("    remove !!!");
             }
-            else{
+            else {
                 childIsNowDisplayed = true;
                 //console.log("    childIsNowDisplayed");
             }
-            i++;
         }
+        childrenToRemove.forEach(function(child) {
+            _this.container.removeChild(child);
+        });
         //console.log("before : ", nbChilds, ", after : ", _this.container.children.length);
     };
 
--- a/utils/pianoroll-client.py	Mon Jan 12 17:23:05 2015 +0100
+++ b/utils/pianoroll-client.py	Tue Jan 13 10:46:05 2015 +0100
@@ -7,12 +7,11 @@
 
 import argparse
 import csv
-import sys
 import time
 
+import ntplib
 from twisted.internet import reactor
 from txosc import osc
-from txosc import dispatch
 from txosc import async
 
 
@@ -20,13 +19,14 @@
     """
     Example that sends UDP messages.
     """
-    def __init__(self, port, host, address, rows):
+    def __init__(self, port, host, address, rows, shift):
         self.port = port
         self.host = host
         self.client = async.DatagramClientProtocol()
         self._client_port = reactor.listenUDP(0, self.client)
         self.rows = rows
         self.address = address
+        self.shift = shift
         reactor.callLater(0, self.send_messages)
 
     def _send(self, element):
@@ -35,8 +35,10 @@
         print("Sent %s to %s:%d" % (element, self.host, self.port))
 
     def send_messages(self):
-        tc = 0
+        t0 = time.time()
         for row in self.rows:
+            if self.shift:
+                row[0] = ntplib.system_to_ntp_time(t0 + float(row[1])/10**3)
             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)
@@ -50,11 +52,12 @@
     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.')
+    parser.add_argument('-s', '--shift', dest='shift', action='store_true', required=False, help='Shift the data.', default=False)
 
     args = parser.parse_args()
 
     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))
+        app = UDPSenderApplication(9090, "127.0.0.1", "/pianoroll/%s/" % args.event, list(reader), args.shift)
 
     reactor.run()