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;&nbsp;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    }