/* 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(); } }