Source: views/NotationView.js

/** @constructor */
MusicXMLAnalyzer.NotationView = function(){

	var that = {},
	context = null,
	canvas = null,
	canvasLeft = null,
	canvasTop = null,
	renderer = null,
	stave = null,

	hoveredNote = null,
	hoveredOctave = null,

	paddingTopStaves = 0,
	spaceBetweenLines = 0,

	topValsNoteElements = null,

	VEXFLOW_REST_SIGN = "r",

	/**
	 * Init function
	 * @function
     * @public
	 */
	init = function() {
		patternController = MusicXMLAnalyzer.PatternController();
		patternModel = MusicXMLAnalyzer.PatternModel();

		initCanvas();
		setTopNoteValues();
		registerListener();
	},

	/**
	 * This method inits canvas and context and sets canvas top and left to variable
	 * @function
     * @public
	 */
	initCanvas = function() {
		canvas = document.getElementById('myCanvas');
	    canvasLeft = canvas.offsetLeft;
	    canvasTop = canvas.offsetTop;

  		renderer = new Vex.Flow.Renderer(canvas,Vex.Flow.Renderer.Backends.CANVAS);

  		context = renderer.getContext();
  		stave = new Vex.Flow.Stave(10, 45, 700);
  		addClef(patternModel.getCurrentMode());
	},

	/**
	 * Method clears canvas, creates new stave and adds a new clef on mode change
	 * @function
     * @public
	 *
	 * @param {event}    event    the triggered event
	 *
	 * @param {int}      mode     the selected mode
	 */
	onChangeMode = function(event, mode) {
		context.clearRect(0, 0, canvas.width, canvas.height);
		stave = new Vex.Flow.Stave(10, 45, 700);
  		addClef(patternModel.getCurrentMode());
	},

	/**
	 * Method adds a clef according to mode
	 * @function
     * @public
	 *
	 * @param {int}    mode    current selected mode
	 */
	addClef = function(mode) {
		if (mode === 1) {
  			stave.addClef("percussion").setContext(context).draw();
  		} else {
  			stave.addClef("treble").setContext(context).draw();
  		}
	},

	/**
	 * Method registers listener to canvas
	 * @function
     * @public
	 */
	registerListener = function() {
		$("#myCanvas").on("mousemove", onMouseMoveCanvas);
		$("#myCanvas").on("click", onMouseClickCanvas);

		$(patternController).on('changed_mode', onChangeMode);
	},

	/**
	 * Method displays note elements on the canvas and get them from model
	 * @function
     * @public
	 *
	 * @param {object}    vexflowNotes    contains notes for vexflow
	 */
	renderNotes = function(vexflowNotes) {

		// delete canvas
		context.clearRect(0, 0, canvas.width, canvas.height);

		stave.setContext(context).draw();

		var voice = new Vex.Flow.Voice({
		    resolution: Vex.Flow.RESOLUTION
		});


		//easiest way to disable time-checking
		voice.setStrict(false);

		// Add notes to voice
		voice.addTickables(vexflowNotes);

		// Format and justify the notes to 700 pixels
		var formatter = new Vex.Flow.Formatter().
		    joinVoices([voice]).format([voice], 700);

		//TRIPLET
		//get beams and tuplets from model
		var beams = patternModel.getTupletArray();
		var tuplets = patternModel.getBeamArray();

		voice.draw(context, stave);

		//TRIPLET
		for(var i=0; i < tuplets.length; i++) {
			tuplets[i].setContext(context).draw();
		}

		for(var i=0; i < beams.length; i++) {
			for(var j=0; j < beams[i].length; j++) {
				beams[i][j].setContext(context).draw();
			}
		}



	},

	/**
	 * Method renders preview note on canvas at mouseover
	 * @function
     * @public
	 */
	renderVexFlowNotePreview = function() {
		// delete canvas
		context.clearRect(0, 0, canvas.width, canvas.height);
		stave.setContext(context).draw();

		// get all vexflow note elements from model which already exist
  		var vexflowNotes = patternModel.getAllVexFlowNoteElements();

  		//bugprevention: vexflow changes the position of dots
  		//for loop sets the position of all available dots to 0
  		for (var i=0;  i < vexflowNotes.length; i++) {

  			if (typeof vexflowNotes[i].modifiers[0] !== 'undefined') {
  				vexflowNotes[i].modifiers[0].dot_shiftY = 0;
  			}
  		}

  		var previewNote = null;
		var key = hoveredNote;
  		var durationContent = patternModel.getDuration4Vexflow(patternModel.getCurrentNoteDuration());
  		// to check if break is selected or not
  		// when break is selected: only key "b/4"
  		var noteName = patternModel.getCurrentNoteName();
  		var accidental = patternModel.getCurrentAccidential();
  		var rythSpec = patternModel.getCurrentNoteRythSpecial();

  		if (accidental === "#" || accidental === "b") {
			key += accidental;
		}
  		// add the preview to to notes array
  		// further down it's been removed again
  		if (noteName === "break") {
			previewNote = new Vex.Flow.StaveNote({ keys: ["b/4"],
	    						duration: durationContent + VEXFLOW_REST_SIGN,
	    						auto_stem: true });
		} else {
			var keys = key + "/" + hoveredOctave;
			if (patternModel.getCurrentMode() === 1) {
				if (durationContent === "w" || durationContent === "h" || durationContent === "wd" || durationContent === "hd") {
					keys += "/d0";
				} else {
					keys += "/d2";
				}
			}
			previewNote = new Vex.Flow.StaveNote({ keys: [keys],
	    						duration: durationContent,
	    						auto_stem: true });
		}

		if (accidental === "#" || accidental === "b") {
			previewNote.addAccidental(0, new Vex.Flow.Accidental(accidental));
		}

		if (rythSpec === "dotted") {
			previewNote.addDot(0);
		}

		// set color of preview note
		previewNote.color = "#8B8B8B";

  		vexflowNotes.push(previewNote);

		var voice = new Vex.Flow.Voice({
		    resolution: Vex.Flow.RESOLUTION
		});

		//disable time-checking
		voice.setStrict(false);
		// Add notes to voice
		voice.addTickables(vexflowNotes);

		// Format and justify the notes to 700 pixels
		var formatter = new Vex.Flow.Formatter().
		    joinVoices([voice]).format([voice], 700);

	    //TRIPLET
		//get beams and tuplets from model
		var beams = patternModel.getTupletArray();
		var tuplets = patternModel.getBeamArray();

		// Render voice
		voice.draw(context, stave);

		//TRIPLET
		for(var i=0; i < tuplets.length; i++) {
			tuplets[i].setContext(context).draw();
		}

		for(var i=0; i < beams.length; i++) {
			for(var j=0; j < beams[i].length; j++) {
				beams[i][j].setContext(context).draw();
			}
		}

		//delete last array entry
		vexflowNotes.pop();

	},
	/**
	 * Method calculates the position of the notes
	 * @function
     * @public
	 */
	setTopNoteValues = function() {
		spaceBetweenLines = (canvas.height/16);
	},

	/**
	 * Method handels the mouseover event on canvas
	 * @function
     * @public
	 *
	 * @param 	{Event} 	The triggered event
	 */
	onMouseMoveCanvas = function(event) {

		var x = event.pageX - canvasLeft,
	        y = event.pageY - canvasTop;

    	//check if cursor is hover a existing note position
    	//if yes the method returns val and not null

    	//when rhythm mode -> just note b/4 is displayed when hovering
    	if (patternModel.getCurrentMode() === 1) {
    		hoveredOctave = "4";
    		hoveredNote = "b";
    		renderVexFlowNotePreview("b");
    	}
    	else if (checkHorizontalArea(y)) {
    		// call method to render note preview with given noteName
    		renderVexFlowNotePreview();
    	}
	},

	/**
	 * Method handels mouseclick event on canvas
	 * @function
     * @public
	 */
	onMouseClickCanvas = function() {

		var noteName = patternModel.getCurrentNoteName();

		var hoveredArea = null;

		if (noteName === "break") {
			hoveredArea = "break/4";
			patternController.addNoteByCanvasClick(hoveredArea);
		} else {
			hoveredArea = hoveredNote + "/" + hoveredOctave;
			patternController.addNoteByCanvasClick(hoveredArea);
		}

	},

	/**
	 * Method checks on which horitiontal position the mousecursor is and saves the corresponding note
	 * @function
     * @public
	 *
	 * @param {int}    		y    		 horizontal position of cursor
	 *
	 * @return {string}     hoverdnote   hovered note according to calculated cursor position
	 */
	checkHorizontalArea = function(y) {

		if (y > spaceBetweenLines * 1.25 && y <= spaceBetweenLines * 1.75) {
			hoveredNote = "b";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 1.75 && y <= spaceBetweenLines * 2.25) {
			hoveredNote = "a";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 2.25 && y <= spaceBetweenLines * 2.75) {
			hoveredNote = "g";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 2.75 && y <= spaceBetweenLines * 3.25) {
			hoveredNote = "f";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 3.25 && y <= spaceBetweenLines * 3.75) {
			hoveredNote = "e";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 3.75 && y <= spaceBetweenLines * 4.25) {
			hoveredNote = "d";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 4.25 && y <= spaceBetweenLines * 4.75) {
			hoveredNote = "c";
			hoveredOctave = "6";
		} else if (y > spaceBetweenLines * 4.75 && y <= spaceBetweenLines * 5.25) {
			hoveredNote = "b";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 5.25 && y <= spaceBetweenLines * 5.75) {
			hoveredNote = "a";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 5.75 && y <= spaceBetweenLines * 6.25) {
			hoveredNote = "g";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 6.25 && y <= spaceBetweenLines * 6.75) {
			hoveredNote = "f";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 6.75 && y <= spaceBetweenLines * 7.25) {
			hoveredNote = "e";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 7.25 && y <= spaceBetweenLines * 7.75) {
			hoveredNote = "d";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 7.75 && y <= spaceBetweenLines * 8.25) {
			hoveredNote = "c";
			hoveredOctave = "5";
		} else if (y > spaceBetweenLines * 8.25 && y <= spaceBetweenLines * 8.75) {
			hoveredNote = "b";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 8.75 && y <= spaceBetweenLines * 9.25) {
			hoveredNote = "a";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 9.25 && y <= spaceBetweenLines * 9.75) {
			hoveredNote = "g";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 9.75 && y <= spaceBetweenLines * 10.25) {
			// c1
			hoveredNote = "f";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 10.25 && y <= spaceBetweenLines * 10.75) {
			hoveredNote = "e";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 10.75 && y <= spaceBetweenLines * 11.25) {
			hoveredNote = "d";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 11.25 && y <= spaceBetweenLines * 11.75) {
			hoveredNote = "c";
			hoveredOctave = "4";
		} else if (y > spaceBetweenLines * 11.75 && y <= spaceBetweenLines * 12.25) {
			hoveredNote = "b";
			hoveredOctave = "3";
		} else if (y > spaceBetweenLines * 12.25 && y <= spaceBetweenLines * 12.75) {
			hoveredNote = "a";
			hoveredOctave = "3";
		} else if (y > spaceBetweenLines * 12.75 && y <= spaceBetweenLines * 13.25) {
			hoveredNote = "g";
			hoveredOctave = "3";
		} else if (y > spaceBetweenLines * 13.25 && y <= spaceBetweenLines * 13.75) {
			hoveredNote = "f";
			hoveredOctave = "3";
		} else if (y > spaceBetweenLines * 13.75 && y <= spaceBetweenLines * 14.25) {
			hoveredNote = "e";
			hoveredOctave = "3";
		} else if (y > spaceBetweenLines * 14.25 && y <= spaceBetweenLines * 14.75) {
			hoveredNote = "d";
			hoveredOctave = "3";
		} else if (y > spaceBetweenLines * 14.75 && y <= spaceBetweenLines * 15.25) {
			hoveredNote = "c";
			hoveredOctave = "3";
		}

		return hoveredNote;
	},

	/**
	 * Method to clear canvas and redraw staves
	 * @function
     * @public
	 */
	clearCanvas = function() {
		// clear canvas and redraw staves
		context.clearRect(0, 0, canvas.width, canvas.height);
		stave.setContext(context).draw();
	};


	that.init = init;
	that.renderNotes = renderNotes;
	that.clearCanvas = clearCanvas;

	return that;
}