source: trunk/src/org/apollo/gui/FrameLayoutDaemon.java@ 1434

Last change on this file since 1434 was 1434, checked in by bln4, 5 years ago

Implementation of ProfileManager. Refactor + additional content for how new profiles are created. The refactoring split out the creation of the default profile from user profiles. Refactoring revealed a long term bug that was causing user profiles to generate with incorrect information. The additional content fixed this bug by introducing the ${USER.NAME} variable, so that the default profile frameset can specify resource locations located in the users resource directory.

org.expeditee.auth.AuthenticatorBrowser
org.expeditee.auth.account.Create
org.expeditee.gui.Browser
org.expeditee.gui.management.ProfileManager
org.expeditee.setting.DirectoryListSetting
org.expeditee.setting.ListSetting
org.expeditee.settings.UserSettings

Implementation of ResourceManager as a core location to get resources from the file system. Also the additional variable ${CURRENT_FRAMESET} to represent the current frameset, so that images can be stored in the directory of the current frameset. This increases portability of framesets.

org.expeditee.gui.FrameIO
org.expeditee.gui.management.ResourceManager
org.expeditee.gui.management.ResourceUtil
Audio:

#NB: Audio used to only operate on a single directory. This has been updated to work in a same way as images. That is: when you ask for a specific resouce, it looks to the user settings to find a sequence of directories to look at in order until it manages to find the desired resource.


There is still need however for a single(ish) source of truth for the .banks and .mastermix file. Therefore these files are now always located in resource-<username>\audio.
org.apollo.agents.MelodySearch
org.apollo.audio.structure.AudioStructureModel
org.apollo.audio.util.MultiTrackPlaybackController
org.apollo.audio.util.SoundDesk
org.apollo.gui.FrameLayoutDaemon
org.apollo.io.AudioPathManager
org.apollo.util.AudioPurger
org.apollo.widgets.FramePlayer
org.apollo.widgets.SampledTrack

Images:

org.expeditee.items.ItemUtils

Frames:

org.expeditee.gui.FrameIO

Fixed a error in the FramePlayer class caused by an incorrect use of toArray().

org.apollo.widgets.FramePlayer


Added several short cut keys to allow for the Play/Pause (Ctrl + P), mute (Ctrl + M) and volume up/down (Ctrl + +/-) when hovering over SampledTrack widgets.

org.apollo.widgets.SampledTrack


Changed the way that Authenticate.login parses the new users profile to be more consistance with other similar places in code.

org.expeditee.auth.account.Authenticate


Encapsulated _body, _surrogateItemsBody and _primaryItemsBody in Frame class. Also changed getBody function to take a boolean flag as to if it should respect the current surrogate mode. If it should then it makes sure that labels have not changed since last time getBody was called.

org.expeditee.gui.Frame

