freakimkaefig/Music-XML-Analyzer

View on GitHub
public/js/views/ScoreView.js

Summary

Maintainability
F
2 wks
Test Coverage
/** @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;
}