Question: import junit.framework.TestCase; /***************************************************************************** * A Song is a sequence of Note objects. * The song can have a special current element, which is specified and
import junit.framework.TestCase;
/*****************************************************************************
* A Song is a sequence of Note objects.
* The song can have a special "current element," which is specified and
* accessed through four methods that are available in the song class
* (start, getCurrent, advance and hasCurrent).
*
* @note
* (1) The capacity of a song can change after it's created, but
* the maximum capacity is limited by the amount of free memory on the
* machine. The constructor, insert, insertAll, clone,
* and catenation methods will result in an
* OutOfMemoryError when free memory is exhausted.
*
* (2) A song's capacity cannot exceed the maximum integer 2,147,483,647
* (Integer.MAX_VALUE). Any attempt to create a larger capacity
* results in a failure due to an arithmetic overflow.
*
* NB: Neither of these conditions require any work for the implementors (students).
*
*
******************************************************************************/
public class Song implements Cloneable {
/** Static Constants */
private static final String DEFAULT_NAME = "Untitled";
private static final int DEFAULT_BPM = 60;
private static final int INITIAL_CAPACITY = 1;
public static final int MIN_BPM = 20, MAX_BPM = 1000;
/** Fields */
private String _name;
private int _bpm;
private Note[ ] _data;
private int _manyItems;
private int _currentIndex;
// Invariant of the Song class:
// 1. The name is not null.
// 2. The bpm is in the range [MIN_BPM,MAX_BPM]
// 3. The number of elements in the songs is in the instance variable
// manyItems.
// 4. For an empty song (with no elements), we do not care what is
// stored in any of data; for a non-empty song, the elements of the
// song are stored in data[0] through data[manyItems-1], and we
// don't care what's in the rest of data.
// 5. If there is a current element, then it lies in data[currentIndex];
// if there is no current element, then currentIndex equals manyItems.
private static boolean doReport = true; // change onluy in invariant tests
private boolean _report(String error) {
if (doReport) {
System.out.println("Invariant error: " + error);
}
return false;
}
private boolean _wellFormed() {
// Check the invariant.
// 1. name is never null
if (_name == null) return _report("name is null");
// 2. _bpm is in valid range: [MIN_BPM, MAX_BPM]
// TODO
// 3. data array is never null
// TODO
// 4. The data array has at least as many items in it as manyItems
// claims the song has
// TODO
// 5. currentIndex is never negative and never more than the number of
// items in the song.
// TODO
// If no problems discovered, return true
return true;
}
// This is only for testing the invariant. Do not change!
private Song(boolean testInvariant) { }
/**
* Initialize an empty song with name DEFAULT_NAME, bpm DEFAULT_BPM, and
* an initial capacity of INITIAL_CAPACITY. The {@link #insert(Note)} method works
* efficiently (without needing more memory) until this capacity is reached.
* @param - none
* @postcondition
* This song has the default name and BPM, is empty and has an initial capacity of INITIAL_CAPACITY
* @exception OutOfMemoryError
* Indicates insufficient memory for initial array.
**/
public Song( )
{
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of constructor";
}
/**
* Initialize an empty song with a specified name and bpm, an initial
* capacity of INITIAL_CAPACITY. The {@link #insert(Note)} method works
* efficiently (without needing more memory) until this capacity is reached.
* @param name
* the name of this song, must not be null
* @param bpm
* the beats per minute of this song, must be in the range [MIN_BPM,MAX_BPM]
* @postcondition
* This song is empty, has specified name and bpm, and has an initial
* capacity of INITIAL_CAPACITY.
* @throws IllegalArgumentException
* If the name is null, or the bpm is out of the legal range.
* @exception OutOfMemoryError
* Indicates insufficient memory for an array with this many elements.
* new Note[initialCapacity].
**/
public Song(String name, int bpm)
{
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of constructor";
}
/**
* Initialize an empty song with a specified initial capacity.
* The {@link #insert(Note)} method works
* efficiently (without needing more memory) until this capacity is reached.
* @param name
* the name of this song, must not be null
* @param bpm
* the beats per minute of this song, must be in the range [MIN_BPM,MAX_BPM]
* @param initialCapacity
* the initial capacity of this song, must not be negative
* @exception IllegalArgumentException
* Indicates that name, bpm or initialCapacity are invalid
* @exception OutOfMemoryError
* Indicates insufficient memory for an array with this many elements.
* new Note[initialCapacity].
**/
public Song(String name, int bpm, int initialCapacity)
{
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of constructor";
}
/**
* Gets the name of the song
* @return the name
*/
public String getName() {
assert _wellFormed() : "invariant failed at start of getName";
return _name;
}
/**
* Gets the beats per minute of the song.
* @return the bpm
*/
public int getBPM() {
assert _wellFormed() : "invariant failed at start of getBPM";
return _bpm;
}
/**
* Gets the total duration of the song by adding duration of all its notes.
* @return the total duration
*/
public double getDuration() {
assert _wellFormed() : "invariant failed at start of getDuration";
double result = 0;
// TODO
return result;
}
/**
* Sets the name of the song.
* @param newName the new name, must not be null
*/
public void setName(String newName) {
assert _wellFormed() : "invariant failed at start of setName";
// TODO
assert _wellFormed() : "invariant failed at end of setName";
}
/**
* Sets the beats per minute (BPM) of the song.
* @param newBPM the new bpm
* @throws IllegalArgumentException in the new BPM is not in the range [MIN_BPM,MAX_BPM]
*/
public void setBPM(int newBPM) {
assert _wellFormed() : "invariant failed at start of setBPM";
// TODO
assert _wellFormed() : "invariant failed at end of setBPM";
}
/**
* Stretches the song by the given factor, lengthening or shortening its duration.
* If there's a problem with one of the notes, this Song may be left
* partially stretched.
* @param factor the factor to multiply each note's duration by
* @throws IllegalArgumentException if song is transposed where a note's duration
* is beyond the valid bounds
*/
public void stretch(double factor) {
assert _wellFormed() : "invariant failed at start of stretch";
// TODO stretch each note in the song
assert _wellFormed() : "invariant failed at end of stretch";
}
/**
* Transposes the song by the given interval, raising or lowering its pitch.
* If there's a problem with transposing one of the notes, this Song may be left
* partially transposed.
* @param interval the interval to transpose each note in the song
* @throws IllegalArgumentException if song is transposed where a note is beyond the bounds
* of valid MIDI pitch values [0,127]
*/
public void transpose(int interval) {
assert _wellFormed() : "invariant failed at start of transpose";
// TODO transpose each note in the song
assert _wellFormed() : "invariant failed at end of transpose";
}
/**
* Add a new element to this song, before the current element (if any).
* If the new element would take this song beyond its current capacity,
* then the capacity is increased before adding the new element.
* @param element
* the new element that is being added
* @postcondition
* A new copy of the element has been added to this song. If there was
* a current element, then the new element is placed before the current
* element. If there was no current element, then the new element is placed
* at the end of the song. In all cases, the new element becomes the
* new current element of this song.
* @exception OutOfMemoryError
* Indicates insufficient memory for increasing the song's capacity.
**/
public void insert(Note element)
{
assert _wellFormed() : "invariant failed at start of insert";
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of insert";
}
/**
* Move forward, so that the current element is now the next element in
* this song.
* @param - none
* @precondition
* hasCurrent() returns true.
* @postcondition
* If the current element was already the end element of this song
* (with nothing after it), then there is no longer any current element.
* Otherwise, the new element is the element immediately after the
* original current element.
* @exception IllegalStateException
* Indicates that there is no current element, so
* advance may not be called.
**/
public void advance( )
{
assert _wellFormed() : "invariant failed at start of advance";
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of advance";
}
/**
* Generate a copy of this song.
* @param - none
* @return
* The return value is a copy of this song. Subsequent changes to the
* copy will not affect the original, nor vice versa.
* @exception OutOfMemoryError
* Indicates insufficient memory for creating the clone.
**/
public Song clone( ) {
assert _wellFormed() : "invariant failed at start of clone";
Song answer;
try
{
answer = (Song) super.clone( );
}
catch (CloneNotSupportedException e)
{ // This exception should not occur. But if it does, it would probably
// indicate a programming error that made super.clone unavailable.
// The most common error would be forgetting the "Implements Cloneable"
// clause at the start of this class.
throw new RuntimeException
("This class does not implement Cloneable");
}
// all that is needed is to clone the data array.
// (Exercise: Why is this needed?)
answer._data = _data.clone( );
assert _wellFormed() : "invariant failed at end of clone";
assert answer._wellFormed() : "invariant on answer failed at end of clone";
return answer;
}
/**
* Place the contents of another song (which may be the
* same song as this!) into this song before the current element.
* @param addend
* a song whose contents will be placed into this song
* @precondition
* The parameter, addend, is not null.
* @postcondition
* The elements from addend have been placed into
* this song. The current element of this song is now
* the first element inserted (if any). If the added song
* is empty, this song and the current element (if any) are
* unchanged.
* @exception NullPointerException
* Indicates that addend is null.
* @exception OutOfMemoryError
* Indicates insufficient memory to increase the size of this song.
**/
public void insertAll(Song addend)
{
assert _wellFormed() : "invariant failed at start of insertAll";
// TODO: Implemented by student.
// Watch out for the this==addend case!
// (It is possible to write code that works for this case AND
// the normal case, but you have to be very careful.)
assert _wellFormed() : "invariant failed at end of insertAll";
assert addend._wellFormed() : "invariant of addend broken in insertAll";
}
/**
* Create a new song that contains all the elements from one song
* followed by another. The new BPM is the average of the two songs,
* and the name is the concatenation of the two names separated by " and "
* @param s1
* the first of two songs
* @param s2
* the second of two songs
* @precondition
* Neither s1 nor s2 is null.
* @return
* a new song that has the elements of s1 followed by the
* elements of s2 (with no current element).
* @exception NullPointerException.
* Indicates that one of the arguments is null.
* @exception OutOfMemoryError
* Indicates insufficient memory for the new song.
**/
public static Song catenation(Song s1, Song s2)
{
assert s1._wellFormed() : "invariant of s1 failed at start of catenation";
assert s2._wellFormed() : "invariant of s2 failed at start of catenation";
Song res;
// TODO: Implemented by student.
assert s1._wellFormed() : "invariant of s1 failed at end of catenation";
assert s2._wellFormed() : "invariant of s2 failed at end of catenation";
assert res._wellFormed() : "invariant of res failed at end of catenation";
return res;
}
/**
* Change the current capacity of this song as needed so that
* the capacity is at least as big as the parameter.
* This code must work correctly and efficiently if the minimum
* capacity is (1) smaller or equal to the current capacity (do nothing)
* (2) at most double the current capacity (double the capacity)
* or (3) more than double the current capacity (new capacity is the
* minimum passed).
* @param minimumCapacity
* the new capacity for this song
* @postcondition
* This song's capacity has been changed to at least minimumCapacity.
* If the capacity was already at or greater than minimumCapacity,
* then the capacity is left unchanged.
* @exception OutOfMemoryError
* Indicates insufficient memory for: new array of minimumCapacity elements.
**/
private void ensureCapacity(int minimumCapacity)
{
// TODO: Implemented by student.
// NB: do not check invariant
}
/**
* Accessor method to get the current element of this song.
* @param - none
* @precondition
* hasCurrent() returns true.
* @return
* the current element of this song
* @exception IllegalStateException
* Indicates that there is no current element, so
* getCurrent may not be called.
**/
public Note getCurrent( )
{
assert _wellFormed() : "invariant failed at start of getCurrent";
// TODO: Implemented by student.
// Don't change "this"!
}
/**
* Accessor method to determine whether this song has a specified
* current element that can be retrieved with the
* getCurrent method.
* @param - none
* @return
* true (there is a current element) or false (there is no current element at the moment)
**/
public boolean hasCurrent( )
{
assert _wellFormed() : "invariant failed at start of hasCurrent";
// TODO: Implemented by student.
}
/**
* Remove the current element from this song.
* @param - none
* @precondition
* hasCurrent() returns true.
* @postcondition
* The current element has been removed from this song, and the
* following element (if there is one) is now the new current element.
* If there was no following element, then there is now no current
* element.
* @exception IllegalStateException
* Indicates that there is no current element, so
* removeCurrent may not be called.
**/
public void removeCurrent( )
{
assert _wellFormed() : "invariant failed at start of removeCurrent";
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of removeCurrent";
}
/**
* Determine the number of elements in this song.
* @param - none
* @return
* the number of elements in this song
**/
public int size( )
{
assert _wellFormed() : "invariant failed at start of size";
// TODO: Implemented by student.
}
/**
* Set the current element at the front of this song.
* @param - none
* @postcondition
* The front element of this song is now the current element (but
* if this song has no elements at all, then there is no current
* element).
**/
public void start( )
{
assert _wellFormed() : "invariant failed at start of start";
// TODO: Implemented by student.
assert _wellFormed() : "invariant failed at end of start";
}
/**
* The Immutable Class Note.
*/
public class Note {
/** Static Constants */
public static final int DEFAULT_INTENSITY = 50;
public static final int REST_PITCH = 128; // First illegal pitch, used for rests.
private static final int PITCHES_PER_OCTAVE = 12;
private static final String[] NOTE_LETTERS = {"c","c#","d","d#","e","f","f#","g","g#","a","a#","b"};
private static final double MIN_DURATION = 1.0/64, // One sixty-fourth
MAX_DURATION = 8.0; // Eight whole notes
/** Fields (Immutable) */
private final String pitch;
private final int midiValue;
private final double duration;
/**
* Instantiates a new note based on a string denoting note letter and octave.
*
* @param pitch the pitch (e.g. "f6")
* @param duration the duration
* @throws NullPointerException if pitch is null
* @throws IllegalArgumentException if:
* 1. The pitch parameter is malformed or out of range.
* 2. The duration parameter is out of range.
*/
public Note(String pitch, double duration) {
// #(
this(toMidi(pitch), duration);
// #)
// Recommended: First implement toMidi(String).
}
/**
* Instantiates a new note based on MIDI value.
*
* @param midiValue the MIDI value (e.g. 68)
* @param duration the duration
* @throws IllegalArgumentException if:
* 1. The MIDI pitch parameter is out of range.
* 2. The duration parameter is out of range.
*/
public Note(int midiValue, double duration) {
// #(
String pitch = toPitch(midiValue);
if (pitch == null)
throw new IllegalArgumentException("Invalid MIDI value: " + midiValue + " -- Must be [0, 128]");
if (duration < MIN_DURATION || duration > MAX_DURATION)
throw new IllegalArgumentException("Invalid note duration: " + duration+" -- Must be [1/64, 8]");
this.pitch = pitch;
this.midiValue = midiValue;
this.duration = duration;
// #)
// Recommended: First implement toPitch(int).
}
/**
* Instantiates a new note from a String matching the format of Note's toString() method.
*
* @param note the string representation
*
* @throws IndexOutOfBoundsException if parameter isn't in correct format
* @throws NumberFormatException if duration representation cannot be parsed as double
* @throws IllegalArgumentException if the elements in the format are not permitted.
*/
public Note(String note) {
this(note.split(" x ")[0], Double.parseDouble(note.split(" x ")[1]));
}
/**
* Converts a pitch string to a MIDI value.
* The pitch "rest" should return {@link #REST_PITCH}.
*
* @param pitch the pitch to convert
* @throws NullPointerException if pitch is null
* @throws IllegalArgumentException is the String is not a legal pitch
* @return the MIDI value
*/
public static int toMidi(String pitch) {
// #(
int result;
if (pitch.equals("rest")) // force NPE
result = REST_PITCH;
else {
try {
String[] tokens = pitch.split("(?=\\d)", 2);
findNote: if (tokens.length == 2) {
int octave = Integer.parseInt(tokens[1]);
for (int i = 0; i < NOTE_LETTERS.length; i++) {
if (NOTE_LETTERS[i].equals(tokens[0])) {
// i is the offset from the octave's "c" note
result = i + PITCHES_PER_OCTAVE * octave;
if (result < 0 || result >= REST_PITCH)
throw new IllegalArgumentException("pitch out of legal range.");
break findNote;
}
}
throw new IllegalArgumentException("unknown note: '" + tokens[0] + "'");
} else {
throw new IllegalArgumentException("can't parse '" + pitch + "'");
}
} catch (IllegalArgumentException ex) {
throw ex;
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
return result;
/* #)
return -1;
## */
}
/**
* Converts a MIDI value to a pitch string.
* The MIDI value 128 should return "rest".
*
* @param midiValue the MIDI value to convert
* @throws IllegalArgumentException if the MIDI value is outside of legal range
* @return the pitch string
*/
public static String toPitch(int midiValue) {
// #(
String result;
if (midiValue >= 0 && midiValue < REST_PITCH) {
int noteIndex = midiValue % PITCHES_PER_OCTAVE;
int octave = midiValue / PITCHES_PER_OCTAVE;
result = NOTE_LETTERS[noteIndex] + (int) octave;
}
else if (midiValue == REST_PITCH)
result = "rest";
else throw new IllegalArgumentException("pitch out of range");
return result;
/* #)
return null;
## */
}
/**
* Gets the pitch string of this note.
*
* @return the pitch
*/
public String getPitch() { return pitch; }
/**
* Gets the MIDI value of this note.
*
* @return the MIDI value
*/
public int getMidiPitch() { return midiValue; }
/**
* Gets the duration of this note.
*
* @return the duration
*/
public double getDuration() { return duration; }
/**
* Returns a new note with the same pitch, but with its duration multiplied by the parameter.
*
* @param factor the amount to scale by
* @throws IllegalArgumentException if resultant duration is outside of valid range
* @return the stretched note
*/
public Note stretch(double factor) {
// #(
return new Note(pitch, duration * factor);
/* #)
return null;
## */
}
/**
* Returns a (new) note with the same duration, but transposed by the given interval.
*
* @param interval the interval to transpose by
* @throws IllegalArgumentException if note is transposed beyond valid bounds [c0, g10]
* @return the transposed note
*/
public Note transpose(int interval) {
// #(
Note result = null;
if (midiValue == REST_PITCH)
result = this;
else {
int newMidiValue = midiValue + interval;
if (newMidiValue == REST_PITCH)
newMidiValue++;
result = new Note(newMidiValue, duration);
}
return result;
/* #)
return null;
## */
}
/**
* Returns a string representation of this Note.
* It should follow the format found in songs/InMyLife.song, namely:
* For a Note with pitch "g#4" and duration 1.0625 -> "g#4 x 1.0625"
* NB1: Identical spacing and format are important!
* NB2: For a "rest" note, the same format must be used (including duration).
*
* @return the string representation
*/
@Override
public String toString() {
// #(
return pitch + " x " + duration;
/* #)
return null;
## */
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
// #(
if (o instanceof Note) {
Note other = (Note) o;
return this.pitch.equals(other.pitch) &&
this.midiValue == other.midiValue &&
this.duration == other.duration;
}
// #)
// Return equal if the argument is a Note and the midiValue and duration are equal
return false;
}
@Override
public int hashCode() {
// #(
long durHash = Double.doubleToLongBits(duration);
durHash = durHash ^ (durHash >> 32);
return (int) (durHash + midiValue);
/* #)
// Compute hash using pieces. (Don't take hash code of strings.)
return -1;
## */
}
}
Step by Step Solution
There are 3 Steps involved in it
Get step-by-step solutions from verified subject matter experts
