Initial import

This commit is contained in:
2018-09-09 19:08:08 +01:00
commit a40f22be16
29 changed files with 2478 additions and 0 deletions

View File

@@ -0,0 +1,884 @@
package uk.co.majenko.audiobookrecorder;
import javax.sound.sampled.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.nio.file.*;
import javax.swing.tree.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.prefs.*;
import java.io.*;
public class AudiobookRecorder extends JFrame {
MainToolBar toolBar;
JMenuBar menuBar;
JMenu fileMenu;
JMenu bookMenu;
JMenu toolsMenu;
JMenuItem fileNewBook;
JMenuItem fileOpenBook;
JMenuItem fileExit;
JMenuItem bookNewChapter;
JMenuItem bookExportAudio;
JMenuItem toolsOptions;
FlashPanel centralPanel;
JPanel statusBar;
JLabel statusFormat;
JScrollPane mainScroll;
Book book = null;
JTree bookTree;
DefaultTreeModel bookTreeModel;
Sentence recording = null;
Sentence playing = null;
Sentence roomNoise = null;
Sentence selectedSentence = null;
JPanel sampleControl;
Waveform sampleWaveform;
JSpinner startOffset;
JSpinner endOffset;
JSpinner postSentenceGap;
Thread playingThread = null;
public static AudiobookRecorder window;
void buildToolbar(Container ob) {
toolBar = new MainToolBar(this);
toolBar.disableBook();
toolBar.disableSentence();
ob.add(toolBar, BorderLayout.NORTH);
}
void buildMenu(Container ob) {
menuBar = new JMenuBar();
fileMenu = new JMenu("File");
fileNewBook = new JMenuItem("New Book...");
fileNewBook.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
createNewBook();
}
});
fileOpenBook = new JMenuItem("Open Book...");
fileExit = new JMenuItem("Exit");
fileMenu.add(fileNewBook);
fileMenu.add(fileOpenBook);
fileMenu.addSeparator();
fileMenu.add(fileExit);
menuBar.add(fileMenu);
bookMenu = new JMenu("Book");
bookNewChapter = new JMenuItem("New Chapter");
bookExportAudio = new JMenuItem("Export Audio...");
bookMenu.add(bookNewChapter);
bookMenu.add(bookExportAudio);
menuBar.add(bookMenu);
toolsMenu = new JMenu("Tools");
toolsOptions = new JMenuItem("Options");
toolsOptions.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Options o = new Options(AudiobookRecorder.this);
}
});
toolsMenu.add(toolsOptions);
menuBar.add(toolsMenu);
ob.add(menuBar, BorderLayout.NORTH);
setPreferredSize(new Dimension(700, 500));
setLocationRelativeTo(null);
pack();
setVisible(true);
}
public AudiobookRecorder() {
window = this;
Icons.loadIcons();
try {
String clsname = "com.jtattoo.plaf.aluminium.AluminiumLookAndFeel";
UIManager.setLookAndFeel(clsname);
Properties p = new Properties();
p.put("windowDecoration", "off");
p.put("logoString", "Audiobook");
p.put("textAntiAliasing", "on");
Class<?> cls = Class.forName(clsname);
Class[] cArg = new Class[1];
cArg[0] = Properties.class;
Method mth = cls.getMethod("setCurrentTheme", cArg);
mth.invoke(cls, p);
} catch (Exception e) {
e.printStackTrace();
}
Options.loadPreferences();
setLayout(new BorderLayout());
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
buildMenu(this);
centralPanel = new FlashPanel();
centralPanel.setLayout(new BorderLayout());
add(centralPanel, BorderLayout.CENTER);
sampleControl = new JPanel();
sampleControl.setLayout(new BorderLayout());
sampleControl.setPreferredSize(new Dimension(400, 150));
sampleWaveform = new Waveform();
sampleControl.add(sampleWaveform, BorderLayout.CENTER);
startOffset = new JSpinner(new SteppedNumericSpinnerModel(0, 0, 1, 0));
startOffset.setPreferredSize(new Dimension(100, 20));
endOffset = new JSpinner(new SteppedNumericSpinnerModel(0, 0, 1, 0));
endOffset.setPreferredSize(new Dimension(100, 20));
postSentenceGap = new JSpinner(new SteppedNumericSpinnerModel(0, 5000, 100, 0));
postSentenceGap.setPreferredSize(new Dimension(75, 20));
startOffset.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSpinner ob = (JSpinner)e.getSource();
if (selectedSentence != null) {
selectedSentence.setStartOffset((Integer)ob.getValue());
sampleWaveform.setLeftMarker((Integer)ob.getValue());
}
}
});
endOffset.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSpinner ob = (JSpinner)e.getSource();
if (selectedSentence != null) {
selectedSentence.setEndOffset((Integer)ob.getValue());
sampleWaveform.setRightMarker((Integer)ob.getValue());
}
}
});
postSentenceGap.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSpinner ob = (JSpinner)e.getSource();
if (selectedSentence != null) {
selectedSentence.setPostGap((Integer)ob.getValue());
}
}
});
JPanel controls = new JPanel();
controls.add(new JLabel("Start Offset:"));
JButton startFastDown = new JButton("<<");
startFastDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
int f = (Integer)startOffset.getValue();
int max = m.getMaximum();
f -= (max / 10);
if (f < 0) f = 0;
startOffset.setValue(f);
}
});
controls.add(startFastDown);
JButton startSlowDown = new JButton("<");
startSlowDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
int f = (Integer)startOffset.getValue();
int max = m.getMaximum();
f -= (max / 100);
if (f < 0) f = 0;
startOffset.setValue(f);
}
});
controls.add(startSlowDown);
controls.add(startOffset);
JButton startSlowUp = new JButton(">");
startSlowUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
int f = (Integer)startOffset.getValue();
int max = m.getMaximum();
f += (max / 100);
if (f > max) f = max;
startOffset.setValue(f);
}
});
controls.add(startSlowUp);
JButton startFastUp = new JButton(">>");
startFastUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
int f = (Integer)startOffset.getValue();
int max = m.getMaximum();
f += (max / 10);
if (f > max) f = max;
startOffset.setValue(f);
}
});
controls.add(startFastUp);
controls.add(new JLabel("End Offset:"));
JButton endFastDown = new JButton("<<");
endFastDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
int f = (Integer)endOffset.getValue();
int max = m.getMaximum();
f -= (max / 10);
if (f < 0) f = 0;
endOffset.setValue(f);
}
});
controls.add(endFastDown);
JButton endSlowDown = new JButton("<");
endSlowDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
int f = (Integer)endOffset.getValue();
int max = m.getMaximum();
f -= (max / 100);
if (f < 0) f = 0;
endOffset.setValue(f);
}
});
controls.add(endSlowDown);
controls.add(endOffset);
JButton endSlowUp = new JButton(">");
endSlowUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
int f = (Integer)endOffset.getValue();
int max = m.getMaximum();
f += (max / 100);
if (f > max) f = max;
endOffset.setValue(f);
}
});
controls.add(endSlowUp);
JButton endFastUp = new JButton(">>");
endFastUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
int f = (Integer)endOffset.getValue();
int max = m.getMaximum();
f += (max / 10);
if (f > max) f = max;
endOffset.setValue(f);
}
});
controls.add(endFastUp);
controls.add(new JLabel("Post gap:"));
controls.add(postSentenceGap);
controls.add(new JLabel("ms"));
sampleControl.add(controls, BorderLayout.SOUTH);
centralPanel.add(sampleControl, BorderLayout.SOUTH);
statusBar = new JPanel();
add(statusBar, BorderLayout.SOUTH);
statusFormat = new JLabel(Options.getAudioFormat().toString());
statusBar.add(statusFormat);
buildToolbar(centralPanel);
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("R"), "startRecord");
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released R"), "stopRecord");
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released D"), "deleteLast");
centralPanel.getActionMap().put("startRecord", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
startRecording();
}
});
centralPanel.getActionMap().put("stopRecord", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
stopRecording();
}
});
centralPanel.getActionMap().put("deleteLast", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
deleteLastRecording();
}
});
mainScroll = new JScrollPane();
centralPanel.add(mainScroll, BorderLayout.CENTER);
setTitle("AudioBook Recorder");
}
public static void main(String args[]) {
AudiobookRecorder frame = new AudiobookRecorder();
}
public void createNewBook() {
BookInfoPanel info = new BookInfoPanel("", "", "", "");
int r = JOptionPane.showConfirmDialog(this, info, "New Book", JOptionPane.OK_CANCEL_OPTION);
if (r != JOptionPane.OK_OPTION) return;
System.err.println("Name: " + info.getTitle());
System.err.println("Author: " + info.getAuthor());
System.err.println("Genre: " + info.getGenre());
System.err.println("Comment: " + info.getComment());
String name = info.getTitle();
File bookdir = new File(Options.get("path.storage"), name);
if (bookdir.exists()) {
JOptionPane.showMessageDialog(this, "File already exists.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
Properties prefs = new Properties();
prefs.setProperty("book.name", info.getTitle());
prefs.setProperty("book.author", info.getAuthor());
prefs.setProperty("book.genre", info.getGenre());
prefs.setProperty("book.comment", info.getComment());
prefs.setProperty("chapter.open.name", "Opening Credits");
prefs.setProperty("chapter.open.pre-gap", Options.get("catenation.pre-chapter"));
prefs.setProperty("chapter.open.post-gap", Options.get("catenation.post-chapter"));
prefs.setProperty("chapter.0001.name", "Chapter 1");
prefs.setProperty("chapter.0001.pre-gap", Options.get("catenation.pre-chapter"));
prefs.setProperty("chapter.0001.post-gap", Options.get("catenation.post-chapter"));
prefs.setProperty("chapter.close.name", "Closing Credits");
prefs.setProperty("chapter.close.pre-gap", Options.get("catenation.pre-chapter"));
prefs.setProperty("chapter.close.post-gap", Options.get("catenation.post-chapter"));
buildBook(prefs);
}
class JMenuObject extends JMenuItem {
Object ob;
public JMenuObject(String p) {
super(p);
ob = null;
}
public JMenuObject(String p, Object o) {
super(p);
ob = o;
}
public void setObject(Object o) {
ob = o;
}
public Object getObject() {
return ob;
}
}
void treePopup(MouseEvent e) {
int selRow = bookTree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = bookTree.getPathForLocation(e.getX(), e.getY());
if (selRow != -1) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)selPath.getLastPathComponent();
if (node instanceof Sentence) {
Sentence s = (Sentence)node;
bookTree.setSelectionPath(new TreePath(s.getPath()));
JPopupMenu menu = new JPopupMenu();
JMenuObject ins = new JMenuObject("Insert sentence above", s);
JMenuObject del = new JMenuObject("Delete sentence", s);
JMenuObject edit = new JMenuObject("Edit text", s);
JMenuObject process = new JMenuObject("Reprocess audio", s);
del.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Sentence s = (Sentence)o.getObject();
s.deleteFiles();
Chapter c = (Chapter)s.getParent();
bookTreeModel.removeNodeFromParent(s);
}
});
edit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Sentence s = (Sentence)o.getObject();
s.editText();
Chapter c = (Chapter)s.getParent();
bookTreeModel.reload(s);
}
});
menu.add(del);
menu.add(edit);
menu.add(process);
menu.show(bookTree, e.getX(), e.getY());
} else if (node instanceof Chapter) {
Chapter c = (Chapter)node;
bookTree.setSelectionPath(new TreePath(c.getPath()));
JPopupMenu menu = new JPopupMenu();
JMenuObject ren = new JMenuObject("Rename chapter", c);
ren.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter c = (Chapter)o.getObject();
c.renameChapter();
bookTreeModel.reload(c);
}
});
menu.add(ren);
menu.show(bookTree, e.getX(), e.getY());
}
}
}
public void startRecording() {
if (recording != null) return;
if (book == null) return;
toolBar.disableBook();
toolBar.disableSentence();
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)bookTree.getLastSelectedPathComponent();
if (selectedNode == null) {
selectedNode = book.getLastChapter();
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
}
if (selectedNode instanceof Book) {
selectedNode = book.getLastChapter();
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
}
if (selectedNode instanceof Sentence) {
selectedNode = (DefaultMutableTreeNode)selectedNode.getParent();
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
}
Chapter c = (Chapter)selectedNode;
Sentence s = new Sentence();
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
bookTree.expandPath(new TreePath(c.getPath()));
bookTree.setSelectionPath(new TreePath(s.getPath()));
bookTree.scrollPathToVisible(new TreePath(s.getPath()));
if (s.startRecording()) {
recording = s;
centralPanel.setFlash(true);
}
}
public void stopRecording() {
if (recording == null) return;
recording.stopRecording();
bookTreeModel.reload(book);
bookTree.expandPath(new TreePath(((DefaultMutableTreeNode)recording.getParent()).getPath()));
bookTree.setSelectionPath(new TreePath(recording.getPath()));
bookTree.scrollPathToVisible(new TreePath(recording.getPath()));
toolBar.enableBook();
toolBar.enableSentence();
centralPanel.setFlash(false);
recording = null;
saveBookStructure();
}
public void deleteLastRecording() {
if (recording != null) return;
if (book == null) return;
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)bookTree.getLastSelectedPathComponent();
if (selectedNode == null) {
selectedNode = book.getLastChapter();
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
}
if (selectedNode instanceof Book) {
selectedNode = book.getLastChapter();
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
}
if (selectedNode instanceof Sentence) {
selectedNode = (DefaultMutableTreeNode)selectedNode.getParent();
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
}
Chapter c = (Chapter)selectedNode;
Sentence s = c.getLastSentence();
if (s == null) return;
s.deleteFiles();
bookTreeModel.removeNodeFromParent(s);
s = c.getLastSentence();
bookTree.expandPath(new TreePath(selectedNode.getPath()));
if (s != null) {
bookTree.setSelectionPath(new TreePath(s.getPath()));
bookTree.scrollPathToVisible(new TreePath(s.getPath()));
}
}
public void addChapter() {
Chapter c = book.addChapter();
Chapter lc = book.getLastChapter();
int i = bookTreeModel.getIndexOfChild(book, lc);
bookTreeModel.insertNodeInto(c, book, i+1);
bookTree.scrollPathToVisible(new TreePath(c.getPath()));
}
@SuppressWarnings("unchecked")
public void saveBookStructure() {
File bookRoot = new File(Options.get("path.storage"), book.getName());
if (!bookRoot.exists()) {
bookRoot.mkdirs();
}
File config = new File(bookRoot, "audiobook.abk");
Properties prefs = new Properties();
prefs.setProperty("book.name", book.getName());
prefs.setProperty("book.author", book.getAuthor());
prefs.setProperty("book.genre", book.getGenre());
prefs.setProperty("book.comment", book.getComment());
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
Chapter c = o.nextElement();
String keybase = "chapter." + c.getId();
prefs.setProperty(keybase + ".name", c.getName());
prefs.setProperty(keybase + ".pre-gap", Integer.toString(c.getPreGap()));
prefs.setProperty(keybase + ".post-gap", Integer.toString(c.getPostGap()));
int i = 0;
for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
Sentence snt = s.nextElement();
prefs.setProperty(String.format("%s.sentence.%08d.id", keybase, i), snt.getId());
prefs.setProperty(String.format("%s.sentence.%08d.text", keybase, i), snt.getText());
prefs.setProperty(String.format("%s.sentence.%08d.post-gap", keybase, i), Integer.toString(snt.getPostGap()));
prefs.setProperty(String.format("%s.sentence.%08d.start-offset", keybase, i), Integer.toString(snt.getStartOffset()));
prefs.setProperty(String.format("%s.sentence.%08d.end-offset", keybase, i), Integer.toString(snt.getEndOffset()));
i++;
}
}
try {
FileOutputStream fos = new FileOutputStream(config);
prefs.storeToXML(fos, "Audiobook Recorder Description");
} catch (Exception e) {
e.printStackTrace();
}
}
public void loadBookStructure(File f) {
try {
Properties prefs = new Properties();
FileInputStream fis = new FileInputStream(f);
prefs.loadFromXML(fis);
buildBook(prefs);
} catch (Exception e) {
e.printStackTrace();
}
}
public void buildBook(Properties prefs) {
book = new Book(prefs.getProperty("book.name"));
book.setAuthor(prefs.getProperty("book.author"));
book.setGenre(prefs.getProperty("book.genre"));
book.setComment(prefs.getProperty("book.comment"));
bookTreeModel = new DefaultTreeModel(book);
bookTree = new JTree(bookTreeModel);
bookTree.setEditable(true);
bookTree.setUI(new CustomTreeUI(mainScroll));
roomNoise = new Sentence("room-noise", "Room Noise");
bookTree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode)bookTree.getLastSelectedPathComponent();
if (n instanceof Sentence) {
Sentence s = (Sentence)n;
selectedSentence = s;
sampleWaveform.setData(s.getAudioData());
sampleWaveform.setMarkers(s.getStartOffset(), s.getEndOffset());
startOffset.setValue(s.getStartOffset());
endOffset.setValue(s.getEndOffset());
postSentenceGap.setValue(s.getPostGap());
int samples = s.getSampleSize();
((SteppedNumericSpinnerModel)startOffset.getModel()).setMaximum(samples);
((SteppedNumericSpinnerModel)endOffset.getModel()).setMaximum(samples);
toolBar.enableSentence();
} else {
selectedSentence = null;
toolBar.disableSentence();
sampleWaveform.clearData();
startOffset.setValue(0);
endOffset.setValue(0);
postSentenceGap.setValue(0);
}
}
});
bookTree.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
treePopup(e);
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
treePopup(e);
}
}
});
mainScroll.setViewportView(bookTree);
Chapter c = new Chapter("open", prefs.getProperty("chapter.open.name"));
c.setPostGap(s2i(prefs.getProperty("chapter.open.post-gap")));
c.setPreGap(s2i(prefs.getProperty("chapter.open.pre-gap")));
bookTreeModel.insertNodeInto(c, book, 0);
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.open.sentence.%08d.id", i));
String text = prefs.getProperty(String.format("chapter.open.sentence.%08d.text", i));
int gap = s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.post-gap", i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.start-offset", i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.end-offset", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
for (int cno = 1; cno < 10000; cno++) {
String cname = prefs.getProperty(String.format("chapter.%04d.name", cno));
if (cname == null) break;
c = new Chapter(String.format("%04d", cno), cname);
c.setPostGap(s2i(prefs.getProperty(String.format("chapter.%04d.post-gap", cno))));
c.setPreGap(s2i(prefs.getProperty(String.format("chapter.%04d.pre-gap", cno))));
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.%04d.sentence.%08d.id", cno, i));
String text = prefs.getProperty(String.format("chapter.%04d.sentence.%08d.text", cno, i));
int gap = s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.post-gap", cno, i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.start-offset", cno, i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.end-offset", cno, i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
}
c = new Chapter("close", prefs.getProperty("chapter.close.name"));
c.setPostGap(s2i(prefs.getProperty("chapter.close.post-gap")));
c.setPreGap(s2i(prefs.getProperty("chapter.close.pre-gap")));
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.close.sentence.%08d.id", i));
String text = prefs.getProperty(String.format("chapter.close.sentence.%08d.text", i));
int gap = s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.post-gap", i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.start-offset", i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.end-offset", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
bookTree.expandPath(new TreePath(book.getPath()));
toolBar.enableBook();
}
public void openBook() {
OpenBookPanel info = new OpenBookPanel();
int r = JOptionPane.showConfirmDialog(this, info, "Open Book", JOptionPane.OK_CANCEL_OPTION);
if (r == JOptionPane.OK_OPTION) {
File f = info.getSelectedFile();
if (!f.exists()) {
JOptionPane.showMessageDialog(this, "File not found.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
if (f.isDirectory()) {
JOptionPane.showMessageDialog(this, "File is directory.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
if (!f.getName().endsWith(".abk")) {
JOptionPane.showMessageDialog(this, "Not a .abk file.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
loadBookStructure(f);
}
}
int s2i(String s) {
try {
return Integer.parseInt(s);
} catch (Exception e) {
}
return 0;
}
public File getBookFolder() {
File bf = new File(Options.get("path.storage"), book.getName());
if (!bf.exists()) {
bf.mkdirs();
}
return bf;
}
public int getNoiseFloor() {
int[] samples = roomNoise.getAudioData();
if (samples == null) {
return 0;
}
int ms = 0;
for (int i = 0; i < samples.length; i++) {
if (Math.abs(samples[i]) > ms) {
ms = Math.abs(samples[i]);
}
}
ms *= 10;
ms /= 9;
return ms;
}
public void recordRoomNoise() {
if (roomNoise.startRecording()) {
centralPanel.setFlash(true);
java.util.Timer ticker = new java.util.Timer(true);
ticker.schedule(new TimerTask() {
public void run() {
roomNoise.stopRecording();
centralPanel.setFlash(false);
}
}, 5000); // 5 seconds of recording
}
}
public void playSelectedSentence() {
if (selectedSentence == null) return;
if (playing != null) return;
playing = selectedSentence;
playingThread = new Thread(new Runnable() {
public void run() {
playing.play();
playing = null;
}
});
playingThread.setDaemon(true);
playingThread.start();
}
}

View File

@@ -0,0 +1,102 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
public class Book extends DefaultMutableTreeNode {
String name;
String author;
String genre;
String comment;
public Book(String bookname) {
super(bookname);
name = bookname;
}
public void setAuthor(String a) {
author = a;
}
public void setGenre(String g) {
genre = g;
}
public void setComment(String c) {
comment = c;
}
public String getAuthor() {
return author;
}
public String getGenre() {
return genre;
}
public String getComment() {
return comment;
}
public Chapter getClosingCredits() {
return getChapterById("close");
}
public Chapter getOpeningCredits() {
return getChapterById("open");
}
@SuppressWarnings("unchecked")
public Chapter getChapterById(String id) {
for (Enumeration<Object>o = children(); o.hasMoreElements();) {
Object ob = o.nextElement();
if (ob instanceof Chapter) {
Chapter c = (Chapter)ob;
if (c.getId().equals(id)) {
return c;
}
}
}
return null;
}
public Chapter getLastChapter() {
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;
}
public Chapter getChapter(int n) {
if (n == 0) return null;
return (Chapter)getChildAt(n);
}
public Chapter addChapter() {
Chapter lc = getLastChapter();
if (lc == null) return new Chapter("1", "Chapter 1");
try {
int lcid = Integer.parseInt(lc.getId());
lcid++;
Chapter nc = new Chapter(String.format("%04d", lcid), "Chapter " + lcid);
return nc;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,92 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class BookInfoPanel extends JPanel {
JTextField title;
JTextField author;
JTextField genre;
JTextField comment;
public BookInfoPanel(String t, String a, String g, String c) {
super();
setLayout(new GridBagLayout());
GridBagConstraints con = new GridBagConstraints();
con.gridx = 0;
con.gridy = 0;
add(new JLabel("Title:"), con);
con.gridx = 1;
title = new JTextField(t);
title.setPreferredSize(new Dimension(200, 20));
add(title, con);
con.gridx = 0;
con.gridy++;
add(new JLabel("Author:"), con);
con.gridx = 1;
author = new JTextField(a);
author.setPreferredSize(new Dimension(200, 20));
add(author, con);
con.gridx = 0;
con.gridy++;
add(new JLabel("Genre:"), con);
con.gridx = 1;
genre = new JTextField(g);
genre.setPreferredSize(new Dimension(200, 20));
add(genre, con);
con.gridx = 0;
con.gridy++;
add(new JLabel("Comment:"), con);
con.gridx = 1;
comment = new JTextField(c);
comment.setPreferredSize(new Dimension(200, 20));
add(comment, con);
con.gridx = 0;
con.gridy++;
}
public String getTitle() {
return title.getText();
}
public String getAuthor() {
return author.getText();
}
public String getGenre() {
return genre.getText();
}
public String getComment() {
return comment.getText();
}
public void setTitle(String t) {
title.setText(t);
}
public void setAuthor(String a) {
author.setText(a);
}
public void setGenre(String g) {
genre.setText(g);
}
public void setComment(String c) {
comment.setText(c);
}
}

View File

@@ -0,0 +1,81 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
public class Chapter extends DefaultMutableTreeNode {
String name;
String id;
int preGap;
int postGap;
public Chapter(String i, String chaptername) {
super(chaptername);
id = i;
name = chaptername;
preGap = Options.getInteger("catenation.pre-chapter");
postGap = Options.getInteger("catenation.post-chapter");
}
public String getId() {
return id;
}
public Sentence getLastSentence() {
DefaultMutableTreeNode ls = getLastLeaf();
if (ls instanceof Sentence) return (Sentence)ls;
return null;
}
public void renameChapter() {
String n = JOptionPane.showInputDialog(null, "Rename Chapter", name);
if (n != null) {
name = n;
}
}
public String toString() {
return name;
}
public void setUserObject(Object o) {
if (o instanceof String) {
String so = (String)o;
name = so;
}
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public void setPreGap(int g) {
preGap = g;
}
public int getPreGap() {
return preGap;
}
public void setPostGap(int g) {
postGap = g;
}
public int getPostGap() {
return postGap;
}
}

View File

@@ -0,0 +1,45 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import java.awt.*;
public class CustomTreeUI extends BasicTreeUI {
JScrollPane pane;
public CustomTreeUI(JScrollPane p) {
super();
pane = p;
}
@Override
protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
return new NodeDimensionsHandler() {
@Override
public Rectangle getNodeDimensions(
Object value, int row, int depth, boolean expanded,
Rectangle size) {
Rectangle dimensions = super.getNodeDimensions(value, row,
depth, expanded, size);
dimensions.width =
pane.getWidth() - getRowX(row, depth);
return dimensions;
}
};
}
@Override
protected void paintHorizontalLine(Graphics g, JComponent c,
int y, int left, int right) {
// do nothing.
}
@Override
protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
Insets insets, TreePath path) {
// do nothing.
}
}

View File

@@ -0,0 +1,51 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class FlashPanel extends JPanel {
boolean flash = false;
boolean col = false;
java.util.Timer ticker;
public FlashPanel() {
super();
ticker = new java.util.Timer(true);
ticker.scheduleAtFixedRate(new TimerTask() {
public void run() {
if (flash) {
col = !col;
repaint();
}
}
}, 0, 500);
}
public void setFlash(boolean f) {
flash = f;
for (Component o : getComponents()) {
((JComponent)o).setVisible(!f);
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
if (flash == false) {
super.paintComponent(g);
return;
}
if (col) {
g.setColor(Color.RED);
} else {
g.setColor(Color.BLACK);
}
Dimension d = getSize();
g.fillRect(0, 0, d.width, d.height);
}
}

View File

@@ -0,0 +1,38 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
public class Icons {
static public ImageIcon book;
static public ImageIcon chapter;
static public ImageIcon sentence;
static public ImageIcon play;
static public ImageIcon playon;
static public ImageIcon stop;
static public ImageIcon record;
static public ImageIcon openBook;
static public ImageIcon newBook;
static public ImageIcon newChapter;
static public ImageIcon recordRoom;
static public ImageIcon save;
static void loadIcons() {
book = new ImageIcon(Icons.class.getResource("icons/book.png"));
chapter = new ImageIcon(Icons.class.getResource("icons/chapter.png"));
sentence = new ImageIcon(Icons.class.getResource("icons/sentence.png"));
play = new ImageIcon(Icons.class.getResource("icons/play.png"));
playon = new ImageIcon(Icons.class.getResource("icons/playon.png"));
stop = new ImageIcon(Icons.class.getResource("icons/stop.png"));
record = new ImageIcon(Icons.class.getResource("icons/record.png"));
openBook = new ImageIcon(Icons.class.getResource("icons/open.png"));
newBook = new ImageIcon(Icons.class.getResource("icons/new.png"));
newChapter = new ImageIcon(Icons.class.getResource("icons/new-chapter.png"));
recordRoom = new ImageIcon(Icons.class.getResource("icons/record-room.png"));
save = new ImageIcon(Icons.class.getResource("icons/save.png"));
}
}

View File

@@ -0,0 +1,116 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class MainToolBar extends JToolBar {
JButton newBook;
JButton openBook;
JButton saveBook;
JButton newChapter;
JButton recordRoomNoise;
JButton playSentence;
JButton playonSentence;
JButton recordSentence;
AudiobookRecorder root;
public MainToolBar(AudiobookRecorder r) {
super();
root = r;
newBook = new JButton(Icons.newBook);
newBook.setToolTipText("New Book");
newBook.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.createNewBook();
}
});
add(newBook);
openBook = new JButton(Icons.openBook);
openBook.setToolTipText("Open Book");
openBook.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.openBook();
}
});
add(openBook);
saveBook = new JButton(Icons.save);
saveBook.setToolTipText("Save Book");
saveBook.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.saveBookStructure();
}
});
add(saveBook);
addSeparator();
newChapter = new JButton(Icons.newChapter);
newChapter.setToolTipText("New Chapter");
newChapter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.addChapter();
}
});
add(newChapter);
recordRoomNoise = new JButton(Icons.recordRoom);
recordRoomNoise.setToolTipText("Record Room Noise");
recordRoomNoise.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.recordRoomNoise();
}
});
add(recordRoomNoise);
addSeparator();
playSentence = new JButton(Icons.play);
playSentence.setToolTipText("Play sentence");
playSentence.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.playSelectedSentence();
}
});
add(playSentence);
playonSentence = new JButton(Icons.playon);
playonSentence.setToolTipText("Play from sentence");
add(playonSentence);
recordSentence = new JButton(Icons.record);
recordSentence.setToolTipText("Re-record sentence");
add(recordSentence);
setFloatable(false);
}
public void enableBook() {
newChapter.setEnabled(true);
recordRoomNoise.setEnabled(true);
}
public void disableBook() {
newChapter.setEnabled(false);
recordRoomNoise.setEnabled(false);
}
public void enableSentence() {
playSentence.setEnabled(true);
playonSentence.setEnabled(true);
recordSentence.setEnabled(true);
}
public void disableSentence() {
playSentence.setEnabled(false);
playonSentence.setEnabled(false);
recordSentence.setEnabled(false);
}
}

