Compare commits

...

20 Commits

Author SHA1 Message Date
90ca84cfbf Released 0.3.4 2020-01-27 01:18:29 +00:00
a18ca1ce21 Fix reload tree on split and cut operations 2020-01-26 17:41:16 +00:00
b92babb5cd Major improvements to tree list, and added time recording 2020-01-20 23:58:56 +00:00
e3231ec495 Cure tree flashing on playback 2020-01-20 16:00:29 +00:00
4896ee7a65 Released 0.3.3 2020-01-17 14:16:31 +00:00
ce3eb7165a Added book/chapter/phrase notes area tabs 2020-01-17 12:31:25 +00:00
4de00b8fac Add processed flag for batch processing of new recordings 2020-01-15 11:35:49 +00:00
ebe777bdc5 Released 0.3.2 2020-01-12 23:43:08 +00:00
3b5cacb8ad Added import / open manuscript file 2020-01-12 22:46:15 +00:00
db7d297dbc Improve controls spacing 2020-01-12 15:23:16 +00:00
2f9abf7629 Fix split location problem 2020-01-12 15:01:22 +00:00
b6063d2fed Fix tree redraw on split resize 2020-01-12 13:15:52 +00:00
e1f566f0c8 Released 0.3.1 2020-01-12 12:23:14 +00:00
9fa892a6fd Added missing IDs in xml files 2020-01-12 12:22:58 +00:00
1572e163ef Added notes panel 2020-01-12 10:38:16 +00:00
2791691057 Switch book data file to full XML 2020-01-11 20:45:07 +00:00
575537ae66 Fix normalize 2020-01-11 12:31:59 +00:00
fde4a597c7 Released 0.2.2 2020-01-10 20:23:43 +00:00
e69ae52f50 Updated example filters 2020-01-10 18:55:46 +00:00
8bdb06749f Improvements to LFO 2020-01-10 18:29:22 +00:00
29 changed files with 1388 additions and 232 deletions

View File

@@ -1,7 +1,4 @@
<effect name="Alien">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowpass" fc="10000" q="1" gain="-10" />
<lfo frequency="100" depth="0.5" />
<!--biquad type="lowpass" fc="10000" q="1" gain="-10" /-->
<lfo frequency="50" depth="1.0" waveform="triangle" mode="replace"/>
</effect>

View File

@@ -1,7 +1,4 @@
<effect name="Big Echo">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<delayline>
<delay samples="22000" gain="0.2" pan="-0.3">
<biquad type="highpass" fc="300" q="1" gain="0" />

View File

@@ -3,5 +3,5 @@
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowpass" fc="10000" q="1" gain="-10" />
<agc ceiling="0.666" limit="1" attack="0.1" decay="0.01" />
<agc ceiling="0.666" limit="1.5" attack="0.08" decay="0.01" />
</effect>

View File

@@ -0,0 +1,15 @@
<effect name="Cut Computer Hum (with AGC and Stereo)">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowpass" fc="10000" q="1" gain="-10" />
<delayline wetonly="false">
<delay samples="-100" gain="0.1" pan="-1.0">
<biquad type="highpass" fc="300" q="1" gain="0" />
</delay>
<delay samples="100" gain="0.1" pan="1.0">
<biquad type="highpass" fc="300" q="1" gain="0" />
</delay>
</delayline>
<agc ceiling="0.666" limit="1.5" attack="0.1" decay="0.01" />
</effect>

View File

@@ -1,7 +1,4 @@
<effect name="Ethereal Voice">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<amplifier gain="0.1" />
<delayline>
<delay samples="2000" gain="1.0" pan="-0.3">

View File

@@ -1,7 +1,4 @@
<effect name="Large Room (quiet)">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowpass" fc="10000" q="1" gain="-10" />
<delayline>
<delay samples="5500" gain="0.2" pan="-0.3">

View File

@@ -1,7 +1,4 @@
<effect name="Large Room (loud)">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowpass" fc="10000" q="1" gain="-10" />
<delayline>
<delay samples="5500" gain="0.2" pan="-0.3">

View File

@@ -0,0 +1,3 @@
<effect name="Pan Left">
<pan pan="-0.4" />
</effect>

View File

@@ -0,0 +1,3 @@
<effect name="Pan Right">
<pan pan="0.4" />
</effect>

View File

@@ -1,7 +1,4 @@
<effect name="Telephone">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowshelf" fc="400" q="10" gain="-20" />
<biquad type="highshelf" fc="8000" q="10" gain="-20" />
<delayline>

View File

@@ -1,14 +1,12 @@
<effect name="Radio">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<amplifier gain="0.1" />
<biquad type="peak" fc="1000" q="10" gain="45" />
<lfo frequency="5000" depth="0.3" />
<clipping clip="0.3" />
<amplifier gain="0.5" />
<biquad type="peak" fc="1000" q="10" gain="25" />
<lfo frequency="3000" depth="0.3" waveform="sine" mode="add" />
<clipping clip="0.9" />
<biquad type="highshelf" fc="8000" q="1" gain="-20" />
<delayline>
<delay samples="100" gain="0.7" />
<delay samples="200" gain="0.5" />
</delayline>
<amplifier gain="0.3" />
</effect>

View File