File size: 26.1 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.mvc.AbstractSubject;
18import org.apollo.mvc.Observer;
19import org.apollo.mvc.Subject;
20import org.apollo.mvc.SubjectChangedEvent;
21import org.apollo.util.Mutable;
22import org.apollo.widgets.LinkedTrack;
23import org.apollo.widgets.SampledTrack;
24import org.expeditee.core.Point;
25import org.expeditee.gio.EcosystemManager;
26import org.expeditee.gio.InputManager.WindowEventListener;
27import org.expeditee.gio.InputManager.WindowEventType;
28import org.expeditee.gio.gesture.StandardGestureActions;
29import org.expeditee.gui.DisplayController;
30import org.expeditee.gui.DisplayObserver;
31import org.expeditee.gui.Frame;
32import org.expeditee.gui.FreeItems;
33import org.expeditee.gui.management.ResourceManager;
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.getFramePaintAreaWidth() && DisplayController.getFramePaintAreaWidth() > 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.getFramePaintAreaWidth() - 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.getFramePaintAreaWidth() - (2 * FREE_SPACE_MARGINS))) {
549 width = DisplayController.getFramePaintAreaWidth() - (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.getX() - offsetX, containerPoint.getY() - 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 File f = ResourceManager.getAudioResource(sampledTrackWidget.getLocalFileName(), DisplayController.getCurrentFrame());
771 if (f.exists()) {
772 try {
773 runningTime = AudioIO.getRunningTime(f.getPath());
774 } catch(Exception e) {
775 e.printStackTrace();
776 }
777 }
778 }
779
780 // If all failed, just set to one... this would be rare, and by setting to
781 // 1 it will just give a incorrect timeline - nothing catistrophic.
782 if (runningTime <= 0) {
783 runningTime = 1;
784 }
785
786 if (it == null) {
787 if (unavilableLongestEndTime == null ||
788 runningTime > unavilableLongestEndTime.value) {
789 unavilableLongestEndTime = Mutable.createMutableLong(runningTime);
790 unavilableLongestEndTimeRightPixel = iw.getX() + iw.getWidth();
791 }
792 } else {
793 long endTime = it.value + runningTime;
794 if (endTime > longestEndTime) {
795 longestEndTime = endTime;
796 longestEndTimeRightPixel = iw.getX() + iw.getWidth();
797 }
798 }
799
800 } else if (iw instanceof LinkedTrack) {
801 LinkedTrack linkedTrackWidget = (LinkedTrack)iw;
802
803 // TrackGraphModel provides best data
804 LinkedTracksGraphNode ltgi = AudioStructureModel.getInstance().getLinkedTrackGraphInfo(
805 linkedTrackWidget.getVirtualFilename(),
806 frame.getName());
807
808 Mutable.Long it = (ltgi != null) ?
809 Mutable.createMutableLong(ltgi.getInitiationTime()) :
810 linkedTrackWidget.getInitiationTimeFromMeta();
811
812 if (it == null &&
813 (unavilableInitiationXPixel == null || iw.getX() < unavilableInitiationXPixel)) {
814 unavilableInitiationXPixel = iw.getX();
815 } else if(it != null && (firstInitiationTime == null ||
816 it.value < firstInitiationTime.value)) {
817 firstInitiationTime = it;
818 initiationXPixel = iw.getX();
819 }
820
821 // Determine running time
822 long runningTime = (ltgi != null) ?
823 ltgi.getRunningTime() :
824 linkedTrackWidget.getRunningMSTimeFromMeta();
825
826 // Note that not recusing through the links because the frames might
827 // not be loaded yet. The TrackGraphModel handles such operations -
828 // and it would be to expensive / overkill to frame search here...
829
830
831 // If all failed, just set to one...
832 if (runningTime <= 0) {
833 runningTime = 1;
834 }
835
836 if (it == null) {
837 if (unavilableLongestEndTime == null ||
838 runningTime > unavilableLongestEndTime.value) {
839 unavilableLongestEndTime = Mutable.createMutableLong(runningTime);
840 unavilableLongestEndTimeRightPixel = iw.getX() + iw.getWidth();
841 }
842 } else {
843 long endTime = it.value + runningTime;
844 if (endTime > longestEndTime) {
845 longestEndTime = endTime;
846 longestEndTimeRightPixel = iw.getX() + iw.getWidth();
847 }
848 }
849
850 }
851
852 }
853
854 if (firstInitiationTime == null) firstInitiationTime = Mutable.createMutableLong(0L);
855
856 if (longestEndTime > 0) {
857
858 assert(longestEndTime > firstInitiationTime.value);
859 assert(longestEndTimeRightPixel > initiationXPixel);
860
861 long rttime = longestEndTime - firstInitiationTime.value;
862 int pxWidth = longestEndTimeRightPixel - initiationXPixel;
863 if (unavilableLongestEndTime != null &&
864 unavilableLongestEndTime.value > rttime) {
865 assert(unavilableInitiationXPixel != null);
866 rttime = unavilableLongestEndTime.value;
867 pxWidth = unavilableLongestEndTimeRightPixel - unavilableInitiationXPixel;
868 }
869
870 return new Timeline(
871 firstInitiationTime.value,
872 rttime,
873 initiationXPixel,
874 pxWidth);
875
876 } else if(unavilableInitiationXPixel != null) { // default
877
878 assert(unavilableInitiationXPixel != null);
879 assert(unavilableLongestEndTimeRightPixel != null);
880
881 return new Timeline(
882 0,
883 unavilableLongestEndTime.value,
884 unavilableInitiationXPixel,
885 unavilableLongestEndTimeRightPixel - unavilableInitiationXPixel);
886
887 }
888
889 return null;
890
891 }
892
893 /**
894 * @return
895 * The total ms time for the current frame. zero or negative if failed or there are no
896 * tracks to play.
897 *
898 */
899 public static long inferCurrentTotalMSTime() {
900
901 Frame currentFrame = DisplayController.getCurrentFrame();
902 String currentFrameName = (currentFrame != null) ? currentFrame.getName() : null;
903
904 if (currentFrameName == null) return -1;
905
906 OverdubbedFrame odf = AudioStructureModel.getInstance().getOverdubbedFrame(currentFrameName);
907
908 if (odf != null) {
909 return odf.calculateRunningTime();
910 }
911
912 // Infer time from meta
913 Timeline tl = FrameLayoutDaemon.inferTimeline(currentFrame);
914
915 if (tl != null) {
916 return tl.getRunningTime();
917 }
918
919 return -1;
920
921 }
922
923
924}
Note: See TracBrowser for help on using the repository browser.