source: trunk/src_apollo/org/apollo/audio/structure/OverdubbedFrame.java@ 315

Last change on this file since 315 was 315, checked in by bjn8, 16 years ago

Apollo spin-off added

File size: 15.6 KB
Line 
1package org.apollo.audio.structure;
2
3import java.util.ArrayList;
4import java.util.Collection;
5import java.util.Collections;
6import java.util.HashMap;
7import java.util.HashSet;
8import java.util.LinkedList;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12import java.util.Stack;
13
14import org.apollo.audio.util.SoundDesk;
15import org.apollo.mvc.AbstractSubject;
16
17/**
18 * Immutable outside package.
19 *
20 * @see {@link AudioStructureModel} for thread safe convention.
21 *
22 * @author Brook Novak
23 *
24 */
25public class OverdubbedFrame extends AbstractSubject {
26
27 private String frameName; // immutable
28
29 private Set<TrackGraphNode> tracks = new HashSet<TrackGraphNode>();
30
31 private Set<LinkedTracksGraphNode> linkedTracks = new HashSet<LinkedTracksGraphNode>();
32
33 /**
34 *
35 * @param frameName
36 *
37 * @throws NullPointerException
38 * if frameName is null.
39 *
40 * @throws IllegalArgumentException
41 * if frameName is empty.
42 */
43 OverdubbedFrame(String frameName) {
44 if (frameName == null) throw new NullPointerException("frameName");
45 else if (frameName.length() == 0) throw new IllegalArgumentException("frameName.length() == 0");
46
47 this.frameName = frameName;
48 }
49
50 /**
51 * @return
52 * The immutable framename for this overdubbed frame.
53 * Never null / empty.
54 */
55 public String getFrameName() {
56 return frameName;
57 }
58
59 /**
60 * @return
61 * A copy of the linked tracks. Never null.
62 * All unique (from a set)
63 */
64 public ArrayList<LinkedTracksGraphNode> getLinkedTracksCopy() {
65 return new ArrayList<LinkedTracksGraphNode>(linkedTracks);
66 }
67
68 /**
69 * @return
70 * An unmodifiable set of all the linked tracks. Never null.
71 */
72 public Set<LinkedTracksGraphNode> getUnmodifiableLinkedTracks() {
73 return Collections.unmodifiableSet(linkedTracks);
74 }
75
76 /**
77 * @return
78 * A copy of the tracks. Never null.
79 * All unique (from a set)
80 */
81 public ArrayList<TrackGraphNode> getTracksCopy() {
82 return new ArrayList<TrackGraphNode>(tracks);
83 }
84
85 /**
86 * @return
87 * An unmodifiable set of all the tracks. Never null.
88 */
89 public Set<TrackGraphNode> getUnmodifiableTracks() {
90 return Collections.unmodifiableSet(tracks);
91 }
92
93 /**
94 * Must be on swing thread.
95 *
96 * @param localFilename
97 * The track to get. Must not be null.
98 *
99 * @return
100 * The track with the given local filename. Null if does not exist.
101 *
102 * @throws NullPointerException
103 * If localFilename is null.
104 */
105 public TrackGraphNode getTrack(String localFilename) {
106 if (localFilename == null) throw new NullPointerException("localFilename");
107
108 for (TrackGraphNode track : tracks) {
109 if (track.getLocalFilename().equals(localFilename)) {
110 return track;
111 }
112 }
113
114 return null;
115 }
116
117 /**
118 * Must be on swing thread.
119 *
120 * @param virtualFilename
121 * The linked track to get. Must not be null.
122 *
123 * @return
124 * The linked track with the given virtual filename. Null if does not exist.
125 *
126 * @throws NullPointerException
127 * If virtualFilename is null.
128 */
129 public LinkedTracksGraphNode getLinkedTrack(String virtualFilename) {
130 if (virtualFilename == null) throw new NullPointerException("virtualFilename");
131
132 for (LinkedTracksGraphNode ltrack : linkedTracks) {
133 if (ltrack.getVirtualFilename().equals(virtualFilename)) {
134 return ltrack;
135 }
136 }
137
138 return null;
139 }
140
141
142 /**
143 * Must be on swing thread.
144 * Note: Only a shallow check.
145 *
146 * @param localFilename
147 * The track to check for. Must not be null.
148 *
149 * @return
150 * True if this OverdubbedFrame owns the track. False otherwise.
151 *
152 * @throws NullPointerException
153 * If localFilename is null.
154 */
155 public boolean containsTrack(String localFilename) {
156 if (localFilename == null) throw new NullPointerException("localFilename");
157
158 for (TrackGraphNode track : tracks) {
159 if (track.getLocalFilename().equals(localFilename)) {
160 return true;
161 }
162 }
163
164 return false;
165 }
166
167 /**
168 * Must be on swing thread.
169 * Note: Only a shallow check.
170 *
171 * @param virtualFilename
172 * The linked track to check for. Must not be null.
173 *
174 * @return
175 * True if this OverdubbedFrame owns the linked track. False otherwise.
176 *
177 * @throws NullPointerException
178 * If virtualFilename is null.
179 */
180 public boolean containsLinkedTrack(String virtualFilename) {
181 if (virtualFilename == null) throw new NullPointerException("virtualFilename");
182
183 for (LinkedTracksGraphNode ltrack : linkedTracks) {
184 if (ltrack.getVirtualFilename().equals(virtualFilename)) {
185 return true;
186 }
187 }
188
189 return false;
190 }
191
192 void addTrack(TrackGraphNode track) {
193 assert(track != null);
194 tracks.add(track);
195 }
196
197 void addAllTracks(Collection<TrackGraphNode> track) {
198 assert(track != null);
199 tracks.addAll(track);
200 }
201
202 void addLinkedTrack(LinkedTracksGraphNode linkedTrack) {
203 assert(linkedTrack != null);
204 linkedTracks.add(linkedTrack);
205 }
206
207 void addAllLinkedTracks(Collection<LinkedTracksGraphNode> linkedTracks) {
208 assert(linkedTracks != null);
209 linkedTracks.addAll(linkedTracks);
210 }
211
212 boolean removeTrack(TrackGraphNode track) {
213 assert(track != null);
214 return tracks.remove(track);
215 }
216
217 TrackGraphNode removeTrack(String localFilename) {
218 assert(localFilename != null);
219
220 TrackGraphNode track = getTrack(localFilename);
221
222 if (track != null && tracks.remove(track)) return track;
223
224 return null;
225 }
226
227 boolean removeLinkedTrack(LinkedTracksGraphNode ltrack) {
228 assert(ltrack != null);
229 return linkedTracks.remove(ltrack);
230 }
231
232 /**
233 * @see #trackExistsDeep
234 *
235 * @return
236 * True is there is no tracks or linked tracks.
237 */
238 public boolean isEmpty() {
239
240 return linkedTracks.isEmpty() && tracks.isEmpty();
241 }
242
243 /**
244 * Checks if there are any tracks on the frame
245 *
246 * @see #trackExistsDeep()
247 *
248 * @return
249 * True if there exists a track (NOT LINKED-TRACK) in this frame
250 */
251 public boolean trackExists() {
252 return !tracks.isEmpty();
253 }
254
255 /**
256 * Intention: to see if this overdub frame contains anything to play.
257 *
258 * @return
259 * True if there exists a track (NOT LINKED-TRACK) in this frame or its
260 * descendant frames.
261 */
262 public boolean trackExistsDeep() {
263 return doesTrackExist(new HashSet<OverdubbedFrame>());
264 }
265
266 private boolean doesTrackExist(Set<OverdubbedFrame> visited) {
267 if (!tracks.isEmpty()) return true;
268 else if (visited.contains(this)) return false;
269
270 // Remember this node
271 visited.add(this);
272
273 // Recurse step
274 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
275 if (linkedTrack.getLinkedFrame().doesTrackExist(visited))
276 return true;
277 }
278
279 return false;
280 }
281
282 /**
283 * Gets a child frame with the given name... if exists / is reachable from this node.
284 *
285 * @param targetFrameName
286 * THe target frame to get.
287 *
288 * @throws NullPointerException
289 * If targetFrameName is null.
290 *
291 * @return
292 * The target OverdubbedFrame. Null if none existed ...
293 */
294 public OverdubbedFrame getChild(String targetFrameName) {
295 return getChild(targetFrameName, new HashSet<OverdubbedFrame>());
296 }
297
298 private OverdubbedFrame getChild(String targetFrameName, Set<OverdubbedFrame> visited) {
299 if(targetFrameName == null) throw new NullPointerException("targetFrameName");
300
301 // Base cases
302 if (targetFrameName.equalsIgnoreCase(frameName)) return this;
303 else if (visited.contains(this)) return null;
304
305 // Remember this node
306 visited.add(this);
307
308 // Recurse step
309 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
310 OverdubbedFrame odFrame = linkedTrack.getLinkedFrame().getChild(targetFrameName, visited);
311 if (odFrame != null) return odFrame;
312 }
313
314 // Reached end of this nodes links - found no match
315 return null;
316 }
317
318
319 /**
320 * Recursivly calculates the total running time for this frame.
321 *
322 * @return
323 * The running time in milliseconds. Always positive. Zero if this frame has no tracks/linked tracks.
324 */
325 public long calculateRunningTime() {
326 return calculateRunningTime(new HashMap<OverdubbedFrame, Long>());
327 }
328
329 /**
330 * Calculates the running time of this overdubbed frame in milliseconds.
331 * Avoids infinit recursion.
332 *
333 * @param visited
334 * The visitive nodes
335 *
336 * @return
337 * The running time. Always positive. Zero if this frame has no tracks/linked tracks.
338 */
339 private long calculateRunningTime(Map<OverdubbedFrame, Long> visited) {
340
341 Long rt = visited.get(this);
342 if (rt != null) { // already considered - re-use value
343 return rt;
344 }
345
346 long runTime = 0;
347
348 if (!tracks.isEmpty() || !linkedTracks.isEmpty()) {
349
350 // Calculate the running time for this frame
351 long initTime = getFirstInitiationTime();
352 long relativeEndTime = Long.MIN_VALUE;
353
354 for (TrackGraphNode track : tracks) {
355 long ret = track.getInitiationTime() + track.getRunningTime();
356 if (ret > relativeEndTime) relativeEndTime = ret;
357 }
358
359 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
360
361 long ret = linkedTrack.getInitiationTime() +
362 linkedTrack.getLinkedFrame().calculateRunningTime(visited);
363
364 if (ret > relativeEndTime) relativeEndTime = ret;
365 }
366
367 assert (initTime <= relativeEndTime);
368
369 runTime = relativeEndTime - initTime;
370
371 }
372
373 // Save this ODFrames rt
374 visited.put(this, new Long(runTime));
375
376 return runTime;
377 }
378
379 /**
380 *
381 * @return
382 * The first initiation time of a track widget or track link on this frame.
383 * Note that these are relative and can be negative.
384 *
385 * Defaults to zero if there are no tracks/track links on this frame.
386 *
387 */
388 public long getFirstInitiationTime() { // relative to this frame - i.e. first initiation time on this frame
389
390 long smallest = 0;
391 boolean hasSet = false;
392
393 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
394 if (!hasSet || linkedTrack.getInitiationTime() < smallest) {
395 smallest = linkedTrack.getInitiationTime();
396 hasSet = true;
397 }
398 }
399
400 for (TrackGraphNode track : tracks) {
401 if (!hasSet || track.getInitiationTime() < smallest) {
402 smallest = track.getInitiationTime();
403 hasSet = true;
404 }
405 }
406
407 return smallest;
408 }
409
410 @Override
411 public String toString() {
412 return frameName;
413 }
414
415 /**
416 * @return
417 * All the TrackGraphInfo's in this frames descendants, including this frame.
418 * Never null. Can of coarse be empty.
419 */
420 public List<TrackGraphNode> getTracksDeep() {
421 List<TrackGraphNode> res = new LinkedList<TrackGraphNode>();
422 allAddTracksDeep(res, new HashSet<OverdubbedFrame>());
423 return res;
424 }
425
426 private void allAddTracksDeep(List<TrackGraphNode> tlist, Set<OverdubbedFrame> visited) {
427 if (visited.contains(this)) return; // already considered
428 visited.add(this);
429
430 tlist.addAll(tracks);
431
432 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
433 linkedTrack.getLinkedFrame().allAddTracksDeep(tlist, visited);
434 }
435 }
436
437 /**
438 * @param masterMixID
439 * The master mix for all the absolute tacks nodes to use. Must not be null or empty.
440 *
441 * @return
442 * The list of tracks and their ABS times according to <i>this frame</i>
443 * starting from ms time 0. Never null - can be empty.
444 */
445 public List<AbsoluteTrackNode> getAbsoluteTrackLayoutDeep(String masterMixID) {
446 assert(masterMixID != null);
447 assert(masterMixID.length() > 0);
448
449 List<AbsoluteTrackNode> res = new LinkedList<AbsoluteTrackNode>();
450 Stack<String> virtualPath = new Stack<String>();
451 virtualPath.add(masterMixID);
452
453 addAbsTrackNode(res, 0, new Stack<OverdubbedFrame>(), virtualPath);
454
455 return res;
456 }
457
458 private void addAbsTrackNode(
459 List<AbsoluteTrackNode> abslist,
460 long currentTime,
461 Stack<OverdubbedFrame> visited,
462 Stack<String> virtualPath) {
463
464 assert(currentTime >= 0);
465
466 // Base case
467 if (visited.contains(this)) return; // already considered - loop safety
468
469 visited.push(this);
470
471 // Get offset - align all tracks on this frame to first track/linked track...
472 long firstInitTime = getFirstInitiationTime();
473
474
475 // Add track info and init times for this frame
476 for (TrackGraphNode tnode : tracks) {
477 abslist.add(new AbsoluteTrackNode(
478 tnode,
479 currentTime + (tnode.getInitiationTime() - firstInitTime),
480 SoundDesk.createIndirectLocalChannelID(
481 virtualPath, tnode.getLocalFilename()),
482 frameName));
483 }
484
485 // Recurse: with a new current time according to the linked track init time
486 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
487 virtualPath.push(linkedTrack.getVirtualFilename()); // build vpath
488
489 linkedTrack.getLinkedFrame().addAbsTrackNode(
490 abslist,
491 currentTime + (linkedTrack.getInitiationTime() - firstInitTime),
492 visited,
493 virtualPath);
494
495 virtualPath.pop(); // maintain vpath
496 }
497
498 visited.pop();
499 }
500
501 /**
502 * Gets all frame positions that occur in a target child frame, from this frame...
503 * <b>WARNING</b>: Infinite recursion if contains loops in structure.
504 *
505 * @param childFrameName
506 * Must not be null. The frame to get all the translated frame positions
507 *
508 * @param relativeMSPosition
509 * A relative ms position for this frame
510 *
511 * @return
512 * A list of ms positions that have been translated ... empty
513 * if there are no occurances.
514 */
515 public List<Integer> getMSPositions(String childFrameName, long relativeMSPosition) {
516 assert(childFrameName != null);
517 assert(childFrameName.length() > 0);
518
519 List<Integer> positions = new LinkedList<Integer>();
520
521 addMSPosition(
522 positions,
523 childFrameName,
524 relativeMSPosition,
525 new HashMap<OverdubbedFrame, CachedODFrameInfo>());
526
527 return positions;
528
529 }
530
531 private class CachedODFrameInfo {
532
533 long runningTime;
534 long firstInitTime;
535
536 public CachedODFrameInfo(long runningTime, long firstInitTime) {
537 this.runningTime = runningTime;
538 this.firstInitTime = firstInitTime;
539 }
540 }
541
542 /**
543 * <b>WARNING</b>: Infinite recursion if contains loops in structure.
544 * A better safety Solution: Keep track of all virtual paths - NOT FRAMES
545 *
546 * @param msPositions
547 * @param childFrameName
548 * @param currentRelativePosition
549 * @param cachedCalcs
550 * @param visited
551 */
552 private void addMSPosition(
553 List<Integer> msPositions,
554 String childFrameName,
555 long currentRelativePosition,
556 Map<OverdubbedFrame, CachedODFrameInfo> cachedCalcs) {
557
558 // Avoid recomputation of calcs
559 CachedODFrameInfo calcCache = cachedCalcs.get(this);
560
561 if (calcCache == null) {
562
563 calcCache = new CachedODFrameInfo(
564 calculateRunningTime(),
565 getFirstInitiationTime());
566
567 cachedCalcs.put(this, calcCache);
568
569 }
570
571 assert(calcCache != null);
572
573 // Is requested frame position in range of this overdub frame?
574 if (childFrameName.equalsIgnoreCase(frameName) &&
575 currentRelativePosition >= calcCache.firstInitTime
576 && currentRelativePosition <= (calcCache.firstInitTime + calcCache.runningTime)) {
577 msPositions.add(new Integer((int)(currentRelativePosition - calcCache.firstInitTime)));
578 }
579
580 // Recurse - even if re-visiting visited frames... risky ... will
581 // get stack overflow if the structure contains loops
582 for (LinkedTracksGraphNode linkedTrack : linkedTracks) {
583
584 CachedODFrameInfo linkedFramesCachedCalc = cachedCalcs.get(linkedTrack.getLinkedFrame());
585
586 if (linkedFramesCachedCalc == null) {
587 linkedFramesCachedCalc = new CachedODFrameInfo(
588 linkedTrack.getLinkedFrame().calculateRunningTime(),
589 linkedTrack.getLinkedFrame().getFirstInitiationTime());
590
591 cachedCalcs.put(linkedTrack.getLinkedFrame(), linkedFramesCachedCalc);
592 }
593
594 // Only recuse down a link if the cuurent position is over top of the link
595 if (currentRelativePosition >= linkedTrack.getInitiationTime() &&
596 currentRelativePosition <= (linkedTrack.getInitiationTime() + linkedFramesCachedCalc.runningTime)) {
597
598 linkedTrack.getLinkedFrame().addMSPosition(
599 msPositions,
600 childFrameName,
601 linkedFramesCachedCalc.firstInitTime + (currentRelativePosition - linkedTrack.getInitiationTime()),
602 cachedCalcs);
603 }
604 }
605
606 }
607
608}
Note: See TracBrowser for help on using the repository browser.