com.rockhoppertech.music.Note.java Source code

Java tutorial

Introduction

Here is the source code for com.rockhoppertech.music.Note.java

Source

package com.rockhoppertech.music;

/*
 * #%L
 * Rocky Music Core
 * %%
 * Copyright (C) 1996 - 2013 Rockhopper Technologies
 * %%
 * 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.
 * #L%
 */

/*
 * $Id$
 *
 * Copyright 1998,1999,2000,2001 by Rockhopper Technologies, Inc.,
 * 75 Trueman Ave., Haddonfield, New Jersey, 08033-2529, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Rockhopper Technologies, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with RTI.
 */

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.text.NumberFormat;

import org.apache.commons.lang3.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rockhoppertech.music.midi.js.MIDINote;

/**
 * The <code>Note</code> class is a utility. The implicit unit is "beats". I
 * suppose that seconds might be useful too.
 * 
 * The XML stuff is in here. I should move it. although it's a delegate.
 * 
 * 
 * @author <a href="mailto:gene@rockhoppertech.com"></a>
 * @version 1.0
 * @since 1.0
 * @see Comparable
 * @see Serializable
 * @see MIDINote
 */
public class Note implements Comparable<Note>, Timed, Serializable {

    /**
     * For Serialization.
     */
    private static final long serialVersionUID = -6744900968063285081L;

    private static final Logger logger = LoggerFactory.getLogger(Note.class);
    private Pitch pitch;
    private double startBeat;
    private double duration; // in beats
    /* is this a sounding note or a rest? */
    private boolean rest = false;
    // these are optional
    private double endBeat;

    /**
     * Hint to notation. e.g. Is it G# or Ab?
     */
    private String spelling;

    protected PropertyChangeSupport changes = new PropertyChangeSupport(this);
    /**
     * Name of the bound property, also XML attributes.
     */
    public static final String PITCH = "pitch";
    public static final String DURATION = "duration";
    public static final String START_BEAT = "startBeat";
    public static final String END_BEAT = "endBeat";
    // public static final String MIDI_NUMBER = "midiNumber";
    public static final String NOTE = "note";

    /**
     * Initializes a new <code>Note</code> instance. The startBeat is 1, the
     * duration is 1 and the pitch is Middle C.
     * 
     */
    public Note() {
        setStartBeat(1d);
        duration = 1d;
        pitch = PitchFactory.getPitch(Pitch.C5);
        endBeat = startBeat + duration;
        this.spelling = pitch.getPreferredSpelling();
    }

    /**
     * Initializes a new <code>Note</code> instance given just a MIDI number.
     * 
     * @param midiNumber
     *            an <code>int</code> representing a MIDI note number
     */
    public Note(final int midiNumber) {
        this(PitchFactory.getPitch(midiNumber), 1d, 1d);
    }

    /**
     * Initializes a new <code>Note</code> instance.
     * 
     * @param midiNumber
     *            an <code>int</code> representing a MIDI note number
     * @param startBeat
     *            a <code>double</code> representing the start beat
     * @param duration
     *            a <code>double</code> duration value
     */
    public Note(final int midiNumber, final double startBeat, final double duration) {
        this(PitchFactory.getPitch(midiNumber), startBeat, duration);
        this.spelling = pitch.getPreferredSpelling();
    }

    /**
     * Initializes a new <code>Note</code> instance.
     * 
     * @param pitch
     *            a <code>Pitch</code> value
     * @param startBeat
     *            a <code>double</code> value
     * @param duration
     *            a <code>double</code> value
     */
    public Note(final Pitch pitch, final double startBeat, final double duration) {
        this.pitch = pitch;
        setStartBeat(startBeat);
        endBeat = startBeat + duration;
        this.duration = duration;
        this.spelling = pitch.getPreferredSpelling();
    }

    /**
     * Initializes a new <code>Note</code> instance.
     * 
     * @param pitch
     *            a <code>String</code> value
     * @param startBeat
     *            a <code>double</code> value
     * @param duration
     *            a <code>double</code> value
     */
    public Note(final String pitch, final double startBeat, final double duration) {
        this.pitch = PitchFactory.getPitch(pitch);
        setStartBeat(startBeat);
        endBeat = startBeat + duration;
        this.duration = duration;
        this.spelling = pitch;
    }

    /**
     * Copy constructor. Clone is evil.
     * 
     * @param note
     *            another {@code Note}
     */
    public Note(Note note) {
        this.duration = note.duration;
        this.startBeat = note.startBeat;
        this.pitch = note.pitch;
        this.rest = note.rest;
        this.endBeat = note.endBeat;
        this.spelling = note.spelling;
    }

