001 package org.bukkit;
002
003 import java.util.Map;
004
005 import org.apache.commons.lang.Validate;
006
007 import com.google.common.collect.Maps;
008
009 /**
010 * A note class to store a specific note.
011 */
012 public class Note {
013
014 /**
015 * An enum holding tones.
016 */
017 public enum Tone {
018 G(0x1, true),
019 A(0x3, true),
020 B(0x5, false),
021 C(0x6, true),
022 D(0x8, true),
023 E(0xA, false),
024 F(0xB, true);
025
026 private final boolean sharpable;
027 private final byte id;
028
029 private static final Map<Byte, Note.Tone> BY_DATA = Maps.newHashMap();
030 /** The number of tones including sharped tones. */
031 public static final byte TONES_COUNT = 12;
032
033 private Tone(int id, boolean sharpable) {
034 this.id = (byte) (id % TONES_COUNT);
035 this.sharpable = sharpable;
036 }
037
038 /**
039 * Returns the not sharped id of this tone.
040 *
041 * @return the not sharped id of this tone.
042 * @deprecated Magic value
043 */
044 @Deprecated
045 public byte getId() {
046 return getId(false);
047 }
048
049 /**
050 * Returns the id of this tone. These method allows to return the
051 * sharped id of the tone. If the tone couldn't be sharped it always
052 * return the not sharped id of this tone.
053 *
054 * @param sharped Set to true to return the sharped id.
055 * @return the id of this tone.
056 * @deprecated Magic value
057 */
058 @Deprecated
059 public byte getId(boolean sharped) {
060 byte id = (byte) (sharped && sharpable ? this.id + 1 : this.id);
061
062 return (byte) (id % TONES_COUNT);
063 }
064
065 /**
066 * Returns if this tone could be sharped.
067 *
068 * @return if this tone could be sharped.
069 */
070 public boolean isSharpable() {
071 return sharpable;
072 }
073
074 /**
075 * Returns if this tone id is the sharped id of the tone.
076 *
077 * @param id the id of the tone.
078 * @return if the tone id is the sharped id of the tone.
079 * @throws IllegalArgumentException if neither the tone nor the
080 * semitone have the id.
081 * @deprecated Magic value
082 */
083 @Deprecated
084 public boolean isSharped(byte id) {
085 if (id == getId(false)) {
086 return false;
087 } else if (id == getId(true)) {
088 return true;
089 } else {
090 // The id isn't matching to the tone!
091 throw new IllegalArgumentException("The id isn't matching to the tone.");
092 }
093 }
094
095 /**
096 * Returns the tone to id. Also returning the semitones.
097 *
098 * @param id the id of the tone.
099 * @return the tone to id.
100 * @deprecated Magic value
101 */
102 @Deprecated
103 public static Tone getById(byte id) {
104 return BY_DATA.get(id);
105 }
106
107 static {
108 for (Tone tone : values()) {
109 int id = tone.id % TONES_COUNT;
110 BY_DATA.put((byte) id, tone);
111
112 if (tone.isSharpable()) {
113 id = (id + 1) % TONES_COUNT;
114 BY_DATA.put((byte) id, tone);
115 }
116 }
117 }
118 }
119
120 private final byte note;
121
122 /**
123 * Creates a new note.
124 *
125 * @param note Internal note id. {@link #getId()} always return this
126 * value. The value has to be in the interval [0; 24].
127 */
128 public Note(int note) {
129 Validate.isTrue(note >= 0 && note <= 24, "The note value has to be between 0 and 24.");
130
131 this.note = (byte) note;
132 }
133
134 /**
135 * Creates a new note.
136 *
137 * @param octave The octave where the note is in. Has to be 0 - 2.
138 * @param tone The tone within the octave. If the octave is 2 the note has
139 * to be F#.
140 * @param sharped Set if the tone is sharped (e.g. for F#).
141 */
142 public Note(int octave, Tone tone, boolean sharped) {
143 if (sharped && !tone.isSharpable()) {
144 tone = Tone.values()[tone.ordinal() + 1];
145 sharped = false;
146 }
147 if (octave < 0 || octave > 2 || (octave == 2 && !(tone == Tone.F && sharped))) {
148 throw new IllegalArgumentException("Tone and octave have to be between F#0 and F#2");
149 }
150
151 this.note = (byte) (octave * Tone.TONES_COUNT + tone.getId(sharped));
152 }
153
154 /**
155 * Creates a new note for a flat tone, such as A-flat.
156 *
157 * @param octave The octave where the note is in. Has to be 0 - 1.
158 * @param tone The tone within the octave.
159 * @return The new note.
160 */
161 public static Note flat(int octave, Tone tone) {
162 Validate.isTrue(octave != 2, "Octave cannot be 2 for flats");
163 tone = tone == Tone.G ? Tone.F : Tone.values()[tone.ordinal() - 1];
164 return new Note(octave, tone, tone.isSharpable());
165 }
166
167 /**
168 * Creates a new note for a sharp tone, such as A-sharp.
169 *
170 * @param octave The octave where the note is in. Has to be 0 - 2.
171 * @param tone The tone within the octave. If the octave is 2 the note has
172 * to be F#.
173 * @return The new note.
174 */
175 public static Note sharp(int octave, Tone tone) {
176 return new Note(octave, tone, true);
177 }
178
179 /**
180 * Creates a new note for a natural tone, such as A-natural.
181 *
182 * @param octave The octave where the note is in. Has to be 0 - 1.
183 * @param tone The tone within the octave.
184 * @return The new note.
185 */
186 public static Note natural(int octave, Tone tone) {
187 Validate.isTrue(octave != 2, "Octave cannot be 2 for naturals");
188 return new Note(octave, tone, false);
189 }
190
191 /**
192 * @return The note a semitone above this one.
193 */
194 public Note sharped() {
195 Validate.isTrue(note < 24, "This note cannot be sharped because it is the highest known note!");
196 return new Note(note + 1);
197 }
198
199 /**
200 * @return The note a semitone below this one.
201 */
202 public Note flattened() {
203 Validate.isTrue(note > 0, "This note cannot be flattened because it is the lowest known note!");
204 return new Note(note - 1);
205 }
206
207 /**
208 * Returns the internal id of this note.
209 *
210 * @return the internal id of this note.
211 * @deprecated Magic value
212 */
213 @Deprecated
214 public byte getId() {
215 return note;
216 }
217
218 /**
219 * Returns the octave of this note.
220 *
221 * @return the octave of this note.
222 */
223 public int getOctave() {
224 return note / Tone.TONES_COUNT;
225 }
226
227 private byte getToneByte() {
228 return (byte) (note % Tone.TONES_COUNT);
229 }
230
231 /**
232 * Returns the tone of this note.
233 *
234 * @return the tone of this note.
235 */
236 public Tone getTone() {
237 return Tone.getById(getToneByte());
238 }
239
240 /**
241 * Returns if this note is sharped.
242 *
243 * @return if this note is sharped.
244 */
245 public boolean isSharped() {
246 byte note = getToneByte();
247 return Tone.getById(note).isSharped(note);
248 }
249
250 @Override
251 public int hashCode() {
252 final int prime = 31;
253 int result = 1;
254 result = prime * result + note;
255 return result;
256 }
257
258 @Override
259 public boolean equals(Object obj) {
260 if (this == obj)
261 return true;
262 if (obj == null)
263 return false;
264 if (getClass() != obj.getClass())
265 return false;
266 Note other = (Note) obj;
267 if (note != other.note)
268 return false;
269 return true;
270 }
271
272 @Override
273 public String toString() {
274 return "Note{" + getTone().toString() + (isSharped() ? "#" : "") + "}";
275 }
276 }