source: trunk/src/org/apollo/audio/TrackSequence.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: 8.3 KB
Line 
1package org.apollo.audio;
2
3import javax.sound.sampled.AudioFormat;
4
5import org.apollo.mvc.AbstractSubject;
6import org.apollo.mvc.SubjectChangedEvent;
7
8/**
9 *
10 * A TrackSequence is a track which can reside in a track graph... for playing back at
11 * a scheduled time and mixing with other tracks in the graph.
12 *
13 * TrackSequence's are immutable (outside package). They can only be played once.
14 *
15 * @author Brook Novak
16 *
17 */
18public final class TrackSequence extends AbstractSubject implements Comparable<TrackSequence> {
19
20 /** FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER. */
21 int currentFrame; // buffer position. Not live.
22
23 private int startFrame; // within audio
24
25 /** The frame at which this track sequence was suspended at (in the audio mixers live timeline) */
26 private int suspendedFrame;
27
28 /** The frame at which this track began at (in the audio mixers live timeline) */
29 long commencedFrame;
30
31 /** FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER. */
32 int endFrame; // inclusive
33
34 private int relativeInitiationFrame;
35
36 /** FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER. */
37 long initiationFrame; // used in pipeline. Absolute. CAn be negitive
38
39 /** FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER. */
40 boolean isBigEndian;
41
42 // Not copied, Arrays are immutable (although contents are not!).
43 /** FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER. */
44 byte[] playbackAudioBytes; // snapshot of track bytes, avoid editing of model audio bytes while playing
45
46 private boolean isPlaying;
47
48 float volume; // friendly - for fast access in audio mixing pipeline, ... and carefully by the track model
49
50 boolean isMuted; // friendly - for fast access in audio mixing pipeline ... and carefully by the track model
51
52 boolean isSolo; // friendly - for fast access in audio mixing pipeline ...
53
54 private Object invoker;
55
56 /** FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER. */
57 boolean stopPending;
58
59 /**
60 * Prepares track sequence for playing. Must not be in a playing state.
61 *
62 * @param playbackAudioBytes
63 * The audio bytes to play.
64 *
65 * @param format
66 * The format of the audio bytes.
67 *
68 * @param startFrame
69 * The frame <i>within the track</i> where to start playback. Inclusive.
70 *
71 * @param endFrame
72 * The frame <i>within the track</i> where to end playback. Inclusive.
73 *
74 * @param relativeInitiationFrame
75 * Where to initiate the frame. This is relative to where the track should start playing
76 * in frames, when queued in the track graph.
77 *
78 * @param invoker
79 * A state object that may be of use.
80 *
81 * @throws IllegalArgumentException
82 * If startFrame >= endFrame. Or startFrame is not in audio range. or end frame is not in audio range
83 *
84 * @throws NullPointerException
85 * If audioBytes or format is null.
86 *
87 */
88 public TrackSequence(byte[] playbackAudioBytes,
89 AudioFormat format,
90 int startFrame,
91 int endFrame,
92 int relativeInitiationFrame,
93 Object invoker) {
94
95 if (startFrame >= endFrame)
96 throw new IllegalArgumentException("startFrame >= endFrame");
97
98 if (format == null)
99 throw new NullPointerException("format");
100
101 if (playbackAudioBytes == null)
102 throw new NullPointerException("audioBytes");
103
104 assert(SampledAudioManager.getInstance().isFormatSupportedForPlayback(format));
105
106 this.startFrame = this.currentFrame = startFrame;
107 this.endFrame = endFrame;
108 this.relativeInitiationFrame = relativeInitiationFrame;
109 this.initiationFrame = -1;
110 this.suspendedFrame = -1;
111 this.commencedFrame = -1;
112 this.isBigEndian = format.isBigEndian();
113 this.playbackAudioBytes = playbackAudioBytes;
114 this.isPlaying = false;
115 this.invoker = invoker;
116 this.stopPending = false;
117
118 assert (currentFrame >= 0 && currentFrame < (playbackAudioBytes.length / format.getFrameSize()));
119 assert (endFrame > currentFrame && endFrame < (playbackAudioBytes.length / format.getFrameSize()));
120
121 }
122
123 /**
124 * Prepares track sequence for playing. Must not be in a playing state.
125 *
126 * @param playbackAudioBytes
127 * The audio bytes to play.
128 *
129 * @param format
130 * The format of the audio bytes.
131 *
132 * @param startFrame
133 * The frame <i>within the track</i> where to start playback. Inclusive.
134 *
135 * @param endFrame
136 * The frame <i>within the track</i> where to end playback. Inclusive.
137 *
138 * @param relativeInitiationFrame
139 * Where to initiate the frame. This is relative to where the track should start playing
140 * in frames, when queued in the track graph.
141 *
142 * @throws IllegalArgumentException
143 * If startFrame >= endFrame. Or startFrame is not in audio range. or end frame is not in audio range
144 *
145 * @throws NullPointerException
146 * If audioBytes or format is null.
147 *
148 */
149 TrackSequence(
150 byte[] playbackAudioBytes,
151 AudioFormat format,
152 int startFrame,
153 int endFrame,
154 int relativeInitiationFrame) {
155 this(playbackAudioBytes, format, startFrame, endFrame, relativeInitiationFrame, null);
156 }
157
158 /**
159 * Order ascending by initiation frame
160 */
161 public final int compareTo(TrackSequence other)
162 {
163 int cmp = (new Long(initiationFrame)).compareTo(other.initiationFrame);
164
165 if (cmp == 0) {
166 return (new Integer(hashCode()).compareTo(other.hashCode()));
167 }
168
169 return cmp;
170 }
171
172 /**
173 * @return
174 * Where the track initiates/initiated. This is relative to where the track should start playing
175 * in frames, when queued in the track graph.
176 */
177 public int getRelativeInitiationFrame() {
178 return relativeInitiationFrame;
179 }
180
181 /**
182 * @return
183 * The frame position of where in the track the bytes have been mixed and buffered - not nessessarily
184 * been played back. This is not its live playback position.
185 */
186 public int getCurrentFrame() {
187 return currentFrame;
188 }
189
190 /**
191 * @return
192 * The last rendered frame when this was stopped.
193 * Negitive if not played and stopped yet.
194 */
195 public int getSuspendedFrame() {
196 return suspendedFrame;
197 }
198
199 /**
200 * FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER.
201 * Fires playback started event - later on swing thread.
202 */
203 final void onInitiated(long commencedFrame) {
204 this.commencedFrame = commencedFrame;
205 isPlaying = true;
206 fireSubjectChangedLaterOnSwingThread(
207 new SubjectChangedEvent(
208 ApolloSubjectChangedEvent.PLAYBACK_STARTED));
209 }
210
211 /**
212 * FRIENDLY: ONLY TO BE ACCESSED BY AUDIO MIXER.
213 * Fires playback stopped event - later on swing thread.
214 *
215 * @param suspendedFramePosition
216 * Must be in range of start frame and end frame (inclusive)
217 */
218 final void onStopped(int suspendedFramePosition) {
219 isPlaying = false;
220
221 assert(suspendedFramePosition >= startFrame);
222 assert(suspendedFramePosition <= endFrame);
223
224 this.suspendedFrame = suspendedFramePosition;
225
226 // Dispose of audio bytes
227 playbackAudioBytes = null;
228
229 fireSubjectChangedLaterOnSwingThread(
230 new SubjectChangedEvent(
231 ApolloSubjectChangedEvent.PLAYBACK_STOPPED));
232
233 }
234
235 /**
236 * @return
237 * true if the track is playing
238 */
239 public boolean isPlaying() {
240 return isPlaying;
241 }
242
243 /**
244 *
245 * @return
246 * True if this sequence has been played before.
247 *
248 */
249 public boolean hasFinished() {
250 return (playbackAudioBytes == null);
251 }
252
253 /**
254 * @return
255 * The object which invoked this track sequence. Can be null.
256 * Only used externally for 3rd party benifits.
257 */
258 public Object getInvoker() {
259 return invoker;
260 }
261
262 /**
263 * Note that the track won't solo unless the mixer is in solo mode.
264 *
265 * @param isSolo
266 * True to set this track as a solo subject.
267 */
268 public void setSoloFlag(boolean isSolo) {
269 this.isSolo = isSolo;
270 }
271
272 public boolean isMuted() {
273 return isMuted;
274 }
275
276 public void setMuted(boolean isMuted) {
277 this.isMuted = isMuted;
278 }
279
280 public float getVolume() {
281 return volume;
282 }
283
284 public void setVolume(float volume) {
285 // Clamp volume argument
286 if (volume < 0.0f) volume = 0.0f;
287 else if (volume > 1.0f) volume = 1.0f;
288 this.volume = volume;
289 }
290
291 /**
292 * @return
293 * The absolute frame when this track is to / was played in the software mixers timeline.
294 */
295 public long getInitiationFrame() {
296 return initiationFrame;
297 }
298
299 /**
300 * @return
301 * The frame at which this track began at (in the audio mixers live timeline)
302 */
303 public long getCommencedFrame() {
304 return commencedFrame;
305 }
306
307 /**
308 *
309 * @return
310 * The start frame <i>within the track</i> at which this track sequence starts / started
311 */
312 public int getStartFrame() {
313 return startFrame;
314 }
315
316 public int getEndFrame() {
317 return endFrame;
318 }
319
320
321
322
323
324
325}
Note: See TracBrowser for help on using the repository browser.