Abstract Classes and Interfaces
Design Issues |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
An Initial Design
An Improved Design
More Improvements
Eliminating the MadisonMusicMachine
Class
Synchronizing the Music and Lyrics
Improved Synchronization
Adding a Presenter
Interface
Combining the Lyrics
and Music
Classes
Adding a PlayList
Class
AbstractLineElement
/** * An element in a line of music * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public abstract class AbstractLineElement { private int durationInMillis, wholeNoteInMillis=1500; private String value; /** * Get the duration of this element in milliseconds * (at the normal tempo) * * @return The duration in milliseconds */ public int getDurationInMillis() { return durationInMillis; } /** * Get a String representation of the value of this * * @return The value */ public String getValue() { return value; } /** * Get the duration of this element * * @param beats The beats in this element (whole=1, half=2, quarter=4) * @param dotted Whether this element is dotted */ protected void setDuration(int beats, boolean dotted) { durationInMillis = wholeNoteInMillis / beats; if (dotted) durationInMillis *= 1.5; } /** * Set the String representation of this element * * @param value The value */ protected void setValue(String value) { this.value = value; } }
Lyric
/** * A lyric in a song * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Lyric extends AbstractLineElement { /** * Explicit Value Constructor * * @param text The text/word * @param beats The number of beats (whole=1, half=2, quarter=4) * @param dotted Dotted or not */ public Lyric(String text, int beats, boolean dotted) { setValue(text); setDuration(beats, dotted); } }
Note
/** * A note in a song * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Note extends AbstractLineElement { private boolean sharp; private char pitch; /** * Default Constructor * * Constructs a whole note at middle C */ public Note() { this('C', false, 0, 1, false); } /** * Explicit Value Constructor * * @param pitch The pitch ('A','B','C','D','E','F', 'G' or 'R') * @param sharp true for a sharp and false for a natural * @param octave The octave (relative to middle C) * @param duration 1 for whole notes, 2 for half notes, etc... * @param dotted Whether the note is dotted */ public Note(char pitch, boolean sharp, int octave, int duration, boolean dotted) { int durationInMillis, midiBase, midiNumber; // Store the pitch and duration this.pitch = Character.toUpperCase(pitch); // Correct for possible "mistakes" (B# and E#) // (Alternatively, we could treat a B# as a C and an // E# as an F) if ((pitch == 'B') || (pitch == 'E')) this.sharp = false; else this.sharp = sharp; // Calculate the MIDI value midiBase = 60; if (pitch == 'A') midiNumber = midiBase + 9; else if (pitch == 'B') midiNumber = midiBase + 11; else if (pitch == 'C') midiNumber = midiBase + 0; else if (pitch == 'D') midiNumber = midiBase + 2; else if (pitch == 'E') midiNumber = midiBase + 4; else if (pitch == 'F') midiNumber = midiBase + 5; else if (pitch == 'G') midiNumber = midiBase + 7; else midiNumber = -1; // Rest if (this.sharp) midiNumber = midiNumber + 1; midiNumber = midiNumber + (octave * 12); setValue(""+midiNumber); setDuration(duration, dotted); } }
Presenter
/** * Requirements of an object that can display the elements * in a Part * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public interface Presenter { /** * Set the value to be used the next time this Presnter * is turned on * * @param value The value */ public abstract void setValue(String value); /** * Turn this presenter off */ public abstract void turnOff(); /** * Turn this presenter on */ public abstract void turnOn(); }
Console
/** * An object that displays text on the console * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Console implements Presenter { private String text; /** * Set the value to be used the next time this Presnter * is turned on (required by Presenter) * * In this case, it is the text to display * * @param value The value */ public void setValue(String value) { text = value; } /** * Turn this presenter off (required by Presenter) * * In this case, this method does nothing */ public void turnOff() { } /** * Turn this presenter on (required by Presenter) * * In this case, this method outputs the value to * the console */ public void turnOn() { System.out.println(text); } }
InstrumentSynthesizer
import javax.sound.midi.*; /** * An object that synthesizes the sounds made by an instrument * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class InstrumentSynthesizer implements Presenter { int midiNote; MidiChannel channel; /** * Explicit Value Constructor * * @param channel The MIDI channel on the synthesizer to use */ public InstrumentSynthesizer(MidiChannel channel) { this.channel = channel; } /** * Set the value to be used the next time this Presnter * is turned on (required by Presenter) * * In this case, it is a String representation of the * MIDI note value * * @param value The value */ public void setValue(String value) { midiNote = Integer.parseInt(value); } /** * Turn this presenter off (required by Presenter) * * In this case, this method turns the MIDI channel * off */ public void turnOff() { channel.noteOff(midiNote); } /** * Turn this presenter on (required by Presenter) * * In this case, this method turns the MIDI channel * on */ public void turnOn() { channel.noteOn(midiNote, 127); } }
MetronomeListener
/** * Requirements of a MetronomeListener * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public interface MetronomeListener { /** * Handle a "tick" of the Metronome * * @param metronome The source of the "tick" */ public abstract void handleTick(Metronome metronome); }
Part
import java.awt.event.*; import java.util.*; /** * A Part (i.e., a sequence of Note or Lyric objects) in a Score * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Part implements MetronomeListener { private AbstractLineElement element; private AbstractLineElement[] elements; private int currentIndex, duration; private int elapsed, lastIndex; private Presenter presenter; /** * Explicit Value Constructor * * @param size The number of elements in this Part */ public Part(int size) { elements = new AbstractLineElement[size]; currentIndex = 0; lastIndex = 0; duration = 0; } /** * Get an AbstractLineElement */ public AbstractLineElement getElement(int i) { AbstractLineElement element; element = null; if ((i >= 0) && (i < elements.length)) { element = elements[i]; } return element; } /** * Handle a "tick" of the Metronome (required by MetronomeListener) * * @param metronome The source of the "tick" */ public void handleTick(Metronome metronome) { elapsed += metronome.DELAY_IN_MILLIS; if (elapsed >= duration) { presenter.turnOff(); element = getElement(currentIndex); currentIndex++; if (element != null) { duration = element.getDurationInMillis(); presenter.setValue(element.getValue()); presenter.turnOn(); } elapsed = 0; } } /** * Set an AbstractLineElement * * @param i The index * @param element The element */ public void setElement(int i, AbstractLineElement element) { if ((i >= 0) && (i < elements.length)) { elements[i] = element; } } /** * Set the Presenter to use when "presenting" this * Part * * @param presenter The Presenter to use */ public void setPresenter(Presenter presenter) { this.presenter = presenter; } }
Song
import java.awt.event.*; /** * A Song * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class Song implements MetronomeListener { private Part[] parts; private String title; /** * Explicit Value Constructor * * @param numberOfParts The number of Part objects in this Song */ public Song(int numberOfParts) { parts = new Part[numberOfParts]; } /** * Set a Part * * @param i The index * @param part The Part */ public void setPart(int i, Part part) { parts[i] = part; } /** * Handle a "tick" of the Metronome (required by MetronomeListener) * * In this case, this method just delegates to the * component Part objects * * @param metronome The source of the "tick" */ public void handleTick(Metronome metronome) { for (int i=0; i<parts.length; i++) { if (parts[i] != null) parts[i].handleTick(metronome); } } }