source: trunk/src_apollo/org/apollo/widgets/SampledTrack.java@ 333

Last change on this file since 333 was 333, checked in by bjn8, 16 years ago

Improved recording interactions for faster idea capturing.
Various fixes for interactive widgets

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