[315] | 1 | package org.apollo.audio.structure;
|
---|
| 2 |
|
---|
| 3 | import java.io.FileNotFoundException;
|
---|
| 4 | import java.io.IOException;
|
---|
| 5 | import java.lang.reflect.InvocationTargetException;
|
---|
| 6 | import java.util.HashMap;
|
---|
| 7 | import java.util.HashSet;
|
---|
| 8 | import java.util.LinkedList;
|
---|
| 9 | import java.util.List;
|
---|
| 10 | import java.util.Map;
|
---|
| 11 | import java.util.Queue;
|
---|
| 12 | import java.util.Set;
|
---|
| 13 | import java.util.Stack;
|
---|
| 14 |
|
---|
| 15 | import javax.sound.sampled.UnsupportedAudioFileException;
|
---|
| 16 | import javax.swing.SwingUtilities;
|
---|
| 17 |
|
---|
| 18 | import org.apollo.audio.ApolloSubjectChangedEvent;
|
---|
| 19 | import org.apollo.io.AudioIO;
|
---|
| 20 | import org.apollo.io.AudioPathManager;
|
---|
| 21 | import org.apollo.mvc.AbstractSubject;
|
---|
| 22 | import org.apollo.mvc.SubjectChangedEvent;
|
---|
| 23 | import org.apollo.util.ExpediteeFileTextSearch;
|
---|
[363] | 24 | import org.apollo.util.Mutable;
|
---|
[315] | 25 | import org.apollo.util.TextItemSearchResult;
|
---|
| 26 | import org.apollo.widgets.LinkedTrack;
|
---|
[318] | 27 | import org.apollo.widgets.SampledTrack;
|
---|
[315] | 28 | import org.apollo.widgets.TrackWidgetCommons;
|
---|
| 29 | import org.expeditee.gui.DisplayIO;
|
---|
| 30 | import org.expeditee.gui.Frame;
|
---|
| 31 | import org.expeditee.gui.FrameGraphics;
|
---|
| 32 | import org.expeditee.gui.FrameIO;
|
---|
| 33 | import org.expeditee.gui.UserSettings;
|
---|
| 34 | import org.expeditee.io.Conversion;
|
---|
| 35 | import org.expeditee.items.ItemUtils;
|
---|
| 36 | import org.expeditee.items.widgets.InteractiveWidget;
|
---|
| 37 |
|
---|
| 38 | /**
|
---|
| 39 | * A thread safe model of the heirarchical structure of a track graph...
|
---|
| 40 | * abstracted from Expeditee frames and audio widgets.
|
---|
| 41 | *
|
---|
| 42 | * The track widgets notifty this model for keeping the structure consistent with
|
---|
| 43 | * expeditees data.
|
---|
| 44 | *
|
---|
| 45 | *
|
---|
| 46 | * <b>Thread safe convention:</b>
|
---|
| 47 | * OverdubbedFrame's and TrackGraphInfo are always handled on the swing thread.
|
---|
| 48 | * This class provides asynch loading routines but that is purely for loading - not
|
---|
| 49 | * handling!
|
---|
| 50 | *
|
---|
| 51 | * The track graph is a loop-free directed graph.
|
---|
| 52 | *
|
---|
| 53 | * A graph root is a graph of OverdubbedFrame's
|
---|
| 54 | *
|
---|
| 55 | * Although a {@link TrackGraphNode} or a {@link LinkedTracksGraphNode} may reside
|
---|
| 56 | * in multiple graphs, there is always only one instance of them - they
|
---|
| 57 | * are just shared.
|
---|
| 58 | *
|
---|
| 59 | * There are multiple graphs because Graphs can be mutually exlusive.
|
---|
| 60 | * Or some frames cannot be reached unless they are a starting point (aka root)
|
---|
| 61 | *
|
---|
| 62 | * @author Brook Novak
|
---|
| 63 | *
|
---|
| 64 | */
|
---|
| 65 | public class AudioStructureModel extends AbstractSubject {
|
---|
| 66 |
|
---|
| 67 | /**
|
---|
| 68 | * A loop-free directed graph.
|
---|
| 69 | * For each graph root, there can be no other graph root that is
|
---|
| 70 | * reachable (other than itself).
|
---|
| 71 | */
|
---|
| 72 | private Set<OverdubbedFrame> graphRoots = new HashSet<OverdubbedFrame>(); // SHARED RESOURCE
|
---|
| 73 |
|
---|
| 74 | /** All overdubbed frames loaded in memory. MAP = Framename -> Overdub instance */
|
---|
| 75 | private Map<String, OverdubbedFrame> allOverdubbedFrames = new HashMap<String, OverdubbedFrame>(); // SHARED RESOURCE
|
---|
| 76 |
|
---|
| 77 | /** For resources {@link AudioStructureModel#graphRoots} and {@link AudioStructureModel#allOverdubbedFrames} */
|
---|
| 78 | private Object sharedResourceLocker = new Object();
|
---|
| 79 |
|
---|
| 80 | private boolean cancelFetch = false;
|
---|
| 81 |
|
---|
| 82 | private DelayedModelUpdator delayedModelUpdator = null;
|
---|
| 83 |
|
---|
| 84 | private static AudioStructureModel instance = new AudioStructureModel();
|
---|
| 85 |
|
---|
| 86 | private AudioStructureModel() { }
|
---|
| 87 |
|
---|
| 88 | public static AudioStructureModel getInstance() {
|
---|
| 89 | return instance;
|
---|
| 90 | }
|
---|
| 91 |
|
---|
| 92 | /**
|
---|
[363] | 93 | * <b>MUST NOT BE IN THE EXPEDITEE THREAD! OTHERWISE WILL DEFINITELY DEADLOCK</b>
|
---|
[315] | 94 | *
|
---|
| 95 | * Same as {@link #fetchGraph(String)} but waits for any updates to finish before
|
---|
| 96 | * the fetch.
|
---|
| 97 | *
|
---|
| 98 | * Intention: really to highlight the need to call waitOnUpdates prior to this ... but
|
---|
| 99 | * may not bee neccessary...
|
---|
| 100 | *
|
---|
| 101 | * @param rootFrameName
|
---|
| 102 | * refer to #fetchGraph(String)
|
---|
| 103 | * @return
|
---|
| 104 | * refer to #fetchGraph(String)
|
---|
| 105 | *
|
---|
| 106 | * @throws NullPointerException
|
---|
| 107 | * if rootFrameName is null.
|
---|
| 108 | *
|
---|
| 109 | * @throws IllegalArgumentException
|
---|
| 110 | * If rootFrameName is not a valid framename.
|
---|
| 111 | *
|
---|
| 112 | * @throws InterruptedException
|
---|
| 113 | * refer to #fetchGraph(String)
|
---|
| 114 | *
|
---|
| 115 | * @throws TrackGraphLoopException
|
---|
| 116 | * refer to #fetchGraph(String) -
|
---|
| 117 | * <B>However</B> - it could also be that the reason was due to the thread being interupted while
|
---|
| 118 | * waiting on a update to finish. Thus may want to manually wait for updates prior - see
|
---|
| 119 | * {@link #waitOnUpdates()}.
|
---|
| 120 | *
|
---|
| 121 | * @see #fetchGraph(String)
|
---|
| 122 | */
|
---|
| 123 | public synchronized OverdubbedFrame fetchLatestGraph(String rootFrameName)
|
---|
| 124 | throws InterruptedException, TrackGraphLoopException {
|
---|
| 125 | waitOnUpdates();
|
---|
| 126 | return fetchGraph(rootFrameName);
|
---|
| 127 | }
|
---|
| 128 |
|
---|
| 129 | /**
|
---|
| 130 | * May have to read from file if not yet loaded in memory.
|
---|
| 131 | * Thus it could take some time.
|
---|
| 132 | *
|
---|
| 133 | * Thread safe.
|
---|
| 134 | *
|
---|
| 135 | * NOTE: The intention is that this is called on a dedicated thread... other than
|
---|
| 136 | * swings thread. However once a OverdubbedFrame is returned be sure
|
---|
| 137 | * to only use it on the swing thread by convention.
|
---|
| 138 | *
|
---|
| 139 | * <b>MUST NOT NE IN THE EXPEDITEE THREAD! OTHERWISE WILL DEFINITELY DEADLOCK</b>
|
---|
| 140 | *
|
---|
| 141 | * @param rootFrameName
|
---|
| 142 | * Must not be null. Mustn't be a link - must be the <i>framename</i>
|
---|
| 143 | *
|
---|
| 144 | * @return
|
---|
| 145 | * The overdubbed frame.
|
---|
| 146 | * Null if the frame does not exist - or if it does exist but there
|
---|
| 147 | * are no track / linked-track widgets on it. Not that it can return
|
---|
| 148 | * a overdubbed frame that contains a heirarchy of linked overdubbed frames
|
---|
| 149 | * but no actual playable tracks.
|
---|
| 150 | *
|
---|
| 151 | * @throws NullPointerException
|
---|
| 152 | * if rootFrameName is null.
|
---|
| 153 | *
|
---|
| 154 | * @throws IllegalArgumentException
|
---|
| 155 | * If rootFrameName is not a valid framename.
|
---|
| 156 | *
|
---|
| 157 | * @throws InterruptedException
|
---|
| 158 | * If the fetch request was cancelled because the model
|
---|
| 159 | * has changed in some way during the read. Must retry the fetch.
|
---|
| 160 | *
|
---|
| 161 | * @throws TrackGraphLoopException
|
---|
| 162 | * If the requested root introduces loops to the current graph state.
|
---|
| 163 | * The loop trace will be provided in the exception.
|
---|
| 164 | *
|
---|
| 165 | * @see TrackGraphLoopException#getFullLoopTrace()
|
---|
| 166 | *
|
---|
| 167 | */
|
---|
| 168 | public synchronized OverdubbedFrame fetchGraph(String rootFrameName)
|
---|
| 169 | throws InterruptedException, TrackGraphLoopException {
|
---|
| 170 |
|
---|
| 171 | if(rootFrameName == null) throw new NullPointerException("rootFrameName");
|
---|
| 172 | if(!FrameIO.isValidFrameName(rootFrameName))
|
---|
| 173 | throw new IllegalArgumentException("the rootFrameName \""
|
---|
| 174 | + rootFrameName +"\" is not a valid framename");
|
---|
| 175 |
|
---|
| 176 | // reset flag
|
---|
| 177 | cancelFetch = false; // note that race conditions here is beside the point...meaningless
|
---|
| 178 |
|
---|
| 179 | OverdubbedFrame rootODFrame = null;
|
---|
| 180 | synchronized(sharedResourceLocker) {
|
---|
| 181 | rootODFrame = allOverdubbedFrames.get(rootFrameName.toLowerCase());
|
---|
| 182 | }
|
---|
| 183 |
|
---|
| 184 | if (rootODFrame != null) {
|
---|
| 185 | return rootODFrame;
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | // There is no overdub frame loaded for the requested frame.
|
---|
| 189 | // Thus create a new root
|
---|
| 190 | Map<String, OverdubbedFrame> newGraph = new HashMap<String, OverdubbedFrame> ();
|
---|
| 191 | rootODFrame = buildGraph(rootFrameName, newGraph); // throws InterruptedException's
|
---|
| 192 |
|
---|
| 193 | // There exists no such frame or is not an actual overdubbed frame
|
---|
| 194 | if (rootODFrame == null || rootODFrame.isEmpty()) return null;
|
---|
| 195 |
|
---|
| 196 | // Must run on swing thread for checking for loops before commiting the new graph to
|
---|
| 197 | // the model.
|
---|
| 198 | NewGraphCommitor commit = new NewGraphCommitor(rootODFrame, newGraph);
|
---|
| 199 | try {
|
---|
| 200 | SwingUtilities.invokeAndWait(commit);
|
---|
| 201 | } catch (InvocationTargetException e) {
|
---|
| 202 | e.printStackTrace();
|
---|
| 203 | assert(false);
|
---|
| 204 | }
|
---|
| 205 |
|
---|
| 206 | // Check if commit aborted
|
---|
| 207 | if (commit.abortedCommit) {
|
---|
| 208 |
|
---|
| 209 | if (cancelFetch) { // Due to cancel request?
|
---|
| 210 | throw new InterruptedException();
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | // Must be due to loop
|
---|
| 214 | assert (!commit.loopTrace.isEmpty());
|
---|
| 215 | throw new TrackGraphLoopException(commit.loopTrace);
|
---|
| 216 | }
|
---|
| 217 |
|
---|
| 218 | return rootODFrame;
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 | /**
|
---|
| 222 | * Assumption: that this is called from another thread other than the swing thread.
|
---|
| 223 | *
|
---|
| 224 | * @param rootFrameName
|
---|
| 225 | *
|
---|
| 226 | * @param newNodes
|
---|
| 227 | *
|
---|
| 228 | * @return
|
---|
| 229 | * Null if rootFrameName does not exist
|
---|
| 230 | *
|
---|
| 231 | * @throws InterruptedException
|
---|
| 232 | */
|
---|
| 233 | private OverdubbedFrame buildGraph(String rootFrameName, Map<String, OverdubbedFrame> newNodes)
|
---|
| 234 | throws InterruptedException
|
---|
| 235 | {
|
---|
| 236 | // Must be a frame name, not a link
|
---|
| 237 | assert(rootFrameName != null);
|
---|
| 238 | assert(FrameIO.isValidFrameName(rootFrameName));
|
---|
| 239 |
|
---|
| 240 | // If cancelled the immediatly abort fetch
|
---|
| 241 | if (cancelFetch) { // check for cancel request
|
---|
| 242 | throw new InterruptedException();
|
---|
| 243 | }
|
---|
| 244 |
|
---|
| 245 | // Look for existing node on previously loaded graph...
|
---|
| 246 | OverdubbedFrame oframe = null;
|
---|
| 247 | synchronized(sharedResourceLocker) {
|
---|
| 248 | oframe = allOverdubbedFrames.get(rootFrameName.toLowerCase());
|
---|
| 249 | }
|
---|
| 250 |
|
---|
| 251 | // Check to see if not already created this node during this recursive call
|
---|
| 252 | if (oframe == null)
|
---|
| 253 | oframe = newNodes.get(rootFrameName.toLowerCase());
|
---|
| 254 |
|
---|
| 255 | if (oframe != null) return oframe;
|
---|
| 256 |
|
---|
| 257 | // There are no existing nodes ... thus create a new node from searching for
|
---|
| 258 | // the frame from file
|
---|
| 259 |
|
---|
| 260 | // But first .. look in expeditee's cache since the cache might not be consistent with the file system yet
|
---|
| 261 | // .. or in the common case - the root could be the current frame.
|
---|
| 262 | ExpediteeCachedTrackInfoFetcher cacheFetch = new ExpediteeCachedTrackInfoFetcher(rootFrameName);
|
---|
| 263 |
|
---|
| 264 | try {
|
---|
| 265 | SwingUtilities.invokeAndWait(cacheFetch);
|
---|
| 266 | } catch (InvocationTargetException e) {
|
---|
| 267 | e.printStackTrace();
|
---|
| 268 | assert(false);
|
---|
| 269 | }
|
---|
| 270 |
|
---|
| 271 | /* Localfilename -> TrackModelData. */
|
---|
| 272 | Map<String, TrackModelData> tracks = null;
|
---|
| 273 |
|
---|
| 274 | /* VirtualFilename -> LinkedTrackModelData. */
|
---|
| 275 | Map<String, LinkedTrackModelData> linkedTracks = null;
|
---|
| 276 |
|
---|
| 277 | if (cacheFetch.tracks != null) {
|
---|
| 278 |
|
---|
| 279 | tracks = cacheFetch.tracks;
|
---|
| 280 | assert(cacheFetch.linkedTracks != null);
|
---|
| 281 | linkedTracks = cacheFetch.linkedTracks;
|
---|
| 282 |
|
---|
| 283 | } else { // search on file system
|
---|
| 284 |
|
---|
| 285 | String trackPrefix = ItemUtils.GetTag(ItemUtils.TAG_IWIDGET) + ": ";
|
---|
| 286 | String linkedTrackPrefix = trackPrefix;
|
---|
| 287 |
|
---|
[318] | 288 | trackPrefix += SampledTrack.class.getName();
|
---|
[315] | 289 | linkedTrackPrefix += LinkedTrack.class.getName();
|
---|
| 290 |
|
---|
| 291 | String fullPath = null;
|
---|
| 292 | for (int i = 0; i < UserSettings.FrameDirs.size(); i++) { // RISKY CODE - IN EXPEDITEE SPACE FROM RANDOM TRHEAD
|
---|
| 293 | String possiblePath = UserSettings.FrameDirs.get(i);
|
---|
| 294 | fullPath = FrameIO.getFrameFullPathName(possiblePath, rootFrameName);
|
---|
| 295 | if (fullPath != null)
|
---|
| 296 | break;
|
---|
| 297 | }
|
---|
| 298 |
|
---|
| 299 | // does even exist?
|
---|
| 300 | if (fullPath == null) {
|
---|
| 301 | return null;
|
---|
| 302 | }
|
---|
| 303 |
|
---|
| 304 | try {
|
---|
| 305 |
|
---|
| 306 | // Perform prefix search
|
---|
| 307 | List<TextItemSearchResult> results = ExpediteeFileTextSearch.prefixSearch(
|
---|
| 308 | fullPath,
|
---|
| 309 | new String[] {trackPrefix, linkedTrackPrefix});
|
---|
| 310 |
|
---|
| 311 | // Parse search results
|
---|
| 312 | tracks = new HashMap<String, TrackModelData>();
|
---|
| 313 | linkedTracks = new HashMap<String, LinkedTrackModelData>();
|
---|
| 314 |
|
---|
| 315 | for (TextItemSearchResult result : results) {
|
---|
| 316 |
|
---|
| 317 | // Track widget
|
---|
| 318 | if (result.text.startsWith(trackPrefix)) {
|
---|
| 319 |
|
---|
| 320 | String name = null;
|
---|
| 321 | String localFileName = null;
|
---|
[363] | 322 | Mutable.Long initiationTime = null;
|
---|
[315] | 323 |
|
---|
| 324 | for (String data : result.data) { // read data lines
|
---|
| 325 | data = data.trim();
|
---|
| 326 |
|
---|
[318] | 327 | if (data.startsWith(SampledTrack.META_LOCALNAME_TAG) &&
|
---|
| 328 | data.length() > SampledTrack.META_LOCALNAME_TAG.length()) {
|
---|
| 329 | localFileName = data.substring(SampledTrack.META_LOCALNAME_TAG.length());
|
---|
[315] | 330 |
|
---|
| 331 | } else if (data.startsWith(TrackWidgetCommons.META_INITIATIONTIME_TAG)
|
---|
| 332 | && data.length() > TrackWidgetCommons.META_INITIATIONTIME_TAG.length()) {
|
---|
| 333 |
|
---|
| 334 | try {
|
---|
[363] | 335 | initiationTime = Mutable.createMutableLong(Long.parseLong(
|
---|
[315] | 336 | data.substring(TrackWidgetCommons.META_INITIATIONTIME_TAG.length())));
|
---|
| 337 | } catch (NumberFormatException e) { /* Consume */ }
|
---|
| 338 |
|
---|
| 339 | } else if (data.startsWith(TrackWidgetCommons.META_NAME_TAG)
|
---|
| 340 | && data.length() > TrackWidgetCommons.META_NAME_TAG.length()) {
|
---|
| 341 |
|
---|
| 342 | name = data.substring(TrackWidgetCommons.META_NAME_TAG.length());
|
---|
| 343 |
|
---|
| 344 | }
|
---|
| 345 | }
|
---|
| 346 |
|
---|
| 347 | // Add track to map
|
---|
| 348 | if (localFileName != null) {
|
---|
| 349 | tracks.put(localFileName, new TrackModelData(
|
---|
[349] | 350 | initiationTime, -1, name, result.position.y)); // pass -1 for running time to signify that must be read from audio file
|
---|
[315] | 351 | }
|
---|
| 352 |
|
---|
| 353 | // Linked track widget
|
---|
| 354 | } else {
|
---|
| 355 |
|
---|
| 356 | assert(result.text.startsWith(linkedTrackPrefix));
|
---|
| 357 |
|
---|
| 358 | // If the linked track infact has a link
|
---|
| 359 | if (result.explink != null && result.explink.length() > 0) {
|
---|
| 360 |
|
---|
[363] | 361 | Mutable.Long initiationTime = null;
|
---|
[315] | 362 | String virtualFilename = null;
|
---|
| 363 | String name = null;
|
---|
| 364 |
|
---|
| 365 | for (String data : result.data) { // read data lines
|
---|
| 366 | data = data.trim();
|
---|
| 367 |
|
---|
| 368 | // OK OK, Smell in code here - duplicated from above. - sorta
|
---|
| 369 | if (data.startsWith(LinkedTrack.META_VIRTUALNAME_TAG) &&
|
---|
| 370 | data.length() > LinkedTrack.META_VIRTUALNAME_TAG.length()) {
|
---|
| 371 | virtualFilename = data.substring(LinkedTrack.META_VIRTUALNAME_TAG.length());
|
---|
| 372 |
|
---|
| 373 | } else if (data.startsWith(TrackWidgetCommons.META_INITIATIONTIME_TAG)
|
---|
| 374 | && data.length() > TrackWidgetCommons.META_INITIATIONTIME_TAG.length()) {
|
---|
| 375 |
|
---|
| 376 | try {
|
---|
[363] | 377 | initiationTime = Mutable.createMutableLong(Long.parseLong(
|
---|
[315] | 378 | data.substring(TrackWidgetCommons.META_INITIATIONTIME_TAG.length())));
|
---|
| 379 | } catch (NumberFormatException e) { /* Consume */ }
|
---|
| 380 |
|
---|
| 381 | } else if (data.startsWith(TrackWidgetCommons.META_NAME_TAG)
|
---|
| 382 | && data.length() > TrackWidgetCommons.META_NAME_TAG.length()) {
|
---|
| 383 |
|
---|
| 384 | name = data.substring(TrackWidgetCommons.META_NAME_TAG.length());
|
---|
| 385 |
|
---|
| 386 | }
|
---|
| 387 | }
|
---|
| 388 |
|
---|
| 389 | // Add linked track to map
|
---|
| 390 | if (virtualFilename != null) {
|
---|
| 391 | linkedTracks.put(virtualFilename, new LinkedTrackModelData(
|
---|
[349] | 392 | initiationTime, result.explink, name, result.position.y)); // pass -1 for running time to signify that must be read from audio file
|
---|
[315] | 393 | }
|
---|
| 394 |
|
---|
| 395 | }
|
---|
| 396 |
|
---|
| 397 |
|
---|
| 398 | }
|
---|
| 399 |
|
---|
| 400 | } // Proccess next result
|
---|
| 401 |
|
---|
| 402 | } catch (FileNotFoundException e) {
|
---|
| 403 | e.printStackTrace();
|
---|
| 404 | return null;
|
---|
| 405 | } catch (IOException e) {
|
---|
| 406 | e.printStackTrace();
|
---|
| 407 | return null;
|
---|
| 408 | }
|
---|
| 409 |
|
---|
| 410 | }
|
---|
| 411 |
|
---|
| 412 | if (cancelFetch) { // check for cancel request
|
---|
| 413 | throw new InterruptedException();
|
---|
| 414 | }
|
---|
| 415 |
|
---|
| 416 | assert(tracks != null);
|
---|
| 417 | assert(linkedTracks != null);
|
---|
| 418 |
|
---|
| 419 | // Add new node (avoid infite recursion)
|
---|
| 420 | oframe = new OverdubbedFrame(rootFrameName);
|
---|
| 421 | newNodes.put(rootFrameName.toLowerCase(), oframe);
|
---|
| 422 |
|
---|
| 423 | // Load track times from file
|
---|
| 424 | for (String localFilename : tracks.keySet()) {
|
---|
| 425 |
|
---|
| 426 | if (cancelFetch) { // check for cancel request
|
---|
| 427 | throw new InterruptedException();
|
---|
| 428 | }
|
---|
| 429 |
|
---|
| 430 | TrackModelData tmodel = tracks.get(localFilename);
|
---|
| 431 | if (tmodel.runningTimeMS <= 0) {
|
---|
| 432 | try {
|
---|
| 433 |
|
---|
| 434 | tmodel.runningTimeMS = AudioIO.getRunningTime(AudioPathManager.AUDIO_HOME_DIRECTORY + localFilename);
|
---|
| 435 |
|
---|
| 436 | } catch (IOException e) {
|
---|
| 437 | e.printStackTrace();
|
---|
| 438 | } catch (UnsupportedAudioFileException e) {
|
---|
| 439 | e.printStackTrace();
|
---|
| 440 | }
|
---|
| 441 | }
|
---|
| 442 |
|
---|
| 443 | // If was able to get the runnning time ... then include in model
|
---|
| 444 | if (tmodel.runningTimeMS > 0) {
|
---|
| 445 |
|
---|
| 446 | if (tmodel.initiationTime == null) {
|
---|
[363] | 447 | tmodel.initiationTime = Mutable.createMutableLong(oframe.getFirstInitiationTime());
|
---|
[315] | 448 | // Remember: initiation times are relative, so setting to zero
|
---|
| 449 | // could be obscure.
|
---|
| 450 | }
|
---|
| 451 |
|
---|
| 452 | oframe.addTrack(new TrackGraphNode(
|
---|
[363] | 453 | tmodel.initiationTime.value,
|
---|
[315] | 454 | localFilename,
|
---|
| 455 | tmodel.runningTimeMS,
|
---|
[349] | 456 | tmodel.name,
|
---|
| 457 | tmodel.ypos));
|
---|
[315] | 458 |
|
---|
| 459 | } // otheriwse omit from model.
|
---|
| 460 |
|
---|
| 461 | }
|
---|
| 462 |
|
---|
| 463 | // Get linked OverdubbedFrame's.. recurse
|
---|
| 464 | for (String virtualFilename : linkedTracks.keySet()) {
|
---|
| 465 |
|
---|
| 466 | LinkedTrackModelData linkedTrackModel = linkedTracks.get(virtualFilename);
|
---|
| 467 |
|
---|
| 468 | // At this point, the link may / maynot be absolute,
|
---|
| 469 | String linkedFrameName = linkedTrackModel.frameLink;
|
---|
| 470 | assert(linkedFrameName != null);
|
---|
| 471 |
|
---|
| 472 | if (linkedFrameName.length() == 0) continue;
|
---|
| 473 |
|
---|
| 474 | if (!(Character.isLetter(linkedFrameName.charAt(0)))) { // relative link
|
---|
| 475 | // Heres a trick: to get the frameset of the relative link - its just the
|
---|
| 476 | // current root framenames frameset
|
---|
| 477 | linkedFrameName = Conversion.getFramesetName(rootFrameName) + linkedFrameName; // WARNING: IN EXPEDITEE THREAD
|
---|
| 478 | }
|
---|
| 479 |
|
---|
| 480 | OverdubbedFrame od = buildGraph(linkedFrameName, newNodes); // Recurse
|
---|
| 481 |
|
---|
| 482 | if (od == null) { // bad link or empty frame
|
---|
| 483 | od = new OverdubbedFrame(linkedFrameName);
|
---|
| 484 | }
|
---|
| 485 |
|
---|
| 486 | if (linkedTrackModel.initiationTime == null) {
|
---|
[363] | 487 | linkedTrackModel.initiationTime = Mutable.createMutableLong(oframe.getFirstInitiationTime());
|
---|
[315] | 488 | // Remember: initiation times are relative, so setting to zero
|
---|
| 489 | // could be obscure.
|
---|
| 490 | }
|
---|
| 491 |
|
---|
| 492 | oframe.addLinkedTrack(new LinkedTracksGraphNode(
|
---|
[363] | 493 | linkedTrackModel.initiationTime.value,
|
---|
[315] | 494 | od,
|
---|
| 495 | virtualFilename,
|
---|
[349] | 496 | linkedTrackModel.name,
|
---|
| 497 | linkedTrackModel.ypos));
|
---|
[315] | 498 |
|
---|
| 499 | }
|
---|
| 500 |
|
---|
| 501 | return oframe;
|
---|
| 502 | }
|
---|
| 503 |
|
---|
| 504 | /**
|
---|
| 505 | *
|
---|
| 506 | * @author Brook Novak
|
---|
| 507 | *
|
---|
| 508 | */
|
---|
| 509 | private class TrackModelData {
|
---|
| 510 |
|
---|
[363] | 511 | TrackModelData(Mutable.Long initiationTime, long runningTimeMS, String name, int ypos) {
|
---|
[315] | 512 | this.initiationTime = initiationTime;
|
---|
| 513 | this.runningTimeMS = runningTimeMS;
|
---|
| 514 | this.name = name;
|
---|
[349] | 515 | this.ypos = ypos;
|
---|
[315] | 516 | }
|
---|
| 517 |
|
---|
| 518 | /** Null if unavilable */
|
---|
[363] | 519 | Mutable.Long initiationTime;
|
---|
[315] | 520 |
|
---|
| 521 | /** negative if unavailable */
|
---|
| 522 | long runningTimeMS;
|
---|
| 523 |
|
---|
| 524 | /** Can be null. Trackname */
|
---|
| 525 | String name;
|
---|
[349] | 526 |
|
---|
| 527 | int ypos;
|
---|
[315] | 528 | }
|
---|
| 529 |
|
---|
| 530 | /**
|
---|
| 531 | *
|
---|
| 532 | * @author Brook Novak
|
---|
| 533 | *
|
---|
| 534 | */
|
---|
| 535 | private class LinkedTrackModelData {
|
---|
| 536 |
|
---|
[363] | 537 | LinkedTrackModelData(Mutable.Long initiationTime, String frameLink, String name, int ypos) {
|
---|
[315] | 538 | assert(frameLink != null);
|
---|
| 539 | this.initiationTime = initiationTime;
|
---|
| 540 | this.frameLink = frameLink;
|
---|
| 541 | this.name = name;
|
---|
[349] | 542 | this.ypos = ypos;
|
---|
[315] | 543 | }
|
---|
| 544 | /** Null if unavilable */
|
---|
[363] | 545 | Mutable.Long initiationTime;
|
---|
[315] | 546 |
|
---|
| 547 | /** The framename */
|
---|
| 548 | String frameLink;
|
---|
| 549 |
|
---|
| 550 | /** Can be null. Trackname */
|
---|
| 551 | String name;
|
---|
[349] | 552 |
|
---|
| 553 | /** The y pixel position */
|
---|
| 554 | int ypos;
|
---|
[315] | 555 | }
|
---|
| 556 |
|
---|
| 557 | /**
|
---|
| 558 | * Used for fetching track info in expeditees cache
|
---|
| 559 | *
|
---|
| 560 | * @author Brook Novak
|
---|
| 561 | *
|
---|
| 562 | */
|
---|
| 563 | private class ExpediteeCachedTrackInfoFetcher implements Runnable
|
---|
| 564 | {
|
---|
| 565 | private String rootFrameName;
|
---|
| 566 |
|
---|
| 567 | /** Localfilename -> TrackModelData. Not null if frame was in memory. */
|
---|
| 568 | Map<String, TrackModelData> tracks = null;
|
---|
| 569 |
|
---|
| 570 | /** VirtualFilename -> LinkedTrackModelData. Not null if frame was in memory. */
|
---|
| 571 | Map<String, LinkedTrackModelData> linkedTracks = null;
|
---|
| 572 |
|
---|
| 573 | ExpediteeCachedTrackInfoFetcher(String rootFrameName)
|
---|
| 574 | {
|
---|
| 575 | assert(rootFrameName != null);
|
---|
| 576 | this.rootFrameName = rootFrameName;
|
---|
| 577 | }
|
---|
| 578 |
|
---|
| 579 | public void run()
|
---|
| 580 | {
|
---|
| 581 | assert(rootFrameName != null);
|
---|
| 582 |
|
---|
| 583 | // Check if the current frame
|
---|
| 584 | Frame rootFrame = null;
|
---|
| 585 | if (DisplayIO.getCurrentFrame() != null && DisplayIO.getCurrentFrame().getName() != null
|
---|
| 586 | && DisplayIO.getCurrentFrame().getName().equals(rootFrameName)) {
|
---|
| 587 | rootFrame = DisplayIO.getCurrentFrame();
|
---|
| 588 | } else {
|
---|
| 589 | // Check if in cache
|
---|
| 590 | rootFrame = FrameIO.FrameFromCache(rootFrameName);
|
---|
| 591 | }
|
---|
| 592 |
|
---|
| 593 | // Frame exists in memory... rummage around for track meta data
|
---|
| 594 | if (rootFrame != null) {
|
---|
| 595 | tracks = new HashMap<String, TrackModelData>();
|
---|
| 596 | linkedTracks = new HashMap<String, LinkedTrackModelData>();
|
---|
| 597 |
|
---|
| 598 | for (InteractiveWidget iw : rootFrame.getInteractiveWidgets()) {
|
---|
| 599 |
|
---|
[318] | 600 | if (iw instanceof SampledTrack) {
|
---|
| 601 | SampledTrack sampledTrackWidget = (SampledTrack)iw;
|
---|
[315] | 602 |
|
---|
| 603 | TrackGraphNode tinf =
|
---|
| 604 | AudioStructureModel.getInstance().getTrackGraphInfo(
|
---|
| 605 | sampledTrackWidget.getLocalFileName(),
|
---|
| 606 | rootFrameName);
|
---|
| 607 |
|
---|
[363] | 608 | Mutable.Long initTime = (tinf != null) ? Mutable.createMutableLong(tinf.getInitiationTime()) :
|
---|
| 609 | sampledTrackWidget.getInitiationTimeFromMeta();
|
---|
[359] | 610 |
|
---|
[315] | 611 | long runningTime = sampledTrackWidget.getRunningMSTimeFromRawAudio();
|
---|
| 612 | if (runningTime <= 0) runningTime = -1; // signify to load from file
|
---|
| 613 |
|
---|
| 614 | tracks.put(sampledTrackWidget.getLocalFileName(),
|
---|
| 615 | new TrackModelData(
|
---|
| 616 | initTime,
|
---|
| 617 | runningTime,
|
---|
[349] | 618 | sampledTrackWidget.getName(),
|
---|
| 619 | sampledTrackWidget.getY()));
|
---|
[315] | 620 |
|
---|
| 621 | // NOTE: If the audio was sotred in a recovery file - then the widget would
|
---|
| 622 | // be deleted - thus no need to consider recovery files.
|
---|
| 623 |
|
---|
| 624 | } else if (iw instanceof LinkedTrack) {
|
---|
| 625 | LinkedTrack linkedTrackWidget = (LinkedTrack)iw;
|
---|
| 626 |
|
---|
| 627 | LinkedTracksGraphNode ltinf =
|
---|
| 628 | AudioStructureModel.getInstance().getLinkedTrackGraphInfo(
|
---|
| 629 | linkedTrackWidget.getVirtualFilename(),
|
---|
| 630 | rootFrameName);
|
---|
| 631 |
|
---|
[363] | 632 | Mutable.Long initTime = (ltinf != null) ? Mutable.createMutableLong(ltinf.getInitiationTime()) :
|
---|
| 633 | linkedTrackWidget.getInitiationTimeFromMeta();
|
---|
| 634 |
|
---|
[315] | 635 | // Don't consider track-links without any links
|
---|
| 636 | if (linkedTrackWidget.getLink() != null) {
|
---|
| 637 | linkedTracks.put(linkedTrackWidget.getVirtualFilename(),
|
---|
| 638 | new LinkedTrackModelData(
|
---|
| 639 | initTime,
|
---|
| 640 | linkedTrackWidget.getLink(),
|
---|
[349] | 641 | linkedTrackWidget.getName(),
|
---|
| 642 | linkedTrackWidget.getY()));
|
---|
[315] | 643 | }
|
---|
| 644 |
|
---|
| 645 | }
|
---|
| 646 |
|
---|
| 647 | }
|
---|
| 648 |
|
---|
| 649 | }
|
---|
| 650 | }
|
---|
| 651 | }
|
---|
| 652 |
|
---|
| 653 |
|
---|
| 654 | /**
|
---|
| 655 | * Used for commiting a new graph in a thread-safe way.
|
---|
| 656 | * Checks for loops in the graph before commiting.
|
---|
| 657 | *
|
---|
| 658 | * If did not commit, then {@link #abortedCommit} will be true. Otherwise it will be false.
|
---|
| 659 | *
|
---|
| 660 | * if {@link #loopTrace} is not empty after the run, then the commit
|
---|
| 661 | * was aborted because there was a loop. (And {@link #loopTrace} contains the trace).
|
---|
| 662 | * The only other reason for aborting the commit was if a cancel was requested
|
---|
| 663 | * (@see TrackGraphModel#cancelFetch)
|
---|
| 664 | *
|
---|
| 665 | * @author Brook Novak
|
---|
| 666 | *
|
---|
| 667 | *
|
---|
| 668 | */
|
---|
| 669 | private class NewGraphCommitor implements Runnable
|
---|
| 670 | {
|
---|
| 671 | private final OverdubbedFrame rootODFrame;
|
---|
| 672 | private final Map<String, OverdubbedFrame> newGraph;
|
---|
| 673 |
|
---|
| 674 | private boolean abortedCommit = false;
|
---|
| 675 |
|
---|
| 676 | /** If not empty after the run, then the commit */
|
---|
| 677 | Stack<OverdubbedFrame> loopTrace = new Stack<OverdubbedFrame>();
|
---|
| 678 |
|
---|
| 679 | NewGraphCommitor(OverdubbedFrame rootODFrame, Map<String, OverdubbedFrame> newGraph)
|
---|
| 680 | {
|
---|
| 681 | assert(rootODFrame != null);
|
---|
| 682 | assert(newGraph != null);
|
---|
| 683 |
|
---|
| 684 | this.rootODFrame = rootODFrame;
|
---|
| 685 | this.newGraph = newGraph;
|
---|
| 686 | }
|
---|
| 687 |
|
---|
| 688 | public void run()
|
---|
| 689 | {
|
---|
| 690 |
|
---|
| 691 | // Theoretically this would never need to be called ... but for super safety
|
---|
| 692 | // keep sync block - since dealing with shared resources.
|
---|
| 693 | synchronized(sharedResourceLocker) {
|
---|
| 694 |
|
---|
| 695 | // At anytime during this point, the new graph could become invalid
|
---|
| 696 | // during this time while officially adding it to the "consistant" model..
|
---|
| 697 | // which is obviously BAD.
|
---|
| 698 | if (cancelFetch) {
|
---|
| 699 | abortedCommit = true;
|
---|
| 700 | return; // important: while locked shared resources
|
---|
| 701 | }
|
---|
| 702 |
|
---|
| 703 | // Check that the graph is loop free.
|
---|
| 704 | boolean ilf = isLoopFree(rootODFrame, loopTrace);
|
---|
| 705 | if (ilf) { // check from existing roots POV's
|
---|
| 706 | for (OverdubbedFrame existingRoot : graphRoots) {
|
---|
| 707 | loopTrace.clear();
|
---|
| 708 | ilf = isLoopFree(existingRoot, loopTrace);
|
---|
| 709 | if (!ilf) break;
|
---|
| 710 | }
|
---|
| 711 | }
|
---|
| 712 |
|
---|
| 713 | if (!ilf) {
|
---|
| 714 | assert (!loopTrace.isEmpty()); // loopTrace will contain the trace
|
---|
| 715 | abortedCommit = true;
|
---|
| 716 | return;
|
---|
| 717 | } else {
|
---|
| 718 | assert (loopTrace.isEmpty());
|
---|
| 719 | }
|
---|
| 720 |
|
---|
| 721 | // Since we are creating a new root, existing roots might
|
---|
| 722 | // be reachable from the new root, thus remove reachable
|
---|
| 723 | // roots ... since they are no longer roots ...
|
---|
| 724 | List<OverdubbedFrame> redundantRoots = new LinkedList<OverdubbedFrame>();
|
---|
| 725 | for (OverdubbedFrame existingRoot : graphRoots) {
|
---|
| 726 | if (rootODFrame.getChild(existingRoot.getFrameName()) != null) {
|
---|
| 727 | redundantRoots.add(existingRoot); // this root is reachable from the new root
|
---|
| 728 | }
|
---|
| 729 | }
|
---|
| 730 |
|
---|
| 731 | // Get rid of the redundant roots
|
---|
| 732 | graphRoots.removeAll(redundantRoots);
|
---|
| 733 |
|
---|
| 734 | // Commit the new loop-free graph to the model
|
---|
| 735 | graphRoots.add(rootODFrame); // add the mutex or superceeding root
|
---|
| 736 | allOverdubbedFrames.putAll(newGraph); // Include new graph into all allOverdubbedFrames
|
---|
| 737 | }
|
---|
| 738 | }
|
---|
| 739 | }
|
---|
| 740 | //
|
---|
| 741 | // /**
|
---|
| 742 | // * Determines if a node is reachable from a given node.
|
---|
| 743 | // *
|
---|
| 744 | // * @param source
|
---|
| 745 | // * Must not be null.
|
---|
| 746 | // *
|
---|
| 747 | // * @param target
|
---|
| 748 | // * Must not be null.
|
---|
| 749 | // *
|
---|
| 750 | // * @param visited
|
---|
| 751 | // * Must be empty. Must not be null.
|
---|
| 752 | // *
|
---|
| 753 | // * @return
|
---|
| 754 | // * True if target is reachable via source.
|
---|
| 755 | // */
|
---|
| 756 | // private boolean isReachable(OverdubbedFrame source, OverdubbedFrame target, Set<OverdubbedFrame> visited) {
|
---|
| 757 | // assert(source != null);
|
---|
| 758 | // assert(target != null);
|
---|
| 759 | // assert(visited != null);
|
---|
| 760 | //
|
---|
| 761 | // // Base cases
|
---|
| 762 | // if (source == target) return true;
|
---|
| 763 | // else if (visited.contains(source)) return false;
|
---|
| 764 | //
|
---|
| 765 | // // Remember visited node
|
---|
| 766 | // visited.add(source);
|
---|
| 767 | //
|
---|
| 768 | // // Recurse
|
---|
| 769 | // for (Iterator<LinkedTracksGraphInfo> itor = source.getLinkedTrackIterator(); itor.hasNext();) {
|
---|
| 770 | // LinkedTracksGraphInfo lti = itor.next();
|
---|
| 771 | //
|
---|
| 772 | // if (isReachable(lti.getLinkedFrame(), target, visited)) {
|
---|
| 773 | // return true;
|
---|
| 774 | // }
|
---|
| 775 | // }
|
---|
| 776 | //
|
---|
| 777 | // // Reached end of this nodes links - found no match
|
---|
| 778 | // return false;
|
---|
| 779 | // }
|
---|
| 780 |
|
---|
| 781 | /**
|
---|
| 782 | * Determines if a graph is loop free from a starting point.
|
---|
| 783 | * <B>MUST BE ON THE SWING THREAD</B>
|
---|
| 784 | *
|
---|
| 785 | * @param current
|
---|
| 786 | * Where to look for loops from.
|
---|
| 787 | *
|
---|
| 788 | * @param visitedStack
|
---|
| 789 | * Must be empty. Used internally. If the
|
---|
| 790 | * function returns false, then the stack will provde a trace of the loop.
|
---|
| 791 | *
|
---|
| 792 | * @return
|
---|
| 793 | * True if the graph at current is loop free.
|
---|
| 794 | */
|
---|
| 795 | private boolean isLoopFree(OverdubbedFrame current, Stack<OverdubbedFrame> visitedStack) {
|
---|
| 796 |
|
---|
| 797 | if (visitedStack.contains(current)) { // found a loop
|
---|
| 798 | visitedStack.add(current); // add to strack for loop trace.
|
---|
| 799 | return false;
|
---|
| 800 | }
|
---|
| 801 |
|
---|
| 802 | visitedStack.push(current); // save current node to stack
|
---|
| 803 |
|
---|
| 804 | for (LinkedTracksGraphNode tl : current.getLinkedTracksCopy()) {
|
---|
| 805 |
|
---|
| 806 | boolean ilf = isLoopFree(tl.getLinkedFrame(), visitedStack);
|
---|
| 807 |
|
---|
| 808 | if (!ilf) return false;
|
---|
| 809 |
|
---|
| 810 | }
|
---|
| 811 |
|
---|
| 812 | visitedStack.pop(); // pop the current node
|
---|
| 813 |
|
---|
| 814 | return true;
|
---|
| 815 |
|
---|
| 816 | }
|
---|
| 817 |
|
---|
| 818 | /**
|
---|
| 819 | * Gets a TrackGraphInfo. <b>MUST BE ON EXPEDITEE THREAD</b>
|
---|
| 820 | *
|
---|
| 821 | * @param localFilename
|
---|
| 822 | * Must not be null.
|
---|
| 823 | *
|
---|
| 824 | * @param parentFrameName
|
---|
| 825 | * If null - the search will be slower.
|
---|
| 826 | *
|
---|
| 827 | * @return
|
---|
| 828 | * The TrackGraphInfo for the given track. Null if not in model
|
---|
| 829 | */
|
---|
| 830 | public TrackGraphNode getTrackGraphInfo(String localFilename, String parentFrameName) {
|
---|
| 831 |
|
---|
| 832 | synchronized(sharedResourceLocker) {
|
---|
| 833 |
|
---|
| 834 | assert(localFilename != null);
|
---|
| 835 |
|
---|
| 836 | if (parentFrameName != null) {
|
---|
| 837 | OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 838 |
|
---|
| 839 |
|
---|
| 840 | if (odframe != null) {
|
---|
| 841 | return odframe.getTrack(localFilename);
|
---|
| 842 | }
|
---|
| 843 |
|
---|
| 844 | } else { // parentFrameName is null .. do search
|
---|
| 845 |
|
---|
| 846 | for (OverdubbedFrame odframe : allOverdubbedFrames.values()) {
|
---|
| 847 | TrackGraphNode tinf = odframe.getTrack(localFilename);
|
---|
| 848 | if (tinf != null) return tinf;
|
---|
| 849 | }
|
---|
| 850 | }
|
---|
| 851 | }
|
---|
| 852 |
|
---|
| 853 | return null;
|
---|
| 854 | }
|
---|
| 855 |
|
---|
| 856 |
|
---|
| 857 | /**
|
---|
| 858 | * Gets a TrackGraphInfo. <b>MUST BE ON EXPEDITEE THREAD</b>
|
---|
| 859 | *
|
---|
| 860 | * @param virtualFilename
|
---|
| 861 | * Must not be null.
|
---|
| 862 | *
|
---|
| 863 | * @param parentFrameName
|
---|
| 864 | * If null - the search will be slower.
|
---|
| 865 | *
|
---|
| 866 | * @return
|
---|
| 867 | * The LinkedTracksGraphInfo for the given virtualFilename. Null if not in model
|
---|
| 868 | */
|
---|
| 869 | public LinkedTracksGraphNode getLinkedTrackGraphInfo(String virtualFilename, String parentFrameName) {
|
---|
| 870 |
|
---|
| 871 | synchronized(sharedResourceLocker) {
|
---|
| 872 |
|
---|
| 873 | assert(virtualFilename != null);
|
---|
| 874 |
|
---|
| 875 | if (parentFrameName != null) {
|
---|
| 876 | OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 877 |
|
---|
| 878 |
|
---|
| 879 | if (odframe != null) {
|
---|
| 880 | return odframe.getLinkedTrack(virtualFilename);
|
---|
| 881 | }
|
---|
| 882 |
|
---|
| 883 | } else {
|
---|
| 884 |
|
---|
| 885 | for (OverdubbedFrame odframe : allOverdubbedFrames.values()) {
|
---|
| 886 | LinkedTracksGraphNode ltinf = odframe.getLinkedTrack(virtualFilename);
|
---|
| 887 | if (ltinf != null) return ltinf;
|
---|
| 888 | }
|
---|
| 889 |
|
---|
| 890 | }
|
---|
| 891 | }
|
---|
| 892 |
|
---|
| 893 | return null;
|
---|
| 894 | }
|
---|
| 895 |
|
---|
| 896 |
|
---|
| 897 | /**
|
---|
| 898 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 899 | * Gets the parent ODFrame of a track - given the tracks local filename.
|
---|
| 900 | *
|
---|
| 901 | * @param localFilename
|
---|
| 902 | * The local filename of the track to get the frame for. Must not be null.
|
---|
| 903 | *
|
---|
| 904 | * @return
|
---|
| 905 | * The parent. Null if no parent exists.
|
---|
| 906 | */
|
---|
| 907 | public OverdubbedFrame getParentOverdubbedFrame(String localFilename) {
|
---|
| 908 | assert(localFilename != null);
|
---|
| 909 |
|
---|
| 910 | synchronized (allOverdubbedFrames) {
|
---|
| 911 | for (OverdubbedFrame odf : allOverdubbedFrames.values()) {
|
---|
| 912 | if (odf.containsTrack(localFilename)) {
|
---|
| 913 | return odf;
|
---|
| 914 | }
|
---|
| 915 | }
|
---|
| 916 | }
|
---|
| 917 |
|
---|
| 918 | return null;
|
---|
| 919 | }
|
---|
| 920 |
|
---|
| 921 | /**
|
---|
| 922 | * Gets an overdubbed frame representation of a given frame.
|
---|
| 923 | *
|
---|
| 924 | * @param framename
|
---|
| 925 | * The name of the frame.
|
---|
| 926 | *
|
---|
| 927 | * @return
|
---|
| 928 | * The OverdubbedFrame for the given framename. Null if does not exist.
|
---|
| 929 | */
|
---|
| 930 | public OverdubbedFrame getOverdubbedFrame(String framename) {
|
---|
| 931 | assert(framename != null);
|
---|
| 932 |
|
---|
| 933 | synchronized (allOverdubbedFrames) {
|
---|
| 934 | return allOverdubbedFrames.get(framename.toLowerCase());
|
---|
| 935 | }
|
---|
| 936 | }
|
---|
| 937 |
|
---|
| 938 | /**
|
---|
| 939 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 940 | * Keeps model consistent with expeditee.
|
---|
| 941 | *
|
---|
| 942 | * @param localFilename
|
---|
| 943 | * Must not be null.
|
---|
| 944 | *
|
---|
| 945 | * @param parentFrameName
|
---|
| 946 | * Can be null.
|
---|
| 947 | *
|
---|
| 948 | * @param currentRunningTime
|
---|
| 949 | * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
|
---|
| 950 | */
|
---|
| 951 | public void onTrackWidgetAudioEdited(String localFilename, String parentFrameName, long currentRunningTime) {
|
---|
| 952 |
|
---|
| 953 | boolean doNotify = false;
|
---|
| 954 | TrackGraphNode tinf = null;
|
---|
| 955 |
|
---|
| 956 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 957 |
|
---|
| 958 | assert(localFilename != null);
|
---|
| 959 | assert (currentRunningTime > 0);
|
---|
| 960 |
|
---|
| 961 | // Locate parent frame
|
---|
| 962 | OverdubbedFrame odframe = null;
|
---|
| 963 |
|
---|
| 964 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 965 | else odframe = getParentOverdubbedFrame(localFilename);
|
---|
| 966 |
|
---|
| 967 | // adjust running time in model
|
---|
| 968 | if (odframe != null) { // is loaded?
|
---|
| 969 | tinf = odframe.getTrack(localFilename);
|
---|
| 970 | assert(tinf != null); // due to the assumption that the model is consistent
|
---|
| 971 | if (tinf.getRunningTime() != currentRunningTime) {
|
---|
| 972 | tinf.setRunningTime(currentRunningTime);
|
---|
| 973 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 974 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 975 | // the current locked object.
|
---|
| 976 | cancelFetch = doNotify = true;
|
---|
| 977 | }
|
---|
| 978 | }
|
---|
| 979 |
|
---|
| 980 |
|
---|
| 981 | }
|
---|
| 982 |
|
---|
| 983 | // Notify observers.
|
---|
| 984 | if (doNotify)
|
---|
| 985 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_EDITED, tinf));
|
---|
| 986 | }
|
---|
| 987 |
|
---|
| 988 | /**
|
---|
| 989 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 990 | * Keeps model consistent with expeditee.
|
---|
| 991 | *
|
---|
| 992 | * @param localFilename
|
---|
| 993 | * Must not be null.
|
---|
| 994 | *
|
---|
| 995 | * @param parentFrameName
|
---|
| 996 | * Can be null.
|
---|
| 997 | *
|
---|
| 998 | * @param newInitiationTime
|
---|
| 999 | * In milliseconds.Relative - i.e. can be negative
|
---|
| 1000 | *
|
---|
| 1001 | * @param name
|
---|
| 1002 | * The name given to the linked track. Can be null if there is no name...
|
---|
| 1003 | *
|
---|
[349] | 1004 | * @param ypos
|
---|
| 1005 | * The Y-pixel position of the track.
|
---|
| 1006 | *
|
---|
[315] | 1007 | * @param currentRunningTime
|
---|
| 1008 | * Must be larger than zero. In milliseconds. Used in case the model has not been create for the widget.
|
---|
| 1009 | */
|
---|
| 1010 | public void onTrackWidgetAnchored(
|
---|
| 1011 | String localFilename, String parentFrameName,
|
---|
| 1012 | long newInitiationTime, long currentRunningTime,
|
---|
[349] | 1013 | String name, int ypos) {
|
---|
[315] | 1014 |
|
---|
| 1015 | boolean doNotify = false;
|
---|
| 1016 | TrackGraphNode tinf = null;
|
---|
| 1017 |
|
---|
| 1018 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1019 |
|
---|
| 1020 | assert(localFilename != null);
|
---|
| 1021 |
|
---|
| 1022 | // Locate parent frame
|
---|
| 1023 | OverdubbedFrame odframe = null;
|
---|
| 1024 |
|
---|
| 1025 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1026 | else odframe = getParentOverdubbedFrame(localFilename);
|
---|
| 1027 |
|
---|
| 1028 | // adjust initiation time in model
|
---|
| 1029 | if (odframe != null) { // is loaded?
|
---|
| 1030 | tinf = odframe.getTrack(localFilename);
|
---|
| 1031 |
|
---|
| 1032 | if (tinf != null && tinf.getInitiationTime() != newInitiationTime) {
|
---|
| 1033 | tinf.setInitiationTime(newInitiationTime);
|
---|
| 1034 | cancelFetch = doNotify = true;
|
---|
| 1035 |
|
---|
| 1036 | } else { // if there is no model but overdub frame is in memory - then create new model for this track.
|
---|
| 1037 |
|
---|
| 1038 | tinf = new TrackGraphNode(
|
---|
| 1039 | newInitiationTime,
|
---|
| 1040 | localFilename,
|
---|
| 1041 | currentRunningTime,
|
---|
[349] | 1042 | name,
|
---|
| 1043 | ypos);
|
---|
[315] | 1044 |
|
---|
| 1045 | odframe.addTrack(tinf); // safe because on swing thread
|
---|
| 1046 |
|
---|
| 1047 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1048 | cancelFetch = doNotify = true;
|
---|
| 1049 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1050 | // the current locked object.
|
---|
| 1051 | }
|
---|
| 1052 |
|
---|
| 1053 |
|
---|
| 1054 | }
|
---|
| 1055 |
|
---|
| 1056 | }
|
---|
| 1057 |
|
---|
| 1058 | // Notify observers.
|
---|
| 1059 | if (doNotify)
|
---|
| 1060 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_ADDED, tinf));
|
---|
| 1061 |
|
---|
| 1062 | }
|
---|
| 1063 |
|
---|
| 1064 | /**
|
---|
| 1065 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1066 | * Keeps model consistent with expeditee.
|
---|
| 1067 | *
|
---|
| 1068 | * Note: Invoke when picked up - not when a frame changes... also if it
|
---|
| 1069 | * is removed due to XRaymode - in that case the whole model will be released.
|
---|
| 1070 | *
|
---|
| 1071 | * @param localFilename
|
---|
| 1072 | * Must not be null.
|
---|
| 1073 | *
|
---|
| 1074 | * @param parentFrameName
|
---|
| 1075 | * Can be null. If given will be faster.
|
---|
| 1076 | *
|
---|
| 1077 | */
|
---|
| 1078 | public void onTrackWidgetRemoved(String localFilename, String parentFrameName) {
|
---|
| 1079 | TrackGraphNode tinf = null;
|
---|
| 1080 | boolean doNotify = false;
|
---|
| 1081 |
|
---|
| 1082 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1083 |
|
---|
| 1084 | if (FrameGraphics.isXRayMode()) { // discard whole model
|
---|
| 1085 |
|
---|
| 1086 | // Neccessary because if the user goes into xray then moves to a new frame
|
---|
| 1087 | // then it wil screw everything up. Also note they can delete the text source items
|
---|
| 1088 | // in xray mode.
|
---|
| 1089 | // This basically will cause short load times to occur again...
|
---|
| 1090 | allOverdubbedFrames.clear();
|
---|
| 1091 | graphRoots.clear();
|
---|
| 1092 |
|
---|
| 1093 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1094 | cancelFetch = doNotify = true;
|
---|
| 1095 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1096 | // the current locked object.
|
---|
| 1097 |
|
---|
| 1098 | } else {
|
---|
| 1099 |
|
---|
| 1100 | // Locate parent frame
|
---|
| 1101 | OverdubbedFrame odframe = null;
|
---|
| 1102 |
|
---|
| 1103 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1104 | else odframe = getParentOverdubbedFrame(localFilename);
|
---|
| 1105 |
|
---|
| 1106 | if (odframe != null) {
|
---|
| 1107 | tinf = odframe.removeTrack(localFilename);
|
---|
[342] | 1108 | //assert(tinf != null);
|
---|
[315] | 1109 | cancelFetch = doNotify = true;
|
---|
| 1110 | }
|
---|
| 1111 |
|
---|
| 1112 | }
|
---|
| 1113 |
|
---|
| 1114 | }
|
---|
| 1115 |
|
---|
| 1116 | // Notify observers.
|
---|
| 1117 | if (doNotify)
|
---|
| 1118 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_REMOVED, tinf));
|
---|
| 1119 | }
|
---|
| 1120 |
|
---|
| 1121 | /**
|
---|
| 1122 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1123 | * Keeps model consistent with expeditee.
|
---|
| 1124 | *
|
---|
| 1125 | * @param localFilename
|
---|
| 1126 | * Must not be null.
|
---|
| 1127 | *
|
---|
| 1128 | * @param parentFrameName
|
---|
| 1129 | * Can be null.
|
---|
| 1130 | *
|
---|
| 1131 | * @param currentRunningTime
|
---|
| 1132 | * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
|
---|
| 1133 | */
|
---|
| 1134 | public void onTrackWidgetNameChanged(String localFilename, String parentFrameName, String newName) {
|
---|
| 1135 |
|
---|
| 1136 | boolean doNotify = false;
|
---|
| 1137 |
|
---|
| 1138 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1139 |
|
---|
| 1140 | assert(localFilename != null);
|
---|
| 1141 |
|
---|
| 1142 | // Locate parent frame
|
---|
| 1143 | OverdubbedFrame odframe = null;
|
---|
| 1144 |
|
---|
| 1145 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1146 | else odframe = getParentOverdubbedFrame(localFilename);
|
---|
| 1147 |
|
---|
| 1148 | // adjust name in model
|
---|
| 1149 | if (odframe != null) { // is loaded?
|
---|
| 1150 | AbstractTrackGraphNode tinf = odframe.getTrack(localFilename);
|
---|
| 1151 | assert(tinf != null); // due to the assumption that the model is consistent
|
---|
| 1152 | if (tinf.getName() != newName) {
|
---|
| 1153 | tinf.setName(newName);
|
---|
| 1154 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1155 | cancelFetch = doNotify = true;
|
---|
| 1156 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1157 | // the current locked object.
|
---|
| 1158 | }
|
---|
| 1159 | }
|
---|
| 1160 |
|
---|
| 1161 | }
|
---|
| 1162 |
|
---|
| 1163 | // Notify observers.
|
---|
| 1164 | if (doNotify)
|
---|
| 1165 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.NAME_CHANGED, localFilename));
|
---|
| 1166 | }
|
---|
| 1167 |
|
---|
[355] | 1168 |
|
---|
| 1169 | /**
|
---|
| 1170 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1171 | * Keeps model consistent with expeditee.
|
---|
| 1172 | *
|
---|
| 1173 | * @param localFilename
|
---|
| 1174 | * Must not be null.
|
---|
| 1175 | *
|
---|
| 1176 | * @param parentFrameName
|
---|
| 1177 | * Can be null.
|
---|
| 1178 | *
|
---|
| 1179 | * @param initTime
|
---|
| 1180 | * The initiation time in ms
|
---|
| 1181 | *
|
---|
| 1182 | * @param yPos
|
---|
| 1183 | * The y pixel position.
|
---|
| 1184 | *
|
---|
| 1185 | */
|
---|
| 1186 | public void onTrackWidgetPositionChanged(String localFilename, String parentFrameName, long initTime, int yPos) {
|
---|
[315] | 1187 |
|
---|
[355] | 1188 | boolean doNotify = false;
|
---|
[367] | 1189 | AbstractTrackGraphNode tinf = null;
|
---|
[355] | 1190 |
|
---|
| 1191 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1192 |
|
---|
| 1193 | assert(localFilename != null);
|
---|
| 1194 |
|
---|
| 1195 | // Locate parent frame
|
---|
| 1196 | OverdubbedFrame odframe = null;
|
---|
[367] | 1197 |
|
---|
[355] | 1198 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1199 | else odframe = getParentOverdubbedFrame(localFilename);
|
---|
| 1200 |
|
---|
| 1201 | // adjust name in model
|
---|
| 1202 | if (odframe != null) { // is loaded?
|
---|
[367] | 1203 | tinf = odframe.getTrack(localFilename);
|
---|
[355] | 1204 | if (tinf != null &&
|
---|
| 1205 | (tinf.getInitiationTime() != initTime || tinf.getYPixelPosition() != yPos)) {
|
---|
| 1206 | tinf.setInitiationTime(initTime);
|
---|
| 1207 | tinf.setYPixelPosition(yPos);
|
---|
| 1208 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1209 | cancelFetch = doNotify = true;
|
---|
| 1210 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1211 | // the current locked object.
|
---|
| 1212 | }
|
---|
| 1213 | }
|
---|
| 1214 |
|
---|
| 1215 | }
|
---|
| 1216 |
|
---|
| 1217 | // Notify observers.
|
---|
| 1218 | if (doNotify)
|
---|
[367] | 1219 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_POSITION_CHANGED, tinf));
|
---|
[355] | 1220 | }
|
---|
| 1221 |
|
---|
| 1222 |
|
---|
[315] | 1223 | /**
|
---|
| 1224 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1225 | * Keeps model consistent with expeditee.
|
---|
| 1226 | *
|
---|
| 1227 | * @param virtualFilename
|
---|
| 1228 | * Must not be null.
|
---|
| 1229 | *
|
---|
| 1230 | * @param parentFrameName
|
---|
| 1231 | * Can be null.
|
---|
| 1232 | *
|
---|
[355] | 1233 | * @param initTime
|
---|
| 1234 | * The initiation time in ms
|
---|
| 1235 | *
|
---|
| 1236 | * @param yPos
|
---|
| 1237 | * The y pixel position.
|
---|
| 1238 | *
|
---|
| 1239 | */
|
---|
| 1240 | public void onLinkedTrackWidgetPositionChanged(String virtualFilename, String parentFrameName, long initTime, int yPos) {
|
---|
| 1241 |
|
---|
| 1242 | boolean doNotify = false;
|
---|
[367] | 1243 | AbstractTrackGraphNode tinf = null;
|
---|
[355] | 1244 |
|
---|
| 1245 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1246 |
|
---|
| 1247 | assert(virtualFilename != null);
|
---|
| 1248 |
|
---|
| 1249 | // Locate parent frame
|
---|
| 1250 | OverdubbedFrame odframe = null;
|
---|
| 1251 |
|
---|
| 1252 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1253 | else odframe = getParentOverdubbedFrame(virtualFilename);
|
---|
| 1254 |
|
---|
| 1255 | // adjust name in model
|
---|
| 1256 | if (odframe != null) { // is loaded?
|
---|
[367] | 1257 | tinf = odframe.getLinkedTrack(virtualFilename);
|
---|
[355] | 1258 | if (tinf != null &&
|
---|
| 1259 | (tinf.getInitiationTime() != initTime || tinf.getYPixelPosition() != yPos)) {
|
---|
| 1260 |
|
---|
| 1261 | tinf.setInitiationTime(initTime);
|
---|
| 1262 | tinf.setYPixelPosition(yPos);
|
---|
| 1263 |
|
---|
| 1264 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1265 | cancelFetch = doNotify = true;
|
---|
| 1266 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1267 | // the current locked object.
|
---|
| 1268 | }
|
---|
| 1269 | }
|
---|
| 1270 | }
|
---|
| 1271 |
|
---|
| 1272 | // Notify observers.
|
---|
| 1273 | if (doNotify)
|
---|
[367] | 1274 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_POSITION_CHANGED, tinf));
|
---|
[355] | 1275 | }
|
---|
| 1276 |
|
---|
| 1277 | /**
|
---|
| 1278 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1279 | * Keeps model consistent with expeditee.
|
---|
| 1280 | *
|
---|
| 1281 | * @param virtualFilename
|
---|
| 1282 | * Must not be null.
|
---|
| 1283 | *
|
---|
| 1284 | * @param parentFrameName
|
---|
| 1285 | * Can be null.
|
---|
| 1286 | *
|
---|
[315] | 1287 | * @param currentRunningTime
|
---|
| 1288 | * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
|
---|
| 1289 | */
|
---|
| 1290 | public void onLinkedTrackWidgetNameChanged(String virtualFilename, String parentFrameName, String newName) {
|
---|
| 1291 |
|
---|
| 1292 | boolean doNotify = false;
|
---|
| 1293 |
|
---|
| 1294 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1295 |
|
---|
| 1296 | assert(virtualFilename != null);
|
---|
| 1297 |
|
---|
| 1298 | // Locate parent frame
|
---|
| 1299 | OverdubbedFrame odframe = null;
|
---|
| 1300 |
|
---|
| 1301 | if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1302 | else odframe = getParentOverdubbedFrame(virtualFilename);
|
---|
| 1303 |
|
---|
| 1304 | // adjust name in model
|
---|
| 1305 | if (odframe != null) { // is loaded?
|
---|
| 1306 | AbstractTrackGraphNode tinf = odframe.getLinkedTrack(virtualFilename);
|
---|
[352] | 1307 | if (tinf != null && tinf.getName() != newName) {
|
---|
[315] | 1308 | tinf.setName(newName);
|
---|
| 1309 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1310 | cancelFetch = doNotify = true;
|
---|
| 1311 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1312 | // the current locked object.
|
---|
| 1313 | }
|
---|
| 1314 | }
|
---|
| 1315 | }
|
---|
| 1316 |
|
---|
| 1317 | // Notify observers.
|
---|
| 1318 | if (doNotify)
|
---|
| 1319 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.NAME_CHANGED, virtualFilename));
|
---|
| 1320 | }
|
---|
| 1321 |
|
---|
| 1322 |
|
---|
| 1323 | /**
|
---|
| 1324 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1325 | * Keeps model consistent with expeditee.
|
---|
| 1326 | *
|
---|
| 1327 | * Doe not use for changing a the widgets link. Must first remove, then acnhor with new link -
|
---|
| 1328 | * tracklinks links are immutable.
|
---|
| 1329 | *
|
---|
| 1330 | * @param virtualFilename
|
---|
| 1331 | * Must not be null.
|
---|
| 1332 | *
|
---|
| 1333 | * @param parentFrameName
|
---|
| 1334 | * Must not be null.
|
---|
| 1335 | *
|
---|
| 1336 | * @param newInitiationTime
|
---|
| 1337 | * In milliseconds.Relative - i.e. can be negative
|
---|
| 1338 | *
|
---|
| 1339 | * @param absoluteLinkedFrame
|
---|
| 1340 | * Must not be null. Must be a valid framename (absolute).
|
---|
| 1341 | *
|
---|
| 1342 | * @param name
|
---|
| 1343 | * The name given to the linked track. Can be null if there is no name...
|
---|
| 1344 | *
|
---|
[349] | 1345 | * @param ypos
|
---|
| 1346 | * The Y-pixel position of the track.
|
---|
| 1347 | *
|
---|
[315] | 1348 | */
|
---|
| 1349 | public void onLinkedTrackWidgetAnchored(
|
---|
| 1350 | String virtualFilename, String parentFrameName,
|
---|
| 1351 | long newInitiationTime, String absoluteLinkedFrame,
|
---|
[349] | 1352 | String name, int ypos) {
|
---|
[315] | 1353 |
|
---|
| 1354 | boolean doNotify = false;
|
---|
| 1355 |
|
---|
| 1356 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1357 |
|
---|
| 1358 | assert(virtualFilename != null);
|
---|
| 1359 | assert(parentFrameName != null);
|
---|
| 1360 | assert(absoluteLinkedFrame != null);
|
---|
| 1361 | assert(FrameIO.isValidFrameName(absoluteLinkedFrame));
|
---|
| 1362 |
|
---|
| 1363 | // Locate parent frame
|
---|
| 1364 | OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1365 |
|
---|
| 1366 | if (odframe != null) { // is loaded?
|
---|
| 1367 |
|
---|
| 1368 | LinkedTracksGraphNode linkInf = odframe.getLinkedTrack(virtualFilename);
|
---|
| 1369 |
|
---|
| 1370 | if (linkInf != null) {
|
---|
| 1371 |
|
---|
| 1372 | // Update the initation time
|
---|
| 1373 | linkInf.setInitiationTime(newInitiationTime);
|
---|
| 1374 |
|
---|
| 1375 | // The link should be consistant - check for miss-use of procedure call
|
---|
| 1376 | assert(linkInf.getLinkedFrame().getFrameName().equalsIgnoreCase(absoluteLinkedFrame));
|
---|
| 1377 |
|
---|
| 1378 | } else { // if there is no model but overdub frame is in memory - then create new model for this track.
|
---|
| 1379 |
|
---|
| 1380 | updateLater(new LinkedTrackUpdate(
|
---|
| 1381 | newInitiationTime, absoluteLinkedFrame,
|
---|
[349] | 1382 | virtualFilename, parentFrameName, name, ypos));
|
---|
[315] | 1383 |
|
---|
| 1384 | }
|
---|
| 1385 |
|
---|
| 1386 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1387 | cancelFetch = doNotify = true;
|
---|
| 1388 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1389 | // the current locked object.
|
---|
| 1390 | }
|
---|
| 1391 |
|
---|
| 1392 | }
|
---|
| 1393 |
|
---|
| 1394 | // Notify observers.
|
---|
| 1395 | if (doNotify)
|
---|
| 1396 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_ADDED, virtualFilename));
|
---|
| 1397 |
|
---|
| 1398 | }
|
---|
| 1399 |
|
---|
| 1400 | /**
|
---|
| 1401 | * <B>MUST BE ON SWING THREAD</B>.
|
---|
| 1402 | * Keeps model consistent with expeditee.
|
---|
| 1403 | *
|
---|
| 1404 | * Note: Invoke when picked up - not when a frame changes... also if it
|
---|
| 1405 | * is removed due to XRaymode - in that case the whole model will be released.
|
---|
| 1406 | *
|
---|
| 1407 | * <b>Also</b> invoke if the track tracks link has changed.
|
---|
| 1408 | *
|
---|
| 1409 | * @param virtualFilename
|
---|
| 1410 | * Must not be null.
|
---|
| 1411 | *
|
---|
| 1412 | * @param parentFrameName
|
---|
| 1413 | * Can be null. If given will be faster.
|
---|
| 1414 | *
|
---|
| 1415 | */
|
---|
| 1416 | public void onLinkedTrackWidgetRemoved(String virtualFilename, String parentFrameName) {
|
---|
| 1417 |
|
---|
| 1418 | boolean doNotify = false;
|
---|
| 1419 |
|
---|
| 1420 | synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
|
---|
| 1421 |
|
---|
| 1422 |
|
---|
| 1423 | if (FrameGraphics.isXRayMode()) { // discard whole model
|
---|
| 1424 |
|
---|
| 1425 | // Neccessary because if the user goes into xray then moves to a new frame
|
---|
| 1426 | // then it wil screw everything up. Also note they can delete the text source items
|
---|
| 1427 | // in xray mode.
|
---|
| 1428 | // This basically will cause short load times to occur again...
|
---|
| 1429 | allOverdubbedFrames.clear();
|
---|
| 1430 | graphRoots.clear();
|
---|
| 1431 |
|
---|
| 1432 |
|
---|
| 1433 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1434 | cancelFetch = doNotify = true;
|
---|
| 1435 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1436 | // the current locked object.
|
---|
| 1437 | } else {
|
---|
| 1438 |
|
---|
| 1439 | // Locate parent frame
|
---|
| 1440 | OverdubbedFrame odframe = null;
|
---|
| 1441 |
|
---|
| 1442 | if (parentFrameName != null) {
|
---|
| 1443 | odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
|
---|
| 1444 | } else {
|
---|
| 1445 | for (OverdubbedFrame odf : allOverdubbedFrames.values()) {
|
---|
| 1446 | if (odf.containsLinkedTrack(virtualFilename)) {
|
---|
| 1447 | odframe = odf;
|
---|
| 1448 | break;
|
---|
| 1449 | }
|
---|
| 1450 | }
|
---|
| 1451 | }
|
---|
| 1452 |
|
---|
| 1453 | if (odframe != null) {
|
---|
| 1454 | LinkedTracksGraphNode linkInf = odframe.getLinkedTrack(virtualFilename);
|
---|
| 1455 |
|
---|
| 1456 | if (linkInf != null) {
|
---|
| 1457 | boolean didRemove = odframe.removeLinkedTrack(linkInf);
|
---|
| 1458 | assert(didRemove);
|
---|
| 1459 |
|
---|
| 1460 | // Track links are the actual links in the directed graph. THus if they are
|
---|
| 1461 | // removed then the graph must be checked for creating new root nodes.
|
---|
| 1462 | // That is, the removed link may have isolated a node (or group of nodes)
|
---|
| 1463 | // for which must be reachable via their own start state...
|
---|
| 1464 | boolean isReachable = false;
|
---|
| 1465 | for (OverdubbedFrame existingRoot : graphRoots) {
|
---|
| 1466 | if (existingRoot.getChild(linkInf.getLinkedFrame().getFrameName()) != null) {
|
---|
| 1467 | isReachable = true;
|
---|
| 1468 | break;
|
---|
| 1469 | }
|
---|
| 1470 |
|
---|
| 1471 | }
|
---|
| 1472 |
|
---|
| 1473 | // Ensure that the frame is reachable
|
---|
| 1474 | if (!isReachable) {
|
---|
| 1475 | graphRoots.add(linkInf.getLinkedFrame());
|
---|
| 1476 | }
|
---|
| 1477 |
|
---|
| 1478 | // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
|
---|
| 1479 | cancelFetch = doNotify = true;
|
---|
| 1480 | // It will die in its own time - and will always be cancelled because it locks
|
---|
| 1481 | // the current locked object.
|
---|
| 1482 |
|
---|
| 1483 | }
|
---|
| 1484 |
|
---|
| 1485 |
|
---|
| 1486 | }
|
---|
| 1487 |
|
---|
| 1488 | }
|
---|
| 1489 |
|
---|
| 1490 | }
|
---|
| 1491 |
|
---|
| 1492 | // Notify observers.
|
---|
| 1493 | if (doNotify)
|
---|
| 1494 | fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_REMOVED, virtualFilename));
|
---|
| 1495 | }
|
---|
| 1496 |
|
---|
| 1497 | /**
|
---|
| 1498 | * @return
|
---|
| 1499 | * True if the graph model is updating.
|
---|
| 1500 | */
|
---|
| 1501 | public boolean isUpdating() {
|
---|
| 1502 | return (delayedModelUpdator != null && delayedModelUpdator.isAlive());
|
---|
| 1503 | }
|
---|
| 1504 |
|
---|
| 1505 |
|
---|
| 1506 | /**
|
---|
| 1507 | * Waits for updates to finish.
|
---|
| 1508 | *
|
---|
| 1509 | * <b>MUST NOT BE ON SWING THREAD - OR MAY DEADLOCK</b>
|
---|
| 1510 | *
|
---|
| 1511 | * @throws InterruptedException
|
---|
| 1512 | * if any thread has interrupted the current thread
|
---|
| 1513 | */
|
---|
| 1514 | public void waitOnUpdates() throws InterruptedException {
|
---|
| 1515 | if (delayedModelUpdator != null && delayedModelUpdator.isAlive()) {
|
---|
| 1516 | delayedModelUpdator.join();
|
---|
| 1517 | }
|
---|
| 1518 | }
|
---|
| 1519 |
|
---|
| 1520 | /**
|
---|
| 1521 | * Queues an update for the consistant model to be updated later... since it may take some time.
|
---|
| 1522 | *
|
---|
| 1523 | * @param update
|
---|
| 1524 | * The update to do. Must not be null.
|
---|
| 1525 | */
|
---|
| 1526 | private void updateLater(LinkedTrackUpdate update) {
|
---|
| 1527 |
|
---|
| 1528 | synchronized(updateQueue) {
|
---|
| 1529 |
|
---|
| 1530 | assert(update != null);
|
---|
| 1531 |
|
---|
| 1532 | // Add the update tot he queu
|
---|
| 1533 | updateQueue.add(update);
|
---|
| 1534 |
|
---|
| 1535 | // Ensure that the update thread is alive
|
---|
| 1536 | if (delayedModelUpdator == null || !delayedModelUpdator.isAlive()) {
|
---|
| 1537 | delayedModelUpdator = new DelayedModelUpdator();
|
---|
| 1538 | delayedModelUpdator.start();
|
---|
| 1539 | }
|
---|
| 1540 |
|
---|
| 1541 | }
|
---|
| 1542 |
|
---|
| 1543 | }
|
---|
| 1544 |
|
---|
| 1545 | private Queue<LinkedTrackUpdate> updateQueue = new LinkedList<LinkedTrackUpdate>(); // SHARED RESOURCE
|
---|
| 1546 |
|
---|
| 1547 | /**
|
---|
| 1548 | * Used for queuing update data.
|
---|
| 1549 | *
|
---|
| 1550 | *
|
---|
| 1551 | * @author Brook Novak
|
---|
| 1552 | *
|
---|
| 1553 | */
|
---|
| 1554 | private class LinkedTrackUpdate extends LinkedTrackModelData {
|
---|
| 1555 |
|
---|
| 1556 | LinkedTrackUpdate(
|
---|
| 1557 | long initiationTime,
|
---|
| 1558 | String absoluteLink,
|
---|
| 1559 | String virtualFilename,
|
---|
| 1560 | String parentFrameToAddTo,
|
---|
[349] | 1561 | String name,
|
---|
| 1562 | int ypos) {
|
---|
[315] | 1563 |
|
---|
[363] | 1564 | super(Mutable.createMutableLong(initiationTime), absoluteLink, name, ypos);
|
---|
| 1565 |
|
---|
[315] | 1566 | assert(virtualFilename != null);
|
---|
| 1567 | assert(parentFrameToAddTo != null);
|
---|
| 1568 | assert(FrameIO.isValidFrameName(absoluteLink));
|
---|
| 1569 |
|
---|
| 1570 | this.virtualFilename = virtualFilename;
|
---|
| 1571 | this.parentFrameToAddTo = parentFrameToAddTo;
|
---|
| 1572 | }
|
---|
| 1573 |
|
---|
| 1574 | String virtualFilename;
|
---|
| 1575 | String parentFrameToAddTo;
|
---|
| 1576 |
|
---|
| 1577 | }
|
---|
| 1578 |
|
---|
| 1579 | /**
|
---|
| 1580 | *
|
---|
| 1581 | * @author Brook Novak
|
---|
| 1582 | *
|
---|
| 1583 | */
|
---|
| 1584 | private class DelayedModelUpdator extends Thread {
|
---|
| 1585 |
|
---|
| 1586 | public void run() {
|
---|
| 1587 | while (true) {
|
---|
| 1588 | LinkedTrackUpdate update;
|
---|
| 1589 | synchronized(updateQueue) {
|
---|
| 1590 | if (updateQueue.isEmpty()) return; // important: only quits when updateQueue is synched
|
---|
| 1591 | update = updateQueue.poll();
|
---|
| 1592 | }
|
---|
| 1593 |
|
---|
| 1594 | assert(update != null);
|
---|
| 1595 |
|
---|
| 1596 | // Keep trying the update until success or encountered loops
|
---|
| 1597 | while (true) {
|
---|
| 1598 | try {
|
---|
| 1599 |
|
---|
| 1600 | OverdubbedFrame odframe = fetchGraph(update.frameLink);
|
---|
| 1601 | if (odframe == null) { // does not exist or is empty...
|
---|
| 1602 | odframe = new OverdubbedFrame(update.frameLink);
|
---|
| 1603 | }
|
---|
| 1604 |
|
---|
| 1605 |
|
---|
| 1606 | // add to parent only if does not already exist - also being aware of loops
|
---|
| 1607 | final LinkedTrackUpdate updateData = update;
|
---|
| 1608 | final OverdubbedFrame linkedODFrame = odframe;
|
---|
| 1609 |
|
---|
| 1610 | try {
|
---|
| 1611 | SwingUtilities.invokeAndWait(new Runnable() {
|
---|
| 1612 |
|
---|
| 1613 | public void run() { // on swing thread
|
---|
| 1614 |
|
---|
| 1615 | synchronized(sharedResourceLocker) {
|
---|
| 1616 |
|
---|
| 1617 | try { // The fetch will be cancelled after this block - within the lock
|
---|
| 1618 |
|
---|
| 1619 | OverdubbedFrame parentFrame = allOverdubbedFrames.get(updateData.parentFrameToAddTo.toLowerCase());
|
---|
| 1620 | if (parentFrame == null) {
|
---|
| 1621 | // No need to both adding - because model does not even reference this new link...
|
---|
| 1622 | // must have cleared while updating... e.g. user could have switched into xray mode.
|
---|
| 1623 | return;
|
---|
| 1624 | }
|
---|
| 1625 |
|
---|
| 1626 | assert(updateData.initiationTime != null);
|
---|
| 1627 |
|
---|
| 1628 | // Create the linked track instance
|
---|
| 1629 | LinkedTracksGraphNode linktInf = new LinkedTracksGraphNode(
|
---|
[363] | 1630 | updateData.initiationTime.value,
|
---|
[315] | 1631 | linkedODFrame,
|
---|
| 1632 | updateData.virtualFilename,
|
---|
[349] | 1633 | updateData.name,
|
---|
| 1634 | updateData.ypos);
|
---|
[315] | 1635 |
|
---|
| 1636 | // Check that the graph will be loop free
|
---|
| 1637 | parentFrame.addLinkedTrack(linktInf); // ADDING TEMPORARILY
|
---|
| 1638 |
|
---|
| 1639 | // Check from parent frame
|
---|
| 1640 | Stack<OverdubbedFrame> loopTrace = new Stack<OverdubbedFrame>();
|
---|
| 1641 | if (!isLoopFree(parentFrame, loopTrace)) {
|
---|
| 1642 | parentFrame.removeLinkedTrack(linktInf);
|
---|
| 1643 | // Ignore link - has loop
|
---|
| 1644 | return;
|
---|
| 1645 | }
|
---|
| 1646 |
|
---|
| 1647 | // Check for all existing graph roots
|
---|
| 1648 | for (OverdubbedFrame existingRoot : graphRoots) {
|
---|
| 1649 | loopTrace.clear();
|
---|
| 1650 | if (!isLoopFree(existingRoot, loopTrace)) {
|
---|
| 1651 | parentFrame.removeLinkedTrack(linktInf);
|
---|
| 1652 | // Ignore link - has loop
|
---|
| 1653 | return;
|
---|
| 1654 | }
|
---|
| 1655 | }
|
---|
| 1656 |
|
---|
| 1657 | // Otherwise leave the link in its place
|
---|
| 1658 |
|
---|
| 1659 | // Ensure that the linked frame is in the allOverdubbedFrames set
|
---|
| 1660 | allOverdubbedFrames.put(linkedODFrame.getFrameName().toLowerCase(), linkedODFrame);
|
---|
| 1661 |
|
---|
| 1662 | } finally { // IMPORTANT: CANCEL WHILE LOCKED
|
---|
| 1663 |
|
---|
| 1664 | // Cancel any fetches
|
---|
| 1665 | cancelFetch = true;
|
---|
| 1666 | }
|
---|
| 1667 |
|
---|
| 1668 | } // release lock
|
---|
| 1669 |
|
---|
| 1670 |
|
---|
| 1671 | }
|
---|
| 1672 | });
|
---|
| 1673 | } catch (InvocationTargetException e) {
|
---|
| 1674 | e.printStackTrace();
|
---|
| 1675 | assert(false);
|
---|
| 1676 | }
|
---|
| 1677 |
|
---|
| 1678 | // Done with this update...
|
---|
| 1679 | break;
|
---|
| 1680 |
|
---|
| 1681 |
|
---|
| 1682 | } catch (InterruptedException e) { // Canceled
|
---|
| 1683 | // Consume and retry
|
---|
| 1684 |
|
---|
| 1685 | } catch (TrackGraphLoopException e) { // bad link
|
---|
| 1686 | // Consume - since is fine - the model will just ignore the link
|
---|
| 1687 | break;
|
---|
| 1688 | }
|
---|
| 1689 | } // retry fetch
|
---|
| 1690 |
|
---|
| 1691 | } // proccess next update
|
---|
| 1692 |
|
---|
| 1693 | } // finished updating
|
---|
| 1694 | }
|
---|
| 1695 |
|
---|
| 1696 | }
|
---|