Source: models/PatternModel.js

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

	var that = {},
	noteElements = [],
	noteElements4VexFlow = [],

	curMode = 2,
	curName = "c",
	curAccidential = "none",
	curDuration = "quarter",
	curRythSpec = "none",
	curOctave = "4",
	VEXFLOW_REST_SIGN = "r",
	first = true,

	tripletCurrentAmount = 0,
	tripletEndPositions = [],
	tupletArray = [],
	beamArray = [],

	tripletEnterMode = false,
	noteElementAccidential = 0,
	isDot = false,
	beamVal = false,

	/**
	 * Init method of PatternModel
	 * @function
     * @public
	 */
	init = function() {

	},

	/**
	 * Calls method to set the current Mode active
	 * @function
     * @public
	 *
	 * @param {number}    the current mode as number 0 (sound sequence), number 1 (rhythm) and number 2 (melody)
	 *
	 */
	setCurrentMode = function(mode) {
		if (curMode != mode) {
			emptyNoteArrays();
			$(that).trigger('clearCanvas');
		}

		curMode = mode;

		switch(curMode) {
			case 0:
				setDefaultValsForSoundSequenceMode();
				break;
			case 1:
				setDefaultValsForRhythmMode();
				break;
			case 2:
				setDefaultValsForMelodyMode();
				break;
		}
		//update view
		$(that).trigger('changeViewToCurrentMode', curMode);

	},

	/**
	 * This method empties the notes arrays and sets the val first to true.
	 * @function
     * @public
	 */
	emptyNoteArrays = function() {
		noteElements = [];
		noteElements4VexFlow = [];
		first = true;
	},

	/**
	 * Returns the current mode
	 * @function
     * @public
	 *
	 * @return {number}    The current mode
	 *
	 */
	getCurrentMode = function() {
		return curMode;
	},

	/**
	 * Sets the current note name
	 * @function
     * @public
	 *
	 * @param {string}    the current note name
	 *
	 */
	setCurrentNoteName = function(noteName) {
		if(getCurrentMode() === 1 && noteName !== 'break'){

			curOctave = 4;
			curName = 'b';
		}else{

			curName = noteName;
		}
	},

	/**
	 * Returns the current note name
	 * @function
     * @public
	 *
	 * @return {string}    The current note name
	 *
	 */
	getCurrentNoteName = function() {
		return curName;
	},

	/**
	 * Sets the current accidential
	 * @function
     * @public
	 *
	 * @param {string}    the current accidential
	 *
	 */
	setCurrentAccidential = function(accidential) {
		curAccidential = accidential;
	},

	/**
	 * Returns the current accidential
	 * @function
     * @public
	 *
	 * @return {string}    The current accidential
	 *
	 */
	getCurrentAccidential = function() {
		return curAccidential;
	},

	/**
	 * Sets the current note duration
	 * @function
     * @public
	 *
	 * @param {string}    the current note duration
	 *
	 */
	setCurrentNoteDuration = function(noteDuration) {
		curDuration = noteDuration;
	},

	/**
	 * Returns the current note duration
	 * @function
     * @public
	 *
	 * @return {string}    The current note duration
	 *
	 */
	getCurrentNoteDuration = function() {
		return curDuration;
	},

	/**
	 * Sets the current rhythmic special
	 * @function
     * @public
	 *
	 * @param {string}    the current rhythmic special
	 *
	 */
	setCurrentNoteRythSpecial = function(rythSpec) {
		curRythSpec = rythSpec;
	},

	/**
	 * Returns the current rhythmic special which can be a dot or triplet
	 * @function
     * @public
	 *
	 * @return {string}    The current rhythmic special
	 *
	 */
	getCurrentNoteRythSpecial = function() {
		return curRythSpec;
	},

	/**
	 * Sets the current octave
	 * @function
     * @public
	 *
	 * @param {string}    the current octave
	 *
	 */
	setCurrentOctave = function(octave) {
		curOctave = octave;
	},

	/**
	 * Returns the current octave
	 * @function
     * @public
	 *
	 * @return {string}    The current octave
	 *
	 */
	getCurrentOctave = function() {
		return curOctave;
	},

	/**
	 * This method sets vals for accidentials, dots and beams for noteElements-Array
	 * @function
     * @public
	 *
	 */
	setValuesForNoteElement = function() {

		//accidential
		if (curAccidential == "#") {
			noteElementAccidential = 1;
		} else if (curAccidential == "b") {
			noteElementAccidential = -1;
		} else {
			noteElementAccidential = 0;
		}

		//dot
		if(curRythSpec == "dotted") {
			isDot = true;
		} else {
			isDot = false;
		}

		//beam
		if(curRythSpec == "triplet") {
				tripletCurrentAmount++;
		}

		//triplet
		switch(tripletCurrentAmount) {
		    case 1:
		        beamVal = "begin";
		        break;
	        case 2:
		        beamVal = "continue";
		        break;
		    case 3:
		        beamVal = "end";
		        break;
		    default:
		        beamVal = false;
		}
	},

	/**
	 * This method adds notes and breaks to the noteElements Array and the noteElements4VexFlow Array
	 * @function
     * @public
	 *
	 */
	addNoteElement = function() {

		setValuesForNoteElement();

		if (curMode == 2) {
			if(first){
				first = false;
				if (curName != "break") {
					noteElements.push(
					{
					type: curMode,
						notes:
						[
							{
								type: "note",
								pitch: {
									step: curName.toUpperCase(),
									alter: noteElementAccidential,
									type: curDuration,
									octave: curOctave,
									dot: isDot,
									beam: beamVal
								}
							}
						]
					});
				} else {
					//break
					noteElements.push(
					{
						type: curMode,
						notes:
						[
							{
							type: "rest",
							dot: isDot,
							duration: curDuration
							}
						]
					});
				}
			} else {
				if (curName != "break") {
					noteElements[0].notes.push(
					{
								type: "note",
								pitch: {
									step: curName.toUpperCase(),
									alter: noteElementAccidential,
									type: curDuration,
									octave: curOctave,
									dot: isDot,
									beam: beamVal
								}
					});
				} else {
					//break
					noteElements[0].notes.push(
					{
						type: "rest",
						dot: isDot,
						duration: curDuration
					});
				}
			}
		} else if (curMode == 1) {
		// rhythm mode
			if(first){
				first = false;
				if (curName != "break") {
					noteElements.push(
					{
					type: curMode,
						notes:
						[
							{
								type: "note",
								pitch: {
									type: curDuration,
									dot: isDot,
									beam: beamVal
								}
							}
						]
					});
				} else {
					//break
					noteElements.push(
					{
						type: curMode,
						notes:
						[
							{
								type: "rest",
								dot: isDot,
								duration: curDuration
							}
						]
					});
				}
			} else {
				if (curName != "break") {
					noteElements[0].notes.push(
					{

						type: "note",
						pitch: {
							type: curDuration,
							dot: isDot,
							beam: beamVal
						}

					});
				} else {
					//break
					noteElements[0].notes.push(
					{
						type: "rest",
						dot: isDot,
						duration: curDuration
					});
				}
			}
		} else if (curMode == 0) {
			if(first){
				first = false;
				if (curName != "break") {
					noteElements.push(
					{
					type: curMode,
						notes:
						[
							{
								type: "note",
								pitch: {
									step: curName.toUpperCase(),
									alter: noteElementAccidential,
									octave: curOctave
								}
							}
						]
					});
				}
			} else {
				if (curName != "break") {
					noteElements[0].notes.push(
					{
						type: "note",
						pitch: {
							step: curName.toUpperCase(),
							alter: noteElementAccidential,
							octave: curOctave
						}
					});
				}
			}
		}

		//check if break or normal note or note with accidential
		//then adapt values for vexflow an put them into an array
		var note;
		if(getCurrentMode() === 1 && curName !== 'break'){
			curName = 'b';
		}
		var keyContent = getKeyContent4Vexflow(curName);
		var durationContent = getDuration4Vexflow(curDuration);

		//check if break or normal note or note with accidential
		//then adapt values for vexflow an put them into an array
		if (curName == "break") {
			note = new Vex.Flow.StaveNote({ keys: ["b/4"],
	    						duration: durationContent + VEXFLOW_REST_SIGN,
	    						auto_stem: true });
		} else {
			var keys = keyContent + "/" + curOctave;
			if (getCurrentMode() == 1) {
				if (durationContent === "w" || durationContent === "h" || durationContent === "wd" || durationContent === "hd") {
					keys += '/d0';
				} else {
					keys += '/d2';
				}
			}
			note = new Vex.Flow.StaveNote({ keys: [keys],
	    						duration: durationContent,
	    						auto_stem: true });
		}

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

		if (curRythSpec == "dotted") {
			note.addDotToAll();
		}

		noteElements4VexFlow.push(note);

		//check if triplet
		if (curRythSpec == "triplet") {
				if (tripletCurrentAmount == 3) {
					$(that).trigger('endTripletEnterMode');
					tripletEnterMode = false;
					tripletCurrentAmount = 0;
					//store all end positions of the triplets
					tripletEndPositions.push(noteElements4VexFlow.length);
					//create tuplet and beam and push it into corresponding array
					var tuplet = new Vex.Flow.Tuplet(noteElements4VexFlow.slice(noteElements4VexFlow.length-3, noteElements4VexFlow.length))
					var beam = new Vex.Flow.Tuplet(noteElements4VexFlow.slice(noteElements4VexFlow.length-3, noteElements4VexFlow.length))
					tupletArray.push(tuplet);
					beamArray.push(beam);
				} else if (tripletCurrentAmount == 1) {
					 tripletEnterMode = true;
					 $(that).trigger('startTripletEnterMode');
				}
		}

		if(noteElements.length == 0) {
			first = true;
			noteElements = [];
		}

		$(that).trigger('patternChange', [noteElements]);
		// send vexflow note elements to controller and then back to view
		$(that).trigger('updateNotationView', [getAllVexFlowNoteElements()]);

	},

	/**
	 * Returns the length of the noteElements Array
	 * @function
     * @public
	 *
	 * @return {number}    length of noteElements Array
	 */
	getPatternLength = function(){
		if(noteElements.length > 0){
			return noteElements[0].notes.length;
		} else {
			return 0;
		}
	},

	/**
	 * Sets the default values when you change to sound sequence mode
	 * @function
     * @public
	 */
	setDefaultValsForSoundSequenceMode = function() {
		curName = "c";
		curOctave = "4";
		curAccidential = "none";

		tripletCurrentAmount = 0;
		tripletEndPositions = [],
		tupletArray = [],
		beamArray = [],
		beamVal = false;
		isDot = false;

		$(that).trigger('changeSelectedNoteName', curName);
		$(that).trigger('changeSelectedOctave', curOctave);
		$(that).trigger('changeSelectedAccidential', curAccidential);
	},

	/**
	 * Sets the default values when you change to rhythm mode
	 * @function
     * @public
	 */
	setDefaultValsForRhythmMode = function() {
		curDuration = "quarter";
		curRythSpec = "none";
		curOctave = "5";

		tripletCurrentAmount = 0;
		tripletEndPositions = [],
		tupletArray = [],
		beamArray = [],
		beamVal = false;
		isDot = false;

		$(that).trigger('changeSelectedDuration', curDuration);
		$(that).trigger('changeSelectedSpecRyth', curRythSpec);
	},

	/**
	 * Sets the default values when you change to melody mode
	 * @function
     * @public
	 */
	setDefaultValsForMelodyMode = function() {
		curMode = 2;
		curName = "c";
		curOctave = "4";
		curAccidential = "none";
		curDuration = "quarter";
		curRythSpec = "none";

		tripletCurrentAmount = 0;
		tripletEndPositions = [],
		tupletArray = [],
		beamArray = [],
		beamVal = false;
		isDot = false;

		$(that).trigger('changeSelectedNoteName', curName);
		$(that).trigger('changeSelectedOctave', curOctave);
		$(that).trigger('changeSelectedAccidential', curAccidential);
		$(that).trigger('changeSelectedDuration', curDuration);
		$(that).trigger('changeSelectedSpecRyth', curRythSpec);
	},

	/**
	 * Returns an Array with triplet end positions
	 * @function
     * @public
	 *
	 * @return {Array.<number>}    Array with trilet end positions
	 */
	getTripletEndPositions = function() {
		return tripletEndPositions;
	},

	/**
	 * Returns an Array with tuplet end positions
	 * @function
     * @public
	 *
	 * @return {Array.<number>}    Array with tuplet end positions
	 */
	getTupletArray = function() {
		return tupletArray;
	},

	/**
	 * Returns an Array with beam end positions
	 * @function
     * @public
	 *
	 * @return {Array.<number>}    Array with beam end positions
	 */
	getBeamArray = function() {
		return beamArray;
	},

	/**
	 * Returns the content of a vexflow key
	 * @function
     * @public
	 *
	 * @return {string}    content of vexflow key
	 */
	getKeyContent4Vexflow = function(noteName) {
		var keyContent = noteName;
		switch (curAccidential) {
			case "#":
				keyContent += "#";
				break;
			case "b":
				keyContent += "b";
				break;
			default:
				//...
		}
		return keyContent;
	},

	/**
	 * Returns the duration label which is used by vexflow
	 * @function
     * @public
	 *
	 * @return {string}    duration for vexflow
	 */
	getDuration4Vexflow = function(duration) {
		var duration4Vexflow = null;

			if ( duration == "whole") {
				duration4Vexflow = "w";
			} else if ( duration == "half") {
				duration4Vexflow = "h";
			} else if ( duration == "quarter") {
				duration4Vexflow = "q";
			} else if ( duration == "eighth") {
				duration4Vexflow = "8";
			} else if ( duration == "16th") {
				duration4Vexflow = "16";
			} else if ( duration == "32nd") {
				duration4Vexflow = "32";
			} else if ( duration == "64th") {
				duration4Vexflow = "64";
			}

			if (curRythSpec == "dotted") {
				duration4Vexflow += "d";
			}

		return duration4Vexflow;
	},

	/**
	 * This method gets called when your click on the canvas to add a note element. The paramter note looks like "c/4". It updates the model values curName and curOctave and calls addNoteElement.
	 * @function
     * @public
	 *
	 * @param {string}    note name in vexflow style like "c/4"
	 *
	 */
	addNoteElementByCanvasClick = function(note) {
		//split string at "/" to get noteName and ovtave
		//and saves it into array noteContainer
		var noteContainer = note.split("/");

		//current vals are being updated after note adding with click
		curName = noteContainer[0];
		curOctave = noteContainer[1];

		// updates selected btns for note name and view in pattern view
		$(that).trigger('changeSelectedNoteName', [curName]);
		$(that).trigger('changeSelectedOctave', [curOctave]);

		addNoteElement();
	},

	/**
	 * Removes last added note element from noteElementsArray and vexflowArray.
	 * @function
     * @public
	 *
	 */
	removeLastNoteElement = function() {
		if(noteElements.length == 0) {
	    	first = true;
	    	noteElements = [];
	    }else if(noteElements[0].notes.length != 0) {
			//check if element you want to delete is triplet
			//and check if there are triplets before
		    if(noteElements[0].notes[noteElements4VexFlow.length-1].pitch && noteElements[0].notes[noteElements4VexFlow.length-1].pitch.beam != false) {
		    	noteElements[0].notes.pop();
		    	noteElements[0].notes.pop();
		    	noteElements[0].notes.pop();
		    	noteElements4VexFlow.pop();
		    	noteElements4VexFlow.pop();
		    	noteElements4VexFlow.pop();
		    	beamArray.pop();
				tupletArray.pop();
		    } else {
		    	noteElements[0].notes.pop();
		    	noteElements4VexFlow.pop();
		    }
		}

	    $(that).trigger('patternChange', [noteElements]);
		// send vexflow note elements to controller and then back to view
		$(that).trigger('updateNotationView', [getAllVexFlowNoteElements()]);
	},

	/**
	 * Returns an array with Note Elements
	 * @function
     * @public
	 *
	 * @return {Array<Notes>}    array of Note Elements
	 */
	getAllNoteElements = function() {
		return noteElements;
	},

	/**
	 * Returns an array wtih vexflow notes elements
	 * @function
     * @public
	 *
	 * @return {Array<vexflowNotes>}    array of vexflowNotes
	 */
	getAllVexFlowNoteElements = function() {
		return noteElements4VexFlow;
	};

	that.init = init;
	that.getKeyContent4Vexflow = getKeyContent4Vexflow;
	that.getCurrentNoteName = getCurrentNoteName;
	that.getCurrentAccidential = getCurrentAccidential;
	that.getCurrentNoteDuration = getCurrentNoteDuration;
	that.getCurrentNoteRythSpecial = getCurrentNoteRythSpecial;
	that.getCurrentOctave = getCurrentOctave;
	that.setCurrentMode = setCurrentMode;
	that.setCurrentNoteName = setCurrentNoteName;
	that.setCurrentAccidential = setCurrentAccidential;
	that.setCurrentNoteDuration = setCurrentNoteDuration;
	that.setCurrentNoteRythSpecial = setCurrentNoteRythSpecial;
	that.setCurrentOctave = setCurrentOctave;
	that.addNoteElement = addNoteElement;
	that.addNoteElementByCanvasClick = addNoteElementByCanvasClick;
	that.removeLastNoteElement = removeLastNoteElement;
	that.getCurrentMode = getCurrentMode;
	that.getAllNoteElements = getAllNoteElements;
	that.getAllVexFlowNoteElements = getAllVexFlowNoteElements;
	that.getDuration4Vexflow = getDuration4Vexflow;
	that.getTupletArray = getTupletArray;
	that.getBeamArray = getBeamArray;
	that.getPatternLength = getPatternLength;

	return that;
}