@@ -1,7 +1,4 @@
<effect name="Robotic">
<biquad type="notch" fc="28" q="20" gain="-50" />
<biquad type="notch" fc="91" q="20" gain="-50" />
<biquad type="notch" fc="120" q="20" gain="-50" />
<biquad type="lowshelf" fc="100" q="2" gain="-20" />
<delayline>
<delay samples="400" gain="1" pan="-0.3" />

View File

@@ -0,0 +1,10 @@
<effect name="Stereo Chorus">
<delayline>
<delay samples="2000" gain="0.1" pan="-0.3">
<biquad type="highpass" fc="300" q="1" gain="0" />
</delay>
<delay samples="4000" gain="0.1" pan="0.3">
<biquad type="highpass" fc="300" q="1" gain="0" />
</delay>
</delayline>
</effect>

View File

@@ -1 +1 @@
version=0.2.1
version=0.3.4

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,20 @@ import java.nio.file.*;
import javax.swing.tree.*;
import javax.sound.sampled.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
public class Book extends DefaultMutableTreeNode {
String name;
@@ -17,6 +31,9 @@ public class Book extends DefaultMutableTreeNode {
String genre;
String comment;
String ACX;
String manuscript;
String defaultEffect = "none";
int sampleRate;
int channels;
@@ -31,7 +48,64 @@ public class Book extends DefaultMutableTreeNode {
prefs = p;
name = bookname;
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name);
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
}
public Book(Element root) {
super(getTextNode(root, "title"));
name = getTextNode(root, "title");
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
}
public void loadBookXML(Element root, DefaultTreeModel model) {
name = getTextNode(root, "title");
author = getTextNode(root, "author");
genre = getTextNode(root, "genre");
comment = getTextNode(root, "comment");
ACX = getTextNode(root, "acx");
manuscript = getTextNode(root, "manuscript");
AudiobookRecorder.window.setBookNotes(getTextNode(root, "notes"));
Element settings = getNode(root, "settings");
Element audioSettings = getNode(settings, "audio");
Element effectSettings = getNode(settings, "effects");
sampleRate = Utils.s2i(getTextNode(audioSettings, "samplerate"));
channels = Utils.s2i(getTextNode(audioSettings, "channels"));
resolution = Utils.s2i(getTextNode(audioSettings, "resolution"));
defaultEffect = getTextNode(settings, "default");
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
Element chapters = getNode(root, "chapters");
NodeList chapterList = chapters.getElementsByTagName("chapter");
for (int i = 0; i < chapterList.getLength(); i++) {
Element chapterElement = (Element)chapterList.item(i);
Chapter newChapter = new Chapter(chapterElement, model);
model.insertNodeInto(newChapter, this, getChildCount());
}
}
public static Element getNode(Element r, String n) {
NodeList nl = r.getElementsByTagName(n);
if (nl == null) return null;
if (nl.getLength() == 0) return null;
return (Element)nl.item(0);
}
public static String getTextNode(Element r, String n) {
return getTextNode(r, n, "");
}
public static String getTextNode(Element r, String n, String d) {
Element node = getNode(r, n);
if (node == null) return d;
return node.getTextContent();
}
public void setAuthor(String a) { author = a; }
@@ -115,8 +189,12 @@ public class Book extends DefaultMutableTreeNode {
}
}
public File getBookPath() {
return new File(Options.get("path.storage"), name);
}
public void renameBook(String newName) {
File oldDir = new File(Options.get("path.storage"), name);
File oldDir = getBookPath();
File newDir = new File(Options.get("path.storage"), newName);
if (newDir.exists()) {
@@ -216,4 +294,108 @@ public class Book extends DefaultMutableTreeNode {
}
}
public Document buildDocument() throws ParserConfigurationException {
DocumentBuilderFactory dbFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.newDocument();
Element root = doc.createElement("book");
doc.appendChild(root);
root.appendChild(makeTextNode(doc, "title", name));
root.appendChild(makeTextNode(doc, "author", author));
root.appendChild(makeTextNode(doc, "comment", comment));
root.appendChild(makeTextNode(doc, "genre", genre));
root.appendChild(makeTextNode(doc, "acx", ACX));
root.appendChild(makeTextNode(doc, "manuscript", manuscript));
root.appendChild(makeTextNode(doc, "notes", AudiobookRecorder.window.getBookNotes()));
Element settingsNode = doc.createElement("settings");
root.appendChild(settingsNode);
Element audioSettingsNode = doc.createElement("audio");
settingsNode.appendChild(audioSettingsNode);
audioSettingsNode.appendChild(makeTextNode(doc, "channels", channels));
audioSettingsNode.appendChild(makeTextNode(doc, "resolution", resolution));
audioSettingsNode.appendChild(makeTextNode(doc, "samplerate", sampleRate));
Element effectsNode = doc.createElement("effects");
settingsNode.appendChild(effectsNode);
effectsNode.appendChild(makeTextNode(doc, "default", defaultEffect));
Element chaptersNode = doc.createElement("chapters");
root.appendChild(chaptersNode);
for (Enumeration o = children(); o.hasMoreElements();) {
Object ob = (Object)o.nextElement();
if (ob instanceof Chapter) {
Chapter c = (Chapter)ob;
chaptersNode.appendChild(c.getChapterXML(doc));
}
}
return doc;
}
public static Element makeTextNode(Document doc, String name, String text) {
Element node = doc.createElement(name);
Text tnode = doc.createTextNode(text == null ? "" : text);
node.appendChild(tnode);
return node;
}
public static Element makeTextNode(Document doc, String name, Integer text) {
Element node = doc.createElement(name);
Text tnode = doc.createTextNode(Integer.toString(text));
node.appendChild(tnode);
return node;
}
public static Element makeTextNode(Document doc, String name, Double text) {
Element node = doc.createElement(name);
Text tnode = doc.createTextNode(String.format("%.8f", text));
node.appendChild(tnode);
return node;
}
public static Element makeTextNode(Document doc, String name, Boolean text) {
Element node = doc.createElement(name);
Text tnode = doc.createTextNode(text ? "true" : "false");
node.appendChild(tnode);
return node;
}
public String getDefaultEffect() {
return defaultEffect;
}
public void setDefaultEffect(String eff) {
defaultEffect = eff;
}
public void setManuscript(File f) {
manuscript = f.getName();
File dst = new File(getBookPath(), manuscript);
try {
Files.copy(f.toPath(), dst.toPath());
} catch (Exception ex) {
ex.printStackTrace();
}
}
public File getManuscript() {
if (manuscript == null) return null;
if (manuscript.equals("")) return null;
File f = new File(getBookPath(), manuscript);
if (f.exists()) {
return f;
}
return null;
}
}

