/**
* Created : Jun 15, 2012
*
* @author pquiring
*/
import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javaforce.*;
import javaforce.media.*;
public class ProjectPanel extends javax.swing.JPanel {
/**
* Creates new form ProjectPanel
*/
public ProjectPanel(String filename) {
initComponents();
tracks.setLayout(new TracksLayout());
if (filename == null) {
Random r = new Random();
this.path = Paths.getNewProjectPath();
new File(this.path).mkdirs();
new File(this.path + "/" + "undo").mkdirs();
new File(this.path + "/" + "cb").mkdirs(); //clipboard
} else {
this.filename = filename;
int idx = filename.lastIndexOf(".");
this.path = filename.substring(0, idx) + "_data";
loadTracks();
deleteUndo();
}
JFLog.log("project:" + path);
calcMaxLength();
timeLine.add(new TimeLine());
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
timeScroll = new javax.swing.JScrollBar();
tracks = new javax.swing.JPanel();
timeLine = new javax.swing.JPanel();
tracksScroll = new javax.swing.JScrollBar();
addComponentListener(new java.awt.event.ComponentAdapter() {
public void componentResized(java.awt.event.ComponentEvent evt) {
formComponentResized(evt);
}
});
timeScroll.setOrientation(javax.swing.JScrollBar.HORIZONTAL);
timeScroll.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
public void adjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {
timeScrollAdjustmentValueChanged(evt);
}
});
javax.swing.GroupLayout tracksLayout = new javax.swing.GroupLayout(tracks);
tracks.setLayout(tracksLayout);
tracksLayout.setHorizontalGroup(
tracksLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
);
tracksLayout.setVerticalGroup(
tracksLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 675, Short.MAX_VALUE)
);
timeLine.setLayout(new java.awt.GridLayout(1, 1));
tracksScroll.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
public void adjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {
tracksScrollAdjustmentValueChanged(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(timeLine, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(timeScroll, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 594, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(tracks, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(tracksScroll, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(timeLine, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(tracks, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(tracksScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 675, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(timeScroll, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
}// </editor-fold>//GEN-END:initComponents
private void timeScrollAdjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {//GEN-FIRST:event_timeScrollAdjustmentValueChanged
setOffset(timeScroll.getValue());
}//GEN-LAST:event_timeScrollAdjustmentValueChanged
private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
resizeTracks();
}//GEN-LAST:event_formComponentResized
private void tracksScrollAdjustmentValueChanged(java.awt.event.AdjustmentEvent evt) {//GEN-FIRST:event_tracksScrollAdjustmentValueChanged
tracks.doLayout();
}//GEN-LAST:event_tracksScrollAdjustmentValueChanged
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel timeLine;
private javax.swing.JScrollBar timeScroll;
private javax.swing.JPanel tracks;
private javax.swing.JScrollBar tracksScroll;
// End of variables declaration//GEN-END:variables
public String filename; //project xml file (.sndxml) (may be null)
public String path; //path to store project files
public double scale = 128.0; //# px per second
public double offset = 0.0; //in seconds
public double selectStart = 0.0; //in seconds
public double selectStop = 0.0; //in seconds
public double maxLength = 10.0; //in seconds
public volatile boolean paused = false;
public final Object pausedLock = new Object();
public ArrayList<TrackPanel> list = new ArrayList<TrackPanel>();
public Config config = new Config();
public static class Config {
public int no_options_yet;
}
public void loadConfig() {
try {
XML xml = new XML();
FileInputStream fis = new FileInputStream(filename);
xml.read(fis);
xml.writeClass(config);
} catch (FileNotFoundException e1) {
defaultConfig();
} catch (Exception e2) {
JFLog.log(e2);
defaultConfig();
}
}
public void defaultConfig() {
config = new Config();
}
public void saveConfig() {
try {
XML xml = new XML();
FileOutputStream fos = new FileOutputStream(filename);
xml.readClass("audio", config);
xml.write(fos);
fos.close();
} catch (Exception e) {
JFLog.log(e);
}
}
public String getTabName() {
int i1 = filename.lastIndexOf("/");
int i2 = filename.lastIndexOf(".");
return filename.substring(i1+1, i2);
}
public int genTrackID() {
int tid = list.size();
for(int a=0;a<list.size();) {
if (list.get(a).tid == tid) {tid++; a = 0;} else {a++;}
}
return tid;
}
boolean use_transcoder = true;
public void importFile(String fn) {
// if (fn.toLowerCase().endsWith(".wav")) {
// importWav(fn); //does not support all wav types (ie: non-PCM)
// } else {
if (use_transcoder) importFileTranscoder(fn); else importFileDecoder(fn);
// }
}
public Transcoder transcoder;
public boolean transcoderSuccess;
public File transcoderOutFile;
public String transcoderInFile;
public void importFileTranscoder(String fn) {
try {
transcoder = new Transcoder();
transcoderOutFile = Paths.getTempFile("temp", ".wav");
transcoderInFile = fn;
JFTask task = new JFTask() {
public boolean work() {
this.setProgress(-1); //indeterminate
this.setTitle("Progress");
this.setLabel("Transcoding file...");
transcoderSuccess = transcoder.transcode(transcoderInFile, transcoderOutFile.getAbsolutePath()
, "wav");
return true;
}
};
ProgressDialog dialog = new ProgressDialog(null, true, task);
dialog.setAutoClose(true);
dialog.setVisible(true);
if (!transcoderSuccess) {
transcoderOutFile.delete();
throw new Exception("error");
}
importWav(transcoderOutFile.getAbsolutePath());
transcoderOutFile.delete();
} catch (Exception e) {
JFLog.log(e);
JF.showError("Error", "Import failed");
}
}
public void importFileDecoder(String fn) {
try {
File tempFile = Paths.getTempFile("temp", ".wav");
FileOutputStream fos = new FileOutputStream(tempFile);
Decoder decoder = new Decoder();
decoder.decode(fn);
//write wav header
TrackPanel.writeWavHeader(fos, decoder.getChannels(), decoder.getSampleRate(), 16, 2, 0x7fffffff);
do {
short samples[] = decoder.getSamples();
if (samples == null) break;
fos.write(LE.shortArray2byteArray(samples, null));
} while (true);
fos.close();
importWav(tempFile.getAbsolutePath());
tempFile.delete();
} catch (Exception e) {
JFLog.log(e);
JF.showError("Error", "Import failed");
}
}
public void importWav(String fn) {
//create a new TrackID # and convert WAV into managable files
int tid = genTrackID();
Wav wav = new Wav();
if (!wav.load(fn)) {
JF.showError("Error", wav.errmsg);
return;
}
String trackPath = path + "/" + tid;
File file = new File(trackPath);
file.mkdirs();
TrackPanel track = new TrackPanel(this, tid, wav);
wav.close();
list.add(track);
tracks.add(track);
resizeTracks();
validate();
}
public void loadTracks() {
File file = new File(path);
File trackFolders[] = file.listFiles();
if (trackFolders == null) return;
for(int a=0;a<trackFolders.length;a++) {
String num = trackFolders[a].getName();
if (num.charAt(0) < '0') continue;
if (num.charAt(0) > '9') continue;
if (!trackFolders[a].isDirectory()) continue;
loadTrack(JF.atoi(num));
}
validate();
repaint();
}
public void loadTrack(int tid) {
TrackPanel track = new TrackPanel(this, tid);
tracks.add(track);
list.add(track);
}
public void close() {
if (Paths.isTempPath(path)) {
JFLog.log("deleting temp project:" + path);
try {Paths.deleteFolderEx(path); } catch (Exception e) {}
}
}
private class TimeLine extends JComponent {
public void paint(Graphics g) {
// JFLog.log("TimeLine:scale=" + scale);
int x = getWidth();
int y = getHeight();
g.setColor(Color.GRAY);
g.fillRect(0,0,x,y);
double point_n = 1.0;
double point_d = scale;
if (point_d > 256.0) {
while (point_d > 256.0) {
point_n /= 2.0;
point_d /= 2.0;
}
} else {
while (point_d < 64.0) {
point_n *= 2.0;
point_d *= 2.0;
}
}
double toff = offset;
double toffDec = toff % point_n;
int poff = (int)(-scale * toffDec);
toff -= toffDec;
int h = getHeight();
g.setColor(Color.BLACK);
FontMetrics fm = this.getFontMetrics(this.getFont());
int fh = fm.getHeight();
String txt;
while (poff < getWidth()) {
int min = (int)(toff / 60.0);
int sec = (int)(toff - (min * 60.0));
int sec100 = (int)((toff - (min * 60.0) - sec + .005) * 100.0);
if (toff >= 60.0) {
txt = String.format("%d:%02d:%02d", min, sec, sec100);
} else {
txt = String.format("%d:%02d", sec, sec100);
}
int fw = fm.stringWidth(txt) / 2;
g.drawBytes(txt.getBytes(), 0, txt.length(), poff - fw, h/2 - 5);
g.drawLine(poff,h/2,poff,h);
if (selectStart >= toff && selectStart < toff + point_n) {
int soff = poff + (int)((selectStart - toff) * scale);
g.drawLine(soff, 0, soff, h);
}
if (selectStop >= toff && selectStop < toff + point_n) {
int soff = poff + (int)((selectStop - toff) * scale);
g.drawLine(soff, 0, soff, h);
}
toff += point_n;
poff += point_d;
}
}
public Dimension getPreferredSize() {
return new Dimension(ProjectPanel.this.getWidth(), 32);
}
public Dimension getMaximumSize() {
return getPreferredSize();
}
}
public void zoom(int dir) {
switch (dir) {
case -1:
if (scale <= 0.25) return;
scale /= 2.0;
break;
case 1:
if (scale >= 32.0 * 1024.0) return;
scale *= 2.0;
break;
}
calcMaxLength();
repaint();
}
public void setOffset(int value) {
offset = value / scale;
repaint();
}
public void calcMaxLength() {
maxLength = getWidth() / scale;
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
double trackLength = track.totalLength / track.rate; //in seconds
if (trackLength > maxLength) maxLength = trackLength;
}
maxLength += 10.0f; //show a bit beyond the end
// JFLog.log("maxLength=" + maxLength);
timeScroll.setMinimum(0);
timeScroll.setMaximum((int)(maxLength * scale));
// JFLog.log(" max=" + maxLength * scale);
// timeScroll.setBlockIncrement(?);
// JFLog.log("view=" + getWidth() / scale);
timeScroll.setVisibleAmount(getWidth());
}
public void autoZoom() {
//make sure to call calcMaxLength() first
//find scale that will show maxLength
//TODO
}
public void record() {
//create a new track and add samples to it
paused = false;
TrackPanel track = new TrackPanel(this, genTrackID(), Settings.current.channels, Settings.current.freq, 16);
list.add(track);
tracks.add(track);
resizeTracks();
tracksScroll.setValue(totalTracksHeight());
// setRecVol();
track.record();
validate();
}
public void stop() {
paused = false;
for(int a=0;a<list.size();a++) {
list.get(a).stop();
}
}
public void stopped(int tid) {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.recording) return;
if (track.playing) return;
}
//tracks all done
MainPanel.main.stopped();
}
public void play() {
paused = false;
// setPlayVol();
for(int a=0;a<list.size();a++) {
list.get(a).play();
}
}
public void pause() {
paused = !paused;
synchronized(pausedLock) {
pausedLock.notifyAll();
}
}
public void selectStart(double pos) {
selectStart = pos;
selectStop = selectStart;
repaint();
}
public void selectStop(double pos) {
selectStop = pos;
repaint();
}
public void selectTrack(TrackPanel track, boolean mute) {
//unselect all other tracks
for(int a=0;a<list.size();a++) {
TrackPanel otherTrack = list.get(a);
if (track.tid != otherTrack.tid) otherTrack.unselectAll();
}
MainPanel.main.selectTrack(track);
}
/** ensure offset (in seconds) is visible */
public void showOffset(long offset) {
int value = timeScroll.getValue();
int newValue = (int)(offset * scale);
int start = value;
int end = (int)(value + getWidth());
if (newValue >= start && newValue <= end) return;
timeScroll.setValue(newValue);
}
public void deleteTrack() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
if (track.playing || track.recording) return;
try {
Paths.deleteFolderEx(path + "/" + track.tid);
} catch (Exception e) {
JF.showError("Error", "Failed to delete track from project data folder");
JFLog.log(e);
return;
}
list.remove(a);
tracks.remove(track);
selectStart = selectStop = 0.0;
calcMaxLength();
resizeTracks();
gotoHome();
validate();
repaint();
return;
}
}
}
public void gotoHome() {
showOffset(0);
}
public void gotoEnd() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
showOffset(track.totalLength);
return;
}
}
}
public void mute() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
track.mute();
return;
}
}
}
public void unmute() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
track.unmute();
return;
}
}
}
public void solo() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
track.unmute();
} else {
track.mute();
}
}
}
public boolean isTrackSelected() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) return true;
}
return false;
}
public void exportFile(String fn, boolean selection) {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
track.exportFile(fn, selection);
return;
}
}
JF.showError("Error", "Select a track to export");
}
public TrackPanel getSelectedTrack() {
for(int a=0;a<list.size();a++) {
TrackPanel track = list.get(a);
if (track.selected) {
return track;
}
}
JF.showError("Error", "Select a track");
return null;
}
public void deleteUndo() {
deleteFiles(path + "/undo/");
}
public static void deleteClipboard() {
deleteFiles(MainPanel.clipboardPath + "/");
}
private static void deleteFiles(String path) {
File files[] = new File(path).listFiles();
if (files == null) return;
for(int a=0;a<files.length;a++) {
files[a].delete();
}
}
private void resizeTracks() {
// JFLog.log("resizeTracks");
tracksScroll.setMinimum(0);
tracksScroll.setMaximum(totalTracksHeight());
tracksScroll.setVisibleAmount(tracks.getHeight());
}
public int getTracksWidth() {
return tracks.getWidth();
}
private Dimension layoutSize;
private class TracksLayout implements LayoutManager {
// private Vector<Component> list = new Vector<Component>();
public void addLayoutComponent(String string, Component cmp) {
// list.add(cmp);
}
public void removeLayoutComponent(Component cmp) {
// list.remove(cmp);
}
public Dimension preferredLayoutSize(Container c) {
if (layoutSize == null) layoutContainer(c);
return layoutSize;
}
public Dimension minimumLayoutSize(Container c) {
return new Dimension(1,1);
}
public void layoutContainer(Container c) {
int cnt = c.getComponentCount();
int x = tracks.getWidth();
int cx = 0;
int cy = -tracksScroll.getValue();
for(int a=0;a<cnt;a++) {
Component child = c.getComponent(a);
Dimension d = child.getPreferredSize();
child.setBounds(cx, cy, x, d.height);
cy += d.height;
}
c.setPreferredSize(new Dimension(x, cy));
layoutSize = new Dimension(x, cy);
}
}
public void undo() {
getSelectedTrack().undo();
}
public void mixTracks() {
if (list.size() < 2) {
JF.showError("Error", "Require at least two tracks to mix");
return;
}
MixTracks dialog = new MixTracks(null, true, this, list);
dialog.setVisible(true);
if (!dialog.accepted) return;
}
public int totalTracksHeight() {
int height = 0;
for(int a=0;a<list.size();a++) {
height += list.get(a).channels * 127;
}
return height;
}
public void newTrack(int chs) {
int tid = genTrackID();
String trackPath = path + "/" + tid;
File file = new File(trackPath);
file.mkdirs();
TrackPanel track = new TrackPanel(this, tid, chs, Settings.current.freq, 16);
list.add(track);
tracks.add(track);
resizeTracks();
validate();
}
}