/* Track Panel
*
* Created : Jun 14, 2012
*
* @author pquiring
*/
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javaforce.*;
import javaforce.media.*;
public class TrackPanel extends javax.swing.JPanel {
/**
* Creates new form TrackPanel
*/
public TrackPanel(ProjectPanel project, int tid, Wav wav) {
initComponents();
setLayout(new TrackLayout());
this.project = project;
this.tid = tid;
new File(project.path + "/" + tid).mkdir();
importWav(wav);
initForms();
}
public TrackPanel(ProjectPanel project, int tid, int chs, int rate, int bits) {
initComponents();
setLayout(new TrackLayout());
this.project = project;
this.tid = tid;
this.channels = chs;
this.rate = rate;
this.bits = bits;
bytes = bits / 8;
new File(project.path + "/" + tid).mkdir();
writeMainHeader();
initForms();
}
public TrackPanel(ProjectPanel project, int tid) {
initComponents();
setLayout(new TrackLayout());
this.project = project;
this.tid = tid;
new File(project.path + "/" + tid).mkdir();
loadChunks();
initForms();
}
/**
* 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() {
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 626, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 83, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
public ProjectPanel project;
public int tid = -1;
public int channels = -1;
public int bits = -1;
public int bytes = -1;
public int rate = -1;
public int first_cid = 0;
public long totalLength = 0; //in samples
public static final int lowRes = 256; //# of samples lowRes per hiRes
public static final int maxChunkSize = 256 * 1024; //in samples
public long selectStart, selectStop; //in samples
public volatile boolean muted = false;
public boolean selected = false;
public int undoAction1; //ACTION_CUT / MODIFY / PASTE
public long undoAction1OffsetStart;
public long undoAction1OffsetStop;
public int undoAction2; //ACTION_PASTE only
public long undoAction2OffsetStart;
public long undoAction2OffsetStop;
public int undoRate = 0;
public static final int ACTION_NA = 0;
public static final int ACTION_CUT = 1;
public static final int ACTION_MODIFY = 2;
public static final int ACTION_PASTE = 3;
public static class MainHeader {
//all chunks must be the same format as this
public int channels;
public int bits;
public int bytes;
public int rate;
public int first_cid; //first chunk id
public void write(OutputStream os) throws Exception {
JF.writeuint32(os, channels);
JF.writeuint32(os, bits);
JF.writeuint32(os, bytes);
JF.writeuint32(os, rate);
JF.writeuint32(os, first_cid);
}
public void read(InputStream is) throws Exception {
channels = JF.readuint32(is);
bits = JF.readuint32(is);
bytes = JF.readuint32(is);
rate = JF.readuint32(is);
first_cid = JF.readuint32(is);
}
public void write(RandomAccessFile raf) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
write(baos);
raf.write(baos.toByteArray());
}
}
/** ClipHeader - used for undo and clipboard */
public static class ClipHeader {
public int tid;
public long offset, length;
public int channels;
public int bits;
public int bytes;
public int rate;
public void write(OutputStream os) throws Exception {
JF.writeuint32(os, tid);
JF.writeuint64(os, offset);
JF.writeuint64(os, length);
JF.writeuint32(os, channels);
JF.writeuint32(os, bits);
JF.writeuint32(os, bytes);
JF.writeuint32(os, rate);
}
public void read(InputStream is) throws Exception {
tid = JF.readuint32(is);
offset = JF.readuint64(is);
length = JF.readuint64(is);
channels = JF.readuint32(is);
bits = JF.readuint32(is);
bytes = JF.readuint32(is);
rate = JF.readuint32(is);
}
}
private static final int chunkHeaderLength = 8;
public static class ChunkHeader {
public int cid; //not written to disk
public int length; //in samples
public int next_cid; //-1 = last chunk
public Object lock = new Object(); //not written to disk
public void write(OutputStream os) throws Exception {
JF.writeuint32(os, length);
JF.writeuint32(os, next_cid);
}
public void write(RandomAccessFile raf) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
write(baos);
raf.write(baos.toByteArray());
}
public void read(InputStream is) throws Exception {
length = JF.readuint32(is);
next_cid = JF.readuint32(is);
}
public void read(RandomAccessFile raf) throws Exception {
byte tmp[] = new byte[chunkHeaderLength];
raf.read(tmp);
ByteArrayInputStream bais = new ByteArrayInputStream(tmp);
read(bais);
}
}
private ArrayList<ChunkHeader> list = new ArrayList<ChunkHeader>();
private WaveForm forms[];
private void loadChunks() {
try {
MainHeader main = new MainHeader();
FileInputStream fis = new FileInputStream(project.path + "/" + tid + "/track.dat");
main.read(fis);
this.channels = main.channels;
this.bits = main.bits;
this.bytes = main.bytes;
this.rate = main.rate;
int cid = main.first_cid;
fis.close();
while (cid != -1) {
fis = new FileInputStream(project.path + "/" + tid + "/c" + cid + "-0.dat");
ChunkHeader chunk = new ChunkHeader();
chunk.read(fis);
fis.close();
list.add(chunk);
totalLength += chunk.length;
if (chunk.next_cid == cid) throw new Exception("chunk sequence error");
cid = chunk.next_cid;
}
} catch (Exception e) {
JFLog.log(e);
}
}
public void writeMainHeader() {
try {
MainHeader main = new MainHeader();
main.channels = channels;
main.rate = rate;
main.bits = bits;
main.bytes = bytes;
main.first_cid = first_cid;
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/track.dat", "rw");
main.write(raf);
raf.close();
} catch (Exception e) {
JFLog.log(e);
}
}
private void importWav(Wav wav) {
JFLog.log("importing wav");
MainHeader main = new MainHeader();
main.channels = wav.chs;
main.rate = wav.rate;
main.bits = wav.bits;
main.bytes = wav.bytes;
if (main.bits == 24) {
//must upgrade to 32bits, there is no 24bit integer
main.bits = 32;
main.bytes = 4;
}
channels = main.channels;
rate = main.rate;
bits = main.bits;
bytes = main.bytes;
writeMainHeader();
long length = wav.dataLength/wav.bytes/wav.chs; //in samples
totalLength = length;
JFTask task = new JFTask() {
public boolean work() {
Wav wav = (Wav)this.getProperty("wav");
this.setLabel("Importing file...");
this.setTitle("Progress");
try {
long toRead = totalLength;
int cid = 0;
byte samples[], chSamples[];
while (toRead > 0) {
this.setProgress((int)((totalLength - toRead) * 100 / totalLength));
if (abort) return true;
int read = maxChunkSize;
if (read > toRead) read = (int)toRead;
samples = wav.readSamples(read);
ChunkHeader chunk = new ChunkHeader();
chunk.cid = cid;
chunk.length = read;
if (read == toRead) {
chunk.next_cid = -1;
} else {
chunk.next_cid = cid+1;
}
chSamples = new byte[read * bytes];
for(int ch=0;ch<channels;ch++) {
int dst, src;
for(int s=0;s<read;s++) {
src = s * channels * bytes + ch * bytes;
switch (bits) {
case 16:
dst = s * 2;
chSamples[dst + 0] = samples[src + 0];
chSamples[dst + 1] = samples[src + 1];
break;
case 32:
dst = s * 4;
chSamples[dst + 0] = samples[src + 0];
chSamples[dst + 1] = samples[src + 1];
chSamples[dst + 2] = samples[src + 2];
chSamples[dst + 3] = samples[src + 3];
break;
}
}
FileOutputStream fos = new FileOutputStream(project.path + "/" + tid + "/c" + cid + "-" + ch + ".dat");
chunk.write(fos);
fos.write(chSamples);
fos.close();
genLowResChunk(chSamples, chunk, ch);
}
list.add(chunk);
toRead -= read;
cid++;
}
JFLog.log("import complete");
project.calcMaxLength();
project.autoZoom();
repaint();
} catch (Exception e) {
JFLog.log(e);
JF.showError("Error", e.toString());
}
return true;
}
};
task.setProperty("wav", wav);
ProgressDialog dialog = new ProgressDialog(null, true, task);
dialog.setAutoClose(true);
dialog.setVisible(true);
}
private void genLowResChunk(byte samples[], ChunkHeader hiResChunk, int ch) throws Exception {
//creates a low-res 8bit copy of a chunk (lowRes samples average)
FileOutputStream fos = new FileOutputStream(project.path + "/" + tid + "/a" + hiResChunk.cid + "-" + ch + ".dat");
ChunkHeader chunk = new ChunkHeader();
chunk.cid = hiResChunk.cid;
chunk.length = hiResChunk.length;
chunk.next_cid = hiResChunk.next_cid;
chunk.write(fos);
int len = chunk.length / lowRes;
if (len == 0) return;
byte avg[] = new byte[len];
switch (bits) {
case 16:
short samples16[] = LE.byteArray2shortArray(samples, null);
for(int a=0;a<len;a++) {
short val = 0, max = 0;
int pos = a * lowRes;
for(int b=0;b<lowRes;b++) {
val = (short)Math.abs(samples16[pos + b]);
if (val > max) max = val;
}
max >>= 8;
avg[a] = (byte)max;
}
break;
case 32:
int samples32[] = LE.byteArray2intArray(samples, null);
for(int a=0;a<len;a++) {
int val = 0, max = 0;
int pos = a * lowRes;
for(int b=0;b<lowRes;b++) {
val = Math.abs(samples32[pos + b]);
if (val > max) max = val;
}
max >>= 24;
avg[a] = (byte)max;
}
break;
}
fos.write(avg);
fos.close();
}
private boolean showChunks = false; //for testing
private class WaveForm extends JComponent implements MouseListener, MouseMotionListener {
public int channel;
public WaveForm(int channel) {
this.channel = channel;
}
public void paint(Graphics g) {
//clear background (muted = black, normal = gray)
if (muted)
g.setColor(Color.BLACK);
else
g.setColor(Color.GRAY);
g.fillRect(0,0,getWidth(),getHeight());
double rateScale = (rate / project.scale);
//paint selection (dark gray)
g.setColor(Color.DARK_GRAY);
int sStart = (int)((selectStart - project.offset * rate) / (rate / project.scale));
int sStop = (int)((selectStop - project.offset * rate) / (rate / project.scale));
if (sStart == sStop) {
sStop++;
}
// JFLog.log("start:" + sStart + ",stop:" + sStop);
if (sStart < sStop) {
g.fillRect(sStart, 0, sStop - sStart, getHeight());
} else {
g.fillRect(sStop, 0, sStart - sStop, getHeight());
}
//paint selected border
if (selected) {
g.setColor(Color.YELLOW);
g.drawRect(0,0,getWidth()-1,getHeight()-1);
}
//paint waveForm
if (rateScale >= lowRes) {
paintLowRes(g);
} else {
paintHiRes(g);
}
}
public void paintHiRes(Graphics g) {
double rateScale = (rate / project.scale);
int intRateScale = (int)rateScale;
// JFLog.log("paintHiRes:offset=" + project.offset + ":rateScale=" + rateScale + ":int=" + intRateScale + ":channel=" + channel);
double rateScaleDec = (rate / project.scale) - (double)intRateScale;
// JFLog.log("rateScaleDec=" + rateScaleDec);
double rateScaleCnt;
int length = getWidth(); //in px
long start = (long)(project.offset * rate); //in samples
long end = (long)(start + (length * rateScale)); //in samples
long skip = start; //in samples
int pos = 0;
g.setColor(Color.BLUE);
int cid = first_cid;
try {
int listSize = list.size();
int px = 0;
for(int lidx=0;lidx<listSize;lidx++) {
boolean first = showChunks;
if (pos == length) break;
ChunkHeader chunk = list.get(lidx);
synchronized(chunk.lock) {
if (chunk.length <= skip) {
skip -= chunk.length;
continue;
}
rateScaleCnt = 0.0;
// JFLog.log("chunk.length=" + chunk.length);
FileInputStream fis = new FileInputStream(project.path + "/" + tid + "/c" + chunk.cid + "-" + channel + ".dat");
fis.skip(chunkHeaderLength);
long chunkLength = chunk.length;
if (skip > 0) {
chunkLength -= skip;
fis.skip(skip * bytes);
skip = 0;
}
int extraSample;
while (chunkLength > 0) {
rateScaleCnt += rateScaleDec;
if (rateScaleCnt >= 1.0) {
rateScaleCnt -= 1.0;
extraSample = 1;
} else {
extraSample = 0;
}
int samplesToRead = (intRateScale + extraSample);
if (samplesToRead > 0) {
if (samplesToRead > chunkLength) samplesToRead = (int)chunkLength;
byte samples[] = new byte[samplesToRead * bytes];
JF.readAll(fis, samples, 0, samples.length);
int max = 0;
switch (bits) {
case 16:
short samples16[] = LE.byteArray2shortArray(samples, null);
for(int a=0;a<samples16.length;a++) {
int val = Math.abs(samples16[a]);
if (val > max) max = val;
}
max >>= 8;
break;
case 32:
int samples32[] = LE.byteArray2intArray(samples, null);
for(int a=0;a<samples32.length;a++) {
int val = Math.abs(samples32[a]);
if (val > max) max = val;
}
max >>= 24;
break;
}
px = (byte)(max >> 1);
}
if (first) {
g.setColor(Color.RED);
px = 63;
}
g.drawLine(pos, 64 - px, pos, 64 + px);
if (first) {
g.setColor(Color.BLUE);
first = false;
}
pos++;
if (pos == length) break;
chunkLength -= samplesToRead;
}
// JFLog.log("last.pos=" + pos);
fis.close();
}
}
} catch (Exception e) {
JFLog.log(e);
}
}
public void paintLowRes(Graphics g) {
//basically same as paint() except lowResRate = rate/lowRes; bytes=1;
// JFLog.log("paintLowRes:scale=" + project.scale);
double rateScale = (rate / project.scale);
double lowResRate = rate / lowRes;
double lowResRateScale = (lowResRate / project.scale);
int intLowResRateScale = (int)lowResRateScale;
double lowResRateScaleDec = (lowResRate / project.scale) - (double)intLowResRateScale;
double lowResRateScaleCnt;
int length = getWidth(); //in px
long start = (long)(project.offset * lowResRate); //in samples/lowRes
long end = (long)(start + (length * lowResRateScale)); //in samples/lowRes
long skip = start; //in samples/lowRes
int pos = 0;
g.setColor(Color.BLUE);
try {
int listSize = list.size();
for(int lidx=0;lidx<listSize;lidx++) {
boolean first = showChunks;
if (pos == length) break;
ChunkHeader chunk = list.get(lidx);
synchronized(chunk.lock) {
if (chunk.length / lowRes <= skip) {
skip -= chunk.length / lowRes;
continue;
}
lowResRateScaleCnt = 0.0;
FileInputStream fis = new FileInputStream(project.path + "/" + tid + "/a" + chunk.cid + "-" + channel + ".dat");
fis.skip(chunkHeaderLength);
long chunkLength = chunk.length / lowRes;
if (skip > 0) {
chunkLength -= skip;
fis.skip(skip);
skip = 0;
}
int extraSample;
while (chunkLength > 0) {
lowResRateScaleCnt += lowResRateScaleDec;
if (lowResRateScaleCnt >= 1.0) {
lowResRateScaleCnt -= 1.0;
extraSample = 1;
} else {
extraSample = 0;
}
int samplesToRead = intLowResRateScale + extraSample;
if (samplesToRead > chunkLength) samplesToRead = (int)chunkLength;
byte samples[] = new byte[samplesToRead];
JF.readAll(fis, samples, 0, samplesToRead);
int px;
int max = 0;
for(int a=0;a<samples.length;a++) {
int val = samples[a];
if (val > max) max = val;
}
px = (byte)(max >> 1);
if (first) {
g.setColor(Color.RED);
px = 63;
}
g.drawLine(pos, 64 - px, pos, 64 + px);
if (first) {
g.setColor(Color.BLUE);
first = false;
}
pos++;
if (pos == length) break;
chunkLength -= samplesToRead;
}
fis.close();
}
}
} catch (Exception e) {
JFLog.log(e);
}
}
//height = 63+1+63 = 127
public Dimension getPreferredSize() {
return new Dimension(project.getTracksWidth(), 127);
}
public Dimension getMaximumSize() {
return getPreferredSize();
}
public void mouseClicked(MouseEvent me) {
}
public void mousePressed(MouseEvent me) {
double timeOffset = (((double)me.getX()) / ((double)project.scale)) + project.offset;
project.selectStart(timeOffset);
double rateScale = rate / project.scale;
selectStart = (long)(((double)me.getX()) * (rateScale) + project.offset * ((double)rate));
if (selectStart < 0) selectStart = 0;
selectStop = selectStart;
selectTrack(true);
repaint();
}
public void mouseReleased(MouseEvent me) {
double timeOffset = me.getX() / project.scale + project.offset;
project.selectStop(timeOffset);
selectStop = (long)(me.getX() * (rate / project.scale) + project.offset * rate);
if (selectStop < 0) selectStop = 0;
repaint();
}
public void mouseEntered(MouseEvent me) {
}
public void mouseExited(MouseEvent me) {
}
public void mouseDragged(MouseEvent me) {
mouseReleased(me);
}
public void mouseMoved(MouseEvent me) {
}
}
private void initForms() {
forms = new WaveForm[channels];
for(int a=0;a<channels;a++) {
forms[a] = new WaveForm(a);
forms[a].addMouseListener(forms[a]);
forms[a].addMouseMotionListener(forms[a]);
add(forms[a]);
}
}
public Dimension getPreferredSize() {
return new Dimension(project.getWidth(),127 * channels);
}
public Dimension getMaximumSize() {
return getPreferredSize();
}
public void swapEndian(int bits, byte samples[]) {
byte tmp;
int len = samples.length;
switch (bits) {
case 16:
for(int a=0;a<len;a+=2) {
tmp = samples[a];
samples[a] = samples[a+1];
samples[a+1] = tmp;
}
break;
case 32:
for(int a=0;a<len;a+=4) {
tmp = samples[a];
samples[a] = samples[a+3];
samples[a+3] = tmp;
tmp = samples[a+1];
samples[a+1] = samples[a+2];
samples[a+2] = tmp;
}
break;
}
}
private void scaleBufferVolume(byte buf[], int bits, int scale) {
int len = buf.length;
short s16;
int s32;
if (scale == 0) {
for (int a = 0; a < len; a++) {
buf[a] = 0;
}
} else {
switch (bits) {
case 16:
float fscale = (float)scale / 100f;
for (int a = 0; a < len; a+=2) {
s16 = (short)LE.getuint16(buf, a);
s16 *= fscale;
LE.setuint16(buf, a, s16);
}
break;
case 32:
double dscale = (double)scale / 100d;
for (int a = 0; a < len; a+=4) {
s32 = LE.getuint32(buf, a);
s32 *= dscale;
LE.setuint32(buf, a, s32);
}
break;
}
}
}
public volatile boolean recording = false;
public volatile boolean playing = false;
private static final int recBufSize = 256; //in bytes (not samples)
public void record() {
selectTrack(true);
this.rate = Settings.current.freq;
recording = true;
new Thread() {
public void run() {
long length = 0;
AudioInput input = new AudioInput();
input.listDevices();
if (!input.start(Settings.current.channels, Settings.current.freq, bits, recBufSize, Settings.getInput())) {
MainPanel.main.stop();
JF.showError("Error", "Failed to open recording device.");
return;
}
byte samples[] = new byte[recBufSize];
while (recording) {
if (!input.read(samples)) {
JF.sleep(10);
continue;
}
swapEndian(bits, samples);
int lvl = MainPanel.main.getRecordLevel();
if (lvl < 100) {
scaleBufferVolume(samples, bits, lvl);
}
addSamples(samples);
length += samples.length / channels / bytes;
project.calcMaxLength();
project.showOffset(length / rate);
repaint();
}
input.stop();
}
}.start();
}
public final int playBufferSize = 4 * 1024; //keep small for better response (in samples)
public void play() {
playing = true;
new Thread() {
public void run() {
int bufferSize = playBufferSize * bytes * channels;
AudioOutput output = new AudioOutput();
output.listDevices();
long offset = selectStart;
boolean first = true;
int firstNumSamples = 0;
int numSamples;
output.start(channels, rate, bits, bufferSize, Settings.getOutput());
while (playing) {
synchronized(project.pausedLock) {
if (project.paused) {
try {project.pausedLock.wait();} catch (Exception e) {}
}
}
byte samples[] = getSamples(offset, playBufferSize);
if (samples == null) break;
if (samples.length != bufferSize) break;
if (muted) {
Arrays.fill(samples, (byte)0);
}
numSamples = samples.length / channels / bytes;
int lvl = MainPanel.main.getPlayLevel();
if (lvl != 100) {
scaleBufferVolume(samples, bits, lvl);
}
swapEndian(bits, samples);
output.write(samples);
offset += playBufferSize;
if (first) {first = false; firstNumSamples = numSamples; continue;}
JF.sleep(numSamples * 1000 / rate);
}
JF.sleep(firstNumSamples * 1000 / rate * 2); //*2 for the delay in starting
output.stop();
playing = false;
project.stopped(tid);
}
}.start();
}
public void stopRecording() {
recording = false;
}
public void stopPlaying() {
playing = false;
}
public void stop() {
if (recording) stopRecording(); else stopPlaying();
}
/** Adds samples to the end of this track. */
public void addSamples(byte data[]) {
//check last chunk
int samplesLength = data.length / channels / bytes;
totalLength += samplesLength;
int cid; //chunk id
ChunkHeader chunk = new ChunkHeader();
ChunkHeader lastChunk;
if (list.size() > 0) {
lastChunk = list.get(list.size() - 1);
if (lastChunk.length + samplesLength <= maxChunkSize) {
//add to last chunk
synchronized(lastChunk.lock) {
lastChunk.length += samplesLength;
try {
cid = list.size() - 1; //last chunk
ByteArrayOutputStream baos = new ByteArrayOutputStream();
lastChunk.write(baos);
for(int ch=0;ch<channels;ch++) {
byte samples[] = sliceSamples(data, ch);
RandomAccessFile file = new RandomAccessFile(project.path + "/" + tid + "/c" + cid + "-" + ch + ".dat", "rw");
int fileLength = (int)file.length();
byte header[] = baos.toByteArray();
file.seek(0);
file.write(header);
file.seek(fileLength);
file.write(samples);
int allLength = fileLength - header.length + samples.length;
byte allSamples[] = new byte[allLength];
file.seek(header.length);
JF.readAll(file, allSamples, 0, fileLength - header.length);
System.arraycopy(samples, 0, allSamples, fileLength - header.length, samples.length);
file.close();
genLowResChunk(allSamples, lastChunk, ch);
}
repaint();
} catch (Exception e) {
JFLog.log(e);
}
}
return;
}
}
//create new chunk (after last chunk)
cid = genChunkID();
//create new chunk
chunk.cid = cid;
chunk.length = samplesLength;
chunk.next_cid = -1;
try {
for(int ch=0;ch<channels;ch++) {
byte samples[] = sliceSamples(data, ch);
FileOutputStream fos = new FileOutputStream(project.path + "/" + tid + "/c" + cid + "-" + ch + ".dat");
chunk.write(fos);
fos.write(samples);
fos.close();
genLowResChunk(samples, chunk, ch);
}
} catch (Exception e) {
JFLog.log(e);
}
if (list.size() > 0) {
patchChunk(list.size()-1, cid); //patch next_cid of last chunk to this new chunk
}
list.add(chunk);
repaint();
}
/** Returns samples for all channels. This is primarily for play() function. */
public byte[] getSamples(long offset, int length) {
ChunkHeader chunk;
byte samples[] = new byte[length * channels * bytes];
boolean ok = false;
//JFLog.log("getSamples(1):" + offset + "," + length);
int pos = 0;
for(int a=0;a<list.size();a++) {
chunk = list.get(a);
if (chunk.length <= offset) {
offset -= chunk.length;
continue;
}
ok = true;
int skip;
if (offset > 0) {
skip = (int)offset;
offset = 0;
} else {
skip = 0;
}
int chunkLength = (int)(chunk.length - skip);
if (chunkLength > length) {
chunkLength = length;
}
//JFLog.log("getSamples(2):" + chunk.cid + "," + chunk.offset + "," + chunk.length + "," + start + "," + chunkLength);
try {
byte read[] = new byte[chunkLength * bytes];
for(int ch=0;ch<channels;ch++) {
FileInputStream fis = new FileInputStream(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat");
fis.skip(chunkHeaderLength);
fis.skip(skip * bytes);
JF.readAll(fis, read, 0, read.length);
fis.close();
int dstpos = pos + ch * bytes;
int srcpos = 0;
for(int s=0;s<chunkLength;s++) {
switch (bits) {
case 16:
samples[dstpos + 0] = read[srcpos + 0];
samples[dstpos + 1] = read[srcpos + 1];
break;
case 32:
samples[dstpos + 0] = read[srcpos + 0];
samples[dstpos + 1] = read[srcpos + 1];
samples[dstpos + 2] = read[srcpos + 2];
samples[dstpos + 3] = read[srcpos + 3];
break;
}
dstpos += channels * bytes;
srcpos += bytes;
}
}
pos += chunkLength * channels * bytes;
length -= chunkLength;
} catch (Exception e) {
JFLog.log(e);
}
if (length == 0) break;
}
if (!ok) return null;
return samples;
}
/** Returns samples for one channel. */
public byte[] getSamples(long offset, int length, int ch) {
ChunkHeader chunk;
byte samples[] = new byte[length * bytes];
boolean ok = false;
//JFLog.log("getSamples(1):" + offset + "," + length);
int pos = 0;
for(int a=0;a<list.size();a++) {
chunk = list.get(a);
if (chunk.length <= offset) {
offset -= chunk.length;
continue;
}
ok = true;
int skip;
if (offset > 0) {
skip = (int)offset;
offset = 0;
} else {
skip = 0;
}
int chunkLength = (int)(chunk.length - skip);
if (chunkLength > length) {
chunkLength = length;
}
//JFLog.log("getSamples(2):" + chunk.cid + "," + chunk.offset + "," + chunk.length + "," + start + "," + chunkLength);
try {
byte read[] = new byte[chunkLength * bytes];
FileInputStream fis = new FileInputStream(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat");
fis.skip(chunkHeaderLength);
fis.skip(skip * bytes);
JF.readAll(fis, read, 0, read.length);
fis.close();
int dstpos = pos;
int srcpos = 0;
for(int s=0;s<chunkLength;s++) {
switch (bits) {
case 16:
samples[dstpos + 0] = read[srcpos + 0];
samples[dstpos + 1] = read[srcpos + 1];
break;
case 32:
samples[dstpos + 0] = read[srcpos + 0];
samples[dstpos + 1] = read[srcpos + 1];
samples[dstpos + 2] = read[srcpos + 2];
samples[dstpos + 3] = read[srcpos + 3];
break;
}
dstpos += bytes;
srcpos += bytes;
}
pos += chunkLength * bytes;
length -= chunkLength;
} catch (Exception e) {
JFLog.log(e);
}
if (length == 0) break;
}
if (!ok) return null;
return samples;
}
/** Sets samples for one channel. */
public void setSamples(long offset, byte samples[], int ch) {
ChunkHeader chunk;
int samplesPos = 0; //in samples
int length = samples.length / bytes;
for(int a=0;a<list.size();a++) {
chunk = list.get(a);
if (chunk.length <= offset) {
offset -= chunk.length;
continue;
}
int skip; //in samples
if (offset > 0) {
skip = (int)offset;
offset = 0;
} else {
skip = 0;
}
int chunkLength = (int)(chunk.length - skip);
if (chunkLength > length) {
chunkLength = length;
}
try {
byte allSamples[] = new byte[chunk.length * bytes];
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat", "rw");
raf.seek(chunkHeaderLength);
JF.readAll(raf, allSamples, 0, chunk.length * bytes);
raf.seek(chunkHeaderLength + skip * bytes);
raf.write(samples, samplesPos * bytes, chunkLength * bytes);
System.arraycopy(samples, samplesPos * bytes, allSamples, skip * bytes, chunkLength * bytes);
raf.close();
genLowResChunk(allSamples, chunk, ch);
samplesPos += chunkLength;
length -= chunkLength;
} catch (Exception e) {
JFLog.log(e);
}
if (length == 0) break;
}
}
/** Returns a channel of samples from a combined samples array. */
public byte[] sliceSamples(byte in[], int ch) {
int length = in.length / channels;
int samples = length / bytes;
byte out[] = new byte[length];
int dstpos = 0;
int srcpos = ch * bytes;
for(int a=0;a<samples;a++) {
for(int b=0;b<bytes;b++) {
out[dstpos++] = in[srcpos++];
}
srcpos += (channels - 1) * bytes; //skip other channels
}
return out;
}
public void unselectAll() {
selectStart = selectStop = 0;
selected = false;
repaint();
}
public void mute() {
muted = true;
repaint();
}
public void unmute() {
muted = false;
repaint();
}
public Transcoder transcoder;
public boolean transcoderSuccess;
public File transcoderInFile;
public String transcoderOutFile;
public String transcoderCodec;
public int transcoderBitRate;
public void exportFile(String fn, boolean selection) {
if (fn.toLowerCase().endsWith(".wav")) {
if (exportWav(fn, selection)) JF.showMessage("Notice", "Export complete");
} else {
//export to Wav to temp file and then convert using CodecPack
transcoderCodec = null;
if (fn.toLowerCase().endsWith(".flac")) {
transcoderCodec = "flac";
}
if (fn.toLowerCase().endsWith(".ogg")) {
transcoderCodec = "ogg";
}
if (fn.toLowerCase().endsWith(".wma")) {
transcoderCodec = "wma";
}
if (fn.toLowerCase().endsWith(".mp3")) {
AudioApp.inDialog = true;
String bitRate = JF.getString("Enter MP3 BitRate (16-384)K", "128");
AudioApp.inDialog = false;
if (bitRate == null) return;
transcoderBitRate = JF.atoi(bitRate);
if (transcoderBitRate < 16) transcoderBitRate = 16;
if (transcoderBitRate > 384) transcoderBitRate = 384;
transcoderCodec = "mp3";
}
if (transcoderCodec == null) {
JF.showError("Error", "Unsupported codec");
return;
}
try {
transcoder = new Transcoder();
transcoder.encoder.setAudioBitRate(transcoderBitRate * 1000);
transcoderInFile = Paths.getTempFile("temp", ".wav");
transcoderOutFile = fn;
if (!exportWav(transcoderInFile.getAbsolutePath(), selection)) return;
JFTask task = new JFTask() {
public boolean work() {
this.setProgress(-1); //indeterminate
this.setTitle("Progress");
this.setLabel("Transcoding file...");
transcoderSuccess = transcoder.transcode(transcoderInFile.getAbsolutePath(), transcoderOutFile
, transcoderCodec);
return true;
}
};
ProgressDialog dialog = new ProgressDialog(null, true, task);
dialog.setAutoClose(true);
dialog.setVisible(true);
// transcoderInFile.delete(); //test
if (transcoderSuccess)
JF.showMessage("Notice", "Export complete");
else
JF.showError("Error", "Export failed");
} catch (Exception e) {
JFLog.log(e);
JF.showError("Error", "Export failed");
}
}
}
public static void writeWavHeader(FileOutputStream fos, int channels, int rate, int bits, int bytes, int dataSize) throws Exception {
fos.write("RIFF".getBytes());
JF.writeuint32(fos, dataSize + 36);
fos.write("WAVE".getBytes());
fos.write("fmt ".getBytes());
JF.writeuint32(fos, 16); //fmt chunk size
JF.writeuint16(fos, 1); //PCM format
JF.writeuint16(fos, channels);
JF.writeuint32(fos, rate);
JF.writeuint32(fos, rate * channels * bytes); //byte rate
JF.writeuint16(fos, channels * bytes); //block align
JF.writeuint16(fos, bits);
fos.write("data".getBytes());
JF.writeuint32(fos, dataSize);
}
public boolean exportWav(String fn, boolean selection) {
long dataSize;
long length, sStart, sStop;
long offset = 0;
if (selection) {
if (selectStart == selectStop) {
JF.showError("Error", "No selection to export");
return false;
}
if (selectStop < selectStart) {
sStart = (int)selectStop;
sStop = (int)selectStart;
} else {
sStart = (int)selectStart;
sStop = (int)selectStop;
}
length = (sStop - sStart + 1);
dataSize = length * channels * bytes;
offset = sStart;
} else {
dataSize = totalLength * channels * bytes;
length = totalLength;
}
if (dataSize > Integer.MAX_VALUE) {
JF.showError("Error", "Output too large for WAV file");
return false;
}
try {
FileOutputStream fos = new FileOutputStream(fn);
writeWavHeader(fos, channels, rate, bits, bytes, (int)dataSize);
// JFLog.log("dataSize=" + dataSize);
JFTask task = new JFTask() {
private FileOutputStream fos;
long length;
long offset;
public boolean work() {
fos = (FileOutputStream)this.getProperty("fos");
offset = (Long)this.getProperty("offset");
length = (Long)this.getProperty("length");
this.setLabel("Exporting file...");
this.setTitle("Progress");
long fullLength = length;
try {
while (length > 0) {
this.setProgress((int)((fullLength - length) * 100 / fullLength));
int read = 64 * 1024;
if (read > length) read = (int)length;
byte samples[] = getSamples(offset, read);
if (samples == null) break;
fos.write(samples);
offset += read;
length -= read;
}
fos.close();
} catch (Exception e) {
JFLog.log(e);
}
return true;
}
};
task.setProperty("fos", fos);
task.setProperty("offset", offset);
task.setProperty("length", length);
ProgressDialog dialog = new ProgressDialog(null, true, task);
dialog.setAutoClose(true);
dialog.setVisible(true);
return true;
} catch (Exception e) {
JFLog.log(e);
JF.showError("Error", "Export Failed");
return false;
}
}
public void selectTrack(boolean unselectOthers) {
selected = true;
if (unselectOthers) project.selectTrack(this, muted);
repaint();
}
public void setRate(int newRate) {
rate = newRate;
writeMainHeader();
}
private void calcLength() {
//recalc totalLength
int listSize = list.size();
totalLength = 0;
for(int lidx = 0;lidx<listSize;lidx++) {
ChunkHeader chunk = list.get(lidx);
totalLength += chunk.length;
}
}
public void cut() {
//cut selection to clipboard
JFLog.log("cut");
if (selectStart == selectStop) return;
copySelection(MainPanel.clipboardPath);
delete(true);
}
public void copy() {
//copy selection to clipboard
JFLog.log("copy");
if (selectStart == selectStop) return;
copySelection(MainPanel.clipboardPath);
}
public void paste(String path, boolean replaceSelection) {
JFLog.log("paste");
ClipHeader clip = new ClipHeader();
try {
FileInputStream fis = new FileInputStream(path + "/clip.dat");
clip.read(fis);
fis.close();
if (clip.length == 0) {JFLog.log("clip is empty"); return;}
if (clip.bits != bits) {
JF.showError("Error", "Clipboard bits doesn't match this track");
return;
}
if (clip.channels != channels) {
JF.showError("Error", "Clipboard channels doesn't match this track");
return;
}
if (clip.rate != rate) {
if (!JF.showConfirm("Warning", "Clipboard sample rate doesn't match this track, use anyways?")) {
return;
}
}
if (replaceSelection) {
if (selectStart != selectStop) {
delete(true);
}
undoAction2 = ACTION_PASTE;
undoAction2OffsetStart = selectStart;
undoAction2OffsetStop = selectStart + clip.length;
}
//find where selectStart is
long offset = selectStart;
int listSize = list.size();
ChunkHeader chunk;
for(int lidx=0;lidx<listSize;lidx++) {
chunk = list.get(lidx);
if (chunk.length <= offset) {
offset -= chunk.length;
continue;
}
if (offset == 0) {
//insert just before this chunk
int _first_cid = copyChunks(path, chunk.cid, lidx);
if (lidx > 0) {
patchChunk(lidx, _first_cid);
} else {
first_cid = _first_cid;
writeMainHeader();
}
calcLength();
repaint();
return;
} else {
//insert somewhere in this chunk
//split this chunk into 2 chunks @ offset
splitChunk(lidx, (int)offset);
lidx++;
chunk = list.get(lidx);
int _first_cid = copyChunks(path, chunk.cid, lidx);
patchChunk(lidx, _first_cid);
calcLength();
repaint();
return;
}
}
//insert at end of track
int _first_cid = copyChunks(path, -1, listSize);
if (listSize > 0) {
patchChunk(listSize-1, _first_cid);
} else {
first_cid = _first_cid;
writeMainHeader();
}
calcLength();
repaint();
} catch (Exception e) {
JFLog.log(e);
}
}
public void splitChunk(int lidx, int length1) {
JFLog.log("splitChunk:" + lidx + "," + length1);
try {
ChunkHeader chunk1 = list.get(lidx);
ChunkHeader chunk2 = new ChunkHeader();
chunk2.length = (int)(chunk1.length - length1);
chunk1.length = (int)length1;
chunk2.cid = genChunkID();
chunk2.next_cid = chunk1.next_cid;
chunk1.next_cid = chunk2.cid;
byte samples1[] = new byte[chunk1.length * bytes];
byte samples2[] = new byte[chunk2.length * bytes];
for(int ch=0;ch<channels;ch++) {
FileInputStream fis = new FileInputStream(project.path + "/" + tid + "/c" + chunk1.cid + "-" + ch + ".dat");
fis.skip(chunkHeaderLength);
JF.readAll(fis, samples1, 0, samples1.length);
JF.readAll(fis, samples2, 0, samples2.length);
fis.close();
FileOutputStream fos1 = new FileOutputStream(project.path + "/" + tid + "/c" + chunk1.cid + "-" + ch + ".dat");
chunk1.write(fos1);
fos1.write(samples1);
fos1.close();
genLowResChunk(samples1, chunk1, ch);
FileOutputStream fos2 = new FileOutputStream(project.path + "/" + tid + "/c" + chunk2.cid + "-" + ch + ".dat");
chunk2.write(fos2);
fos2.write(samples2);
fos2.close();
genLowResChunk(samples2, chunk2, ch);
}
list.add(lidx+1, chunk2);
} catch (Exception e) {
JFLog.log(e);
}
}
/** Ensures all chunks are <= maxChunkSize. */
/*
public void splitChunks() {
for(int a=0;a<list.size();a++) {
ChunkHeader chunk = list.get(a);
if (chunk.length > maxChunkSize) {
splitChunk(a, maxChunkSize);
}
}
}
*/
public void patchChunk(int lidx, int newnext_cid) {
ChunkHeader chunk = list.get(lidx);
try {
//patch last chunk next_cid to cid
chunk.next_cid = newnext_cid;
ChunkHeader patch = new ChunkHeader();
for(int ch=0;ch<channels;ch++) {
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat", "rw");
patch.read(raf);
raf.seek(0);
patch.next_cid = newnext_cid;
patch.write(raf);
raf.close();
raf = new RandomAccessFile(project.path + "/" + tid + "/a" + chunk.cid + "-" + ch + ".dat", "rw");
patch.read(raf);
raf.seek(0);
patch.next_cid = newnext_cid;
patch.write(raf);
raf.close();
}
} catch (Exception e) {
JFLog.log(e);
}
}
public int copyChunks(String path, int last_cid, int insertIdx) {
JFLog.log("copyChunks:" + last_cid + "," + insertIdx);
int _first_cid;
try {
int cid = genChunkID();
_first_cid = cid;
int clipid = 0;
ChunkHeader chunk;
byte samples[] = new byte[maxChunkSize * bytes];
int next_cid;
boolean lastChunk = false;
do {
chunk = new ChunkHeader();
chunk.cid = cid;
list.add(insertIdx++, chunk);
if (!new File(path + "/c" + (clipid+1) + "-0.dat").exists()) {
next_cid = last_cid;
lastChunk = true;
} else {
next_cid = genChunkID();
}
chunk.next_cid = next_cid;
for(int ch=0;ch<channels;ch++) {
ChunkHeader clip = new ChunkHeader();
FileInputStream fis = new FileInputStream(path + "/c" + clipid + "-" + ch + ".dat");
clip.read(fis);
JF.readAll(fis, samples, 0, clip.length * bytes);
chunk.length = clip.length;
FileOutputStream fos = new FileOutputStream(project.path + "/" + tid + "/c" + cid + "-" + ch + ".dat");
chunk.write(fos);
fos.write(samples, 0, chunk.length * bytes);
fos.close();
genLowResChunk(samples, chunk, ch);
}
cid = next_cid;
clipid++; //clips are always sequential
} while (!lastChunk);
return _first_cid;
} catch (Exception e) {
JFLog.log(e);
return -1;
}
}
public void delete(boolean createUndo) {
JFLog.log("delete");
if (selectStart == selectStop) {
JFLog.log("nothing to delete");
return;
}
long length, sStart, sStop;
if (selectStop < selectStart) {
sStart = selectStop;
sStop = selectStart;
} else {
sStart = selectStart;
sStop = selectStop;
}
if (createUndo) {
undoAction1 = ACTION_CUT;
undoAction2 = ACTION_NA;
copySelection(project.path + "/undo");
undoAction1OffsetStart = sStart;
undoAction1OffsetStop = sStop;
}
selectStart = selectStop = sStart;
project.selectStart(selectStart);
project.selectStop(selectStop);
length = (sStop - sStart + 1);
long offset = sStart;
int listSize = list.size();
int first_idx = -1, last_idx = -1;
for(int lidx = 0;lidx<listSize;) {
if (length == 0) break;
ChunkHeader chunk = list.get(lidx);
if (chunk.length < offset) {
offset -= chunk.length;
lidx++;
continue;
}
if (offset == 0 && chunk.length <= length) {
//delete entire chunk
if (first_idx == -1 && lidx != 0) first_idx = lidx-1;
if (last_idx == -1 && chunk.length == length && lidx != listSize-1) last_idx = lidx+1;
for(int ch=0;ch<channels;ch++) {
new File(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat").delete();
new File(project.path + "/" + tid + "/a" + chunk.cid + "-" + ch + ".dat").delete();
}
if (lidx == 0) {
//need to patch first_cid
if (list.size() == 1) {
first_cid = -1; //deleting last chunk
} else {
first_cid = list.get(lidx+1).cid;
}
writeMainHeader();
}
list.remove(lidx);
listSize--;
if (last_idx != -1) last_idx--;
length -= chunk.length;
continue;
}
if (offset > 0) {
if (chunk.length > offset + length) {
//delete middle part of chunk (first and last)
int orgChunkLength = chunk.length;
chunk.length -= length;
for(int ch=0;ch<channels;ch++) {
try {
byte samples[] = new byte[orgChunkLength * bytes];
byte newSamples[] = new byte[chunk.length * bytes];
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat", "rw");
chunk.write(raf);
JF.readAll(raf, samples, 0, orgChunkLength * bytes);
System.arraycopy(samples, 0, newSamples, 0, (int)offset * bytes);
System.arraycopy(samples, (int)(offset + length) * bytes, newSamples, (int)offset * bytes, (int)(orgChunkLength - offset - length) * bytes);
raf.seek(chunkHeaderLength);
raf.write(newSamples, 0, chunk.length * bytes);
raf.setLength(chunkHeaderLength + chunk.length * bytes);
raf.close();
genLowResChunk(newSamples, chunk, ch);
} catch (Exception e) {
JFLog.log(e);
}
}
break;
} else {
//delete last part of chunk (first chunk)
if (first_idx == -1 && lidx != 0) first_idx = lidx;
int orgChunkLength = chunk.length;
chunk.length -= chunk.length - offset;
for(int ch=0;ch<channels;ch++) {
try {
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat", "rw");
chunk.write(raf);
raf.setLength(chunkHeaderLength + chunk.length * bytes);
raf.close();
raf = new RandomAccessFile(project.path + "/" + tid + "/a" + chunk.cid + "-" + ch + ".dat", "rw");
chunk.write(raf);
raf.setLength(chunkHeaderLength + chunk.length / lowRes);
raf.close();
} catch (Exception e) {
JFLog.log(e);
}
}
length -= orgChunkLength - offset;
if (last_idx == -1 && length == 0 && lidx != listSize-1) last_idx = lidx;
offset = 0;
lidx++;
continue;
}
} else {
//delete first part of chunk (last chunk) [offset = 0]
if (first_idx == -1 && lidx != 0) first_idx = lidx;
if (last_idx == -1 && lidx != listSize-1) last_idx = lidx;
int orgChunkLength = chunk.length;
chunk.length -= length;
for(int ch=0;ch<channels;ch++) {
try {
byte samples[] = new byte[orgChunkLength * bytes];
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/c" + chunk.cid + "-" + ch + ".dat", "rw");
chunk.write(raf);
JF.readAll(raf, samples, 0, orgChunkLength * bytes);
System.arraycopy(samples, (int)length * bytes, samples, 0, chunk.length * bytes);
raf.seek(chunkHeaderLength);
raf.write(samples, 0, chunk.length * bytes);
raf.setLength(chunkHeaderLength + chunk.length * bytes);
raf.close();
genLowResChunk(samples, chunk, ch);
} catch (Exception e) {
JFLog.log(e);
}
}
// length = 0;
lidx++;
break;
}
}
//patch first chunk.next_cid to last chunk.cid
if (first_idx == -1 && last_idx == -1) {
calcLength();
repaint();
return;
}
ChunkHeader chunk_first = list.get(first_idx);
ChunkHeader chunk_last;
if (last_idx != -1) chunk_last = list.get(last_idx); else chunk_last = null;
for(int ch=0;ch<channels;ch++) {
try {
RandomAccessFile raf = new RandomAccessFile(project.path + "/" + tid + "/c" + chunk_first.cid + "-" + ch + ".dat", "rw");
chunk_first.read(raf);
chunk_first.next_cid = (chunk_last != null ? chunk_last.cid : -1);
raf.seek(0);
chunk_first.write(raf);
raf.close();
} catch (Exception e) {
JFLog.log(e);
}
}
calcLength();
repaint();
}
public void copySelection(String toPath) {
long length, sStart, sStop;
if (selectStop < selectStart) {
sStart = selectStop;
sStop = selectStart;
} else {
sStart = selectStart;
sStop = selectStop;
}
length = (sStop - sStart + 1);
long offset = sStart;
JFLog.log("copySelection:" + sStart + "-" + sStop + ":path=" + toPath);
try {
FileOutputStream fos = new FileOutputStream(toPath + "/clip.dat");
ClipHeader clip = new ClipHeader();
clip.offset = sStart;
clip.length = length;
clip.tid = tid;
clip.channels = channels;
clip.bits = bits;
clip.bytes = bytes;
clip.rate = rate;
clip.write(fos);
fos.close();
int cid = 0;
while (length > 0) {
int read = maxChunkSize;
if (read > length) read = (int)length;
for(int ch=0;ch<channels;ch++) {
byte samples[] = getSamples(offset, read, ch);
fos = new FileOutputStream(toPath + "/c" + cid + "-" + ch + ".dat");
ChunkHeader chunk = new ChunkHeader();
chunk.cid = cid;
chunk.length = samples.length / bytes;
chunk.next_cid = (read == length ? 0 : cid+1);
chunk.write(fos);
fos.write(samples);
fos.close();
}
length -= read;
}
} catch (Exception e) {
JFLog.log(e);
}
}
/** Returns a chunk id that is not in use. */
public int genChunkID() {
int cid = list.size();
for(int a=0;a<list.size();) {
if (list.get(a).cid == cid) {cid++; a = 0;} else {a++;}
}
return cid;
}
public void selectAll() {
selectStart = 0;
selectStop = totalLength-1;
repaint();
}
public void createModifyUndo() {
copySelection(project.path + "/undo");
undoAction1 = ACTION_MODIFY;
undoAction1OffsetStart = selectStart;
undoAction1OffsetStop = selectStop;
}
public void undo() {
switch (undoAction2) {
case ACTION_PASTE:
JFLog.log("undo:paste(2)");
selectStart = undoAction2OffsetStart;
selectStop = undoAction2OffsetStop;
delete(false);
selectStart = selectStop = undoAction2OffsetStart;
break;
}
switch (undoAction1) {
case ACTION_CUT:
JFLog.log("undo:cut");
selectStart = undoAction1OffsetStart;
selectStop = selectStart;
paste(project.path + "/undo", false);
selectStart = selectStop = undoAction1OffsetStart;
break;
case ACTION_PASTE:
JFLog.log("undo:paste(1)");
selectStart = undoAction1OffsetStart;
selectStop = undoAction1OffsetStop;
delete(false);
selectStart = selectStop = undoAction1OffsetStart;
break;
case ACTION_MODIFY:
JFLog.log("undo:modify");
selectStart = undoAction1OffsetStart;
selectStop = undoAction1OffsetStop;
delete(false);
selectStart = selectStop = undoAction1OffsetStart;
if (undoRate != 0) {
//fxResample
rate = undoRate;
undoRate = 0;
}
paste(project.path + "/undo", false);
break;
}
undoAction1 = ACTION_NA;
undoAction2 = ACTION_NA;
calcLength();
project.deleteUndo();
}
private Dimension layoutSize;
private class TrackLayout 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();
if (cnt != channels) return;
int x = project.getTracksWidth();
int cx = 0;
int cy = 0;
for(int ch=0;ch<channels;ch++) {
Component child = c.getComponent(ch);
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 info() {
String info = "Length:" + totalLength + " (samples)\n";
info += "Freq:" + rate + "\n";
info += "Bits:" + bits + "\n";
JF.showMessage("Track Info", info);
}
public void fxAmplify() {
if (totalLength == 0) {
JFLog.log("totalLength=0");
return;
}
if (selectStart == selectStop) selectAll();
AudioApp.inDialog = true;
FxAmplify fx = new FxAmplify(null, true, this);
fx.setVisible(true);
AudioApp.inDialog = false;
repaint();
}
public void fxFadeIn() {
if (totalLength == 0) {
JFLog.log("totalLength=0");
return;
}
if (selectStart == selectStop) selectAll();
FxFade.fadeIn(this);
repaint();
}
public void fxFadeOut() {
if (totalLength == 0) {
JFLog.log("totalLength=0");
return;
}
if (selectStart == selectStop) selectAll();
FxFade.fadeOut(this);
repaint();
}
public void fxResample() {
if (totalLength == 0) {
JFLog.log("totalLength=0");
return;
}
AudioApp.inDialog = true;
FxResample fx = new FxResample(null, true, this);
fx.setVisible(true);
AudioApp.inDialog = false;
repaint();
}
public void genTone() {
AudioApp.inDialog = true;
GenTone fx = new GenTone(null, true, this);
fx.setVisible(true);
AudioApp.inDialog = false;
repaint();
}
public void genSilence() {
AudioApp.inDialog = true;
GenSilence fx = new GenSilence(null, true, this);
fx.setVisible(true);
AudioApp.inDialog = false;
repaint();
}
}