Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21d7d9d597 | |||
| 746f47a5fa | |||
| c67e6d6abc | |||
| 1f722f5df3 | |||
| df4eae1d66 | |||
| 4d435b4fc1 | |||
| f95ae10d03 | |||
| 423d840d83 | |||
| 1997b0bf9b | |||
| b206fb33aa | |||
| 11b26e396c | |||
| 94139e6ac6 | |||
| c0cc2432ff | |||
| f86aaa3782 |
38
README.md
38
README.md
@@ -29,12 +29,13 @@ From here on much is controlled by key presses.
|
||||
appended to the currently selected chapter, or to the last chapter if none is selected.
|
||||
* Press and hold "T" to record a new phrase that is the start of a new paragraph. This adds the "post paragraph" gap to the previous sentence. Otherwise it does the same as "R".
|
||||
* Press and hold "F" to record a "continuation" phrase. This sets the previous phrase's post-gap to be the "short" gap instead of the normal length gap.
|
||||
* Press and hold "Y" to record a new phrase that is the start of a new section. This add the "post section" gap to the previous sentence. Otherwise it does the same as "R".
|
||||
* Press "D" to delete the last phrase you recorded.
|
||||
* Press "E" to re-record the currently selected phrase.
|
||||
|
||||
Each phrase you record will be briefly analysed using FFT to find the start and end of the audio and set
|
||||
Each phrase you record can be automatically analysed to find the start and end of the audio and set
|
||||
crop marks appropriately. These can be adjusted in the waveform display when a phrase is selected. You can also
|
||||
re-run the analysis using either the default FFT method or using a peak detector method (finding the first and last points
|
||||
re-run the analysis using either FFT or a peak detector method (finding the first and last points
|
||||
where the audio amplitude rises above the backround noise).
|
||||
|
||||
The phrases also have a "post gap" associated with them. This is the amount of room noise (in milliseconds) to place between
|
||||
@@ -53,18 +54,14 @@ edit the text of this ID to identify the recordings. You
|
||||
may, for instance, change it to have the same text as the
|
||||
audio contains.
|
||||
|
||||
To help with this the Haven On-Demand online speech recognition
|
||||
service is integrated with the system and can be used to try and convert the
|
||||
audio into text. Right clicking on a recording brings
|
||||
up a menu which includes the option to try and convert
|
||||
the audio into text. The detected text is then used to
|
||||
replace the current recording ID / text.
|
||||
The audio can also be automatically converted to text if you have an suitable command-line
|
||||
executable that will work. One example is (on Linux) [DeepSpeech](https://github.com/mozilla/DeepSpeech) by Mozilla.
|
||||
|
||||
File layout
|
||||
-----------
|
||||
|
||||
All data is stored in your "storage" directory (specified in Options). Each book (which is a directory named after the
|
||||
title of the book) has an associated XML file (audiobook.abk) and a directory "files" where all the audio (stored as WAV
|
||||
title of the book) has an associated XML file (audiobook.abx) and a directory "files" where all the audio (stored as WAV
|
||||
files) is placed.
|
||||
|
||||
When you export the book as MP3 a new folder "export" is created within the book's folder where the MP3 files are placed.
|
||||
@@ -81,3 +78,26 @@ Building
|
||||
5. Build with `ant build`
|
||||
6. Run with `java -jar ./AudiobookRecorder.jar`
|
||||
|
||||
|
||||
----
|
||||
|
||||
Extra Resources
|
||||
===============
|
||||
|
||||
* DeepSpeech wrapper script
|
||||
|
||||
This is a small script that will convert the audio into a format DeepSpeech likes and call the `deepspeech` executable, removing any extra rubbish from the output. It
|
||||
also requires `sox` to be installed for the audio conversion.
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
ID=$$
|
||||
FILE=$1
|
||||
BINPATH=${HOME}/local/bin
|
||||
MODELS=${HOME}/ds/deepspeech-0.6.1-models
|
||||
|
||||
sox "$FILE" -r 16000 -c 1 -b 16 "/tmp/ds-${ID}.wav"
|
||||
${BINPATH}/deepspeech --model ${MODELS}/output_graph.pbmm --lm ${MODELS}/lm.binary --trie ${MODELS}/trie --audio "/tmp/ds-${ID}.wav" 2>/dev/null
|
||||
rm /tmp/ds-${ID}.wav
|
||||
```
|
||||
|
||||
BIN
deps/json-20190722.jar
LFS
vendored
Normal file
BIN
deps/json-20190722.jar
LFS
vendored
Normal file
Binary file not shown.
@@ -1 +1 @@
|
||||
version=0.3.6
|
||||
version=0.3.9
|
||||
|
||||
BIN
resources/uk/co/majenko/audiobookrecorder/icons/close.png
Normal file
BIN
resources/uk/co/majenko/audiobookrecorder/icons/close.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/processing.png
Normal file
BIN
resources/uk/co/majenko/audiobookrecorder/icons/processing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 489 B |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/queued.png
Normal file
BIN
resources/uk/co/majenko/audiobookrecorder/icons/queued.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 717 B |
@@ -11,6 +11,7 @@ import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
@@ -18,6 +19,7 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Desktop;
|
||||
@@ -66,6 +68,7 @@ import javax.swing.JTree;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.tree.TreeCellRenderer;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
@@ -137,6 +140,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
JMenuItem toolsArchive;
|
||||
JMenuItem toolsCoverArt;
|
||||
JMenuItem toolsManuscript;
|
||||
JMenuItem toolsReloadEffects;
|
||||
JMenuItem toolsOptions;
|
||||
|
||||
JMenuItem helpAbout;
|
||||
@@ -145,7 +149,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
|
||||
JPanel statusBar;
|
||||
|
||||
JLabel statusLabel;
|
||||
NoiseFloor noiseFloorLabel;
|
||||
|
||||
JScrollPane mainScroll;
|
||||
|
||||
@@ -177,6 +181,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
|
||||
JSpinner postSentenceGap;
|
||||
JSpinner gainPercent;
|
||||
Timer waveformUpdater = new Timer();
|
||||
JCheckBox locked;
|
||||
JCheckBox attention;
|
||||
JCheckBox rawAudio;
|
||||
@@ -203,7 +208,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
|
||||
public static AudiobookRecorder window;
|
||||
|
||||
public Queue<Runnable>speechProcessQueue = null;
|
||||
public Queue<Runnable>processQueue = null;
|
||||
public QueueMonitor queueMonitor = null;
|
||||
|
||||
void buildToolbar(Container ob) {
|
||||
Debug.trace();
|
||||
@@ -369,6 +375,18 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
}
|
||||
});
|
||||
|
||||
toolsReloadEffects = new JMenuItem("Reload effects");
|
||||
toolsReloadEffects.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
queueJob(new Runnable() {
|
||||
public void run() {
|
||||
loadEffects();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
toolsOptions = new JMenuItem("Options");
|
||||
toolsOptions.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -381,6 +399,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
toolsMenu.add(toolsArchive);
|
||||
toolsMenu.add(toolsCoverArt);
|
||||
toolsMenu.add(toolsManuscript);
|
||||
toolsMenu.add(toolsReloadEffects);
|
||||
toolsMenu.addSeparator();
|
||||
toolsMenu.add(toolsOptions);
|
||||
|
||||
@@ -419,7 +438,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Debug.debugEnabled = CLI.isSet("debug");
|
||||
Debug.traceEnabled = CLI.isSet("trace");
|
||||
|
||||
speechProcessQueue = new ArrayDeque<Runnable>();
|
||||
processQueue = new ArrayDeque<Runnable>();
|
||||
|
||||
try {
|
||||
|
||||
@@ -444,8 +463,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
|
||||
Options.loadPreferences();
|
||||
|
||||
queueMonitor = new QueueMonitor(processQueue);
|
||||
|
||||
for (int i = 0; i < Options.getInteger("process.threads"); i++) {
|
||||
WorkerThread worker = new WorkerThread(speechProcessQueue);
|
||||
WorkerThread worker = new WorkerThread(processQueue, queueMonitor);
|
||||
queueMonitor.addThread(worker);
|
||||
worker.start();
|
||||
}
|
||||
|
||||
@@ -529,11 +551,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
if (selectedSentence != null) {
|
||||
selectedSentence.autoTrimSampleFFT();
|
||||
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
|
||||
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
|
||||
postSentenceGap.setValue(selectedSentence.getPostGap());
|
||||
gainPercent.setValue((int)(selectedSentence.getGain() * 100d));
|
||||
queueJob(new SentenceJob(selectedSentence) {
|
||||
public void run() {
|
||||
sentence.autoTrimSampleFFT();
|
||||
updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -542,11 +565,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
if (selectedSentence != null) {
|
||||
selectedSentence.autoTrimSamplePeak();
|
||||
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
|
||||
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
|
||||
postSentenceGap.setValue(selectedSentence.getPostGap());
|
||||
gainPercent.setValue((int)(selectedSentence.getGain() * 100d));
|
||||
queueJob(new SentenceJob(selectedSentence) {
|
||||
public void run() {
|
||||
sentence.autoTrimSamplePeak();
|
||||
updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -654,8 +678,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
doCutSplit.setEnabled(false);
|
||||
selectCutMode.setSelected(false);
|
||||
selectSplitMode.setSelected(false);
|
||||
|
||||
bookTreeModel.reload(selectedSentence);
|
||||
selectedSentence.reloadTree();
|
||||
}
|
||||
});
|
||||
controlsTop.add(locked);
|
||||
@@ -676,7 +699,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
selectedSentence.setAttentionFlag(false);
|
||||
}
|
||||
}
|
||||
bookTreeModel.reload(selectedSentence);
|
||||
selectedSentence.reloadTree();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -744,10 +767,14 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
centralPanel.add(sampleControl, BorderLayout.SOUTH);
|
||||
|
||||
statusBar = new JPanel();
|
||||
statusBar.setLayout(new FlowLayout(FlowLayout.RIGHT));
|
||||
add(statusBar, BorderLayout.SOUTH);
|
||||
|
||||
statusLabel = new JLabel("Noise floor: " + getNoiseFloorDB() + "dB");
|
||||
statusBar.add(statusLabel);
|
||||
noiseFloorLabel = new NoiseFloor();
|
||||
|
||||
statusBar.add(noiseFloorLabel);
|
||||
// statusBar.add(Box.createHorizontalStrut(2));
|
||||
statusBar.add(queueMonitor);
|
||||
|
||||
buildToolbar(centralPanel);
|
||||
|
||||
@@ -931,12 +958,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Debug.trace();
|
||||
if (ev.getPropertyName().equals("dividerLocation")) {
|
||||
if ((bookTreeModel != null) && (book != null)) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
Debug.trace();
|
||||
bookTreeModel.reload(book);
|
||||
}
|
||||
});
|
||||
Debug.trace();
|
||||
book.reloadTree();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -989,6 +1012,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Options.savePreferences();
|
||||
}
|
||||
|
||||
queueJob(new VersionChecker());
|
||||
|
||||
}
|
||||
|
||||
void bindKeys(JComponent component) {
|
||||
@@ -1039,6 +1064,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
|
||||
public static void main(String args[]) {
|
||||
Debug.trace();
|
||||
Properties props = System.getProperties();
|
||||
props.setProperty("sun.java2d.opengl", "true");
|
||||
try {
|
||||
config.load(AudiobookRecorder.class.getResourceAsStream("config.txt"));
|
||||
} catch (Exception e) {
|
||||
@@ -1191,10 +1218,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (!snt.isLocked()) {
|
||||
if (snt.getId().equals(snt.getText())) {
|
||||
Runnable r = snt.getRecognitionRunnable();
|
||||
snt.setQueued();
|
||||
speechProcessQueue.add(r);
|
||||
speechProcessQueue.notify();
|
||||
queueJob(new SentenceJob(snt) {
|
||||
public void run() {
|
||||
sentence.doRecognition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1227,7 +1255,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Sentence s = (Sentence)o.getObject();
|
||||
if (!s.isLocked()) {
|
||||
s.recognise();
|
||||
queueJob(new SentenceJob(s) {
|
||||
public void run() {
|
||||
sentence.doRecognition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1304,7 +1336,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
String type = (String)o.getObject2();
|
||||
sent.setPostGapType(type);
|
||||
sent.setPostGap(Options.getInteger("catenation.post-sentence"));
|
||||
bookTreeModel.reload(sent);
|
||||
sent.reloadTree();
|
||||
}
|
||||
});
|
||||
setGapType.add(gapTypeSentence);
|
||||
@@ -1317,7 +1349,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
String type = (String)o.getObject2();
|
||||
sent.setPostGapType(type);
|
||||
sent.setPostGap(Options.getInteger("catenation.short-sentence"));
|
||||
bookTreeModel.reload(sent);
|
||||
sent.reloadTree();
|
||||
}
|
||||
});
|
||||
setGapType.add(gapTypeContinuation);
|
||||
@@ -1330,7 +1362,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
String type = (String)o.getObject2();
|
||||
sent.setPostGapType(type);
|
||||
sent.setPostGap(Options.getInteger("catenation.post-paragraph"));
|
||||
bookTreeModel.reload(sent);
|
||||
sent.reloadTree();
|
||||
}
|
||||
});
|
||||
setGapType.add(gapTypeParagraph);
|
||||
@@ -1343,7 +1375,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
String type = (String)o.getObject2();
|
||||
sent.setPostGapType(type);
|
||||
sent.setPostGap(Options.getInteger("catenation.post-section"));
|
||||
bookTreeModel.reload(sent);
|
||||
sent.reloadTree();
|
||||
}
|
||||
});
|
||||
setGapType.add(gapTypeSection);
|
||||
@@ -1416,7 +1448,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Debug.trace();
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Sentence s = (Sentence)o.getObject();
|
||||
s.runExternalProcessor(Utils.s2i(o.getActionCommand()));
|
||||
queueJob(new SentenceExternalJob(s, Utils.s2i(o.getActionCommand())));
|
||||
}
|
||||
});
|
||||
ob.setActionCommand(Integer.toString(i));
|
||||
@@ -1472,14 +1504,19 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Chapter chap = (Chapter)o.getObject();
|
||||
Chapter c = (Chapter)o.getObject();
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (!snt.isProcessed()) {
|
||||
queueJob(new SentenceJob(snt) {
|
||||
public void run() {
|
||||
sentence.autoTrimSampleFFT();
|
||||
updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
|
||||
|
||||
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.Peak, AutoTrimThread.NewOnly);
|
||||
Thread nt = new Thread(t);
|
||||
nt.start();
|
||||
ed.setVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1487,14 +1524,18 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Chapter chap = (Chapter)o.getObject();
|
||||
|
||||
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
|
||||
|
||||
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.FFT, AutoTrimThread.NewOnly);
|
||||
Thread nt = new Thread(t);
|
||||
nt.start();
|
||||
ed.setVisible(true);
|
||||
Chapter c = (Chapter)o.getObject();
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (!snt.isProcessed()) {
|
||||
queueJob(new SentenceJob(snt) {
|
||||
public void run() {
|
||||
sentence.autoTrimSampleFFT();
|
||||
updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1502,14 +1543,16 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Chapter chap = (Chapter)o.getObject();
|
||||
|
||||
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
|
||||
|
||||
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.Peak, AutoTrimThread.All);
|
||||
Thread nt = new Thread(t);
|
||||
nt.start();
|
||||
ed.setVisible(true);
|
||||
Chapter c = (Chapter)o.getObject();
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
queueJob(new SentenceJob(snt) {
|
||||
public void run() {
|
||||
sentence.autoTrimSamplePeak();
|
||||
updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1517,14 +1560,16 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Debug.trace();
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Chapter chap = (Chapter)o.getObject();
|
||||
|
||||
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
|
||||
|
||||
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.FFT, AutoTrimThread.All);
|
||||
Thread nt = new Thread(t);
|
||||
nt.start();
|
||||
ed.setVisible(true);
|
||||
Chapter c = (Chapter)o.getObject();
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
queueJob(new SentenceJob(snt) {
|
||||
public void run() {
|
||||
sentence.autoTrimSampleFFT();
|
||||
updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1597,7 +1642,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
snt.setLocked(true);
|
||||
bookTreeModel.reload(snt);
|
||||
snt.reloadTree();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1610,7 +1655,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
snt.setLocked(false);
|
||||
bookTreeModel.reload(snt);
|
||||
snt.reloadTree();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1658,13 +1703,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (!snt.isLocked()) {
|
||||
if (!snt.beenDetected()) {
|
||||
Debug.d("Queueing recognition of", snt.getId());
|
||||
synchronized(speechProcessQueue) {
|
||||
Runnable r = snt.getRecognitionRunnable();
|
||||
snt.setQueued();
|
||||
speechProcessQueue.add(r);
|
||||
speechProcessQueue.notify();
|
||||
}
|
||||
queueJob(new SentenceJob(snt) {
|
||||
public void run() {
|
||||
sentence.doRecognition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1701,7 +1744,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (!snt.isLocked()) {
|
||||
snt.runExternalProcessor(Utils.s2i(o.getActionCommand()));
|
||||
queueJob(new SentenceExternalJob(snt, Utils.s2i(o.getActionCommand())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2073,7 +2116,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
if (recording == null) return;
|
||||
recording.stopRecording();
|
||||
|
||||
bookTreeModel.reload(book);
|
||||
// book.reloadTree();
|
||||
|
||||
bookTree.expandPath(new TreePath(((DefaultMutableTreeNode)recording.getParent()).getPath()));
|
||||
bookTree.setSelectionPath(new TreePath(recording.getPath()));
|
||||
@@ -2131,9 +2174,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
public void addChapter() {
|
||||
Debug.trace();
|
||||
Chapter c = book.addChapter();
|
||||
Chapter lc = book.getLastChapter();
|
||||
int i = bookTreeModel.getIndexOfChild(book, lc);
|
||||
bookTreeModel.insertNodeInto(c, book, i+1);
|
||||
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
|
||||
bookTree.scrollPathToVisible(new TreePath(c.getPath()));
|
||||
}
|
||||
|
||||
@@ -2188,8 +2229,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
bookTree.setEditable(true);
|
||||
bookTree.setUI(new CustomTreeUI(mainScroll));
|
||||
|
||||
bookTree.setCellRenderer(new BookTreeRenderer());
|
||||
|
||||
TreeCellRenderer renderer = new BookTreeRenderer();
|
||||
try {
|
||||
bookTree.setCellRenderer(renderer);
|
||||
} catch (Exception ex) {
|
||||
bookTree.setCellRenderer(renderer);
|
||||
}
|
||||
|
||||
InputMap im = bookTree.getInputMap(JComponent.WHEN_FOCUSED);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "startStopPlayback");
|
||||
@@ -2283,11 +2328,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
} else {
|
||||
book.setIcon(Icons.book);
|
||||
}
|
||||
bookTreeModel.reload(book);
|
||||
book.reloadTree();
|
||||
|
||||
bookTree.expandPath(new TreePath(book.getPath()));
|
||||
|
||||
statusLabel.setText("Noise floor: " + getNoiseFloorDB() + "dB");
|
||||
noiseFloorLabel.setNoiseFloor(getNoiseFloorDB());
|
||||
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
@@ -2319,7 +2364,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
ImageIcon i = new ImageIcon(cf.getAbsolutePath());
|
||||
Image ri = Utils.getScaledImage(i.getImage(), 22, 22);
|
||||
book.setIcon(new ImageIcon(ri));
|
||||
bookTreeModel.reload(book);
|
||||
book.reloadTree();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -2545,7 +2590,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
|
||||
bookTree.expandPath(new TreePath(book.getPath()));
|
||||
|
||||
statusLabel.setText("Noise floor: " + getNoiseFloorDB() + "dB");
|
||||
noiseFloorLabel.setNoiseFloor(getNoiseFloorDB());
|
||||
book.setIcon(Icons.book);
|
||||
}
|
||||
|
||||
@@ -2618,7 +2663,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
Debug.trace();
|
||||
roomNoise.stopRecording();
|
||||
centralPanel.setFlash(false);
|
||||
statusLabel.setText("Noise floor: " + getNoiseFloorDB() + "dB");
|
||||
noiseFloorLabel.setNoiseFloor(getNoiseFloorDB());
|
||||
}
|
||||
}, 5000); // 5 seconds of recording
|
||||
}
|
||||
@@ -2719,55 +2764,6 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
}
|
||||
}
|
||||
|
||||
class AutoTrimThread implements Runnable {
|
||||
ProgressDialog dialog;
|
||||
Chapter chapter;
|
||||
int type;
|
||||
int scope;
|
||||
|
||||
public final static int FFT = 0;
|
||||
public final static int Peak = 1;
|
||||
public final static int NewOnly = 0;
|
||||
public final static int All = 1;
|
||||
|
||||
public AutoTrimThread(Chapter c, ProgressDialog e, int t, int sc) {
|
||||
super();
|
||||
Debug.trace();
|
||||
dialog = e;
|
||||
chapter = c;
|
||||
type = t;
|
||||
scope = sc;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run() {
|
||||
Debug.trace();
|
||||
|
||||
int numKids = chapter.getChildCount();
|
||||
int kidCount = 0;
|
||||
for (Enumeration s = chapter.children(); s.hasMoreElements();) {
|
||||
kidCount++;
|
||||
dialog.setProgress(kidCount * 2000 / numKids);
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (scope == NewOnly) {
|
||||
if (snt.isProcessed()) continue;
|
||||
}
|
||||
switch (type) {
|
||||
case FFT:
|
||||
snt.autoTrimSampleFFT();
|
||||
break;
|
||||
case Peak:
|
||||
snt.autoTrimSamplePeak();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dialog.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ExportThread implements Runnable {
|
||||
ProgressDialog exportDialog;
|
||||
Chapter chapter;
|
||||
@@ -3503,7 +3499,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
ImageIcon i = new ImageIcon(dest.getAbsolutePath());
|
||||
Image ri = Utils.getScaledImage(i.getImage(), 22, 22);
|
||||
book.setIcon(new ImageIcon(ri));
|
||||
bookTreeModel.reload(book);
|
||||
book.reloadTree();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -3516,16 +3512,42 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
updateWaveform(false);
|
||||
}
|
||||
|
||||
public void updateWaveform(boolean force) {
|
||||
TimerTask waveformUpdaterTask = null;
|
||||
|
||||
synchronized public void updateWaveform(boolean force) {
|
||||
Debug.trace();
|
||||
if (selectedSentence != null) {
|
||||
if ((!force) && (sampleWaveform.getId() != null) && (sampleWaveform.getId().equals(selectedSentence.getId()))) return;
|
||||
sampleWaveform.setId(selectedSentence.getId());
|
||||
if (rawAudio.isSelected()) {
|
||||
sampleWaveform.setData(selectedSentence.getRawAudioData());
|
||||
} else {
|
||||
sampleWaveform.setData(selectedSentence.getDoubleAudioData(effectsEnabled));
|
||||
|
||||
synchronized (waveformUpdater) {
|
||||
try {
|
||||
if (waveformUpdaterTask != null) {
|
||||
waveformUpdaterTask.cancel();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
|
||||
waveformUpdaterTask = new TimerTask() {
|
||||
public void run() {
|
||||
sampleWaveform.setId(selectedSentence.getId());
|
||||
if (rawAudio.isSelected()) {
|
||||
sampleWaveform.setData(selectedSentence.getRawAudioData());
|
||||
} else {
|
||||
sampleWaveform.setData(selectedSentence.getDoubleAudioData(effectsEnabled));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
waveformUpdater.schedule(waveformUpdaterTask, 20);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public void updateWaveformMarkers() {
|
||||
if (selectedSentence != null) {
|
||||
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
|
||||
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4175,6 +4197,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (orphans.getChildCount() == 0) {
|
||||
try {
|
||||
bookTreeModel.removeNodeFromParent(orphans);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Chapter getChapterById(String id) {
|
||||
@@ -4185,4 +4213,14 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void queueJob(Runnable r) {
|
||||
synchronized(processQueue) {
|
||||
processQueue.add(r);
|
||||
if (r instanceof SentenceJob) {
|
||||
SentenceJob sj = (SentenceJob)r;
|
||||
sj.setQueued();
|
||||
}
|
||||
processQueue.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
// Biquad.java
|
||||
//
|
||||
// Created by Nigel Redmon on 11/24/12
|
||||
// EarLevel Engineering: earlevel.com
|
||||
// Copyright 2012 Nigel Redmon
|
||||
// Translated to Java 2019 Majenko Technologies
|
||||
//
|
||||
// For a complete explanation of the Biquad code:
|
||||
// http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/
|
||||
//
|
||||
// License:
|
||||
//
|
||||
// This source code is provided as is, without warranty.
|
||||
// You may copy and distribute verbatim copies of this document.
|
||||
// You may modify and use this source code to create binary code
|
||||
// for your own purposes, free or commercial.
|
||||
//
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Biquad implements Effect {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.UUID;
|
||||
import java.util.Properties;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
@@ -154,12 +155,7 @@ public class Book extends BookTreeNode {
|
||||
|
||||
public Chapter getLastChapter() {
|
||||
Debug.trace();
|
||||
Chapter cc = getClosingCredits();
|
||||
if (cc == null) return null;
|
||||
Chapter c = (Chapter)getChildBefore(cc);
|
||||
if (c == null) return null;
|
||||
if (c.getId().equals("open")) return null;
|
||||
return c;
|
||||
return (Chapter)getLastLeaf();
|
||||
}
|
||||
|
||||
public Chapter getChapter(int n) {
|
||||
@@ -217,7 +213,7 @@ public class Book extends BookTreeNode {
|
||||
oldDir.renameTo(newDir);
|
||||
name = newName;
|
||||
AudiobookRecorder.window.saveBookStructure();
|
||||
AudiobookRecorder.window.bookTreeModel.reload(this);
|
||||
reloadTree();
|
||||
Options.set("path.last-book", name);
|
||||
Options.savePreferences();
|
||||
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name);
|
||||
@@ -447,4 +443,18 @@ public class Book extends BookTreeNode {
|
||||
public void setLocation(File l) {
|
||||
location = l;
|
||||
}
|
||||
|
||||
public void reloadTree() {
|
||||
Debug.trace();
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
if (AudiobookRecorder.window == null) return;
|
||||
if (AudiobookRecorder.window.bookTreeModel == null) return;
|
||||
try {
|
||||
AudiobookRecorder.window.bookTreeModel.reload(Book.this);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTree;
|
||||
@@ -84,7 +86,8 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
||||
}
|
||||
|
||||
|
||||
JLabel time = new JLabel(Utils.secToTime(s.getLength(), "mm:ss.SSS") + " ");
|
||||
JLabel time = new JLabelFixedWidth(75, " " + Utils.secToTime(s.getLength(), "ss.SSS") + " ");
|
||||
time.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
|
||||
ctx.gridx = 0;
|
||||
ctx.gridy = 0;
|
||||
@@ -93,17 +96,18 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
||||
ctx.anchor = GridBagConstraints.LINE_START;
|
||||
p.add(ret, ctx);
|
||||
|
||||
if (s.isDetecting()) {
|
||||
JLabel eff = new JLabel(" recognising... ");
|
||||
if (s.isProcessing()) {
|
||||
JLabel eff = new JLabel();
|
||||
eff.setIcon(Icons.processing);
|
||||
ctx.weightx = 0.0d;
|
||||
ctx.gridx = 1;
|
||||
p.add(eff);
|
||||
} else if (s.isQueued()) {
|
||||
JLabel eff = new JLabel(" queued ");
|
||||
JLabel eff = new JLabel();
|
||||
eff.setIcon(Icons.queued);
|
||||
ctx.weightx = 0.0d;
|
||||
ctx.gridx = 1;
|
||||
p.add(eff);
|
||||
} else if (s.isQueued()) {
|
||||
}
|
||||
|
||||
String effectChain = s.getEffectChain();
|
||||
@@ -121,7 +125,8 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
||||
ctx.gridx = 3;
|
||||
ctx.anchor = GridBagConstraints.LINE_END;
|
||||
int peak = s.getPeakDB();
|
||||
JLabel peakLabel = new JLabel(peak + "dB ");
|
||||
JLabel peakLabel = new JLabelFixedWidth(50, peak + "dB ");
|
||||
peakLabel.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
if (peak > 0) {
|
||||
peakLabel.setForeground(new Color(0xCC, 0x00, 0x00));
|
||||
}
|
||||
|
||||
@@ -1,130 +1,93 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
/**
|
||||
* @author Orlando Selenu
|
||||
*
|
||||
*/
|
||||
public class FFT {
|
||||
/**
|
||||
* The Fast Fourier Transform (generic version, with NO optimizations).
|
||||
*
|
||||
* @param inputReal
|
||||
* an array of length n, the real part
|
||||
* @param inputImag
|
||||
* an array of length n, the imaginary part
|
||||
* @param DIRECT
|
||||
* TRUE = direct transform, FALSE = inverse transform
|
||||
* @return a new array of length 2n
|
||||
*/
|
||||
public static double[] fft(final double[] inputReal, double[] inputImag,
|
||||
boolean DIRECT) {
|
||||
// - n is the dimension of the problem
|
||||
// - nu is its logarithm in base e
|
||||
int n = inputReal.length;
|
||||
public static double[] fft(final double[] inputReal, double[] inputImag, boolean DIRECT) {
|
||||
int n = inputReal.length;
|
||||
|
||||
// If n is a power of 2, then ld is an integer (_without_ decimals)
|
||||
double ld = Math.log(n) / Math.log(2.0);
|
||||
double ld = Math.log(n) / Math.log(2.0);
|
||||
|
||||
// Here I check if n is a power of 2. If exist decimals in ld, I quit
|
||||
// from the function returning null.
|
||||
if (((int) ld) - ld != 0) {
|
||||
System.out.println("The number of elements is not a power of 2.");
|
||||
return null;
|
||||
}
|
||||
if (((int) ld) - ld != 0) {
|
||||
System.out.println("The number of elements is not a power of 2.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Declaration and initialization of the variables
|
||||
// ld should be an integer, actually, so I don't lose any information in
|
||||
// the cast
|
||||
int nu = (int) ld;
|
||||
int n2 = n / 2;
|
||||
int nu1 = nu - 1;
|
||||
double[] xReal = new double[n];
|
||||
double[] xImag = new double[n];
|
||||
double tReal, tImag, p, arg, c, s;
|
||||
int nu = (int) ld;
|
||||
int n2 = n / 2;
|
||||
int nu1 = nu - 1;
|
||||
double[] xReal = new double[n];
|
||||
double[] xImag = new double[n];
|
||||
double tReal, tImag, p, arg, c, s;
|
||||
|
||||
// Here I check if I'm going to do the direct transform or the inverse
|
||||
// transform.
|
||||
double constant;
|
||||
if (DIRECT)
|
||||
constant = -2 * Math.PI;
|
||||
else
|
||||
constant = 2 * Math.PI;
|
||||
double constant;
|
||||
if (DIRECT) {
|
||||
constant = -2 * Math.PI;
|
||||
} else {
|
||||
constant = 2 * Math.PI;
|
||||
}
|
||||
|
||||
// I don't want to overwrite the input arrays, so here I copy them. This
|
||||
// choice adds \Theta(2n) to the complexity.
|
||||
for (int i = 0; i < n; i++) {
|
||||
xReal[i] = inputReal[i];
|
||||
xImag[i] = inputImag[i];
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
xReal[i] = inputReal[i];
|
||||
xImag[i] = inputImag[i];
|
||||
}
|
||||
|
||||
// First phase - calculation
|
||||
int k = 0;
|
||||
for (int l = 1; l <= nu; l++) {
|
||||
while (k < n) {
|
||||
for (int i = 1; i <= n2; i++) {
|
||||
p = bitreverseReference(k >> nu1, nu);
|
||||
// direct FFT or inverse FFT
|
||||
arg = constant * p / n;
|
||||
c = Math.cos(arg);
|
||||
s = Math.sin(arg);
|
||||
tReal = xReal[k + n2] * c + xImag[k + n2] * s;
|
||||
tImag = xImag[k + n2] * c - xReal[k + n2] * s;
|
||||
xReal[k + n2] = xReal[k] - tReal;
|
||||
xImag[k + n2] = xImag[k] - tImag;
|
||||
xReal[k] += tReal;
|
||||
xImag[k] += tImag;
|
||||
k++;
|
||||
int k = 0;
|
||||
for (int l = 1; l <= nu; l++) {
|
||||
while (k < n) {
|
||||
for (int i = 1; i <= n2; i++) {
|
||||
p = bitreverseReference(k >> nu1, nu);
|
||||
// direct FFT or inverse FFT
|
||||
arg = constant * p / n;
|
||||
c = Math.cos(arg);
|
||||
s = Math.sin(arg);
|
||||
tReal = xReal[k + n2] * c + xImag[k + n2] * s;
|
||||
tImag = xImag[k + n2] * c - xReal[k + n2] * s;
|
||||
xReal[k + n2] = xReal[k] - tReal;
|
||||
xImag[k + n2] = xImag[k] - tImag;
|
||||
xReal[k] += tReal;
|
||||
xImag[k] += tImag;
|
||||
k++;
|
||||
}
|
||||
k += n2;
|
||||
}
|
||||
k += n2;
|
||||
k = 0;
|
||||
nu1--;
|
||||
n2 /= 2;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
nu1--;
|
||||
n2 /= 2;
|
||||
}
|
||||
|
||||
// Second phase - recombination
|
||||
k = 0;
|
||||
int r;
|
||||
while (k < n) {
|
||||
r = bitreverseReference(k, nu);
|
||||
if (r > k) {
|
||||
tReal = xReal[k];
|
||||
tImag = xImag[k];
|
||||
xReal[k] = xReal[r];
|
||||
xImag[k] = xImag[r];
|
||||
xReal[r] = tReal;
|
||||
xImag[r] = tImag;
|
||||
int r;
|
||||
while (k < n) {
|
||||
r = bitreverseReference(k, nu);
|
||||
if (r > k) {
|
||||
tReal = xReal[k];
|
||||
tImag = xImag[k];
|
||||
xReal[k] = xReal[r];
|
||||
xImag[k] = xImag[r];
|
||||
xReal[r] = tReal;
|
||||
xImag[r] = tImag;
|
||||
}
|
||||
k++;
|
||||
}
|
||||
k++;
|
||||
|
||||
double[] newArray = new double[xReal.length * 2];
|
||||
double radice = 1 / Math.sqrt(n);
|
||||
for (int i = 0; i < newArray.length; i += 2) {
|
||||
int i2 = i / 2;
|
||||
newArray[i] = xReal[i2] * radice;
|
||||
newArray[i + 1] = xImag[i2] * radice;
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
// Here I have to mix xReal and xImag to have an array (yes, it should
|
||||
// be possible to do this stuff in the earlier parts of the code, but
|
||||
// it's here to readibility).
|
||||
double[] newArray = new double[xReal.length * 2];
|
||||
double radice = 1 / Math.sqrt(n);
|
||||
for (int i = 0; i < newArray.length; i += 2) {
|
||||
int i2 = i / 2;
|
||||
// I used Stephen Wolfram's Mathematica as a reference so I'm going
|
||||
// to normalize the output while I'm copying the elements.
|
||||
newArray[i] = xReal[i2] * radice;
|
||||
newArray[i + 1] = xImag[i2] * radice;
|
||||
private static int bitreverseReference(int j, int nu) {
|
||||
int j2;
|
||||
int j1 = j;
|
||||
int k = 0;
|
||||
for (int i = 1; i <= nu; i++) {
|
||||
j2 = j1 / 2;
|
||||
k = 2 * k + j1 - 2 * j2;
|
||||
j1 = j2;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* The reference bitreverse function.
|
||||
*/
|
||||
private static int bitreverseReference(int j, int nu) {
|
||||
int j2;
|
||||
int j1 = j;
|
||||
int k = 0;
|
||||
for (int i = 1; i <= nu; i++) {
|
||||
j2 = j1 / 2;
|
||||
k = 2 * k + j1 - 2 * j2;
|
||||
j1 = j2;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,7 @@ public class Icons {
|
||||
static public final ImageIcon disable = new ImageIcon(Icons.class.getResource("icons/disable-effects.png"));
|
||||
static public final ImageIcon manuscript = new ImageIcon(Icons.class.getResource("icons/manuscript.png"));
|
||||
static public final ImageIcon tooltip = new ImageIcon(Icons.class.getResource("icons/tooltip.png"));
|
||||
static public final ImageIcon queued = new ImageIcon(Icons.class.getResource("icons/queued.png"));
|
||||
static public final ImageIcon processing = new ImageIcon(Icons.class.getResource("icons/processing.png"));
|
||||
static public final ImageIcon close = new ImageIcon(Icons.class.getResource("icons/close.png"));
|
||||
}
|
||||
|
||||
38
src/uk/co/majenko/audiobookrecorder/JLabelFixedWidth.java
Normal file
38
src/uk/co/majenko/audiobookrecorder/JLabelFixedWidth.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import java.awt.Dimension;
|
||||
|
||||
public class JLabelFixedWidth extends JLabel {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
Dimension size;
|
||||
public JLabelFixedWidth(int w, String txt) {
|
||||
super(txt);
|
||||
JLabel t = new JLabel(txt);
|
||||
size = t.getPreferredSize();
|
||||
size.width = w;
|
||||
}
|
||||
|
||||
public JLabelFixedWidth(int w) {
|
||||
super();
|
||||
JLabel t = new JLabel("nothing");
|
||||
size = t.getPreferredSize();
|
||||
size.width = w;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,6 @@ public class KVPair<K,V> implements Comparable {
|
||||
}
|
||||
|
||||
public int compareTo(Object o) {
|
||||
// if (o instanceof KVPair) {
|
||||
// KVPair ko = (KVPair)o;
|
||||
// return key.compareTo(ko.key);
|
||||
// }
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ public class MainToolBar extends JToolBar {
|
||||
JButtonSpacePlay playonSentence;
|
||||
JButtonSpacePlay playtoSentence;
|
||||
JButtonSpacePlay stopPlaying;
|
||||
JButtonSpacePlay eq;
|
||||
JButtonSpacePlay openManuscript;
|
||||
JToggleButtonSpacePlay mic;
|
||||
|
||||
@@ -102,16 +101,6 @@ public class MainToolBar extends JToolBar {
|
||||
});
|
||||
add(stopPlaying);
|
||||
|
||||
addSeparator();
|
||||
eq = new JButtonSpacePlay(Icons.eq, "Reload Effects", new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
root.loadEffects();
|
||||
CacheManager.purgeCache();
|
||||
}
|
||||
});
|
||||
|
||||
add(eq);
|
||||
|
||||
addSeparator();
|
||||
|
||||
mic = new JToggleButtonSpacePlay(Icons.mic, "Enable / disable microphone", new ActionListener() {
|
||||
|
||||
51
src/uk/co/majenko/audiobookrecorder/NoiseFloor.java
Normal file
51
src/uk/co/majenko/audiobookrecorder/NoiseFloor.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Color;
|
||||
|
||||
public class NoiseFloor extends JPanel {
|
||||
|
||||
int noiseFloor = 0;
|
||||
|
||||
public NoiseFloor() {
|
||||
super();
|
||||
noiseFloor = 0;
|
||||
}
|
||||
|
||||
public void setNoiseFloor(int n) {
|
||||
noiseFloor = n;
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(128, 24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
Rectangle size = g.getClipBounds();
|
||||
g.setColor(getBackground());
|
||||
g.fillRect(0, 0, size.width - 1, size.height - 1);
|
||||
g.setColor(new Color(10, 10, 10));
|
||||
g.drawRect(0, 0, size.width - 1, size.height - 1);
|
||||
g.setFont(getFont());
|
||||
|
||||
g.setColor(getForeground());
|
||||
g.drawString("Noise Floor: " + noiseFloor + "dB", 6, 16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ public class Options extends JDialog {
|
||||
|
||||
enableParsing = addCheckBox(optionsPanel, "Enable automatic speech-to-text (**SLOW**)", getBoolean("process.sphinx"), "This will automatically start recognising the speech in every sentence you record. This can really slow down recording though so it's recommended to leave it turned off and do your recognition afterwards as a batch operation.");
|
||||
speechCommand = addTextField(optionsPanel, "Speech to text command (must take 1 filename parameter):", get("process.command"), "This specifies what command to run to recognize the speech. This command must take only one parameter, which is the full path of the WAV file. It should return (on standard output) the recognised speech.");
|
||||
workerThreads = addSpinner(optionsPanel, "Worker threads:", 1, 100, 1, getInteger("process.threads"), "", "How many concurrent threads to run when processing speech. This should ideally be no more than the number of CPU cores you have in your computer.");
|
||||
workerThreads = addSpinner(optionsPanel, "Worker threads:", 1, 100, 1, getInteger("process.threads"), "", "How many concurrent threads to run when processing speech. This should ideally be no more than the number of CPU cores you have in your computer minus one.");
|
||||
|
||||
addSeparator(optionsPanel);
|
||||
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Majenko Technologies
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Majenko Technologies nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
110
src/uk/co/majenko/audiobookrecorder/QueueMonitor.java
Normal file
110
src/uk/co/majenko/audiobookrecorder/QueueMonitor.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Queue;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
public class QueueMonitor extends JPanel implements MouseListener {
|
||||
|
||||
ArrayList<WorkerThread> threadList = new ArrayList<WorkerThread>();
|
||||
Queue<Runnable> queue;
|
||||
|
||||
public QueueMonitor(Queue<Runnable> q) {
|
||||
super();
|
||||
queue = q;
|
||||
addMouseListener(this);
|
||||
}
|
||||
|
||||
public void addThread(WorkerThread t) {
|
||||
threadList.add(t);
|
||||
}
|
||||
|
||||
public void purgeQueue() {
|
||||
Runnable work;
|
||||
synchronized (queue) {
|
||||
while (queue.size() > 0) {
|
||||
work = queue.remove();
|
||||
if (work instanceof SentenceJob) {
|
||||
SentenceJob sj = (SentenceJob)work;
|
||||
sj.setDequeued();
|
||||
}
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(150 + (24 * threadList.size()), 24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
Rectangle size = g.getClipBounds();
|
||||
g.setColor(getBackground());
|
||||
g.fillRect(0, 0, size.width - 1, size.height - 1);
|
||||
g.setColor(new Color(10, 10, 10));
|
||||
g.drawRect(0, 0, size.width - 1, size.height - 1);
|
||||
g.setFont(getFont());
|
||||
|
||||
for (int i = 0; i < threadList.size(); i++) {
|
||||
WorkerThread t = threadList.get(i);
|
||||
if (t.isRunning()) {
|
||||
g.setColor(new Color(50, 200, 0));
|
||||
} else {
|
||||
g.setColor(new Color(80, 0, 0));
|
||||
}
|
||||
g.fillOval(i * 24 + 4, 4, 22 - 8, 22 - 8);
|
||||
}
|
||||
|
||||
g.setColor(getForeground());
|
||||
g.drawString("Queued: " + queue.size(), threadList.size() * 24 + 4, 16);
|
||||
|
||||
if (queue.size() > 0) {
|
||||
Icons.close.paintIcon(this, g, size.width - 23, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent evt) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent evt) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent evt) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent evt) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent evt) {
|
||||
if (queue.size() == 0) return; // No button - ignore it
|
||||
Dimension size = getPreferredSize();
|
||||
if (evt.getX() > (size.width - 24)) {
|
||||
purgeQueue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.io.FileInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.AudioFileFormat;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
@@ -65,8 +66,11 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
boolean attention = false;
|
||||
boolean processed = false;
|
||||
boolean isDetected = false;
|
||||
boolean detecting = false;
|
||||
boolean queued = false;
|
||||
|
||||
int state = IDLE;
|
||||
static final int IDLE = 0;
|
||||
static final int QUEUED = 1;
|
||||
static final int PROCESSING = 2;
|
||||
|
||||
String effectChain = null;
|
||||
|
||||
@@ -218,7 +222,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
if (text.equals("")) text = id;
|
||||
|
||||
if ((crossStartOffset == -1) || (crossEndOffset == -1)) {
|
||||
updateCrossings(true);
|
||||
updateCrossings();
|
||||
}
|
||||
|
||||
if (runtime <= 0.01d) getLength();
|
||||
@@ -256,26 +260,40 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
CacheManager.removeFromCache(this);
|
||||
|
||||
if (!id.equals("room-noise")) {
|
||||
autoTrimSample(true);
|
||||
autoTrimSample();
|
||||
if (Options.getBoolean("process.sphinx")) {
|
||||
recognise();
|
||||
AudiobookRecorder.window.queueJob(new SentenceJob(this) {
|
||||
public void run() {
|
||||
sentence.doRecognition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void autoTrimSample() {
|
||||
public void autoTrimSample(boolean ignored) {
|
||||
Debug.trace();
|
||||
autoTrimSample(false);
|
||||
autoTrimSample();
|
||||
}
|
||||
|
||||
public void autoTrimSample(boolean useRaw) {
|
||||
public void autoTrimSample() {
|
||||
Debug.trace();
|
||||
String tm = Options.get("audio.recording.trim");
|
||||
if (tm.equals("peak")) {
|
||||
autoTrimSamplePeak(useRaw);
|
||||
AudiobookRecorder.window.queueJob(new SentenceJob(this) {
|
||||
public void run() {
|
||||
sentence.autoTrimSamplePeak();
|
||||
AudiobookRecorder.window.updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
} else if (tm.equals("fft")) {
|
||||
autoTrimSampleFFT(useRaw);
|
||||
AudiobookRecorder.window.queueJob(new SentenceJob(this) {
|
||||
public void run() {
|
||||
sentence.autoTrimSampleFFT();
|
||||
AudiobookRecorder.window.updateWaveformMarkers();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
startOffset = 0;
|
||||
crossStartOffset = 0;
|
||||
@@ -284,13 +302,13 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
processed = false;
|
||||
// peak = -1d;
|
||||
}
|
||||
AudiobookRecorder.window.updateWaveform(true);
|
||||
}
|
||||
|
||||
public static final int FFTBuckets = 1024;
|
||||
|
||||
public void autoTrimSampleFFT() {
|
||||
public void autoTrimSampleFFT(boolean ignored) {
|
||||
Debug.trace();
|
||||
autoTrimSampleFFT(false);
|
||||
}
|
||||
|
||||
public double bucketDifference(double[] a, double[] b) {
|
||||
@@ -304,16 +322,12 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
return diff;
|
||||
}
|
||||
|
||||
public void autoTrimSampleFFT(boolean useRaw) {
|
||||
public void autoTrimSampleFFT() {
|
||||
Debug.trace();
|
||||
crossStartOffset = -1;
|
||||
crossEndOffset = -1;
|
||||
double[][] samples;
|
||||
if (useRaw) {
|
||||
samples = getRawAudioData();
|
||||
} else {
|
||||
samples = getProcessedAudioData();
|
||||
}
|
||||
samples = getProcessedAudioData();
|
||||
if (samples == null) {
|
||||
return;
|
||||
}
|
||||
@@ -385,7 +399,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
if (endOffset <= startOffset) endOffset = startOffset + fftSize;
|
||||
if (endOffset < 0) endOffset = 0;
|
||||
if (endOffset >= samples[LEFT].length) endOffset = samples[LEFT].length;
|
||||
updateCrossings(useRaw);
|
||||
updateCrossings();
|
||||
intens = null;
|
||||
samples = null;
|
||||
processed = true;
|
||||
@@ -436,21 +450,17 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
}
|
||||
|
||||
|
||||
public void autoTrimSamplePeak() {
|
||||
public void autoTrimSamplePeak(boolean ignored) {
|
||||
Debug.trace();
|
||||
autoTrimSamplePeak(false);
|
||||
autoTrimSamplePeak();
|
||||
}
|
||||
|
||||
public void autoTrimSamplePeak(boolean useRaw) {
|
||||
public void autoTrimSamplePeak() {
|
||||
Debug.trace();
|
||||
crossStartOffset = -1;
|
||||
crossEndOffset = -1;
|
||||
double[][] samples;
|
||||
if (useRaw) {
|
||||
samples = getRawAudioData();
|
||||
} else {
|
||||
samples = getProcessedAudioData();
|
||||
}
|
||||
samples = getProcessedAudioData();
|
||||
if (samples == null) return;
|
||||
double noiseFloor = AudiobookRecorder.window.getNoiseFloor();
|
||||
noiseFloor *= 1.1;
|
||||
@@ -490,7 +500,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
|
||||
if (startOffset < 0) startOffset = 0;
|
||||
if (endOffset >= samples[LEFT].length) endOffset = samples[LEFT].length-1;
|
||||
updateCrossings(useRaw);
|
||||
updateCrossings();
|
||||
processed = true;
|
||||
reloadTree();
|
||||
}
|
||||
@@ -597,38 +607,23 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
|
||||
public void updateCrossings() {
|
||||
Debug.trace();
|
||||
updateCrossings(false);
|
||||
}
|
||||
|
||||
public void updateCrossings(boolean useRaw) {
|
||||
Debug.trace();
|
||||
updateStartCrossing(useRaw);
|
||||
updateEndCrossing(useRaw);
|
||||
updateStartCrossing();
|
||||
updateEndCrossing();
|
||||
runtime = -1d;
|
||||
getLength();
|
||||
}
|
||||
|
||||
public void updateStartCrossing() {
|
||||
Debug.trace();
|
||||
updateStartCrossing(false);
|
||||
}
|
||||
|
||||
public void updateStartCrossing(boolean useRaw) {
|
||||
Debug.trace();
|
||||
if (crossStartOffset == -1) {
|
||||
crossStartOffset = findNearestZeroCrossing(useRaw, startOffset, 4096);
|
||||
crossStartOffset = findNearestZeroCrossing(startOffset, 4096);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateEndCrossing() {
|
||||
Debug.trace();
|
||||
updateEndCrossing(false);
|
||||
}
|
||||
|
||||
public void updateEndCrossing(boolean useRaw) {
|
||||
Debug.trace();
|
||||
if (crossEndOffset == -1) {
|
||||
crossEndOffset = findNearestZeroCrossing(useRaw, endOffset, 4096);
|
||||
crossEndOffset = findNearestZeroCrossing(endOffset, 4096);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,20 +689,8 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Runnable getRecognitionRunnable() {
|
||||
Runnable r = new Runnable() {
|
||||
public void run() {
|
||||
Debug.d("Starting recognition of", getId());
|
||||
doRecognition();
|
||||
}
|
||||
};
|
||||
return r;
|
||||
}
|
||||
|
||||
public void doRecognition() {
|
||||
Debug.trace();
|
||||
detecting = true;
|
||||
queued = false;
|
||||
try {
|
||||
reloadTree();
|
||||
|
||||
@@ -727,18 +710,10 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
|
||||
setText(res);
|
||||
isDetected = true;
|
||||
detecting = false;
|
||||
reloadTree();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
detecting = false;
|
||||
}
|
||||
|
||||
public void recognise() {
|
||||
Debug.trace();
|
||||
Thread t = new Thread(getRecognitionRunnable());
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void setLocked(boolean l) {
|
||||
@@ -776,18 +751,9 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
}
|
||||
|
||||
public int findNearestZeroCrossing(int pos, int range) {
|
||||
Debug.trace();
|
||||
return findNearestZeroCrossing(false, pos, range);
|
||||
}
|
||||
|
||||
public int findNearestZeroCrossing(boolean useRaw, int pos, int range) {
|
||||
Debug.trace();
|
||||
double[][] data = null;
|
||||
if (useRaw) {
|
||||
data = getRawAudioData();
|
||||
} else {
|
||||
data = getProcessedAudioData();
|
||||
}
|
||||
data = getProcessedAudioData();
|
||||
if (data == null) return 0;
|
||||
if (data[LEFT].length == 0) return 0;
|
||||
|
||||
@@ -855,7 +821,6 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
File to = sentence.getFile();
|
||||
Files.copy(from.toPath(), to.toPath());
|
||||
|
||||
// sentence.updateCrossings();
|
||||
return sentence;
|
||||
}
|
||||
|
||||
@@ -873,24 +838,15 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
|
||||
public double getPeakValue() {
|
||||
Debug.trace();
|
||||
return getPeakValue(false, true);
|
||||
return getPeakValue(true);
|
||||
}
|
||||
|
||||
public double getPeakValue(boolean useRaw) {
|
||||
Debug.trace();
|
||||
return getPeakValue(useRaw, true);
|
||||
}
|
||||
|
||||
public double getPeakValue(boolean useRaw, boolean applyGain) {
|
||||
public double getPeakValue(boolean applyGain) {
|
||||
Debug.trace();
|
||||
double oldGain = gain;
|
||||
gain = 1.0d;
|
||||
double[][] samples = null;
|
||||
if (useRaw) {
|
||||
samples = getRawAudioData();
|
||||
} else {
|
||||
samples = getProcessedAudioData(true, applyGain);
|
||||
}
|
||||
samples = getProcessedAudioData(true, applyGain);
|
||||
gain = oldGain;
|
||||
if (samples == null) {
|
||||
return 0;
|
||||
@@ -937,7 +893,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
public double normalize(double low, double high) {
|
||||
Debug.trace();
|
||||
if (locked) return gain;
|
||||
double max = getPeakValue(true, false);
|
||||
double max = getPeakValue(false);
|
||||
double d = 0.708 / max;
|
||||
if (d > 1d) d = 1d;
|
||||
if (d < low) d = low;
|
||||
@@ -952,7 +908,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
public double normalize() {
|
||||
Debug.trace();
|
||||
if (locked) return gain;
|
||||
double max = getPeakValue(true, false);
|
||||
double max = getPeakValue(false);
|
||||
double d = 0.708 / max;
|
||||
if (d > 1d) d = 1d;
|
||||
setGain(d);
|
||||
@@ -1000,8 +956,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
public void openInExternalEditor() {
|
||||
Debug.trace();
|
||||
ExternalEditor ed = new ExternalEditor(this);
|
||||
Thread t = new Thread(ed);
|
||||
t.start();
|
||||
ed.run();
|
||||
}
|
||||
|
||||
public void backup() throws IOException {
|
||||
@@ -1095,8 +1050,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
Debug.trace();
|
||||
if (isLocked()) return;
|
||||
ExternalProcessor ed = new ExternalProcessor(this, num);
|
||||
Thread t = new Thread(ed);
|
||||
t.start();
|
||||
ed.run();
|
||||
}
|
||||
|
||||
public void undo() {
|
||||
@@ -1427,21 +1381,23 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
|
||||
|
||||
String def = AudiobookRecorder.window.getDefaultEffectsChain();
|
||||
Effect eff = AudiobookRecorder.window.effects.get(def);
|
||||
if ((def != null) && (AudiobookRecorder.window.effects != null)) {
|
||||
Effect eff = AudiobookRecorder.window.effects.get(def);
|
||||
|
||||
if (effectsEnabled) {
|
||||
if (eff != null) {
|
||||
eff.init(getAudioFormat().getFrameRate());
|
||||
eff.process(processedAudio);
|
||||
}
|
||||
if (effectsEnabled) {
|
||||
if (eff != null) {
|
||||
eff.init(getAudioFormat().getFrameRate());
|
||||
eff.process(processedAudio);
|
||||
}
|
||||
|
||||
if (effectChain != null) {
|
||||
// Don't double up the default chain
|
||||
if (!effectChain.equals(def)) {
|
||||
eff = AudiobookRecorder.window.effects.get(effectChain);
|
||||
if (eff != null) {
|
||||
eff.init(getAudioFormat().getFrameRate());
|
||||
eff.process(processedAudio);
|
||||
if (effectChain != null) {
|
||||
// Don't double up the default chain
|
||||
if (!effectChain.equals(def)) {
|
||||
eff = AudiobookRecorder.window.effects.get(effectChain);
|
||||
if (eff != null) {
|
||||
eff.init(getAudioFormat().getFrameRate());
|
||||
eff.process(processedAudio);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1670,9 +1626,11 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
Debug.trace();
|
||||
if (id.equals("room-noise")) return;
|
||||
if (getParent() == null) return;
|
||||
synchronized (AudiobookRecorder.window.bookTreeModel) {
|
||||
AudiobookRecorder.window.bookTreeModel.reload(this);
|
||||
}
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
AudiobookRecorder.window.bookTreeModel.reload(Sentence.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public double getPeak() {
|
||||
@@ -1707,20 +1665,31 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
||||
return (int)db;
|
||||
}
|
||||
|
||||
public boolean isDetecting() {
|
||||
return detecting;
|
||||
}
|
||||
|
||||
public boolean beenDetected() {
|
||||
return isDetected;
|
||||
}
|
||||
|
||||
public boolean isProcessing() {
|
||||
return state == PROCESSING;
|
||||
}
|
||||
|
||||
public boolean isQueued() {
|
||||
return queued;
|
||||
return state == QUEUED;
|
||||
}
|
||||
|
||||
public void setProcessing() {
|
||||
state = PROCESSING;
|
||||
reloadTree();
|
||||
}
|
||||
|
||||
public void setQueued() {
|
||||
queued = true;
|
||||
state = QUEUED;
|
||||
reloadTree();
|
||||
}
|
||||
|
||||
public void setDequeued() {
|
||||
state = IDLE;
|
||||
reloadTree();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
src/uk/co/majenko/audiobookrecorder/SentenceExternalJob.java
Normal file
17
src/uk/co/majenko/audiobookrecorder/SentenceExternalJob.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import java.lang.Runnable;
|
||||
|
||||
public class SentenceExternalJob extends SentenceJob {
|
||||
protected int command;
|
||||
|
||||
public SentenceExternalJob(Sentence s, int c) {
|
||||
super(s);
|
||||
command = c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sentence.runExternalProcessor(command);
|
||||
}
|
||||
}
|
||||
25
src/uk/co/majenko/audiobookrecorder/SentenceJob.java
Normal file
25
src/uk/co/majenko/audiobookrecorder/SentenceJob.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import java.lang.Runnable;
|
||||
|
||||
public abstract class SentenceJob implements Runnable {
|
||||
protected Sentence sentence;
|
||||
|
||||
public SentenceJob(Sentence s) {
|
||||
sentence = s;
|
||||
}
|
||||
|
||||
public void setQueued() {
|
||||
sentence.setQueued();
|
||||
}
|
||||
|
||||
public void setDequeued() {
|
||||
sentence.setDequeued();
|
||||
}
|
||||
|
||||
public void setProcessing() {
|
||||
sentence.setProcessing();
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
}
|
||||
77
src/uk/co/majenko/audiobookrecorder/VersionChecker.java
Normal file
77
src/uk/co/majenko/audiobookrecorder/VersionChecker.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import java.lang.Runnable;
|
||||
import java.net.URL;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.BufferedReader;
|
||||
import org.json.JSONObject;
|
||||
import javax.swing.JButton;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public class VersionChecker implements Runnable {
|
||||
public VersionChecker() {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
URL url = new URL("https://api.github.com/repos/MajenkoProjects/AudiobookRecorder/releases/latest");
|
||||
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
|
||||
InputStream is = conn.getInputStream();
|
||||
InputStreamReader isr = new InputStreamReader(is);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
|
||||
String inputLine;
|
||||
StringBuilder jsonData = new StringBuilder();
|
||||
|
||||
while ((inputLine = br.readLine()) != null) {
|
||||
jsonData.append(inputLine);
|
||||
}
|
||||
|
||||
br.close();
|
||||
|
||||
JSONObject job = new JSONObject(jsonData.toString());
|
||||
|
||||
String installed = AudiobookRecorder.config.getProperty("version");
|
||||
String available = job.getString("tag_name");
|
||||
if (available.startsWith("v")) {
|
||||
available = available.substring(1);
|
||||
}
|
||||
String website = job.getString("html_url");
|
||||
|
||||
|
||||
String[] installedParts = installed.split("\\.");
|
||||
String[] availableParts = available.split("\\.");
|
||||
// Must be x.y.z
|
||||
|
||||
if (installedParts.length != 3) return;
|
||||
if (availableParts.length != 3) return;
|
||||
|
||||
// Convert to xxxyyyzzz
|
||||
String installedVersion = String.format("%03d%03d%03d", Utils.s2i(installedParts[0]), Utils.s2i(installedParts[1]), Utils.s2i(installedParts[2]));
|
||||
String availableVersion = String.format("%03d%03d%03d", Utils.s2i(availableParts[0]), Utils.s2i(availableParts[1]), Utils.s2i(availableParts[2]));
|
||||
|
||||
if (Utils.s2i(installedVersion) >= Utils.s2i(availableVersion)) return;
|
||||
|
||||
System.err.println("Version installed: " + installed);
|
||||
System.err.println("Version available: " + available);
|
||||
System.err.println("URL: " + website);
|
||||
|
||||
JButton upgrade = new JButton("A new version is available.");
|
||||
upgrade.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
Utils.browse(website);
|
||||
}
|
||||
});
|
||||
|
||||
AudiobookRecorder.window.statusBar.add(upgrade);
|
||||
AudiobookRecorder.window.statusBar.revalidate();
|
||||
|
||||
} catch (Exception ignored) {
|
||||
ignored.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,13 @@ import java.util.Queue;
|
||||
public class WorkerThread extends Thread {
|
||||
private static int instance = 0;
|
||||
private final Queue<Runnable> queue;
|
||||
private final QueueMonitor monitor;
|
||||
|
||||
public WorkerThread(Queue<Runnable> queue) {
|
||||
private boolean running = false;
|
||||
|
||||
public WorkerThread(Queue<Runnable> queue, QueueMonitor mon) {
|
||||
this.queue = queue;
|
||||
monitor = mon;
|
||||
setName("Worker Thread " + (instance++));
|
||||
}
|
||||
|
||||
@@ -30,8 +34,19 @@ public class WorkerThread extends Thread {
|
||||
work = queue.remove();
|
||||
}
|
||||
|
||||
// Process the work item
|
||||
running = true;
|
||||
monitor.repaint();
|
||||
if (work instanceof SentenceJob) {
|
||||
SentenceJob sj = (SentenceJob)work;
|
||||
sj.setProcessing();
|
||||
}
|
||||
work.run();
|
||||
if (work instanceof SentenceJob) {
|
||||
SentenceJob sj = (SentenceJob)work;
|
||||
sj.setDequeued();
|
||||
}
|
||||
running = false;
|
||||
monitor.repaint();
|
||||
}
|
||||
catch ( InterruptedException ie ) {
|
||||
ie.printStackTrace();
|
||||
@@ -40,4 +55,8 @@ public class WorkerThread extends Thread {
|
||||
}
|
||||
Debug.d(getName(), "died");
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user