Source code: jmqt/QTCycle.java
1 /*
2 <This Java Class is part of the jMusic API version 1.4, February 2003.>
3
4 Copyright (C) 2000 Andrew Sorensen & Andrew Brown
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or any
9 later version.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 package jmqt;
22
23 import jm.JMC;
24 import jm.music.data.*;
25 import jmqt.QTUtil;
26 import jm.util.View;
27 import jm.music.tools.Mod;
28 import javax.swing.*;
29 import javax.swing.event.*;
30 import java.awt.event.*;
31
32 import quicktime.*;
33 import quicktime.app.time.Ticklish;
34 import quicktime.app.time.Timer;
35
36 /**
37 * Real Time MIDI playback for jMusic using Apple's QuickTime Java API.
38 * To use, create a score and pass it to the constructor of this class
39 * then call the startPlayback() method (only once!) to begin looping.
40 * Use suspendPlayback() and resumePlayback() to interupt the playback
41 * once started.
42 * It is the responsibility of the calling app to
43 * update the scores as required, and numerous accessor methods are
44 * provided to adjust elements of the score or cycle process on the fly.
45 * A swing GUI is provided to adjust the scheduling
46 * parameters as required. It is initiated by thecalling the settings() method.
47 *
48 * @author Andrew Brown
49 */
50
51 public final class QTCycle implements JMC, ChangeListener, ActionListener, Ticklish{
52 private Thread myThread;
53 private QTUtil qtu1;
54 private Object obj;
55 private Score score = new Score();
56 private double tempo = 60.0; // bpm
57 private double nextTempo = 0.0; // bpm, 0.0 == off
58 private double scoreLength = CROTCHET;
59 private double waitTime;
60 private double anticipationAmt = 0.0;
61 private int cycleCount = 0;
62 // non accessable attributes
63 private double time;
64 private JSlider tempoSlider, sleepSlider, gapSlider;
65 private JLabel tempoNumber, sleepNumber, gapNumber;
66 private JButton startButton, stopButton;
67 private boolean newScoreFlag = true;
68 private Timer timer;
69
70 /**
71 * Create an empty object ready to add a score to with setScore().
72 */
73 public QTCycle() {
74 this(new Score( new Part( new Phrase(new Note(REST, 0.125)))));
75 }
76
77 /**
78 * @param Score the first score object
79 */
80 public QTCycle(Score score) {
81 this.obj = obj;
82 this.score = score;
83 this.scoreLength = score.getEndTime();
84 this.tempo = score.getTempo();
85 qtu1 = new QTUtil(this.score);
86 // setup quicktime
87 try {
88 QTSession.open();
89 // ms time for loop
90 int ms = (int)(1000 * scoreLength * 60.0/tempo);
91 // n times every n seconds - (3, 2, this) = two times every three seconds
92 //System.out.println("tempo = " + tempo + " length = " + scoreLength + " ms = " + ms);
93 timer = new Timer(1000, ms, this);
94 //timer.setActive(true);
95 timer.setRate (1.0f);
96 } catch (QTException e) {
97 System.out.println("Timer error");
98 }
99 }
100
101 /**
102 * Begin the cycling playback of the scores.
103 */
104 public void startPlayback() {
105 try {
106 timer.setActive(true);
107 } catch (QTException e) {
108 System.out.println("Timer playback error");
109 }
110 }
111
112 /**
113 * Shedule score playback and score updating when required.
114 */
115 private void playCycle() {
116 updateTempo();
117 qtu1.setSpeed(tempo/60.0);
118 if (newScoreFlag) {
119 qtu1.playback(score);
120 newScoreFlag = false;
121 } else qtu1.replay();
122 cycleCount ++;
123 }
124
125 /**
126 * Specifies the length of a score to be played.
127 * Changes the interval between score update calls.
128 */
129 public void setScoreLength(double newLength) {
130 if (newLength > 0.0) scoreLength = newLength;
131 }
132
133 /**
134 * Reports the current score length setting.
135 */
136 public double getScoreLength() {
137 return scoreLength;
138 }
139
140 /**
141 * Updates the score to be played.
142 */
143 public void setScore(Score s) {
144 this.score = s.copy();
145 scoreLength = s.getEndTime();
146 tempo = s.getTempo();
147 newScoreFlag = true;
148 }
149
150 /**
151 * Specifies the speed in beats per minute.
152 */
153 public void setTempo(double newTempo) {
154 if (newTempo > 0.0) tempo = newTempo;
155 }
156
157 /**
158 * Reports the current speed in beats per minute.
159 */
160 public double getTempo() {
161 return tempo;
162 }
163
164 /**
165 * Specifies the speed in beats per minute.
166 */
167 public void setNextTempo(double newTempo) {
168 if (newTempo > 0.0) nextTempo = newTempo;
169 }
170
171 /**
172 * Updates the current speed in beats per minute
173 * based on a waiting tempo change.
174 * This prevents gaps or overlaps in the playback
175 * which can occur from the use of setTempo()
176 * which updates immediatly.
177 */
178 public void updateTempo() {
179 if (nextTempo != 0.0) {
180 tempo = nextTempo;
181 nextTempo = 0.0;
182 }
183 }
184
185 /**
186 * Choose the balance between computing power
187 * allocated to music timing compared to other tasks.
188 * There are constants such as Thread.MAX_PRIORITY
189 */
190 public void setThreadPriority(int newPriority) {
191 myThread.setPriority(newPriority);
192 }
193
194 /**
195 * Specify the number ot count cycles from.
196 */
197 public void setCycleCount(int newCount) {
198 this.cycleCount = newCount;
199 }
200
201 /**
202 * Report the number of times the cycle method has been run.
203 * Useful for counting beats , bars, phrases or
204 * whatever chunk of music was passed to this class.
205 */
206 public int getCycleCount() {
207 return this.cycleCount;
208 }
209
210 /**
211 * provide access to the quicktime utility object used by this class
212 */
213 public QTUtil getQTUtil() {
214 return this.qtu1;
215 }
216
217 /**
218 * If paused, restart playback.
219 */
220 public void resumePlayback() {
221 try{
222 timer.setActive(true);
223 } catch (QTException e) {
224 System.out.println("Timer resume error");
225 }
226 //myThread.resume();
227 }
228
229 /**
230 * Pause playback.
231 */
232 public void suspendPlayback() {
233 try {
234 timer.setActive(false);
235 } catch (QTException e) {
236 System.out.println("Timer error");
237 }
238 /*
239 myThread.suspend();
240 qtu1.stopPlayback();
241 */
242 }
243
244 /**
245 * Update the sleep time value..
246 */
247 public void setAnticipationAmt( double newGap) {
248 this.anticipationAmt = newGap;
249 }
250
251
252 /**
253 * Report the current loop anticipation time.
254 */
255 public double getAnticipationAmt() {
256 return this.anticipationAmt;
257 }
258
259 /**
260 * Open a GUI with sliders to adjust the scheduler loading.
261 * Used to optimise the timing and efficiecy of the real time
262 * QuickTime playback.
263 */
264 public void settings() {
265 JFrame f = new JFrame("Thread GUI");
266 f.setSize(400, 200);
267 Box masterBox = new Box(0);
268 // tempo
269 Box b1 = new Box(1);
270 JLabel tempoTitle = new JLabel("Tempo");
271 b1.add(tempoTitle);
272 tempoSlider = new JSlider(1, 20, 200, 120);
273 tempoSlider.addChangeListener(this);
274 b1.add(tempoSlider);
275 tempoNumber = new JLabel("120");
276 b1.add(tempoNumber);
277 masterBox.add(b1);
278
279
280 // time in ms to start score early to copmpensate for slow processing
281 Box b3 = new Box(1);
282 JLabel gapTitle = new JLabel("Anticipation");
283 b3.add(gapTitle);
284 gapSlider = new JSlider(1, 0, 100, (int)(anticipationAmt));
285 gapSlider.addChangeListener(this);
286 b3.add(gapSlider);
287 gapNumber = new JLabel(Double.toString(anticipationAmt));
288 b3.add(gapNumber);
289 masterBox.add(b3);
290
291 // buttons
292 Box b4 = new Box(1);
293 startButton = new JButton("Start");
294 startButton.addActionListener(this);
295 b4.add(startButton);
296 stopButton = new JButton("Stop");
297 stopButton.addActionListener(this);
298 b4.add(stopButton);
299 masterBox.add(b4);
300
301 f.getContentPane().add(masterBox);
302 f.setVisible(true);
303 }
304
305 public void stateChanged(ChangeEvent e) {
306 if(e.getSource() == tempoSlider) changeTempo();
307 if(e.getSource() == gapSlider) changeGap();
308 }
309
310 private void changeTempo() {
311 tempo = (double)(tempoSlider.getValue());
312 tempoNumber.setText(Integer.toString(tempoSlider.getValue()));
313 }
314
315 private void changeGap() {
316 anticipationAmt = (double)(gapSlider.getValue());
317 gapNumber.setText(Integer.toString(gapSlider.getValue()));
318 }
319
320 public void actionPerformed(ActionEvent ae) {
321 if(ae.getSource() == startButton) resumePlayback();
322 if(ae.getSource() == stopButton) suspendPlayback();
323 }
324
325 // required methods for Ticklish interface
326 public boolean tickle(float er, int timer) {
327 //System.out.println("Tickle: " + er + " " + timer);
328 playCycle();
329 return true;
330 }
331
332 public void timeChanged(int newTime) {
333 //System.out.println("Time changed to " + newTime);
334 }
335 }