View File

@@ -0,0 +1,133 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
public class OpenBookPanel extends JPanel {
JScrollPane scroll;
JTable table;
class BookInfo {
public String name;
public String author;
public String genre;
public String comment;
public BookInfo(String n, String a, String g, String c) {
name = n;
author = a;
genre = g;
comment = c;
}
}
class BookTableModel extends AbstractTableModel {
ArrayList<BookInfo> books;
public BookTableModel() {
super();
books = new ArrayList<BookInfo>();
}
public int getRowCount() {
return books.size();
}
public int getColumnCount() {
return 4;
}
public boolean isCellEditable(int row, int column) {
return false;
}
public void addBook(BookInfo b) {
books.add(b);
}
public Object getValueAt(int r, int c) {
if (c > 3) return null;
if (r > books.size()) return null;
BookInfo b = books.get(r);
switch (c) {
case 0: return b.name;
case 1: return b.author;
case 2: return b.genre;
case 4: return b.comment;
}
return null;
}
public String getColumnName(int i) {
switch(i) {
case 0: return "Name";
case 1: return "Author";
case 2: return "Genre";
case 3: return "Comment";
}
return null;
}
}
BookTableModel model;
public OpenBookPanel() {
super();
model = new BookTableModel();
setLayout(new BorderLayout());
scroll = new JScrollPane();
add(scroll, BorderLayout.CENTER);
try {
File dir = new File(Options.get("path.storage"));
for (File b : dir.listFiles()) {
if (!b.isDirectory()) continue;
File xml = new File(b, "audiobook.abk");
if (xml.exists()) {
Properties props = new Properties();
props.loadFromXML(new FileInputStream(xml));
BookInfo book = new BookInfo(
props.getProperty("book.name"),
props.getProperty("book.author"),
props.getProperty("book.genre"),
props.getProperty("book.comment")
);
model.addBook(book);
}
}
table = new JTable(model);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
scroll.setViewportView(table);
} catch (Exception e) {
e.printStackTrace();
}
}
public File getSelectedFile() {
int sel = table.getSelectedRow();
if (sel == -1) {
return null;
}
String name = (String)table.getValueAt(sel, 0);
File d = new File(Options.get("path.storage"), name);
File f = new File(d, "audiobook.abk");
return f;
}
}

