Source code: jm/music/data/Phrase.java
1 /*
2
3 <This Java Class is part of the jMusic API version 1.4, February 2003.>
4
5
6 Copyright (C) 2000 Andrew Sorensen & Andrew Brown
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or any
11 later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 */
23
24 package jm.music.data;
25
26 import jm.JMC;
27 import jm.util.*;
28 import jm.gui.cpn.Notate;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.Serializable;
35 import java.util.Vector;
36 import java.util.Enumeration;
37
38 /**
39 * The Phrase class is representative of a single musical phrase.
40 * Phrases are held in Parts and can be played at any time
41 * based on there start times. They may be played sequentially or in parallel.
42 * Phrases can be added to an Part like this...
43 * <pre>
44 * Part inst = new Part("Flute");
45 * //Phrase for the right hand
46 * Phrase rightHand = new Phrase(0.0) //start this phrase on the first beat
47 * //Phrase for the left hand
48 * Phrase leftHane = new Phrase(4.0) //start this phrase on the fifth beat
49 * inst.addPhrase(rightHand);
50 * inst.addPhrase(leftHand);
51 * </pre>
52 * @see Note
53 * @see Part
54 * @author Andrew Sorensen and Andrew Brown
55 * @version 1.0,Sun Feb 25 18:43:32 2001
56 */
57
58 public class Phrase implements JMC, Cloneable, Serializable{
59 //----------------------------------------------
60 // Limits
61 //-----------------------------------------------
62 /** The smallest start time in beats */
63 public static final double MIN_START_TIME = 0.0;
64
65 //----------------------------------------------
66 // Default constants
67 //----------------------------------------------
68
69 public static final String DEFAULT_TITLE = "Untitled Phrase";
70
71 public static final double DEFAULT_START_TIME = MIN_START_TIME;
72
73 public static final int DEFAULT_INSTRUMENT = NO_INSTRUMENT;
74
75 public static final boolean DEFAULT_APPEND = false;
76
77 public static final double DEFAULT_TEMPO = -1.0;
78
79 public static final double DEFAULT_PAN = Note.DEFAULT_PAN;
80
81 public static final int DEFAULT_NUMERATOR = 4;
82
83 public static final int DEFAULT_DENOMINATOR = 4;
84
85 public static final int DEFAULT_VOLUME = 100;
86
87 //----------------------------------------------
88 // Attributes
89 //----------------------------------------------
90 /** An array containing mutiple voices */
91 private Vector noteList;
92 /** The title/name given to this phrase */
93 private String title;
94
95 // /** The phrases start time in beats */
96 // private double startTime;
97
98 private Position position;
99
100 /** instrumet / MIDI program change number for this phrase */
101 private int instrument;
102 /** speed in beats per minute for this phrase */
103 private double tempo;
104 /** Setting the phrase to append when added to a part
105 * rather than use its start time.
106 */
107 private boolean append = false;
108 /** A phrase to have a relative start time with if required. */
109 private Phrase linkedPhrase;
110 /** The pan position for notes in this phrase.
111 * This must be set delibertley to override a note's pan position. */
112 private double pan = DEFAULT_PAN;
113
114 /** the top number of the time signature */
115 private int numerator;
116 /** the bottom number of the time signature */
117 private int denominator;
118 /** A reference to this phrases part */
119 private Part myPart = null;
120 /** The loudness for this phrase */
121 private int volume;
122 /** Weather the phrase should play or not */
123 private boolean mute = false;
124 //----------------------------------------------
125 // Constructors
126 //----------------------------------------------
127 /**
128 * Creates an empty Phrase.
129 * The default start time is a flag which means the phrase will be
130 * appended to the end of any part it is added to.
131 */
132 public Phrase(){
133 this(DEFAULT_START_TIME);
134 this.append = true;
135 }
136
137 /**
138 * Creates an empty Phrase starting at the specified beat.
139 * @param startTime The beat at which the phrase will be positioned in its part.
140 */
141 public Phrase(double startTime){
142 this(startTime, DEFAULT_INSTRUMENT);
143 }
144
145 /**
146 * Creates an empty Phrase
147 * @param startTime The beat at which the phrase will be positioned in its part.
148 * @param instrument The sound or instrument number to be used for this phrase.
149 */
150 public Phrase(double startTime, int instrument) {
151 this(DEFAULT_TITLE, startTime, instrument);
152 }
153
154 /**
155 * Creates an empty Phrase
156 * @param title The name for the phrase.
157 */
158 public Phrase(String title){
159 this(title, DEFAULT_START_TIME);
160 this.append = true;
161 }
162
163 /**
164 * Creates an empty Phrase.
165 * @param title The name for the phrase.
166 * @param startTime The beat at which the phrase will be positioned in its part.
167 */
168 public Phrase(String title, double startTime){
169 this(title, startTime, DEFAULT_INSTRUMENT);
170 }
171
172 /**
173 * Creates an empty Phrase.
174 * @param title The name for the phrase.
175 * @param startTime The beat at which the phrase will be positioned in its part.
176 * @param instrument The sound or instrument number to be used for this phrase.
177 */
178 public Phrase(String title, double startTime, int instrument){
179 this(title, startTime, instrument, DEFAULT_APPEND);
180 }
181
182 /**
183 * Creates an empty Phrase.
184 * @param title The name for the phrase.
185 * @param startTime The beat at which the phrase will be positioned in its part.
186 * @param instrument The sound or instrument number to be used for this phrase.
187 * @param append A flag specifying wheather or not this phrase should be added to the
188 * end of the part it is added too, or should use its start time value.
189 */
190 public Phrase(String title, double startTime, int instrument, boolean append){
191 this.title = title;
192 // this.startTime = startTime;
193 this.position = new Position(startTime, this);
194 this.append = append;
195 if(instrument < NO_INSTRUMENT){
196 System.err.println(new Exception("jMusic EXCEPTION: instrument " +
197 "value must be greater than 0"));
198 (new Exception()).printStackTrace();
199 System.exit(1); //crash ungracefully
200 }
201 this.instrument = instrument;
202 this.noteList = new Vector();
203 this.numerator = DEFAULT_NUMERATOR;
204 this.denominator = DEFAULT_DENOMINATOR;
205 this.tempo = DEFAULT_TEMPO;
206 this.volume = DEFAULT_VOLUME;
207 }
208
209 /**
210 * Constructs a new Phrase containing the specified <CODE>note</CODE>.
211 *
212 * @param note Note to be containing by the phrase.
213 */
214 public Phrase(Note note) {
215 this();
216 addNote(note);
217 }
218
219 /**
220 * Constructs a new Phrase containing the specified <CODE>notes</CODE>.
221 *
222 * @param notes array of Note to be contained by the phrase.
223 */
224 public Phrase(Note[] notes) {
225 this();
226 addNoteList(notes);
227 }
228
229 /**
230 * Constructs a new Phrase containing the specified <CODE>note</CODE> with
231 * the specified <CODE>title</CODE>.
232 *
233 * @param note Note to be containing by the phrase.
234 * @param title String describing the title of the Phrase.
235 */
236 public Phrase(Note note, String title) {
237 this(title);
238 addNote(note);
239 }
240
241 /**
242 * Constructs a new Phrase containing the specified <CODE>notes</CODE> with
243 * the specified <CODE>title</CODE>.
244 *
245 * @param notes array of Note to be contained by the phrase.
246 * @param title String describing the title of the Phrase.
247 */
248 public Phrase(Note[] notes, String title) {
249 this(title);
250 this.addNoteList(notes);
251 }
252
253 /**
254 * Constructs a new Phrase containing the specified <CODE>note</CODE> with
255 * the specified <CODE>title</CODE>.
256 *
257 * @param note Note to be containing by the phrase.
258 * @param title String describing the title of the Phrase.
259 */
260 public Phrase(Note note, double startTime) {
261 this(note);
262 this.setStartTime(startTime);
263 }
264
265 //----------------------------------------------
266 // Data Methods
267 //----------------------------------------------
268
269 /**
270 * Return the program change assigned by this phrase
271 * @return int
272 */
273 public int getInstrument(){
274 return this.instrument;
275 }
276
277 /**
278 * Sets the program change value
279 * @param int program change
280 */
281 public void setInstrument(int value){
282 this.instrument = value;
283 }
284
285 /**
286 * Add a note to this Phrase
287 * @param Note note - add a note to this phrase
288 */
289 public void addNote(Note note){
290 note.setMyPhrase(this);
291 noteList.addElement(note);
292 }
293
294
295 /**
296 * Add a note to this Phrase
297 * @param pitch -the pitch of the note
298 *@param rv - the rhythmValue of the note
299 */
300 public void addNote(int pitch, double rv){
301 Note note = new Note(pitch, rv);
302 this.addNote(note);
303 }
304
305 /**
306 * Add a note to this Phrase
307 * @param Note note - add a note to this phrase
308 */
309 public void add(Note note){
310 this.addNote(note);
311 }
312
313
314 /**
315 * Appends the specified notes to the end of this Phrase.
316 *
317 * @param array of Notes to append.
318 */
319 public void addNoteList(Note[] notes) {
320 for (int i = 0; i < notes.length; i++) {
321 this.addNote(notes[i]);
322 }
323 }
324
325 /**
326 * Adds a vector of notes to the phrase
327 * A boolean option when true appends the notes to the end of the list
328 * @param noteVector the vector of notes to add
329 * @param append do we append or not?
330 */
331 public void addNoteList(Vector noteVector, boolean append){
332 Enumeration enum = noteVector.elements();
333 if(!append) this.noteList.removeAllElements();
334 while(enum.hasMoreElements()){
335 try{
336 Note note = (Note) enum.nextElement();
337 this.addNote(note);
338 //note.setMyPhrase(this);
339 }catch(RuntimeException re){
340 System.err.println("The vector passed to this method must " + "contain Notes only!");
341 }
342 }
343 }
344
345 /**
346 * Adds an array of notes to the phrase
347 * A boolean option when true appends the notes to the end of the list
348 * @param noteArray the array of notes to add
349 * @param append do we append or not?
350 */
351 public void addNoteList(Note[] noteArray, boolean append){
352 if(!append) this.noteList.removeAllElements();
353 for(int i=0;i<noteArray.length;i++){
354 this.addNote(noteArray[i]);
355 }
356 }
357
358 /**
359 * Adds Multiple notes to the phrase from several arrays
360 * @param pitchArray array of pitch values
361 * @param rhythmValue a rhythmic value
362 */
363 public void addNoteList(int[] pitchArray, double rhythmValue){
364 double[] rvArray = new double[pitchArray.length];
365 for(int i=0;i<rvArray.length;i++){
366 rvArray[i] = rhythmValue;
367 }
368 this.addNoteList(pitchArray, rvArray);
369 }
370
371 /**
372 * Adds Multiple notes to the phrase from an array of frequency values
373 * @param freqArray array of freequency values
374 * @param rhythmValue a rhythmic value
375 */
376 public void addNoteList(double[] freqArray, double rhythmValue){
377 double[] rvArray = new double[freqArray.length];
378 for(int i=0;i<rvArray.length;i++){
379 rvArray[i] = rhythmValue;
380 }
381 this.addNoteList(freqArray, rvArray);
382 }
383
384
385
386 /**
387 * Adds Multiple notes to the phrase from several arrays
388 * @param pitchArray array of pitch values
389 * @param rhythmArray array of rhythmic values
390 */
391 public void addNoteList(int[] pitchArray, double[] rhythmArray){
392 int[] dynamic = new int[pitchArray.length];
393 for(int i=0;i<pitchArray.length;i++){
394 dynamic[i] = Note.DEFAULT_DYNAMIC;
395 }
396 this.addNoteList(pitchArray, rhythmArray, dynamic);
397 }
398
399 /**
400 * Adds Multiple notes to the phrase from several arrays
401 * @param freqArray array of frequency values
402 * @param rhythmArray array of rhythmic values
403 */
404 public void addNoteList(double[] freqArray, double[] rhythmArray){
405 int[] dynamic = new int[freqArray.length];
406 for(int i=0;i<freqArray.length;i++){
407 dynamic[i] = Note.DEFAULT_DYNAMIC;
408 }
409 this.addNoteList(freqArray, rhythmArray, dynamic);
410 }
411
412 /**
413 * Adds Multiple notes to the phrase from several arrays
414 * @param pitchArray array of pitch values
415 * @param rhythmArray array of rhythmic values
416 * @param dynmaic array of dynamic values
417 */
418 public void addNoteList(int[] pitchArray, double[] rhythmArray,
419 int[] dynamic){
420 this.addNoteList(pitchArray, rhythmArray, dynamic, true);
421 }
422
423 /**
424 * Adds Multiple notes to the phrase from several arrays
425 * @param freqArray array of frequency values
426 * @param rhythmArray array of rhythmic values
427 * @param dynmaic array of dynamic values
428 */
429 public void addNoteList(double[] freqArray, double[] rhythmArray,
430 int[] dynamic){
431 this.addNoteList(freqArray, rhythmArray, dynamic, true);
432 }
433
434 /**
435 * Adds Multiple notes to the phrase from several arrays
436 * A boolean option when true appends the notes to the end of the list
437 * if non true the current list is errased and replaced by the new notes
438 * @param pitchArray array of pitch values
439 * @param rhythmArray array of rhythmic values
440 * @param dynamic int
441 * @param append do we append or not?
442 */
443 public void addNoteList(int[] pitchArray, double[] rhythmArray,
444 int[] dynamic, boolean append){
445 if(!append) this.noteList.removeAllElements();
446 for(int i=0;i<pitchArray.length;i++){
447 try{
448 Note knote = new Note(pitchArray[i],rhythmArray[i],dynamic[i]);
449 this.addNote(knote);
450 }catch(RuntimeException re){
451 System.err.println("You must enter arrays of even length");
452 }
453 }
454 }
455
456 /**
457 * Adds Multiple notes to the phrase from several arrays
458 * A boolean option when true appends the notes to the end of the list
459 * if non true the current list is errased and replaced by the new notes
460 * @param freqArray array of frequency values
461 * @param rhythmArray array of rhythmic values
462 * @param dynamic int
463 * @param append do we append or not?
464 */
465 public void addNoteList(double[] freqArray, double[] rhythmArray,
466 int[] dynamic, boolean append){
467 if(!append) this.noteList.removeAllElements();
468 for(int i=0;i<freqArray.length;i++){
469 try{
470 Note knote = new Note(freqArray[i],rhythmArray[i],dynamic[i]);
471 this.addNote(knote);
472 }catch(RuntimeException re){
473 System.err.println("jMusic Phrase error: You must enter arrays of even length");
474 }
475 }
476 }
477
478 /**
479 * Adds Multiple notes to the phrase from one array of pitch, rhythm pairs
480 * @param pitchAndRhythmArray - an array of pitch and rhythm values
481 */
482 public void addNoteList(double[] pitchAndRhythmArray){
483 for(int i=0;i< pitchAndRhythmArray.length;i+=2){
484 try {
485 Note knote = new Note((int)pitchAndRhythmArray[i],pitchAndRhythmArray[i+1]);
486 this.addNote(knote);
487 }catch(RuntimeException re) {
488 System.err.println("Error adding note list: Possibly the wrong number of values in the pitch and rhythm array.");
489 }
490 }
491 }
492
493 /**
494 * Adds Multiple notes to the phrase from one pitch and an array of rhythm values
495 *@param pitch The pitch values for the notes
496 * @param rhythms An array of rhythm values
497 */
498 public void addNoteList(int pitch, double[] rhythms){
499 for(int i=0; i<rhythms.length; i++) {
500 this.addNote(new Note(pitch, rhythms[i]));
501 }
502 }
503
504 /**
505 * Adds Multiple notes to the phrase from one pitch and an array of rhythm values
506 *@param frequency The pitch values for the notes in hertz
507 * @param rhythms An array of rhythm values
508 */
509 public void addNoteList(double frequency, double[] rhythms){
510 for(int i=0; i<rhythms.length; i++) {
511 this.addNote(new Note(frequency, rhythms[i]));
512 }
513 }
514
515
516 /**
517 * Adds Multiple notes to the phrase all of which start at the same time
518 * and share the same duration.
519 * @param pitches An array of pitch values
520 * @param rv the rhythmValue
521 */
522 public void addChord(int[] pitches, double rv) {
523 for(int i=0; i<pitches.length - 1; i++) {
524 Note n = new Note(pitches[i], 0.0);
525 n.setDuration(rv * Note.DEFAULT_DURATION_MULTIPLIER);
526 this.addNote(n);
527 }
528 this.addNote(pitches[pitches.length - 1], rv);
529
530 System.out.println("In phrase" + this.toString());
531 }
532
533 /**
534 * Deletes the specified note in the phrase
535 * @param int noteNumb the index of the note to be deleted
536 */
537 public void removeNote(int noteNumb) {
538 Vector vct = (Vector)this.noteList;
539 try{
540 vct.removeElement(vct.elementAt(noteNumb));
541 } catch (RuntimeException re){
542 System.err.println("Note index to be deleted must be within the phrase.");
543 }
544 }
545
546 /**
547 * Deletes the first occurence of the specified note in the phrase
548 * @param note the note object to be deleted.
549 */
550 public void removeNote(Note note) {
551 this.noteList.removeElement(note);
552 }
553
554 /**
555 * Deletes the last note in the phrase
556 */
557 public void removeLastNote() {
558 Vector vct = (Vector)this.noteList;
559 vct.removeElementAt(vct.size()-1);
560 }
561
562 /**
563 * Returns the entire note list contained in a single voice
564 * @return Vector A vector containing all Note objects in this phrase
565 */
566 public Vector getNoteList(){
567 return this.noteList;
568 }
569
570 /**
571 * Replaces the entire note list with a new note list vector
572 * @param Vector of notes
573 */
574 public void setNoteList(Vector newNoteList){
575 this.noteList = newNoteList;
576 }
577
578 /**
579 * Returns the all notes in the phrase as a array of notes
580 * @return Note[] An array containing all Note objects in this phrase
581 */
582 public Note[] getNoteArray(){
583 Vector vct = (Vector) this.noteList;
584 Note[] noteArray = new Note[vct.size()];
585 for(int i=0;i< noteArray.length;i++){
586 noteArray[i] = (Note) vct.elementAt(i);
587 }
588 return noteArray;
589 }
590
591 /**
592 * Return the phrases startTime
593 * @return double the phrases startTime
594 */
595 public double getStartTime(){
596 return position.getStartTime();
597 // return this.startTime;
598 }
599
600
601 /**
602 * Sets the phrases startTime
603 *
604 * <p>This positions the phrase absolutely. If this phrase is currently
605 * positioned relative to another phrase that anchoring will be lost.
606 *
607 * <p>To position this relative to another class use the
608 * <code>anchor</code> method instead.
609 *
610 * @param double the time at which to start the phrase
611 */
612 public void setStartTime(double startTime){
613 if(startTime >= MIN_START_TIME){
614 position.setStartTime(startTime);
615 // this.startTime = startTime;
616 this.setAppend(false);
617 }else{
618 System.err.println("Error setting phrase start time value: You must enter values greater than "+MIN_START_TIME);
619 }
620 }
621
622 /** <p>The positions tries the phrase relative to another using the
623 * alignment specified. If the arrangement causes this class to start
624 * before a start time of 0.0, the repositioning is considered invalid
625 * and will fail. The original positioning will be restored and this
626 * method will return false.
627 *
628 * If successful, the previous positioning whether absolute or relative
629 * will be lost.
630 *
631 * <p>To position this absolutely use the <code>setStartTime</code>
632 * method instead.
633 *
634 * @param anchor the phrase against which this should be positioned
635 * @param alignment how this should be positioned relative to anchor
636 * @returns false if anchoring failed due to being positioned
637 * before the 0.0 start time barrier. True otherwise.
638 */
639 public boolean attemptAnchoringTo(final Phrase anchor,
640 final Alignment alignment,
641 final double offset) {
642 Position newPosition = new Position(anchor.position, alignment,
643 offset, this);
644 if (newPosition.getStartTime() < 0.0) {
645 return false;
646 } else {
647 position = newPosition;
648 return true;
649 }
650
651 }
652
653 /** Returns details of how this is aligned relative to another phrase.
654 * Alternatively, if this phrase is aligned absolutely returns null.
655 *
656 * @returns null if aligned with setStartTime(), or details of alignment
657 * if aligned with attemptAnchoringTo()
658 */
659 public Anchoring getAnchoring() {
660 return position.getAnchoring();
661 }
662
663 /**
664 * Return the phrases endTime
665 * @return double the phrases endTime
666 */
667 public double getEndTime(){
668 double tempStartTime = (getStartTime() < MIN_START_TIME) ? MIN_START_TIME : getStartTime();
669 double endTime = tempStartTime;
670 Enumeration enum = this.noteList.elements();
671 while(enum.hasMoreElements()){
672 Note nextNote = (Note)enum.nextElement();
673 endTime += nextNote.getRhythmValue();
674 }
675 return endTime;
676 }
677
678 /**
679 * Returns the length of the whole phrase in beats.
680 * @return double duration in beats
681 */
682 final double getTotalDuration(){
683 double cumulativeLength = 0.0;
684 Enumeration enum = this.noteList.elements();
685 while(enum.hasMoreElements()){
686 Note nextNote = (Note)enum.nextElement();
687 cumulativeLength += nextNote.getRhythmValue();
688 }
689 return cumulativeLength;
690 }
691
692 /**
693 * Return this phrases title
694 * @return String the phrases title
695 */
696 public String getTitle(){
697 return this.title;
698 }
699
700 /**
701 * Gives the Phrase a new title
702 * @param phrases title
703 */
704 public void setTitle(String title){
705 this.title = title;
706 }
707
708 /**
709 * Return this phrases append status
710 * @return boolean the phrases append value
711 */
712 public boolean getAppend(){
713 return this.append;
714 }
715
716 /**
717 * Gives the Phrase a new append status
718 * @param boolean the append status
719 */
720 public void setAppend(boolean append){
721 this.append = append;
722 }
723
724 /**
725 * Return this phrases this phrase is linked to
726 * @return Phrase the phrases linked to
727 */
728 public Phrase getLinkedPhrase(){
729 return this.linkedPhrase;
730 }
731
732 /**
733 * Make a link from this phrase to another
734 * @param Phrase the phrase to link to
735 */
736 public void setLinkedPhrase(Phrase link){
737 this.linkedPhrase = link;
738 }
739
740 /**
741 * Return the pan position for this phrase
742 * @return double the phrases pan setting
743 */
744 public double getPan(){
745 return this.pan;
746 }
747
748 /**
749 * Determine the pan position for all notes in this phrase.
750 * @param double the phrase's pan setting
751 */
752 public void setPan(double pan){
753 this.pan = pan;
754 Enumeration enum = noteList.elements();
755 while(enum.hasMoreElements()){
756 Note note = (Note) enum.nextElement();
757 note.setPan(pan);
758 }
759 }
760
761 /**
762 * Return the tempo in beats per minute for this phrase
763 * @return double the phrase's tempo setting
764 */
765 public double getTempo(){
766 return this.tempo;
767 }
768
769 /**
770 * Determine the tempo in beats per minute for this phrase
771 * @param double the phrase's tempo
772 */
773 public void setTempo(double newTempo){
774 this.tempo = newTempo;
775 }
776
777 /**
778 * Get an individual note object by its number
779 * @param int number - the number of the Track to return
780 * @return Note answer - the note object to return
781 */
782 public Note getNote(int number){
783 Enumeration enum = noteList.elements();
784 int counter = 0;
785 while(enum.hasMoreElements()){
786 Note note = (Note) enum.nextElement();
787 if(counter == number){
788 return note;
789 }
790 counter++;
791 }
792 return null;
793 }
794
795
796 /**
797 * Get the number of notes in this phrase
798 * @return int The number of notes
799 */
800 public int length(){
801 return size();
802 }
803
804 /**
805 * Get the number of notes in this phrase
806 * @return int length - the number of notes
807 */
808 public int size(){
809 return(noteList.size());
810 }
811
812 /**
813 * Get the number of notes in this phrase
814 * @return int length - the number of notes
815 */
816 public int getSize(){
817 return(noteList.size());
818 }
819
820
821 /**
822 * Returns the numerator of the Phrase's time signature
823 * @return int time signature numerator
824 */
825 public int getNumerator(){
826 return this.numerator;
827 }
828
829 /**
830 * Specifies the numerator of the Phrase's time signature
831 * @param int time signature numerator
832 */
833 public void setNumerator(int num){
834 this.numerator = num;
835 }
836
837 /**
838 * Returns the denominator of the Phrase's time signature
839 * @return int time signature denominator
840 */
841 public int getDenominator(){
842 return this.denominator;
843 }
844
845 /**
846 * Specifies the denominator of the Phrase's time signature
847 * @param int time signature denominator
848 */
849 public void setDenominator(int dem){
850 this.denominator= dem;
851 }
852
853 /** Sets a reference to the part containing this phrase */
854 public void setMyPart(Part part){
855 this.myPart = part;
856 }
857
858 /** returns a reference to the part that contains this phrase */
859 public Part getMyPart(){
860 return myPart;
861 }
862
863 /**
864 * Returns a copy of the entire Phrase
865 * @return Phrase a copy of the Phrase
866 */
867 public Phrase copy(){
868 Phrase phr = new Phrase();
869 copyAttributes(phr);
870 Enumeration enum = this.noteList.elements();
871 while(enum.hasMoreElements()){
872 phr.addNote( ((Note) enum.nextElement()).copy() );
873 }
874 return phr;
875 }
876
877 private void copyAttributes(Phrase phr) {
878 // NB: start time now covered by position
879 phr.position = this.position.copy(phr);
880 phr.setTitle(this.title + " copy");
881 phr.setInstrument(this.instrument);
882 phr.setAppend(this.append);
883 phr.setPan(this.pan);
884 phr.setLinkedPhrase(this.linkedPhrase);
885 phr.setMyPart(this.getMyPart());
886 phr.setTempo(this.tempo);
887 phr.setNumerator(this.numerator);
888 phr.setDenominator(this.denominator);
889 phr.setVolume(this.volume);
890 }
891
892 /**
893 * Returns a copy of a specified section of the Phrase,
894 * pads beginning and end with shortedend notes and rests
895 * if notes or phrase boundaries don't align with locations.
896 * @param double start location
897 * @param double end location
898 * @return Phrase a copy of the Phrase
899 */
900 public Phrase copy(double startLoc, double endLoc){
901 Phrase phr = this.copy(startLoc, endLoc, true);
902 return phr;
903 }
904
905 /**
906 * Returns a copy of a specified section of the Phrase,
907 * pads beginning and end with shortedend notes and rests
908 * if notes or phrase boundaries don't align with locations.
909 * @param double start location
910 * @param double end location
911 * @param boolean requireNoteStart if true only notes that start inside
912 * the copy range are included in the copy. Notes starting prior but
913 * overlapping are replaced by rests.
914 * @return Phrase a copy of the Phrase
915 */
916
917 public Phrase copy(double startLoc, double endLoc, boolean requireNS){
918 // are the arguments valid?
919 if (startLoc >= endLoc || endLoc < this.getStartTime()) {
920 return null;
921 }
922 boolean requireNoteStart = requireNS;
923 Phrase tempPhr = new Phrase();
924 copyAttributes(tempPhr);
925 double beatCounter = this.getStartTime();
926 if (beatCounter < 0.0) beatCounter = 0.0;
927 //is it before the phrase?
928 if(startLoc < beatCounter) {
929 Note r = new Note(REST, beatCounter - startLoc);
930 tempPhr.addNote(r);
931 }
932
933 // re there notes before the startLoc to pass up?
934 for(int i=0; i< this.size(); i++) {
935
936 if (beatCounter < startLoc) { // this note starts before the space
937 if((beatCounter + this.getNote(i).getRhythmValue() > startLoc) &&
938 (beatCounter + this.getNote(i).getRhythmValue() <= endLoc)) { // ends within the space
939 if (requireNoteStart) {
940 Note n = new Note( REST, beatCounter +
941 this.getNote(i).getRhythmValue() - startLoc, this.getNote(i).getDynamic());
942 tempPhr.addNote(n);
943 } else {
944 if(this.getNote(i).getPitchType() == Note.MIDI_PITCH) {
945 Note n = new Note( this.getNote(i).getPitch(), beatCounter +
946 this.getNote(i).getRhythmValue() - startLoc,
947 this.getNote(i).getDynamic());
948 tempPhr.addNote(n);
949 } else {
950 Note n = new Note( this.getNote(i).getFrequency(), beatCounter +
951 this.getNote(i).getRhythmValue() - startLoc,
952 this.getNote(i).getDynamic());
953 tempPhr.addNote(n);
954 }
955 }
956 }
957 if(beatCounter + this.getNote(i).getRhythmValue() > endLoc) { // ends after the space
958 if (requireNoteStart) {
959 Note n = new Note( REST, beatCounter +
960 this.getNote(i).getRhythmValue() - startLoc, this.getNote(i).getDynamic());
961 tempPhr.addNote(n);
962 } else {
963 if(this.getNote(i).getPitchType() == Note.MIDI_PITCH) {
964 Note n = new Note( this.getNote(i).getPitch(), beatCounter +
965 endLoc - startLoc, this.getNote(i).getDynamic());
966 tempPhr.addNote(n);
967 } else {
968 Note n = new Note( this.getNote(i).getPitch(), beatCounter +
969 endLoc - startLoc, this.getNote(i).getDynamic());
970 tempPhr.addNote(n);
971 }
972 }
973 }
974 }
975
976 if ( beatCounter >= startLoc && beatCounter < endLoc) { // this note starts in the space
977 if (beatCounter + this.getNote(i).getRhythmValue() <= endLoc) { // also ends in it
978 tempPhr.addNote(this.getNote(i));
979 } else { //ends after the end. Make up last note.
980 Note n = new Note(this.getNote(i).getPitch(), endLoc - beatCounter, this.getNote(i).getDynamic());
981 tempPhr.addNote(n);
982 }
983 }
984 beatCounter += this.getNote(i).getRhythmValue();
985 }
986 // is there more space past the end of the phrase?
987 if (beatCounter < endLoc) { // make up a rest to fill the space
988 Note r = new Note(REST, endLoc - beatCounter);
989 tempPhr.addNote(r);
990 }
991 // done!
992 return tempPhr;
993 }
994
995
996 /**
997 * Returns a copy of a specified section of the Phrase,
998 * pads beginning and end with shortedend notes and rests
999 * if notes or phrase boundaries don't align with locations.
1000 * @param boolean trimmed wether to truncte notes (as per the
1001 * other versions of copy) or not
1002 * @param boolean startTimeShifts wether to shift the start
1003 * time or to add a rest if if the start is afte startloc
1004 * @param double start location
1005 * @param double end location
1006 * @return Phrase a copy of the Phrase
1007 */
1008 public Phrase copy(double startLoc, double endLoc,
1009 boolean trimmed, boolean truncated, boolean startTimeShifts) {
1010 // are the arguments valid?
1011 if (startLoc >= endLoc || endLoc < this.getStartTime()) {
1012 System.out.println("invalid arguments in Phrase.copy");
1013 return null;
1014 }
1015 Phrase tempPhr = new Phrase( "", startLoc, this.instrument);
1016 //this.title + " copy", startLoc, this.instrument);
1017 tempPhr.setAppend(this.append);
1018 tempPhr.setPan(this.pan);
1019 tempPhr.setLinkedPhrase(this.linkedPhrase);
1020 tempPhr.setMyPart(this.getMyPart());
1021 double beatCounter = this.getStartTime();
1022 if (beatCounter < 0.0) beatCounter = 0.0;
1023 //is it before the phrase?
1024
1025 //make beatCounter add up to the right amount before going though the segment
1026 Enumeration noteEnum = this.getNoteList().elements();
1027 while(startLoc > beatCounter && noteEnum.hasMoreElements()) {
1028 Note n = (Note)noteEnum.nextElement();
1029 beatCounter += n.getRhythmValue();
1030 }
1031
1032 // now it is in the segment, should a rest be added in the begining because
1033 // a note overlaps?
1034 if(startLoc < beatCounter) {
1035 if(beatCounter < endLoc) {
1036 if(startTimeShifts) {
1037 tempPhr.setStartTime(beatCounter+this.getStartTime());
1038 } else {
1039 Note r = new Note(REST, beatCounter - startLoc);
1040 tempPhr.addNote(r);
1041 }
1042 } else {
1043 Note r = new Note(REST, endLoc - startLoc);
1044 tempPhr.addNote(r);
1045 return tempPhr;
1046 }
1047 }
1048 double addedCounter = 0.0;
1049
1050 // go through the rest of the notes in the segment, until it equals the
1051 // end or runs out of notes
1052 while(noteEnum.hasMoreElements() && beatCounter < endLoc) {
1053 Note n = ((Note)noteEnum.nextElement()).copy();
1054 //if the note goes over the end
1055 if((n.getRhythmValue()+beatCounter) > endLoc && trimmed) {
1056 //trimm it back
1057 n.setRhythmValue(endLoc - beatCounter, truncated);
1058 }
1059 tempPhr.addNote(n);
1060 addedCounter += n.getRhythmValue();
1061 beatCounter += n.getRhythmValue();
1062 }
1063
1064 // is there more space past the end of the phrase?
1065 if (beatCounter < endLoc) { // make up a rest to fill the space
1066 Note r = new Note(REST, endLoc - beatCounter);
1067 tempPhr.addNote(r);
1068 } else if (addedCounter == 0.0) { // or if nothing was added at all
1069 Note r = new Note(REST, endLoc - startLoc);
1070 tempPhr.addNote(r);
1071 }
1072 // done!
1073 return tempPhr;
1074 }
1075
1076 /**
1077 * Returns a copy of the entire Phrase only ontaining notes
1078 * between highest and lowset specified pitch.
1079 * @ param highestPitch The top MIDI pitch to include in the copy
1080 * @ param lowestPitch The bottom MIDI pitch to include in the copy
1081 * @return Phrase a partical copy of the Phrase
1082 */
1083 public Phrase copy(int highestPitch, int lowestPitch){
1084 if (lowestPitch >= highestPitch) {
1085 System.err.println("jMusic Phrase copy error: "+
1086 "lowset pitch is not lower than highest pitch");
1087 System.exit(0);
1088 }
1089 Phrase phr = new Phrase(this.title + " copy");
1090// phr.setStartTime(this.startTime);
1091 phr.position = this.position.copy(phr);
1092 phr.setInstrument(this.instrument);
1093 phr.setAppend(this.append);
1094 phr.setPan(this.pan);
1095 phr.setLinkedPhrase(this.linkedPhrase);
1096 phr.setMyPart(this.getMyPart());
1097 Enumeration enum = this.noteList.elements();
1098 while(enum.hasMoreElements()){
1099 Note n = ((Note)enum.nextElement()).copy();
1100 if (n.getPitch() > highestPitch && n.getPitch() < lowestPitch) n.setPitch(REST);
1101 phr.addNote(n);
1102 }
1103 return phr;
1104 }
1105
1106
1107 /**
1108 * Prints the tracks attributes to stdout
1109 */
1110 public String toString(){
1111 String phraseData = new String("-------- jMusic PHRASE: '" +
1112 title + "' contains " + this.size() + " notes. Start time: " +
1113 getStartTime() +" --------" +'\n');
1114 if(this.tempo > 0) phraseData += "Phrase Tempo = "+ this.tempo + '\n';
1115 Enumeration enum = getNoteList().elements();
1116 int counter = 0;
1117 while(enum.hasMoreElements()){
1118 Note note = (Note) enum.nextElement();
1119 phraseData = phraseData + note.toString() + '\n';
1120 }
1121 return phraseData;
1122 }
1123
1124 /**
1125 * Empty removes all elements in the vector
1126 */
1127 public void empty(){
1128 noteList.removeAllElements();
1129 }
1130
1131 /**
1132 * Returns a carbon copy of a specified Phrase
1133 * Changes to notes in the original or the alias will be echoed in the other.
1134 * Note: that for this to work other phrase classes must to change the
1135 * noteList attribute to point to another object, but instead
1136 * should always update the noteList itself. See shuffle() as an example.
1137 */
1138 public Phrase alias() {
1139 Phrase phr = new Phrase(this.title + " alias", this.getStartTime(), this.instrument);
1140 phr.setTempo(this.tempo);
1141 phr.setAppend(this.append);
1142 phr.noteList = this.noteList;
1143 return phr;
1144 }
1145
1146 /**
1147 * Return the pitch value of the highest note in the phrase.
1148 */
1149 public int getHighestPitch() {
1150 int max = -1;
1151 Enumeration enum = getNoteList().elements();
1152 while(enum.hasMoreElements()){
1153 Note note = (Note) enum.nextElement();
1154 if(note.getPitchType() == Note.MIDI_PITCH)
1155 if (note.getPitch() > max) max = note.getPitch();
1156 }
1157 return max;
1158 }
1159
1160 /**
1161 * Return the pitch value of the lowest note in the phrase.
1162 */
1163 public int getLowestPitch() {
1164 int min = 128;
1165 Enumeration enum = getNoteList().elements();
1166 while(enum.hasMoreElements()){
1167 Note note = (Note) enum.nextElement();
1168 if(note.getPitchType() == Note.MIDI_PITCH)
1169 if(note.getPitch() < min && note.getPitch() >= 0 ) min = note.getPitch();;
1170 }
1171 return min;
1172 }
1173
1174 /**
1175 * Return the value of the longest rhythm value in the phrase.
1176 */
1177 public double getLongestRhythmValue() {
1178 double max = 0.0;
1179 Enumeration enum = getNoteList().elements();
1180 while(enum.hasMoreElements()){
1181 Note note = (Note) enum.nextElement();
1182 if(note.getRhythmValue() > max) max = note.getRhythmValue();
1183 }
1184 return max;
1185 }
1186
1187 /**
1188 * Return the value of the shortest rhythm value in the phrase.
1189 */
1190 public double getShortestRhythmValue() {
1191 double min = 1000.0;
1192 Enumeration enum = getNoteList().elements();
1193 while(enum.hasMoreElements()){
1194 Note note = (Note) enum.nextElement();
1195 if(note.getRhythmValue() < min) min = note.getRhythmValue();
1196 }
1197 return min;
1198 }
1199
1200 /**
1201 * Change the dynamic value of each note in the phrase.
1202 */
1203 public void setDynamic(int dyn) {
1204 Enumeration enum = getNoteList().elements();
1205 while(enum.hasMoreElements()){
1206 Note note = (Note) enum.nextElement();
1207 note.setDynamic(dyn);
1208 }
1209 }
1210
1211 /**
1212 * Change the pitch value of each note in the phrase.
1213 */
1214 public void setPitch(int val) {
1215 Enumeration enum = getNoteList().elements();
1216 while(enum.hasMoreElements()){
1217 Note note = (Note) enum.nextElement();
1218 note.setPitch(val);
1219 }
1220 }
1221
1222 /**
1223 * Change the rhythmValue value of each note in the phrase.
1224 */
1225 public void setRhythmValue(int val) {
1226 Enumeration enum = getNoteList().elements();
1227 while(enum.hasMoreElements()){
1228 Note note = (Note) enum.nextElement();
1229 note.setRhythmValue(val);
1230 }
1231 }
1232
1233 /**
1234 * Change the Duration value of each note in the phrase.
1235 */
1236 public void setDuration(double val) {
1237 Enumeration enum = getNoteList().elements();
1238 while(enum.hasMoreElements()){
1239 Note note = (Note) enum.nextElement();
1240 note.setDuration(val);
1241 }
1242 }
1243
1244 /**
1245 * Return the Duration of the phrase in beats.
1246 */
1247 public double getBeatLength() {
1248 return getEndTime();
1249 }
1250
1251 private final class Position implements Serializable{
1252 private double startTime = 0.0;
1253
1254 private final Phrase phrase;
1255
1256 private boolean isAbsolute = false;
1257
1258 private Position anchor;
1259
1260 private Alignment alignment = Alignment.AFTER;
1261
1262 private double offset;
1263
1264 private Position(final double startTime, final Phrase phrase) {
1265 this.isAbsolute = true;
1266 this.startTime = startTime;
1267 this.phrase = phrase;
1268 }
1269
1270 private Position(final Position anchor,
1271 final Alignment alignment,
1272 final double offset,
1273 final Phrase phrase) {
1274 this.isAbsolute = false;
1275 this.anchor = anchor;
1276 this.alignment = alignment;
1277 this.offset = offset;
1278 this.phrase = phrase;
1279 }
1280
1281 private final Anchoring getAnchoring() {
1282 if (isAbsolute) {
1283 return null;
1284 }
1285 return new Anchoring(anchor.phrase, alignment, offset);
1286 }
1287
1288 private final void setStartTime(final double startTime) {
1289 this.isAbsolute = true;
1290 this.startTime = startTime;
1291 }
1292
1293 private final double getStartTime() {
1294 if (isAbsolute) {
1295 return startTime;
1296 } else {
1297 return alignment.determineStartTime(
1298 phrase.getTotalDuration(),
1299 anchor.getStartTime(),
1300 anchor.getEndTime())
1301 + offset;
1302 }
1303 }
1304
1305 private final double getEndTime() {
1306 return phrase.getEndTime();
1307 }
1308
1309 private final Position copy(final Phrase newCopy) {
1310 return (isAbsolute)
1311 ? new Position(startTime, newCopy)
1312 : new Position(anchor, alignment, offset,
1313 newCopy);
1314 }
1315 }
1316
1317 /**
1318 * Generates and returns a new note with default values
1319 * and adds it to this phrase.
1320 */
1321 public Note createNote() {
1322 Note n = new Note();
1323 this.addNote(n);
1324 return n;
1325 }
1326
1327 /**
1328 * Specify a new volume amount for this phrase.
1329 */
1330 public void setVolume(int val) {
1331 this.volume = val;
1332 }
1333
1334 /**
1335 * Retreive the current volume setting for this phrase.
1336 */
1337 public int getVolume() {
1338 return this.volume;
1339 }
1340
1341 /*
1342 * Replace a note with another.
1343 * @param Note the new note
1344 * @param index the phrase position to replace
1345 */
1346 public void setNote(Note n, int index) {
1347 if(index >= this.getSize()) {
1348 System.out.println("jMusic error: Phrase setNote index is too large.");
1349 return;
1350 }
1351 this.noteList.removeElementAt(index);
1352 this.noteList.insertElementAt(n, index);
1353 }
1354
1355 /**
1356 * Specify the mute status of this phrase.
1357 * @param state True or False, muted or not.
1358 */
1359 public void setMute(boolean state) {
1360 this.mute = state;
1361 }
1362
1363 /**
1364 * Retrieve the current mute status.
1365 * @return boolean True or False, muted ot not.
1366 */
1367 public boolean getMute() {
1368 return this.mute;
1369 }
1370}
1371