001package net.kreatious.pianoleopard.midi.event;
002
003import javax.sound.midi.ShortMessage;
004
005/**
006 * Represents a note on or off event that is associated with a particular key
007 * (note) number.
008 *
009 * @author Jay-R Studer
010 */
011public class NoteEvent extends Event {
012    private final int key;
013    private final int velocity;
014    private final boolean on;
015    private transient Slot slot;
016
017    /**
018     * Array of flags indicating if a raw MIDI key (note) modulus 12 is sharp or
019     * not.
020     * <p>
021     * The first element corresponds to C, second is C#, third is D, etc.
022     */
023    private static final boolean[] SHARP_KEYS = { false, true, false, true, false, false, true, false, true, false,
024            true, false };
025
026    /**
027     * Constructs a new {@link NoteEvent} with the specified data.
028     * <p>
029     * The channel will be 0 and velocity will be 127.
030     *
031     * @param key
032     *            the raw MIDI key (note) for this event, between 0 and 127
033     *            inclusive
034     * @param on
035     *            {@code true} if this event is a note on event, otherwise
036     *            {@code false}
037     * @param time
038     *            the time that this event occurs, in microseconds
039     */
040    public NoteEvent(int key, boolean on, long time) {
041        super(0, time);
042
043        if (key < 0 || key > 127) {
044            throw new IllegalArgumentException("Key " + key + " is out of range [0, 127]");
045        }
046
047        this.key = key;
048        this.on = on;
049        velocity = 127;
050        slot = new Slot(getChannel(), key);
051    }
052
053    NoteEvent(ShortMessage message, long time) {
054        super(message, time);
055
056        key = message.getData1();
057        velocity = message.getData2();
058        slot = new Slot(message.getChannel(), key);
059
060        if (message.getCommand() == ShortMessage.NOTE_OFF) {
061            on = false;
062        } else if (velocity == 0) {
063            on = false;
064        } else if (message.getCommand() == ShortMessage.NOTE_ON) {
065            on = true;
066        } else {
067            throw new IllegalArgumentException("message " + message.getCommand() + " is not a note on/off message");
068        }
069    }
070
071    private NoteEvent(int channel, long time, int key, int velocity, boolean on, Slot slot) {
072        super(channel, time);
073        this.key = key;
074        this.velocity = velocity;
075        this.on = on;
076        this.slot = slot;
077    }
078
079    static boolean canCreate(ShortMessage message) {
080        return message.getCommand() == ShortMessage.NOTE_OFF || message.getCommand() == ShortMessage.NOTE_ON;
081    }
082
083    /**
084     * Returns the note number associated with this event.
085     * <p>
086     * MIDI note numbers range from 0 to 127, inclusive. Note C4 is number 60.
087     *
088     * @return the raw key (note) number associated with this event
089     */
090    public int getKey() {
091        return key;
092    }
093
094    /**
095     * Returns the velocity associated with this event.
096     * <p>
097     * Velocity ranges from 0 to 127, inclusive.
098     *
099     * @return the velocity (loudness) associated with this event.
100     */
101    public int getVelocity() {
102        return velocity;
103    }
104
105    /**
106     * Returns if this note is considered sharp or not.
107     *
108     * @return {@code true} if this note event is a sharp note, {@code false}
109     *         otherwise.
110     */
111    public boolean isSharp() {
112        return SHARP_KEYS[key % 12];
113    }
114
115    @Override
116    public boolean isOn() {
117        return on;
118    }
119
120    @Override
121    public Slot getSlot() {
122        return slot;
123    }
124
125    @Override
126    @SuppressWarnings("unchecked")
127    public NoteEvent createOff(long offTime) {
128        return new NoteEvent(getChannel(), offTime, key, 127, false, slot);
129    }
130}