source: trunk/src/org/apollo/gui/FrameLayoutDaemon.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: 26.0 KB
Line 
1package org.apollo.gui;
2
3import java.io.File;
4import java.util.Collection;
5import java.util.HashMap;
6import java.util.Map;
7
8import org.apollo.audio.ApolloSubjectChangedEvent;
9import org.apollo.audio.structure.AbstractTrackGraphNode;
10import org.apollo.audio.structure.AudioStructureModel;
11import org.apollo.audio.structure.LinkedTracksGraphNode;
12import org.apollo.audio.structure.OverdubbedFrame;
13import org.apollo.audio.structure.TrackGraphLoopException;
14import org.apollo.audio.structure.TrackGraphNode;
15import org.apollo.audio.util.Timeline;
16import org.apollo.io.AudioIO;
17import org.apollo.io.AudioPathManager;
18import org.apollo.mvc.AbstractSubject;
19import org.apollo.mvc.Observer;
20import org.apollo.mvc.Subject;
21import org.apollo.mvc.SubjectChangedEvent;
22import org.apollo.util.Mutable;
23import org.apollo.widgets.LinkedTrack;
24import org.apollo.widgets.SampledTrack;
25import org.expeditee.core.Point;
26import org.expeditee.gio.EcosystemManager;
27import org.expeditee.gio.InputManager.WindowEventListener;
28import org.expeditee.gio.InputManager.WindowEventType;
29import org.expeditee.gio.gesture.StandardGestureActions;
30import org.expeditee.gui.DisplayController;
31import org.expeditee.gui.DisplayObserver;
32import org.expeditee.gui.Frame;
33import org.expeditee.gui.FreeItems;
34import org.expeditee.items.Item;
35import org.expeditee.items.ItemUtils;
36import org.expeditee.items.widgets.Widget;
37import org.expeditee.items.widgets.WidgetCorner;
38import org.expeditee.items.widgets.WidgetEdge;
39
40/**
41 * A daemon that lays out overdubbed frames.
42 *
43 * It is a singleton. Also it is a subject which raises empty events whenever
44 * a new timeline is created.
45 *
46 * @author Brook Novak
47 *
48 */
49public class FrameLayoutDaemon extends AbstractSubject implements Observer, DisplayObserver {
50
51 /** The frame that should not be layout out at any point in time. */
52 private Frame suspendedFrame = null;
53
54 /** The state info passed while suspending layout on {@link #suspendedFrame} */
55 private Object suspendendUserData = null;
56
57 private LayoutDaemon daemon = null;
58 private Object daemonWaker = new Object();
59 private boolean layoutInvalid = false;
60
61 private Timeline currentTimeline = null;
62 private Frame timelineOwner = null;
63
64 private int leftMargin = DEFAULT_LAYOUT_LEFT_MARGIN;
65 private int rightMargin = DEFAULT_LAYOUT_RIGHT_MARGIN;
66
67 /*
68 public static final int DEFAULT_LAYOUT_RIGHT_MARGIN = 30;
69 public static final int DEFAULT_LAYOUT_LEFT_MARGIN = 30;
70 public static final int MAX_TIMELINE_MARGIN_AREA = 400;
71 */
72
73 public static final int DEFAULT_LAYOUT_RIGHT_MARGIN = 170;
74 public static final int DEFAULT_LAYOUT_LEFT_MARGIN = 170; // used to be 100
75 public static final int MAX_TIMELINE_MARGIN_AREA = 500;
76
77
78 public static final int LAYOUT_MAX_INITIATION_PIXEL = 200; // used to be 100
79 public static final int MIN_TRACK_WIDGET_WIDTH = 20;
80 public static final int FREE_SPACE_MARGINS = 30;
81
82 public static final int TRACK_WIDGET_HEIGHT = 100;
83
84 public static final int TRACK_WIDGET_DEFAULT_WIDTH = 350;
85
86 private static FrameLayoutDaemon instance = new FrameLayoutDaemon();
87
88 /**
89 * Singleton constructor,
90 */
91 private FrameLayoutDaemon() {
92 daemon = new LayoutDaemon();
93 daemon.start();
94 AudioStructureModel.getInstance().addObserver(this);
95 DisplayController.addDisplayObserver(this);
96
97 EcosystemManager.getInputManager().addWindowEventListener(new WindowEventListener()
98 {
99 @Override
100 public void onWindowEvent(WindowEventType type)
101 {
102 if (type == WindowEventType.WINDOW_RESIZED) forceRecheck();
103 }
104 });
105
106 }
107
108 /**
109 * {@inheritDoc}
110 */
111 public void frameChanged() {
112 forceRecheck();
113 }
114
115 /**
116 *
117 * @return
118 * The singleton instance.
119 */
120 public static FrameLayoutDaemon getInstance() {
121 return instance;
122 }
123
124 /**
125 * @see #getTimelineOwner() for determining which frame the timeline was for.
126 *
127 * @return
128 * The last format hence the current timeline. Can be null
129 * if no format has not occured yet or last format
130 * had no timeline... i.e no tracks were found...
131 */
132 public Timeline getLastComputedTimeline() {
133 return currentTimeline;
134 }
135
136 /**
137 * @see #getLastComputedTimeline()
138 *
139 * @return
140 * Can be null if a timeline has not yet been computed.
141 */
142 public Frame getTimelineOwner() {
143 return timelineOwner;
144 }
145
146 public void forceRecheck() {
147 assert(daemon != null && daemon.isAlive());
148 layoutInvalid = true;
149 synchronized(daemonWaker) {
150 daemonWaker.notify();
151 }
152 }
153
154 /**
155 * {@inheritDoc}
156 */
157 public Subject getObservedSubject() {
158 return AudioStructureModel.getInstance();
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 public void setObservedSubject(Subject parent) {
165 }
166
167 /**
168 * {@inheritDoc}
169 */
170 public void modelChanged(Subject source, SubjectChangedEvent event) {
171 if (event.getID() != ApolloSubjectChangedEvent.NAME_CHANGED)
172 forceRecheck();
173 }
174
175 private void setCurrentTimeline(Timeline newTimeline, Frame timelineOwner) {
176 this.currentTimeline = newTimeline;
177 this.timelineOwner = timelineOwner;
178 fireSubjectChanged(SubjectChangedEvent.EMPTY);
179 }
180
181 /**
182 * Suspends the layout daemon from laying out a frame.
183 * Resumes any previous suspended layouts.
184 *
185 * @see #resumeLayout(Object)
186 *
187 * @param frame
188 * The frame to suspend
189 *
190 * @param userData
191 * Any user data to attatch to frame layout.
192 *
193 */
194 public void suspendLayout(Frame frame, Object userData) {
195 assert(frame != null);
196
197 resumeLayout(null);
198
199 suspendedFrame = frame;
200 suspendendUserData = userData;
201
202 }
203
204 /**
205 * Resumes the suspended layout if has one.
206 *
207 * @see #suspendLayout(Frame, Object)
208 *
209 * @param userData
210 * Null to resume layout regardless, otherwise it will only resume
211 * layout if the user data matches the user data passed when calling
212 * the suspension.
213 */
214 public void resumeLayout(Object userData) {
215 if (suspendedFrame == null) return;
216
217 if (userData == null || userData == suspendendUserData) {
218 suspendedFrame = null;
219 suspendendUserData = null;
220 }
221
222 }
223
224 public int getLeftMargin() {
225 return leftMargin;
226 }
227
228 public int getRightMargin() {
229 return rightMargin;
230 }
231
232 /**
233 * Sets the layout margins. Values are clamped.
234 *
235 * @param left
236 * The left margin in pixels.
237 *
238 * @param right
239 * The right margin in pixels.
240 *
241 */
242 public void setTimelineMargins(int left, int right) {
243
244 if (left < 0) left = 0;
245 if (right < 0) right = 0;
246
247 if ((left + right) >= MAX_TIMELINE_MARGIN_AREA) {
248 left = right = (MAX_TIMELINE_MARGIN_AREA / 2);
249 }
250
251 leftMargin = left;
252 rightMargin = right;
253 }
254
255 /**
256 * Lays out frames according to the current frame state...
257 *
258 * @author Brook Novak
259 *
260 */
261 private class LayoutDaemon extends Thread {
262
263 private TrackFormatter trackFormatter = new TrackFormatter();
264
265 LayoutDaemon() {
266 super("TrackLayoutDaemon");
267 super.setDaemon(true);
268 }
269
270 public void run() {
271
272 while (true) { // keep daemon running for app lifetime
273
274 if (!layoutInvalid) {
275 try {
276 synchronized(daemonWaker) {
277 daemonWaker.wait();
278 }
279 } catch (InterruptedException e) {
280 // Time to do some layout
281 }
282 }
283
284 layoutInvalid = false;
285
286 boolean hasUpdated = false;
287 do {
288 try {
289 AudioStructureModel.getInstance().waitOnUpdates();
290 hasUpdated = true;
291 } catch (InterruptedException e) {
292 e.printStackTrace();
293 continue;
294 }
295 } while (!hasUpdated);
296
297
298 // Note that accessing expeditee model in another thread --
299 // Being real careful not to do anything unsafe
300 Frame currentFrame = DisplayController.getCurrentFrame();
301 if (currentFrame == null) continue;
302
303 boolean hasFetched = false;
304 do {
305 try {
306 AudioStructureModel.getInstance().fetchGraph(currentFrame.getName());
307 hasFetched = true;
308 } catch (InterruptedException e) { // cancelled
309 /* Consume */
310 } catch (TrackGraphLoopException e) { // contains loop
311 e.printStackTrace();
312 break;
313 }
314 } while (!hasFetched);
315
316 trackFormatter.toFormat = currentFrame;
317
318 try {
319 EcosystemManager.getMiscManager().runOnGIOThread(trackFormatter);
320 } catch (Throwable e) {
321 e.printStackTrace();
322 }
323
324 }
325
326 }
327
328 private class TrackFormatter implements Runnable {
329
330 Frame toFormat = null;
331
332 public void run() {
333
334 assert(toFormat != null);
335
336 // Too late?
337 if (toFormat != DisplayController.getCurrentFrame()) return;
338
339 boolean supressSpatialLayout = suspendedFrame == toFormat;
340
341 long firstInitiationTime = 0;
342 int initiationXPixel = 0;
343 int rightMostPixel = 0; // FOR SUSPENDED LAYOUTS ONLY
344 long longestEndTime = 0;
345 boolean hasRunningTime = false; // A flag set to true if there is actual audio to [lay
346
347 Map<Widget, AbstractTrackGraphNode> widgetsToFormat = new HashMap<Widget, AbstractTrackGraphNode>();
348
349 for (Widget iw : toFormat.getInteractiveWidgets()) {
350
351 AbstractTrackGraphNode abinf = null;
352
353 if (iw instanceof SampledTrack) {
354 SampledTrack sampledTrackWidget = (SampledTrack)iw;
355
356
357 abinf = AudioStructureModel.getInstance().getTrackGraphInfo(
358 sampledTrackWidget.getLocalFileName(),
359 toFormat.getName());
360
361
362 } else if (iw instanceof LinkedTrack) {
363 LinkedTrack linkedTrackWidget = (LinkedTrack)iw;
364
365 if (linkedTrackWidget.shouldLayout()) {
366 // TrackGraphModel provides best data
367 abinf = AudioStructureModel.getInstance().getLinkedTrackGraphInfo(
368 linkedTrackWidget.getVirtualFilename(),
369 toFormat.getName());
370 }
371
372 }
373
374 if (abinf != null) {
375
376 if (widgetsToFormat.isEmpty() ||
377 abinf.getInitiationTime() < firstInitiationTime) {
378 firstInitiationTime = abinf.getInitiationTime();
379 initiationXPixel = iw.getX();
380 }
381
382 // Remember for suspended layouts
383 if (widgetsToFormat.isEmpty() ||
384 (iw.getX() + iw.getWidth()) > rightMostPixel) {
385 rightMostPixel = iw.getX() + iw.getWidth();
386 }
387
388
389 long endTime = abinf.getInitiationTime() + abinf.getRunningTime();
390 if (widgetsToFormat.isEmpty() || endTime > longestEndTime) {
391 longestEndTime = endTime;
392 }
393
394 widgetsToFormat.put(iw, abinf);
395
396 // Set flag
397 if (!hasRunningTime) {
398 hasRunningTime = abinf.getRunningTime() > 0;
399 }
400
401 }
402
403 }
404
405 // Anything to format?
406 if (!hasRunningTime || widgetsToFormat.isEmpty() || longestEndTime == firstInitiationTime) {
407
408 // NOTE: longestEndTime == firstInitiationTime indicates linked tracks
409 // linking to no actual audio data.
410
411 // Reset the current timeline
412 setCurrentTimeline(null, toFormat);
413 return;
414 }
415
416 assert (longestEndTime > firstInitiationTime);
417 assert(rightMostPixel >= initiationXPixel);
418
419 int timelineWidth = TRACK_WIDGET_DEFAULT_WIDTH;
420
421 if (widgetsToFormat.size() > 1) {
422
423 // Ensure that left most pixel is at a reasonable position
424 if (!supressSpatialLayout && (initiationXPixel < 0 ||
425 (initiationXPixel > DisplayController.getFramePaintArea().getWidth() && DisplayController.getFramePaintArea().getWidth() > leftMargin))) {
426 initiationXPixel = leftMargin;
427 } else if (!supressSpatialLayout && initiationXPixel > LAYOUT_MAX_INITIATION_PIXEL) {
428 initiationXPixel = LAYOUT_MAX_INITIATION_PIXEL;
429 }
430
431 if (supressSpatialLayout) {
432 timelineWidth = rightMostPixel - initiationXPixel;
433 } else {
434 timelineWidth = DisplayController.getFramePaintArea().getWidth() - initiationXPixel - rightMargin;
435 }
436
437 if (timelineWidth < 0) timelineWidth = 1;
438
439 } else {
440
441 // If only has one widget - the time line pixel length is set to whatever that is set to
442 timelineWidth = widgetsToFormat.keySet().iterator().next().getWidth();
443
444 }
445
446 // Set the new timelinetimelineWidth
447 setCurrentTimeline(
448 new Timeline(
449 firstInitiationTime,
450 longestEndTime - firstInitiationTime,
451 initiationXPixel,
452 timelineWidth),
453 toFormat);
454
455 // Do the actual formatting
456 if (!supressSpatialLayout) {
457
458 for (Widget iw : widgetsToFormat.keySet()) {
459
460 AbstractTrackGraphNode atgi = widgetsToFormat.get(iw);
461 assert(atgi != null);
462
463 // Update width first
464 int width = (int)((double)atgi.getRunningTime() / currentTimeline.getTimePerPixel());
465
466 // Clamp to something sensible
467 if (width < MIN_TRACK_WIDGET_WIDTH)
468 width = MIN_TRACK_WIDGET_WIDTH;
469
470 Collection<Item> enclosed = iw.getItems().get(0).getEnclosedItems();
471 Map<Item, Float> enclosedPortions = new HashMap<Item, Float>();
472 if (enclosed != null) {
473 for (Item i : enclosed) {
474 if (!(i instanceof WidgetCorner || i instanceof WidgetEdge)) {
475 float f = i.getX() - iw.getX();
476 f /= iw.getWidth();
477 enclosedPortions.put(i, f);
478 }
479
480 }
481 }
482
483 if (widgetsToFormat.size() == 1) {
484
485 if (iw.isFixedSize()) {
486 iw.setSize(
487 -1, -1, // minWidth, maxWidth,
488 iw.getHeight(), iw.getHeight(), // minHeight, maxHeight
489 iw.getWidth(), iw.getWidth()); // newWidth, newHeight
490 }
491
492
493 } else {
494
495 if (iw.getWidth() != width || !iw.isFixedSize()) {
496 iw.setSize(
497 width, width, // minWidth, maxWidth,
498 iw.getHeight(), iw.getHeight(), // minHeight, maxHeight
499 width, width); // newWidth, newHeight
500 }
501
502 }
503
504 // Update position last
505 int xpos = currentTimeline.getXAtMSTime(atgi.getInitiationTime());
506 iw.setPosition(xpos, iw.getY());
507
508 for (Item i : enclosedPortions.keySet()) {
509 i.setX(xpos + (enclosedPortions.get(i) * iw.getWidth()));
510 }
511
512 }
513
514 }
515
516 // Also since this daemons responisiblilty it to layout track widgets on frames ...
517 // it should also take care of free space items
518
519 Item i = FreeItems.getItemAttachedToCursor();
520 Widget freespaceTrackToFormat = null;
521 if (i != null && i instanceof WidgetEdge) {
522 freespaceTrackToFormat = ((WidgetEdge)i).getWidgetSource();
523 } else if (i != null && i instanceof WidgetCorner) {
524 freespaceTrackToFormat = ((WidgetCorner)i).getWidgetSource();
525 }
526
527 if (freespaceTrackToFormat != null) {
528
529 int width = -1;
530 if (freespaceTrackToFormat instanceof SampledTrack) {
531
532 long rt = ((SampledTrack)freespaceTrackToFormat).getRunningMSTimeFromRawAudio();
533 if (rt <= 0) rt = ((SampledTrack)freespaceTrackToFormat).getRunningMSTimeFromMeta();
534 if (rt > 0) {
535 width = (int)(currentTimeline.getPixelPerTime() * rt);
536 }
537
538 } else if (freespaceTrackToFormat instanceof LinkedTrack) {
539
540 long rt = ((LinkedTrack)freespaceTrackToFormat).getRunningMSTimeFromMeta();
541 if (rt > 0) {
542 width = (int)(currentTimeline.getPixelPerTime() * rt);
543 }
544 }
545
546 if (width > 0) {
547
548 if (width > (DisplayController.getFramePaintArea().getWidth() - (2 * FREE_SPACE_MARGINS))) {
549 width = DisplayController.getFramePaintArea().getWidth() - (2 * FREE_SPACE_MARGINS);
550 }
551
552 freespaceTrackToFormat.setSize(
553 width, width,
554 freespaceTrackToFormat.getHeight(), freespaceTrackToFormat.getHeight(),
555 width, freespaceTrackToFormat.getHeight());
556
557 // Keep the floating track on the cursor
558 Point containerPoint = EcosystemManager.getInputManager().getCursorPosition();
559
560 // If due to resize the track is not longer over the cursor then just center it.
561 if (!ItemUtils.expandRectangle(freespaceTrackToFormat.getBounds(), 20).contains(containerPoint)) {
562 int offsetX = width / 2;
563 int offsetY = freespaceTrackToFormat.getHeight() / 2;
564 freespaceTrackToFormat.setPosition(containerPoint.x - offsetX, containerPoint.y - offsetY);
565 StandardGestureActions.resetOffset();
566 }
567
568
569
570
571 }
572
573 }
574
575 }
576
577 }
578
579 }
580
581 /**
582 * Selects best timeline for given frame - may have to infer.
583 *
584 * @param targetFrame
585 *
586 * @return
587 * The best timeline for the given frame - null if could not infer (if had to).
588 */
589 public Timeline getTimeline(Frame targetFrame) {
590 assert(targetFrame != null);
591
592 if (currentTimeline != null && targetFrame == timelineOwner) return currentTimeline;
593
594 return inferTimeline(targetFrame);
595 }
596
597 /**
598 * Determines the ms time at a x pixel poistion on a target frame.
599 *
600 * @see #getMSTimeAndWidth(int, long, Frame) for efficient use.
601 *
602 * @param x
603 * The x pixel posotion to determine the ms time from.
604 *
605 * @param targetFrame
606 * The target frame that the ms will be derived from.
607 *
608 * @return
609 * The ms time at the x pixel poistion.
610 */
611 public long getMSAtX(int x, Frame targetFrame) {
612 return getMSAtX(x, getTimeline(targetFrame));
613 }
614
615 private long getMSAtX(int x, Timeline tl) {
616 if (tl != null) return tl.getMSTimeAtX(x);
617 return 0; // Default to zero with no timeline available.
618 }
619
620 /**
621 * @param ms
622 * The time to translate the x position from
623 *
624 * @param targetFrame
625 * The frame to determine the timeline information from
626 *
627 * @return
628 * Null if the timeline for the target frame could not
629 * be determined/inferred or has no timeline. Otherwise the
630 * X pixal position at the given time.
631 */
632 public Mutable.Long getXAtMS(long ms, Frame targetFrame) {
633 assert(targetFrame != null);
634
635 Timeline tl = getTimeline(targetFrame);
636 if (tl == null) return null;
637
638 return Mutable.createMutableLong(tl.getXAtMSTime(ms));
639 }
640
641 /**
642 * Determines the pixel width from a time range (ms) for a target frame.
643 * <b>NOTE</B> Width is clamped to it is never smaller than {@link #MIN_TRACK_WIDGET_WIDTH}
644 *
645 * @see #getMSTimeAndWidth(int, long, Frame) for efficient use.
646 *
647 * @param ms
648 * The amount of time to determine a width from (in milliseconds)
649 *
650 * @param targetFrame
651 * The target frame that the <i>clamped</i> width will be derived from.
652 *
653 * @return
654 * The width in pixels of the ms range. -1 if there is no timeline
655 */
656 public int getWidthFromMS(long ms, Frame targetFrame) {
657 return getWidthFromMS(ms, getTimeline(targetFrame));
658 }
659
660 private int getWidthFromMS(long ms, Timeline tl) {
661 int width = -1;
662 if (tl != null) {
663 width = (int)((double)ms / tl.getTimePerPixel());
664 }
665
666 // Clamp width to something reasonable.
667 if (width > 0 && width < MIN_TRACK_WIDGET_WIDTH) width = MIN_TRACK_WIDGET_WIDTH;
668
669 return width;
670 }
671
672 /**
673 * Gets the ms time and width in one call - more efficient than
674 * calling {@link #getMSAtX(int, Frame)} and {@link #getWidthFromMS(long, Frame)}
675 * seperatly.
676 *
677 * <b>NOTE</B> Width is clamped to it is never smaller than {@link #MIN_TRACK_WIDGET_WIDTH}
678 *
679 * @param x
680 * The x pixel posotion to determine the ms time from.
681 *
682 * @param ms
683 * The amount of time to determine a width from (in milliseconds)
684 *
685 * @param targetFrame
686 * The target frame that the ms and width will be derived from.
687 *
688 * @return
689 * A Two dimensional array of the ms time and <i>clamped</i> width, respectivly.
690 */
691 public long[] getMSTimeAndWidth(int x, long ms, Frame targetFrame) {
692
693 // Re-use timeline
694 Timeline tl = getTimeline(targetFrame);
695
696 return new long[] { getMSAtX(x, tl), getWidthFromMS(ms, tl)};
697 }
698
699
700 /**
701 * <b>MUST BE ON EXPEDITEE THREAD</b>
702 *
703 * If the frame is not loaded in TrackGraphModel then the timeline will
704 * be a rough estimation using meta data. Otherwise it will be accurate.
705 * Fast operation - does not recurse.
706 *
707 * @param frame
708 * The target frame for inferring the timeline.
709 *
710 * @return
711 * A timeline. Null if there are no track widgets fround on the frame.
712 */
713 public static Timeline inferTimeline(Frame frame) {
714 assert(frame != null);
715
716 // Unavilable times relative to zero - and used if no availabel times exist.
717 Integer unavilableInitiationXPixel = null;
718 Mutable.Long unavilableLongestEndTime = null;
719 Integer unavilableLongestEndTimeRightPixel = null;
720
721 Mutable.Long firstInitiationTime = null;
722 int initiationXPixel = 0;
723
724 long longestEndTime = -1;
725 int longestEndTimeRightPixel = 0;
726
727 // Look for all track widgets
728 for (Widget iw : frame.getInteractiveWidgets()) {
729
730 if (iw instanceof SampledTrack) {
731 SampledTrack sampledTrackWidget = (SampledTrack)iw;
732
733 // TrackGraphModel provides best data
734 TrackGraphNode tgi = AudioStructureModel.getInstance().getTrackGraphInfo(
735 sampledTrackWidget.getLocalFileName(),
736 frame.getName());
737
738 // Is the initiation time available?
739 Mutable.Long it = (tgi != null) ?
740 Mutable.createMutableLong(tgi.getInitiationTime()) :
741 sampledTrackWidget.getInitiationTimeFromMeta();
742
743 // If not - keep record left-most unavilable x pixel
744 if (it == null &&
745 (unavilableInitiationXPixel == null || iw.getX() < unavilableInitiationXPixel)) {
746 unavilableInitiationXPixel = iw.getX();
747
748 // If so - check to see if it is the first initiation time on the frame
749 } else if(it != null && (firstInitiationTime == null ||
750 it.value < firstInitiationTime.value)) {
751 firstInitiationTime = it;
752 initiationXPixel = iw.getX();
753 }
754
755 // Determine running time
756 long runningTime = sampledTrackWidget.getRunningMSTimeFromRawAudio();
757
758 if (runningTime <= 0 && tgi != null) {
759 runningTime = tgi.getRunningTime();
760
761 } else if (runningTime <= 0) {
762 runningTime = sampledTrackWidget.getRunningMSTimeFromMeta();
763 }
764
765 // Note that this will be in rare cases .. the meta should rarely be missing
766 // so cann afford to hang the thread a little for checking the actual time...
767 if (runningTime <= 0) {
768 File f = new File(AudioPathManager.AUDIO_HOME_DIRECTORY +
769 sampledTrackWidget.getLocalFileName());
770 if (f.exists()) {
771 try {
772 runningTime = AudioIO.getRunningTime(f.getPath());
773 } catch(Exception e) {
774 e.printStackTrace();
775 }
776 }
777 }
778
779 // If all failed, just set to one... this would be rare, and by setting to
780 // 1 it will just give a incorrect timeline - nothing catistrophic.
781 if (runningTime <= 0) {
782 runningTime = 1;
783 }
784
785 if (it == null) {
786 if (unavilableLongestEndTime == null ||
787 runningTime > unavilableLongestEndTime.value) {
788 unavilableLongestEndTime = Mutable.createMutableLong(runningTime);
789 unavilableLongestEndTimeRightPixel = iw.getX() + iw.getWidth();
790 }
791 } else {
792 long endTime = it.value + runningTime;
793 if (endTime > longestEndTime) {
794 longestEndTime = endTime;
795 longestEndTimeRightPixel = iw.getX() + iw.getWidth();
796 }
797 }
798
799 } else if (iw instanceof LinkedTrack) {
800 LinkedTrack linkedTrackWidget = (LinkedTrack)iw;
801
802 // TrackGraphModel provides best data
803 LinkedTracksGraphNode ltgi = AudioStructureModel.getInstance().getLinkedTrackGraphInfo(
804 linkedTrackWidget.getVirtualFilename(),
805 frame.getName());
806
807 Mutable.Long it = (ltgi != null) ?
808 Mutable.createMutableLong(ltgi.getInitiationTime()) :
809 linkedTrackWidget.getInitiationTimeFromMeta();
810
811 if (it == null &&
812 (unavilableInitiationXPixel == null || iw.getX() < unavilableInitiationXPixel)) {
813 unavilableInitiationXPixel = iw.getX();
814 } else if(it != null && (firstInitiationTime == null ||
815 it.value < firstInitiationTime.value)) {
816 firstInitiationTime = it;
817 initiationXPixel = iw.getX();
818 }
819
820 // Determine running time
821 long runningTime = (ltgi != null) ?
822 ltgi.getRunningTime() :
823 linkedTrackWidget.getRunningMSTimeFromMeta();
824
825 // Note that not recusing through the links because the frames might
826 // not be loaded yet. The TrackGraphModel handles such operations -
827 // and it would be to expensive / overkill to frame search here...
828
829
830 // If all failed, just set to one...
831 if (runningTime <= 0) {
832 runningTime = 1;
833 }
834
835 if (it == null) {
836 if (unavilableLongestEndTime == null ||
837 runningTime > unavilableLongestEndTime.value) {
838 unavilableLongestEndTime = Mutable.createMutableLong(runningTime);
839 unavilableLongestEndTimeRightPixel = iw.getX() + iw.getWidth();
840 }
841 } else {
842 long endTime = it.value + runningTime;
843 if (endTime > longestEndTime) {
844 longestEndTime = endTime;
845 longestEndTimeRightPixel = iw.getX() + iw.getWidth();
846 }
847 }
848
849 }
850
851 }
852
853 if (firstInitiationTime == null) firstInitiationTime = Mutable.createMutableLong(0L);
854
855 if (longestEndTime > 0) {
856
857 assert(longestEndTime > firstInitiationTime.value);
858 assert(longestEndTimeRightPixel > initiationXPixel);
859
860 long rttime = longestEndTime - firstInitiationTime.value;
861 int pxWidth = longestEndTimeRightPixel - initiationXPixel;
862 if (unavilableLongestEndTime != null &&
863 unavilableLongestEndTime.value > rttime) {
864 assert(unavilableInitiationXPixel != null);
865 rttime = unavilableLongestEndTime.value;
866 pxWidth = unavilableLongestEndTimeRightPixel - unavilableInitiationXPixel;
867 }
868
869 return new Timeline(
870 firstInitiationTime.value,
871 rttime,
872 initiationXPixel,
873 pxWidth);
874
875 } else if(unavilableInitiationXPixel != null) { // default
876
877 assert(unavilableInitiationXPixel != null);
878 assert(unavilableLongestEndTimeRightPixel != null);
879
880 return new Timeline(
881 0,
882 unavilableLongestEndTime.value,
883 unavilableInitiationXPixel,
884 unavilableLongestEndTimeRightPixel - unavilableInitiationXPixel);
885
886 }
887
888 return null;
889
890 }
891
892 /**
893 * @return
894 * The total ms time for the current frame. zero or negative if failed or there are no
895 * tracks to play.
896 *
897 */
898 public static long inferCurrentTotalMSTime() {
899
900 Frame currentFrame = DisplayController.getCurrentFrame();
901 String currentFrameName = (currentFrame != null) ? currentFrame.getName() : null;
902
903 if (currentFrameName == null) return -1;
904
905 OverdubbedFrame odf = AudioStructureModel.getInstance().getOverdubbedFrame(currentFrameName);
906
907 if (odf != null) {
908 return odf.calculateRunningTime();
909 }
910
911 // Infer time from meta
912 Timeline tl = FrameLayoutDaemon.inferTimeline(currentFrame);
913
914 if (tl != null) {
915 return tl.getRunningTime();
916 }
917
918 return -1;
919
920 }
921
922
923}
Note: See TracBrowser for help on using the repository browser.