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