From 187c3edaf6033523efec2ab185b462c41c313431 Mon Sep 17 00:00:00 2001 From: Matt Jenkins Date: Wed, 14 Aug 2019 19:58:06 +0100 Subject: [PATCH] Add waveform split and cut functionality --- .../majenko/audiobookrecorder/icons/cut.png | Bin 0 -> 674 bytes .../audiobookrecorder/icons/do-cut.png | Bin 0 -> 576 bytes .../audiobookrecorder/icons/play-to.png | Bin 0 -> 752 bytes .../majenko/audiobookrecorder/icons/split.png | Bin 0 -> 409 bytes .../audiobookrecorder/AudiobookRecorder.java | 213 +++++++++++++++++- .../co/majenko/audiobookrecorder/Icons.java | 4 + .../audiobookrecorder/MainToolBar.java | 13 ++ .../majenko/audiobookrecorder/Sentence.java | 194 ++++++++++++++-- .../majenko/audiobookrecorder/Waveform.java | 89 +++++++- 9 files changed, 483 insertions(+), 30 deletions(-) create mode 100644 resources/uk/co/majenko/audiobookrecorder/icons/cut.png create mode 100644 resources/uk/co/majenko/audiobookrecorder/icons/do-cut.png create mode 100644 resources/uk/co/majenko/audiobookrecorder/icons/play-to.png create mode 100644 resources/uk/co/majenko/audiobookrecorder/icons/split.png diff --git a/resources/uk/co/majenko/audiobookrecorder/icons/cut.png b/resources/uk/co/majenko/audiobookrecorder/icons/cut.png new file mode 100644 index 0000000000000000000000000000000000000000..133f3c3687701f89351c215eef1f88fe373fdc21 GIT binary patch literal 674 zcmV;T0$u%yP)|`u_OyrnAU$j&$+d0Uwr$(CZQJ%6 z1jAV8PwkWMPQtUNSsSHQ|0;OuG~=vZwI6SDv=AVjN^WRH;RtcIHHXoDbKT4wKAPMe6UkBf>R9OFA_DFz# zQ3;$IHv1&iZRPjr6G|4G2rR0WgnKhC$$xTQ@h?eAB9K3Gst@MUo5 zv;a@=HNT5i&Bs!`>g&lDC&xKM13hG>CXkjGOJZCQsa|u62-{&rN>V79X^Aw~*Kl@n zl%pm`1AX1JG}Td8UBUC`&yZA7l&&mazkWqSZ51tzbu2B+A`6@t6G3@N5ffu0Twa`$ zo1I2kNg;XJ85|!SGCes?Re34GCl4GO6-HrRHnY={Y;CSnn43jOVID>KIc%)2GBq(q zjPS(E1xJJg5E&N4>B%v}!~G=1#|TIy;h}*vH`Q~nzlXDfjpUIB_V#e*P7=V++k-$K zPy9vcVsFcxyxm>ofhB;2sS$g-J2cc);U$8+ydfPzmJF96}uvRo$YPn zJjV&}^CmdZPn2GajttS&*^Y;+ljM;Hc5$>LG&q3f#(HsCYxw(m5gi$ZpRXtDYpdd% z8wd*UMfl`_B~W}!6e-CGC=@6a6CJ56-@biALR_>63YWseJCp_E>;=p;NT!@5!Tk$YCn!X09z`l=v(z7IsgCw07*qo IM6N<$f^BFu5C8xG literal 0 HcmV?d00001 diff --git a/resources/uk/co/majenko/audiobookrecorder/icons/do-cut.png b/resources/uk/co/majenko/audiobookrecorder/icons/do-cut.png new file mode 100644 index 0000000000000000000000000000000000000000..472b48847e01b7e35be2ba3a24045ca106757559 GIT binary patch literal 576 zcmV-G0>AxMzCV_|S* zE^l&Yo9;Xs0005MNklARz0#n0v9xW@7DP@SBiLF*c(UL2~92b{~p*{uV?|4Cy%6;v4e4-$bOJwhpL z9rvU~@zF}F;!Df82Wx3uZ64f9r&29LEkmi)UfMiJbxADX^sgr6TWBtn6#pttp)`iN zfsuVVP$$$a85p51ALsMlwV!eMBlmac6Ut%hv-N+nFJnJ&l*K0dX85b@$nW@hYZ;7T z>2*RpYD5yU{EDigm@2xs&P}G9LSJGp7iN8*Vpo6R#;K*1Ihb~~_>P`zVA8S+1W%Fx zpJ_Y6SXktufEOW}{l8ozi7+ovkC#Rrk7Cz`k5IXNYR5YW_q5wB9_#ocwOa6&v4Z!z zS=7#Y6dnKN2{xYNrLq7Ud5;?#PYLPRMT8hDToMWkGarbv%UuCELmdHgT)v;C1R5v+ O0000MzCV_|S* zE^l&Yo9;Xs0007SNklcV~;QcAWC8x~fbj0fnx8EqB~7EPds}<#e|ezkM`t)JePUsT-6? zB@)q|Qf9=zHdS7{`AO29*{}1mA(<_o`vt&;_r2{${a(d>y*rcS(;ZJ3QxWZH`r1eS z0YTHYuBv{Ab>!1YoH}qwN1<)cBTc`%u8Dyf+$MjYVfyGDM(d3uA=HKmyAi(Bq*&3 z;%X8biA^1a#A61Hdd^kzQbDg#nL=S*A~v$PlGyankW@|8yt6XEfDu()ZTBVON_eAI z{?V=u)|H}OC=^JgQt761H08U z#MS>xV^;hxj9B_VH)PTOOuq&HQ@m&Yk9V2zFUE1||8VPxw?Zr?b7L6%_wQdy4E`5k zJ?TZT#UvpNgMa`2O?FU3J5D|Cqt(S2jx8vD{P;m`S*e z%*3fC`19w_AVcgyobJ*clxGlbZf-#!mH*|-7m&em{QUg9)X2^pKwJchxEC*8{Qvp$ zC&=(5AZDgUIZ*+`GhrB5C}mNr5aI)3VI<7MFya6J>K@bVfNWxM00000NkvXXu0mjf Dt{u3N literal 0 HcmV?d00001 diff --git a/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java b/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java index 2533fd7..e66e731 100644 --- a/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java +++ b/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java @@ -30,7 +30,7 @@ public class AudiobookRecorder extends JFrame { // Settings - tweakable - public static final int PLAYBACK_CHUNK_SIZE = 16384; + public static final int PLAYBACK_CHUNK_SIZE = 1024; public static final String SPHINX_MODEL = "resource:/edu/cmu/sphinx/models/en-us/en-us"; @@ -103,6 +103,9 @@ public class AudiobookRecorder extends JFrame { JButtonSpacePlay reprocessAudioFFT; JButtonSpacePlay reprocessAudioPeak; JButtonSpacePlay normalizeAudio; + JToggleButtonSpacePlay selectSplitMode; + JToggleButtonSpacePlay selectCutMode; + JButtonSpacePlay doCutSplit; JComboBox> effectChain; @@ -423,6 +426,23 @@ public class AudiobookRecorder extends JFrame { } }); + selectSplitMode = new JToggleButtonSpacePlay(Icons.split, "Toggle split mode", new ActionListener() { + public void actionPerformed(ActionEvent e) { + toggleSplitMode(); + } + }); + + selectCutMode = new JToggleButtonSpacePlay(Icons.cut, "Toggle cut mode", new ActionListener() { + public void actionPerformed(ActionEvent e) { + toggleCutMode(); + } + }); + + doCutSplit = new JButtonSpacePlay(Icons.docut, "Perform cut or split", new ActionListener() { + public void actionPerformed(ActionEvent e) { + executeCutOrSplit(); + } + }); postSentenceGap = new JSpinner(new SteppedNumericSpinnerModel(0, 5000, 100, 0)); postSentenceGap.setPreferredSize(new Dimension(50, 20)); @@ -487,6 +507,11 @@ public class AudiobookRecorder extends JFrame { gainPercent.setEnabled(!selectedSentence.isLocked()); reprocessAudioFFT.setEnabled(!selectedSentence.isLocked()); reprocessAudioPeak.setEnabled(!selectedSentence.isLocked()); + selectCutMode.setEnabled(!selectedSentence.isLocked()); + selectSplitMode.setEnabled(!selectedSentence.isLocked()); + doCutSplit.setEnabled(false); + selectCutMode.setSelected(false); + selectSplitMode.setSelected(false); bookTreeModel.reload(selectedSentence); } @@ -561,6 +586,10 @@ public class AudiobookRecorder extends JFrame { } }); + controlsBottom.add(selectSplitMode); + controlsBottom.add(selectCutMode); + controlsBottom.add(doCutSplit); + sampleControl.add(controlsTop, BorderLayout.NORTH); sampleControl.add(controlsLeft, BorderLayout.WEST); sampleControl.add(controlsRight, BorderLayout.EAST); @@ -1704,13 +1733,14 @@ public class AudiobookRecorder extends JFrame { try { fos = new FileOutputStream(config); prefs.storeToXML(fos, "Audiobook Recorder Description"); - } catch (Exception e) { - e.printStackTrace(); + } catch (Exception ex) { + ex.printStackTrace(); } if (fos != null) { try { fos.close(); - } catch (Exception e) { + } catch (Exception ex) { + ex.printStackTrace(); } } } @@ -1813,6 +1843,11 @@ public class AudiobookRecorder extends JFrame { gainPercent.setEnabled(!s.isLocked()); reprocessAudioFFT.setEnabled(!s.isLocked()); reprocessAudioPeak.setEnabled(!s.isLocked()); + selectCutMode.setEnabled(!s.isLocked()); + selectSplitMode.setEnabled(!s.isLocked()); + doCutSplit.setEnabled(false); + selectCutMode.setSelected(false); + selectSplitMode.setSelected(false); } else { selectedSentence = null; sampleWaveform.clearData(); @@ -1820,6 +1855,11 @@ public class AudiobookRecorder extends JFrame { gainPercent.setValue(100); locked.setSelected(false); attention.setSelected(false); + selectCutMode.setEnabled(false); + selectSplitMode.setEnabled(false); + doCutSplit.setEnabled(false); + selectCutMode.setSelected(false); + selectSplitMode.setSelected(false); } } }); @@ -2045,6 +2085,7 @@ public class AudiobookRecorder extends JFrame { play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer()); play.open(format); play.start(); + play.drain(); bookTree.scrollPathToVisible(new TreePath(s.getPath())); data = s.getPCMData(); @@ -2068,6 +2109,7 @@ public class AudiobookRecorder extends JFrame { play.close(); } play = null; + e.printStackTrace(); } } }); @@ -2117,6 +2159,82 @@ public class AudiobookRecorder extends JFrame { } + public void playToSelectedSentence() { + if (selectedSentence == null) return; + if (playing != null) return; + if (getNoiseFloor() == 0) { + alertNoRoomNoise(); + return; + } + playing = selectedSentence; + + playingThread = new Thread(new Runnable() { + public void run() { + Sentence s = playing; + byte[] data; + + try { + + AudioFormat sampleformat = s.getAudioFormat(); + AudioFormat format = new AudioFormat(sampleformat.getSampleRate(), 16, 2, true, false); + + play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer()); + play.open(format); + play.start(); + play.drain(); + + bookTree.scrollPathToVisible(new TreePath(s.getPath())); + data = s.getPCMData(); + + int startPos = 0; + int endPos = data.length / format.getFrameSize(); + +//foobar + if (selectSplitMode.isSelected()) { + endPos = sampleWaveform.getCutStart() - selectedSentence.getStartCrossing(); + if (endPos < 0) endPos = 0; + } else if (selectCutMode.isSelected()) { + startPos = sampleWaveform.getCutStart() - selectedSentence.getStartCrossing();; + endPos = sampleWaveform.getCutEnd() - selectedSentence.getStartCrossing();; + if (startPos < 0) startPos = 0; + if (endPos < 0) endPos = 0; + } + + startPos *= format.getFrameSize(); + endPos *= format.getFrameSize(); + + if (startPos > data.length) startPos = data.length; + if (endPos > data.length) endPos = data.length; + + for (int pos = startPos; pos < endPos; pos += PLAYBACK_CHUNK_SIZE) { + sampleWaveform.setPlayMarker(pos / format.getFrameSize()); + int l = data.length - pos; + if (l > PLAYBACK_CHUNK_SIZE) l = PLAYBACK_CHUNK_SIZE; + play.write(data, pos, l); + } + + play.drain(); + play.stop(); + play.close(); + play = null; + playing = null; + } catch (Exception e) { + playing = null; + if (play != null) { + play.drain(); + play.stop(); + play.close(); + } + play = null; + e.printStackTrace(); + } + } + }); + + playingThread.setDaemon(true); + playingThread.start(); + } + public void playFromSelectedSentence() { if (selectedSentence == null) return; if (playing != null) return; @@ -2138,6 +2256,7 @@ public class AudiobookRecorder extends JFrame { play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer()); play.open(format); play.start(); + play.drain(); while (playing != null) { bookTree.scrollPathToVisible(new TreePath(s.getPath())); @@ -2202,6 +2321,7 @@ public class AudiobookRecorder extends JFrame { play.close(); } play = null; + e.printStackTrace(); } } }); @@ -2409,6 +2529,7 @@ public class AudiobookRecorder extends JFrame { try { Files.copy(srcFile.toPath(), dstFile.toPath()); } catch (Exception e) { + e.printStackTrace(); } } @@ -2536,6 +2657,7 @@ public class AudiobookRecorder extends JFrame { f.delete(); } } catch (Exception e) { + e.printStackTrace(); } pd.closeDialog(); } @@ -3012,4 +3134,87 @@ public class AudiobookRecorder extends JFrame { state = STOPPING; } + public void toggleSplitMode() { + selectCutMode.setSelected(false); + if (selectedSentence != null) { + sampleWaveform.setDisplaySplit(selectSplitMode.isSelected()); + } + doCutSplit.setEnabled(selectSplitMode.isSelected()); + toolBar.enablePlayTo(selectSplitMode.isSelected()); + } + + public void toggleCutMode() { + selectSplitMode.setSelected(false); + if (selectedSentence != null) { + sampleWaveform.setDisplayCut(selectCutMode.isSelected()); + } + doCutSplit.setEnabled(selectCutMode.isSelected()); + toolBar.enablePlayTo(selectCutMode.isSelected()); + } + + public void doCut(int start, int end) { + try { + double[][] samples = selectedSentence.getRawAudioData(); + double[][] croppedSamples = new double[samples.length - (end - start)][2]; + + int a = 0; + for (int i = 0; i < samples.length; i++) { + if ((i < start) || (i > end)) { + croppedSamples[a++] = samples[i]; + } + } + selectedSentence.writeAudioData(croppedSamples); + selectedSentence.autoTrimSample(); + updateWaveform(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void doSplit(int at) { + try { + Sentence newSentence = selectedSentence.cloneSentence(); + Chapter c = (Chapter)selectedSentence.getParent(); + int idx = bookTreeModel.getIndexOfChild(c, selectedSentence); + bookTreeModel.insertNodeInto(newSentence, c, idx); + + double[][] samples = selectedSentence.getRawAudioData(); + double[][] startSamples = new double[at][2]; + double[][] endSamples = new double[samples.length - at][2]; + + int a = 0; + int b = 0; + + for (int i = 0; i < samples.length; i++) { + if (i < at) { + startSamples[a++] = samples[i]; + } else { + endSamples[b++] = samples[i]; + } + } + + newSentence.writeAudioData(startSamples); + selectedSentence.writeAudioData(endSamples); + selectedSentence.autoTrimSample(); + newSentence.autoTrimSample(); + updateWaveform(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void executeCutOrSplit() { + int start = sampleWaveform.getCutStart(); + int end = sampleWaveform.getCutEnd(); + if (selectCutMode.isSelected()) { + doCut(start, end); + } else if (selectSplitMode.isSelected()) { + doSplit(start); + } + selectCutMode.setSelected(false); + selectSplitMode.setSelected(false); + toolBar.enablePlayTo(false); + doCutSplit.setEnabled(false); + } + } diff --git a/src/uk/co/majenko/audiobookrecorder/Icons.java b/src/uk/co/majenko/audiobookrecorder/Icons.java index 62d9893..47ce025 100644 --- a/src/uk/co/majenko/audiobookrecorder/Icons.java +++ b/src/uk/co/majenko/audiobookrecorder/Icons.java @@ -33,4 +33,8 @@ public class Icons { static public final ImageIcon dollar = new ImageIcon(Icons.class.getResource("icons/dollar.png")); static public final ImageIcon attention = new ImageIcon(Icons.class.getResource("icons/attention.png")); static public final ImageIcon normalize = new ImageIcon(Icons.class.getResource("icons/normalize.png")); + static public final ImageIcon split = new ImageIcon(Icons.class.getResource("icons/split.png")); + static public final ImageIcon cut = new ImageIcon(Icons.class.getResource("icons/cut.png")); + static public final ImageIcon docut = new ImageIcon(Icons.class.getResource("icons/do-cut.png")); + static public final ImageIcon playto = new ImageIcon(Icons.class.getResource("icons/play-to.png")); } diff --git a/src/uk/co/majenko/audiobookrecorder/MainToolBar.java b/src/uk/co/majenko/audiobookrecorder/MainToolBar.java index 9a7ff67..a229cb1 100644 --- a/src/uk/co/majenko/audiobookrecorder/MainToolBar.java +++ b/src/uk/co/majenko/audiobookrecorder/MainToolBar.java @@ -14,6 +14,7 @@ public class MainToolBar extends JToolBar { JButtonSpacePlay recordRoomNoise; JButtonSpacePlay playSentence; JButtonSpacePlay playonSentence; + JButtonSpacePlay playtoSentence; JButtonSpacePlay stopPlaying; JButtonSpacePlay eq; JToggleButton mic; @@ -78,6 +79,14 @@ public class MainToolBar extends JToolBar { }); add(playonSentence); + playtoSentence = new JButtonSpacePlay(Icons.playto, "Play sentence to cut", new ActionListener() { + public void actionPerformed(ActionEvent e) { + root.playToSelectedSentence(); + } + }); + add(playtoSentence); + playtoSentence.setEnabled(false); + stopPlaying = new JButtonSpacePlay(Icons.stop, "Stop playing", new ActionListener() { public void actionPerformed(ActionEvent e) { root.stopPlaying(); @@ -124,4 +133,8 @@ public class MainToolBar extends JToolBar { setFloatable(false); } + public void enablePlayTo(boolean b) { + playtoSentence.setEnabled(b); + } + } diff --git a/src/uk/co/majenko/audiobookrecorder/Sentence.java b/src/uk/co/majenko/audiobookrecorder/Sentence.java index 5432808..ccad553 100644 --- a/src/uk/co/majenko/audiobookrecorder/Sentence.java +++ b/src/uk/co/majenko/audiobookrecorder/Sentence.java @@ -187,12 +187,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { processedAudio = null; if (!id.equals("room-noise")) { - String tm = Options.get("audio.recording.trim"); - if (tm.equals("peak")) { - autoTrimSamplePeak(true); - } else if (tm.equals("fft")) { - autoTrimSampleFFT(true); - } + autoTrimSample(true); if (Options.getBoolean("process.sphinx")) { recognise(); } @@ -200,6 +195,19 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { } + public void autoTrimSample() { + autoTrimSample(false); + } + + public void autoTrimSample(boolean useRaw) { + String tm = Options.get("audio.recording.trim"); + if (tm.equals("peak")) { + autoTrimSamplePeak(useRaw); + } else if (tm.equals("fft")) { + autoTrimSampleFFT(useRaw); + } + } + public static final int FFTBuckets = 1024; public void autoTrimSampleFFT() { @@ -712,7 +720,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { if (g == gain) return; if (gain != g) { - clearCache(); + CacheManager.removeFromCache(this); } gain = g; } @@ -758,7 +766,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { proc.waitFor(); } catch (Exception e) { } - clearCache(); + CacheManager.removeFromCache(Sentence.this); AudiobookRecorder.window.updateWaveform(); } } @@ -848,7 +856,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { } } - clearCache(); + CacheManager.removeFromCache(Sentence.this); AudiobookRecorder.window.updateWaveform(); } } @@ -891,7 +899,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { e.printStackTrace(); } - clearCache(); + CacheManager.removeFromCache(this); AudiobookRecorder.window.updateWaveform(); } @@ -899,7 +907,6 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { long len = s.getFrameLength(); int frameSize = format.getFrameSize(); int chans = format.getChannels(); - int bytes = frameSize / chans; byte[] frame = new byte[frameSize]; double[][] samples = new double[(int)len][2]; @@ -916,26 +923,163 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { int right = (rh << 8) | rl; if ((left & 0x8000) == 0x8000) left |= 0xFFFF0000; if ((right & 0x8000) == 0x8000) right |= 0xFFFF0000; - samples[(int)fno][LEFT] = (double)left / 32768d; - samples[(int)fno][RIGHT] = (double)right / 32768d; + samples[(int)fno][LEFT] = (double)left / 32767d; + samples[(int)fno][RIGHT] = (double)right / 32767d; } else { int l = frame[0] >= 0 ? frame[0] : 256 + frame[0]; int h = frame[1] >= 0 ? frame[1] : 256 + frame[1]; int mono = (h << 8) | l; if ((mono & 0x8000) == 0x8000) mono |= 0xFFFF0000; - samples[(int)fno][LEFT] = (double)mono / 32768d; - samples[(int)fno][RIGHT] = (double)mono / 32768d; + samples[(int)fno][LEFT] = (double)mono / 32767d; + samples[(int)fno][RIGHT] = (double)mono / 32767d; } } return samples; } + public void writeDoubleDataS16LE(double[][] samples, AudioFormat format) throws IOException { + int chans = format.getChannels(); + + int frames = samples.length; + + byte[] buffer; + + if (chans == 2) { + int buflen = frames * 4; + buffer = new byte[buflen]; + + for (int i = 0; i < frames; i++) { + double left = samples[i][LEFT]; + double right = samples[i][RIGHT]; + int off = i * 4; + left *= 32767d; + right *= 32767d; + int li = (int)left; + int ri = (int)right; + + if (li > 32767) li = 32767; + if (li < -32767) li = -32767; + if (ri > 32767) ri = 32767; + if (ri < -32767) ri = -32767; + + buffer[off + 0] = (byte)(li & 0xFF); + buffer[off + 1] = (byte)((li >> 8) & 0xFF); + buffer[off + 2] = (byte)(ri & 0xFF); + buffer[off + 3] = (byte)((ri >> 8) & 0xFF); + } + } else { + int buflen = frames * 2; + buffer = new byte[buflen]; + + for (int i = 0; i < frames; i++) { + double left = samples[i][LEFT]; + double right = samples[i][RIGHT]; + double mono = (left + right) / 2d; + int off = i * 2; + mono *= 32767d; + int mi = (int)mono; + + if (mi > 32767) mi = 32767; + if (mi < -32767) mi = -32767; + + buffer[off + 0] = (byte)(mi & 0xFF); + buffer[off + 1] = (byte)((mi >> 8) & 0xFF); + } + } + + backup(); + ByteArrayInputStream bis = new ByteArrayInputStream(buffer); + AudioInputStream ais = new AudioInputStream(bis, format, frames); + File wavFile = getFile(); + FileOutputStream fos = new FileOutputStream(wavFile); + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos); + fos.close(); + ais.close(); + } + + public void writeDoubleDataS24LE(double[][] samples, AudioFormat format) throws IOException { + int chans = format.getChannels(); + + int frames = samples.length; + + byte[] buffer; + + if (chans == 2) { + int buflen = frames * 6; + buffer = new byte[buflen]; + + for (int i = 0; i < frames; i++) { + double left = samples[i][LEFT]; + double right = samples[i][RIGHT]; + int off = i * 6; + left *= 8388607d; + right *= 8388607d; + int li = (int)left; + int ri = (int)right; + + if (li > 8388607) li = 8388607; + if (li < -8388607) li = -8388607; + if (ri > 8388607) ri = 8388607; + if (ri < -8388607) ri = -8388607; + + buffer[off + 0] = (byte)(li & 0xFF); + buffer[off + 1] = (byte)((li >> 8) & 0xFF); + buffer[off + 2] = (byte)((li >> 16) & 0xFF); + buffer[off + 3] = (byte)(ri & 0xFF); + buffer[off + 4] = (byte)((ri >> 8) & 0xFF); + buffer[off + 5] = (byte)((ri >> 16) & 0xFF); + } + } else { + int buflen = frames * 3; + buffer = new byte[buflen]; + + for (int i = 0; i < frames; i++) { + double left = samples[i][LEFT]; + double right = samples[i][RIGHT]; + double mono = (left + right) / 2d; + int off = i * 3; + mono *= 8388607d; + int mi = (int)mono; + + if (mi > 8388607) mi = 8388607; + if (mi < -8388607) mi = -8388607; + + buffer[off + 0] = (byte)(mi & 0xFF); + buffer[off + 1] = (byte)((mi >> 8) & 0xFF); + buffer[off + 2] = (byte)((mi >> 16) & 0xFF); + } + } + + backup(); + ByteArrayInputStream bis = new ByteArrayInputStream(buffer); + AudioInputStream ais = new AudioInputStream(bis, format, frames); + File wavFile = getFile(); + FileOutputStream fos = new FileOutputStream(wavFile); + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos); + fos.close(); + ais.close(); + } + + public void writeAudioData(double[][] samples) throws IOException { + AudioFormat format = getAudioFormat(); + + switch (format.getSampleSizeInBits()) { + case 16: + writeDoubleDataS16LE(samples, format); + break; + case 24: + writeDoubleDataS24LE(samples, format); + break; + } + + CacheManager.removeFromCache(this); + } + public double[][] getDoubleDataS24LE(AudioInputStream s, AudioFormat format) throws IOException { long len = s.getFrameLength(); int frameSize = format.getFrameSize(); int chans = format.getChannels(); - int bytes = frameSize / chans; byte[] frame = new byte[frameSize]; double[][] samples = new double[(int)len][2]; @@ -955,16 +1099,16 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { int right = (rh << 16) | (rm << 8) | rl; if ((left & 0x800000) == 0x800000) left |= 0xFF000000; if ((right & 0x800000) == 0x800000) right |= 0xFF000000; - samples[(int)fno][LEFT] = (double)left / 8388608d; - samples[(int)fno][RIGHT] = (double)right / 8388608d; + samples[(int)fno][LEFT] = (double)left / 8388607d; + samples[(int)fno][RIGHT] = (double)right / 8388607d; } else { int l = frame[0] >= 0 ? frame[0] : 256 + frame[0]; int m = frame[1] >= 0 ? frame[1] : 256 + frame[1]; int h = frame[2] >= 0 ? frame[2] : 256 + frame[2]; int mono = (h << 16) | (m << 8) | l; if ((mono & 0x800000) == 0x800000) mono |= 0xFF000000; - samples[(int)fno][LEFT] = (double)mono / 8388608d; - samples[(int)fno][RIGHT] = (double)mono / 8388608d; + samples[(int)fno][LEFT] = (double)mono / 8388607d; + samples[(int)fno][RIGHT] = (double)mono / 8388607d; } } @@ -1076,16 +1220,16 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { int length = croppedData.length; byte[] pcmData = new byte[length * 4]; for (int i = 0; i < length; i++) { - double sd = croppedData[i][LEFT] * 32768d; + double sd = croppedData[i][LEFT] * 32767d; int si = (int)sd; if (si > 32767) si = 32767; - if (si < -32768) si = -32768; + if (si < -32767) si = -32767; pcmData[i * 4] = (byte)(si & 0xFF); pcmData[(i * 4) + 1] = (byte)((si & 0xFF00) >> 8); - sd = croppedData[i][RIGHT] * 32768d; + sd = croppedData[i][RIGHT] * 32767d; si = (int)sd; if (si > 32767) si = 32767; - if (si < -32768) si = -32768; + if (si < -32767) si = -32767; pcmData[(i * 4) + 2] = (byte)(si & 0xFF); pcmData[(i * 4) + 3] = (byte)((si & 0xFF00) >> 8); } @@ -1094,7 +1238,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable { public void setEffectChain(String key) { if ((effectChain != null) && (!effectChain.equals(key))) { - clearCache(); + CacheManager.removeFromCache(this); } effectChain = key; } diff --git a/src/uk/co/majenko/audiobookrecorder/Waveform.java b/src/uk/co/majenko/audiobookrecorder/Waveform.java index 78c45f6..3deb789 100644 --- a/src/uk/co/majenko/audiobookrecorder/Waveform.java +++ b/src/uk/co/majenko/audiobookrecorder/Waveform.java @@ -21,6 +21,11 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen int leftAltMarker = 0; int rightAltMarker = 0; + int cutEntry = 0; + int cutExit = 0; + boolean displayCut = false; + boolean displaySplit = false; + int dragging = 0; int step = 1; @@ -133,7 +138,7 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen g.drawLine(n, (int)(h/2 + lave), n, (int)(h/2 - have)); } - g.setColor(new Color(255, 0, 0, 32)); + g.setColor(new Color(255, 0, 0, 64)); g.fillRect(0, 0, (leftAltMarker - offset)/step, h); g.fillRect((rightAltMarker - offset)/step, 0, (num - rightAltMarker) / step , h); @@ -145,6 +150,19 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen g.drawLine((leftMarker - offset)/step, 0, (leftMarker - offset)/step, h); g.drawLine((rightMarker - offset)/step, 0, (rightMarker - offset)/step, h); + if (displayCut || displaySplit) { + g.setColor(new Color(0, 255, 255)); + g.drawLine((cutEntry - offset)/step, 0, (cutEntry - offset)/step, h); + } + + if (displayCut) { + g.setColor(new Color(0, 255, 255)); + g.drawLine((cutExit - offset)/step, 0, (cutExit - offset)/step, h); + + g.setColor(new Color(0, 255, 255, 80)); + g.fillRect((cutEntry - offset)/step, 0, ((cutExit - offset) - (cutEntry - offset))/step , h); + } + g.setColor(new Color(0, 255, 255)); for (int i = 0; i < h; i += 2) { g.drawLine((playMarker - offset) / step, i, (playMarker - offset) / step, i); @@ -192,6 +210,8 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen public void setData(double[][] s) { samples = s; playMarker = 0; + displayCut = false; + displaySplit = false; repaint(); } @@ -221,6 +241,19 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen dragging = 2; return; } + if (displayCut || displaySplit) { + if ((x >= ((cutEntry - offset)/step) - 10) && (x <= ((cutEntry - offset)/step) + 10)) { + dragging = 3; + return; + } + } + if (displayCut) { + if ((x >= ((cutExit - offset)/step) - 10) && (x <= ((cutExit - offset)/step) + 10)) { + dragging = 4; + return; + } + } + } public void mouseReleased(MouseEvent e) { @@ -251,6 +284,19 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); return; } + if (displayCut || displaySplit) { + if ((x >= ((cutEntry - offset)/step) - 10) && (x <= ((cutEntry - offset)/step) + 10)) { + setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + return; + } + } + + if (displayCut) { + if ((x >= ((cutExit - offset)/step) - 10) && (x <= ((cutExit - offset)/step) + 10)) { + setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + return; + } + } setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); @@ -270,6 +316,18 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen if (rightMarker < leftMarker) { rightMarker = leftMarker; } + } else if (dragging == 3) { + cutEntry = (x * step) + offset; + if (displayCut) { + if (cutEntry > cutExit) { + cutEntry = cutExit; + } + } + } else if (dragging == 4) { + cutExit = (x * step) + offset; + if (cutExit < cutEntry) { + cutExit = cutEntry; + } } repaint(); @@ -299,4 +357,33 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen playMarker = leftAltMarker + m; repaint(); } + + public void setDisplayCut(boolean c) { + displayCut = c; + displaySplit = false; + if (displayCut) { + int d = rightMarker - leftMarker; + cutEntry = leftMarker + (d / 3); + cutExit = leftMarker + (d / 3 * 2); + } + repaint(); + } + + public void setDisplaySplit(boolean c) { + displayCut = false; + displaySplit = c; + if (displaySplit) { + int d = rightMarker - leftMarker; + cutEntry = leftMarker + (d / 2); + } + repaint(); + } + + public int getCutStart() { + return cutEntry; + } + + public int getCutEnd() { + return cutExit; + } }