1 package tim.prune.correlate;
3 import java.awt.FlowLayout;
4 import java.awt.GridLayout;
6 import javax.swing.JOptionPane;
7 import javax.swing.JPanel;
8 import javax.swing.JTable;
11 import tim.prune.DataSubscriber;
12 import tim.prune.I18nManager;
13 import tim.prune.UpdateMessageBroker;
14 import tim.prune.config.TimezoneHelper;
15 import tim.prune.data.AudioClip;
16 import tim.prune.data.AudioList;
17 import tim.prune.data.DataPoint;
18 import tim.prune.data.MediaObject;
19 import tim.prune.data.MediaList;
20 import tim.prune.data.TimeDifference;
21 import tim.prune.data.Timestamp;
22 import tim.prune.data.TimestampUtc;
23 import tim.prune.undo.UndoCorrelateAudios;
26 * Class to manage the automatic correlation of audio clips to points
27 * which is very similar to the PhotoCorrelator apart from the clip lengths
29 public class AudioCorrelator extends Correlator
31 private AudioTimestampSelector _fileTimesSelector = null, _correlTimesSelector = null;
36 * @param inApp App object
38 public AudioCorrelator(App inApp) {
45 public String getNameKey() {
46 return "function.correlateaudios";
49 /** @return type key */
50 protected String getMediaTypeKey() {
54 /** @return photo list*/
55 protected MediaList getMediaList() {
56 return _app.getTrackInfo().getAudioList();
61 * @return first gui panel including timestamp specification (beginning, middle, end)
63 protected JPanel makeFirstPanel()
65 // First panel for timestamp stuff
66 JPanel card1 = new JPanel();
67 card1.setLayout(new FlowLayout(FlowLayout.CENTER));
68 JPanel grid1 = new JPanel();
69 grid1.setLayout(new GridLayout(0, 1));
70 _fileTimesSelector = new AudioTimestampSelector("dialog.correlate.filetimes", "dialog.correlate.filetimes2");
71 grid1.add(_fileTimesSelector);
72 _correlTimesSelector = new AudioTimestampSelector("dialog.correlate.correltimes", null);
73 grid1.add(_correlTimesSelector);
80 * @return array of boolean flags denoting availability of cards
82 protected boolean[] getCardEnabledFlags()
84 boolean[] cards = super.getCardEnabledFlags();
85 cards[0] = getAudioLengthAvailability(_app.getTrackInfo().getAudioList());
90 * @param inAudios AudioList object
91 * @return true if there are any audio lengths available
93 private static boolean getAudioLengthAvailability(AudioList inAudios)
95 for (int i=0; i<inAudios.getNumMedia(); i++)
97 AudioClip a = inAudios.getAudio(i);
98 if (a.getLengthInSeconds() > 0) {return true;}
104 * Create a preview of the correlate action using the selected time difference
105 * @param inTimeDiff TimeDifference to use for preview
106 * @param inShowWarning true to show warning if all points out of range
108 protected void createPreview(TimeDifference inTimeDiff, boolean inShowWarning)
110 TimeDifference timeLimit = parseTimeLimit();
111 double angDistLimit = parseDistanceLimit();
112 MediaPreviewTableModel model = new MediaPreviewTableModel("dialog.correlate.select.audioname");
113 AudioList audios = _app.getTrackInfo().getAudioList();
114 // Loop through audios deciding whether to set correlate flag or not
115 int numAudios = audios.getNumAudios();
116 for (int i=0; i<numAudios; i++)
118 AudioClip audio = audios.getAudio(i);
119 PointMediaPair pair = getPointPairForMedia(_app.getTrackInfo().getTrack(), audio, inTimeDiff);
120 MediaPreviewTableRow row = new MediaPreviewTableRow(pair);
121 // Don't try to correlate audios which don't have points either side
122 boolean correlateAudio = pair.isValid();
123 // Don't select audios which already have a point
124 if (audio.getCurrentStatus() != AudioClip.Status.NOT_CONNECTED) {correlateAudio = false;}
125 // Check time limits, distance limits
126 if (timeLimit != null && correlateAudio) {
127 long numSecs = pair.getMinSeconds();
128 correlateAudio = (numSecs <= timeLimit.getTotalSeconds());
130 if (angDistLimit > 0.0 && correlateAudio)
132 final double angDistPair = DataPoint.calculateRadiansBetween(pair.getPointBefore(), pair.getPointAfter());
133 double frac = pair.getFraction();
134 if (frac > 0.5) {frac = 1 - frac;}
135 final double angDistPhoto = angDistPair * frac;
136 correlateAudio = (angDistPhoto < angDistLimit);
138 // Don't select audios which are already correlated to the same point
139 if (pair.getSecondsBefore() == 0L && pair.getPointBefore().isDuplicate(audio.getDataPoint())) {
140 correlateAudio = false;
142 row.setCorrelateFlag(correlateAudio);
145 _previewTable.setModel(model);
146 // Set distance units
147 model.setDistanceUnits(getSelectedDistanceUnits());
149 _previewTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
150 final int[] colWidths = {150, 160, 100, 100, 50};
151 for (int i=0; i<model.getColumnCount(); i++) {
152 _previewTable.getColumnModel().getColumn(i).setPreferredWidth(colWidths[i]);
154 // check if any audios found
155 _okButton.setEnabled(model.hasAnySelected());
156 if (inShowWarning && !model.hasAnySelected())
158 JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.correlate.alloutsiderange"),
159 I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
164 * @return modified timestamp of specified media object
166 protected Timestamp getMediaTimestamp(MediaObject inMedia)
168 Timestamp tstamp = super.getMediaTimestamp(inMedia);
169 long mediaMillis = tstamp.getMilliseconds(TimezoneHelper.getSelectedTimezone());
171 AudioClip audio = (AudioClip) inMedia;
172 int audioLength = audio.getLengthInSeconds();
173 // Each option is worth half the length of the audio clip, so need to divide by 2
174 int secsToAdd = audioLength *
175 (_correlTimesSelector.getSelectedOption() - _fileTimesSelector.getSelectedOption()) / 2;
176 if (audioLength > 0 && secsToAdd != 0)
178 mediaMillis += (secsToAdd * 1000L);
179 tstamp = new TimestampUtc(mediaMillis);
180 // Here we create a Utc timestamp but it's only temporary for the correlation
181 // so it will never have to react to timezone changes
184 catch (ClassCastException cce) {}
189 * Finish the correlation by modifying the track
190 * and passing the Undo information back to the App
192 protected void finishCorrelation()
194 // TODO: Probably should be able to combine this into the Correlator?
195 PointMediaPair[] pointPairs = getPointPairs();
196 if (pointPairs == null || pointPairs.length <= 0) {return;}
198 // begin to construct undo information
199 UndoCorrelateAudios undo = new UndoCorrelateAudios(_app.getTrackInfo());
201 int arraySize = pointPairs.length;
202 int i = 0, numAudios = 0;
203 int numPointsToCreate = 0;
204 PointMediaPair pair = null;
205 for (i=0; i<arraySize; i++)
207 pair = pointPairs[i];
208 if (pair != null && pair.isValid())
210 if (pair.getMinSeconds() == 0L)
213 AudioClip pointAudio = pair.getPointBefore().getAudio();
214 if (pointAudio == null)
216 // photo coincides with audioless point so connect the two
217 pair.getPointBefore().setAudio((AudioClip) pair.getMedia());
218 pair.getMedia().setDataPoint(pair.getPointBefore());
220 else if (pointAudio.equals(pair.getMedia())) {
221 // photo is already connected, nothing to do
224 // point is already connected to a different audio, so need to clone point
230 // audio time falls between two points, so need to interpolate new one
236 // Second loop, to create points if necessary
237 if (numPointsToCreate > 0)
239 // make new array for added points
240 DataPoint[] addedPoints = new DataPoint[numPointsToCreate];
242 DataPoint pointToAdd = null;
243 for (i=0; i<arraySize; i++)
245 pair = pointPairs[i];
246 if (pair != null && pair.isValid())
249 if (pair.getMinSeconds() == 0L && pair.getPointBefore().getAudio() != null
250 && !pair.getPointBefore().getAudio().equals(pair.getMedia()))
253 pointToAdd = pair.getPointBefore().clonePoint();
255 else if (pair.getMinSeconds() > 0L)
258 pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction());
260 if (pointToAdd != null)
262 // link audio to point
263 pointToAdd.setAudio((AudioClip) pair.getMedia());
264 pair.getMedia().setDataPoint(pointToAdd);
265 // set to start of segment so not joined in track
266 pointToAdd.setSegmentStart(true);
267 // add to point array
268 addedPoints[pointNum] = pointToAdd;
274 _app.getTrackInfo().getTrack().appendPoints(addedPoints);
277 // send undo information back to controlling app
278 undo.setNumAudiosCorrelated(numAudios);
279 _app.completeFunction(undo, ("" + numAudios + " "
280 + (numAudios==1?I18nManager.getText("confirm.correlateaudios.single"):I18nManager.getText("confirm.correlateaudios.multi"))));
281 // observers already informed by track update if new points created
282 if (numPointsToCreate == 0) {
283 UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);