    /*
     * GoF visitor design pattern
     * 
     * @see
     * com.rockhoppertech.music.MusicalElement#accept(com.rockhoppertech.music
     * .MusicVisitor)
     */
    // public void accept(final MusicVisitor v) {
    // v.visit(this);
    // }
    // TODO FIXME visitor

    /**
     * <code>addPropertyChangeListener</code>
     * 
     * @param l
     *            a <code>PropertyChangeListener</code> value
     */
    public void addPropertyChangeListener(final PropertyChangeListener l) {
        changes.addPropertyChangeListener(l);
    }

    /**
     * <code>compareTo</code> checks the startBeat.
     * 
     * @param n
     *            a note to compare
     * 
     * @return an <code>int</code> value
     * 
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(final Note n) {
        final double nt = n.getStartBeat();
        int r = 0;
        if (startBeat < nt) {
            r = -1;
        } else if (Math.abs(startBeat - nt) < .0000001) {
            r = 0;
        } else if (startBeat > nt) {
            r = 1;
        }
        return r;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if ((o instanceof Note) == false) {
            return false;
        }

        final Note n = (Note) o;

        if (n.pitch.equals(pitch) == false) {
            return false;
        }

        if (Math.abs(n.startBeat - startBeat) > .0000001) {
            return false;
        }
        if (Math.abs(n.duration - duration) > .0000001) {
            return false;
        }
        if (Math.abs(n.endBeat - endBeat) > .0000001) {
            return false;
        }
        /*
         * if (n.startBeat != startBeat) { return false; } if (n.duration !=
         * duration) { return false; } if (n.endBeat != endBeat) { return false;
         * }
         */

