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.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.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);
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) {
tstamp = tstamp.createPlusOffset(secsToAdd);
}
}
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);
}
}
}