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

Last change on this file since 1179 was 1179, checked in by bln4, 6 years ago

org.apollo.ApolloGestureActions ->
org.apollo.widgets.SampledTrack ->

Reimplemented the ability to stamp SampledTracks

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