package midiReference;

import java.util.HashMap;
import java.util.Map;

/**
 * 
 * @author gmuller
 * This is a reference class for mapping MIDI Note numbers to musical note names and vice versa.
 * For instance MIDI note number 60 = note/position C4
 * Note/Position D-1 = MIDI Note number 2
 * 
 */

//TODO: Add map to get frequencies from note names and numbers

public class NoteReference {
	
	//instantiate basic variables
	private static NoteReference noteReference;
	private static Map<String, Integer> noteNameMap = new HashMap<String, Integer>();
	private static String[] noteNumberArray = new String[132];
	
	//List of notes on a keyboard
	String[] noteNames = 
 	{"C","C#","Db","D","D#","Eb","E","F","F#","Gb","G","G#","Ab","A","A#","Bb","B"};
	
	/*
	constructor is private, this should be a reference so we only want one instance
	to instantiate use something like:
			MidiReference midiNote = MidiReference.getMidiReference();
	*/
	private NoteReference(){ 
		mapNoteNames();
		mapNoteNumbers();
		};
	
	/*
	 * method to create a map of Note names to Midi Numbers
	 *	to get a note number out of the map use something like:
	 *		Map<String, Integer> noteNameMap = midiNote.getNoteNameMap();
	 *			println(noteNameMap.get("C#3")); //prints 49
	 *			println(noteNameMap.get("D-1")); //prints 2
	 *			println(noteNameMap.get("Ab8")); //print 116
	 */
	private void mapNoteNames(){
	
		//Loop through all of the note numbers
		for (int noteNumber=0; noteNumber<127;){
			//loop through the ranges (-1 through 9)
			for (Integer range=-1; range<10; range++){
				//loop through all of the note names
				for (String nextNote : noteNames){
					//if  the note name contains a "flat" then it has the identical note number
					//as the previous flat, decrement the total note number count and insert into
					//the map so that note numbers can be translated from either sharps or flats
					if (nextNote.contains("b")) noteNumber--;
					noteNameMap.put(nextNote + range.toString(), noteNumber);
					noteNumber++;
				}
			}
		}
		NoteReference.setNoteNameMap(noteNameMap);
	}
	/*
	 * method to create a map of Note numbers to Note Names Numbers
	 *	to get a note number out of the map use something like:
	 *		String[] noteNumberArray = midiNote.getNoteNumberArray();
	 *			println(noteNumberArray[60]);
	 *			println(noteNumberArray[2]);
	 *	  		println(noteNumberArray[127]);
	 *
	 * Intentionally using a simple array here for performance reasons
	 */
	private void mapNoteNumbers(){
		//Loop through all of the note numbers
		for (int noteNumber=0; noteNumber<128;){
			//loop through the ranges (-1 through 9)
			for (Integer range=-1; range<10; range++){
				//loop through all of the note names
				for (String nextNote : noteNames){
					String noteName;
					//if  the note name contains a "flat" then it has the identical note number
					//as the previous flat, decrement the total note number count and insert into
					//the array a string made up of both the previous notes name and the current one 
					if (nextNote.contains("b")){
						noteNumber--;
						String previous = noteNumberArray[noteNumber];
						noteName = previous + "/" + nextNote + range.toString();
					} else {
						noteName = nextNote + range.toString();
					}
					noteNumberArray[noteNumber] = noteName;
					noteNumber++;
				}
			}
		}
		NoteReference.setNoteNumberArray(noteNumberArray);
	}

	/**
	 * Get the Note name to number map
	 * @return
	 */
	public Map<String, Integer> getNoteNameMap() {
		return noteNameMap;
	}

	/**
	 * Get the note number to name array
	 * @return
	 */
	public String[] getNoteNumberArray() {
		return noteNumberArray;
	}

	private static void setNoteNameMap(Map<String, Integer> noteNameMap) {
		NoteReference.noteNameMap = noteNameMap;
	}

	private static void setNoteNumberArray(String[] noteNumberArray) {
		NoteReference.noteNumberArray = noteNumberArray;
	}

	/**
	 * This is the ONLY way to instantiate this object, and will prevent more than one from being created
	 */
	public static NoteReference getMidiReference() {
		if (noteReference == null)
			noteReference = new NoteReference();
	    return noteReference;
	}
	
	//prevent cloning, enforce singleton
	public Object clone()
		throws CloneNotSupportedException
	{
	    throw new CloneNotSupportedException(); 
	}
}