View File

@@ -0,0 +1,359 @@
package uk.co.majenko.audiobookrecorder;
import javax.sound.sampled.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.prefs.*;
import java.io.*;
public class Options extends JDialog {
GridBagConstraints constraint;
JComboBox<KVPair> mixerList;
JComboBox<KVPair> playbackList;
JComboBox<KVPair> channelList;
JComboBox<KVPair> rateList;
JTextField storageFolder;
JSpinner preChapterGap;
JSpinner postChapterGap;
JSpinner postSentenceGap;
static HashMap<String, String> defaultPrefs;
static Preferences prefs = null;
static class KVPair implements Comparable {
public String key;
public String value;
public KVPair(String k, String v) {
key = k;
value = v;
}
public String toString() {
return value;
}
public int compareTo(Object o) {
if (o instanceof KVPair) {
KVPair ko = (KVPair)o;
return key.compareTo(ko.key);
}
return 0;
}
}
JComboBox<KVPair> addDropdown(String label, KVPair[] options, String def) {
JLabel l = new JLabel(label);
constraint.gridx = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.anchor = GridBagConstraints.LINE_START;
add(l, constraint);
JComboBox<KVPair> o = new JComboBox<KVPair>(options);
constraint.gridx = 1;
add(o, constraint);
for (KVPair p : options) {
if (p.key.equals(def)) {
o.setSelectedItem(p);
}
}
constraint.gridy++;
return o;
}
JTextField addFilePath(String label, String path) {
JLabel l = new JLabel(label);
constraint.gridx = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.anchor = GridBagConstraints.LINE_START;
add(l, constraint);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
JTextField a = new JTextField(path);
p.add(a, BorderLayout.CENTER);
JButton b = new JButton("...");
p.add(b, BorderLayout.EAST);
constraint.gridx = 1;
constraint.fill = GridBagConstraints.HORIZONTAL;
add(p, constraint);
constraint.fill = GridBagConstraints.NONE;
constraint.gridy++;
return a;
}
void addSeparator() {
constraint.gridx = 0;
constraint.gridwidth = 2;
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
JSeparator sep = new JSeparator(SwingConstants.HORIZONTAL);
sep.setPreferredSize(new Dimension(1, 1));
p.add(sep, BorderLayout.CENTER);
constraint.fill = GridBagConstraints.HORIZONTAL;
constraint.insets = new Insets(10, 2, 10, 2);
add(p, constraint);
constraint.insets = new Insets(2, 2, 2, 2);
constraint.fill = GridBagConstraints.NONE;
constraint.gridwidth = 1;
constraint.gridy++;
}
JSpinner addSpinner(String label, int min, int max, int step, int value, String suffix) {
JLabel l = new JLabel(label);
constraint.gridx = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.anchor = GridBagConstraints.LINE_START;
add(l, constraint);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
JSpinner a = new JSpinner(new SteppedNumericSpinnerModel(min, max, step, value));
p.add(a, BorderLayout.CENTER);
JLabel b = new JLabel(suffix);
p.add(b, BorderLayout.EAST);
constraint.gridx = 1;
constraint.fill = GridBagConstraints.HORIZONTAL;
add(p, constraint);
constraint.fill = GridBagConstraints.NONE;
constraint.gridy++;
return a;
}
public Options(JFrame parent) {
loadPreferences(); // Just in case. It should do nothing.
setLayout(new GridBagLayout());
constraint = new GridBagConstraints();
constraint.gridx = 0;
constraint.gridy = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.insets = new Insets(2, 2, 2, 2);
addSeparator();
mixerList = addDropdown("Recording device:", getMixerList(), get("audio.recording.device"));
channelList = addDropdown("Channels:", getChannelCountList(), get("audio.recording.channels"));
rateList = addDropdown("Sample rate:", getSampleRateList(), get("audio.recording.samplerate"));
addSeparator();
playbackList = addDropdown("Playback device:", getMixerList(), get("audio.playback.device"));
addSeparator();
storageFolder = addFilePath("Storage folder:", get("path.storage"));
addSeparator();
preChapterGap = addSpinner("Default pre-chapter gap:", 0, 5000, 100, getInteger("catenation.pre-chapter"), "ms");
postChapterGap = addSpinner("Default post-chapter gap:", 0, 5000, 100, getInteger("catenation.post-chapter"), "ms");
postSentenceGap = addSpinner("Default post-sentence gap:", 0, 5000, 100, getInteger("catenation.post-sentence"), "ms");
addSeparator();
setTitle("Options");
setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
constraint.gridx = 1;
constraint.gridwidth = 1;
constraint.gridheight = 1;
JPanel box = new JPanel();
JButton ok = new JButton("OK");
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
storePreferences();
Options.this.dispatchEvent(new WindowEvent(Options.this, WindowEvent.WINDOW_CLOSING));
}
});
box.add(ok);
JButton cancel = new JButton("Cancel");
cancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Options.this.dispatchEvent(new WindowEvent(Options.this, WindowEvent.WINDOW_CLOSING));
}
});
box.add(cancel);
constraint.anchor = GridBagConstraints.LINE_END;
add(box, constraint);
pack();
setLocationRelativeTo(parent);
setVisible(true);
}
static KVPair[] getMixerList() {
TreeSet<KVPair> list = new TreeSet<KVPair>();
Mixer.Info[] info = AudioSystem.getMixerInfo();
for (Mixer.Info i : info) {
KVPair p = new KVPair(i.getName(), i.getDescription());
list.add(p);
}
return list.toArray(new KVPair[0]);
}
static KVPair[] getChannelCountList() {
KVPair[] l = new KVPair[2];
l[0] = new KVPair("1", "Mono");
l[1] = new KVPair("2", "Stereo");
return l;
}
static KVPair[] getSampleRateList() {
KVPair[] l = new KVPair[2];
l[0] = new KVPair("44100", "44100");
l[1] = new KVPair("48000", "48000");
return l;
}
public static void loadPreferences() {
defaultPrefs = new HashMap<String, String>();
KVPair[] mixers = getMixerList();
defaultPrefs.put("audio.recording.device", mixers[0].key);
defaultPrefs.put("audio.recording.channels", "2");
defaultPrefs.put("audio.recording.samplerate", "48000");
defaultPrefs.put("audio.playback.device", mixers[0].key);
defaultPrefs.put("path.storage", (new File(System.getProperty("user.home"), "Recordings")).toString());
defaultPrefs.put("catenation.pre-chapter", "2000");
defaultPrefs.put("catenation.post-chapter", "2000");
defaultPrefs.put("catenation.post-sentence", "1000");
if (prefs == null) {
prefs = Preferences.userNodeForPackage(AudiobookRecorder.class);
}
}
public static void savePreferences() {
if (prefs != null) {
try {
prefs.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String get(String key) {
if (prefs == null) return null;
String def = defaultPrefs.get(key);
String v = prefs.get(key, def);
return v;
}
public static Integer getInteger(String key) {
try {
Integer i = Integer.parseInt(get(key));
return i;
} catch (Exception e) {
}
return 0;
}
public static void set(String key, String value) {
if (prefs == null) return;
prefs.put(key, value);
}
void storePreferences() {
set("audio.recording.device", ((KVPair)mixerList.getSelectedItem()).key);
set("audio.recording.channels", ((KVPair)channelList.getSelectedItem()).key);
set("audio.recording.samplerate", ((KVPair)rateList.getSelectedItem()).key);
set("audio.playback.device", ((KVPair)playbackList.getSelectedItem()).key);
set("path.storage", storageFolder.getText());
set("catenation.pre-chapter", "" + preChapterGap.getValue());
set("catenation.post-chapter", "" + preChapterGap.getValue());
set("catenation.post-sentence", "" + preChapterGap.getValue());
savePreferences();
}
public static AudioFormat getAudioFormat() {
String sampleRate = get("audio.recording.samplerate");
String channels = get("audio.recording.channels");
float sr = 48000f;
int chans = 2;
try {
sr = Float.parseFloat(sampleRate);
} catch (Exception e) {
sr = 48000f;
}
try {
chans = Integer.parseInt(channels);
} catch (Exception e) {
chans = 2;
}
AudioFormat af = new AudioFormat(sr, 16, chans, true, false);
return af;
}
public static Mixer.Info getMixerByName(String name) {
Mixer.Info[] mixers = AudioSystem.getMixerInfo();
for (Mixer.Info info : mixers) {
if (info.getName().equals(name)) {
return info;
}
}
return mixers[0];
}
public static Mixer.Info getRecordingMixer() {
return getMixerByName(get("audio.recording.device"));
}
public static Mixer.Info getPlaybackMixer() {
return getMixerByName(get("audio.playback.device"));
}
}

View File

@@ -0,0 +1,335 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import javax.sound.sampled.*;
public class Sentence extends DefaultMutableTreeNode {
String text;
String id;
int postGap;
int startOffset = 0;
int endOffset = 0;
int sampleSize = -1;
boolean isSilence = false;
boolean recording;
TargetDataLine line;
AudioInputStream inputStream;
Thread recordingThread = null;
public Sentence() {
super("");
id = UUID.randomUUID().toString();
text = id;
setUserObject(text);
postGap = Options.getInteger("catenation.post-sentence");
}
public Sentence(String i, String t) {
super("");
id = i;
text = t;
setUserObject(text);
postGap = Options.getInteger("catenation.post-sentence");
}
public boolean startRecording() {
AudioFormat format = new AudioFormat(
Options.getInteger("audio.recording.samplerate"),
16,
Options.getInteger("audio.recording.channels"),
true,
false
);
if (format == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
Mixer.Info mixer = Options.getRecordingMixer();
line = null;
try {
line = AudioSystem.getTargetDataLine(format, mixer);
} catch (Exception e) {
e.printStackTrace();
}
if (line == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
inputStream = new AudioInputStream(line);
try {
line.open();
} catch (Exception e) {
e.printStackTrace();
return false;
}
line.start();
File audioFile = new File(AudiobookRecorder.window.getBookFolder(), id + ".wav");
recordingThread = new Thread(new Runnable() {
public void run() {
try {
AudioSystem.write(inputStream, AudioFileFormat.Type.WAVE, audioFile);
} catch (Exception e) {
e.printStackTrace();
}
}
});
recordingThread.setDaemon(true);
recordingThread.start();
recording = true;
return true;
}
public void stopRecording() {
try {
inputStream.close();
line.stop();
line.close();
} catch (Exception e) {
e.printStackTrace();
}
recording = false;
if (!id.equals("room-noise")) {
autoTrimSample();
}
}
public void autoTrimSample() {
int[] samples = getAudioData();
int noiseFloor = AudiobookRecorder.window.getNoiseFloor();
isSilence = false;
// Find start
for (int i = 0; i < samples.length; i++) {
startOffset = i;
if (Math.abs(samples[i]) > noiseFloor) {
startOffset --;
if (startOffset < 0) startOffset = 0;
break;
}
}
if (startOffset >= samples.length-1) { // Failed! Silence?
isSilence = true;
startOffset = 0;
}
for (int i = samples.length-1; i >= 0; i--) {
endOffset = i;
if (Math.abs(samples[i]) > noiseFloor) {
endOffset ++;
if (endOffset >= samples.length-1) endOffset = samples.length-1;
break;
}
}
if (endOffset <= 0) {
isSilence = true;
endOffset = samples.length-1;
}
}
public String getId() {
return id;
}
public void setText(String t) {
text = t;
}
public String getText() {
return text;
}
public File getFile() {
return new File(AudiobookRecorder.window.getBookFolder(), id + ".wav");
}
public void editText() {
String t = JOptionPane.showInputDialog(null, "Edit Text", text);
if (t != null) {
text = t;
}
}
public String toString() {
return text;
}
public boolean isRecording() {
return recording;
}
public void setUserObject(Object o) {
if (o instanceof String) {
String so = (String)o;
text = so;
}
}
public int getPostGap() {
return postGap;
}
public void setPostGap(int g) {
postGap = g;
}
public void deleteFiles() {
File audioFile = new File(AudiobookRecorder.window.getBookFolder(), id + ".wav");
if (audioFile.exists()) {
audioFile.delete();
}
}
public int[] getAudioData() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
long len = s.getFrameLength();
int frameSize = format.getFrameSize();
int chans = format.getChannels();
int bytes = frameSize / chans;
byte[] frame = new byte[frameSize];
int[] samples = new int[(int)len];
if (bytes != 2) return null;
for (long fno = 0; fno < len; fno++) {
s.read(frame);
int sample = 0;
if (chans == 2) { // Stereo
int left = (frame[1] << 8) | frame[0];
int right = (frame[3] << 8) | frame[2];
sample = (left + right) / 2;
} else {
sample = (frame[1] << 8) | frame[0];
}
samples[(int)fno] = sample;
}
s.close();
sampleSize = samples.length;
return samples;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int getStartOffset() {
return startOffset;
}
public void setStartOffset(int o) {
startOffset = o;
}
public int getEndOffset() {
return endOffset;
}
public void setEndOffset(int o) {
endOffset = o;
}
public int getSampleSize() {
if (sampleSize == -1) {
getAudioData();
}
return sampleSize;
}
// Open the audio file as an AudioInputStream and automatically
// skip the first startOffset frames.
public AudioInputStream getAudioStream() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
long len = s.getFrameLength();
int frameSize = format.getFrameSize();
s.skip(frameSize * startOffset);
return s;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public AudioFormat getAudioFormat() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
return format;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void play() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
long len = s.getFrameLength();
int frameSize = format.getFrameSize();
int pos = startOffset * frameSize;
SourceDataLine play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer());
play.open(format);
play.start();
byte[] buffer = new byte[1024];
s.skip(pos);
while (pos < endOffset * frameSize) {
int nr = s.read(buffer);
pos += nr;
play.write(buffer, 0, nr);
};
play.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,78 @@
package uk.co.majenko.audiobookrecorder;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class SteppedNumericSpinnerModel implements SpinnerModel {
int min;
int max;
int step;
int value;
ArrayList<ChangeListener> listeners;
public SteppedNumericSpinnerModel(int amin, int amax, int astep, int avalue) {
min = amin;
max = amax;
step = astep;
value = avalue;
listeners = new ArrayList<ChangeListener>();
}
public Object getNextValue() {
Integer v = value;
v += step;
if (v > max) return null;
return v;
}
public Object getPreviousValue() {
Integer v = value;
v -= step;
if (v < min) return null;
return v;
}
public Object getValue() {
Integer v = value;
return v;
}
public void setValue(Object v) {
if (v instanceof Integer) {
Integer i = (Integer)v;
value = i;
ChangeEvent e = new ChangeEvent(this);
for (ChangeListener l : listeners) {
l.stateChanged(e);
}
}
}
public void addChangeListener(ChangeListener l) {
listeners.add(l);
}
public void removeChangeListener(ChangeListener l) {
listeners.remove(l);
}
public void setMinimum(int i) {
min = i;
}
public void setMaximum(int i) {
max = i;
}
public int getMaximum() {
return max;
}
public int getMinimum() {
return min;
}
}

View File

@@ -0,0 +1,118 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.io.*;
import javax.sound.sampled.*;
public class Waveform extends JPanel {
int[] samples = null;
int leftMarker = 0;
int rightMarker = 0;
public Waveform() {
super();
}
public void paintComponent(Graphics g) {
Dimension size = getSize();
int w = size.width;
int h = size.height;
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.GREEN);
g.drawRect(0, 0, w, h);
g.drawLine(0, h/2, w, h/2);
g.setColor(new Color(0, 150, 0));
for (int x = 0; x < w; x += w/10) {
g.drawLine(x, 0, x, h);
}
for (int x = 0; x < w; x += 4) {
g.drawLine(x, h/4, x, h/4);
g.drawLine(x, h/4*3, x, h/4*3);
}
int scale = 32768/(h/2);
if (samples != null) {
int num = samples.length;
int step = num / w;
for (int n = 0; n < w; n++) {
int hcnt = 0;
long have = 0;
int hmax = 0;
int lcnt = 0;
int lave = 0;
int lmax = 0;
for (int o = 0; o < step; o++) {
int sample = samples[(n * step) + o];
if (sample >= 0) {
have += sample;
hcnt++;
if (sample > hmax) hmax = sample;
} else {
sample = Math.abs(sample);
lave += sample;
lcnt++;
if (sample > lmax) lmax = sample;
}
}
if (hcnt > 0) have /= hcnt;
if (lcnt > 0) lave /= lcnt;
hmax /= scale;
lmax /= scale;
have /= scale;
lave /= scale;
g.setColor(new Color(0, 0, 100));
g.drawLine(n, h/2 + lmax, n, h/2 - hmax);
g.setColor(new Color(0, 0, 200));
g.drawLine(n, h/2 + (int)lave, n, h/2 - (int)have);
}
g.setColor(new Color(255, 0, 0));
g.drawLine(leftMarker/step, 0, leftMarker/step, h);
g.drawLine(rightMarker/step, 0, rightMarker/step, h);
}
}
public void setMarkers(int l, int r) {
leftMarker = l;
rightMarker = r;
repaint();
}
public void setLeftMarker(int l) {
leftMarker = l;
repaint();
}
public void setRightMarker(int r) {
rightMarker = r;
repaint();
}
public void clearData() {
samples = null;
repaint();
}
public void setData(int[] s) {
samples = s;
repaint();
}
}