package com.teotigraphix.caustic.sound.midi;
import java.util.HashMap;
import java.util.Map;
/**
* Collection of methods designed to make translating between the language of
* music to midi values. Contains methods that make note numbers, not names, and
* frequencies available, as well as simple methods to create scales and chords
* based on a note.
*
* @author gmuller
*/
public class MidiReference {
private static MidiReference midiReference;
private static Map<String, Integer> noteNameMap = new HashMap<String, Integer>();
private static String[] noteNumberArray = new String[132];
private static float[] noteFreqArray = new float[132];
private int[] range = {
-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int[] octave = {
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
};
private int a = 440; // a is 440 hz...
private MidiReference() {
mapNoteNames();
mapNoteNumbers();
mapNoteFreq();
}
private void mapNoteNames() {
for (int i = 1; i < 12; i++) {
for (NoteReference note : NoteReference.values()) {
int noteNumber = note.getBaseNumber() + (octave[i] * 12);
noteNameMap.put(note.getBaseName() + range[i], noteNumber);
}
}
}
/*
* 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() {
String noteName;
int octaveValue;
for (int i = 1; i < 11; i++) {
octaveValue = octave[i] * 12;
for (NoteReference note : NoteReference.values()) {
//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 (note.getBaseName().contains("b")) {
String previous = noteNumberArray[note.getBaseNumber() + octaveValue];
noteName = previous + "/" + note.getBaseName() + range[i];
} else {
noteName = note.getBaseName() + range[i];
}
noteNumberArray[note.getBaseNumber() + octaveValue] = noteName;
}
}
}
private void mapNoteFreq() {
for (Integer i = 0; i < noteNumberArray.length; ++i) {
noteFreqArray[i] = (float)(a * Math.pow(2, (i - 69d) / 12));
}
}
/**
* Get note name from note number.
*
* @param noteNumber
* @return String note name with no formatting (B Flat at octave 6 will
* appear as "Bb6/A#6") ;
*/
public String getNoteName(int noteNumber) {
return noteNumberArray[noteNumber];
}
/**
* Get note name from midi note number. If transform note is true, return
* only one note name (i.e. B flat at octave 6 returns "Bb6")
*
* @param noteNumber
* @param transformNote
* @return String note name
*/
public String getNoteName(int noteNumber, boolean transformNote) {
if (transformNote) {
return transformNote(getNoteName(noteNumber));
} else {
return getNoteName(noteNumber);
}
}
/**
* Get note name from midi note number. If transform note is true, return
* only one note name (i.e. B flat at octave 6 returns "Bb" when both
* transform note and stripOctave are true)
*
* @param noteNumber
* @param transformNote
* @param stripOctave
* @return String note name
*/
public String getNoteName(int noteNumber, boolean transformNote, boolean stripOctave) {
String noteName = getNoteName(noteNumber);
if (transformNote) {
noteName = transformNote(noteName);
}
if (stripOctave) {
noteName = stripOctave(noteName);
}
return noteName;
}
/**
* Get midi note number from note name
*
* @param noteName
* @return int midi note number
*/
public int getNoteNumber(String noteName) {
return noteNameMap.get(noteName);
}
/**
* Get note frequency from midi note number
*
* @param noteNumber
* @return float frequency
*/
public float getNoteFrequency(int noteNumber) {
return noteFreqArray[noteNumber];
}
/**
* Get note frequency from note name.
*
* @param noteName
* @return float frequency
*/
public float getNoteFrequency(String noteName) {
return getNoteFrequency(getNoteNumber(noteName));
}
/**
* Get approximate midi note number from frequency
*
* @param frequency
* @return int midi note number
*/
public int getNoteFromFrequency(float frequency) {
float noteNumber;
noteNumber = (float)(69 + 12 * (Math.log(frequency / 440) / Math.log(2)));
return Math.round(noteNumber);
}
/**
* Get note number from samples
*
* @param sampleRate (ex: 44100)
* @param samples (256)
* @return Integer midi note number
*/
public Integer getNoteFromSamples(Integer sampleRate, float samples) {
return getNoteFromFrequency(sampleRate / samples);
}
private String transformNote(String longNote) {
String shortNote;
String temp[] = null;
temp = longNote.split("/");
if ((temp.length > 1) && (temp[1].contains("Bb") || temp[1].contains("Eb"))) {
shortNote = temp[1];
} else {
shortNote = temp[0];
}
return shortNote;
}
private String stripOctave(String shortNote) {
if (shortNote.contains("-")) {
shortNote = shortNote.substring(0, shortNote.length() - 2);
} else {
shortNote = shortNote.substring(0, shortNote.length() - 1);
}
return shortNote;
}
/**
* Create a scale type using the int note number and int[] scale degrees
* provided
*
* @param scaleDegrees
* @param noteNumber
* @return int[] scale
*/
public static int[] createScale(int[] scaleDegrees, int noteNumber) {
int[] newScale = new int[scaleDegrees.length];
if (noteNumber > 11) {
noteNumber = noteNumber % 12;
}
for (int i = 0; i < scaleDegrees.length; i++) {
int scaleDegree = scaleDegrees[i] + noteNumber;
if (scaleDegree < 12) {
newScale[i] = scaleDegree;
} else {
newScale[i] = scaleDegree - 12;
}
}
return newScale;
}
/**
* Create a scale from a NoteReference and int[] scale degrees
*
* @see MidiReference#createScale(int[], int)
* @param scaleDegrees
* @param baseNote
* @return int[] scale
*/
public int[] createScale(int[] scaleDegrees, NoteReference baseNote) {
return createScale(scaleDegrees, baseNote.getBaseNumber());
}
/**
* Create a scale from a NoteReference and a ScaleReference
*
* @see MidiReference#createScale(int[], int)
* @param baseScale
* @param baseNote
* @return int[] scale
*/
public static int[] createScale(ScaleReference baseScale, NoteReference baseNote) {
return createScale(baseScale.getDegrees(), baseNote.getBaseNumber());
}
/**
* NOT YET IMPLEMENTED
*
* @param scaleIn
* @param scaleRef
* @return int[] scale
*/
public int[] getRelatedMode(int[] scaleIn, ScaleReference scaleRef) {
//TODO: Implement this method.
return null;
}
/**
* Check if a note is in a scale
*
* @param scale
* @param noteNumber
* @return boolean note is in scale
*/
public boolean isScale(int[] scale, int noteNumber) {
for (int i = 0; i < scale.length; i++) {
if (noteNumber % 12 == (scale[i]))
return true;
}
return false;
}
/**
* Create a int[] chord using a NoteReference and a ChordReference using
* octave 0
*
* @see MidiReference#createChord(int, int[])
* @param noteRef
* @param chordRef
* @return int[] chord
*/
public int[] createChord(NoteReference noteRef, ChordReference chordRef) {
return createChord(noteRef.getBaseNumber(), chordRef.getDegrees(), 0);
}
/**
* Create a int[] chord using an NoteReference and ChordReference
*
* @see MidiReference#createChord(int, int[], int)
* @param noteRef
* @param chordRef
* @param octave
* @return int[] chord
*/
public int[] createChord(NoteReference noteRef, ChordReference chordRef, int octave) {
return createChord(noteRef.getBaseNumber(), chordRef.getDegrees(), octave);
}
/**
* Create a int[] chord using an int note number and a ChordReference using
* octave 0
*
* @see MidiReference#createChord(int, int[])
* @param noteNumber
* @param chordRef
* @return int[] chord
*/
public int[] createChord(int noteNumber, ChordReference chordRef) {
return createChord(noteNumber, chordRef.getDegrees(), 0);
}
/**
* Create a int[] chord using an int note number and ChordReference
*
* @see MidiReference#createChord(int, int[], int)
* @param note
* @param chordRef
* @param octave
* @return int[] chord
*/
public int[] createChord(int note, ChordReference chordRef, int octave) {
return createChord(note, chordRef.getDegrees(), octave);
}
/**
* Create a int[] chord using an NoteReference and an int[] chord using
* octave 0
*
* @see MidiReference#createChord(int, int[])
* @param noteRef
* @param chordDegrees
* @return int[] chord
*/
public int[] createChord(NoteReference noteRef, int[] chordDegrees) {
return createChord(noteRef.getBaseNumber(), chordDegrees, 0);
}
/**
* Create a int[] chord using an int note number and int[] chord using
* octave 0
*
* @see MidiReference#createChord(int, int[], int)
* @param note
* @param chordDegrees
* @return int[] chord
*/
public int[] createChord(int note, int[] chordDegrees) {
return createChord(note, chordDegrees, 0);
}
/**
* Create a int[] chord using an int note number and int[] chord
*
* @see MidiReference#createChord(int, int[], int)
* @param note
* @param chordDegrees
* @param octave
* @return int[] chord
*/
public int[] createChord(int note, int[] chordDegrees, int octave) {
int[] notesOut = new int[chordDegrees.length];
for (int i = 0; i < notesOut.length; i++) {
notesOut[i] = (chordDegrees[i] + note) + (12 * octave);
}
return notesOut;
}
/**
* Create a midiReference class. Calls is protected to the extent that it
* will only allow one instantiation.
*
* @return MidiReference
*/
public static MidiReference getInstance() {
if (midiReference == null)
midiReference = new MidiReference();
return midiReference;
}
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}