|
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);
}
}
}