/** @constructor */
MusicXMLAnalyzer.ScoreView = function(){
var that = {},
$scoreValue = null,
$partSelectorContainer = null,
/**
* Init function
* @function
* @public
*/
init = function() {
console.info("MusicXMLAnalyzer.ScoreView.init");
$scoreValue = $('#scoreValue');
scoreData = JSON.parse($scoreValue.val());
console.log("scoreData: ",scoreData.measures);
// initialize canvas
var canvas = document.createElement('canvas');
canvas.id = "canvas";
canvas.className = "canvas";
canvas.width = 970;
canvas.height = Math.ceil(scoreData.measures.length / 2) * 180;
var canvasContainer = document.getElementById('canvasContainer');
canvasContainer.innerHTML = "";
canvasContainer.appendChild(canvas);
var renderer = new Vex.Flow.Renderer(canvas, Vex.Flow.Renderer.Backends.CANVAS);
var context = renderer.getContext();
var stave = new Vex.Flow.Stave(10, 0, 700);
stave.addClef("treble").setContext(context).draw();
var vexflowMeasures = generateVexflowNotes(scoreData, true);
renderNotes(vexflowMeasures, canvas, renderer, context, stave, false);
$partSelector = $('#partSelector');
$partSelector.on('change', onPartSelectorChange);
},
/**
* Callback function for part selector on change event
* @function
* @public
*/
onPartSelectorChange = function(event) {
var url = $partSelector.val();
window.location.href = url;
},
/**
* Method renders result extract
* @function
* @public
*
* @param {array} measures array containing the measures of result extract
* @param {object} canvas the canvas proportions
* @param {object} renderer the vexflow renderer
* @param {canvas} context the canvas context
* @param {object} stave the note stave
* @param {object} score the user score
*/
renderNotes = function(measures, canvas, renderer, context, stave, score) {
// clear canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#000000';
var voice = new Vex.Flow.Voice({
num_beats: 4,
beat_value: 4,
resolution: Vex.Flow.RESOLUTION
});
// disable strict timing
voice.setStrict(false);
var tieStart = null;
var tieStop = null;
var ties = [];
for (var i = 0; i < measures.length; i++) {
// calculate x & y coordinates and width of the current measure
var x, y, width;
width = 480;
height = 180;
padding = 5;
if (i%2 === 0) {
x = padding;
y = i * (height / 2);
} else {
x = padding + width;
y = (i - 1) * (height / 2);
}
if (score) {
width = 690;
height = 120;
}
// Add offset from top to center vertical
y += 25;
staveBar = new Vex.Flow.Stave(x, y, width); // generate new stave for measure
if (i%2 === 0) {
staveBar.addClef("treble"); // add clef to every measure starting in a new line
}
if (measures[i].time_signature) {
staveBar.addTimeSignature(measures[i].time_signature); // add time signature if changed
}
if (i > 0 && i < measures.length-1) {
staveBar.setBegBarType(Vex.Flow.Barline.type.SINGLE); // set measure bar line
}
if (i === measures.length-1) {
staveBar.setEndBarType(Vex.Flow.Barline.type.END); // set double measure bar line at last measure
}
// creating ties between notes
for (var j = 0; j < measures[i].notes.length; j++) {
// ties
if (measures[i].ties) {
if (measures[i].ties[j] !== false && measures[i].ties[j] !== undefined) {
if (measures[i].ties[j].indexOf("stop") > -1) {
tieStop = measures[i].notes[j];
if (tieStart !== null) {
var tie = new Vex.Flow.StaveTie({ first_note: tieStart, last_note: tieStop, first_indices: [0], last_indices: [0] });
ties.push(tie);
tieStart = null;
tieStop = null;
}
}
if (measures[i].ties[j].indexOf("start") > -1) {
tieStart = measures[i].notes[j];
}
}
}
}
// tuplets
var tuplets = [];
for (var j = 0; j < measures[i].notes.length; j++) {
if (measures[i].tuplets && measures[i].tuplets[j]) {
if (measures[i].tuplets[j].toString() !== 'false' && measures[i].tuplets[j].toString() !== 'undefined') {
var tupletNotes = measures[i].notes.slice(j, (j + parseInt(measures[i].tuplets[j])));
var tupletLocation = tupletNotes[0].stem.stem_direction;
var tuplet = new Vex.Flow.Tuplet(tupletNotes);
tuplet.setTupletLocation(tupletLocation);
tuplets.push(tuplet);
j = (j + parseInt(measures[i].tuplets[j])-1);
}
}
}
// draw measure bar line
staveBar.setContext(context).draw();
// draw measure with notes
var beams = Vex.Flow.Beam.generateBeams(measures[i].notes);
Vex.Flow.Formatter.FormatAndDraw(context, staveBar, measures[i].notes);
beams.forEach(function(beam) {
beam.setContext(context).draw();
});
// draw tuplets
for (var t = 0; t < tuplets.length; t++) {
tuplets[t].setContext(context).draw();
}
}
for (var t2 = 0; t2 < ties.length; t2++) {
ties[t2].setContext(context).draw();
}
},
/**
* Method generates vexflow notes
* @function
* @public
*
* @param {object} score the user score
* @param {object} result search result
*/
generateVexflowNotes = function(score, result) {
var measures = [];
for (var i = 0; i < score.measures.length; i++) {
for (var j = 0; j < score.measures[i].notes.length; j++) {
if (score.measures[i].notes[j].pitch && score.measures[i].notes[j].pitch.beam) {
score.measures[i].notes[j].pitch.tuplet = "3";
} else if(score.measures[i].notes[j].pitch) {
score.measures[i].notes[j].pitch.tuplet = false;
}
}
var notes = [];
noteCounter = 0;
// var beams = [];
var ties = [];
var tuplets = [];
var time_signature = score.measures[i].time_signature;
if (score.measures[i].notes) {
for (var j = 0; j < score.measures[i].notes.length; j++) {
var note;
if (score.measures[i].notes[j].type === "note") {
if (!score.measures[i].notes[j].pitch.chord) {
// determine note variables
var step = score.measures[i].notes[j].pitch.step;
var octave = score.measures[i].notes[j].pitch.octave;
var alter = score.measures[i].notes[j].pitch.alter;
var keys = [getVexflowKey(step, octave, alter )];
var noteTies = [false];
if (score.measures[i].notes[j].pitch.ties) {
noteTies = score.measures[i].notes[j].pitch.ties;
}
var tuplet = false;
if (score.measures[i].notes[j].pitch.tuplet) {
tuplet = score.measures[i].notes[j].pitch.tuplet;
}
var type = score.measures[i].notes[j].pitch.type;
var durationType = 0;
if (score.measures[i].notes[j].pitch.dot) {
durationType = 2;
}
var noteDuration = getVexflowDuration(type, durationType);
note = new Vex.Flow.StaveNote({ keys: keys, duration: noteDuration, auto_stem: true });
switch (alter) {
case -2: note.addAccidental(0, new Vex.Flow.Accidental("bb")); break;
case -1: note.addAccidental(0, new Vex.Flow.Accidental("b")); break;
case 1: note.addAccidental(0, new Vex.Flow.Accidental("#")); break;
case 2: note.addAccidental(0, new Vex.Flow.Accidental("#")); break;
}
if (score.measures[i].notes[j].pitch.dot) {
note.addDotToAll();
}
ties[noteCounter] = noteTies;
tuplets[noteCounter] = tuplet;
notes.push(note);
noteCounter++;
}
} else if (score.measures[i].notes[j].type === "rest") {
var durationType = 1; // rests type is 1
var noteDuration = getVexflowDuration(score.measures[i].notes[j].duration, durationType);
note = new Vex.Flow.StaveNote({ keys: ["b/4"], duration: noteDuration });
if (score.measures[i].notes[j].dot) {
note.addDotToAll();
}
ties[noteCounter] = [false];
notes.push(note);
noteCounter++;
} else if (score.measures[i].notes[j].type === "unpitched") {
var step = score.measures[i].notes[j].pitch.step;
var octave = score.measures[i].notes[j].pitch.octave;
var alter = score.measures[i].notes[j].pitch.alter;
var keys = [getVexflowKey(step, octave, alter ) + "/d2"];
var noteTies = [false];
if (score.measures[i].notes[j].pitch.ties) {
noteTies = score.measures[i].notes[j].pitch.ties;
}
var type = score.measures[i].notes[j].pitch.type;
var durationType = 0;
if (score.measures[i].notes[j].pitch.dot) {
durationType = 2;
}
var noteDuration = getVexflowDuration(type, durationType);
note = new Vex.Flow.StaveNote({ keys: keys, duration: noteDuration, auto_stem: true});
if (score.measures[i].notes[j].pitch.dot) {
note.addDotToAll();
}
ties[noteCounter] = noteTies;
notes.push(note);
noteCounter++;
}
}
}
measures.push({ notes: notes, ties: ties, tuplets: tuplets, time_signature: time_signature, score: score });
}
return measures;
},
/**
* Method converts duration from string to number values as 1/64
* @function
* @public
*
* @param {string} duration string of note duration
*
* @return {number} duration value as number
*
*/
getDurationIn64th = function(duration) {
switch (duration) {
case "whole":
return 64; break;
case "half":
return 32; break;
case "quarter":
return 16; break;
case "eighth":
return 8; break;
case "16th":
return 4; break;
case "32nd":
return 2; break;
case "64th":
return 1; break;
default:
return 0; break;
}
},
/**
* Method returns the note duration in vexflow style
* @function
* @public
*
* @param {string} duration duration of note
* @param {number} type type of note (0 = note, 1 = rest, 2 = dotted note)
*
* @return {string} duration for vexflow
*/
getVexflowDuration = function(duration, type) {
switch (duration) {
case "whole":
switch (type) {
case 0: return "w"; break; // 0 is normal note
case 1: return "wr"; break; // 1 is a rest
case 2: return "wd"; break; // 2 is a dotted note
}
break;
case "half":
switch (type) {
case 0: return "h"; break;
case 1: return "hr"; break;
case 2: return "hd"; break;
}
break;
case "quarter":
switch (type) {
case 0: return "q"; break;
case 1: return "qr"; break;
case 2: return "qd"; break;
}
break;
case "eighth":
switch (type) {
case 0: return "8"; break;
case 1: return "8r"; break;
case 2: return "8d"; break;
}
break;
case "16th":
switch (type) {
case 0: return "16"; break;
case 1: return "16r"; break;
case 2: return "16d"; break;
}
break;
case "32nd":
switch (type) {
case 0: return "32"; break;
case 1: return "32r"; break;
case 2: return "32d"; break;
}
break;
case "64th":
switch (type) {
case 0: return "64"; break;
case 1: return "64r"; break;
case 2: return "64d"; break;
}
break;
default:
switch (type) {
case 0: return "128"; break;
case 1: return "128r"; break;
case 2: return "128d"; break;
}
break;
}
},
/**
* Method returns key description for vexflow
* @function
* @public
*
* @param {string} step note name
* @param {string} octave octave number
* @param {string} alter accidential of the note
*
* @return {string} key description for vexflow
*/
getVexflowKey = function(step, octave, alter) {
key = step.toLowerCase();
switch (alter) {
case -2:
key += "bb"; break;
case -1:
key += "b"; break;
case 1:
key += "#"; break;
case 2:
key += "##"; break;
default:
break;
}
key += "/";
key += octave;
return key;
};
that.init = init;
return that;
}