        return true;
    }

    /**
     * <code>getBeat</code>
     * 
     * @return a <code>double</code> value
     */
    public double getBeat() {
        return startBeat;
    }

    /**
     * <code>getDuration</code>
     * 
     * @return a <code>double</code> value
     */
    @Override
    public double getDuration() {
        return duration;
    }

    /**
     * <code>getEndBeat</code>
     * 
     * @return a <code>double</code> value
     */
    @Override
    public double getEndBeat() {
        return endBeat;
    }

    /**
     * <code>getMidiNumber</code>
     * 
     * @return an <code>int</code> value
     */
    public int getMidiNumber() {
        return pitch.getMidiNumber();
    }

    /**
     * Get the value of pitch.
     * 
     * @return Value of pitch.
     */
    public Pitch getPitch() {
        return pitch;
    }

    /**
     * <code>getStartBeat</code>
     * 
     * @return a <code>double</code> value
     */
    @Override
    public double getStartBeat() {
        return startBeat;
    }

    /*
     * <p>See Effective Java. </p>
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = PRIME * result + pitch.getMidiNumber();
        return result;
    }

    /**
     * Determines if the time range of the given note (start beat to end beat)
     * overlaps the time range of this note.
     * <p>
     * Uses a tiny fudge factor to exclude start times that match end times.
     * 
     * @param note
     *            - the guy to compare to
     * @return
     */
    public boolean isOverlapping(final Note note) {
        final double fudge = .0000001;
        Range<Double> range = Range.between(this.getStartBeat(), this.getEndBeat());
        Range<Double> range2 = Range.between(note.getStartBeat() + fudge, note.getEndBeat());
        return range.isOverlappedBy(range2);

        // NumberRange range = new NumberRange(this.getStartBeat(), this
        // .getEndBeat());
        // NumberRange range2 = new NumberRange(note.getStartBeat() +
        // Double.MIN_VALUE, note
        // .getEndBeat());
        // return range.overlapsRange(range2);
    }

    // public boolean isOverlapping(final Note note) {
    // final double fudge = .0000001;
    // Range r;
    // final DoubleRange range = new DoubleRange(this.getStartBeat(), this
    // .getEndBeat());
    // final DoubleRange range2 = new DoubleRange(note.getStartBeat() +fudge,
    // note
    // .getEndBeat());
    // return range.overlapsRange(range2);
    //
    // // NumberRange range = new NumberRange(this.getStartBeat(), this
    // // .getEndBeat());
    // // NumberRange range2 = new NumberRange(note.getStartBeat() +
    // Double.MIN_VALUE, note
    // // .getEndBeat());
    // // return range.overlapsRange(range2);
    // }

    /**
     * @return the rest
     */
    public boolean isRest() {
        return rest;
    }

    /**
     * <code>removePropertyChangeListener</code>
     * 
     * @param l
     *            a <code>PropertyChangeListener</code> value
     */
    public void removePropertyChangeListener(final PropertyChangeListener l) {
        changes.removePropertyChangeListener(l);
    }

    /**
     * <code>setBeat</code> is really setStartBeat
     * 
     * @param b
     *            a <code>double</code> value
     */
    public void setBeat(final double b) {
        setStartBeat(b);
    }

    /**
     * <code>setDuration</code>
     * 
     * @param d
     *            a <code>double</code> value
     */
    @Override
    public void setDuration(final double d) {
        final double old = duration;
        duration = d;
        endBeat = startBeat + duration;

        logger.debug("new duration {} new end beat {}", duration, endBeat);

        changes.firePropertyChange(Note.DURATION, Double.valueOf(old), Double.valueOf(duration));
    }

    /**
     * <code>setEndBeat</code>
     * 
     * @param b
     *            a <code>double</code> value
     */
    public void setEndBeat(final double b) {
        // final double old = this.endBeat;
        endBeat = b;
        duration = endBeat - startBeat;
        if (duration <= 0d) {
            throw new IllegalArgumentException("duration cannot be <=0");
        }
        // changes.firePropertyChange(END_BEAT, new Double(old), new Double(
        // this.endBeat));
    }

    /**
     * <code>setMidiNumber</code> sets the pitch property.
     * 
     * @param n
     *            an <code>int</code> value
     */
    public void setMidiNumber(final int n) {
        if ((n <= 0) || (n > 127)) {
            throw new IllegalArgumentException("value must be between 0 and 127");
        }

        this.setPitch(PitchFactory.getPitch(n));

    }

    /**
     * Set the value of pitch. This is a bound property.
     * 
     * @param p
     *            v Value to assign to pitch.
     */
    public void setPitch(final Pitch p) {
        final Pitch old = pitch;
        pitch = p;
        changes.firePropertyChange(Note.PITCH, old, pitch);
    }

    /**
     * <code>setPitch</code> This is a bound property.
     * 
     * @param s
     *            a <code>String</code> value
     */
    public void setPitch(final String s) {
        final Pitch old = pitch;
        pitch = new Pitch(s);
        changes.firePropertyChange(Note.PITCH, old, pitch);
    }

    /**
     * @param rest
     *            the rest to set
     */
    public void setRest(final boolean rest) {
        this.rest = rest;
    }

    /**
     * <code>setStartBeat</code> sets the start beat. The minimum value is 1
     * which is the beginning of the sequence.
     * 
     * @param b
     *            a <code>double</code> value
     */
    @Override
    public void setStartBeat(final double b) {
        if (b < 1d) {
            throw new IllegalArgumentException("Start beat cannot be before 1! " + b);
        }
        final double old = startBeat;
        startBeat = b;
        endBeat = startBeat + duration;

        logger.debug("new start beat {} new end beat {}", startBeat, endBeat);
        changes.firePropertyChange(Note.START_BEAT, Double.valueOf(old), Double.valueOf(startBeat));
    }

    /**
     * @param minorSeconds
     *            can be + or -
     * @return the transposed Note
     */
    public Note transpose(int minorSeconds) {
        pitch = pitch.transpose(minorSeconds);
        return this;
    }

    /**
     * <code>toString</code>
     * 
     * @return a <code>String</code> value
     */
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        final NumberFormat nf = NumberFormat.getInstance();
        nf.setMaximumFractionDigits(2);
        nf.setMinimumFractionDigits(2);
        nf.setMaximumIntegerDigits(3);
        nf.setMinimumIntegerDigits(3);
        sb.append(this.getClass().getSimpleName()).append('[');
        sb.append("startBeat: ").append(nf.format(startBeat));
        sb.append(" pitch: ").append(pitch);
        sb.append(" dur: ").append(nf.format(duration));
        sb.append(" : ").append(getDurationString());

        sb.append(" endBeat: ").append(nf.format(endBeat));
        sb.append(" midinum: ").append(pitch.getMidiNumber());
        sb.append(" rest: ").append(rest);
        sb.append(']');
        return sb.toString();
    }

    public String getDurationString() {
        return DurationParser.getDurationString(duration);
    }

    public String getString() {
        final NumberFormat nf = NumberFormat.getInstance();
        nf.setMaximumFractionDigits(2);
        nf.setMinimumFractionDigits(2);
        nf.setMaximumIntegerDigits(3);
        // nf.setMinimumIntegerDigits(3);
        PitchFormat pf = PitchFormat.getInstance();
        return String.format("%s,%s,%s", pf.format(pitch).trim(), nf.format(startBeat),
                DurationParser.getDurationString(duration));
    }

    /*
     * Clone is evil.
     * 
     * @see com.rockhoppertech.music.Timed#duplicate()
     */
    @Override
    public Timed duplicate() {
        return new Note(this);
    }

    /**
     * @return the spelling
     */
    public String getSpelling() {
        return spelling;
    }

    /**
     * @param spelling the spelling to set
     */
    public void setSpelling(String spelling) {
        this.spelling = spelling;
    }

}