jm.music.data.Phrase Maven / Gradle / Ivy
Show all versions of jmusic Show documentation
/*
Copyright (C) 2000 Andrew Sorensen & Andrew Brown
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or any
later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jm.music.data;
import jm.JMC;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;
/**
* The Phrase class is representative of a single musical phrase.
* Phrases are held in Parts and can be played at any time
* based on there start times. They may be played sequentially or in parallel.
* Phrases can be added to an Part like this...
*
* Part inst = new Part("Flute");
* //Phrase for the right hand
* Phrase rightHand = new Phrase(0.0) //start this phrase on the first beat
* //Phrase for the left hand
* Phrase leftHane = new Phrase(4.0) //start this phrase on the fifth beat
* inst.addPhrase(rightHand);
* inst.addPhrase(leftHand);
*
*
* @author Andrew Sorensen and Andrew Brown
* @version 1.0, Sun Feb 25 18:43:32 2001
* @see Note
* @see Part
*/
public class Phrase implements JMC, Cloneable, Serializable {
//----------------------------------------------
// Limits
//-----------------------------------------------
/**
* The smallest start time in beats
*/
public static final double MIN_START_TIME = 0.0;
//----------------------------------------------
// Default constants
//----------------------------------------------
public static final double DEFAULT_START_TIME = MIN_START_TIME;
public static final String DEFAULT_TITLE = "Untitled Phrase";
public static final int DEFAULT_INSTRUMENT = NO_INSTRUMENT;
public static final boolean DEFAULT_APPEND = false;
public static final double DEFAULT_TEMPO = -1.0;
public static final double DEFAULT_PAN = Note.DEFAULT_PAN;
/**
* The pan position for notes in this phrase.
* This must be set delibertley to override a note's pan position.
*/
private double pan = DEFAULT_PAN;
public static final int DEFAULT_NUMERATOR = 4;
//----------------------------------------------
// Attributes
//----------------------------------------------
public static final int DEFAULT_DENOMINATOR = 4;
/**
* An array containing mutiple voices
*/
private Vector noteList;
// /** The phrases start time in beats */
// private double startTime;
/**
* The title/name given to this phrase
*/
private String title = "Unnamed Phrase";
private Position position;
/**
* instrumet / MIDI program change number for this phrase
*/
private int instrument;
/**
* speed in beats per minute for this phrase
*/
private double tempo;
/**
* Setting the phrase to append when added to a part
* rather than use its start time.
*/
private boolean append = false;
/**
* A phrase to have a relative start time with if required.
*/
private Phrase linkedPhrase;
/**
* the top number of the time signature
*/
private int numerator;
/**
* the bottom number of the time signature
*/
private int denominator;
/**
* A reference to this phrases part
*/
private Part myPart = null;
/**
* Weather the phrase should play or not
*/
private boolean mute = false;
//----------------------------------------------
// Constructors
//----------------------------------------------
/**
* Creates an empty Phrase.
* The default start time is a flag which means the phrase will be
* appended to the end of any part it is added to.
*/
public Phrase() {
this(DEFAULT_START_TIME);
this.append = true;
}
/**
* Creates an empty Phrase starting at the specified beat.
*
* @param startTime The beat at which the phrase will be positioned in its part.
*/
public Phrase(double startTime) {
this(startTime, DEFAULT_INSTRUMENT);
}
/**
* Creates an empty Phrase
*
* @param startTime The beat at which the phrase will be positioned in its part.
* @param instrument The sound or instrument number to be used for this phrase.
*/
public Phrase(double startTime, int instrument) {
this(DEFAULT_TITLE, startTime, instrument);
}
/**
* Creates an empty Phrase
*
* @param title The name for the phrase.
*/
public Phrase(String title) {
this(title, DEFAULT_START_TIME);
this.append = true;
}
/**
* Creates an empty Phrase.
*
* @param title The name for the phrase.
* @param startTime The beat at which the phrase will be positioned in its part.
*/
public Phrase(String title, double startTime) {
this(title, startTime, DEFAULT_INSTRUMENT);
}
/**
* Creates an empty Phrase.
*
* @param title The name for the phrase.
* @param startTime The beat at which the phrase will be positioned in its part.
* @param instrument The sound or instrument number to be used for this phrase.
*/
public Phrase(String title, double startTime, int instrument) {
this(title, startTime, instrument, DEFAULT_APPEND);
}
/**
* Creates an empty Phrase.
*
* @param title The name for the phrase.
* @param startTime The beat at which the phrase will be positioned in its part.
* @param instrument The sound or instrument number to be used for this phrase.
* @param append A flag specifying wheather or not this phrase should be added to the
* end of the part it is added too, or should use its start time value.
*/
public Phrase(String title, double startTime, int instrument, boolean append) {
this.title = title;
// this.startTime = startTime;
this.position = new Position(startTime, this);
this.append = append;
if (instrument < NO_INSTRUMENT) {
System.err.println(new Exception("jMusic EXCEPTION: instrument " +
"value must be greater than 0"));
(new Exception()).printStackTrace();
System.exit(1); //crash ungracefully
}
this.instrument = instrument;
this.noteList = new Vector();
this.numerator = DEFAULT_NUMERATOR;
this.denominator = DEFAULT_DENOMINATOR;
this.tempo = DEFAULT_TEMPO;
}
/**
* Constructs a new Phrase containing the specified note
.
*
* @param note Note to be containing by the phrase.
*/
public Phrase(Note note) {
this();
addNote(note);
}
/**
* Constructs a new Phrase containing the specified notes
.
*
* @param notes array of Note to be contained by the phrase.
*/
public Phrase(Note[] notes) {
this();
addNoteList(notes);
}
/**
* Constructs a new Phrase containing the specified note
with
* the specified title
.
*
* @param note Note to be containing by the phrase.
* @param title String describing the title of the Phrase.
*/
public Phrase(Note note, String title) {
this(title);
addNote(note);
}
/**
* Constructs a new Phrase containing the specified notes
with
* the specified title
.
*
* @param notes array of Note to be contained by the phrase.
* @param title String describing the title of the Phrase.
*/
public Phrase(Note[] notes, String title) {
this(title);
this.addNoteList(notes);
}
/**
* Constructs a new Phrase containing the specified note
with
* the specified title
.
*
* @param note Note to be containing by the phrase.
* @param title String describing the title of the Phrase.
*/
public Phrase(Note note, double startTime) {
this(note);
this.setStartTime(startTime);
}
//----------------------------------------------
// Data Methods
//----------------------------------------------
/**
* Return the program change assigned by this phrase
*
* @return int
*/
public int getInstrument() {
return this.instrument;
}
/**
* Sets the program change value
*
* @param int program change
*/
public void setInstrument(int value) {
this.instrument = value;
}
/**
* Add a note to this Phrase
*
* @param Note note - add a note to this phrase
*/
public void addNote(Note note) {
note.setMyPhrase(this);
noteList.addElement(note);
}
/**
* Add a note to this Phrase
*
* @param pitch -the pitch of the note
* @param rv - the rhythmValue of the note
*/
public void addNote(int pitch, double rv) {
Note note = new Note(pitch, rv);
this.addNote(note);
}
/**
* Add a note to this Phrase
*
* @param Note note - add a note to this phrase
*/
public void add(Note note) {
this.addNote(note);
}
/**
* Add a rest to this Phrase
*
* @param Rest rest - The rest to be added to this phrase
*/
public void addRest(Rest newRest) {
newRest.setMyPhrase(this);
noteList.addElement(newRest);
}
/**
* Appends the specified notes to the end of this Phrase.
*
* @param array of Notes to append.
*/
public void addNoteList(Note[] notes) {
for (int i = 0; i < notes.length; i++) {
this.addNote(notes[i]);
}
}
/**
* Adds a vector of notes to the phrase.
* A boolean option when true appends the notes to the end of the list,
* if false the notes in noteVector will replace the notes currently in the phrase.
*
* @param noteVector the vector of notes to add
* @param append do we append or not?
*/
public void addNoteList(Vector noteVector, boolean append) {
Enumeration enum1 = noteVector.elements();
if (!append) this.noteList.removeAllElements();
while (enum1.hasMoreElements()) {
try {
Note note = (Note) enum1.nextElement();
this.addNote(note);
//note.setMyPhrase(this);
} catch (RuntimeException re) {
System.err.println("The vector passed to this method must " + "contain Notes only!");
}
}
}
/**
* Adds an array of notes to the phrase.
* A boolean option when true appends the notes to the end of the list.
*
* @param noteArray the array of notes to add
* @param append do we append or not?
*/
public void addNoteList(Note[] noteArray, boolean append) {
if (!append) this.noteList.removeAllElements();
for (int i = 0; i < noteArray.length; i++) {
this.addNote(noteArray[i]);
}
}
/**
* Adds Multiple notes to the phrase from a pitch array and rhythm value
*
* @param pitchArray array of pitch values
* @param rhythmValue a rhythmic value
*/
public void addNoteList(int[] pitchArray, double rhythmValue) {
double[] rvArray = new double[pitchArray.length];
for (int i = 0; i < rvArray.length; i++) {
rvArray[i] = rhythmValue;
}
this.addNoteList(pitchArray, rvArray);
}
/**
* Adds Multiple notes to the phrase from a pitch array, rhythm value, and dynmaic value
*
* @param pitchArray - An array of pitch values
* @param rhythmValue - A rhythmic value
* @param dynamic - A dynmaic value (1-127)
*/
public void addNoteList(int[] pitchArray, double rhythmValue, int dynamic) {
double[] rvArray = new double[pitchArray.length];
int[] dynArray = new int[pitchArray.length];
for (int i = 0; i < rvArray.length; i++) {
rvArray[i] = rhythmValue;
dynArray[i] = dynamic;
}
this.addNoteList(pitchArray, rvArray, dynArray);
}
/**
* Adds Multiple notes to the phrase from an array of frequency values
*
* @param freqArray array of freequency values
* @param rhythmValue a rhythmic value
*/
public void addNoteList(double[] freqArray, double rhythmValue) {
double[] rvArray = new double[freqArray.length];
for (int i = 0; i < rvArray.length; i++) {
rvArray[i] = rhythmValue;
}
this.addNoteList(freqArray, rvArray);
}
/**
* Adds Multiple notes to the phrase from several arrays
*
* @param pitchArray array of pitch values
* @param rhythmArray array of rhythmic values
*/
public void addNoteList(int[] pitchArray, double[] rhythmArray) {
int[] dynamic = new int[pitchArray.length];
for (int i = 0; i < pitchArray.length; i++) {
dynamic[i] = Note.DEFAULT_DYNAMIC;
}
this.addNoteList(pitchArray, rhythmArray, dynamic);
}
/**
* Adds Multiple notes to the phrase from several arrays
*
* @param freqArray array of frequency values
* @param rhythmArray array of rhythmic values
*/
public void addNoteList(double[] freqArray, double[] rhythmArray) {
int[] dynamic = new int[freqArray.length];
for (int i = 0; i < freqArray.length; i++) {
dynamic[i] = Note.DEFAULT_DYNAMIC;
}
this.addNoteList(freqArray, rhythmArray, dynamic);
}
/**
* Adds Multiple notes to the phrase from several arrays
*
* @param pitchArray array of pitch values
* @param rhythmArray array of rhythmic values
* @param dynmaic array of dynamic values
*/
public void addNoteList(int[] pitchArray, double[] rhythmArray,
int[] dynamic) {
this.addNoteList(pitchArray, rhythmArray, dynamic, true);
}
/**
* Adds Multiple notes to the phrase from several arrays
*
* @param freqArray array of frequency values
* @param rhythmArray array of rhythmic values
* @param dynmaic array of dynamic values
*/
public void addNoteList(double[] freqArray, double[] rhythmArray,
int[] dynamic) {
this.addNoteList(freqArray, rhythmArray, dynamic, true);
}
/**
* Adds Multiple notes to the phrase from several arrays
* A boolean option when true appends the notes to the end of the list
* if non true the current list is errased and replaced by the new notes
*
* @param pitchArray array of pitch values
* @param rhythmArray array of rhythmic values
* @param dynamic int
* @param append do we append or not?
*/
public void addNoteList(int[] pitchArray, double[] rhythmArray,
int[] dynamic, boolean append) {
if (!append) this.noteList.removeAllElements();
for (int i = 0; i < pitchArray.length; i++) {
try {
Note knote = new Note(pitchArray[i], rhythmArray[i], dynamic[i]);
this.addNote(knote);
} catch (RuntimeException re) {
System.err.println("You must enter arrays of even length");
}
}
}
/**
* Adds Multiple notes to the phrase from several arrays
* A boolean option when true appends the notes to the end of the list
* if non true the current list is errased and replaced by the new notes
*
* @param freqArray array of frequency values
* @param rhythmArray array of rhythmic values
* @param dynamic int
* @param append do we append or not?
*/
public void addNoteList(double[] freqArray, double[] rhythmArray,
int[] dynamic, boolean append) {
if (!append) this.noteList.removeAllElements();
for (int i = 0; i < freqArray.length; i++) {
try {
Note knote = new Note(freqArray[i], rhythmArray[i], dynamic[i]);
this.addNote(knote);
} catch (RuntimeException re) {
System.err.println("jMusic Phrase error: You must enter arrays of even length");
}
}
}
/**
* Adds Multiple notes to the phrase from one array of pitch, rhythm pairs
*
* @param pitchAndRhythmArray - an array of pitch and rhythm values
*/
public void addNoteList(double[] pitchAndRhythmArray) {
for (int i = 0; i < pitchAndRhythmArray.length; i += 2) {
try {
Note knote = new Note((int) pitchAndRhythmArray[i], pitchAndRhythmArray[i + 1]);
this.addNote(knote);
} catch (RuntimeException re) {
System.err.println("Error adding note list: Possibly the wrong number of values in the pitch and rhythm array.");
}
}
}
/**
* Adds Multiple notes to the phrase from one pitch and an array of rhythm values
*
* @param pitch The pitch values for the notes
* @param rhythms An array of rhythm values
*/
public void addNoteList(int pitch, double[] rhythms) {
for (int i = 0; i < rhythms.length; i++) {
this.addNote(new Note(pitch, rhythms[i]));
}
}
/**
* Adds Multiple notes to the phrase from one pitch and an array of rhythm values
*
* @param frequency The pitch values for the notes in hertz
* @param rhythms An array of rhythm values
*/
public void addNoteList(double frequency, double[] rhythms) {
for (int i = 0; i < rhythms.length; i++) {
this.addNote(new Note(frequency, rhythms[i]));
}
}
/**
* Adds Multiple notes to the phrase all of which start at the same time
* and share the same duration.
*
* @param pitches An array of pitch values
* @param rv the rhythmValue
*/
public void addChord(int[] pitches, double rv) {
for (int i = 0; i < pitches.length - 1; i++) {
Note n = new Note(pitches[i], 0.0);
n.setDuration(rv * Note.DEFAULT_DURATION_MULTIPLIER);
this.addNote(n);
}
this.addNote(pitches[pitches.length - 1], rv);
//System.out.println("In phrase" + this.toString());
}
public int[] getPitchArray() {
Note[] notes = this.getNoteArray();
int[] pitches = new int[notes.length];
for (int i = 0; i < notes.length; i++) {
pitches[i] = notes[i].getPitch();
}
return pitches;
}
public double[] getRhythmArray() {
Note[] notes = this.getNoteArray();
double[] rhythms = new double[notes.length];
for (int i = 0; i < notes.length; i++) {
rhythms[i] = notes[i].getRhythmValue();
}
return rhythms;
}
public int[] getDynamicArray() {
Note[] notes = this.getNoteArray();
int[] dynamics = new int[notes.length];
for (int i = 0; i < notes.length; i++) {
dynamics[i] = notes[i].getPitch();
}
return dynamics;
}
/**
* Deletes the specified note in the phrase
*
* @param int noteNumb the index of the note to be deleted
*/
public void removeNote(int noteNumb) {
Vector vct = (Vector) this.noteList;
try {
vct.removeElement(vct.elementAt(noteNumb));
} catch (RuntimeException re) {
System.err.println("Note index to be deleted must be within the phrase.");
}
}
/**
* Deletes the first occurence of the specified note in the phrase
*
* @param note the note object to be deleted.
*/
public void removeNote(Note note) {
this.noteList.removeElement(note);
}
/**
* Deletes the last note in the phrase
*/
public void removeLastNote() {
Vector vct = (Vector) this.noteList;
vct.removeElementAt(vct.size() - 1);
}
/**
* Returns the entire note list contained in a single voice
*
* @return Vector A vector containing all Note objects in this phrase
*/
public Vector getNoteList() {
return this.noteList;
}
/**
* Replaces the entire note list with a new note list vector
*
* @param Vector of notes
*/
public void setNoteList(Vector newNoteList) {
this.noteList = newNoteList;
}
/**
* Returns the all notes in the phrase as a array of notes
*
* @return Note[] An array containing all Note objects in this phrase
*/
public Note[] getNoteArray() {
Vector vct = (Vector) this.noteList;
Note[] noteArray = new Note[vct.size()];
for (int i = 0; i < noteArray.length; i++) {
noteArray[i] = (Note) vct.elementAt(i);
}
return noteArray;
}
/**
* Return the phrase's startTime
*
* @return double The phrases startTime in beats from the beginning of the part or score.
*/
public double getStartTime() {
return position.getStartTime();
// return this.startTime;
}
/**
* Sets the phrases startTime
*
* This positions the phrase absolutely. If this phrase is currently
* positioned relative to another phrase that anchoring will be lost.
*
* To position this relative to another class use the
* anchor
method instead.
*
* @param double the time at which to start the phrase
*/
public void setStartTime(double startTime) {
if (startTime >= MIN_START_TIME) {
position.setStartTime(startTime);
// this.startTime = startTime;
this.setAppend(false);
} else {
System.err.println("Error setting phrase start time value: You must enter values greater than " + MIN_START_TIME);
}
}
/**
*
The positions tries the phrase relative to another using the
* alignment specified. If the arrangement causes this class to start
* before a start time of 0.0, the repositioning is considered invalid
* and will fail. The original positioning will be restored and this
* method will return false.
*
* If successful, the previous positioning whether absolute or relative
* will be lost.
*
* To position this absolutely use the setStartTime
* method instead.
*
* @param anchor the phrase against which this should be positioned
* @param alignment how this should be positioned relative to anchor
* @returns false if anchoring failed due to being positioned
* before the 0.0 start time barrier. True otherwise.
*/
public boolean attemptAnchoringTo(final Phrase anchor,
final Alignment alignment,
final double offset) {
Position newPosition = new Position(anchor.position, alignment,
offset, this);
if (newPosition.getStartTime() < 0.0) {
return false;
} else {
position = newPosition;
return true;
}
}
/**
* Returns details of how this is aligned relative to another phrase.
* Alternatively, if this phrase is aligned absolutely returns null.
*
* @returns null if aligned with setStartTime(), or details of alignment
* if aligned with attemptAnchoringTo()
*/
public Anchoring getAnchoring() {
return position.getAnchoring();
}
/**
* Return the phrases endTime
*
* @return double the phrases endTime
*/
public double getEndTime() {
double tempStartTime = (getStartTime() < MIN_START_TIME) ? MIN_START_TIME : getStartTime();
double endTime = tempStartTime;
Enumeration enum1 = this.noteList.elements();
while (enum1.hasMoreElements()) {
Note nextNote = (Note) enum1.nextElement();
endTime += nextNote.getRhythmValue();
}
return endTime;
}
/**
* Returns the length of the whole phrase in beats.
*
* @return double duration in beats
*/
final double getTotalDuration() {
double cumulativeLength = 0.0;
Enumeration enum1 = this.noteList.elements();
while (enum1.hasMoreElements()) {
Note nextNote = (Note) enum1.nextElement();
cumulativeLength += nextNote.getRhythmValue();
}
return cumulativeLength;
}
/**
* Return this phrases title
*
* @return String the phrases title
*/
public String getTitle() {
return this.title;
}
/**
* Gives the Phrase a new title
*
* @param phrases title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Return this phrases append status
*
* @return boolean the phrases append value
*/
public boolean getAppend() {
return this.append;
}
/**
* Gives the Phrase a new append status
*
* @param boolean the append status
*/
public void setAppend(boolean append) {
this.append = append;
}
/**
* Return this phrases this phrase is linked to
*
* @return Phrase the phrases linked to
*/
public Phrase getLinkedPhrase() {
return this.linkedPhrase;
}
/**
* Make a link from this phrase to another
*
* @param Phrase the phrase to link to
*/
public void setLinkedPhrase(Phrase link) {
this.linkedPhrase = link;
}
/**
* Return the pan position for this phrase
*
* @return double the phrases pan setting
*/
public double getPan() {
return this.pan;
}
/**
* Determine the pan position for all notes in this phrase.
*
* @param double the phrase's pan setting
*/
public void setPan(double pan) {
this.pan = pan;
Enumeration enum1 = noteList.elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
note.setPan(pan);
}
}
/**
* Return the tempo in beats per minute for this phrase
*
* @return double the phrase's tempo setting
*/
public double getTempo() {
return this.tempo;
}
/**
* Determine the tempo in beats per minute for this phrase
*
* @param double the phrase's tempo
*/
public void setTempo(double newTempo) {
this.tempo = newTempo;
}
/**
* Get an individual note object by its number
*
* @param int number - the number of the Track to return
* @return Note answer - the note object to return
*/
public Note getNote(int number) {
Enumeration enum1 = noteList.elements();
int counter = 0;
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
if (counter == number) {
return note;
}
counter++;
}
return null;
}
/**
* Get the number of notes in this phrase
*
* @return int The number of notes
*/
public int length() {
return size();
}
/**
* Get the number of notes in this phrase
*
* @return int length - the number of notes
*/
public int size() {
return (noteList.size());
}
/**
* Get the number of notes in this phrase
*
* @return int length - the number of notes
*/
public int getSize() {
return (noteList.size());
}
/**
* Returns the numerator of the Phrase's time signature
*
* @return int time signature numerator
*/
public int getNumerator() {
return this.numerator;
}
/**
* Specifies the numerator of the Phrase's time signature
*
* @param int time signature numerator
*/
public void setNumerator(int num) {
this.numerator = num;
}
/**
* Returns the denominator of the Phrase's time signature
*
* @return int time signature denominator
*/
public int getDenominator() {
return this.denominator;
}
/**
* Specifies the denominator of the Phrase's time signature
*
* @param int time signature denominator
*/
public void setDenominator(int dem) {
this.denominator = dem;
}
/**
* returns a reference to the part that contains this phrase
*/
public Part getMyPart() {
return myPart;
}
/**
* Sets a reference to the part containing this phrase
*/
public void setMyPart(Part part) {
this.myPart = part;
}
/**
* Returns a copy of the entire Phrase
*
* @return Phrase a copy of the Phrase
*/
public Phrase copy() {
Phrase phr = new Phrase();
copyAttributes(phr);
Enumeration enum1 = this.noteList.elements();
while (enum1.hasMoreElements()) {
phr.addNote(((Note) enum1.nextElement()).copy());
}
return phr;
}
private void copyAttributes(Phrase phr) {
// NB: start time now covered by position
phr.position = this.position.copy(phr);
phr.setTitle(this.title + " copy");
phr.setInstrument(this.instrument);
phr.setAppend(this.append);
phr.setPan(this.pan);
phr.setLinkedPhrase(this.linkedPhrase);
phr.setMyPart(this.getMyPart());
phr.setTempo(this.tempo);
phr.setNumerator(this.numerator);
phr.setDenominator(this.denominator);
}
/**
* Returns a copy of a specified section of the Phrase,
* pads beginning and end with shortedend notes and rests
* if notes or phrase boundaries don't align with locations.
*
* @param double start location
* @param double end location
* @return Phrase a copy of the Phrase
*/
public Phrase copy(double startLoc, double endLoc) {
Phrase phr = this.copy(startLoc, endLoc, true);
return phr;
}
/**
* Returns a copy of a specified section of the Phrase,
* pads beginning and end with shortedend notes and rests
* if notes or phrase boundaries don't align with locations.
*
* @param double start location
* @param double end location
* @param boolean requireNoteStart If true, only notes that start inside
* the copy range are included in the copy. Notes starting prior but
* overlapping are replaced by rests. Otherwise sections of notes inseide
* the bounds are included.
* @return Phrase a copy of the Phrase
*/
public Phrase copy(double startLoc, double endLoc, boolean requireNoteStart) {
// are the arguments valid?
if (startLoc >= endLoc || endLoc < this.getStartTime()) {
return null;
}
Phrase tempPhr = new Phrase(0.0);
copyAttributes(tempPhr);
double beatCounter = this.getStartTime();
if (beatCounter < 0.0) beatCounter = 0.0;
//is it before the phrase?
if (startLoc < beatCounter) {
Note r = new Note(REST, beatCounter - startLoc);
tempPhr.addNote(r);
endLoc += beatCounter - startLoc;
}
// are there notes before the startLoc to pass up?
for (int i = 0; i < this.size(); i++) {
if (beatCounter < startLoc) { // this note starts before the space
if ((beatCounter + this.getNote(i).getRhythmValue() > startLoc) &&
(beatCounter + this.getNote(i).getRhythmValue() <= endLoc)) { // ends within the space
if (requireNoteStart) {
Note n = new Note(REST, beatCounter +
this.getNote(i).getRhythmValue() - startLoc);
tempPhr.addNote(n);
} else {
if (this.getNote(i).getPitchType() == Note.MIDI_PITCH) {
Note n = new Note(this.getNote(i).getPitch(), beatCounter +
this.getNote(i).getRhythmValue() - startLoc,
this.getNote(i).getDynamic()
);
tempPhr.addNote(n);
} else {
Note n = new Note(this.getNote(i).getFrequency(), beatCounter +
this.getNote(i).getRhythmValue() - startLoc,
this.getNote(i).getDynamic()
);
tempPhr.addNote(n);
}
}
}
if (beatCounter + this.getNote(i).getRhythmValue() > endLoc) { // ends after the space
if (requireNoteStart) {
Note n = new Note(REST, beatCounter +
this.getNote(i).getRhythmValue() - startLoc, this.getNote(i).getDynamic());
tempPhr.addNote(n);
} else {
if (this.getNote(i).getPitchType() == Note.MIDI_PITCH) {
Note n = new Note(this.getNote(i).getPitch(), beatCounter +
endLoc - startLoc, this.getNote(i).getDynamic());
tempPhr.addNote(n);
} else {
Note n = new Note(this.getNote(i).getPitch(), beatCounter +
endLoc - startLoc, this.getNote(i).getDynamic());
tempPhr.addNote(n);
}
}
}
}
if (beatCounter >= startLoc && beatCounter < endLoc) { // this note starts in the space
if (beatCounter + this.getNote(i).getRhythmValue() <= endLoc) { // also ends in it
tempPhr.addNote(this.getNote(i));
} else { //ends after the end. Make up last note.
Note n = new Note(this.getNote(i).getPitch(), endLoc - beatCounter, this.getNote(i).getDynamic());
tempPhr.addNote(n);
}
}
beatCounter += this.getNote(i).getRhythmValue();
}
// is there more space past the end of the phrase?
if (beatCounter < endLoc) { // make up a rest to fill the space
Note r = new Note(REST, endLoc - beatCounter);
tempPhr.addNote(r);
}
// done!
return tempPhr;
}
/**
* Returns a copy of a specified section of the Phrase,
* pads beginning and end with shortedend notes and rests
* if notes or phrase boundaries don't align with locations.
*
* @param boolean trimmed wether to truncte notes (as per the
* other versions of copy) or not
* @param boolean startTimeShifts wether to shift the start
* time or to add a rest if if the start is afte startloc
* @param double start location
* @param double end location
* @return Phrase a copy of the Phrase
*/
public Phrase copy(double startLoc, double endLoc,
boolean trimmed, boolean truncated, boolean startTimeShifts) {
// are the arguments valid?
if (startLoc >= endLoc || endLoc < this.getStartTime()) {
System.out.println("invalid arguments in Phrase.copy");
return null;
}
Phrase tempPhr = new Phrase("", startLoc, this.instrument);
//this.title + " copy", startLoc, this.instrument);
tempPhr.setAppend(this.append);
tempPhr.setPan(this.pan);
tempPhr.setLinkedPhrase(this.linkedPhrase);
tempPhr.setMyPart(this.getMyPart());
double beatCounter = this.getStartTime();
if (beatCounter < 0.0) beatCounter = 0.0;
//is it before the phrase?
//make beatCounter add up to the right amount before going though the segment
Enumeration noteEnum = this.getNoteList().elements();
while (startLoc > beatCounter && noteEnum.hasMoreElements()) {
Note n = (Note) noteEnum.nextElement();
beatCounter += n.getRhythmValue();
}
// now it is in the segment, should a rest be added in the begining because
// a note overlaps?
if (startLoc < beatCounter) {
if (beatCounter < endLoc) {
if (startTimeShifts) {
tempPhr.setStartTime(beatCounter + this.getStartTime());
} else {
Note r = new Note(REST, beatCounter - startLoc);
tempPhr.addNote(r);
}
} else {
Note r = new Note(REST, endLoc - startLoc);
tempPhr.addNote(r);
return tempPhr;
}
}
double addedCounter = 0.0;
// go through the rest of the notes in the segment, until it equals the
// end or runs out of notes
while (noteEnum.hasMoreElements() && beatCounter < endLoc) {
Note n = ((Note) noteEnum.nextElement()).copy();
//if the note goes over the end
if ((n.getRhythmValue() + beatCounter) > endLoc && trimmed) {
//trimm it back
n.setRhythmValue(endLoc - beatCounter, truncated);
}
tempPhr.addNote(n);
addedCounter += n.getRhythmValue();
beatCounter += n.getRhythmValue();
}
// is there more space past the end of the phrase?
if (beatCounter < endLoc) { // make up a rest to fill the space
Note r = new Note(REST, endLoc - beatCounter);
tempPhr.addNote(r);
} else if (addedCounter == 0.0) { // or if nothing was added at all
Note r = new Note(REST, endLoc - startLoc);
tempPhr.addNote(r);
}
// done!
return tempPhr;
}
/**
* Returns a copy of the entire Phrase only ontaining notes
* between highest and lowset specified pitch.
*
* @return Phrase a partical copy of the Phrase
* @ param highestPitch The top MIDI pitch to include in the copy
* @ param lowestPitch The bottom MIDI pitch to include in the copy
*/
public Phrase copy(int highestPitch, int lowestPitch) {
if (lowestPitch >= highestPitch) {
System.err.println("jMusic Phrase copy error: " +
"lowset pitch is not lower than highest pitch");
System.exit(0);
}
Phrase phr = new Phrase(this.title + " copy");
// phr.setStartTime(this.startTime);
phr.position = this.position.copy(phr);
phr.setInstrument(this.instrument);
phr.setAppend(this.append);
phr.setPan(this.pan);
phr.setLinkedPhrase(this.linkedPhrase);
phr.setMyPart(this.getMyPart());
Enumeration enum1 = this.noteList.elements();
while (enum1.hasMoreElements()) {
Note n = ((Note) enum1.nextElement()).copy();
if (n.getPitch() > highestPitch && n.getPitch() < lowestPitch) n.setPitch(REST);
phr.addNote(n);
}
return phr;
}
/**
* Prints the tracks attributes to stdout
*/
public String toString() {
String phraseData = new String("-------- jMusic PHRASE: '" +
title + "' contains " + this.size() + " notes. Start time: " +
getStartTime() + " --------" + '\n');
if (this.tempo > 0) phraseData += "Phrase Tempo = " + this.tempo + '\n';
Enumeration enum1 = getNoteList().elements();
int counter = 0;
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
phraseData = phraseData + note.toString() + '\n';
}
return phraseData;
}
/**
* Empty removes all elements in the note list vector
*/
public void empty() {
noteList.removeAllElements();
}
/**
* Returns a carbon copy of a specified Phrase
* Changes to notes in the original or the alias will be echoed in the other.
* Note: that for this to work other phrase classes must to change the
* noteList attribute to point to another object, but instead
* should always update the noteList itself. See shuffle() as an example.
*/
public Phrase alias() {
Phrase phr = new Phrase(this.title + " alias", this.getStartTime(), this.instrument);
phr.setTempo(this.tempo);
phr.setAppend(this.append);
phr.noteList = this.noteList;
return phr;
}
/**
* Return the pitch value of the highest note in the phrase.
*/
public int getHighestPitch() {
int max = -1;
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
if (note.getPitchType() == Note.MIDI_PITCH)
if (note.getPitch() > max) max = note.getPitch();
}
return max;
}
/**
* Return the pitch value of the lowest note in the phrase.
*/
public int getLowestPitch() {
int min = 128;
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
if (note.getPitchType() == Note.MIDI_PITCH)
if (note.getPitch() < min && note.getPitch() >= 0) min = note.getPitch();
;
}
return min;
}
/**
* Return the value of the longest rhythm value in the phrase.
*/
public double getLongestRhythmValue() {
double max = 0.0;
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
if (note.getRhythmValue() > max) max = note.getRhythmValue();
}
return max;
}
/**
* Return the value of the shortest rhythm value in the phrase.
*/
public double getShortestRhythmValue() {
double min = 1000.0;
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
if (note.getRhythmValue() < min) min = note.getRhythmValue();
}
return min;
}
/**
* Change the dynamic value of each note in the phrase.
*/
public void setDynamic(int dyn) {
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
note.setDynamic(dyn);
}
}
/**
* Change the pitch value of each note in the phrase.
*/
public void setPitch(int val) {
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
note.setPitch(val);
}
}
/**
* Change the rhythmValue value of each note in the phrase.
*/
public void setRhythmValue(double val) {
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
note.setRhythmValue(val);
}
}
/**
* Change the Duration value of each note in the phrase.
*/
public void setDuration(double val) {
Enumeration enum1 = getNoteList().elements();
while (enum1.hasMoreElements()) {
Note note = (Note) enum1.nextElement();
note.setDuration(val);
}
}
/**
* Return the Duration of the phrase in beats.
*/
public double getBeatLength() {
return getEndTime();
}
/**
* Generates and returns a new note with default values
* and adds it to this phrase.
*/
public Note createNote() {
Note n = new Note();
this.addNote(n);
return n;
}
/*
* Replace one note with another.
* @param Note the new note
* @param index the phrase position to replace
*/
public void setNote(Note n, int index) {
if (index >= this.getSize()) {
System.out.println("jMusic error: Phrase setNote index is too large.");
return;
}
this.noteList.removeElementAt(index);
this.noteList.insertElementAt(n, index);
}
/**
* Retrieve the current mute status.
*
* @return boolean True or False, muted ot not.
*/
public boolean getMute() {
return this.mute;
}
/**
* Specify the mute status of this phrase.
*
* @param state True or False, muted or not.
*/
public void setMute(boolean state) {
this.mute = state;
}
/**
* Change both the rhythmValue and duration of each note in the phrase.
*
* @param newLength The new rhythmValue for the note (Duration is a proportion of this value)
*/
public void setLength(double newLength) {
this.setRhythmValue(newLength);
this.setDuration(newLength * Note.DEFAULT_DURATION_MULTIPLIER);
}
/**
* Calculate the start time, in beats, of the note at the specified index.
*
* @param noteIndex The note's position in the phrase.
* @return double The absolute (taking into account phrase start time) beat
* position where the note starts. -1 is returned when an index out of range is encounterd.
*/
public double getNoteStartTime(int noteIndex) {
if (noteIndex >= this.size()) return -1.0;
double startLoc = this.getStartTime();
for (int i = 0; i < noteIndex; i++) {
startLoc += this.getNote(i).getRhythmValue();
}
return startLoc;
}
private final class Position implements Serializable {
private final Phrase phrase;
private double startTime = 0.0;
private boolean isAbsolute = false;
private Position anchor;
private Alignment alignment = Alignment.AFTER;
private double offset;
private Position(final double startTime, final Phrase phrase) {
this.isAbsolute = true;
this.startTime = startTime;
this.phrase = phrase;
}
private Position(final Position anchor,
final Alignment alignment,
final double offset,
final Phrase phrase) {
this.isAbsolute = false;
this.anchor = anchor;
this.alignment = alignment;
this.offset = offset;
this.phrase = phrase;
}
private final Anchoring getAnchoring() {
if (isAbsolute) {
return null;
}
return new Anchoring(anchor.phrase, alignment, offset);
}
private final double getStartTime() {
if (isAbsolute) {
return startTime;
} else {
return alignment.determineStartTime(
phrase.getTotalDuration(),
anchor.getStartTime(),
anchor.getEndTime())
+ offset;
}
}
private final void setStartTime(final double startTime) {
this.isAbsolute = true;
this.startTime = startTime;
}
private final double getEndTime() {
return phrase.getEndTime();
}
private final Position copy(final Phrase newCopy) {
return (isAbsolute)
? new Position(startTime, newCopy)
: new Position(anchor, alignment, offset,
newCopy);
}
}
}