source: trunk/src/org/apollo/widgets/SampledTrack.java@ 1516

Last change on this file since 1516 was 1516, checked in by bnemhaus, 4 years ago

Fixed an issue with saving (and subsequent loading) of new SampledTrack widgets.

File size: 64.6 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.getFilepath().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 Frame parentFrame = this.getParentFrame();
971 File audioResource = ResourceManager.getAudioUnusedFilename(localFileName, parentFrame);
972 trackModel.setFilepath(audioResource.getAbsolutePath());
973 loadFilenameArgument = localFileName; // set to now local, next load will be local
974 }
975
976 // Save audio bytes.
977 try {
978
979 AudioIO.savePCMAudioToWaveFile(
980 trackModel.getFilepath(),
981 trackModel.getAllAudioBytes(), // Safe: arrays are immutable
982 trackModel.getFormat());
983
984 // Reset modified flag
985 trackModel.setAudioModifiedFlag(false);
986
987 } catch (IOException e) {
988 e.printStackTrace();
989 } catch (UnsupportedAudioFileException e) {
990 e.printStackTrace();
991 }
992
993 // Ensure audio bytes can be collected if this has expired
994 if (isExpired()) {
995
996 try {
997 SwingUtilities.invokeAndWait(new Runnable() {
998 @Override
999 public void run() {
1000 releaseMemory(true);
1001 }
1002 });
1003 } catch (InterruptedException e) {
1004 e.printStackTrace();
1005 } catch (InvocationTargetException e) {
1006 e.printStackTrace();
1007 }
1008
1009 }
1010
1011
1012 }
1013
1014 @Override
1015 public void onDelete() {
1016 super.onDelete();
1017 // Its nice to keep everything layed out as much as possible
1018 FrameLayoutDaemon.getInstance().resumeLayout(this);
1019 }
1020
1021 @Override
1022 protected void unloadWidgetData() {
1023
1024 // Still needs to save?
1025 if (doesNeedSaving() || trackModel == null)
1026 {
1027 return; // Release memory later when saved
1028 }
1029
1030 try {
1031 // Release memory - on swing thread to avoid nullified model data wil painting / editing.
1032 SwingUtilities.invokeAndWait(new Runnable() {
1033 @Override
1034 public void run() {
1035 releaseMemory(true);
1036 }
1037 });
1038 } catch (InterruptedException e) {
1039 e.printStackTrace();
1040 } catch (InvocationTargetException e) {
1041 e.printStackTrace();
1042 }
1043
1044
1045 }
1046
1047 @Override
1048 protected void tempUnloadWidgetData() { // basically this is deleting
1049
1050 // Need unloading?
1051 if (trackModel == null) {
1052 return;
1053 }
1054
1055 // Get rid of old temporary files
1056 if (recoveryFile != null && recoveryFile.exists()) {
1057 recoveryFile.delete();
1058 }
1059
1060 // Get rid of local file
1061 //File oldLocalFile = new File(AudioPathManager.AUDIO_HOME_DIRECTORY + localFileName);
1062 File oldLocalFile = ResourceManager.getAudioResource(localFileName, getParentFrame());
1063
1064 if (oldLocalFile == null) {
1065 //It was never saved and therefore does not need to be deleted.
1066 return;
1067 }
1068
1069 if (oldLocalFile.isFile() && oldLocalFile.exists()) {
1070 try {
1071 oldLocalFile.delete();
1072 } catch(Exception e) {
1073 e.printStackTrace();
1074 }
1075 }
1076
1077 // Dump memory to a temp file
1078 try {
1079
1080 // Always be unique
1081 String uniqueID = AudioPathManager.generateLocateFileName("wav");
1082 recoveryFile = File.createTempFile("APOLLO_BACKUP" + uniqueID, null);
1083
1084 // To avoid filling up the end-users hardisk, mark file to be
1085 // deleted on exit (if not already deleted).
1086 recoveryFile.deleteOnExit();
1087
1088 } catch (IOException e) {
1089 e.printStackTrace();
1090 return; // cannot dump ... must keep in memory.
1091 }
1092
1093 // Dump audio to file
1094 try {
1095 AudioIO.savePCMAudioToWaveFile(
1096 recoveryFile.getAbsolutePath(),
1097 trackModel.getAllAudioBytes(),
1098 trackModel.getFormat());
1099 } catch (IOException e1) {
1100 e1.printStackTrace();
1101 return; // cannot dump ... must keep in memory.
1102 } catch (UnsupportedAudioFileException e1) {
1103 e1.printStackTrace();
1104 return; // cannot dump ... must keep in memory.
1105 }
1106
1107 try {
1108 // Release memory - on swing thread to avoid nullified model data wil painting / editing.
1109 SwingUtilities.invokeAndWait(new Runnable() {
1110 @Override
1111 public void run() {
1112 releaseMemory(false);
1113 }
1114 });
1115 } catch (InterruptedException e) {
1116 e.printStackTrace();
1117 } catch (InvocationTargetException e) {
1118 e.printStackTrace();
1119 }
1120
1121 }
1122
1123 /**
1124 * To be called by the swing thread only
1125 * This is nessessary to avoid runtime memory leaks!!
1126 */
1127 private void releaseMemory(boolean onlyIfExpired) {
1128
1129 if (!onlyIfExpired || (onlyIfExpired && isExpired())) {
1130
1131 if (trackModel != null) {
1132
1133 trackModel.removeObserver(fulltrackView);
1134 trackModel.removeObserver(this);
1135 trackModel = null;
1136
1137 assert (fulltrackView.getObservedSubject() == null);
1138 assert (fulltrackView.getMix() == null);
1139 }
1140
1141 fulltrackView.releaseBuffer();
1142
1143 ExpandedTrackManager.getInstance().removeObserver(this);
1144
1145 }
1146
1147 }
1148
1149 /**
1150 * Global Track model re-use ....
1151 */
1152 @Override
1153 public SampledTrackModel getSharedSampledTrackModel(String localfilename) {
1154 if (trackModel != null &&
1155 trackModel.getLocalFilename().equals(localfilename)) {
1156 assert(localfilename.equals(localFileName));
1157 return trackModel;
1158 }
1159 return null;
1160
1161 }
1162
1163 /**
1164 * @see #getAudioFormat()
1165 *
1166 * @return
1167 * The audio bytes for this track widget. Null if not loaded.
1168 */
1169 public byte[] getAudioBytes() {
1170 return (trackModel != null) ? trackModel.getAllAudioBytes() : null;
1171 }
1172
1173 /**
1174 * @see #getAudioBytes()
1175 *
1176 * @return
1177 * The audio format of the audio bytes for this track widget. Null if not loaded.
1178 */
1179 public AudioFormat getAudioFormat() {
1180 return (trackModel != null) ? trackModel.getFormat() : null;
1181 }
1182
1183 /**
1184 * <b>Warning</b> if the path happens to be a path of a recovery file
1185 * then it is deleted if the widget is reloaded.
1186 *
1187 * @return
1188 * A full filepath to load the audio from. Never null. The path
1189 * may lead to a out of date file or no file.
1190 */
1191 public String getLatestSavedAudioPath() {
1192
1193 if (recoveryFile != null && recoveryFile.exists()) {
1194 return recoveryFile.getAbsolutePath();
1195 }
1196
1197 File audioResource = ResourceManager.getAudioResource(localFileName, getParentFrame());
1198 return audioResource.getAbsolutePath();
1199
1200
1201 }
1202
1203 @Override
1204 public void onGraphDirty(SampledTrackGraphView graph, Rectangle dirty)
1205 {
1206 dirty.translate(getX(), getY() - 1);
1207 DisplayController.invalidateArea(SwingConversions.fromSwingRectangle(dirty));
1208 DisplayController.requestRefresh(true);
1209 }
1210
1211 @Override
1212 public Subject getObservedSubject() {
1213 return null;
1214 }
1215
1216
1217 @Override
1218 protected void onSizeChanged()
1219 {
1220 super.onSizeChanged();
1221 // Keep meta as constant as possible for best reults
1222 updateData(TrackWidgetCommons.META_LAST_WIDTH_TAG, TrackWidgetCommons.META_LAST_WIDTH_TAG + getWidth());
1223 }
1224
1225 /**
1226 * Responds to model changed events by updating the GUI ...
1227 */
1228 @Override
1229 public void modelChanged(Subject source, SubjectChangedEvent event) {
1230
1231 // If the expansion selection has changed - check to see if was for this
1232 if (source == ExpandedTrackManager.getInstance()) {
1233
1234 // Show graph as being selected if expanded / pending to expand.
1235 if (trackModel != null &&
1236 ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel)) {
1237 fulltrackView.setBackColor(new Color(100, 100, 100), new Color(120, 120, 120));
1238 } else {
1239 fulltrackView.setBackColor(SampledTrackGraphView.DEFAULT_BACKGROUND_COLOR, SampledTrackGraphView.DEFAULT_BACKGROUND_HIGHTLIGHTS_COLOR);
1240 }
1241
1242 return;
1243 }
1244
1245 Frame parent = null;
1246
1247 switch (event.getID()) {
1248
1249 case ApolloSubjectChangedEvent.LOAD_STATUS_REPORT:
1250 // If this widget is loading - then update the load status
1251 if (isInLoadProgress()) {
1252
1253 // If the load has been cancelled, then cancel the loader
1254 if (hasCancelBeenRequested()) {
1255
1256 AudioFileLoader loader = (AudioFileLoader)source;
1257 loader.cancelLoad();
1258
1259 } else {
1260 float perc = ((Float)event.getState()).floatValue();
1261 if (perc > 1.0f) {
1262 perc = 1.0f;
1263 }
1264 perc *= FILE_LOADING_PERCENT_RANGE; // Dont stretch load to all of bar - still will have more work to do
1265 updateLoadPercentage(perc);
1266 }
1267
1268 }
1269
1270 break;
1271
1272 case ApolloSubjectChangedEvent.NAME_CHANGED:
1273 if (trackModel != null && !nameLabel.getText().equals(trackModel.getName())) {
1274 nameLabel.setText(trackModel.getName());
1275 }
1276
1277 // Get graph model consistant
1278 parent = getParentFrame();
1279 String pfname = (parent != null) ? parent.getName() : null;
1280
1281 AudioStructureModel.getInstance().onTrackWidgetNameChanged(
1282 localFileName,
1283 pfname,
1284 getName());
1285
1286 break;
1287
1288 case ApolloSubjectChangedEvent.AUDIO_INSERTED:
1289 case ApolloSubjectChangedEvent.AUDIO_REMOVED:
1290 long newRunningTime = getRunningMSTimeFromRawAudio();
1291 assert(newRunningTime > 0);
1292
1293 long oldRunningTime = getRunningMSTimeFromMeta();
1294
1295 // Keep meta as constant as possible for best reults
1296 updateData(TrackWidgetCommons.META_RUNNINGMSTIME_TAG, TrackWidgetCommons.META_RUNNINGMSTIME_TAG + newRunningTime);
1297
1298 if (trackModel != null) {
1299
1300 parent = getParentFrame();
1301
1302 // Keep TrackGraphModel consistant
1303 AudioStructureModel.getInstance().onTrackWidgetAudioEdited(
1304 localFileName,
1305 (parent != null) ? parent.getName() : null,
1306 newRunningTime);
1307
1308 if (trackModel.getSelectionStart() == 0 && oldRunningTime > newRunningTime) {
1309
1310
1311 Mutable.Long inittime = getInitiationTimeFromMeta();
1312 if (inittime == null) {
1313 inittime = Mutable.createMutableLong(0);
1314 }
1315 inittime.value += (oldRunningTime - newRunningTime);
1316
1317 updateData(TrackWidgetCommons.META_INITIATIONTIME_TAG,
1318 TrackWidgetCommons.META_INITIATIONTIME_TAG + inittime);
1319
1320 AudioStructureModel.getInstance().onTrackWidgetRemoved(localFileName, (parent != null) ? parent.getName() : null);
1321 AudioStructureModel.getInstance().onTrackWidgetAnchored(localFileName, (parent != null) ? parent.getName() : null,
1322 inittime.value, newRunningTime, getName(), getY());
1323 }
1324
1325 }
1326
1327 break;
1328
1329
1330 }
1331
1332 }
1333
1334 @Override
1335 public void setObservedSubject(Subject parent) {
1336 }
1337
1338 @Override
1339 public void paintInFreeSpace()
1340 {
1341 paintInFreeSpace(false);
1342 }
1343
1344 public void paintInFreeSpace(boolean isAtFinalPass)
1345 {
1346 Graphics g = SwingMiscManager.getIfUsingSwingGraphicsManager().getCurrentSurface();
1347
1348 if (isLoaded()) {
1349
1350 if (ExpandedTrackManager.getInstance().isAnyExpandedTrackVisible() &&
1351 !isAtFinalPass) {
1352 // If a expanded track is in view .. then must render lastly
1353 return;
1354 }
1355
1356 // Check to see if dragging over a EditableSampledTrackGraphView
1357 MouseEvent me = MouseEventRouter.getCurrentMouseEvent();
1358 if (me != null) {
1359
1360 if (me.getComponent() != fulltrackView &&
1361 me.getComponent() instanceof EditableSampledTrackGraphView &&
1362 !((EditableSampledTrackGraphView)me.getComponent()).isPlaying()) {
1363
1364
1365 Point containerPoint = SwingConversions.fromSwingPoint(SwingUtilities.convertPoint(me.getComponent(),
1366 new java.awt.Point(0,0), SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane()));
1367
1368 Shape clipBackUp = g.getClip();
1369 g.setClip(null);
1370
1371 g.setColor(SwingConversions.toSwingColor(Colour.ORANGE));
1372 ((Graphics2D)g).setStroke(EditableSampledTrackGraphView.GRAPH_BAR_STROKE);
1373 g.drawLine(
1374 containerPoint.getX() + me.getX(),
1375 containerPoint.getY(),
1376 containerPoint.getX() + me.getX(),
1377 containerPoint.getY() + me.getComponent().getHeight());
1378
1379 DisplayController.invalidateArea(new AxisAlignedBoxBounds(
1380 containerPoint.getX() + me.getX(),
1381 containerPoint.getY(),
1382 1,
1383 containerPoint.getY() + me.getComponent().getHeight() + 1));
1384
1385 // Restore clip
1386 g.setClip(clipBackUp);
1387
1388 g.setColor(SwingConversions.toSwingColor(SEMI_TRANSPARENT_FREESPACE_BACKCOLOR));
1389 g.fillRect(getX(), getY(), getWidth(), getHeight());
1390
1391 if (isAtFinalPass) { // final pass does not draw the borders... so must manually draw them
1392 g.setColor(SwingConversions.toSwingColor(Colour.BLACK));
1393 ((Graphics2D)g).setStroke(SwingConversions.toSwingStroke(FREESPACE_OUTLINING));
1394 g.drawRect(getX(), getY(), getWidth(), getHeight());
1395 }
1396
1397 return;
1398 }
1399
1400 }
1401
1402
1403
1404 }
1405
1406 super.paintInFreeSpace();
1407
1408 if (isLoaded()) {
1409
1410 Shape clipBackUp = g.getClip();
1411 Rectangle tmpClip = (clipBackUp != null) ? clipBackUp.getBounds() :
1412 new Rectangle(0, 0,
1413 SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane().getWidth(),
1414 SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane().getHeight());
1415
1416 g.setClip(tmpClip.intersection(SwingConversions.toSwingRectangle(getBounds())));
1417
1418 // Draw the name
1419 String name = getName();
1420 if (name == null) {
1421 name = "Unnamed";
1422 }
1423
1424 g.setFont(SwingMiscManager.getIfUsingSwingFontManager().getInternalFont(TrackWidgetCommons.FREESPACE_TRACKNAME_FONT));
1425 g.setColor(SwingConversions.toSwingColor(TrackWidgetCommons.FREESPACE_TRACKNAME_TEXT_COLOR));
1426
1427 // Center track name
1428 FontMetrics fm = g.getFontMetrics(SwingMiscManager.getIfUsingSwingFontManager().getInternalFont(TrackWidgetCommons.FREESPACE_TRACKNAME_FONT));
1429 Rectangle2D rect = fm.getStringBounds(name, g);
1430
1431 g.drawString(
1432 name,
1433 this.getX() + (int)((getWidth() - rect.getWidth()) / 2),
1434 this.getY() + (int)((getHeight() - rect.getHeight()) / 2) + (int)rect.getHeight()
1435 );
1436
1437 g.setClip(clipBackUp);
1438
1439 }
1440
1441 }
1442
1443
1444
1445 @Override
1446 public void paintHeavyDutyWidget(Graphics2D g)
1447 {
1448 super.paintHeavyDutyWidget(g);
1449
1450 if (isLoaded() && nameLabel != null) {
1451 nameLabel.paint();
1452
1453 if (shouldOmitIndexAudio()) {
1454
1455 int shiftOffset = SoundDesk.getInstance().isPlaying(trackMix.getChannelID()) ?
1456 -20 : 0;
1457
1458 /*IconRepository.getIcon("omitindexed.png").paintIcon(
1459 _swingComponent,
1460 g,
1461 getX() + getWidth() - EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET + shiftOffset,
1462 getY() + EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET - 16);*/
1463
1464 g.drawImage(SwingMiscManager.getIfUsingSwingImageManager().getInternalImage(IconRepository.getIcon("omitindexed.png")),
1465 getX() + getWidth() - EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET + shiftOffset,
1466 getY() + EditableSampledTrackGraphView.LOCK_ICON_CORNER_OFFSET - 16, null);
1467
1468 }
1469 }
1470
1471 }
1472
1473 private boolean ignoreInjection = false;
1474 private MouseEvent lastInsertME = null;
1475
1476 @Override
1477 protected void onParentStateChanged(int eventType) {
1478 super.onParentStateChanged(eventType);
1479
1480 Frame parent = null;
1481
1482 switch (eventType) {
1483
1484 // Logic for injecting audio tracks into EditableSampledTrackGraphView's
1485 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
1486 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
1487
1488 // Resume any layouts suspended by this track widget
1489 FrameLayoutDaemon.getInstance().resumeLayout(this);
1490
1491 if (trackModel != null) {
1492
1493 parent = getParentFrame();
1494
1495 // Determine new initation time according to anchored position...
1496 Mutable.Long initTime = getInitiationTimeFromMeta();
1497
1498 // If the user is restricting-y-axis movement then they might be moving
1499 // this tracks Y-position only for layout reasons as opposed to repositioning
1500 // where in the audio timeline the track should be. This must be accurate and
1501 // avoid loosing the exact initiation time due to pixel-resolutoin issues
1502 if (parent != null) {
1503
1504 boolean inferInitTime = true;
1505
1506 if (ApolloGestureActions.isYAxisRestictionOn()) {
1507 Mutable.Long ms = getInitiationTimeFromMeta();
1508 if (ms != null) {
1509 Mutable.Long timex = FrameLayoutDaemon.getInstance().getXAtMS(
1510 ms.value,
1511 parent);
1512 if (timex != null && timex.value == getX()) {
1513 initTime = ms;
1514 inferInitTime = false;
1515 }
1516 }
1517 }
1518
1519 // Also must not set initiation time if the frame is simply being displayed
1520 //inferInitTime &= (AudioFrameMouseActions.isMouseAnchoring() || AudioFrameMouseActions.isMouseStamping());
1521 inferInitTime &= (eventType == ItemParentStateChangedEvent.EVENT_TYPE_ADDED);
1522
1523 if (inferInitTime) {
1524 initTime = Mutable.createMutableLong(FrameLayoutDaemon.getInstance().getMSAtX(getX(), parent));
1525 }
1526 }
1527
1528 updateData(TrackWidgetCommons.META_INITIATIONTIME_TAG,
1529 TrackWidgetCommons.META_INITIATIONTIME_TAG + initTime);
1530
1531 // Keep TrackGraphModel consistant
1532 AudioStructureModel.getInstance().onTrackWidgetAnchored(
1533 localFileName,
1534 (parent != null) ? parent.getName() : null,
1535 initTime.value,
1536 AudioMath.framesToMilliseconds(trackModel.getFrameCount(), trackModel.getFormat()),
1537 getName(),
1538 getY());
1539
1540
1541 MouseEvent me = MouseEventRouter.getCurrentMouseEvent();
1542
1543 if (!isIgnoreInjection() && me != null && me != lastInsertME &&
1544 (me.getButton() == MouseEvent.BUTTON2 ||
1545 me.getButton() == MouseEvent.BUTTON3)) {
1546
1547 // Although widgets filter out multiple parent changed events per mouse event -
1548 // it also considers the event type, and because this it remove itself from the frame/freespace
1549 // it jumbles up the filtering so that this will be invoked for each corner.
1550 lastInsertME = me;
1551
1552 if (me.getComponent() != fulltrackView &&
1553 me.getComponent() instanceof EditableSampledTrackGraphView) {
1554
1555 EditableSampledTrackGraphView editableSampledTrack =
1556 (EditableSampledTrackGraphView)me.getComponent();
1557
1558 // Inject this into the widget
1559 try {
1560 injectAudio(editableSampledTrack, me.getX(), true);
1561 } catch (IOException ex) {
1562 ex.printStackTrace();
1563 }
1564
1565 }
1566
1567 } else if (!isIgnoreInjection() && me == lastInsertME) {
1568 // Note due to a injection removing this widget while in the midst of
1569 // anchoring, the widget parent event filtering will not work thus
1570 // must keep removing self until the mouse event has done.
1571 removeSelf();
1572 }
1573
1574 // Wakeup the daemon to note that it should recheck -- in the case that a track is
1575 // added to a non-overdubbed frame the audio structure model will not bother
1576 // adding the track until something requests for it.
1577 FrameLayoutDaemon.getInstance().forceRecheck();
1578 }
1579
1580 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
1581 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
1582
1583
1584/*
1585 // Listen for volume or mute changed events
1586 trackMix.addObserver(this);
1587
1588 // Listen for solo events and track sequence creation events
1589 MixDesk.getInstance().addObserver(this);
1590 */
1591
1592 break;
1593
1594
1595 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
1596 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY:
1597
1598 // If yanking this from the frame into free space suspend the layout daemon
1599 // for this frame
1600 if (MouseEventRouter.getCurrentMouseEvent() != null &&
1601 MouseEventRouter.getCurrentMouseEvent().getButton() == MouseEvent.BUTTON2 &&
1602 MouseEventRouter.getCurrentMouseEvent() != lastInsertME) {
1603 Frame suspended = DisplayController.getCurrentFrame();
1604 if (suspended != null) {
1605 FrameLayoutDaemon.getInstance().suspendLayout(suspended, this);
1606 }
1607 }
1608
1609 if (trackModel != null) {
1610 parent = getParentFrame();
1611 // Keep TrackGraphModel consistant
1612 AudioStructureModel.getInstance().onTrackWidgetRemoved(localFileName,
1613 (parent != null) ? parent.getName() : null);
1614 }
1615
1616 case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
1617 /*
1618 trackMix.removeObserver(this);
1619 MixDesk.getInstance().removeObserver(this);*/
1620
1621 break;
1622
1623 }
1624
1625 }
1626
1627 /**
1628 * Injects this widgets bytes into a EditableSampledTrackGraphView
1629 * and removes this widget from freespace/parent-frame.
1630 *
1631 * Doesn't inject bytes if the target EditableSampledTrackGraphView is in a playing state.
1632 *
1633 * @param target
1634 * The target EditableSampledTrackGraphView to inject this widgets bytes into
1635 *
1636 * @param graphX
1637 * The X pixel in the target's graph. Must be in valid range.
1638 *
1639 * @param destroySelf
1640 * If want to destroy this widget
1641 *
1642 * @throws IOException
1643 * If the insert failed ... can occur if bytes need to be
1644 * converted into targets format.
1645 */
1646 public void injectAudio(EditableSampledTrackGraphView target, int graphX, boolean destroySelf)
1647 throws IOException {
1648 assert(target != null);
1649 assert(graphX >= 0);
1650 assert(graphX <= target.getWidth());
1651
1652 // Cannot inject into EditableSampledTrackGraphView's while playing, although
1653 // won't break anything, this would be confusing for the user.
1654 if (target.isPlaying()) {
1655 return;
1656 }
1657
1658 // Inject the audio at the graph poistion
1659 int insertFramePoint = target.frameAtX(graphX);
1660
1661 // Inject audio
1662 target.insertAudio(
1663 trackModel.getAllAudioBytes(),
1664 trackModel.getFormat(),
1665 insertFramePoint);
1666
1667 // Note: if removed from free space .. then there should be no
1668 // more references to this item therefore the memory will be freed
1669 // eventually after this invoke
1670 if (destroySelf) {
1671 removeSelf();
1672 }
1673 }
1674
1675 /**
1676 *
1677 * @return
1678 * The unique loval filename for this track. Auto-assigned - and rememered. Never null.
1679 */
1680 public String getLocalFileName() {
1681 return localFileName;
1682 }
1683
1684 /**
1685 * Determines the running time from the raw audio in memory.
1686 *
1687 * @return
1688 * The running time or this track in MS.
1689 * -1 if not loaded.
1690 */
1691 public long getRunningMSTimeFromRawAudio()
1692 {
1693 if (this.trackModel != null) {
1694 return AudioMath.framesToMilliseconds(trackModel.getFrameCount(), trackModel.getFormat());
1695 }
1696
1697 return -1;
1698 }
1699
1700 /**
1701 * Determines the running time from the meta data.
1702 *
1703 * @return
1704 * The running time or this track in MS.
1705 * -1 if unavilable.
1706 */
1707 public long getRunningMSTimeFromMeta() {
1708 return getStrippedDataLong(TrackWidgetCommons.META_RUNNINGMSTIME_TAG, new Long(-1));
1709 }
1710
1711 /**
1712 * @return
1713 * The name given to this widget... can be null.
1714 */
1715 @Override
1716 public String getName() {
1717 if (this.trackModel != null) {
1718 return trackModel.getName();
1719 } else if (this.nameLabel != null) {
1720 return nameLabel.getText();
1721 }
1722 return getStrippedDataString(TrackWidgetCommons.META_NAME_TAG);
1723 }
1724
1725 /**
1726 * Determines the initiation time from the meta data.
1727 *
1728 * @return
1729 * The initiation time or this track in MS.
1730 * null if unavilable.
1731 */
1732 public Mutable.Long getInitiationTimeFromMeta() {
1733 Long l = getStrippedDataLong(TrackWidgetCommons.META_INITIATIONTIME_TAG, null);
1734 if (l != null) {
1735 return Mutable.createMutableLong(l.longValue());
1736 }
1737 return null;
1738 }
1739
1740 /**
1741 * Adjusts the initiation time for this track - to the exact millisecond.
1742 *
1743 * The x-position is updated (which will be eventually done with possibly better
1744 * accuracy if the layout daemon is on)
1745 *
1746 * @param specificInitTime
1747 * The new initiation time for this track in milliseconds
1748 */
1749 public void setInitiationTime(long specificInitTime) {
1750
1751 Frame parent = getParentFrame();
1752
1753 // Update x position if it can
1754 if (parent != null) {
1755 Timeline tl = FrameLayoutDaemon.getInstance().getTimeline(parent);
1756 if (tl != null) {
1757 this.setPosition(tl.getXAtMSTime(specificInitTime), getY());
1758 }
1759 }
1760
1761 updateData(TrackWidgetCommons.META_INITIATIONTIME_TAG,
1762 TrackWidgetCommons.META_INITIATIONTIME_TAG + specificInitTime);
1763
1764 AudioStructureModel.getInstance().onTrackWidgetPositionChanged(
1765 localFileName, (parent != null) ? parent.getName() : null, specificInitTime, getY());
1766
1767 }
1768
1769 /**
1770 * Sets Y position - retaining the init time.
1771 * @param newY
1772 */
1773 public void setYPosition(int newY) {
1774
1775 if (getY() == newY || isFloating()) {
1776 return;
1777 }
1778
1779 Frame parent = getParentFrame();
1780
1781 Mutable.Long initTime = getInitiationTimeFromMeta();
1782 if (initTime == null) {
1783 return;
1784 }
1785
1786 setPosition(getX(), newY);
1787
1788 AudioStructureModel.getInstance().onTrackWidgetPositionChanged(
1789 localFileName, (parent != null) ? parent.getName() : null, initTime.value, newY);
1790
1791 }
1792
1793
1794 private void updateBorderColor() {
1795
1796 // Get border color currently used
1797 Colour oldC = getSource().getBorderColor();
1798
1799 Colour newC = TrackWidgetCommons.getBorderColor(
1800 SoundDesk.getInstance().isSolo(trackMix.getChannelID()),
1801 trackMix.isMuted());
1802
1803 // Update the color
1804 if (!newC.equals(oldC)) {
1805 setWidgetEdgeColor(newC);
1806 }
1807 }
1808
1809 /**
1810 * State icons live at the top right corner
1811 *
1812 */
1813 private void invalidateStateIcons() {
1814 this.invalidateSelf(); // TODO
1815 }
1816
1817 private void setShouldOmitIndexAudio(boolean shouldOmit) {
1818 this.shouldOmitAudioIndexing = shouldOmit;
1819 if (!shouldOmit) {
1820 removeData(META_DONT_INDEX_AUDIO_TAG);
1821 } else {
1822 addDataIfCaseInsensitiveNotExists(META_DONT_INDEX_AUDIO_TAG);
1823 }
1824 invalidateStateIcons();
1825 }
1826
1827 public boolean shouldOmitIndexAudio() {
1828 return shouldOmitAudioIndexing;
1829 }
1830
1831// /**
1832// * Invalidates.
1833// * @param shouldLayout
1834// */
1835// private void setShouldLayout(boolean shouldLayout) {
1836//
1837// if (shouldLayout) {
1838// removeData(TrackWidgetCommons.META_OMIT_LAYOUT_TAG);
1839// } else {
1840// addDataIfCaseInsensitiveNotExists(TrackWidgetCommons.META_OMIT_LAYOUT_TAG);
1841// }
1842//
1843// invalidateStateIcons();
1844// }
1845//
1846
1847//
1848// public boolean shouldLayout() {
1849//
1850// return (!containsDataTrimmedIgnoreCase(TrackWidgetCommons.META_OMIT_LAYOUT_TAG));
1851// }
1852
1853 private boolean split(boolean extractSelection) {
1854
1855 // First is operation valid?
1856 if (trackModel != null && trackModel.getSelectionLength() > 1
1857 && !fulltrackView.isPlaying()
1858 && (trackModel.getFrameCount() - trackModel.getSelectionLength()) > EditableSampledTrackGraphView.MIN_FRAME_SELECTION_SIZE) {
1859
1860 // If so... can a slip be performed? i.e. is there unselected audio to
1861 // the left and right of selection
1862 if (trackModel.getSelectionStart() > 0 &&
1863 (trackModel.getSelectionStart() + trackModel.getSelectionLength()) < trackModel.getFrameCount()) {
1864
1865 // Perform split
1866 int rightSideStartFrame = trackModel.getSelectionStart() + trackModel.getSelectionLength();
1867
1868 // Create a new track widget to contain the right-side audio
1869 byte[] rightsideAudio = new byte[(trackModel.getFrameCount() - rightSideStartFrame) * trackModel.getFormat().getFrameSize()];
1870
1871 // Copy bytes into new location
1872 System.arraycopy(
1873 trackModel.getAllAudioBytes(),
1874 rightSideStartFrame * trackModel.getFormat().getFrameSize(),
1875 rightsideAudio,
1876 0, rightsideAudio.length);
1877
1878 byte[] selectedBytes = (extractSelection) ? trackModel.getSelectedFramesCopy() : null;
1879
1880 // Let this widget keep the left-side audio
1881 trackModel.setSelection(trackModel.getSelectionStart(), trackModel.getFrameCount() - trackModel.getSelectionStart());
1882 trackModel.removeSelectedBytes();
1883 trackModel.setSelection(0,0);
1884
1885 // Build the new neighbouring widget
1886 Frame target = getParentFrame();
1887 if (target == null) {
1888 target = DisplayController.getCurrentFrame();
1889 }
1890
1891 // Determine init time
1892 Mutable.Long initTime = getInitiationTimeFromMeta();
1893
1894 if (initTime == null) {
1895 initTime = Mutable.createMutableLong(0);
1896 }
1897
1898 initTime.value += AudioMath.framesToMilliseconds(rightSideStartFrame, trackModel.getFormat());
1899
1900 SampledTrack rightSideTrack = SampledTrack.createFromMemory(
1901 rightsideAudio,
1902 trackModel.getFormat(),
1903 target,
1904 0, // X Coord overridden
1905 getY(),
1906 getName() + " part",
1907 trackMix);
1908
1909 // Anchor it
1910 rightSideTrack.setIgnoreInjection(true);
1911 target.addAllItems(rightSideTrack.getItems());
1912 rightSideTrack.setIgnoreInjection(false);
1913
1914 // Adjust initiation time to be exact
1915 rightSideTrack.setInitiationTime(initTime.value);
1916
1917 // If extracting audio then attatch it to the cursor
1918 if (selectedBytes != null) {
1919 assert(extractSelection);
1920
1921 SampledTrack extractedTrack = SampledTrack.createFromMemory(
1922 selectedBytes,
1923 trackModel.getFormat(),
1924 target,
1925 getX(), // X Coord overridden
1926 getY(),
1927 getName() + " part",
1928 trackMix);
1929
1930 StandardGestureActions.pickup(extractedTrack.getItems());
1931
1932 }
1933 }
1934
1935 return true;
1936 }
1937
1938 return false;
1939
1940 }
1941 /**
1942 * Doesn't expand if not anchored ...
1943 * @param addToExpand
1944 */
1945 private void expand(boolean addToExpand) {
1946
1947 Frame parent = getParentFrame();
1948 String pfname = (parent != null) ? parent.getName() : null;
1949 if (pfname == null) {
1950 return;
1951 }
1952
1953 if (!ExpandedTrackManager.getInstance().isTrackInExpansionSelection(trackModel)) {
1954
1955 if (addToExpand) {
1956
1957 // Show the expanded view for this track once control has been released
1958 ExpandedTrackManager.getInstance().addTrackToSelection(
1959 trackModel,
1960 pfname,
1961 SwingConversions.fromSwingRectangle(_swingComponent.getBounds()),
1962 trackMix);
1963
1964 } else {
1965
1966 // Get rid of all popups
1967 PopupManager.getInstance().hideAutoHidePopups();
1968
1969 int start = trackModel.getSelectionStart();
1970 int length = trackModel.getSelectionLength();
1971
1972 if (length <= 1) {
1973 start = 0;
1974 length = trackModel.getFrameCount();
1975 }
1976
1977 // Show the expanded view for this track
1978 ExpandedTrackManager.getInstance().expandSingleTrack(
1979 trackModel,
1980 SwingConversions.fromSwingRectangle(_swingComponent.getBounds()),
1981 trackMix,
1982 pfname,
1983 start,
1984 length
1985 );
1986
1987 }
1988
1989 } else {
1990
1991 // Take away track from being selected.
1992 ExpandedTrackManager.getInstance().removeTrackFromSelection(trackModel);
1993 }
1994
1995 }
1996
1997 /**
1998 * The small popup for common actions.
1999 *
2000 * @author Brook Novak
2001 *
2002 */
2003 private class PlaybackPopup extends PlaybackControlPopup implements Observer
2004 {
2005 public PlaybackPopup()
2006 {
2007 miscButton.setActionCommand("expand");
2008 SwingMiscManager.setJButtonIcon(miscButton, IconRepository.getIcon("expand.png"));
2009 miscButton.setToolTipText("Expand");
2010 }
2011
2012 @Override
2013 public void onHide()
2014 {
2015 // Listen for volume or mute changed events
2016 trackMix.removeObserver(this);
2017
2018 // Listen for solo events and track sequence creation events
2019 SoundDesk.getInstance().removeObserver(this);
2020 }
2021
2022 @Override
2023 public void onShow()
2024 {
2025 // Listen for volume or mute changed events
2026 trackMix.addObserver(this);
2027
2028 // Listen for solo events and track sequence creation events
2029 SoundDesk.getInstance().addObserver(this);
2030 updateVolume((int)(100 * trackMix.getVolume()));
2031 updateMute(trackMix.isMuted());
2032 updateSolo(SoundDesk.getInstance().isSolo(trackMix.getChannelID()));
2033 }
2034
2035 @Override
2036 public void actionPerformed(ActionEvent e) {
2037 if (trackModel == null) {
2038 return;
2039 }
2040
2041 if (e.getSource() == playPauseButton) {
2042
2043 try {
2044
2045 playPauseResume();
2046
2047 } catch (LineUnavailableException e1) {
2048 e1.printStackTrace();
2049 }
2050
2051 } else if (e.getSource() == stopButton) {
2052
2053 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
2054
2055 // reset any paused mark
2056 SoundDesk.getInstance().setPaused(trackMix.getChannelID(), false);
2057
2058 if (ts != null &&
2059 ts.isPlaying()) {
2060 // Stop playback
2061 ApolloPlaybackMixer.getInstance().stop(ts);
2062 }
2063
2064 } else if (e.getSource() == rewindButton) {
2065
2066 trackModel.setSelection(0, 0);
2067 SoundDesk.getInstance().setPaused(trackMix.getChannelID(), false);
2068
2069 } else if (e.getSource() == miscButton) {
2070 expand(false);
2071 }
2072 }
2073
2074 private void playPauseResume() throws LineUnavailableException {
2075 if (!SoundDesk.getInstance().isPlaying(trackMix.getChannelID())) { // play / resume
2076
2077 int startFrame = -1, endFrame = -1;
2078
2079 // Resume playback?
2080 if (SoundDesk.getInstance().isPaused(trackMix.getChannelID())) {
2081 startFrame = SoundDesk.getInstance().getLastPlayedFramePosition(trackMix.getChannelID());
2082 if (startFrame >= 0 && startFrame < trackModel.getFrameCount()) {
2083
2084 // The user may have edited the audio track and reselected it
2085 // since the last pause. Thus select an appropriate end frame
2086 endFrame = (trackModel.getSelectionLength() > 1) ?
2087 trackModel.getSelectionStart() + trackModel.getSelectionLength():
2088 trackModel.getFrameCount() - 1;
2089
2090 // Changed selection? it play range invalid?
2091 if (endFrame <= startFrame || startFrame < trackModel.getSelectionStart()) {
2092 startFrame = -1; // Play new selection (see below)
2093
2094 } else if (endFrame >= trackModel.getFrameCount()) {
2095 endFrame = trackModel.getFrameCount() - 1;
2096 }
2097
2098 }
2099 }
2100
2101 // Play from beginning of selection to end of selection
2102 if (startFrame < 0) {
2103 startFrame = trackModel.getSelectionStart();
2104 endFrame = (trackModel.getSelectionLength() > 1) ?
2105 startFrame + trackModel.getSelectionLength():
2106 trackModel.getFrameCount() - 1;
2107 }
2108
2109 // Safety clamp:
2110 if (endFrame >= trackModel.getFrameCount()) {
2111 endFrame = trackModel.getFrameCount() - 1;
2112 }
2113
2114 if (startFrame < endFrame) {
2115 SoundDesk.getInstance().playSampledTrackModel(
2116 trackModel,
2117 trackMix.getChannelID(),
2118 startFrame,
2119 endFrame,
2120 0);
2121 }
2122
2123 } else { // pause
2124
2125 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
2126
2127 if (ts != null &&
2128 ts.isPlaying()) {
2129
2130 // Mark channel as paused.
2131 SoundDesk.getInstance().setPaused(trackMix.getChannelID(), true);
2132
2133 // Stop playback for this channel
2134 ApolloPlaybackMixer.getInstance().stop(ts);
2135
2136 }
2137
2138 }
2139 }
2140
2141 @Override
2142 public Subject getObservedSubject()
2143 {
2144 return null;
2145 }
2146
2147 @Override
2148 public void setObservedSubject(Subject parent)
2149 {
2150 }
2151
2152 /**
2153 * Receives events from the track model OR from the observed track sequence.
2154 */
2155 @Override
2156 public void modelChanged(Subject source, SubjectChangedEvent event)
2157 {
2158 // Synch GUI with track state
2159 switch (event.getID()) {
2160
2161 case ApolloSubjectChangedEvent.TRACK_SEQUENCE_CREATED: // from sound desk
2162
2163 if (event.getState().equals(trackMix.getChannelID())) {
2164 // The channel being played is the same as this one ...
2165 // even if the track model is unloaded must enter into a playing state
2166 // if the created track sequence will play
2167 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
2168 assert(ts != null);
2169 assert(!ts.hasFinished());
2170 assert(!ts.isPlaying());
2171 ts.addObserver(this);
2172 }
2173
2174 break;
2175
2176 case ApolloSubjectChangedEvent.PLAYBACK_STARTED: // From observed track sequence
2177 stopButton.setEnabled(true);
2178 rewindButton.setEnabled(false);
2179 SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("pause.png"));
2180
2181 invalidateStateIcons();
2182
2183 SampledTrack.this.setWidgetEdgeThickness(TrackWidgetCommons.PLAYING_TRACK_EDGE_THICKNESS);
2184 //FrameGraphics.refresh(true);
2185 break;
2186
2187 case ApolloSubjectChangedEvent.PLAYBACK_STOPPED: // From observed track sequence
2188
2189 invalidateStateIcons();
2190
2191 rewindButton.setEnabled(true);
2192 stopButton.setEnabled(false);
2193 SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("play.png"));
2194
2195 // Note:
2196 // No need to remove self from observing the dead track since the track references this
2197 // and will get garbage collected
2198
2199 SampledTrack.this.setWidgetEdgeThickness(TrackWidgetCommons.STOPPED_TRACK_EDGE_THICKNESS);
2200
2201 break;
2202
2203 case ApolloSubjectChangedEvent.PAUSE_MARK_CHANGED: // When stopped or paused
2204 /*
2205 if (ae.getState().equals(trackMix.getChannelID())) {
2206
2207 if (MixDesk.getInstance().isPaused(trackMix.getChannelID())) {
2208 // Do nothing .. the paused mark is set prior to a stop
2209 } else {
2210 // Esnure that the GUI represents a stopped state
2211 stopButton.setEnabled(false);
2212 playPauseButton.setIcon(IconRepository.getIcon("play.png"));
2213 }
2214
2215 }*/
2216
2217 break;
2218
2219 case ApolloSubjectChangedEvent.VOLUME: // From obseved track mix
2220 updateVolume((int)(100 * trackMix.getVolume()));
2221 break;
2222
2223 case ApolloSubjectChangedEvent.MUTE: // From obseved track mix
2224 updateMute(trackMix.isMuted());
2225 updateBorderColor();
2226 break;
2227
2228 case ApolloSubjectChangedEvent.SOLO_PREFIX_CHANGED: // From mix desk
2229 updateSolo(SoundDesk.getInstance().isSolo(trackMix.getChannelID()));
2230 updateBorderColor();
2231 break;
2232 }
2233
2234
2235
2236 }
2237
2238 @Override
2239 protected void volumeChanged()
2240 {
2241 trackMix.setVolume((volumeSlider.getValue()) / 100.0f);
2242 }
2243
2244 @Override
2245 protected void muteChanged()
2246 {
2247 trackMix.setMuted(muteButton.isSelected());
2248 }
2249
2250 @Override
2251 protected void soloChanged()
2252 {
2253 SoundDesk.getInstance().setSoloIDPrefix(soloButton.isSelected() ?
2254 trackMix.getChannelID() : null
2255 );
2256 }
2257 }
2258
2259 @Override
2260 public boolean isWidgetEdgeThicknessAdjustable()
2261 {
2262 return false;
2263 }
2264
2265 public boolean isIgnoreInjection() {
2266 return ignoreInjection;
2267 }
2268
2269 public void setIgnoreInjection(boolean ignoreInjection) {
2270 this.ignoreInjection = ignoreInjection;
2271 }
2272}
Note: See TracBrowser for help on using the repository browser.