/*
* JFugue, an Application Programming Interface (API) for Music Programming
* http://www.jfugue.org
*
* Copyright (C) 2003-2014 David Koelle
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jfugue.theory;
import org.jfugue.midi.MidiDefaults;
import org.jfugue.pattern.Pattern;
import org.jfugue.pattern.PatternProducer;
import org.jfugue.provider.NoteProviderFactory;
import org.staccato.DefaultNoteSettingsManager;
import org.staccato.NoteSubparser;
public class Note implements PatternProducer
{
private byte value;
private double duration;
private boolean wasDurationExplicitlySet;
private byte onVelocity;
private byte offVelocity;
private boolean isRest;
private boolean isStartOfTie;
private boolean isEndOfTie;
private boolean isFirstNote = true;
private boolean isMelodicNote;
private boolean isHarmonicNote;
private boolean isPercussionNote;
public String originalString;
public Note() {
this.onVelocity = DefaultNoteSettingsManager.getInstance().getDefaultOnVelocity();
this.offVelocity = DefaultNoteSettingsManager.getInstance().getDefaultOffVelocity();
}
public Note(String note) {
this(NoteProviderFactory.getNoteProvider().createNote(note));
}
public Note(Note note) {
this.value = note.value;
this.duration = note.duration;
this.wasDurationExplicitlySet = note.wasDurationExplicitlySet;
this.onVelocity = note.onVelocity;
this.offVelocity = note.offVelocity;
this.isRest = note.isRest;
this.isStartOfTie = note.isStartOfTie;
this.isEndOfTie = note.isEndOfTie;
this.isFirstNote = note.isFirstNote;
this.isMelodicNote = note.isMelodicNote;
this.isHarmonicNote = note.isHarmonicNote;
this.isPercussionNote = note.isPercussionNote;
this.originalString = note.originalString;
}
public Note(int value) {
this((byte)value);
}
public Note(byte value) {
this();
this.value = value;
useDefaultDuration();
}
public Note(int value, double duration) {
this((byte)value, duration);
}
public Note(byte value, double duration) {
this();
this.value = value;
setDuration(duration);
}
public Note setValue(byte value) {
this.value = value;
return this;
}
public byte getValue() {
return this.value;
}
public Note changeValue(byte delta) {
return setValue((byte)(getValue() + delta));
}
public byte getOctave() {
return (byte)(this.getValue() / 12);
}
public double getDuration() {
return this.duration;
}
public Note setDuration(double d) {
this.duration = d;
this.wasDurationExplicitlySet = true;
return this;
}
public Note useDefaultDuration() {
this.duration = DefaultNoteSettingsManager.getInstance().getDefaultDuration();
// And do not set wasDurationExplicitlySet
return this;
}
public Note useSameDurationAs(Note note2) {
this.duration = note2.duration;
this.wasDurationExplicitlySet = note2.wasDurationExplicitlySet;
return this;
}
public Note setDuration(String duration) {
return setDuration(NoteProviderFactory.getNoteProvider().getDurationForString(duration));
}
public boolean isDurationExplicitlySet() {
return this.wasDurationExplicitlySet;
}
/**
* FOR TESTING PURPOSES ONLY - avoids setting "isDurationExplicitlySet" - Please use setDuration instead!
*/
public Note setImplicitDurationForTestingOnly(double d) {
this.duration = d;
// And do not set wasDurationExplicitlySet
return this;
}
public Note setRest(boolean rest) {
this.isRest = rest;
return this;
}
public boolean isRest() {
return this.isRest;
}
public Note setPercussionNote(boolean perc) {
this.isPercussionNote = perc;
return this;
}
public boolean isPercussionNote() {
return this.isPercussionNote;
}
public Note setOnVelocity(byte velocity) {
this.onVelocity = velocity;
return this;
}
public byte getOnVelocity() {
return this.onVelocity;
}
public Note setOffVelocity(byte velocity) {
this.offVelocity = velocity;
return this;
}
public byte getOffVelocity() {
return this.offVelocity;
}
public Note setStartOfTie(boolean isStartOfTie) {
this.isStartOfTie = isStartOfTie;
return this;
}
public Note setEndOfTie(boolean isEndOfTie) {
this.isEndOfTie = isEndOfTie;
return this;
}
public boolean isStartOfTie() {
return isStartOfTie;
}
public boolean isEndOfTie() {
return isEndOfTie;
}
public Note setFirstNote(boolean isFirstNote) {
this.isFirstNote = isFirstNote;
return this;
}
public boolean isFirstNote() {
return this.isFirstNote;
}
public Note setMelodicNote(boolean isMelodicNote) {
this.isMelodicNote = isMelodicNote;
return this;
}
public boolean isMelodicNote() {
return this.isMelodicNote;
}
public Note setHarmonicNote(boolean isHarmonicNote) {
this.isHarmonicNote = isHarmonicNote;
return this;
}
public boolean isHarmonicNote() {
return this.isHarmonicNote;
}
public Note setOriginalString(String originalString) {
this.originalString = originalString;
return this;
}
public String getOriginalString() {
return this.originalString;
}
public double getMicrosecondDuration(double mpq) {
return (this.duration * 4.0f) * mpq;
}
public byte getPositionInOctave() {
return (byte)(getValue() % 12);
}
public static boolean isSameNote(String note1, String note2) {
if (note1.equalsIgnoreCase(note2)) return true;
for (int i=0; i < NOTE_NAMES_COMMON.length; i++) {
if (note1.equalsIgnoreCase(NOTE_NAMES_FLAT[i]) && note2.equalsIgnoreCase(NOTE_NAMES_SHARP[i])) return true;
if (note1.equalsIgnoreCase(NOTE_NAMES_SHARP[i]) && note2.equalsIgnoreCase(NOTE_NAMES_FLAT[i])) return true;
}
return false;
}
public static Note createRest(double duration) {
return new Note().setRest(true).setDuration(duration);
}
/**
* Returns a MusicString representation of the given MIDI note value,
* which indicates a note and an octave.
*
* @param noteValue this MIDI note value, like 60
* @return a MusicString value, like C5
*/
public static String getToneString(byte noteValue) {
StringBuilder buddy = new StringBuilder();
buddy.append(getToneStringWithoutOctave(noteValue));
buddy.append(noteValue / 12); // Octave: this should say "-1" if octaves are -1..9
return buddy.toString();
}
/**
* Returns a MusicString representation of the given MIDI note value,
* but just the note - not the octave. This means that the value returned
* can not be used to accurately recalculate the noteValue, since information
* will be missing. But this is useful for knowing what note within any octave
* the corresponding value belongs to.
*
* @param noteValue this MIDI note value, like 60
* @return a MusicString value, like C
*/
public static String getToneStringWithoutOctave(byte noteValue) {
return NOTE_NAMES_COMMON[noteValue % 12];
}
/**
* Returns a MusicString representation of the given MIDI note value,
* just the note (not the octave), disposed to use either flats or sharps.
* Pass -1 to get a flat name and +1 to get a sharp name for any notes
* that are accidentals.
*
* @param dispose -1 to get a flat value, +1 to get a sharp value
* @param noteValue this MIDI note value, like 61
* @return a MusicString value, like Db if -1 or C# if +1
*/
public static String getDispositionedToneStringWithoutOctave(int dispose, byte noteValue) {
if (dispose == -1) {
return NOTE_NAMES_FLAT[noteValue % 12];
} else {
return NOTE_NAMES_SHARP[noteValue % 12];
}
}
/**
* Returns a MusicString representation of the given MIDI note value
* using the name of a percussion instrument.
* @param noteValue this MIDI note value, like 60
* @return a MusicString value, like [AGOGO]
*/
public static String getPercussionString(byte noteValue) {
StringBuilder buddy = new StringBuilder();
buddy.append("[");
buddy.append(PERCUSSION_NAMES[noteValue-35]);
buddy.append("]");
return buddy.toString();
}
/**
* Returns the frequency, in Hertz, for the given note.
* For example, the frequency for A5 (MIDI note 69) is 440.0
* @param noteValue the MIDI note value
* @return frequency in Hertz
*/
public static double getFrequencyForNote(String note) {
return getFrequencyForNote(NoteProviderFactory.getNoteProvider().createNote(note).getValue());
}
/**
* Returns the frequency, in Hertz, for the given note value.
* For example, the frequency for A5 (MIDI note 69) is 440.0
* @param noteValue the MIDI note value
* @return frequency in Hertz
*/
public static double getFrequencyForNote(int noteValue) {
return truncateTo3DecimalPlaces(getPreciseFrequencyForNote(noteValue));
}
private static double truncateTo3DecimalPlaces(double preciseNumber) {
return Math.rint(preciseNumber * 10000.0) / 10000.0;
}
private static double getPreciseFrequencyForNote(int noteValue) {
return getFrequencyAboveBase(8.1757989156, noteValue / 12.0);
}
private static double getFrequencyAboveBase(double baseFrequency, double octavesAboveBase) {
return baseFrequency * Math.pow(2.0, octavesAboveBase);
}
public boolean isValidNote(String candidateNote) {
return NoteSubparser.getInstance().matches(candidateNote);
}
/**
* Returns a MusicString representation of a decimal duration. This code
* currently only converts single duration values representing whole, half,
* quarter, eighth, etc. durations; and dotted durations associated with those
* durations (such as "h.", equal to 0.75). This method does not convert
* combined durations (for example, "hi" for 0.625). For these values,
* the original decimal duration is returned in a string, prepended with a "/"
* to make the returned value a valid MusicString duration indicator.
* It does handle durations greater than 1.0 (for example, "wwww" for 4.0).
*
* @param decimalDuration The decimal value of the duration to convert
* @return a MusicString fragment representing the duration
*/
public static String getDurationString(double decimalDuration) {
double originalDecimalDuration = decimalDuration;
StringBuilder buddy = new StringBuilder();
if (decimalDuration >= 1.0) {
int numWholeDurations = (int)Math.floor(decimalDuration);
buddy.append("w");
if (numWholeDurations > 1) {
buddy.append(numWholeDurations);
}
decimalDuration -= numWholeDurations;
}
if (decimalDuration == 0.75) buddy.append("h.");
else if (decimalDuration == 0.5) buddy.append("h");
else if (decimalDuration == 0.375) buddy.append("q.");
else if (decimalDuration == 0.25) buddy.append("q");
else if (decimalDuration == 0.1875) buddy.append("i.");
else if (decimalDuration == 0.125) buddy.append("i");
else if (decimalDuration == 0.09375) buddy.append("s.");
else if (decimalDuration == 0.0625) buddy.append("s");
else if (decimalDuration == 0.046875) buddy.append("t.");
else if (decimalDuration == 0.03125) buddy.append("t");
else if (decimalDuration == 0.0234375) buddy.append("x.");
else if (decimalDuration == 0.015625) buddy.append("x");
else if (decimalDuration == 0.01171875) buddy.append("o.");
else if (decimalDuration == 0.0078125) buddy.append("o");
else if (decimalDuration == 0.0) { }
else {
return "/" + originalDecimalDuration;
}
return buddy.toString();
}
public static String getDurationStringForBeat(int beat) {
switch(beat) {
case 2 : return "h";
case 4 : return "q";
case 8 : return "i";
case 16 : return "s";
default : return "/"+(1.0/(double)beat);
}
}
public String getVelocityString() {
StringBuilder buddy = new StringBuilder();
if (this.onVelocity != MidiDefaults.MIDI_DEFAULT_ON_VELOCITY) {
buddy.append("a"+this.onVelocity);
}
if (this.offVelocity != MidiDefaults.MIDI_DEFAULT_OFF_VELOCITY) {
buddy.append("d"+this.offVelocity);
}
return buddy.toString();
}
/**
* Returns a pattern representing this note. Does not
* return indicators of whether the note is harmonic
* or melodic.
*/
@Override
public Pattern getPattern() {
StringBuilder buddy = new StringBuilder();
buddy.append(toStringWithoutDuration());
buddy.append(getDecoratorString());
return new Pattern(buddy.toString());
}
public Pattern getPercussionPattern() {
if (getValue() < MidiDefaults.MIN_PERCUSSION_NOTE || getValue() > MidiDefaults.MAX_PERCUSSION_NOTE) return getPattern();
StringBuilder buddy = new StringBuilder();
buddy.append(Note.getPercussionString(getValue()));
buddy.append(getDecoratorString());
return new Pattern(buddy.toString());
}
public String toString() {
return getPattern().toString();
}
public String toStringWithoutDuration() {
if (isRest()) {
return "R";
} else if (isPercussionNote()) {
return Note.getPercussionString(this.getValue());
} else {
return (originalString != null) ? this.originalString : Note.getToneString(this.getValue());
}
}
/**
* Returns the "decorators" to the base note, which includes the duration if one is explicitly specified, and velocity dynamics if provided
*/
public String getDecoratorString() {
StringBuilder buddy = new StringBuilder();
if (isDurationExplicitlySet()) {
buddy.append(Note.getDurationString(this.duration));
}
buddy.append(getVelocityString());
return buddy.toString();
}
public boolean equals(Object o) {
if (!(o instanceof Note)) {
return false;
}
Note n2 = (Note)o;
boolean originalStringsMatchSufficientlyWell = ((n2.originalString == null) || (this.originalString == null)) ? true : n2.originalString.equalsIgnoreCase(this.originalString);
return ((n2.value == this.value) &&
(n2.duration == this.duration) &&
(n2.wasDurationExplicitlySet == this.wasDurationExplicitlySet) &&
(n2.isEndOfTie == this.isEndOfTie) &&
(n2.isStartOfTie == this.isStartOfTie) &&
(n2.isMelodicNote == this.isMelodicNote) &&
(n2.isHarmonicNote == this.isHarmonicNote) &&
(n2.isPercussionNote == this.isPercussionNote) &&
(n2.isFirstNote == this.isFirstNote) &&
(n2.isRest == this.isRest) &&
(n2.onVelocity == this.onVelocity) &&
(n2.offVelocity == this.offVelocity) &&
originalStringsMatchSufficientlyWell);
}
public String toDebugString() {
StringBuilder buddy = new StringBuilder();
buddy.append("Note:");
buddy.append(" value=").append(this.value);
buddy.append(" duration=").append(this.duration);
buddy.append(" wasDurationExplicitlySet=").append(this.wasDurationExplicitlySet);
buddy.append(" isEndOfTie=").append(this.isEndOfTie);
buddy.append(" isStartOfTie=").append(this.isStartOfTie);
buddy.append(" isMelodicNote=").append(this.isMelodicNote);
buddy.append(" isHarmonicNote=").append(this.isHarmonicNote);
buddy.append(" isPercussionNote=").append(this.isPercussionNote) ;
buddy.append(" isFirstNote=").append(this.isFirstNote);
buddy.append(" isRest=").append(this.isRest);
buddy.append(" onVelocity=").append(this.onVelocity);
buddy.append(" offVelocity=").append(this.offVelocity);
buddy.append(" originalString=").append(this.originalString);
return buddy.toString();
}
public final static String[] NOTE_NAMES_COMMON = new String[] { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" };
public final static String[] NOTE_NAMES_SHARP = new String[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
public final static String[] NOTE_NAMES_FLAT = new String[] { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" };
public static String[] PERCUSSION_NAMES = new String[] {
// Percussion Name // MIDI Note Value
"ACOUSTIC_BASS_DRUM", // 35
"BASS_DRUM", // 36
"SIDE_STICK", // 37
"ACOUSTIC_SNARE", // 38
"HAND_CLAP", // 39
"ELECTRIC_SNARE", // 40
"LO_FLOOR_TOM", // 41
"CLOSED_HI_HAT", // 42
"HIGH_FLOOR_TOM", // 43
"PEDAL_HI_HAT", // 44
"LO_TOM", // 45
"OPEN_HI_HAT", // 46
"LO_MID_TOM", // 47
"HI_MID_TOM", // 48
"CRASH_CYMBAL_1", // 49
"HI_TOM", // 50
"RIDE_CYMBAL_1", // 51
"CHINESE_CYMBAL", // 52
"RIDE_BELL", // 53
"TAMBOURINE", // 54
"SPLASH_CYMBAL", // 55
"COWBELL", // 56
"CRASH_CYMBAL_2", // 57
"VIBRASLAP", // 58
"RIDE_CYMBAL_2", // 59
"HI_BONGO", // 60
"LO_BONGO", // 61
"MUTE_HI_CONGA", // 62
"OPEN_HI_CONGA", // 63
"LO_CONGA", // 64
"HI_TIMBALE", // 65
"LO_TIMBALE", // 66
"HI_AGOGO", // 67
"LO_AGOGO", // 68
"CABASA", // 69
"MARACAS", // 70
"SHORT_WHISTLE", // 71
"LONG_WHISTLE", // 72
"SHORT_GUIRO", // 73
"LONG_GUIRO", // 74
"CLAVES", // 75
"HI_WOOD_BLOCK", // 76
"LO_WOOD_BLOCK", // 77
"MUTE_CUICA", // 78
"OPEN_CUICA", // 79
"MUTE_TRIANGLE", // 80
"OPEN_TRIANGLE" // 81
};
public static final Note REST = new Note(0).setRest(true);
public static final byte OCTAVE = 12;
public static final byte MIN_OCTAVE = 0;
public static final byte MAX_OCTAVE = 10;
}