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

Last change on this file since 1558 was 1558, checked in by davidb, 3 years ago

Changed to CLOCK_RESOLUTION comes from the users Settings frameset; also some comment/spelling tidyup

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