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

1 Expert Approved Answer
Step: 1 Unlock blur-text-image
Question Has Been Solved by an Expert!

Get step-by-step solutions from verified subject matter experts

Step: 2 Unlock
Step: 3 Unlock

Students Have Also Explored These Related Databases Questions!