--- /dev/null
+package tim.prune.correlate;
+
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.TimezoneHelper;
+import tim.prune.data.AudioClip;
+import tim.prune.data.AudioList;
+import tim.prune.data.DataPoint;
+import tim.prune.data.MediaObject;
+import tim.prune.data.MediaList;
+import tim.prune.data.TimeDifference;
+import tim.prune.data.Timestamp;
+import tim.prune.data.TimestampUtc;
+import tim.prune.undo.UndoCorrelateAudios;
+
+/**
+ * Class to manage the automatic correlation of audio clips to points
+ * which is very similar to the PhotoCorrelator apart from the clip lengths
+ */
+public class AudioCorrelator extends Correlator
+{
+ private AudioTimestampSelector _fileTimesSelector = null, _correlTimesSelector = null;
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public AudioCorrelator(App inApp) {
+ super(inApp);
+ }
+
+ /**
+ * @return name key
+ */
+ public String getNameKey() {
+ return "function.correlateaudios";
+ }
+
+ /** @return type key */
+ protected String getMediaTypeKey() {
+ return "audio";
+ }
+
+ /** @return photo list*/
+ protected MediaList getMediaList() {
+ return _app.getTrackInfo().getAudioList();
+ }
+
+
+ /**
+ * @return first gui panel including timestamp specification (beginning, middle, end)
+ */
+ protected JPanel makeFirstPanel()
+ {
+ // First panel for timestamp stuff
+ JPanel card1 = new JPanel();
+ card1.setLayout(new FlowLayout(FlowLayout.CENTER));
+ JPanel grid1 = new JPanel();
+ grid1.setLayout(new GridLayout(0, 1));
+ _fileTimesSelector = new AudioTimestampSelector("dialog.correlate.filetimes", "dialog.correlate.filetimes2");
+ grid1.add(_fileTimesSelector);
+ _correlTimesSelector = new AudioTimestampSelector("dialog.correlate.correltimes", null);
+ grid1.add(_correlTimesSelector);
+ card1.add(grid1);
+ return card1;
+ }
+
+
+ /**
+ * @return array of boolean flags denoting availability of cards
+ */
+ protected boolean[] getCardEnabledFlags()
+ {
+ boolean[] cards = super.getCardEnabledFlags();
+ cards[0] = getAudioLengthAvailability(_app.getTrackInfo().getAudioList());
+ return cards;
+ }
+
+ /**
+ * @param inAudios AudioList object
+ * @return true if there are any audio lengths available
+ */
+ private static boolean getAudioLengthAvailability(AudioList inAudios)
+ {
+ for (int i=0; i<inAudios.getNumMedia(); i++)
+ {
+ AudioClip a = inAudios.getAudio(i);
+ if (a.getLengthInSeconds() > 0) {return true;}
+ }
+ return false;
+ }
+
+ /**
+ * Create a preview of the correlate action using the selected time difference
+ * @param inTimeDiff TimeDifference to use for preview
+ * @param inShowWarning true to show warning if all points out of range
+ */
+ protected void createPreview(TimeDifference inTimeDiff, boolean inShowWarning)
+ {
+ TimeDifference timeLimit = parseTimeLimit();
+ double angDistLimit = parseDistanceLimit();
+ MediaPreviewTableModel model = new MediaPreviewTableModel("dialog.correlate.select.audioname");
+ AudioList audios = _app.getTrackInfo().getAudioList();
+ // Loop through audios deciding whether to set correlate flag or not
+ int numAudios = audios.getNumAudios();
+ for (int i=0; i<numAudios; i++)
+ {
+ AudioClip audio = audios.getAudio(i);
+ PointMediaPair pair = getPointPairForMedia(_app.getTrackInfo().getTrack(), audio, inTimeDiff);
+ MediaPreviewTableRow row = new MediaPreviewTableRow(pair);
+ // Don't try to correlate audios which don't have points either side
+ boolean correlateAudio = pair.isValid();
+ // Don't select audios which already have a point
+ if (audio.getCurrentStatus() != AudioClip.Status.NOT_CONNECTED) {correlateAudio = false;}
+ // Check time limits, distance limits
+ if (timeLimit != null && correlateAudio) {
+ long numSecs = pair.getMinSeconds();
+ correlateAudio = (numSecs <= timeLimit.getTotalSeconds());
+ }
+ if (angDistLimit > 0.0 && correlateAudio)
+ {
+ final double angDistPair = DataPoint.calculateRadiansBetween(pair.getPointBefore(), pair.getPointAfter());
+ double frac = pair.getFraction();
+ if (frac > 0.5) {frac = 1 - frac;}
+ final double angDistPhoto = angDistPair * frac;
+ correlateAudio = (angDistPhoto < angDistLimit);
+ }
+ // Don't select audios which are already correlated to the same point
+ if (pair.getSecondsBefore() == 0L && pair.getPointBefore().isDuplicate(audio.getDataPoint())) {
+ correlateAudio = false;
+ }
+ row.setCorrelateFlag(correlateAudio);
+ model.addRow(row);
+ }
+ _previewTable.setModel(model);
+ // Set distance units
+ model.setDistanceUnits(getSelectedDistanceUnits());
+ // Set column widths
+ _previewTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+ final int[] colWidths = {150, 160, 100, 100, 50};
+ for (int i=0; i<model.getColumnCount(); i++) {
+ _previewTable.getColumnModel().getColumn(i).setPreferredWidth(colWidths[i]);
+ }
+ // check if any audios found
+ _okButton.setEnabled(model.hasAnySelected());
+ if (inShowWarning && !model.hasAnySelected())
+ {
+ JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.correlate.alloutsiderange"),
+ I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * @return modified timestamp of specified media object
+ */
+ protected Timestamp getMediaTimestamp(MediaObject inMedia)
+ {
+ Timestamp tstamp = super.getMediaTimestamp(inMedia);
+ long mediaMillis = tstamp.getMilliseconds(TimezoneHelper.getSelectedTimezone());
+ try {
+ AudioClip audio = (AudioClip) inMedia;
+ int audioLength = audio.getLengthInSeconds();
+ // Each option is worth half the length of the audio clip, so need to divide by 2
+ int secsToAdd = audioLength *
+ (_correlTimesSelector.getSelectedOption() - _fileTimesSelector.getSelectedOption()) / 2;
+ if (audioLength > 0 && secsToAdd != 0)
+ {
+ mediaMillis += (secsToAdd * 1000L);
+ tstamp = new TimestampUtc(mediaMillis);
+ // Here we create a Utc timestamp but it's only temporary for the correlation
+ // so it will never have to react to timezone changes
+ }
+ }
+ catch (ClassCastException cce) {}
+ return tstamp;
+ }
+
+ /**
+ * Finish the correlation by modifying the track
+ * and passing the Undo information back to the App
+ */
+ protected void finishCorrelation()
+ {
+ // TODO: Probably should be able to combine this into the Correlator?
+ PointMediaPair[] pointPairs = getPointPairs();
+ if (pointPairs == null || pointPairs.length <= 0) {return;}
+
+ // begin to construct undo information
+ UndoCorrelateAudios undo = new UndoCorrelateAudios(_app.getTrackInfo());
+ // loop over Audios
+ int arraySize = pointPairs.length;
+ int i = 0, numAudios = 0;
+ int numPointsToCreate = 0;
+ PointMediaPair pair = null;
+ for (i=0; i<arraySize; i++)
+ {
+ pair = pointPairs[i];
+ if (pair != null && pair.isValid())
+ {
+ if (pair.getMinSeconds() == 0L)
+ {
+ // exact match
+ AudioClip pointAudio = pair.getPointBefore().getAudio();
+ if (pointAudio == null)
+ {
+ // photo coincides with audioless point so connect the two
+ pair.getPointBefore().setAudio((AudioClip) pair.getMedia());
+ pair.getMedia().setDataPoint(pair.getPointBefore());
+ }
+ else if (pointAudio.equals(pair.getMedia())) {
+ // photo is already connected, nothing to do
+ }
+ else {
+ // point is already connected to a different audio, so need to clone point
+ numPointsToCreate++;
+ }
+ }
+ else
+ {
+ // audio time falls between two points, so need to interpolate new one
+ numPointsToCreate++;
+ }
+ numAudios++;
+ }
+ }
+ // Second loop, to create points if necessary
+ if (numPointsToCreate > 0)
+ {
+ // make new array for added points
+ DataPoint[] addedPoints = new DataPoint[numPointsToCreate];
+ int pointNum = 0;
+ DataPoint pointToAdd = null;
+ for (i=0; i<arraySize; i++)
+ {
+ pair = pointPairs[i];
+ if (pair != null && pair.isValid())
+ {
+ pointToAdd = null;
+ if (pair.getMinSeconds() == 0L && pair.getPointBefore().getAudio() != null
+ && !pair.getPointBefore().getAudio().equals(pair.getMedia()))
+ {
+ // clone point
+ pointToAdd = pair.getPointBefore().clonePoint();
+ }
+ else if (pair.getMinSeconds() > 0L)
+ {
+ // interpolate point
+ pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction());
+ }
+ if (pointToAdd != null)
+ {
+ // link audio to point
+ pointToAdd.setAudio((AudioClip) pair.getMedia());
+ pair.getMedia().setDataPoint(pointToAdd);
+ // set to start of segment so not joined in track
+ pointToAdd.setSegmentStart(true);
+ // add to point array
+ addedPoints[pointNum] = pointToAdd;
+ pointNum++;
+ }
+ }
+ }
+ // expand track
+ _app.getTrackInfo().getTrack().appendPoints(addedPoints);
+ }
+
+ // send undo information back to controlling app
+ undo.setNumAudiosCorrelated(numAudios);
+ _app.completeFunction(undo, ("" + numAudios + " "
+ + (numAudios==1?I18nManager.getText("confirm.correlateaudios.single"):I18nManager.getText("confirm.correlateaudios.multi"))));
+ // observers already informed by track update if new points created
+ if (numPointsToCreate == 0) {
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ }
+ }
+}