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

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

Added y-pixel information to audio model.
Added Position-info retreival in quick text search util.
Added mutable primitive type
Linked track layout views now position tracks according to position in frame...a long coming modification.

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