source: trunk/src/org/apollo/widgets/SampledTrack.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: 64.4 KB
Line 
1package org.apollo.widgets;
2
3import java.awt.Color;
4import java.awt.FontMetrics;
5import java.awt.Graphics;
6import java.awt.Graphics2D;
7import java.awt.Rectangle;
8import java.awt.Shape;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.KeyListener;
12import java.awt.event.MouseEvent;
13import java.awt.event.MouseListener;
14import java.awt.event.MouseMotionListener;
15import java.awt.geom.Rectangle2D;
16import java.io.File;
17import java.io.IOException;
18import java.lang.reflect.InvocationTargetException;
19import java.util.LinkedList;
20import java.util.List;
21
22import javax.sound.sampled.AudioFormat;
23import javax.sound.sampled.LineUnavailableException;
24import javax.sound.sampled.UnsupportedAudioFileException;
25import javax.swing.JSlider;
26import javax.swing.SwingUtilities;
27
28import org.apollo.ApolloGestureActions;
29import org.apollo.ApolloKBMGestureTranslator;
30import org.apollo.audio.ApolloPlaybackMixer;
31import org.apollo.audio.ApolloSubjectChangedEvent;
32import org.apollo.audio.SampledTrackModel;
33import org.apollo.audio.TrackSequence;
34import org.apollo.audio.structure.AudioStructureModel;
35import org.apollo.audio.structure.TrackGraphNode;
36import org.apollo.audio.util.SoundDesk;
37import org.apollo.audio.util.Timeline;
38import org.apollo.audio.util.TrackMixSubject;
39import org.apollo.gui.EditableSampledTrackGraphView;
40import org.apollo.gui.ExpandedTrackManager;
41import org.apollo.gui.FrameLayoutDaemon;
42import org.apollo.gui.PlaybackControlPopup;
43import org.apollo.gui.SampledTrackGraphView;
44import org.apollo.gui.SampledTrackGraphView.EffecientInvalidator;
45import org.apollo.gui.Strokes;
46import org.apollo.io.AudioIO;
47import org.apollo.io.AudioIO.AudioFileLoader;
48import org.apollo.io.AudioPathManager;
49import org.apollo.io.IconRepository;
50import org.apollo.items.EmulatedTextItem;
51import org.apollo.items.EmulatedTextItem.TextChangeListener;
52import org.apollo.mvc.Observer;
53import org.apollo.mvc.Subject;
54import org.apollo.mvc.SubjectChangedEvent;
55import org.apollo.util.AudioMath;
56import org.apollo.util.Mutable;
57import org.apollo.util.TrackModelHandler;
58import org.apollo.util.TrackModelLoadManager;
59import org.apollo.util.TrackNameCreator;
60import org.expeditee.Util;
61import org.expeditee.core.Colour;
62import org.expeditee.core.Point;
63import org.expeditee.core.Stroke;
64import org.expeditee.core.bounds.AxisAlignedBoxBounds;
65import org.expeditee.gio.gesture.StandardGestureActions;
66import org.expeditee.gio.swing.MouseEventRouter;
67import org.expeditee.gio.swing.SwingConversions;
68import org.expeditee.gio.swing.SwingMiscManager;
69import org.expeditee.gui.DisplayController;
70import org.expeditee.gui.Frame;
71import org.expeditee.gui.FrameIO;
72import org.expeditee.gui.PopupManager;
73import org.expeditee.gui.management.ResourceManager;
74import org.expeditee.items.ItemParentStateChangedEvent;
75import org.expeditee.items.ItemUtils;
76import org.expeditee.items.Text;
77import org.expeditee.items.widgets.HeavyDutyInteractiveWidget;
78import org.expeditee.items.widgets.InteractiveWidgetInitialisationFailedException;
79import org.expeditee.items.widgets.InteractiveWidgetNotAvailableException;
80import org.expeditee.items.widgets.Widget;
81
82/**
83 * The sampled track widgets in apollo.
84 *
85 * @author Brook Novak
86 *
87 */
88public class SampledTrack extends HeavyDutyInteractiveWidget implements TrackModelHandler, EffecientInvalidator, Observer {
89
90 /** The observered subject. Can be null */
91 private SampledTrackModel trackModel = null;
92
93 /** Used for the loading phase. Can change several times of a lifetime. for example after import and save it becomes local */
94 private String loadFilenameArgument = null; // preproceeds with ARG_IMPORT_TAG if importing.
95
96 private String localFileName; // Immutable - assigned on construction
97
98 /** Used for dumping audio to a temp file when deleted - to free memory.
99 * Protocol: Set when the widget is deleted. Unset when loaded. */
100 private File recoveryFile = null;
101
102 private boolean shouldOmitAudioIndexing = false;
103
104 private EditableSampledTrackGraphView fulltrackView;
105 private EmulatedTextItem nameLabel;
106 private PlaybackPopup playbackControlPopup = null;
107 private TrackMixSubject trackMix; // immutable - like local filename
108
109 /** The amount of load bar that is allocated to file loading */
110 private static final float FILE_LOADING_PERCENT_RANGE = 0.95f;
111
112 /** For parsing arguments */
113 public static final String ARG_IMPORT_TAG = "if=";
114
115 /** For parsing metadata */
116 public static final String META_LOCALNAME_TAG = "ln=";
117
118 private static final Stroke FREESPACE_OUTLINING = Strokes.SOLID_2;
119
120 private static final Colour SEMI_TRANSPARENT_FREESPACE_BACKCOLOR = new Colour(
121 FREESPACE_BACKCOLOR.getRed(), FREESPACE_BACKCOLOR.getGreen(),
122 FREESPACE_BACKCOLOR.getBlue(), Colour.FromComponent255(128));
123
124
125 /** If a track widget has a data string thaty contains META_SHOULD_INDEX_AUDIO_TAG, then
126 * the track should not be indexed for searching the audio.... default isdoes not exist
127 */
128 public static final String META_DONT_INDEX_AUDIO_TAG = "dontindexaudio";
129
130 /**
131 * Constructor used for loading directly from given bytes.
132 *
133 * @param source
134 *
135 * @param audioBytes
136 *
137 * @param format
138 *
139 * @param mixTemplate
140 * Can be null
141 *
142 */
143 private SampledTrack(Text source, byte[] audioBytes, AudioFormat format, TrackMixSubject mixTemplate) {
144
145 super(source, new EditableSampledTrackGraphView(),
146 100, -1,
147 FrameLayoutDaemon.TRACK_WIDGET_HEIGHT, FrameLayoutDaemon.TRACK_WIDGET_HEIGHT,
148 TrackWidgetCommons.CACHE_DEPTH,
149 true);
150
151 // Must set upon construction - always
152 localFileName = AudioPathManager.generateLocateFileName("wav");
153 updateData(META_LOCALNAME_TAG, META_LOCALNAME_TAG + localFileName);
154
155 trackModel = new SampledTrackModel(audioBytes, format, localFileName);
156
157 // Ensure that the model is marked as modified so that it will save
158 trackModel.setAudioModifiedFlag(true);
159
160 trackModel.setName(getStrippedDataString(TrackWidgetCommons.META_NAME_TAG));
161
162 // Create the immutable mix subject
163 if (mixTemplate == null) {
164 trackMix = SoundDesk.getInstance().getOrCreateMix(SoundDesk.createPureLocalChannelID(this));
165 } else {
166 trackMix = SoundDesk.getInstance().createMix(
167 SoundDesk.createPureLocalChannelID(this),
168 mixTemplate.getVolume(),
169 mixTemplate.isMuted());
170 }
171
172 // Keep meta as constant as possible for best results
173 updateData(TrackWidgetCommons.META_RUNNINGMSTIME_TAG, TrackWidgetCommons.META_RUNNINGMSTIME_TAG + getRunningMSTimeFromRawAudio());
174
175 createGUI();
176
177 initObservers();
178
179 }
180
181 @Override
182 public void onMoved()
183 {
184 if (nameLabel != null) {
185 nameLabel.setPosition(new Point(getX() + 10, getY() + 20));
186 }
187 }
188
189 /**
190 * Constructor called by Expeditee. Eventually loads audio from file.
191 *
192 * @param source
193 *
194 * @param args
195 * Can have a ARG_IMPORT_TAG argument...
196 */
197 public SampledTrack(Text source, String[] args) {
198 super(source, new EditableSampledTrackGraphView(),
199 100, -1,
200 FrameLayoutDaemon.TRACK_WIDGET_HEIGHT, FrameLayoutDaemon.TRACK_WIDGET_HEIGHT,
201 TrackWidgetCommons.CACHE_DEPTH);
202
203 // Read the metadata
204 localFileName = getStrippedDataString(META_LOCALNAME_TAG);
205
206 // Ensure the local filename is assigned - even if file does not exist...
207 // Also it could be importing a file...
208 if (localFileName == null) {
209 localFileName = AudioPathManager.generateLocateFileName("wav");
210 updateData(META_LOCALNAME_TAG, META_LOCALNAME_TAG + localFileName);
211 }
212
213 trackMix = SoundDesk.getInstance().getOrCreateMix(SoundDesk.createPureLocalChannelID(this));
214
215 loadFilenameArgument = localFileName;
216 if (args != null) { // parse args
217 for (String arg : args) {
218 if (arg != null && arg.length() > ARG_IMPORT_TAG.length()) {
219 loadFilenameArgument = arg;
220 }
221 }
222 }
223
224 createGUI();
225 }
226
227 private void initObservers() {
228
229 // Listen for model events
230 trackModel.addObserver(this);
231 trackModel.addObserver(fulltrackView);
232 fulltrackView.setMix(trackMix); // use the same mix/channel for playback
233 ExpandedTrackManager.getInstance().addObserver(this);
234
235 // Show graph as being selected if expanded / pending to expand.
236 if (ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel)) {
237 fulltrackView.setBackColor(new Color(100, 100, 100), new Color(120, 120, 120));
238 } else {
239 fulltrackView.setBackColor(SampledTrackGraphView.DEFAULT_BACKGROUND_COLOR, SampledTrackGraphView.DEFAULT_BACKGROUND_HIGHTLIGHTS_COLOR);
240 }
241 }
242
243 private void createGUI() {
244
245 // Set widget as a fixed size widget- using width from last recorded width in the meta data.
246 int width = getStrippedDataInt(TrackWidgetCommons.META_LAST_WIDTH_TAG, -1);
247 if (width >= 0 && width < FrameLayoutDaemon.MIN_TRACK_WIDGET_WIDTH) {
248 width = FrameLayoutDaemon.MIN_TRACK_WIDGET_WIDTH;
249 }
250
251 if (width < 0) {
252 setSize(-1, -1,
253 FrameLayoutDaemon.TRACK_WIDGET_HEIGHT, FrameLayoutDaemon.TRACK_WIDGET_HEIGHT,
254 FrameLayoutDaemon.TRACK_WIDGET_DEFAULT_WIDTH, FrameLayoutDaemon.TRACK_WIDGET_HEIGHT);
255 } else {
256 setSize(width, width,
257 FrameLayoutDaemon.TRACK_WIDGET_HEIGHT, FrameLayoutDaemon.TRACK_WIDGET_HEIGHT,
258 width, FrameLayoutDaemon.TRACK_WIDGET_HEIGHT);
259 }
260
261 shouldOmitAudioIndexing = containsDataTrimmedIgnoreCase(META_DONT_INDEX_AUDIO_TAG);
262
263 playbackControlPopup = new PlaybackPopup();
264
265 fulltrackView = (EditableSampledTrackGraphView)_swingComponent;
266 fulltrackView.setAlwaysFullView(true);
267
268 /*fulltrackView.setInvalidator(new EffecientInvalidator() {
269 public void onGraphDirty(SampledTrackGraphView graph, Rectangle dirty) {
270 dirty.translate(getX(), getY() - 1);
271 FrameGraphics.invalidateArea(dirty);
272 FrameGraphics.refresh(true);
273 }
274 });*/
275
276 // Auto-show playback popup.
277 // Block messages if the track is expanded
278 fulltrackView.addMouseMotionListener(new MouseMotionListener() {
279
280 @Override
281 public void mouseDragged(MouseEvent e) {
282 if (nameLabel != null) {
283 if (nameLabel.onMouseDragged(e)) {
284 e.consume();
285 }
286 }
287 }
288
289 @Override
290 public void mouseMoved(MouseEvent e) {
291
292 if (nameLabel != null) {
293 nameLabel.gainFocus();
294 }
295
296 if (playbackControlPopup == null) {
297 playbackControlPopup = new PlaybackPopup();
298 }
299
300 // Only show popup iff there is nothing expanded / expanding.
301 if (!PopupManager.getInstance().isShowing(playbackControlPopup) &&
302 !ExpandedTrackManager.getInstance().doesExpandedTrackExist()) {
303
304 // Get rid of all popups
305 PopupManager.getInstance().hideAutoHidePopups();
306
307 Rectangle animationSource = _swingComponent.getBounds();
308
309 // Determine where popup should show
310 //int x = SampledTrack.this.getX();
311 int y = SampledTrack.this.getY() - playbackControlPopup.getFullBounds().getHeight() - 2; // by default show above
312
313 // I get sick.dizzy from the popup expanding from the whole thing...
314 animationSource.height = 1;
315 animationSource.width = Math.min(animationSource.width, playbackControlPopup.getFullBounds().getWidth());
316
317 if (y < 0) {
318 y = SampledTrack.this.getY() + SampledTrack.this.getHeight() + 2;
319 animationSource.y = y - 2;
320 }
321
322 // Animate the popup
323 playbackControlPopup.getAutoHideTime().setLifetime(TrackWidgetCommons.POPUP_LIFETIME);
324 PopupManager.getInstance().add(playbackControlPopup);
325 playbackControlPopup.show();
326 } else {
327 playbackControlPopup.show();
328 }
329
330 }
331
332 });
333
334 // Expand on double click
335 // Block messages if the track is expanded
336 fulltrackView.addMouseListener(new MouseListener() {
337
338 @Override
339 public void mouseClicked(MouseEvent e) {
340 if (trackModel == null) {
341 return;
342 }
343
344 if (nameLabel != null) {
345 if (nameLabel.onMouseClicked(e)) {
346 e.consume();
347 return;
348 }
349 }
350
351 if (e.getClickCount() >= 2) {
352
353 expand(e.isControlDown());
354
355 }
356
357 }
358
359 @Override
360 public void mouseEntered(MouseEvent e) {
361 }
362
363 @Override
364 public void mouseExited(MouseEvent e) {
365 }
366
367 @Override
368 public void mousePressed(MouseEvent e) {
369 if (nameLabel != null) {
370 if (nameLabel.onMousePressed(e)) {
371 e.consume();
372 return;
373 }
374 }
375
376 // Consume events if track is selected for expansion
377 if (ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel) &&
378 e.getButton() != MouseEvent.BUTTON1) { // but allow selection only
379 e.consume();
380 }
381 }
382
383 @Override
384 public void mouseReleased(MouseEvent e) {
385 if (nameLabel != null) {
386 if (nameLabel.onMouseReleased(e)) {
387 e.consume();
388 }
389 }
390
391 if (!e.isConsumed() && e.getButton() == MouseEvent.BUTTON2) {
392 if (split(true)) {
393 e.consume();
394 }
395 }
396
397 // Consume events if track is selected for expansion
398 if (ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel)) {
399 e.consume();
400 }
401
402 }
403
404 });
405
406 fulltrackView.addKeyListener(new KeyListener() {
407
408 @Override
409 public void keyPressed(KeyEvent e) {
410 }
411
412 @Override
413 public void keyReleased(KeyEvent e) {
414
415 // On 26th September, Bryce added P M + - keys to play/pause/resume, mute and adjust volume.
416 if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_P) {
417 try {
418 playbackControlPopup.playPauseResume();
419 } catch (LineUnavailableException e1) {
420 e1.printStackTrace();
421 }
422 } else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_M) {
423 boolean selected = playbackControlPopup.muteButton.isSelected();
424 playbackControlPopup.muteButton.setSelected(!selected);
425 playbackControlPopup.muteChanged();
426 } else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_SUBTRACT) {
427 JSlider volumeSlider = playbackControlPopup.volumeSlider;
428 int delta = Math.round(((float) (volumeSlider.getMaximum() - volumeSlider.getMinimum())) / 10f);
429 int newVolume = Math.max(volumeSlider.getValue() - delta, volumeSlider.getMinimum());
430 volumeSlider.setValue(newVolume);
431 playbackControlPopup.volumeChanged();
432 } else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_ADD) {
433 JSlider volumeSlider = playbackControlPopup.volumeSlider;
434 int delta = Math.round(((float) (volumeSlider.getMaximum() - volumeSlider.getMinimum())) / 10f);
435 int newVolume = Math.min(volumeSlider.getValue() + delta, volumeSlider.getMaximum());
436 volumeSlider.setValue(newVolume);
437 playbackControlPopup.volumeChanged();
438 }
439
440 // Toggle pitch-track indexing
441 else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_I) {
442 setShouldOmitIndexAudio(!shouldOmitIndexAudio());
443 DisplayController.requestRefresh(true);
444 }
445
446 // Delete-and-Split audio command
447 else if (!e.isControlDown() && e.getKeyCode() == KeyEvent.VK_DELETE) {
448
449 if (split(false)) {
450 e.consume();
451 }
452
453 }
454 // Convert to linked track
455 else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_L) {
456
457 Frame current = DisplayController.getCurrentFrame();
458
459 if (current != null) {
460
461 String name = getName();
462 if (name == null) {
463 name = "Unamed";
464 }
465
466 Mutable.Long initTime = getInitiationTimeFromMeta();
467 if (initTime == null) {
468 initTime = Mutable.createMutableLong(0);
469 }
470
471 Text linkSource = new Text(current.getNextItemID());
472 linkSource.addToData(TrackWidgetCommons.META_NAME_TAG + name);
473 linkSource.setPosition(getPosition());
474 linkSource.setParent(current);
475 LinkedTrack linkedVersion = new LinkedTrack(linkSource, null);
476
477 // Save any changes in the audio - or save for the first time
478 if (trackModel.isAudioModified())
479 {
480 saveWidgetData(); // a little lag is OK .. could make smarter if really need to
481 }
482
483 // Create a new frame to hold the track
484 Frame newFrame = FrameIO.CreateNewFrame(SampledTrack.this.getFirstCorner());
485
486 // Remove track from current frame
487 removeSelf();
488
489 // Add to new frame
490 newFrame.addAllItems(getItems());
491
492 // Save changes
493 FrameIO.SaveFrame(newFrame);
494
495 // Link it
496 linkedVersion.setLink(newFrame.getName(), null);
497
498 // Add the new link
499 current.addAllItems(linkedVersion.getItems());
500
501 // Ensure initiation times are retained to the exact frame... avoiding loss due to resolution
502 linkedVersion.setInitiationTime(initTime.value);
503
504
505 }
506
507 } else if (e.isControlDown()) {
508 if (e.getKeyCode() == KeyEvent.VK_LEFT) {
509 e.consume();
510 ApolloGestureActions.adjustInitiationTime(SampledTrack.this, -ApolloKBMGestureTranslator.TRACK_WIDGET_TIMELINE_ADJUSTMENT);
511 } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
512 e.consume();
513 ApolloGestureActions.adjustInitiationTime(SampledTrack.this, ApolloKBMGestureTranslator.TRACK_WIDGET_TIMELINE_ADJUSTMENT);
514 } else if (e.getKeyCode() == KeyEvent.VK_UP) {
515 e.consume();
516 ApolloGestureActions.adjustVerticalPosition(SampledTrack.this, -ApolloKBMGestureTranslator.TRACK_WIDGET_VERTICAL_ADJUSTMENT);
517 } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
518 e.consume();
519 ApolloGestureActions.adjustVerticalPosition(SampledTrack.this, ApolloKBMGestureTranslator.TRACK_WIDGET_VERTICAL_ADJUSTMENT);
520 }
521 }
522
523 }
524
525 @Override
526 public void keyTyped(KeyEvent e) {
527 }
528
529 });
530
531 nameLabel = new EmulatedTextItem("", (new Point(10, 20)).add(getBounds().getTopLeft()));
532 nameLabel.setBackgroundColor(Colour.WHITE);
533
534 String metaName = getStrippedDataString(TrackWidgetCommons.META_NAME_TAG);
535 if (metaName == null) {
536 metaName = "Untitled";
537 }
538 nameLabel.setText(metaName);
539
540 nameLabel.addTextChangeListener(new TextChangeListener() { // a little bit loopy!
541
542 @Override
543 public void onTextChanged(Object source, String newLabel) {
544 if (trackModel != null && !nameLabel.getText().equals(trackModel.getName())) {
545 trackModel.setName(nameLabel.getText());
546 SampledTrack.this.updateData(TrackWidgetCommons.META_NAME_TAG, trackModel.getName());
547
548 }
549 }
550
551 });
552
553 // Make sure the above mouse listeners get first serve
554 fulltrackView.reAddListeners();
555
556 // Make sure border color is correct
557 updateBorderColor();
558 setWidgetEdgeThickness(TrackWidgetCommons.STOPPED_TRACK_EDGE_THICKNESS);
559 }
560
561 /**
562 * Creates a {@link SampledTrack} instantly from audio bytes in memory and adds it to a given frame.
563 *
564 * @param audioBytes
565 * The audio samples.
566 *
567 * @param format
568 * The format of the audio samples.
569 *
570 * @param targetFrame
571 * The frame that the widget will reside.
572 *
573 * @param x
574 *
575 * @param yinitTime
576 *
577 * @param name
578 * The name of the new track. If null then a default name will be used.
579 *
580 * @param mixTemplate
581 * The mix data to clone for the new sampled tracks mix.
582 * If null then default mix settings will be used.
583 *
584 * @return The {@link SampledTrack} instance added to the given frame.
585 *
586 */
587 public static SampledTrack createFromMemory(
588 byte[] audioBytes,
589 AudioFormat format,
590 Frame targetFrame,
591 int x, int y,
592 String name,
593 TrackMixSubject mixTemplate) {
594
595 assert (targetFrame != null);
596 assert (audioBytes != null);
597 assert (format != null);
598
599 Text source = new Text(targetFrame.getNextItemID());
600 source.setParent(targetFrame);
601
602
603 long runningtime = AudioMath.framesToMilliseconds(
604 audioBytes.length / format.getFrameSize(), format);
605
606 Timeline tl = FrameLayoutDaemon.getInstance().getTimeline(targetFrame);
607
608 int width = -1;
609 if (tl != null) {
610 width = (int)(runningtime / tl.getTimePerPixel());
611 }
612
613 // Clamp width to something reasonable.
614 if (width > 0 && width < FrameLayoutDaemon.MIN_TRACK_WIDGET_WIDTH) {
615 width = FrameLayoutDaemon.MIN_TRACK_WIDGET_WIDTH;
616 }
617
618 long initTime = (tl == null) ? 0 : tl.getMSTimeAtX(x);
619
620 source.setPosition(x, y);
621
622 // Setup metadata
623 LinkedList<String> data = new LinkedList<String>();
624
625 data.add(TrackWidgetCommons.META_INITIATIONTIME_TAG + initTime);
626 data.add(TrackWidgetCommons.META_LAST_WIDTH_TAG + width); // although layout manager will handle, just to quick set
627 if (name != null) {
628 data.add(TrackWidgetCommons.META_NAME_TAG + name);
629 }
630
631 source.setData(data);
632
633 SampledTrack strack = new SampledTrack(source, audioBytes, format, mixTemplate);
634
635 return strack;
636 }
637
638
639 /**
640 * Creates a {@link SampledTrack} from file - which is tyo be imported into apollos
641 *
642 * @param targetFrame
643 * The frame that the widget will reside.
644 *
645 * @param file
646 * The audio file to import
647 *
648 * @return The {@link SampledTrack} instance added to the given frame.
649 *
650 */
651 public static SampledTrack createFromFile(File file, Frame targetFrame, int x, int y) {
652 assert (targetFrame != null);
653 assert (file != null);
654
655
656 String[] args = new String[] {
657 ARG_IMPORT_TAG + file.getAbsolutePath()
658 };
659
660 Text source = new Text(
661 targetFrame.getNextItemID(),
662 ItemUtils.GetTag(ItemUtils.TAG_IWIDGET) + ":" + Util.formatArgs(args));
663
664 source.setParent(targetFrame);
665
666 source.setPosition(x, y);
667
668 long initTime = FrameLayoutDaemon.getInstance().getMSAtX(x, targetFrame);
669
670 // Setup metadata
671 LinkedList<String> data = new LinkedList<String>();
672
673 data.add(TrackWidgetCommons.META_INITIATIONTIME_TAG + initTime);
674 data.add(TrackWidgetCommons.META_LAST_WIDTH_TAG + FrameLayoutDaemon.TRACK_WIDGET_DEFAULT_WIDTH); // once loaded then the layout will handle proper size
675
676 int extIndex = file.getName().lastIndexOf('.');
677 String name = (extIndex > 0) ? file.getName().substring(0, extIndex) : file.getName();
678 data.add(TrackWidgetCommons.META_NAME_TAG + name);
679
680 source.setData(data);
681
682 SampledTrack strack = new SampledTrack(source, args);
683
684 return strack;
685 }
686
687
688 @Override
689 protected String[] getArgs() {
690 return null;
691 }
692
693 @Override
694 protected List<String> getData() {
695
696 List<String> data = new LinkedList<String>();
697
698 data.add(META_LOCALNAME_TAG + localFileName);
699
700 if (shouldOmitAudioIndexing) {
701 data.add(META_DONT_INDEX_AUDIO_TAG);
702 }
703
704 String lastName = getName();
705
706 if (lastName != null) {
707 data.add(TrackWidgetCommons.META_NAME_TAG + lastName);
708 }
709
710 data.add(TrackWidgetCommons.META_LAST_WIDTH_TAG + getWidth());
711
712 Mutable.Long initTime = null;
713
714 Frame f = getParentFrame();
715 if (f != null) {
716 TrackGraphNode tinf = AudioStructureModel.getInstance().getTrackGraphInfo(localFileName, f.getName());
717 if (tinf != null) {
718 initTime = Mutable.createMutableLong(tinf.getInitiationTime());
719 }
720 }
721
722 if (initTime == null)
723 {
724 initTime = getInitiationTimeFromMeta(); // old meta
725 }
726 if (initTime == null) {
727 initTime = Mutable.createMutableLong(0L);
728 }
729
730 data.add(TrackWidgetCommons.META_INITIATIONTIME_TAG + initTime);
731
732 data.add(TrackWidgetCommons.META_RUNNINGMSTIME_TAG + getRunningMSTimeFromRawAudio());
733
734 return data;
735 }
736
737
738 @Override
739 public Widget copy()
740 throws InteractiveWidgetNotAvailableException, InteractiveWidgetInitialisationFailedException {
741 if (trackModel == null) {
742 return super.copy();
743
744 } else {
745 return SampledTrack.createFromMemory(
746 trackModel.getAllAudioBytesCopy(),
747 trackModel.getFormat(),
748 getSource().getParentOrCurrentFrame(),
749 getX(),
750 getY(),
751 TrackNameCreator.getNameCopy(trackModel.getName()),
752 trackMix);
753 }
754
755 }
756
757 @Override
758 public int getLoadDelayTime() {
759 return 0;
760 }
761
762 @Override
763 protected float loadWidgetData() {
764
765 // Load audio from file
766 File f = null;
767 boolean isImporting = false;
768
769 if (recoveryFile != null) { // are we recovering?
770
771 setLoadScreenMessage("Recovering audio file...");
772
773 if (recoveryFile.exists()) {
774 f = recoveryFile;
775 }
776
777 }
778
779 if (f == null) { // must be importing or loading from local repository
780
781 if (loadFilenameArgument != null && loadFilenameArgument.startsWith(ARG_IMPORT_TAG)) { // importing a file?
782
783 setLoadScreenMessage("Importing audio file...");
784
785 isImporting = true;
786 f = new File(loadFilenameArgument.substring(ARG_IMPORT_TAG.length()));
787
788 } else { // local
789 assert(loadFilenameArgument == null || loadFilenameArgument == localFileName);
790
791 setLoadScreenMessage("Loading audio file...");
792
793 //f = new File(
794 // AudioPathManager.AUDIO_HOME_DIRECTORY + localFileName);
795 f = ResourceManager.getAudioResource(localFileName, getParentFrame());
796 }
797
798 // Nullify
799 if (!f.exists() || !f.isFile()) {
800 f = null;
801 }
802
803 }
804
805 if (f == null) {
806 if (recoveryFile != null) {
807 setLoadScreenMessage("Recovery file missing");
808 } else {
809 setLoadScreenMessage("File missing");
810 }
811
812 return LOAD_STATE_FAILED;
813 }
814
815 if (trackModel != null && trackModel.getFilepath() != null &&
816 trackModel.equals(f.getPath())) {
817
818 // already have loaded
819 assert(!isImporting);
820
821 } else {
822
823 try {
824
825 trackModel = TrackModelLoadManager.getInstance().load(f.getPath(), localFileName, this, false);
826
827 if (trackModel == null) { // load operation canceled
828 assert(hasCancelBeenRequested());
829 return LOAD_STATE_INCOMPLETED;
830
831 } else if(isImporting) { // ensure that file path is null - since not yet saved
832 trackModel.setFilepath(null);
833
834 } else if (recoveryFile != null) {
835
836 // If recovering - might be recovering an existing track that had been
837 // saved to the repository .. thus re-use the old file
838 File audioResource = ResourceManager.getAudioResource(localFileName, getParentFrame());
839 trackModel.setFilepath(audioResource.getAbsolutePath());
840 }
841
842 } catch (IOException e) {
843 e.printStackTrace();
844 setLoadScreenMessage("Failed to load audio file");
845 return LOAD_STATE_FAILED;
846
847 } catch (UnsupportedAudioFileException e) {
848 e.printStackTrace();
849 setLoadScreenMessage("Format not supported");
850 return LOAD_STATE_FAILED;
851 } catch (OutOfMemoryError e) {
852 e.printStackTrace();
853 setLoadScreenMessage("Out of memory");
854 return LOAD_STATE_FAILED;
855 }
856
857 }
858
859 // If was imported / recovered, then set as being modified
860 if (isImporting || recoveryFile != null) {
861 trackModel.setAudioModifiedFlag(true);
862 }
863
864 // Set the name for this track
865 String name = getStrippedDataString(TrackWidgetCommons.META_NAME_TAG);
866 if (name != null) {
867 trackModel.setName(name);
868 }
869
870 initObservers(); // sets default name if non set
871
872 // If was recovering - get rid of temp data
873 if (recoveryFile != null) {
874 recoveryFile.delete();
875 recoveryFile = null; // ensure that out of a recovery state
876 }
877
878 // Keep meta as constant as possible for best results
879 updateData(TrackWidgetCommons.META_RUNNINGMSTIME_TAG, TrackWidgetCommons.META_RUNNINGMSTIME_TAG + getRunningMSTimeFromRawAudio());
880
881 // Must make sure that this track is on the track graph model
882 try {
883 SwingUtilities.invokeAndWait(new Runnable() {
884
885 @Override
886 public void run() {
887
888 Frame parent = getParentFrame();
889 String pfname = (parent != null) ? parent.getName() : null;
890
891 TrackGraphNode tinf = AudioStructureModel.getInstance().getTrackGraphInfo(localFileName, pfname);
892 if (tinf == null) {
893
894 // Determine new initation time according to position
895 long initTime = (parent != null) ?
896 FrameLayoutDaemon.getInstance().getMSAtX(getX(), parent)
897 : 0;
898
899 // Keep TrackGraphModel consistant
900 AudioStructureModel.getInstance().onTrackWidgetAnchored(
901 localFileName,
902 pfname,
903 initTime,
904 AudioMath.framesToMilliseconds(trackModel.getFrameCount(), trackModel.getFormat()),
905 getName(),
906 getY());
907
908 }
909 }
910 });
911 } catch (InterruptedException e) {
912 e.printStackTrace();
913 } catch (InvocationTargetException e) {
914 e.printStackTrace();
915 }
916
917
918 // Notify layout manager - not really needed but to be extra safe force the daemon
919 // to be super consistant
920 FrameLayoutDaemon.getInstance().forceRecheck();
921
922 return LOAD_STATE_COMPLETED;
923 }
924
925 /**
926 * Used by save manager
927 */
928 @Override
929 public boolean doesNeedSaving() {
930 return (trackModel != null && (trackModel.isAudioModified() || trackModel.getFilepath() == null));
931 }
932
933 /**
934 * Used by save manager
935 */
936 @Override
937 public String getSaveName() {
938
939 if (trackModel != null && trackModel.getName() != null) {
940 return "Sampled Track: " + trackModel.getName();
941 } else {
942 return "A Sampled Track";
943 }
944
945 }
946
947 /**
948 * Saves audio if the audio is loaded and modified / unsaved.
949 * Blocking.
950 */
951 public void saveAudio() {
952 if (trackModel != null && trackModel.isAudioModified()) {
953 saveWidgetData();
954 }
955 }
956
957 /**
958 * Saves the audio bytes to file
959 */
960 @Override
961 protected void saveWidgetData() {
962
963 if (trackModel == null)
964 {
965 return; // nothing to save
966 }
967
968 // If saving for the file time then get a filename
969 if (trackModel.getFilepath() == null) {
970 File audioResource = ResourceManager.getAudioResource(localFileName, DisplayController.getCurrentFrame());
971 trackModel.setFilepath(audioResource.getAbsolutePath());
972 loadFilenameArgument = localFileName; // set to now local, next load will be local
973 }
974
975 // Save audio bytes.
976 try {
977
978 AudioIO.savePCMAudioToWaveFile(
979 trackModel.getFilepath(),
980 trackModel.getAllAudioBytes(), // Safe: arrays are immutable
981 trackModel.getFormat());
982
983 // Reset modified flag
984 trackModel.setAudioModifiedFlag(false);
985
986 } catch (IOException e) {
987 e.printStackTrace();
988 } catch (UnsupportedAudioFileException e) {
989 e.printStackTrace();
990 }
991
992 // Ensure audio bytes can be collected if this has expired
993 if (isExpired()) {
994
995 try {
996 SwingUtilities.invokeAndWait(new Runnable() {
997 @Override
998 public void run() {
999 releaseMemory(true);
1000 }
1001 });
1002 } catch (InterruptedException e) {
1003 e.printStackTrace();
1004 } catch (InvocationTargetException e) {
1005 e.printStackTrace();
1006 }
1007
1008 }
1009
1010
1011 }
1012
1013 @Override
1014 public void onDelete() {
1015 super.onDelete();
1016 // Its nice to keep everything layed out as much as possible
1017 FrameLayoutDaemon.getInstance().resumeLayout(this);
1018 }
1019
1020 @Override
1021 protected void unloadWidgetData() {
1022
1023 // Still needs to save?
1024 if (doesNeedSaving() || trackModel == null)
1025 {
1026 return; // Release memory later when saved
1027 }
1028
1029 try {
1030 // Release memory - on swing thread to avoid nullified model data wil painting / editing.
1031 SwingUtilities.invokeAndWait(new Runnable() {
1032 @Override
1033 public void run() {
1034 releaseMemory(true);
1035 }
1036 });
1037 } catch (InterruptedException e) {
1038 e.printStackTrace();
1039 } catch (InvocationTargetException e) {
1040 e.printStackTrace();
1041 }
1042
1043
1044 }
1045
1046 @Override
1047 protected void tempUnloadWidgetData() { // basically this is deleting
1048
1049 // Need unloading?
1050 if (trackModel == null) {
1051 return;
1052 }
1053
1054 // Get rid of old temporary files
1055 if (recoveryFile != null && recoveryFile.exists()) {
1056 recoveryFile.delete();
1057 }
1058
1059 // Get rid of local file
1060 //File oldLocalFile = new File(AudioPathManager.AUDIO_HOME_DIRECTORY + localFileName);
1061 File oldLocalFile = ResourceManager.getAudioResource(localFileName, getParentFrame());
1062 if (oldLocalFile.isFile() && oldLocalFile.exists()) {
1063 try {
1064 oldLocalFile.delete();
1065 } catch(Exception e) {
1066 e.printStackTrace();
1067 }
1068 }
1069
1070 // Dump memory to a temp file
1071 try {
1072
1073 // Always be unique
1074 String uniqueID = AudioPathManager.generateLocateFileName("wav");
1075 recoveryFile = File.createTempFile("APOLLO_BACKUP" + uniqueID, null);
1076
1077 // To avoid filling up the end-users hardisk, mark file to be
1078 // deleted on exit (if not already deleted).
1079 recoveryFile.deleteOnExit();
1080
1081 } catch (IOException e) {
1082 e.printStackTrace();
1083 return; // cannot dump ... must keep in memory.
1084 }
1085
1086 // Dump audio to file
1087 try {
1088 AudioIO.savePCMAudioToWaveFile(
1089 recoveryFile.getAbsolutePath(),
1090 trackModel.getAllAudioBytes(),
1091 trackModel.getFormat());
1092 } catch (IOException e1) {
1093 e1.printStackTrace();
1094 return; // cannot dump ... must keep in memory.
1095 } catch (UnsupportedAudioFileException e1) {
1096 e1.printStackTrace();
1097 return; // cannot dump ... must keep in memory.
1098 }
1099
1100 try {
1101 // Release memory - on swing thread to avoid nullified model data wil painting / editing.
1102 SwingUtilities.invokeAndWait(new Runnable() {
1103 @Override
1104 public void run() {
1105 releaseMemory(false);
1106 }
1107 });
1108 } catch (InterruptedException e) {
1109 e.printStackTrace();
1110 } catch (InvocationTargetException e) {
1111 e.printStackTrace();
1112 }
1113
1114 }
1115
1116 /**
1117 * To be called by the swing thread only
1118 * This is nessessary to avoid runtime memory leaks!!
1119 */
1120 private void releaseMemory(boolean onlyIfExpired) {
1121
1122 if (!onlyIfExpired || (onlyIfExpired && isExpired())) {
1123
1124 if (trackModel != null) {
1125
1126 trackModel.removeObserver(fulltrackView);
1127 trackModel.removeObserver(this);
1128 trackModel = null;
1129
1130 assert (fulltrackView.getObservedSubject() == null);
1131 assert (fulltrackView.getMix() == null);
1132 }
1133
1134 fulltrackView.releaseBuffer();
1135
1136 ExpandedTrackManager.getInstance().removeObserver(this);
1137
1138 }
1139
1140 }
1141
1142 /**
1143 * Global Track model re-use ....
1144 */
1145 @Override
1146 public SampledTrackModel getSharedSampledTrackModel(String localfilename) {
1147 if (trackModel != null &&
1148 trackModel.getLocalFilename().equals(localfilename)) {
1149 assert(localfilename.equals(localFileName));
1150 return trackModel;
1151 }
1152 return null;
1153
1154 }
1155
1156 /**
1157 * @see #getAudioFormat()
1158 *
1159 * @return
1160 * The audio bytes for this track widget. Null if not loaded.
1161 */
1162 public byte[] getAudioBytes() {
1163 return (trackModel != null) ? trackModel.getAllAudioBytes() : null;
1164 }
1165
1166 /**
1167 * @see #getAudioBytes()
1168 *
1169 * @return
1170 * The audio format of the audio bytes for this track widget. Null if not loaded.
1171 */
1172 public AudioFormat getAudioFormat() {
1173 return (trackModel != null) ? trackModel.getFormat() : null;
1174 }
1175
1176 /**
1177 * <b>Warning</b> if the path happens to be a path of a recovery file
1178 * then it is deleted if the widget is reloaded.
1179 *
1180 * @return
1181 * A full filepath to load the audio from. Never null. The path
1182 * may lead to a out of date file or no file.
1183 */
1184 public String getLatestSavedAudioPath() {
1185
1186 if (recoveryFile != null && recoveryFile.exists()) {
1187 return recoveryFile.getAbsolutePath();
1188 }
1189
1190 File audioResource = ResourceManager.getAudioResource(localFileName, getParentFrame());
1191 return audioResource.getAbsolutePath();
1192
1193
1194 }
1195
1196 @Override
1197 public void onGraphDirty(SampledTrackGraphView graph, Rectangle dirty)
1198 {
1199 dirty.translate(getX(), getY() - 1);
1200 DisplayController.invalidateArea(SwingConversions.fromSwingRectangle(dirty));
1201 DisplayController.requestRefresh(true);
1202 }
1203
1204 @Override
1205 public Subject getObservedSubject() {
1206 return null;
1207 }
1208
1209
1210 @Override
1211 protected void onSizeChanged()
1212 {
1213 super.onSizeChanged();
1214 // Keep meta as constant as possible for best reults
1215 updateData(TrackWidgetCommons.META_LAST_WIDTH_TAG, TrackWidgetCommons.META_LAST_WIDTH_TAG + getWidth());
1216 }
1217
1218 /**
1219 * Responds to model changed events by updating the GUI ...
1220 */
1221 @Override
1222 public void modelChanged(Subject source, SubjectChangedEvent event) {
1223
1224 // If the expansion selection has changed - check to see if was for this
1225 if (source == ExpandedTrackManager.getInstance()) {
1226
1227 // Show graph as being selected if expanded / pending to expand.
1228 if (trackModel != null &&
1229 ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel)) {
1230 fulltrackView.setBackColor(new Color(100, 100, 100), new Color(120, 120, 120));
1231 } else {
1232 fulltrackView.setBackColor(SampledTrackGraphView.DEFAULT_BACKGROUND_COLOR, SampledTrackGraphView.DEFAULT_BACKGROUND_HIGHTLIGHTS_COLOR);
1233 }
1234
1235 return;
1236 }
1237
1238 Frame parent = null;
1239
1240 switch (event.getID()) {
1241
1242 case ApolloSubjectChangedEvent.LOAD_STATUS_REPORT:
1243 // If this widget is loading - then update the load status
1244 if (isInLoadProgress()) {
1245
1246 // If the load has been cancelled, then cancel the loader
1247 if (hasCancelBeenRequested()) {
1248
1249 AudioFileLoader loader = (AudioFileLoader)source;
1250 loader.cancelLoad();
1251
1252 } else {
1253 float perc = ((Float)event.getState()).floatValue();
1254 if (perc > 1.0f) {
1255 perc = 1.0f;
1256 }
1257 perc *= FILE_LOADING_PERCENT_RANGE; // Dont stretch load to all of bar - still will have more work to do
1258 updateLoadPercentage(perc);
1259 }
1260
1261 }
1262
1263 break;
1264
1265 case ApolloSubjectChangedEvent.NAME_CHANGED:
1266 if (trackModel != null && !nameLabel.getText().equals(trackModel.getName())) {
1267 nameLabel.setText(trackModel.getName());
1268 }
1269
1270 // Get graph model consistant
1271 parent = getParentFrame();
1272 String pfname = (parent != null) ? parent.getName() : null;
1273
1274 AudioStructureModel.getInstance().onTrackWidgetNameChanged(
1275 localFileName,
1276 pfname,
1277 getName());
1278
1279 break;
1280
1281 case ApolloSubjectChangedEvent.AUDIO_INSERTED:
1282 case ApolloSubjectChangedEvent.AUDIO_REMOVED:
1283 long newRunningTime = getRunningMSTimeFromRawAudio();
1284 assert(newRunningTime > 0);
1285
1286 long oldRunningTime = getRunningMSTimeFromMeta();
1287
1288 // Keep meta as constant as possible for best reults
1289 updateData(TrackWidgetCommons.META_RUNNINGMSTIME_TAG, TrackWidgetCommons.META_RUNNINGMSTIME_TAG + newRunningTime);
1290
1291 if (trackModel != null) {
1292
1293 parent = getParentFrame();
1294
1295 // Keep TrackGraphModel consistant
1296 AudioStructureModel.getInstance().onTrackWidgetAudioEdited(
1297 localFileName,
1298 (parent != null) ? parent.getName() : null,
1299 newRunningTime);
1300
1301 if (trackModel.getSelectionStart() == 0 && oldRunningTime > newRunningTime) {
1302
1303
1304 Mutable.Long inittime = getInitiationTimeFromMeta();
1305 if (inittime == null) {
1306 inittime = Mutable.createMutableLong(0);
1307 }
1308 inittime.value += (oldRunningTime - newRunningTime);
1309
1310 updateData(TrackWidgetCommons.META_INITIATIONTIME_TAG,
1311 TrackWidgetCommons.META_INITIATIONTIME_TAG + inittime);
1312
1313 AudioStructureModel.getInstance().onTrackWidgetRemoved(localFileName, (parent != null) ? parent.getName() : null);
1314 AudioStructureModel.getInstance().onTrackWidgetAnchored(localFileName, (parent != null) ? parent.getName() : null,
1315 inittime.value, newRunningTime, getName(), getY());
1316 }
1317
1318 }
1319
1320 break;
1321
1322
1323 }
1324
1325 }
1326
1327 @Override
1328 public void setObservedSubject(Subject parent) {
1329 }
1330
1331 @Override
1332 public void paintInFreeSpace()
1333 {
1334 paintInFreeSpace(false);
1335 }
1336
1337 public void paintInFreeSpace(boolean isAtFinalPass)
1338 {
1339 Graphics g = SwingMiscManager.getIfUsingSwingGraphicsManager().getCurrentSurface();
1340
1341 if (isLoaded()) {
1342
1343 if (ExpandedTrackManager.getInstance().isAnyExpandedTrackVisible() &&
1344 !isAtFinalPass) {
1345 // If a expanded track is in view .. then must render lastly
1346 return;
1347 }
1348
1349 // Check to see if dragging over a EditableSampledTrackGraphView
1350 MouseEvent me = MouseEventRouter.getCurrentMouseEvent();
1351 if (me != null) {
1352
1353 if (me.getComponent() != fulltrackView &&
1354 me.getComponent() instanceof EditableSampledTrackGraphView &&
1355 !((EditableSampledTrackGraphView)me.getComponent()).isPlaying()) {
1356
1357
1358 Point containerPoint = SwingConversions.fromSwingPoint(SwingUtilities.convertPoint(me.getComponent(),
1359 new java.awt.Point(0,0), SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane()));
1360
1361 Shape clipBackUp = g.getClip();
1362 g.setClip(null);
1363
1364 g.setColor(SwingConversions.toSwingColor(Colour.ORANGE));
1365 ((Graphics2D)g).setStroke(EditableSampledTrackGraphView.GRAPH_BAR_STROKE);
1366 g.drawLine(
1367 containerPoint.getX() + me.getX(),
1368 containerPoint.getY(),
1369 containerPoint.getX() + me.getX(),
1370 containerPoint.getY() + me.getComponent().getHeight());
1371
1372 DisplayController.invalidateArea(new AxisAlignedBoxBounds(
1373 containerPoint.getX() + me.getX(),
1374 containerPoint.getY(),
1375 1,
1376 containerPoint.getY() + me.getComponent().getHeight() + 1));
1377
1378 // Restore clip
1379 g.setClip(clipBackUp);
1380
1381 g.setColor(SwingConversions.toSwingColor(SEMI_TRANSPARENT_FREESPACE_BACKCOLOR));
1382 g.fillRect(getX(), getY(), getWidth(), getHeight());
1383
1384 if (isAtFinalPass) { // final pass does not draw the borders... so must manually draw them
1385 g.setColor(SwingConversions.toSwingColor(Colour.BLACK));
1386 ((Graphics2D)g).setStroke(SwingConversions.toSwingStroke(FREESPACE_OUTLINING));
1387 g.drawRect(getX(), getY(), getWidth(), getHeight());
1388 }
1389
1390 return;
1391 }
1392
1393 }
1394
1395
1396
1397 }
1398
1399 super.paintInFreeSpace();
1400
1401 if (isLoaded()) {
1402
1403 Shape clipBackUp = g.getClip();
1404 Rectangle tmpClip = (clipBackUp != null) ? clipBackUp.getBounds() :
1405 new Rectangle(0, 0,
1406 SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane().getWidth(),
1407 SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane().getHeight());
1408
1409 g.setClip(tmpClip.intersection(SwingConversions.toSwingRectangle(getBounds())));
1410
1411 // Draw the name
1412 String name = getName();
1413 if (name == null) {
1414 name = "Unnamed";
1415 }
1416
1417 g.setFont(SwingMiscManager.getIfUsingSwingFontManager().getInternalFont(TrackWidgetCommons.FREESPACE_TRACKNAME_FONT));
1418 g.setColor(SwingConversions.toSwingColor(TrackWidgetCommons.FREESPACE_TRACKNAME_TEXT_COLOR));
1419
1420 // Center track name
1421 FontMetrics fm = g.getFontMetrics(SwingMiscManager.getIfUsingSwingFontManager().getInternalFont(TrackWidgetCommons.FREESPACE_TRACKNAME_FONT));
1422 Rectangle2D rect = fm.getStringBounds(name, g);
1423
1424 g.drawString(
1425 name,
1426 this.getX() + (int)((getWidth() - rect.getWidth()) / 2),
1427 this.getY() + (int)((getHeight() - rect.getHeight()) / 2) + (int)rect.getHeight()
1428 );
1429
1430 g.setClip(clipBackUp);
1431
1432 }
1433
1434 }
1435
1436
1437
1438 @Override
1439 public void paintHeavyDutyWidget(Graphics2D g)
1440 {
1441 super.paintHeavyDutyWidget(g);
1442
1443 if (isLoaded() && nameLabel != null) {
1444 nameLabel.paint();
1445
1446 if (shouldOmitIndexAudio()) {
1447
1448 int shiftOffset = SoundDesk.getInstance().isPlaying(trackMix.getChannelID()) ?
1449 -20 : 0;
1450
1451 /*IconRepository.getIcon("omitindexed.png").paintIcon(
1452 _swingComponent,
1453 g,
1454 getX() + getWidth() - EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET + shiftOffset,
1455 getY() + EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET - 16);*/
1456
1457 g.drawImage(SwingMiscManager.getIfUsingSwingImageManager().getInternalImage(IconRepository.getIcon("omitindexed.png")),
1458 getX() + getWidth() - EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET + shiftOffset,
1459 getY() + EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET - 16, null);
1460
1461 }
1462 }
1463
1464 }
1465
1466 private boolean ignoreInjection = false;
1467 private MouseEvent lastInsertME = null;
1468
1469 @Override
1470 protected void onParentStateChanged(int eventType) {
1471 super.onParentStateChanged(eventType);
1472
1473 Frame parent = null;
1474
1475 switch (eventType) {
1476
1477 // Logic for injecting audio tracks into EditableSampledTrackGraphView's
1478 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
1479 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
1480
1481 // Resume any layouts suspended by this track widget
1482 FrameLayoutDaemon.getInstance().resumeLayout(this);
1483
1484 if (trackModel != null) {
1485
1486 parent = getParentFrame();
1487
1488 // Determine new initation time according to anchored position...
1489 Mutable.Long initTime = getInitiationTimeFromMeta();
1490
1491 // If the user is restricting-y-axis movement then they might be moving
1492 // this tracks Y-position only for layout reasons as opposed to repositioning
1493 // where in the audio timeline the track should be. This must be accurate and
1494 // avoid loosing the exact initiation time due to pixel-resolutoin issues
1495 if (parent != null) {
1496
1497 boolean inferInitTime = true;
1498
1499 if (ApolloGestureActions.isYAxisRestictionOn()) {
1500 Mutable.Long ms = getInitiationTimeFromMeta();
1501 if (ms != null) {
1502 Mutable.Long timex = FrameLayoutDaemon.getInstance().getXAtMS(
1503 ms.value,
1504 parent);
1505 if (timex != null && timex.value == getX()) {
1506 initTime = ms;
1507 inferInitTime = false;
1508 }
1509 }
1510 }
1511
1512 // Also must not set initiation time if the frame is simply being displayed
1513 //inferInitTime &= (AudioFrameMouseActions.isMouseAnchoring() || AudioFrameMouseActions.isMouseStamping());
1514 inferInitTime &= (eventType == ItemParentStateChangedEvent.EVENT_TYPE_ADDED);
1515
1516 if (inferInitTime) {
1517 initTime = Mutable.createMutableLong(FrameLayoutDaemon.getInstance().getMSAtX(getX(), parent));
1518 }
1519 }
1520
1521 updateData(TrackWidgetCommons.META_INITIATIONTIME_TAG,
1522 TrackWidgetCommons.META_INITIATIONTIME_TAG + initTime);
1523
1524 // Keep TrackGraphModel consistant
1525 AudioStructureModel.getInstance().onTrackWidgetAnchored(
1526 localFileName,
1527 (parent != null) ? parent.getName() : null,
1528 initTime.value,
1529 AudioMath.framesToMilliseconds(trackModel.getFrameCount(), trackModel.getFormat()),
1530 getName(),
1531 getY());
1532
1533
1534 MouseEvent me = MouseEventRouter.getCurrentMouseEvent();
1535
1536 if (!isIgnoreInjection() && me != null && me != lastInsertME &&
1537 (me.getButton() == MouseEvent.BUTTON2 ||
1538 me.getButton() == MouseEvent.BUTTON3)) {
1539
1540 // Although widgets filter out multiple parent changed events per mouse event -
1541 // it also considers the event type, and because this it remove itself from the frame/freespace
1542 // it jumbles up the filtering so that this will be invoked for each corner.
1543 lastInsertME = me;
1544
1545 if (me.getComponent() != fulltrackView &&
1546 me.getComponent() instanceof EditableSampledTrackGraphView) {
1547
1548 EditableSampledTrackGraphView editableSampledTrack =
1549 (EditableSampledTrackGraphView)me.getComponent();
1550
1551 // Inject this into the widget
1552 try {
1553 injectAudio(editableSampledTrack, me.getX(), true);
1554 } catch (IOException ex) {
1555 ex.printStackTrace();
1556 }
1557
1558 }
1559
1560 } else if (!isIgnoreInjection() && me == lastInsertME) {
1561 // Note due to a injection removing this widget while in the midst of
1562 // anchoring, the widget parent event filtering will not work thus
1563 // must keep removing self until the mouse event has done.
1564 removeSelf();
1565 }
1566
1567 // Wakeup the daemon to note that it should recheck -- in the case that a track is
1568 // added to a non-overdubbed frame the audio structure model will not bother
1569 // adding the track until something requests for it.
1570 FrameLayoutDaemon.getInstance().forceRecheck();
1571 }
1572
1573 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
1574 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
1575
1576
1577/*
1578 // Listen for volume or mute changed events
1579 trackMix.addObserver(this);
1580
1581 // Listen for solo events and track sequence creation events
1582 MixDesk.getInstance().addObserver(this);
1583 */
1584
1585 break;
1586
1587
1588 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
1589 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY:
1590
1591 // If yanking this from the frame into free space suspend the layout daemon
1592 // for this frame
1593 if (MouseEventRouter.getCurrentMouseEvent() != null &&
1594 MouseEventRouter.getCurrentMouseEvent().getButton() == MouseEvent.BUTTON2 &&
1595 MouseEventRouter.getCurrentMouseEvent() != lastInsertME) {
1596 Frame suspended = DisplayController.getCurrentFrame();
1597 if (suspended != null) {
1598 FrameLayoutDaemon.getInstance().suspendLayout(suspended, this);
1599 }
1600 }
1601
1602 if (trackModel != null) {
1603 parent = getParentFrame();
1604 // Keep TrackGraphModel consistant
1605 AudioStructureModel.getInstance().onTrackWidgetRemoved(localFileName,
1606 (parent != null) ? parent.getName() : null);
1607 }
1608
1609 case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
1610 /*
1611 trackMix.removeObserver(this);
1612 MixDesk.getInstance().removeObserver(this);*/
1613
1614 break;
1615
1616 }
1617
1618 }
1619
1620 /**
1621 * Injects this widgets bytes into a EditableSampledTrackGraphView
1622 * and removes this widget from freespace/parent-frame.
1623 *
1624 * Doesn't inject bytes if the target EditableSampledTrackGraphView is in a playing state.
1625 *
1626 * @param target
1627 * The target EditableSampledTrackGraphView to inject this widgets bytes into
1628 *
1629 * @param graphX
1630 * The X pixel in the target's graph. Must be in valid range.
1631 *
1632 * @param destroySelf
1633 * If want to destroy this widget
1634 *
1635 * @throws IOException
1636 * If the insert failed ... can occur if bytes need to be
1637 * converted into targets format.
1638 */
1639 public void injectAudio(EditableSampledTrackGraphView target, int graphX, boolean destroySelf)
1640 throws IOException {
1641 assert(target != null);
1642 assert(graphX >= 0);
1643 assert(graphX <= target.getWidth());
1644
1645 // Cannot inject into EditableSampledTrackGraphView's while playing, although
1646 // won't break anything, this would be confusing for the user.
1647 if (target.isPlaying()) {
1648 return;
1649 }
1650
1651 // Inject the audio at the graph poistion
1652 int insertFramePoint = target.frameAtX(graphX);
1653
1654 // Inject audio
1655 target.insertAudio(
1656 trackModel.getAllAudioBytes(),
1657 trackModel.getFormat(),
1658 insertFramePoint);
1659
1660 // Note: if removed from free space .. then there should be no
1661 // more references to this item therefore the memory will be freed
1662 // eventually after this invoke
1663 if (destroySelf) {
1664 removeSelf();
1665 }
1666 }
1667
1668 /**
1669 *
1670 * @return
1671 * The unique loval filename for this track. Auto-assigned - and rememered. Never null.
1672 */
1673 public String getLocalFileName() {
1674 return localFileName;
1675 }
1676
1677 /**
1678 * Determines the running time from the raw audio in memory.
1679 *
1680 * @return
1681 * The running time or this track in MS.
1682 * -1 if not loaded.
1683 */
1684 public long getRunningMSTimeFromRawAudio()
1685 {
1686 if (this.trackModel != null) {
1687 return AudioMath.framesToMilliseconds(trackModel.getFrameCount(), trackModel.getFormat());
1688 }
1689
1690 return -1;
1691 }
1692
1693 /**
1694 * Determines the running time from the meta data.
1695 *
1696 * @return
1697 * The running time or this track in MS.
1698 * -1 if unavilable.
1699 */
1700 public long getRunningMSTimeFromMeta() {
1701 return getStrippedDataLong(TrackWidgetCommons.META_RUNNINGMSTIME_TAG, new Long(-1));
1702 }
1703
1704 /**
1705 * @return
1706 * The name given to this widget... can be null.
1707 */
1708 @Override
1709 public String getName() {
1710 if (this.trackModel != null) {
1711 return trackModel.getName();
1712 } else if (this.nameLabel != null) {
1713 return nameLabel.getText();
1714 }
1715 return getStrippedDataString(TrackWidgetCommons.META_NAME_TAG);
1716 }
1717
1718 /**
1719 * Determines the initiation time from the meta data.
1720 *
1721 * @return
1722 * The initiation time or this track in MS.
1723 * null if unavilable.
1724 */
1725 public Mutable.Long getInitiationTimeFromMeta() {
1726 Long l = getStrippedDataLong(TrackWidgetCommons.META_INITIATIONTIME_TAG, null);
1727 if (l != null) {
1728 return Mutable.createMutableLong(l.longValue());
1729 }
1730 return null;
1731 }
1732
1733 /**
1734 * Adjusts the initiation time for this track - to the exact millisecond.
1735 *
1736 * The x-position is updated (which will be eventually done with possibly better
1737 * accuracy if the layout daemon is on)
1738 *
1739 * @param specificInitTime
1740 * The new initiation time for this track in milliseconds
1741 */
1742 public void setInitiationTime(long specificInitTime) {
1743
1744 Frame parent = getParentFrame();
1745
1746 // Update x position if it can
1747 if (parent != null) {
1748 Timeline tl = FrameLayoutDaemon.getInstance().getTimeline(parent);
1749 if (tl != null) {
1750 this.setPosition(tl.getXAtMSTime(specificInitTime), getY());
1751 }
1752 }
1753
1754 updateData(TrackWidgetCommons.META_INITIATIONTIME_TAG,
1755 TrackWidgetCommons.META_INITIATIONTIME_TAG + specificInitTime);
1756
1757 AudioStructureModel.getInstance().onTrackWidgetPositionChanged(
1758 localFileName, (parent != null) ? parent.getName() : null, specificInitTime, getY());
1759
1760 }
1761
1762 /**
1763 * Sets Y position - retaining the init time.
1764 * @param newY
1765 */
1766 public void setYPosition(int newY) {
1767
1768 if (getY() == newY || isFloating()) {
1769 return;
1770 }
1771
1772 Frame parent = getParentFrame();
1773
1774 Mutable.Long initTime = getInitiationTimeFromMeta();
1775 if (initTime == null) {
1776 return;
1777 }
1778
1779 setPosition(getX(), newY);
1780
1781 AudioStructureModel.getInstance().onTrackWidgetPositionChanged(
1782 localFileName, (parent != null) ? parent.getName() : null, initTime.value, newY);
1783
1784 }
1785
1786
1787 private void updateBorderColor() {
1788
1789 // Get border color currently used
1790 Colour oldC = getSource().getBorderColor();
1791
1792 Colour newC = TrackWidgetCommons.getBorderColor(
1793 SoundDesk.getInstance().isSolo(trackMix.getChannelID()),
1794 trackMix.isMuted());
1795
1796 // Update the color
1797 if (!newC.equals(oldC)) {
1798 setWidgetEdgeColor(newC);
1799 }
1800 }
1801
1802 /**
1803 * State icons live at the top right corner
1804 *
1805 */
1806 private void invalidateStateIcons() {
1807 this.invalidateSelf(); // TODO
1808 }
1809
1810 private void setShouldOmitIndexAudio(boolean shouldOmit) {
1811 this.shouldOmitAudioIndexing = shouldOmit;
1812 if (!shouldOmit) {
1813 removeData(META_DONT_INDEX_AUDIO_TAG);
1814 } else {
1815 addDataIfCaseInsensitiveNotExists(META_DONT_INDEX_AUDIO_TAG);
1816 }
1817 invalidateStateIcons();
1818 }
1819
1820 public boolean shouldOmitIndexAudio() {
1821 return shouldOmitAudioIndexing;
1822 }
1823
1824// /**
1825// * Invalidates.
1826// * @param shouldLayout
1827// */
1828// private void setShouldLayout(boolean shouldLayout) {
1829//
1830// if (shouldLayout) {
1831// removeData(TrackWidgetCommons.META_OMIT_LAYOUT_TAG);
1832// } else {
1833// addDataIfCaseInsensitiveNotExists(TrackWidgetCommons.META_OMIT_LAYOUT_TAG);
1834// }
1835//
1836// invalidateStateIcons();
1837// }
1838//
1839
1840//
1841// public boolean shouldLayout() {
1842//
1843// return (!containsDataTrimmedIgnoreCase(TrackWidgetCommons.META_OMIT_LAYOUT_TAG));
1844// }
1845
1846 private boolean split(boolean extractSelection) {
1847
1848 // First is operation valid?
1849 if (trackModel != null && trackModel.getSelectionLength() > 1
1850 && !fulltrackView.isPlaying()
1851 && (trackModel.getFrameCount() - trackModel.getSelectionLength()) > EditableSampledTrackGraphView.MIN_FRAME_SELECTION_SIZE) {
1852
1853 // If so... can a slip be performed? i.e. is there unselected audio to
1854 // the left and right of selection
1855 if (trackModel.getSelectionStart() > 0 &&
1856 (trackModel.getSelectionStart() + trackModel.getSelectionLength()) < trackModel.getFrameCount()) {
1857
1858 // Perform split
1859 int rightSideStartFrame = trackModel.getSelectionStart() + trackModel.getSelectionLength();
1860
1861 // Create a new track widget to contain the right-side audio
1862 byte[] rightsideAudio = new byte[(trackModel.getFrameCount() - rightSideStartFrame) * trackModel.getFormat().getFrameSize()];
1863
1864 // Copy bytes into new location
1865 System.arraycopy(
1866 trackModel.getAllAudioBytes(),
1867 rightSideStartFrame * trackModel.getFormat().getFrameSize(),
1868 rightsideAudio,
1869 0, rightsideAudio.length);
1870
1871 byte[] selectedBytes = (extractSelection) ? trackModel.getSelectedFramesCopy() : null;
1872
1873 // Let this widget keep the left-side audio
1874 trackModel.setSelection(trackModel.getSelectionStart(), trackModel.getFrameCount() - trackModel.getSelectionStart());
1875 trackModel.removeSelectedBytes();
1876 trackModel.setSelection(0,0);
1877
1878 // Build the new neighbouring widget
1879 Frame target = getParentFrame();
1880 if (target == null) {
1881 target = DisplayController.getCurrentFrame();
1882 }
1883
1884 // Determine init time
1885 Mutable.Long initTime = getInitiationTimeFromMeta();
1886
1887 if (initTime == null) {
1888 initTime = Mutable.createMutableLong(0);
1889 }
1890
1891 initTime.value += AudioMath.framesToMilliseconds(rightSideStartFrame, trackModel.getFormat());
1892
1893 SampledTrack rightSideTrack = SampledTrack.createFromMemory(
1894 rightsideAudio,
1895 trackModel.getFormat(),
1896 target,
1897 0, // X Coord overridden
1898 getY(),
1899 getName() + " part",
1900 trackMix);
1901
1902 // Anchor it
1903 rightSideTrack.setIgnoreInjection(true);
1904 target.addAllItems(rightSideTrack.getItems());
1905 rightSideTrack.setIgnoreInjection(false);
1906
1907 // Adjust initiation time to be exact
1908 rightSideTrack.setInitiationTime(initTime.value);
1909
1910 // If extracting audio then attatch it to the cursor
1911 if (selectedBytes != null) {
1912 assert(extractSelection);
1913
1914 SampledTrack extractedTrack = SampledTrack.createFromMemory(
1915 selectedBytes,
1916 trackModel.getFormat(),
1917 target,
1918 getX(), // X Coord overridden
1919 getY(),
1920 getName() + " part",
1921 trackMix);
1922
1923 StandardGestureActions.pickup(extractedTrack.getItems());
1924
1925 }
1926 }
1927
1928 return true;
1929 }
1930
1931 return false;
1932
1933 }
1934 /**
1935 * Doesn't expand if not anchored ...
1936 * @param addToExpand
1937 */
1938 private void expand(boolean addToExpand) {
1939
1940 Frame parent = getParentFrame();
1941 String pfname = (parent != null) ? parent.getName() : null;
1942 if (pfname == null) {
1943 return;
1944 }
1945
1946 if (!ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel)) {
1947
1948 if (addToExpand) {
1949
1950 // Show the expanded view for this track once control has been released
1951 ExpandedTrackManager.getInstance().addTrackToSelection(
1952 trackModel,
1953 pfname,
1954 SwingConversions.fromSwingRectangle(_swingComponent.getBounds()),
1955 trackMix);
1956
1957 } else {
1958
1959 // Get rid of all popups
1960 PopupManager.getInstance().hideAutoHidePopups();
1961
1962 int start = trackModel.getSelectionStart();
1963 int length = trackModel.getSelectionLength();
1964
1965 if (length <= 1) {
1966 start = 0;
1967 length = trackModel.getFrameCount();
1968 }
1969
1970 // Show the expanded view for this track
1971 ExpandedTrackManager.getInstance().expandSingleTrack(
1972 trackModel,
1973 SwingConversions.fromSwingRectangle(_swingComponent.getBounds()),
1974 trackMix,
1975 pfname,
1976 start,
1977 length
1978 );
1979
1980 }
1981
1982 } else {
1983
1984 // Take away track from being selected.
1985 ExpandedTrackManager.getInstance().removeTrackFromSelection(trackModel);
1986 }
1987
1988 }
1989
1990 /**
1991 * The small popup for common actions.
1992 *
1993 * @author Brook Novak
1994 *
1995 */
1996 private class PlaybackPopup extends PlaybackControlPopup implements Observer
1997 {
1998 public PlaybackPopup()
1999 {
2000 miscButton.setActionCommand("expand");
2001 SwingMiscManager.setJButtonIcon(miscButton, IconRepository.getIcon("expand.png"));
2002 miscButton.setToolTipText("Expand");
2003 }
2004
2005 @Override
2006 public void onHide()
2007 {
2008 // Listen for volume or mute changed events
2009 trackMix.removeObserver(this);
2010
2011 // Listen for solo events and track sequence creation events
2012 SoundDesk.getInstance().removeObserver(this);
2013 }
2014
2015 @Override
2016 public void onShow()
2017 {
2018 // Listen for volume or mute changed events
2019 trackMix.addObserver(this);
2020
2021 // Listen for solo events and track sequence creation events
2022 SoundDesk.getInstance().addObserver(this);
2023 updateVolume((int)(100 * trackMix.getVolume()));
2024 updateMute(trackMix.isMuted());
2025 updateSolo(SoundDesk.getInstance().isSolo(trackMix.getChannelID()));
2026 }
2027
2028 @Override
2029 public void actionPerformed(ActionEvent e) {
2030 if (trackModel == null) {
2031 return;
2032 }
2033
2034 if (e.getSource() == playPauseButton) {
2035
2036 try {
2037
2038 playPauseResume();
2039
2040 } catch (LineUnavailableException e1) {
2041 e1.printStackTrace();
2042 }
2043
2044 } else if (e.getSource() == stopButton) {
2045
2046 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
2047
2048 // reset any paused mark
2049 SoundDesk.getInstance().setPaused(trackMix.getChannelID(), false);
2050
2051 if (ts != null &&
2052 ts.isPlaying()) {
2053 // Stop playback
2054 ApolloPlaybackMixer.getInstance().stop(ts);
2055 }
2056
2057 } else if (e.getSource() == rewindButton) {
2058
2059 trackModel.setSelection(0, 0);
2060 SoundDesk.getInstance().setPaused(trackMix.getChannelID(), false);
2061
2062 } else if (e.getSource() == miscButton) {
2063 expand(false);
2064 }
2065 }
2066
2067 private void playPauseResume() throws LineUnavailableException {
2068 if (!SoundDesk.getInstance().isPlaying(trackMix.getChannelID())) { // play / resume
2069
2070 int startFrame = -1, endFrame = -1;
2071
2072 // Resume playback?
2073 if (SoundDesk.getInstance().isPaused(trackMix.getChannelID())) {
2074 startFrame = SoundDesk.getInstance().getLastPlayedFramePosition(trackMix.getChannelID());
2075 if (startFrame >= 0 && startFrame < trackModel.getFrameCount()) {
2076
2077 // The user may have edited the audio track and reselected it
2078 // since the last pause. Thus select an appropriate end frame
2079 endFrame = (trackModel.getSelectionLength() > 1) ?
2080 trackModel.getSelectionStart() + trackModel.getSelectionLength():
2081 trackModel.getFrameCount() - 1;
2082
2083 // Changed selection? it play range invalid?
2084 if (endFrame <= startFrame || startFrame < trackModel.getSelectionStart()) {
2085 startFrame = -1; // Play new selection (see below)
2086
2087 } else if (endFrame >= trackModel.getFrameCount()) {
2088 endFrame = trackModel.getFrameCount() - 1;
2089 }
2090
2091 }
2092 }
2093
2094 // Play from beginning of selection to end of selection
2095 if (startFrame < 0) {
2096 startFrame = trackModel.getSelectionStart();
2097 endFrame = (trackModel.getSelectionLength() > 1) ?
2098 startFrame + trackModel.getSelectionLength():
2099 trackModel.getFrameCount() - 1;
2100 }
2101
2102 // Safety clamp:
2103 if (endFrame >= trackModel.getFrameCount()) {
2104 endFrame = trackModel.getFrameCount() - 1;
2105 }
2106
2107 if (startFrame < endFrame) {
2108 SoundDesk.getInstance().playSampledTrackModel(
2109 trackModel,
2110 trackMix.getChannelID(),
2111 startFrame,
2112 endFrame,
2113 0);
2114 }
2115
2116 } else { // pause
2117
2118 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
2119
2120 if (ts != null &&
2121 ts.isPlaying()) {
2122
2123 // Mark channel as paused.
2124 SoundDesk.getInstance().setPaused(trackMix.getChannelID(), true);
2125
2126 // Stop playback for this channel
2127 ApolloPlaybackMixer.getInstance().stop(ts);
2128
2129 }
2130
2131 }
2132 }
2133
2134 @Override
2135 public Subject getObservedSubject()
2136 {
2137 return null;
2138 }
2139
2140 @Override
2141 public void setObservedSubject(Subject parent)
2142 {
2143 }
2144
2145 /**
2146 * Receives events from the track model OR from the observed track sequence.
2147 */
2148 @Override
2149 public void modelChanged(Subject source, SubjectChangedEvent event)
2150 {
2151 // Synch GUI with track state
2152 switch (event.getID()) {
2153
2154 case ApolloSubjectChangedEvent.TRACK_SEQUENCE_CREATED: // from sound desk
2155
2156 if (event.getState().equals(trackMix.getChannelID())) {
2157 // The channel being played is the same as this one ...
2158 // even if the track model is unloaded must enter into a playing state
2159 // if the created track sequence will play
2160 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
2161 assert(ts != null);
2162 assert(!ts.hasFinished());
2163 assert(!ts.isPlaying());
2164 ts.addObserver(this);
2165 }
2166
2167 break;
2168
2169 case ApolloSubjectChangedEvent.PLAYBACK_STARTED: // From observed track sequence
2170 stopButton.setEnabled(true);
2171 rewindButton.setEnabled(false);
2172 SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("pause.png"));
2173
2174 invalidateStateIcons();
2175
2176 SampledTrack.this.setWidgetEdgeThickness(TrackWidgetCommons.PLAYING_TRACK_EDGE_THICKNESS);
2177 //FrameGraphics.refresh(true);
2178 break;
2179
2180 case ApolloSubjectChangedEvent.PLAYBACK_STOPPED: // From observed track sequence
2181
2182 invalidateStateIcons();
2183
2184 rewindButton.setEnabled(true);
2185 stopButton.setEnabled(false);
2186 SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("play.png"));
2187
2188 // Note:
2189 // No need to remove self from observing the dead track since the track references this
2190 // and will get garbage collected
2191
2192 SampledTrack.this.setWidgetEdgeThickness(TrackWidgetCommons.STOPPED_TRACK_EDGE_THICKNESS);
2193
2194 break;
2195
2196 case ApolloSubjectChangedEvent.PAUSE_MARK_CHANGED: // When stopped or paused
2197 /*
2198 if (ae.getState().equals(trackMix.getChannelID())) {
2199
2200 if (MixDesk.getInstance().isPaused(trackMix.getChannelID())) {
2201 // Do nothing .. the paused mark is set prior to a stop
2202 } else {
2203 // Esnure that the GUI represents a stopped state
2204 stopButton.setEnabled(false);
2205 playPauseButton.setIcon(IconRepository.getIcon("play.png"));
2206 }
2207
2208 }*/
2209
2210 break;
2211
2212 case ApolloSubjectChangedEvent.VOLUME: // From obseved track mix
2213 updateVolume((int)(100 * trackMix.getVolume()));
2214 break;
2215
2216 case ApolloSubjectChangedEvent.MUTE: // From obseved track mix
2217 updateMute(trackMix.isMuted());
2218 updateBorderColor();
2219 break;
2220
2221 case ApolloSubjectChangedEvent.SOLO_PREFIX_CHANGED: // From mix desk
2222 updateSolo(SoundDesk.getInstance().isSolo(trackMix.getChannelID()));
2223 updateBorderColor();
2224 break;
2225 }
2226
2227
2228
2229 }
2230
2231 @Override
2232 protected void volumeChanged()
2233 {
2234 trackMix.setVolume((volumeSlider.getValue()) / 100.0f);
2235 }
2236
2237 @Override
2238 protected void muteChanged()
2239 {
2240 trackMix.setMuted(muteButton.isSelected());
2241 }
2242
2243 @Override
2244 protected void soloChanged()
2245 {
2246 SoundDesk.getInstance().setSoloIDPrefix(soloButton.isSelected() ?
2247 trackMix.getChannelID() : null
2248 );
2249 }
2250 }
2251
2252 @Override
2253 public boolean isWidgetEdgeThicknessAdjustable()
2254 {
2255 return false;
2256 }
2257
2258 public boolean isIgnoreInjection() {
2259 return ignoreInjection;
2260 }
2261
2262 public void setIgnoreInjection(boolean ignoreInjection) {
2263 this.ignoreInjection = ignoreInjection;
2264 }
2265}
Note: See TracBrowser for help on using the repository browser.