View File

@@ -9,6 +9,20 @@ import javax.swing.border.*;
import java.util.*;
import java.io.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
public class BookPanel extends JPanel {
String name;
String author;
@@ -25,6 +39,7 @@ public class BookPanel extends JPanel {
JPanel panel;
File root;
File configFile;
boolean highlight = false;
@@ -36,13 +51,78 @@ public class BookPanel extends JPanel {
try {
root = r;
Properties props = new Properties();
props.loadFromXML(new FileInputStream(new File(root, "audiobook.abk")));
loadBookData(props, null);
configFile = new File(root, "audiobook.abx");
if (configFile.exists()) {
loadXMLData(configFile);
} else {
configFile = new File(root, "audiobook.abk");
props.loadFromXML(new FileInputStream(configFile));
loadBookData(props, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public BookPanel(String n, String a, String g, String c, ImageIcon i) {
name = n;
author = a;
genre = g;
comment = c;
cover = i;
if (i != null) {
cover = i;
resizedCover = Utils.getScaledImage(cover.getImage(), 75, 75);
iconLabel = new JLabel(new ImageIcon(resizedCover));
} else {
cover = null;
resizedCover = null;
iconLabel = new JLabel("");
}
populate();
}
public void loadXMLData(File inputFile) {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(inputFile);
doc.getDocumentElement().normalize();
Element rootnode = doc.getDocumentElement();
name = Book.getTextNode(rootnode, "title");
author = Book.getTextNode(rootnode, "author");
genre = Book.getTextNode(rootnode, "genre");
comment = Book.getTextNode(rootnode, "comment");
File icon = new File(root, "coverart.png");
if (!icon.exists()) {
icon = new File(root, "coverart.jpg");
}
if (!icon.exists()) {
icon = new File(root, "coverart.gif");
}
if (icon.exists()) {
cover = new ImageIcon(icon.getAbsolutePath());
resizedCover = Utils.getScaledImage(cover.getImage(), 75, 75);
iconLabel = new JLabel(new ImageIcon(resizedCover));
} else {
cover = null;
resizedCover = null;
iconLabel = new JLabel("");
}
populate();
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void loadBookData(Properties props, ImageIcon i) {
try {
name = props.getProperty("book.name");
@@ -71,41 +151,44 @@ public class BookPanel extends JPanel {
resizedCover = Utils.getScaledImage(cover.getImage(), 75, 75);
iconLabel = new JLabel(new ImageIcon(resizedCover));
}
iconLabel.setSize(new Dimension(75, 75));
iconLabel.setPreferredSize(new Dimension(75, 75));
titleLabel = new JLabel(name);
authorLabel = new JLabel(author);
otherLabel = new JLabel(genre + " :: " + comment);
authorLabel.setForeground(new Color(0x80, 0x80, 0x80));
otherLabel.setForeground(new Color(0x80, 0x80, 0x80));
setLayout(new BorderLayout());
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
panel.add(titleLabel);
panel.add(authorLabel);
panel.add(otherLabel);
add(iconLabel, BorderLayout.WEST);
add(panel, BorderLayout.CENTER);
panel.setBackground(new Color(0x20, 0x20, 0x20));
panel.setOpaque(true);
setBackground(new Color(0x20, 0x20, 0x20));
setOpaque(true);
populate();
} catch (Exception e) {
}
}
void populate() {
iconLabel.setSize(new Dimension(75, 75));
iconLabel.setPreferredSize(new Dimension(75, 75));
titleLabel = new JLabel(name);
authorLabel = new JLabel(author);
otherLabel = new JLabel(genre + " :: " + comment);
authorLabel.setForeground(new Color(0x80, 0x80, 0x80));
otherLabel.setForeground(new Color(0x80, 0x80, 0x80));
setLayout(new BorderLayout());
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
panel.add(titleLabel);
panel.add(authorLabel);
panel.add(otherLabel);
add(iconLabel, BorderLayout.WEST);
add(panel, BorderLayout.CENTER);
panel.setBackground(new Color(0x20, 0x20, 0x20));
panel.setOpaque(true);
setBackground(new Color(0x20, 0x20, 0x20));
setOpaque(true);
}
public File getConfigFile() {
return new File(root, "audiobook.abk");
return configFile;
}
public void highlight() {

View File

@@ -0,0 +1,20 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.tree.DefaultMutableTreeNode;
public abstract class BookTreeNode extends DefaultMutableTreeNode {
public BookTreeNode(String t) {
super(t);
}
public BookTreeNode() {
super("");
}
public abstract void setNotes(String t);
public abstract String getNotes();
public abstract void onSelect();
}

View File

@@ -9,24 +9,32 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
JLabel ret = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
ret.setIconTextGap(0);
ret.setIconTextGap(5);
ret.setBorder(new EmptyBorder(0, 0, 0, 0));
if (value instanceof Sentence) {
Sentence s = (Sentence)value;
JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
GridBagConstraints ctx = new GridBagConstraints();
OverlayIcon icn = new OverlayIcon(Icons.sentence);
if (s.getOverrideText() != null) {
ret.setText(s.getOverrideText());
}
if (!s.isProcessed()) {
ret.setForeground(new Color(0x88, 0x88, 0x88));
}
if (s.getAttentionFlag()) {
ret.setForeground(new Color(0xFF, 0xFF, 0x00));
icn.add(Overlays.attention, OverlayIcon.TOP_LEFT);
}
if (s.isLocked()) {
ret.setForeground(new Color(0x00, 0x80, 0xFF));
ret.setForeground(new Color(0x30, 0xb0, 0xFF));
icn.add(Overlays.locked, OverlayIcon.BOTTOM_LEFT);
}
@@ -34,6 +42,10 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
icn.add(Overlays.important, OverlayIcon.TOP_RIGHT);
}
if (s.getEndOffset() == s.getSampleSize() - 1) {
icn.add(Overlays.important, OverlayIcon.TOP_RIGHT);
}
if (s.getEffectChain() != null) {
if (!s.getEffectChain().equals("none")) {
icn.add(Overlays.filter, OverlayIcon.BOTTOM_RIGHT);
@@ -55,17 +67,72 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
}
if (gaptype.equals("sentence")) {
ret.setBorder(new EmptyBorder(0, 0, 0, 0));
p.setBorder(new EmptyBorder(0, 0, 0, 0));
} else if (gaptype.equals("continuation")) {
ret.setBorder(new EmptyBorder(0, 0, 0, 0));
p.setBorder(new EmptyBorder(0, 0, 0, 0));
} else if (gaptype.equals("paragraph")) {
ret.setBorder(new EmptyBorder(0, 0, 7, 0));
p.setBorder(new EmptyBorder(0, 0, 7, 0));
} else if (gaptype.equals("section")) {
ret.setBorder(new EmptyBorder(0, 0, 15, 0));
p.setBorder(new EmptyBorder(0, 0, 15, 0));
}
JLabel time = new JLabel(Utils.secToTime(s.getLength(), "mm:ss.SSS") + " ");
ctx.gridx = 0;
ctx.gridy = 0;
ctx.fill = GridBagConstraints.HORIZONTAL;
ctx.anchor = GridBagConstraints.LINE_START;
String effectChain = s.getEffectChain();
if ((effectChain == null) || (effectChain.equals("none"))) {
ctx.weightx = 1.0d;
ctx.gridwidth = 2;
p.add(ret, ctx);
} else {
ctx.weightx = 0.1d;
ctx.gridwidth = 1;
p.add(ret, ctx);
Effect e = AudiobookRecorder.window.effects.get(effectChain);
JLabel eff = new JLabel(e.toString() + " ");
ctx.weightx = 0.0d;
ctx.gridwidth = 1;
ctx.gridx = 1;
p.add(eff);
}
ctx.weightx = 0.0d;
ctx.gridx = 2;
ctx.anchor = GridBagConstraints.LINE_END;
p.add(time, ctx);
p.setOpaque(false);
return p;
} else if (value instanceof Chapter) {
Chapter c = (Chapter)value;
ret.setIcon(Icons.chapter);
JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
GridBagConstraints ctx = new GridBagConstraints();
JLabel time = new JLabel(Utils.secToTime(c.getLength(), "mm:ss") + " ");
ctx.gridx = 0;
ctx.gridy = 0;
ctx.fill = GridBagConstraints.HORIZONTAL;
ctx.anchor = GridBagConstraints.LINE_START;
ctx.weightx = 1.0d;
p.add(ret, ctx);
ctx.weightx = 0.0d;
ctx.gridx = 1;
ctx.anchor = GridBagConstraints.LINE_END;
p.add(time, ctx);
p.setOpaque(false);
return p;
} else if (value instanceof Book) {
ret.setIcon(((Book)value).getIcon());
}

View File

@@ -12,8 +12,21 @@ import it.sauronsoftware.jave.*;
import com.mpatric.mp3agic.*;
import javax.sound.sampled.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
public class Chapter extends DefaultMutableTreeNode {
public class Chapter extends BookTreeNode {
String name;
String id;
@@ -21,6 +34,8 @@ public class Chapter extends DefaultMutableTreeNode {
int preGap;
int postGap;
String notes;
public Chapter(String i, String chaptername) {
super(chaptername);
@@ -28,7 +43,25 @@ public class Chapter extends DefaultMutableTreeNode {
name = chaptername;
preGap = Options.getInteger("catenation.pre-chapter");
postGap = Options.getInteger("catenation.post-chapter");
}
public Chapter(Element root, DefaultTreeModel model) {
name = Book.getTextNode(root, "name");
id = root.getAttribute("id");
preGap = Utils.s2i(Book.getTextNode(root, "pre-gap"));
postGap = Utils.s2i(Book.getTextNode(root, "post-gap"));
notes = Book.getTextNode(root, "notes");
Element sentencesNode = Book.getNode(root, "sentences");
NodeList sentences = sentencesNode.getElementsByTagName("sentence");
for (int i = 0; i < sentences.getLength(); i++) {
Element sentenceElement = (Element)sentences.item(i);
Sentence newSentence = new Sentence(sentenceElement);
model.insertNodeInto(newSentence, this, getChildCount());
}
}
public String getId() {
@@ -249,4 +282,50 @@ public class Chapter extends DefaultMutableTreeNode {
}
}
public Element getChapterXML(Document doc) {
Element chapterNode = doc.createElement("chapter");
chapterNode.setAttribute("id", id);
chapterNode.appendChild(Book.makeTextNode(doc, "name", name));
chapterNode.appendChild(Book.makeTextNode(doc, "pre-gap", preGap));
chapterNode.appendChild(Book.makeTextNode(doc, "post-gap", postGap));
chapterNode.appendChild(Book.makeTextNode(doc, "notes", notes));
Element sentencesNode = doc.createElement("sentences");
chapterNode.appendChild(sentencesNode);
for (Enumeration o = children(); o.hasMoreElements();) {
Object ob = (Object)o.nextElement();
if (ob instanceof Sentence) {
Sentence s = (Sentence)ob;
sentencesNode.appendChild(s.getSentenceXML(doc));
}
}
return chapterNode;
}
public String getNotes() {
return notes;
}
public void setNotes(String t) {
notes = t;
}
public void onSelect() {
AudiobookRecorder.window.setChapterNotes(notes);
}
public double getLength() {
double len = 0;
for (Enumeration o = children(); o.hasMoreElements();) {
Object ob = (Object)o.nextElement();
if (ob instanceof Sentence) {
Sentence s = (Sentence)ob;
len += s.getLength();
}
}
return len;
}
}

View File

@@ -38,4 +38,5 @@ public class Icons {
static public final ImageIcon docut = new ImageIcon(Icons.class.getResource("icons/do-cut.png"));
static public final ImageIcon playto = new ImageIcon(Icons.class.getResource("icons/play-to.png"));
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"));
}

View File

@@ -9,37 +9,73 @@ public class LFO implements Effect {
double depth;
double sampleRate;
double sampleStep;
int waveform;
double duty;
int mode;
public static final int SINE = 0;
public static final int COSINE = 1;
public static final int SQUARE = 2;
public static final int TRIANGLE = 3;
public static final int SAWTOOTH = 4;
public static final int ADD = 0;
public static final int REPLACE = 1;
public LFO(double f, double d) {
frequency = f;
depth = d;
phase = 0;
this(f, d, 0, SINE, Math.PI, REPLACE);
}
public LFO(double f, double d, double p) {
this(f, d, p, SINE, Math.PI, REPLACE);
}
public LFO(double f, double d, double p, int w) {
this(f, d, p, w, Math.PI, REPLACE);
}
public LFO(double f, double d, double p, int w, double dty) {
this(f, d, p, w, dty, REPLACE);
}
public LFO(double f, double d, double p, int w, double dty, int m) {
frequency = f;
depth = d;
phase = p;
waveform = w;
duty = dty;
mode = m;
}
public void process(double[][] samples) {
for (double[] sample : samples) {
double v = Math.sin(phase);
double v = 0;
switch (waveform) {
case SINE: v = Math.sin(phase); break;
case COSINE: v = Math.cos(phase); break;
case SQUARE: v = (phase > duty) ? 1d : 0d; break;
case TRIANGLE: v = (phase < Math.PI) ? (phase / Math.PI) : (1d - ((phase - Math.PI) / Math.PI)); break;
case SAWTOOTH: v = (phase / (Math.PI*2d)); break;
}
phase += sampleStep;
if (phase > (Math.PI * 2d)) {
phase -= (Math.PI * 2d);
}
// // Make it between 0 and 1.
// v = 1d + v;
// v /= 2d;
// Multiply it by the gain factor
v *= depth;
// Apply it to the sample
sample[Sentence.LEFT] += (sample[Sentence.LEFT] * v);
sample[Sentence.RIGHT] += (sample[Sentence.RIGHT] * v);
switch (mode) {
case REPLACE:
sample[Sentence.LEFT] = (sample[Sentence.LEFT] * v);
sample[Sentence.RIGHT] = (sample[Sentence.RIGHT] * v);
break;
case ADD:
sample[Sentence.LEFT] += (sample[Sentence.LEFT] * v);
sample[Sentence.RIGHT] += (sample[Sentence.RIGHT] * v);
break;
}
}
}

View File

@@ -17,6 +17,7 @@ public class MainToolBar extends JToolBar {
JButtonSpacePlay playtoSentence;
JButtonSpacePlay stopPlaying;
JButtonSpacePlay eq;
JButtonSpacePlay openManuscript;
JToggleButtonSpacePlay mic;
JComboBox<String> playbackSpeed;
@@ -171,6 +172,15 @@ public class MainToolBar extends JToolBar {
playbackSpeed.setSelectedIndex(1);
add(playbackSpeed);
addSeparator();
openManuscript = new JButtonSpacePlay(Icons.manuscript, "Open Manuscript", new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.openManuscript();
}
});
add(openManuscript);
setFloatable(false);
}

View File

@@ -86,10 +86,16 @@ public class OpenBookPanel extends JPanel {
for (File b : dir.listFiles()) {
if (b == null) continue;
if (!b.isDirectory()) continue;
File xml = new File(b, "audiobook.abk");
File xml = new File(b, "audiobook.abx");
if (xml.exists()) {
BookPanel book = new BookPanel(b);
model.addBook(book);
} else {
xml = new File(b, "audiobook.abk");
if (xml.exists()) {
BookPanel book = new BookPanel(b);
model.addBook(book);
}
}
}
}

View File

@@ -34,6 +34,7 @@ public class Options extends JDialog {
JSpinner shortSentenceGap;
JSpinner postParagraphGap;
JSpinner postSectionGap;
JSpinner maxGainVariance;
JTextField ffmpegLocation;
JComboBox<KVPair> bitRate;
JComboBox<KVPair> channels;
@@ -301,6 +302,7 @@ public class Options extends JDialog {
trimMethod = addDropdown(optionsPanel, "Auto-trim method:", getTrimMethods(), get("audio.recording.trim"));
fftThreshold = addSpinner(optionsPanel, "FFT threshold:", 0, 100, 1, getInteger("audio.recording.trim.fft"), "");
fftBlockSize = addDropdown(optionsPanel, "FFT Block size:", getFFTBlockSizes(), get("audio.recording.trim.blocksize"));
maxGainVariance = addSpinner(optionsPanel, "Maximum gain variance:", 0, 100, 1, getInteger("audio.recording.variance"), "");
addSeparator(optionsPanel);
@@ -582,6 +584,7 @@ public class Options extends JDialog {
defaultPrefs.put("catenation.post-section", "3000");
defaultPrefs.put("audio.recording.trim.fft", "10");
defaultPrefs.put("audio.recording.variance", "10");
defaultPrefs.put("path.storage", (new File(System.getProperty("user.home"), "Recordings")).toString());
defaultPrefs.put("path.archive", (new File(new File(System.getProperty("user.home"), "Recordings"),"archive")).toString());
@@ -718,6 +721,7 @@ public class Options extends JDialog {
set("editor.external", externalEditor.getText());
set("cache.size", cacheSize.getValue());
set("audio.recording.trim.fft", fftThreshold.getValue());
set("audio.recording.variance", maxGainVariance.getValue());
set("audio.recording.trim.blocksize", ((KVPair)fftBlockSize.getSelectedItem()).key);
set("audio.playback.blocksize", ((KVPair)playbackBlockSize.getSelectedItem()).key);

View File

@@ -29,11 +29,23 @@ import org.json.*;
import java.util.Timer;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public class Sentence extends BookTreeNode implements Cacheable {
String text;
String id;
String notes;
int postGap;
int startOffset = 0;
int endOffset = 0;
@@ -49,6 +61,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
boolean inSample;
boolean attention = false;
boolean processed = false;
String effectChain = null;
@@ -65,7 +78,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
TargetDataLine line;
AudioInputStream inputStream;
AudioFormat storedFormat = null;
double storedLength = -1d;
double runtime = -1d;
double[][] audioData = null;
@@ -75,20 +89,26 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
boolean effectEthereal = false;
public void setSampleSize(int s) {
sampleSize = s;
}
static class RecordingThread implements Runnable {
boolean running = false;
boolean recording = false;
Sentence sent = null;
File tempFile;
File wavFile;
AudioFormat format;
public RecordingThread(File tf, File wf, AudioFormat af) {
public RecordingThread(File tf, File wf, AudioFormat af, Sentence s) {
tempFile = tf;
wavFile = wf;
format = af;
sent = s;
}
public void run() {
@@ -110,6 +130,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
fos.write(buf, 0, nr);
fos.close();
sent.setSampleSize(len / format.getFrameSize());
FileInputStream fis = new FileInputStream(tempFile);
AudioInputStream ais = new AudioInputStream(fis, format, len / format.getFrameSize());
fos = new FileOutputStream(wavFile);
@@ -155,6 +177,34 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
postGap = Options.getInteger("catenation.post-sentence");
}
public Sentence(Element root) {
super("");
id = root.getAttribute("id");
text = Book.getTextNode(root, "text");
notes = Book.getTextNode(root, "notes");
setUserObject(text);
setPostGap(Utils.s2i(Book.getTextNode(root, "post-gap")));
setStartOffset(Utils.s2i(Book.getTextNode(root, "start-offset")));
setEndOffset(Utils.s2i(Book.getTextNode(root, "end-offset")));
crossStartOffset = Utils.s2i(Book.getTextNode(root, "cross-start-offset", "-1"));
crossEndOffset = Utils.s2i(Book.getTextNode(root, "end-offset", "-1"));
setLocked(Utils.s2b(Book.getTextNode(root, "locked")));
setAttentionFlag(Utils.s2b(Book.getTextNode(root, "attention")));
setGain(Utils.s2d(Book.getTextNode(root, "gain")));
setEffectChain(Book.getTextNode(root, "effect"));
setPostGapType(Book.getTextNode(root, "gaptype"));
sampleSize = Utils.s2i(Book.getTextNode(root, "samples"));
processed = Utils.s2b(Book.getTextNode(root, "processed"));
runtime = Utils.s2d(Book.getTextNode(root, "time", "-1.000"));
if ((crossStartOffset == -1) || (crossEndOffset == -1)) {
System.err.println("Updating " + id);
updateCrossings(true);
}
if (runtime <= 0.01d) getLength();
}
public boolean startRecording() {
if (AudiobookRecorder.window.microphone == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Microphone not started. Start the microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
@@ -163,7 +213,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
CacheManager.removeFromCache(this);
recordingThread = new RecordingThread(getTempFile(), getFile(), Options.getAudioFormat());
recordingThread = new RecordingThread(getTempFile(), getFile(), Options.getAudioFormat(), this);
Thread rc = new Thread(recordingThread);
rc.setDaemon(true);
@@ -203,6 +253,12 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
autoTrimSamplePeak(useRaw);
} else if (tm.equals("fft")) {
autoTrimSampleFFT(useRaw);
} else {
startOffset = 0;
crossStartOffset = 0;
endOffset = sampleSize - 1;
crossEndOffset = sampleSize - 1;
processed = false;
}
}
@@ -304,7 +360,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
updateCrossings(useRaw);
intens = null;
samples = null;
processed = true;
reloadTree();
}
public void autoTrimSamplePeak() {
@@ -360,6 +417,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
if (startOffset < 0) startOffset = 0;
if (endOffset >= samples.length) endOffset = samples.length-1;
updateCrossings(useRaw);
processed = true;
reloadTree();
}
public String getId() {
@@ -369,6 +428,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void setText(String t) {
overrideText = null;
text = t;
reloadTree();
}
public String getText() {
@@ -401,11 +461,14 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public String toString() {
return text;
/*
if (effectChain == null) return text;
if (effectChain.equals("none")) return text;
Effect e = AudiobookRecorder.window.effects.get(effectChain);
if (e == null) return text;
return text + " (" + e.toString() + ")";
*/
}
public boolean isRecording() {
@@ -416,6 +479,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
if (o instanceof String) {
String so = (String)o;
text = so;
reloadTree();
}
}
@@ -449,6 +513,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void updateCrossings(boolean useRaw) {
updateStartCrossing(useRaw);
updateEndCrossing(useRaw);
runtime = -1d;
getLength();
}
public void updateStartCrossing() {
@@ -475,6 +541,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
if (startOffset != o) {
startOffset = o;
crossStartOffset = -1;
reloadTree();
}
}
@@ -490,9 +557,18 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
if (endOffset != o) {
endOffset = o;
crossEndOffset = -1;
reloadTree();
}
}
public void setStartCrossing(int o) {
crossStartOffset = o;
}
public void setEndCrossing(int o) {
crossEndOffset = o;
}
public int getSampleSize() {
if (sampleSize == -1) {
loadFile();
@@ -518,7 +594,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void doRecognition(StreamSpeechRecognizer recognizer) {
try {
setText("[recognising...]");
AudiobookRecorder.window.bookTreeModel.reload(this);
reloadTree();
byte[] inData = getPCMData();
@@ -533,7 +609,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
recognizer.stopRecognition();
setText(res);
AudiobookRecorder.window.bookTreeModel.reload(this);
reloadTree();
} catch (Exception e) {
e.printStackTrace();
}
@@ -569,7 +645,9 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public void setLocked(boolean l) {
if (locked == l) return;
locked = l;
reloadTree();
}
public boolean isLocked() {
@@ -588,7 +666,6 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
audioData = null;
processedAudio = null;
storedFormat = null;
storedLength = -1;
}
public boolean lockedInCache() {
@@ -644,13 +721,16 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
/* Get the length of the sample in seconds */
public double getLength() {
if (storedLength > -1d) return storedLength;
if (runtime > 0.01d) return runtime;
File f = getFile();
if (!f.exists()) { // Not recorded yet!
return 0;
}
AudioFormat format = getAudioFormat();
float sampleFrequency = format.getFrameRate();
int length = crossEndOffset - crossStartOffset;
double time = (double)length / (double)sampleFrequency;
storedLength = time;
return time;
runtime = (double)length / (double)sampleFrequency;
return runtime;
}
public Sentence cloneSentence() throws IOException {
@@ -661,17 +741,21 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
sentence.setStartOffset(getStartOffset());
sentence.setEndOffset(getEndOffset());
sentence.setStartCrossing(getStartCrossing());
sentence.setEndCrossing(getEndCrossing());
File from = getFile();
File to = sentence.getFile();
Files.copy(from.toPath(), to.toPath());
sentence.updateCrossings();
// sentence.updateCrossings();
return sentence;
}
public void setAttentionFlag(boolean f) {
if (attention == f) return;
attention = f;
reloadTree();
}
public boolean getAttentionFlag() {
@@ -679,17 +763,21 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public double getPeakValue() {
return getPeakValue(false);
return getPeakValue(false, true);
}
public double getPeakValue(boolean useRaw) {
return getPeakValue(useRaw, true);
}
public double getPeakValue(boolean useRaw, boolean applyGain) {
double oldGain = gain;
gain = 1.0d;
double[][] samples = null;
if (useRaw) {
samples = getRawAudioData();
} else {
samples = getProcessedAudioData();
samples = getProcessedAudioData(true, applyGain);
}
gain = oldGain;
if (samples == null) {
@@ -729,12 +817,24 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
return gain;
}
public void normalize() {
if (locked) return;
double max = getPeakValue();
public double normalize(double low, double high) {
if (locked) return gain;
double max = getPeakValue(true, false);
double d = 0.708 / max;
if (d > 1d) d = 1d;
if (d < low) d = low;
if (d > high) d = high;
setGain(d);
return d;
}
public double normalize() {
if (locked) return gain;
double max = getPeakValue(true, false);
double d = 0.708 / max;
if (d > 1d) d = 1d;
setGain(d);
return d;
}
class ExternalEditor implements Runnable {
@@ -1155,10 +1255,14 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
synchronized public double[][] getProcessedAudioData() {
return getProcessedAudioData(true);
return getProcessedAudioData(true, true);
}
synchronized public double[][] getProcessedAudioData(boolean effectsEnabled) {
return getProcessedAudioData(effectsEnabled, true);
}
synchronized public double[][] getProcessedAudioData(boolean effectsEnabled, boolean applyGain) {
loadFile();
if (processedAudio != null) {
return processedAudio;
@@ -1194,14 +1298,14 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
}
// Add final master gain stage
for (int i = 0; i < processedAudio.length; i++) {
processedAudio[i][LEFT] *= gain;
processedAudio[i][RIGHT] *= gain;
if (applyGain) {
// Add final master gain stage
for (int i = 0; i < processedAudio.length; i++) {
processedAudio[i][LEFT] *= gain;
processedAudio[i][RIGHT] *= gain;
}
}
return processedAudio;
}
@@ -1259,10 +1363,14 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public void setEffectChain(String key) {
if ((effectChain != null) && (effectChain.equals(key))) {
return;
}
if ((effectChain != null) && (!effectChain.equals(key))) {
CacheManager.removeFromCache(this);
}
effectChain = key;
reloadTree();
}
public String getEffectChain() {
@@ -1289,6 +1397,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
}
postGapType = t;
reloadTree();
}
public void resetPostGap() {
@@ -1325,6 +1434,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
out.put("gain", String.format("%.8f", getGain()));
out.put("effect", getEffectChain());
out.put("gaptype", getPostGapType());
// out.put("samples", Integer.toString(getSampleSize()));
return out;
}
@@ -1341,4 +1451,53 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
}
}
public Element getSentenceXML(Document doc) {
Element sentenceNode = doc.createElement("sentence");
sentenceNode.setAttribute("id", getId());
sentenceNode.appendChild(Book.makeTextNode(doc, "text", getText()));
sentenceNode.appendChild(Book.makeTextNode(doc, "post-gap", getPostGap()));
sentenceNode.appendChild(Book.makeTextNode(doc, "start-offset", getStartOffset()));
sentenceNode.appendChild(Book.makeTextNode(doc, "end-offset", getEndOffset()));
sentenceNode.appendChild(Book.makeTextNode(doc, "cross-start-offset", crossStartOffset));
sentenceNode.appendChild(Book.makeTextNode(doc, "cross-end-offset", crossEndOffset));
sentenceNode.appendChild(Book.makeTextNode(doc, "locked", isLocked()));
sentenceNode.appendChild(Book.makeTextNode(doc, "attention", getAttentionFlag()));
sentenceNode.appendChild(Book.makeTextNode(doc, "gain", getGain()));
sentenceNode.appendChild(Book.makeTextNode(doc, "effect", getEffectChain()));
sentenceNode.appendChild(Book.makeTextNode(doc, "gaptype", getPostGapType()));
sentenceNode.appendChild(Book.makeTextNode(doc, "samples", getSampleSize()));
sentenceNode.appendChild(Book.makeTextNode(doc, "processed", isProcessed()));
sentenceNode.appendChild(Book.makeTextNode(doc, "notes", getNotes()));
sentenceNode.appendChild(Book.makeTextNode(doc, "time", getLength()));
return sentenceNode;
}
public boolean isProcessed() {
return processed;
}
public void setProcessed(boolean p) {
processed = p;
reloadTree();
}
public void setNotes(String n) {
notes = n;
}
public String getNotes() {
return notes;
}
public void onSelect() {
AudiobookRecorder.window.setSentenceNotes(notes);
}
void reloadTree() {
if (id.equals("room-noise")) return;
if (getParent() == null) return;
AudiobookRecorder.window.bookTreeModel.reload(this);
}
}

View File

@@ -10,6 +10,8 @@ import java.util.*;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
public class Utils {
public static Image getScaledImage(Image srcImg, int w, int h){
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
@@ -76,4 +78,12 @@ public class Utils {
Runtime.getRuntime().freeMemory()
));
}
public static String secToTime(double sec, String fmt) {
Date d = new Date((long)(sec * 1000d));
SimpleDateFormat df = new SimpleDateFormat(fmt);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
String time = df.format(d);
return time;
}
}

View File

@@ -97,6 +97,7 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
double lmax = 0;
for (int o = 0; o < step; o++) {
if (offset + (n * step) + o >= samples.length) break;
double sample = (samples[offset + (n * step) + o][Sentence.LEFT] + samples[offset + (n * step) + o][Sentence.RIGHT]) / 2d;
if (sample >= 0) {
have += sample;