client/annotviz/app/js/pianoroll.js
author ymh <ymh.work@gmail.com>
Tue, 20 Jan 2015 11:57:44 +0100
changeset 98 72d767c5142d
parent 96 f58715468f1e
child 103 123a611c7903
permissions -rw-r--r--
refactor and make work annotsroll

/**
* js/pianoroll.js
*
* pianoroll basic component
*
*/

'use strict';


var PIXI = require('pixi');
var randomColor = require('randomColor');
var _ = require('lodash');

var NTP_EPOCH_DELTA = 2208988800; //c.f. RFC 868

function PianoRoll(options) {
    var _this = this;
    this.container = new PIXI.DisplayObjectContainer();
    this.container.x = options.xInit;
    this.container.y = options.yInit;
    options.parentContainer.addChild(this.container);

    var orientation = options.orientation;
    var isHorizontal = (orientation !== 'vertical');

    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();

    var started = false;

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

    //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;
            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);
            }
        }
        return color;
    };

    this.getNoteRect = function(x, y, color, alpha, width, height) {
        var graphics = new PIXI.Graphics();
        graphics.beginFill(color, alpha);
        graphics.drawRect(0, 0, width, height);
        graphics.endFill();
        graphics.x = x;
        graphics.y = y;
        graphics.width = width;
        graphics.height = height;
        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;
        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);
            }
            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 x = -this.container.x;
        var y = this.linesDown ? this.height - 20 : 0;

        var graphics = new PIXI.Graphics()
            .beginFill(0xFFFF00)
            .lineStyle(1, this.lineColor)
            .moveTo(0, 0)
            .lineTo(0, 20)
            .endFill();
        graphics.x = x;
        graphics.y = y;
        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);
        if(isHorizontal) {
            t.x = x + 2;
            t.y = this.linesDown ? this.height - 15 : 2;
        }
        else {
            t.rotation = -Math.PI/2;
            t.x = x ;
            t.y = this.linesDown ? this.height - 2 : t.width + 2;
        }
        this.container.addChild(t);
    };

    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(){
        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);
        });
    };

    this.start = function() {
        if(!started) {
            this.startTs = Date.now();
            this.addLine();
            started = true;
        }
        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);
        clearInterval(this.verticalLinesInterval);
        clearInterval(this.cleanInterval);
    };


}

module.exports = PianoRoll;