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

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

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