freakimkaefig/Music-XML-Analyzer

View on GitHub
public/js/controllers/PatternController.js

Summary

Maintainability
F
6 days
Test Coverage
/** @constructor */
MusicXMLAnalyzer.PatternController = function() {
    var that = {},
    once = true,
    once2 = true,
    stop = false,
    tonika = { 'C':0, 'D':2, 'E':4, 'F':5, 'G':7, 'A':9, 'B':11 },

    /**
     * Init method of PatternController
     * @function
     * @public
     */
    init = function() {
        patternView = MusicXMLAnalyzer.PatternView();
        patternView.init();

        notationView = MusicXMLAnalyzer.NotationView();
        notationView.init();

        patternModel = MusicXMLAnalyzer.PatternModel();
        patternModel.init();

        $(patternModel).on('patternChange', onPatternChange);
        $(patternModel).on('updateNotationView', onNotationViewUpdate);
        $(patternModel).on('changeSelectedNoteName', onNoteNameSelectionChange);
        $(patternModel).on('changeSelectedOctave', onOctaveSelectionChange);
        $(patternModel).on('changeSelectedAccidential', onAccidentialSelectionChange);
        $(patternModel).on('changeSelectedDuration', onDurationSelectionChange);
        $(patternModel).on('changeSelectedSpecRyth', onSpecRythSelectionChange);
        $(patternModel).on('startTripletEnterMode', onTripletEnterModeStart);
        $(patternModel).on('endTripletEnterMode', onTripletEnterModeEnd);
        $(patternModel).on('changeViewToCurrentMode', onViewChangedToCurrentMode);
        $(patternModel).on('clearCanvas', onCanvasClear);

        MIDI.loadPlugin({
            soundfontUrl: "../../libs/midijs/soundfont/",
            instrument: "acoustic_grand_piano",
            callback: function() {
            }
        });

        $searchPatternButton = $('#searchPatternButton');
        $searchPatternButton.prop('disabled', true);
        $playPattern = $('#playPattern');
        $stopPattern = $('#stopPattern');

        $stopPattern.prop('disabled', true);

        $playPattern.click(function(){
            stop = false;
            once2 = true;
            once = true;
            playPattern();
            $playPattern.prop('disabled', true);
            $stopPattern.prop('disabled', false);
        });

        $stopPattern.click(function(){
            stop = true;
            $playPattern.prop('disabled', false);
            $stopPattern.prop('disabled', true);
        });
    },

    /**
     * Function to get Duration of Notes
     * @function
     * @public
     *
     * @param     {string}     type     The note type
     *
     * @retrun     {float}     The duration of the note as float
     */
    getDuration = function(type){
        var duration;

        if (type == "whole"){
            duration = 1;
        } else if (type === "half") {
            duration = 0.5;
        } else if (type === "quarter") {
            duration = 0.25;
        } else if (type === "eighth") {
            duration = 0.125;
        } else if (type === "16th") {
            duration = 0.0625;
        } else if (type === "32nd") {
            duration = 0.03125;
        } else if (type === "64th") {
            duration = 0.015625;
        }

        return duration;
    },

    /**
     * Method to get MIDI-Values
     * @function
     * @public
     *
     * @param     {string}     step     The note step
     * @param     {string}     octave     The notes octave
     * @param     {string}     alter     The alter value
     *
     * @return     {number}     The MIDI value
     */
    getMidiValue = function(step, octave, alter) {
        return (parseInt(octave) * 12) + (tonika[step] + parseInt(alter));
    },

    /**
    * Method to play the created Pattern
     * @function
     * @public
    */
    playPattern = function() {
        var notesToBePlayed = [];

        //get notes of current extract:
        var currentPatternNotes = patternModel.getAllNoteElements();
        console.log("notes: ",currentPatternNotes);
        MIDI.setVolume(0, 127);

        //determine MIDI values for currentPatternNotes
        for(var i = 0; i < currentPatternNotes.length; i++){
            for(var j = 0; j < currentPatternNotes[i].notes.length; j++){
                // check if rest
                if(currentPatternNotes[i].notes[j].type === 'rest'){
                    var rest = currentPatternNotes[i].notes[j];
                    var restDuration = getDuration(currentPatternNotes[i].notes[j].duration);
                    notesToBePlayed.push({'note': 0, 'noteDuration': restDuration});
                }
                else if(currentPatternNotes[i].notes[j].type === 'note'){
                    var note = currentPatternNotes[i].notes[j];
                    var noteDuration = (getDuration(note.pitch.type) !== undefined ? getDuration(note.pitch.type) : 0.25);
                    var noteStep = (note.pitch.step !== undefined ? note.pitch.step : 'C');
                    var noteOctave = (note.pitch.octave !== undefined ? note.pitch.octave : 4);
                    var noteAlter = (note.pitch.alter !== undefined ? note.pitch.alter : 0);
                    var noteBeam = (note.pitch.beam !== undefined ? note.pitch.beam : false);

                    if (note.pitch.dot) {
                        noteDuration += 0.5 * noteDuration;
                    }

                    var midiNote = getMidiValue(noteStep, noteOctave, noteAlter);
                    notesToBePlayed.push({'note': midiNote, 'noteDuration': noteDuration, 'noteBeam' : noteBeam});
                }
            }
        }

        i = 0;
        var playTune = function() {

            if(i < notesToBePlayed.length){
                var note = notesToBePlayed[i].note;
                // how hard the note gets hit
                var velocity = 100;
                // delay is set to fix value
                var delay = 0;
                var timeout = 0;
                if(!once){
                    timeout = notesToBePlayed[i-1].noteDuration*2000;
                    if(notesToBePlayed[i-1].noteBeam === "begin" || notesToBePlayed[i-1].noteBeam === "continue" ||
                        notesToBePlayed[i-1].noteBeam === "end") {
                        timeout = (timeout * 2) / 3;
                    }
                }
                once = false;

                setTimeout(function(){
                    if(stop){
                        i = notesToBePlayed.length;
                    }
                    else{
                        if(i === notesToBePlayed.length -1){
                                MIDI.noteOn(0, note, velocity, delay);
                                MIDI.noteOff(0, note, delay + 0.75);
                        }
                        else{
                            MIDI.noteOn(0, note, velocity, delay);
                            MIDI.noteOff(0, note, delay + 0.75);
                        }
                        i++;
                    }
                    // recursively call playTune()
                    playTune();
                 }, timeout);
            }
            // else when finished - reset play&stop buttons after 1.5 sec
            else{
                setTimeout(function(){

                    $playPattern.prop('disabled', false);
                    $stopPattern.prop('disabled', true);
                }, 1500);
            }
        };
        if(once2){
            once2 = false;
            playTune();
        }
    },

    /**
     * Method updates notes in notationView
     * @function
     * @public
     *
     * @param {event}                         event            update event
     * @param {Array.<Vex.Flow.StaveNote>}     vexflowNotes     The current notes
     */
    onNotationViewUpdate = function(event, vexflowNotes) {
        notationView.renderNotes(vexflowNotes);
    },

    /**
     * Method updates note selection
     * @function
     * @public
     *
     * @param {event}     event                update event
     * @param {string}     selectedNoteName     the selected note name
     */
    onNoteNameSelectionChange = function(event, selectedNoteName) {
        patternView.setNoteNameActive(selectedNoteName);
    },

    /**
     * Method updates octave selection
     * @function
     * @public
     *
     * @param {event}     event            update event
     * @param {string}     selectedOctave     the selected octave
     */
    onOctaveSelectionChange = function(event, selectedOctave) {
        patternView.setOctaveActive(selectedOctave);
    },

    /**
     * Method updates accidential selection
     * @function
     * @public
     *
     * @param {event}     event            update event
     * @param {string}     selectedAcc     the selected accidential
     */
    onAccidentialSelectionChange = function(event, selectedAcc) {
        patternView.setAccidentialActive(selectedAcc);
    },

    /**
     * Method updates duration selection
     * @function
     * @public
     *
     * @param {event}     event            update event
     * @param {string}     selectedDur     the selected duration
     */
    onDurationSelectionChange = function(event, selectedDur) {
        patternView.setDurationActive(selectedDur);
    },

    /**
     * Method updates rhythm selection
     * @function
     * @public
     *
     * @param {event}     event                update event
     * @param {string}     selectedSpecRyth     the selected rhythmic special
     */
    onSpecRythSelectionChange = function(event, selectedSpecRyth) {
        patternView.setSpecRythActive(selectedSpecRyth);
    },

    /**
     * Method changes current mode
     * @function
     * @public
     *
     * @param {number}    val    mode name
     */
    changeMode = function(val) {
        patternModel.setCurrentMode(val);
        $(that).trigger('changed_mode', [val]);
    },

    /**
     * Method changes current note
     * @function
     * @public
     *
     * @param {string}    val    note name
     */
    changeNote = function(val) {
        patternModel.setCurrentNoteName(val);
    },

    /**
     * Method changes current accidential
     * @function
     * @public
     *
     * @param {string}    val    accidential name
     */
    changeAccidential = function(val) {
        patternModel.setCurrentAccidential(val);
    },

    /**
     * Method changes current note duration
     * @function
     * @public
     *
     * @param {string}    val    note duration
     */
    changeDuration = function(val) {
        patternModel.setCurrentNoteDuration(val);
    },

    /**
     * Method changes current special rhythm
     * @function
     * @public
     *
     * @param {string}    val    special rhythm
     */
    changeSpecialRyth = function(val) {
        patternModel.setCurrentNoteRythSpecial(val);
    },

    /**
     * Method changes current octave
     * @function
     * @public
     *
     * @param {string}    val    current octave
     */
    changeOctave = function(val) {
        patternModel.setCurrentOctave(val);
    },

    /**
     * Method adds a note to pattern by click on button if pattern.length < 12
     * @function
     * @public
     */
    addNote = function() {
        if(patternModel.getPatternLength() < 12){
            patternModel.addNoteElement();
        }
    },

    /**
     * Method adds a note to pattern by click on canvas if pattern.length < 12
     * @function
     * @public
     *
     * @param {Vex.Flow.StavNote}     note     the note that should be added
     */
    addNoteByCanvasClick = function(note) {
        if(patternModel.getPatternLength() < 12){
            patternModel.addNoteElementByCanvasClick(note);
        }
    },

    /**
     * Method clears the canvas
     * @function
     * @public
     */
    onCanvasClear = function() {
        notationView.clearCanvas();
    },

    /**
     * Method removes last element on canvas
     * @function
     * @public
     */
    removeLastNote = function() {
        patternModel.removeLastNoteElement();
    },

    /**
     * Method enables triplet input
     * @function
     * @public
     */
    onTripletEnterModeStart = function() {
        patternView.startTripletEnterMode();
    },

    /**
     * Method ends triplet input
     * @function
     * @public
     */
    onTripletEnterModeEnd = function() {
        patternView.endTripletEnterMode();
    },

    /**
     * Method returns the current mode
     * @function
     * @public
     *
     * @return {string}    currentMode
     */
    getCurrentMode = function(){
        return patternModel.getCurrentMode();
    },

    /**
     * Method changes state of SearchPatternButton
     * @function
     * @public
     *
     * @param {event}    event         The triggered event
     * @param {object}   pattern     The current configured patter
     */
    onPatternChange = function(event, pattern) {
        patternView.setPatternValue(JSON.stringify(pattern));
        if(pattern.length != 0){
            if(pattern[0].notes.length >= 12){
                // enable search button
                $searchPatternButton.prop('disabled', false);
            }
            else if(pattern[0].notes.length >= 2 && pattern[0].notes.length < 12){
                // enable search button
                $searchPatternButton.prop('disabled', false);
            }
            else if(pattern[0].notes.length < 2){
                // disable search button
                $searchPatternButton.prop('disabled', true);
            }
        }
    },

    /**
     * Method changes current mode
     * @function
     * @public
     *
     * @param {event}   event     The triggered event
     * @param {int}     mode     The selected mode
     */
    onViewChangedToCurrentMode = function(event, mode) {
        switch(mode) {
            //sound sequence
            case 0:
            patternView.setToSoundSequenceMode();
                break;
            //rhythm
            case 1:
            patternView.setToRhythmMode();
                break;
            //melody
            case 2:
            patternView.setToMelodyMode();
                break;
        }
    };

    that.init = init;
    that.changeMode = changeMode;
    that.changeNote = changeNote;
    that.changeAccidential = changeAccidential;
    that.changeDuration = changeDuration;
    that.changeSpecialRyth = changeSpecialRyth;
    that.changeOctave = changeOctave;
    that.addNote = addNote;
    that.addNoteByCanvasClick = addNoteByCanvasClick;
    that.removeLastNote = removeLastNote;
    that.getCurrentMode = getCurrentMode;

    return that;
}