source: trunk/src/org/apollo/audio/util/PlaybackClock.java@ 1102

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

File size: 5.7 KB
Line 
1package org.apollo.audio.util;
2
3import java.util.LinkedList;
4import java.util.List;
5
6import org.apollo.audio.ApolloPlaybackMixer;
7import org.apollo.audio.ApolloSubjectChangedEvent;
8import org.apollo.mvc.Observer;
9import org.apollo.mvc.Subject;
10import org.apollo.mvc.SubjectChangedEvent;
11import org.apollo.util.AudioMath;
12import org.expeditee.gui.DisplayController;
13
14/**
15 * The audio playback clock ticks at {@link #CLOCK_RESOLUTION} milliseconds and
16 * invokes all registered clock listeners <b>on the same thread as the clock<b>.
17 * Thus the clock is not entiry acurrate: its intention of use is for updating
18 * a GUI according to the live frame position in the software mixer.
19 *
20 * Thus all thread safety precautions and efficiencies are at the users discretion.
21 * The ideal usage would be to simply update some data without the need of locking
22 * and returning asap
23 *
24 * @see {@link ApolloPlaybackMixer}
25 *
26 * @author Brook Novak
27 *
28 */
29public class PlaybackClock {
30
31 public static final long CLOCK_RESOLUTION = 300;
32
33 private Clock clockThread = null;
34
35 private List<PlaybackClockListener> playbackClockListeners = new LinkedList<PlaybackClockListener>();
36
37 private static PlaybackClock instance = new PlaybackClock();
38 public static PlaybackClock getInstance() {
39 return instance;
40 }
41
42 /**
43 * Singleton constructor preps the PlaybackClock for listening for
44 * audio events from the software mixer
45 *
46 */
47 private PlaybackClock() {
48
49 ApolloPlaybackMixer.getInstance().addObserver(new Observer() {
50
51 public Subject getObservedSubject() {
52 return ApolloPlaybackMixer.getInstance();
53 }
54 public void setObservedSubject(Subject parent) {
55 }
56
57 public void modelChanged(Subject source, SubjectChangedEvent event) {
58
59 if (event.getID() == ApolloSubjectChangedEvent.PLAYBACK_STARTED) {
60
61 // Start the clock
62 assert(clockThread == null);
63 clockThread = new Clock();
64 clockThread.start();
65
66 } else if (event.getID() == ApolloSubjectChangedEvent.PLAYBACK_STOPPED) {
67
68 // Kill the clock
69 clockThread.interrupt();
70 clockThread = null; // it will die in its own time
71 }
72 }
73
74 });
75 }
76
77 /**
78 * Adds a PlaybackClockListener for receiveing notifications whenever audio is played.
79 *
80 * @param listener
81 * The listener to add. If already added then it won't be added again.
82 *
83 * @throws NullPointerException
84 * If listener is null.
85 */
86 public void addPlaybackClockListener(PlaybackClockListener listener) {
87 if (listener == null) throw new NullPointerException("listener");
88 synchronized(playbackClockListeners) {
89 if (!playbackClockListeners.contains(listener))
90 playbackClockListeners.add(listener);
91 }
92 }
93
94 /**
95 * Removes a listener such that it wil stop receiving notifications.
96 *
97 * @param listener
98 * The listener to remove.
99 *
100 * @throws NullPointerException
101 * If listener is null.
102 */
103 public void removePlaybackClockListener(PlaybackClockListener listener) {
104 if (listener == null) throw new NullPointerException("listener");
105 synchronized(playbackClockListeners) {
106 playbackClockListeners.remove(listener);
107 }
108 }
109
110 /**
111 * The clock periodically wakes and updates playback-related GUI according to the
112 * live position of the playback.
113 *
114 * The clock thread dies once its interupted.
115 *
116 * @author Brook Novak
117 *
118 */
119 private class Clock extends Thread {
120
121 Clock() {
122 super("Playback Clock");
123 }
124
125 public void run() {
126
127 try {
128 long framePos, msPos;
129 boolean shouldRepaint;
130
131 while (!interrupted()) {
132
133 // Get the live time in frames - directly from the hardware
134 framePos = ApolloPlaybackMixer.getInstance().getLiveFramePosition();
135
136 shouldRepaint = false;
137
138 if (framePos >= 0) { // is playing still?
139
140 msPos = AudioMath.framesToMilliseconds(
141 framePos,
142 ApolloPlaybackMixer.getInstance().getLiveAudioFormat());
143
144 // Update the GUI. Because the clock awakes in small time slices must
145 // avoid updating the GUI on the swing thread: otherwise the AWT Event
146 // queue could easily become saturated.
147 synchronized(playbackClockListeners) { // arg, the most expensive operation!
148
149 for (PlaybackClockListener listener : playbackClockListeners) {
150
151 listener.onTick(framePos, msPos); // trusting that an exception does not occur
152 // If one does occur the worst that can happen is the the user doesn't
153 // have playback freedback.
154 shouldRepaint = true;
155 }
156
157 }
158
159 }
160
161 // Do a repaint is something was listening
162 if (shouldRepaint) {
163 DisplayController.requestRefresh(true); // later - should not congest AWT queue
164 }
165
166 sleep(CLOCK_RESOLUTION);
167
168 } // Keep updating GUI until interupted
169 } catch (InterruptedException e) { // consume
170 // Done
171 }
172
173 // Notify that have completed
174 synchronized(playbackClockListeners) {
175
176 for (PlaybackClockListener listener : playbackClockListeners) {
177
178 listener.onTick(-1, -1);
179
180 }
181
182 }
183
184 }
185
186 }
187
188 /**
189 * A PlaybackClockListener is periodically notified while there is playback occuring.
190 *
191 * @author Brook Novak
192 *
193 */
194 public interface PlaybackClockListener {
195
196 /**
197 * At every tick, this is invoked <i>directly from the clock thread</i>
198 *
199 * @see {@link PlaybackClock} for implementation considerations.
200 *
201 * @param framePosition
202 * The current position of the playback in frames.
203 * Negative if playback has finished.
204 *
205 * @param msPosition
206 * The current position of the playback in milliseconds.
207 * Negative if playback has finished.
208 *
209 */
210 public void onTick(long framePosition, long msPosition);
211
212 }
213}
Note: See TracBrowser for help on using the repository browser.