Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1005619211 | |||
| af31ba91a4 | |||
| 0e9ae7cc91 | |||
| 702160c1df | |||
| b57c1b5753 | |||
| e22a1ce84a | |||
| 83c728e7c8 | |||
| ed75718d34 | |||
| 75873b9e8c | |||
| 1416f6d632 | |||
| 8a7e070f46 | |||
| 1c953dbf1f | |||
| da3b88e083 | |||
| 13d8dc4612 | |||
| 785a4f1b7b | |||
| 43b0ccd96d | |||
| f6af5ce2e7 | |||
| 577ff9c1eb | |||
| f0ae69d610 | |||
| 7e6a08d64b | |||
| 5f34c57b23 | |||
| 356d95191b | |||
| 903f8eaa91 | |||
| 1b7dd7dfb1 | |||
| ca0a15ba09 | |||
| c52422247c | |||
| c9b65cb315 | |||
| fc3365af62 | |||
| 7c7cd58963 | |||
| aea5a58691 | |||
| fab7f1a91c | |||
| 9b23eb56ce | |||
| 450e80ad21 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ AudiobookRecorder.jar
|
|||||||
AudiobookRecorder-linux
|
AudiobookRecorder-linux
|
||||||
AudiobookRecorder-osx.dmg
|
AudiobookRecorder-osx.dmg
|
||||||
AudiobookRecorder-win.exe
|
AudiobookRecorder-win.exe
|
||||||
|
.*.swp
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ A system for easing the task of recording and editing audiobooks.
|
|||||||
* Chapter management
|
* Chapter management
|
||||||
* Audio effect chains (biquad, delay line, etc)
|
* Audio effect chains (biquad, delay line, etc)
|
||||||
|
|
||||||
|
# [Manual and Tutorial](https://majenkoprojects.github.io/AudiobookRecorder)
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
133
iircoeff.c
133
iircoeff.c
@@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2002-2006 Felipe Rivera <liebremx at users.sourceforge.net>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Coefficient stuff
|
|
||||||
*
|
|
||||||
* $Id: iir_cfs.c,v 1.2 2006/01/15 00:17:46 liebremx Exp $
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
static const double band_f031[] =
|
|
||||||
{ 20,25,31.5,40,50,63,80,100,125,160,200,250,315,400,500,630,800,
|
|
||||||
1000,1250,1600,2000,2500,3150,4000,5000,6300,8000,10000,12500,16000,20000
|
|
||||||
};
|
|
||||||
|
|
||||||
#define GAIN_F0 1.0
|
|
||||||
#define GAIN_F1 GAIN_F0 / M_SQRT2
|
|
||||||
|
|
||||||
#define SAMPLING_FREQ 44100.0
|
|
||||||
#define TETA(f) (2*M_PI*(double)f/sample_frequency)
|
|
||||||
#define TWOPOWER(value) (value * value)
|
|
||||||
|
|
||||||
#define BETA2(tf0, tf) \
|
|
||||||
(TWOPOWER(GAIN_F1)*TWOPOWER(cos(tf0)) \
|
|
||||||
- 2.0 * TWOPOWER(GAIN_F1) * cos(tf) * cos(tf0) \
|
|
||||||
+ TWOPOWER(GAIN_F1) \
|
|
||||||
- TWOPOWER(GAIN_F0) * TWOPOWER(sin(tf)))
|
|
||||||
#define BETA1(tf0, tf) \
|
|
||||||
(2.0 * TWOPOWER(GAIN_F1) * TWOPOWER(cos(tf)) \
|
|
||||||
+ TWOPOWER(GAIN_F1) * TWOPOWER(cos(tf0)) \
|
|
||||||
- 2.0 * TWOPOWER(GAIN_F1) * cos(tf) * cos(tf0) \
|
|
||||||
- TWOPOWER(GAIN_F1) + TWOPOWER(GAIN_F0) * TWOPOWER(sin(tf)))
|
|
||||||
#define BETA0(tf0, tf) \
|
|
||||||
(0.25 * TWOPOWER(GAIN_F1) * TWOPOWER(cos(tf0)) \
|
|
||||||
- 0.5 * TWOPOWER(GAIN_F1) * cos(tf) * cos(tf0) \
|
|
||||||
+ 0.25 * TWOPOWER(GAIN_F1) \
|
|
||||||
- 0.25 * TWOPOWER(GAIN_F0) * TWOPOWER(sin(tf)))
|
|
||||||
|
|
||||||
#define GAMMA(beta, tf0) ((0.5 + beta) * cos(tf0))
|
|
||||||
#define ALPHA(beta) ((0.5 - beta)/2.0)
|
|
||||||
|
|
||||||
/*************
|
|
||||||
* Functions *
|
|
||||||
*************/
|
|
||||||
|
|
||||||
/* Get the band_f031 at both sides of F0. These will be cut at -3dB */
|
|
||||||
static void find_f1_and_f2(double f0, double octave_percent, double *f1, double *f2)
|
|
||||||
{
|
|
||||||
double octave_factor = pow(2.0, octave_percent/2.0);
|
|
||||||
*f1 = f0/octave_factor;
|
|
||||||
*f2 = f0*octave_factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find the quadratic root
|
|
||||||
* Always return the smallest root */
|
|
||||||
static int find_root(double a, double b, double c, double *x0) {
|
|
||||||
double k = c-((b*b)/(4.*a));
|
|
||||||
double h = -(b/(2.*a));
|
|
||||||
double x1 = 0.;
|
|
||||||
if (-(k/a) < 0.)
|
|
||||||
return -1;
|
|
||||||
*x0 = h - sqrt(-(k/a));
|
|
||||||
x1 = h + sqrt(-(k/a));
|
|
||||||
if (x1 < *x0)
|
|
||||||
*x0 = x1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void calc_coeffs(double sample_frequency)
|
|
||||||
{
|
|
||||||
int i, n;
|
|
||||||
double f1, f2;
|
|
||||||
double x0;
|
|
||||||
|
|
||||||
printf(" public final static IIRCoefficients iir_cf31_%d[] = {\n", (int)sample_frequency);
|
|
||||||
for (i = 0; i < 31; i++) {
|
|
||||||
|
|
||||||
/* Find -3dB frequencies for the center freq */
|
|
||||||
find_f1_and_f2(band_f031[i], 1.0/3.0, &f1, &f2);
|
|
||||||
/* Find Beta */
|
|
||||||
if ( find_root(
|
|
||||||
BETA2(TETA(band_f031[i]), TETA(f1)),
|
|
||||||
BETA1(TETA(band_f031[i]), TETA(f1)),
|
|
||||||
BETA0(TETA(band_f031[i]), TETA(f1)),
|
|
||||||
&x0) == 0)
|
|
||||||
{
|
|
||||||
/* Got a solution, now calculate the rest of the factors */
|
|
||||||
/* Take the smallest root always (find_root returns the smallest one)
|
|
||||||
*
|
|
||||||
* NOTE: The IIR equation is
|
|
||||||
* y[n] = 2 * (alpha*(x[n]-x[n-2]) + gamma*y[n-1] - beta*y[n-2])
|
|
||||||
* Now the 2 factor has been distributed in the coefficients
|
|
||||||
*/
|
|
||||||
/* Now store the coefficients */
|
|
||||||
printf(" /* %.1f Hz */\n", band_f031[i]);
|
|
||||||
printf(" new IIRCoefficients(%.10e, %010e, %.10e),\n",
|
|
||||||
(double)(2.0 * x0),
|
|
||||||
(double)(2.0 * ALPHA(x0)),
|
|
||||||
(double)(2.0 * GAMMA(x0, TETA(band_f031[i])))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
printf(" **** Where are the roots?\n");
|
|
||||||
}
|
|
||||||
}// for i
|
|
||||||
printf(" };\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
if (argc != 2) {
|
|
||||||
printf("Usage: iircoeff <sample frequency>\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
double f = strtod(argv[1], NULL);
|
|
||||||
calc_coeffs(f);
|
|
||||||
}
|
|
||||||
76
iircoeff.pl
76
iircoeff.pl
@@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use Math::Trig;
|
|
||||||
|
|
||||||
my $fs = 48000;
|
|
||||||
my $q = 1.414;
|
|
||||||
|
|
||||||
printCo(20, $fs, $q);
|
|
||||||
printCo(25, $fs, $q);
|
|
||||||
printCo(31.5, $fs, $q);
|
|
||||||
printCo(40, $fs, $q);
|
|
||||||
printCo(50, $fs, $q);
|
|
||||||
printCo(63, $fs, $q);
|
|
||||||
printCo(80, $fs, $q);
|
|
||||||
printCo(100, $fs, $q);
|
|
||||||
printCo(125, $fs, $q);
|
|
||||||
printCo(160, $fs, $q);
|
|
||||||
printCo(200, $fs, $q);
|
|
||||||
printCo(250, $fs, $q);
|
|
||||||
printCo(315, $fs, $q);
|
|
||||||
printCo(400, $fs, $q);
|
|
||||||
printCo(500, $fs, $q);
|
|
||||||
printCo(630, $fs, $q);
|
|
||||||
printCo(800, $fs, $q);
|
|
||||||
printCo(1000, $fs, $q);
|
|
||||||
printCo(1250, $fs, $q);
|
|
||||||
printCo(1600, $fs, $q);
|
|
||||||
printCo(2000, $fs, $q);
|
|
||||||
printCo(2500, $fs, $q);
|
|
||||||
printCo(3150, $fs, $q);
|
|
||||||
printCo(4000, $fs, $q);
|
|
||||||
printCo(5000, $fs, $q);
|
|
||||||
printCo(6300, $fs, $q);
|
|
||||||
printCo(8000, $fs, $q);
|
|
||||||
printCo(10000, $fs, $q);
|
|
||||||
printCo(12500, $fs, $q);
|
|
||||||
printCo(16000, $fs, $q);
|
|
||||||
printCo(20000, $fs, $q);
|
|
||||||
|
|
||||||
sub printCo($$$$) {
|
|
||||||
my $f0 = shift;
|
|
||||||
my $fs = shift;
|
|
||||||
my $q = shift;
|
|
||||||
|
|
||||||
|
|
||||||
@coeff = coefficient($f0, $fs, $q);
|
|
||||||
print "/* $f0 Hz */\n";
|
|
||||||
printf("new IIRCoefficients(%.10e, %.10e, %.10e),\n" , $coeff[1] * 2, $coeff[0] * 2, $coeff[2] * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub coefficient($$$$) {
|
|
||||||
my $f0 = shift;
|
|
||||||
my $fs = shift;
|
|
||||||
my $q = shift;
|
|
||||||
|
|
||||||
my $q2 = $q * $q;
|
|
||||||
|
|
||||||
my $f1 = $f0 * (sqrt(1 + (1 / (4 * $q2))) - (1 / (2 * $q)));
|
|
||||||
my $f2 = $f0 * (sqrt(1 + (1 / (4 * $q2))) + (1 / (2 * $q)));
|
|
||||||
|
|
||||||
my $pi = 3.141592653;
|
|
||||||
|
|
||||||
my $theta0 = 2 * $pi * ($f0 / $fs);
|
|
||||||
|
|
||||||
my $thetaOverTwoQ = $theta0 / (2 * $q);
|
|
||||||
|
|
||||||
my $beta = 0.5 * ((1 - tan($thetaOverTwoQ)) / (1 + tan($thetaOverTwoQ)));
|
|
||||||
|
|
||||||
my $gamma = (0.5 + $beta) * cos($theta0);
|
|
||||||
|
|
||||||
my $alpha = (0.5 - $beta) / 2;
|
|
||||||
|
|
||||||
return ($alpha, $beta, $gamma);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1 +1 @@
|
|||||||
version=0.3.9
|
version=0.4.1
|
||||||
|
|||||||
BIN
resources/uk/co/majenko/audiobookrecorder/icons/refresh.png
Normal file
BIN
resources/uk/co/majenko/audiobookrecorder/icons/refresh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,40 @@
|
|||||||
package uk.co.majenko.audiobookrecorder;
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.Properties;
|
import java.util.Random;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.awt.Image;
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.transform.Transformer;
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import javax.xml.transform.TransformerConfigurationException;
|
||||||
import javax.xml.transform.TransformerFactory;
|
import javax.xml.transform.TransformerFactory;
|
||||||
import javax.xml.transform.dom.DOMSource;
|
import javax.xml.transform.dom.DOMSource;
|
||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
import javax.xml.transform.OutputKeys;
|
||||||
import org.w3c.dom.Attr;
|
import org.w3c.dom.Attr;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Text;
|
import org.w3c.dom.Text;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class Book extends BookTreeNode {
|
public class Book extends BookTreeNode {
|
||||||
|
|
||||||
@@ -33,34 +44,86 @@ public class Book extends BookTreeNode {
|
|||||||
String comment;
|
String comment;
|
||||||
String ACX;
|
String ACX;
|
||||||
String manuscript;
|
String manuscript;
|
||||||
|
|
||||||
String defaultEffect = "none";
|
String defaultEffect = "none";
|
||||||
|
Sentence roomNoise = null;
|
||||||
int sampleRate;
|
|
||||||
int channels;
|
|
||||||
int resolution;
|
|
||||||
|
|
||||||
String notes = null;
|
String notes = null;
|
||||||
|
|
||||||
ImageIcon icon;
|
ImageIcon icon;
|
||||||
|
|
||||||
Properties prefs;
|
|
||||||
|
|
||||||
File location;
|
File location;
|
||||||
|
Random rng = new Random();
|
||||||
|
TreeMap<String, EffectGroup> effects;
|
||||||
|
AudioFormat cachedFormat = null;
|
||||||
|
|
||||||
public Book(Properties p, String bookname) {
|
public Book(String bookname) {
|
||||||
super(bookname);
|
super(bookname);
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
prefs = p;
|
|
||||||
name = bookname;
|
name = bookname;
|
||||||
|
location = new File(Options.get("path.storage"), sanitize(name));
|
||||||
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
|
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
|
||||||
|
setIcon(Icons.book);
|
||||||
|
roomNoise = new Sentence("room-noise", "Room Noise");
|
||||||
|
roomNoise.setParentBook(this);
|
||||||
|
loadEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Book(Element root) {
|
public Book(File inputFile) throws SAXException, IOException, ParserConfigurationException {
|
||||||
super(getTextNode(root, "title"));
|
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
|
Debug.d("Loading book from", inputFile.getCanonicalPath());
|
||||||
|
if (inputFile.getName().endsWith(".abx")) {
|
||||||
|
location = inputFile.getParentFile();
|
||||||
|
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||||
|
Document doc = dBuilder.parse(inputFile);
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
Element root = doc.getDocumentElement();
|
||||||
|
|
||||||
name = getTextNode(root, "title");
|
name = getTextNode(root, "title");
|
||||||
|
author = getTextNode(root, "author");
|
||||||
|
genre = getTextNode(root, "genre");
|
||||||
|
comment = getTextNode(root, "comment");
|
||||||
|
ACX = getTextNode(root, "acx");
|
||||||
|
manuscript = getTextNode(root, "manuscript");
|
||||||
|
notes = getTextNode(root, "notes");
|
||||||
|
|
||||||
|
Element settings = getNode(root, "settings");
|
||||||
|
Element audioSettings = getNode(settings, "audio");
|
||||||
|
Element effectSettings = getNode(settings, "effects");
|
||||||
|
|
||||||
|
defaultEffect = getTextNode(settings, "default");
|
||||||
|
|
||||||
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
|
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
|
||||||
|
|
||||||
|
loadEffects();
|
||||||
|
|
||||||
|
File cf = new File(location, "coverart.png");
|
||||||
|
if (!cf.exists()) {
|
||||||
|
cf = new File(location, "coverart.jpg");
|
||||||
|
if (!cf.exists()) {
|
||||||
|
cf = new File(location, "coverart.gif");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cf.exists()) {
|
||||||
|
ImageIcon i = new ImageIcon(cf.getAbsolutePath());
|
||||||
|
Image ri = Utils.getScaledImage(i.getImage(), 22, 22);
|
||||||
|
setIcon(new ImageIcon(ri));
|
||||||
|
} else {
|
||||||
|
setIcon(Icons.book);
|
||||||
|
}
|
||||||
|
|
||||||
|
roomNoise = new Sentence("room-noise", "Room Noise");
|
||||||
|
roomNoise.setParentBook(this);
|
||||||
|
|
||||||
|
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);
|
||||||
|
newChapter.setParentBook(this);
|
||||||
|
add(newChapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudiobookRecorder.window.updateEffectChains(effects);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadBookXML(Element root, DefaultTreeModel model) {
|
public void loadBookXML(Element root, DefaultTreeModel model) {
|
||||||
@@ -71,18 +134,12 @@ public class Book extends BookTreeNode {
|
|||||||
comment = getTextNode(root, "comment");
|
comment = getTextNode(root, "comment");
|
||||||
ACX = getTextNode(root, "acx");
|
ACX = getTextNode(root, "acx");
|
||||||
manuscript = getTextNode(root, "manuscript");
|
manuscript = getTextNode(root, "manuscript");
|
||||||
|
|
||||||
AudiobookRecorder.window.setBookNotes(getTextNode(root, "notes"));
|
|
||||||
notes = getTextNode(root, "notes");
|
notes = getTextNode(root, "notes");
|
||||||
|
|
||||||
Element settings = getNode(root, "settings");
|
Element settings = getNode(root, "settings");
|
||||||
Element audioSettings = getNode(settings, "audio");
|
Element audioSettings = getNode(settings, "audio");
|
||||||
Element effectSettings = getNode(settings, "effects");
|
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");
|
defaultEffect = getTextNode(settings, "default");
|
||||||
|
|
||||||
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
|
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name); // This should be in the load routine!!!!
|
||||||
@@ -91,6 +148,9 @@ public class Book extends BookTreeNode {
|
|||||||
|
|
||||||
NodeList chapterList = chapters.getElementsByTagName("chapter");
|
NodeList chapterList = chapters.getElementsByTagName("chapter");
|
||||||
|
|
||||||
|
roomNoise = new Sentence("room-noise", "Room Noise");
|
||||||
|
roomNoise.setParentBook(this);
|
||||||
|
|
||||||
for (int i = 0; i < chapterList.getLength(); i++) {
|
for (int i = 0; i < chapterList.getLength(); i++) {
|
||||||
Element chapterElement = (Element)chapterList.item(i);
|
Element chapterElement = (Element)chapterList.item(i);
|
||||||
Chapter newChapter = new Chapter(chapterElement, model);
|
Chapter newChapter = new Chapter(chapterElement, model);
|
||||||
@@ -118,26 +178,18 @@ public class Book extends BookTreeNode {
|
|||||||
return node.getTextContent();
|
return node.getTextContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTitle(String n) { Debug.trace(); name = n; }
|
||||||
public void setAuthor(String a) { Debug.trace(); author = a; }
|
public void setAuthor(String a) { Debug.trace(); author = a; }
|
||||||
public void setGenre(String g) { Debug.trace(); genre = g; }
|
public void setGenre(String g) { Debug.trace(); genre = g; }
|
||||||
public void setComment(String c) { Debug.trace(); comment = c; }
|
public void setComment(String c) { Debug.trace(); comment = c; }
|
||||||
public void setACX(String c) { Debug.trace(); ACX = c; }
|
public void setACX(String c) { Debug.trace(); ACX = c; }
|
||||||
|
|
||||||
|
public String getTitle() { Debug.trace(); return name; }
|
||||||
public String getAuthor() { Debug.trace(); return author; }
|
public String getAuthor() { Debug.trace(); return author; }
|
||||||
public String getGenre() { Debug.trace(); return genre; }
|
public String getGenre() { Debug.trace(); return genre; }
|
||||||
public String getComment() { Debug.trace(); return comment; }
|
public String getComment() { Debug.trace(); return comment; }
|
||||||
public String getACX() { Debug.trace(); if (ACX == null) return ""; return ACX; }
|
public String getACX() { Debug.trace(); if (ACX == null) return ""; return ACX; }
|
||||||
|
|
||||||
public Chapter getClosingCredits() {
|
|
||||||
Debug.trace();
|
|
||||||
return getChapterById("close");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chapter getOpeningCredits() {
|
|
||||||
Debug.trace();
|
|
||||||
return getChapterById("open");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Chapter getChapterById(String id) {
|
public Chapter getChapterById(String id) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
@@ -153,10 +205,32 @@ public class Book extends BookTreeNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Chapter getChapterByName(String name) {
|
||||||
|
Debug.trace();
|
||||||
|
for (Enumeration o = children(); o.hasMoreElements();) {
|
||||||
|
Object ob = (Object)o.nextElement();
|
||||||
|
if (ob instanceof Chapter) {
|
||||||
|
Chapter c = (Chapter)ob;
|
||||||
|
if (c.getName().equals(name)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Chapter getLastChapter() {
|
public Chapter getLastChapter() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
|
DefaultMutableTreeNode leaf = getLastLeaf();
|
||||||
|
if (leaf instanceof Sentence) {
|
||||||
|
Sentence s = (Sentence)leaf;
|
||||||
|
return (Chapter)s.getParent();
|
||||||
|
}
|
||||||
|
if (leaf instanceof Chapter) {
|
||||||
return (Chapter)getLastLeaf();
|
return (Chapter)getLastLeaf();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Chapter getChapter(int n) {
|
public Chapter getChapter(int n) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
@@ -167,7 +241,27 @@ public class Book extends BookTreeNode {
|
|||||||
public Chapter addChapter() {
|
public Chapter addChapter() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
return new Chapter(uuid, uuid);
|
Chapter c = new Chapter(uuid, uuid);
|
||||||
|
add(c);
|
||||||
|
c.setParentBook(this);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter addChapter(String name) {
|
||||||
|
Debug.trace();
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
Chapter c = new Chapter(uuid, name);
|
||||||
|
add(c);
|
||||||
|
c.setParentBook(this);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter addChapter(String id, String name) {
|
||||||
|
Debug.trace();
|
||||||
|
Chapter c = new Chapter(id, name);
|
||||||
|
add(c);
|
||||||
|
c.setParentBook(this);
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -194,14 +288,9 @@ public class Book extends BookTreeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getBookPath() {
|
|
||||||
Debug.trace();
|
|
||||||
return new File(Options.get("path.storage"), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renameBook(String newName) {
|
public void renameBook(String newName) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
File oldDir = getBookPath();
|
File oldDir = location;
|
||||||
File newDir = new File(Options.get("path.storage"), newName);
|
File newDir = new File(Options.get("path.storage"), newName);
|
||||||
|
|
||||||
if (newDir.exists()) {
|
if (newDir.exists()) {
|
||||||
@@ -212,7 +301,11 @@ public class Book extends BookTreeNode {
|
|||||||
if (oldDir.exists() && oldDir.isDirectory()) {
|
if (oldDir.exists() && oldDir.isDirectory()) {
|
||||||
oldDir.renameTo(newDir);
|
oldDir.renameTo(newDir);
|
||||||
name = newName;
|
name = newName;
|
||||||
AudiobookRecorder.window.saveBookStructure();
|
try {
|
||||||
|
save();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
reloadTree();
|
reloadTree();
|
||||||
Options.set("path.last-book", name);
|
Options.set("path.last-book", name);
|
||||||
Options.savePreferences();
|
Options.savePreferences();
|
||||||
@@ -225,58 +318,17 @@ public class Book extends BookTreeNode {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void renumberChapters() {
|
|
||||||
Debug.trace();
|
|
||||||
int id = 1;
|
|
||||||
|
|
||||||
for (Enumeration c = children(); c.hasMoreElements();) {
|
|
||||||
Chapter chp = (Chapter)c.nextElement();
|
|
||||||
if (Utils.s2i(chp.getId()) > 0) {
|
|
||||||
chp.setId(String.format("%04d", id));
|
|
||||||
id++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSampleRate() { Debug.trace(); return sampleRate; }
|
|
||||||
public void setSampleRate(int sr) { Debug.trace(); sampleRate = sr; }
|
|
||||||
public int getChannels() { Debug.trace(); return channels; }
|
|
||||||
public void setChannels(int c) { Debug.trace(); channels = c; }
|
|
||||||
public int getResolution() { Debug.trace(); return resolution; }
|
|
||||||
public void setResolution(int r) { Debug.trace(); resolution = r; }
|
|
||||||
|
|
||||||
public AudioFormat getAudioFormat() {
|
public AudioFormat getAudioFormat() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
return new AudioFormat(getSampleRate(), getResolution(), getChannels(), true, false);
|
if (cachedFormat != null) {
|
||||||
|
return cachedFormat;
|
||||||
}
|
}
|
||||||
|
cachedFormat = roomNoise.getAudioFormat();
|
||||||
public String get(String key) {
|
return cachedFormat;
|
||||||
Debug.trace();
|
|
||||||
if (prefs.getProperty(key) == null) { return Options.get(key); }
|
|
||||||
return prefs.getProperty(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getInteger(String key) {
|
|
||||||
Debug.trace();
|
|
||||||
if (prefs.getProperty(key) == null) { return Options.getInteger(key); }
|
|
||||||
return Utils.s2i(prefs.getProperty(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(String key, String value) {
|
|
||||||
Debug.trace();
|
|
||||||
prefs.setProperty(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(String key, Integer value) {
|
|
||||||
Debug.trace();
|
|
||||||
prefs.setProperty(key, "" + value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getBookFolder() {
|
public File getBookFolder() {
|
||||||
Debug.trace();
|
return location;
|
||||||
File dir = new File(Options.get("path.storage"), name);
|
|
||||||
return dir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<String> getUsedEffects() {
|
public ArrayList<String> getUsedEffects() {
|
||||||
@@ -327,19 +379,11 @@ public class Book extends BookTreeNode {
|
|||||||
root.appendChild(makeTextNode(doc, "genre", genre));
|
root.appendChild(makeTextNode(doc, "genre", genre));
|
||||||
root.appendChild(makeTextNode(doc, "acx", ACX));
|
root.appendChild(makeTextNode(doc, "acx", ACX));
|
||||||
root.appendChild(makeTextNode(doc, "manuscript", manuscript));
|
root.appendChild(makeTextNode(doc, "manuscript", manuscript));
|
||||||
|
root.appendChild(makeTextNode(doc, "notes", notes));
|
||||||
root.appendChild(makeTextNode(doc, "notes", AudiobookRecorder.window.getBookNotes()));
|
|
||||||
|
|
||||||
Element settingsNode = doc.createElement("settings");
|
Element settingsNode = doc.createElement("settings");
|
||||||
root.appendChild(settingsNode);
|
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");
|
Element effectsNode = doc.createElement("effects");
|
||||||
settingsNode.appendChild(effectsNode);
|
settingsNode.appendChild(effectsNode);
|
||||||
|
|
||||||
@@ -405,7 +449,7 @@ public class Book extends BookTreeNode {
|
|||||||
public void setManuscript(File f) {
|
public void setManuscript(File f) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
manuscript = f.getName();
|
manuscript = f.getName();
|
||||||
File dst = new File(getBookPath(), manuscript);
|
File dst = new File(location, manuscript);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.copy(f.toPath(), dst.toPath());
|
Files.copy(f.toPath(), dst.toPath());
|
||||||
@@ -418,29 +462,47 @@ public class Book extends BookTreeNode {
|
|||||||
Debug.trace();
|
Debug.trace();
|
||||||
if (manuscript == null) return null;
|
if (manuscript == null) return null;
|
||||||
if (manuscript.equals("")) return null;
|
if (manuscript.equals("")) return null;
|
||||||
File f = new File(getBookPath(), manuscript);
|
File f = new File(location, manuscript);
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSelect() {
|
public void onSelect(BookTreeNode target) {
|
||||||
|
Debug.trace();
|
||||||
|
AudiobookRecorder.setSelectedBook(this);
|
||||||
|
if (target == this) {
|
||||||
|
AudiobookRecorder.setSelectedChapter(null);
|
||||||
|
AudiobookRecorder.setSelectedSentence(null);
|
||||||
|
}
|
||||||
|
AudiobookRecorder.window.setBookNotes(notes);
|
||||||
|
AudiobookRecorder.window.noiseFloorLabel.setNoiseFloor(getNoiseFloorDB());
|
||||||
|
// AudiobookRecorder.window.updateEffectChains(effects);
|
||||||
|
TreeNode p = getParent();
|
||||||
|
if (p instanceof BookTreeNode) {
|
||||||
|
BookTreeNode btn = (BookTreeNode)p;
|
||||||
|
btn.onSelect(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNotes() {
|
public String getNotes() {
|
||||||
|
Debug.trace();
|
||||||
return notes;
|
return notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNotes(String n) {
|
public void setNotes(String n) {
|
||||||
|
Debug.trace();
|
||||||
notes = n;
|
notes = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getLocation() {
|
public File getLocation() {
|
||||||
|
Debug.trace();
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocation(File l) {
|
public void setLocation(File l) {
|
||||||
|
Debug.trace();
|
||||||
location = l;
|
location = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,6 +510,7 @@ public class Book extends BookTreeNode {
|
|||||||
Debug.trace();
|
Debug.trace();
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
Debug.trace();
|
||||||
if (AudiobookRecorder.window == null) return;
|
if (AudiobookRecorder.window == null) return;
|
||||||
if (AudiobookRecorder.window.bookTreeModel == null) return;
|
if (AudiobookRecorder.window.bookTreeModel == null) return;
|
||||||
try {
|
try {
|
||||||
@@ -457,4 +520,172 @@ public class Book extends BookTreeNode {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Book getBook() {
|
||||||
|
Debug.trace();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRoomNoise(int ms) {
|
||||||
|
Debug.trace();
|
||||||
|
|
||||||
|
if (roomNoise == null) return null;
|
||||||
|
|
||||||
|
// roomNoise.setEffectChain(getDefaultEffect());
|
||||||
|
int len = roomNoise.getSampleSize();
|
||||||
|
if (len == 0) return null;
|
||||||
|
|
||||||
|
AudioFormat f = roomNoise.getAudioFormat();
|
||||||
|
|
||||||
|
float sr = f.getSampleRate();
|
||||||
|
|
||||||
|
int samples = (int)(ms * (sr / 1000f));
|
||||||
|
|
||||||
|
int start = rng.nextInt(len - samples);
|
||||||
|
int end = start + samples;
|
||||||
|
|
||||||
|
roomNoise.setStartOffset(start);
|
||||||
|
roomNoise.setEndOffset(end);
|
||||||
|
|
||||||
|
byte[] data = roomNoise.getPCMData();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getNoiseFloor() {
|
||||||
|
Debug.trace();
|
||||||
|
if (roomNoise == null) return 0;
|
||||||
|
return roomNoise.getPeak();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNoiseFloorDB() {
|
||||||
|
Debug.trace();
|
||||||
|
if (roomNoise == null) return 0;
|
||||||
|
return roomNoise.getPeakDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sentence getRoomNoiseSentence() {
|
||||||
|
Debug.trace();
|
||||||
|
return roomNoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordRoomNoise() {
|
||||||
|
Debug.trace();
|
||||||
|
if (roomNoise.startRecording()) {
|
||||||
|
|
||||||
|
java.util.Timer ticker = new java.util.Timer(true);
|
||||||
|
ticker.schedule(new TimerTask() {
|
||||||
|
public void run() {
|
||||||
|
Debug.trace();
|
||||||
|
roomNoise.stopRecording();
|
||||||
|
}
|
||||||
|
}, 5000); // 5 seconds of recording
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadEffects() {
|
||||||
|
Debug.trace();
|
||||||
|
effects = new TreeMap<String,EffectGroup>();
|
||||||
|
loadEffectsFromFolder(new File(Options.get("path.storage"), "System"));
|
||||||
|
if (location != null) {
|
||||||
|
loadEffectsFromFolder(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadEffectsFromFolder(File dir) {
|
||||||
|
Debug.trace();
|
||||||
|
if (dir == null) return;
|
||||||
|
if (!dir.exists()) return;
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
for (File f : files) {
|
||||||
|
if (f.getName().endsWith(".eff")) {
|
||||||
|
EffectGroup g = loadEffect(f);
|
||||||
|
if (g != null) {
|
||||||
|
String fn = f.getName().replace(".eff","");
|
||||||
|
effects.put(fn, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EffectGroup loadEffect(File xml) {
|
||||||
|
Debug.trace();
|
||||||
|
try {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document document = builder.parse(xml);
|
||||||
|
|
||||||
|
Element root = document.getDocumentElement();
|
||||||
|
if (root.getTagName().equals("effect")) {
|
||||||
|
EffectGroup g = EffectGroup.loadEffectGroup(root);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void save() throws ParserConfigurationException, TransformerConfigurationException, TransformerException {
|
||||||
|
Debug.trace();
|
||||||
|
if (location == null) {
|
||||||
|
location = new File(Options.get("path.storage"), getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!location.exists()) {
|
||||||
|
location.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
File xml = new File(location, "audiobook.abx");
|
||||||
|
Document doc = buildDocument();
|
||||||
|
|
||||||
|
|
||||||
|
// write the content into xml file
|
||||||
|
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||||
|
Transformer transformer = transformerFactory.newTransformer();
|
||||||
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
|
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||||
|
DOMSource source = new DOMSource(doc);
|
||||||
|
StreamResult result = new StreamResult(xml);
|
||||||
|
transformer.transform(source, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getBookFile() {
|
||||||
|
return new File(location, "audiobook.abx");
|
||||||
|
}
|
||||||
|
|
||||||
|
final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47};
|
||||||
|
|
||||||
|
static {
|
||||||
|
Arrays.sort(illegalChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sanitize(String badFileName) {
|
||||||
|
StringBuilder cleanName = new StringBuilder();
|
||||||
|
int len = badFileName.codePointCount(0, badFileName.length());
|
||||||
|
for (int i=0; i<len; i++) {
|
||||||
|
int c = badFileName.codePointAt(i);
|
||||||
|
if (Arrays.binarySearch(illegalChars, c) < 0) {
|
||||||
|
cleanName.appendCodePoint(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleanName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getLength() {
|
||||||
|
Debug.trace();
|
||||||
|
double len = 0;
|
||||||
|
for (Enumeration o = children(); o.hasMoreElements();) {
|
||||||
|
Object ob = (Object)o.nextElement();
|
||||||
|
if (ob instanceof Chapter) {
|
||||||
|
Chapter c = (Chapter)ob;
|
||||||
|
len += c.getLength();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ public class BookInfoPanel extends JPanel {
|
|||||||
Pattern p = Pattern.compile("\\/titleview\\/([A-Z0-9]{14})");
|
Pattern p = Pattern.compile("\\/titleview\\/([A-Z0-9]{14})");
|
||||||
Matcher m = p.matcher(acx.getText());
|
Matcher m = p.matcher(acx.getText());
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
System.err.println(m);
|
|
||||||
return m.group(1);
|
return m.group(1);
|
||||||
}
|
}
|
||||||
return acx.getText();
|
return acx.getText();
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public abstract class BookTreeNode extends DefaultMutableTreeNode {
|
|||||||
|
|
||||||
public abstract void setNotes(String t);
|
public abstract void setNotes(String t);
|
||||||
public abstract String getNotes();
|
public abstract String getNotes();
|
||||||
|
public abstract void onSelect(BookTreeNode target);
|
||||||
public abstract void onSelect();
|
public abstract Book getBook();
|
||||||
|
public abstract double getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
JLabel time = new JLabelFixedWidth(75, " " + Utils.secToTime(s.getLength(), "ss.SSS") + " ");
|
JLabel time = new JLabelFixedWidth(75, " " + Utils.secToTime(s.getStartTime(), "mm.ss.SSS") + " ");
|
||||||
time.setHorizontalAlignment(SwingConstants.RIGHT);
|
time.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||||
|
|
||||||
ctx.gridx = 0;
|
ctx.gridx = 0;
|
||||||
@@ -96,30 +96,31 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
|||||||
ctx.anchor = GridBagConstraints.LINE_START;
|
ctx.anchor = GridBagConstraints.LINE_START;
|
||||||
p.add(ret, ctx);
|
p.add(ret, ctx);
|
||||||
|
|
||||||
|
String effectChain = s.getEffectChain();
|
||||||
|
if ((effectChain != null) && (!effectChain.equals("none"))) {
|
||||||
|
Effect e = s.getBook().effects.get(effectChain);
|
||||||
|
if (e != null) {
|
||||||
|
JLabel eff = new JLabel(e.toString() + " ");
|
||||||
|
ctx.weightx = 0.0d;
|
||||||
|
ctx.gridx = 1;
|
||||||
|
p.add(eff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (s.isProcessing()) {
|
if (s.isProcessing()) {
|
||||||
JLabel eff = new JLabel();
|
JLabel eff = new JLabel();
|
||||||
eff.setIcon(Icons.processing);
|
eff.setIcon(Icons.processing);
|
||||||
ctx.weightx = 0.0d;
|
ctx.weightx = 0.0d;
|
||||||
ctx.gridx = 1;
|
ctx.gridx = 2;
|
||||||
p.add(eff);
|
p.add(eff);
|
||||||
} else if (s.isQueued()) {
|
} else if (s.isQueued()) {
|
||||||
JLabel eff = new JLabel();
|
JLabel eff = new JLabel();
|
||||||
eff.setIcon(Icons.queued);
|
eff.setIcon(Icons.queued);
|
||||||
ctx.weightx = 0.0d;
|
ctx.weightx = 0.0d;
|
||||||
ctx.gridx = 1;
|
|
||||||
p.add(eff);
|
|
||||||
}
|
|
||||||
|
|
||||||
String effectChain = s.getEffectChain();
|
|
||||||
if ((effectChain != null) && (!effectChain.equals("none"))) {
|
|
||||||
Effect e = AudiobookRecorder.window.effects.get(effectChain);
|
|
||||||
if (e != null) {
|
|
||||||
JLabel eff = new JLabel(e.toString() + " ");
|
|
||||||
ctx.weightx = 0.0d;
|
|
||||||
ctx.gridx = 2;
|
ctx.gridx = 2;
|
||||||
p.add(eff);
|
p.add(eff);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ctx.weightx = 0.0d;
|
ctx.weightx = 0.0d;
|
||||||
ctx.gridx = 3;
|
ctx.gridx = 3;
|
||||||
@@ -180,7 +181,7 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
|||||||
ret.setIcon(b.getIcon());
|
ret.setIcon(b.getIcon());
|
||||||
p.add(ret, ctx);
|
p.add(ret, ctx);
|
||||||
|
|
||||||
JLabel author = new JLabel(b.getAuthor());
|
JLabel author = new JLabel(b.getAuthor() + " - " + Utils.secToTime(b.getLength(), "HH:mm:ss"));
|
||||||
ctx.gridy++;
|
ctx.gridy++;
|
||||||
author.setBorder(new EmptyBorder(0, 27, 0, 0));
|
author.setBorder(new EmptyBorder(0, 27, 0, 0));
|
||||||
Font f = author.getFont();
|
Font f = author.getFont();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class Chain implements Effect {
|
|||||||
|
|
||||||
public void process(double[][] samples) {
|
public void process(double[][] samples) {
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
Effect t = AudiobookRecorder.window.effects.get(target);
|
Effect t = AudiobookRecorder.window.getBook().effects.get(target);
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
t.process(samples);
|
t.process(samples);
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ public class Chain implements Effect {
|
|||||||
|
|
||||||
public void init(double sf) {
|
public void init(double sf) {
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
Effect t = AudiobookRecorder.window.effects.get(target);
|
Effect t = AudiobookRecorder.window.getBook().effects.get(target);
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
t.init(sf);
|
t.init(sf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
|
|
||||||
import it.sauronsoftware.jave.FFMPEGLocator;
|
import it.sauronsoftware.jave.FFMPEGLocator;
|
||||||
import it.sauronsoftware.jave.AudioAttributes;
|
import it.sauronsoftware.jave.AudioAttributes;
|
||||||
@@ -52,6 +53,7 @@ public class Chapter extends BookTreeNode {
|
|||||||
int postGap;
|
int postGap;
|
||||||
|
|
||||||
String notes;
|
String notes;
|
||||||
|
Book parentBook = null;
|
||||||
|
|
||||||
public Chapter(String i, String chaptername) {
|
public Chapter(String i, String chaptername) {
|
||||||
super(chaptername);
|
super(chaptername);
|
||||||
@@ -81,6 +83,25 @@ public class Chapter extends BookTreeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Chapter(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
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);
|
||||||
|
add(newSentence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
return id;
|
return id;
|
||||||
@@ -149,7 +170,7 @@ public class Chapter extends BookTreeNode {
|
|||||||
|
|
||||||
if (getChildCount() == 0) return;
|
if (getChildCount() == 0) return;
|
||||||
|
|
||||||
Book book = AudiobookRecorder.window.book;
|
Book book = getBook();
|
||||||
|
|
||||||
File bookRoot = new File(Options.get("path.storage"), book.getName());
|
File bookRoot = new File(Options.get("path.storage"), book.getName());
|
||||||
if (!bookRoot.exists()) {
|
if (!bookRoot.exists()) {
|
||||||
@@ -183,7 +204,7 @@ public class Chapter extends BookTreeNode {
|
|||||||
attributes.setAudioAttributes(audioAttributes);
|
attributes.setAudioAttributes(audioAttributes);
|
||||||
|
|
||||||
|
|
||||||
AudioFormat sampleformat = AudiobookRecorder.window.roomNoise.getAudioFormat();
|
AudioFormat sampleformat = getBook().getRoomNoiseSentence().getAudioFormat();
|
||||||
AudioFormat format = new AudioFormat(sampleformat.getSampleRate(), 16, 2, true, false);
|
AudioFormat format = new AudioFormat(sampleformat.getSampleRate(), 16, 2, true, false);
|
||||||
byte[] data;
|
byte[] data;
|
||||||
|
|
||||||
@@ -201,7 +222,7 @@ public class Chapter extends BookTreeNode {
|
|||||||
File taggedFile = new File(export, book.getName() + " - " + name + ".mp3");
|
File taggedFile = new File(export, book.getName() + " - " + name + ".mp3");
|
||||||
|
|
||||||
FileOutputStream fos = new FileOutputStream(exportFile);
|
FileOutputStream fos = new FileOutputStream(exportFile);
|
||||||
data = AudiobookRecorder.window.getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter")));
|
data = getBook().getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter")));
|
||||||
fullLength += data.length;
|
fullLength += data.length;
|
||||||
fos.write(data);
|
fos.write(data);
|
||||||
|
|
||||||
@@ -218,9 +239,9 @@ public class Chapter extends BookTreeNode {
|
|||||||
fos.write(data);
|
fos.write(data);
|
||||||
|
|
||||||
if (s.hasMoreElements()) {
|
if (s.hasMoreElements()) {
|
||||||
data = AudiobookRecorder.window.getRoomNoise(snt.getPostGap());
|
data = getBook().getRoomNoise(snt.getPostGap());
|
||||||
} else {
|
} else {
|
||||||
data = AudiobookRecorder.window.getRoomNoise(Utils.s2i(Options.get("catenation.post-chapter")));
|
data = getBook().getRoomNoise(Utils.s2i(Options.get("catenation.post-chapter")));
|
||||||
}
|
}
|
||||||
fullLength += data.length;
|
fullLength += data.length;
|
||||||
fos.write(data);
|
fos.write(data);
|
||||||
@@ -348,11 +369,21 @@ public class Chapter extends BookTreeNode {
|
|||||||
notes = t;
|
notes = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSelect() {
|
public void onSelect(BookTreeNode target) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
|
AudiobookRecorder.setSelectedChapter(this);
|
||||||
|
if (target == this) {
|
||||||
|
AudiobookRecorder.setSelectedSentence(null);
|
||||||
|
}
|
||||||
AudiobookRecorder.window.setChapterNotes(notes);
|
AudiobookRecorder.window.setChapterNotes(notes);
|
||||||
|
TreeNode p = getParent();
|
||||||
|
if (p instanceof BookTreeNode) {
|
||||||
|
BookTreeNode btn = (BookTreeNode)p;
|
||||||
|
btn.onSelect(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public double getLength() {
|
public double getLength() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
double len = 0;
|
double len = 0;
|
||||||
@@ -361,9 +392,32 @@ public class Chapter extends BookTreeNode {
|
|||||||
if (ob instanceof Sentence) {
|
if (ob instanceof Sentence) {
|
||||||
Sentence s = (Sentence)ob;
|
Sentence s = (Sentence)ob;
|
||||||
len += s.getLength();
|
len += s.getLength();
|
||||||
|
len += (s.getPostGap() / 1000d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
len += (getPreGap() / 1000d);
|
||||||
|
len += (getPostGap() / 1000d);
|
||||||
|
}
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Book getBook() {
|
||||||
|
if (parentBook != null) return parentBook;
|
||||||
|
if (getParent() == null) return null;
|
||||||
|
return (Book)getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentBook(Book me) {
|
||||||
|
parentBook = me;
|
||||||
|
for (Enumeration o = children(); o.hasMoreElements();) {
|
||||||
|
Object ob = (Object)o.nextElement();
|
||||||
|
if (ob instanceof Sentence) {
|
||||||
|
Sentence s = (Sentence)ob;
|
||||||
|
s.setParentBook(me);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,27 +45,13 @@ public class DelayLine implements Effect {
|
|||||||
for (int i = 0; i < subSamples[Sentence.LEFT].length; i++) {
|
for (int i = 0; i < subSamples[Sentence.LEFT].length; i++) {
|
||||||
int off = i + d.getSamples();
|
int off = i + d.getSamples();
|
||||||
if ((off < samples[Sentence.LEFT].length) && (off > 0)) {
|
if ((off < samples[Sentence.LEFT].length) && (off > 0)) {
|
||||||
samples[Sentence.LEFT][off] = mix(samples[Sentence.LEFT][off], subSamples[Sentence.LEFT][i]);
|
samples[Sentence.LEFT][off] = Utils.mix(samples[Sentence.LEFT][off], subSamples[Sentence.LEFT][i]);
|
||||||
samples[Sentence.RIGHT][off] = mix(samples[Sentence.RIGHT][off], subSamples[Sentence.RIGHT][i]);
|
samples[Sentence.RIGHT][off] = Utils.mix(samples[Sentence.RIGHT][off], subSamples[Sentence.RIGHT][i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double mix(double a, double b) {
|
|
||||||
double out;
|
|
||||||
|
|
||||||
if ((a < 0) && (b < 0)) {
|
|
||||||
out = (a + b) - (a * b);
|
|
||||||
} else if ((a > 0) && (b > 0)) {
|
|
||||||
out = (a + b) - (a * b);
|
|
||||||
} else {
|
|
||||||
out = a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DelayLineStore addDelayLine(int samples, double gain, double pan) {
|
public DelayLineStore addDelayLine(int samples, double gain, double pan) {
|
||||||
DelayLineStore s = new DelayLineStore(samples, gain, pan);
|
DelayLineStore s = new DelayLineStore(samples, gain, pan);
|
||||||
delayLines.add(s);
|
delayLines.add(s);
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package uk.co.majenko.audiobookrecorder;
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
|
||||||
public class EffectGroup implements Effect {
|
public class EffectGroup implements Effect {
|
||||||
String name;
|
String name;
|
||||||
@@ -66,4 +70,259 @@ public class EffectGroup implements Effect {
|
|||||||
e.init(sf);
|
e.init(sf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static EffectGroup loadEffectGroup(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
EffectGroup group = new EffectGroup(root.getAttribute("name"));
|
||||||
|
NodeList kids = root.getChildNodes();
|
||||||
|
for (int i = 0; i < kids.getLength(); i++) {
|
||||||
|
Node kid = kids.item(i);
|
||||||
|
if (kid instanceof Element) {
|
||||||
|
Element e = (Element)kid;
|
||||||
|
if (e.getTagName().equals("biquad")) {
|
||||||
|
Effect eff = (Effect)loadBiquad(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("delayline")) {
|
||||||
|
Effect eff = (Effect)loadDelayLine(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("pan")) {
|
||||||
|
Effect eff = (Effect)loadPan(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("amplifier")) {
|
||||||
|
Effect eff = (Effect)loadAmplifier(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("chain")) {
|
||||||
|
Effect eff = (Effect)loadChain(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("group")) {
|
||||||
|
Effect eff = (Effect)loadEffectGroup(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("lfo")) {
|
||||||
|
Effect eff = (Effect)loadLFO(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("agc")) {
|
||||||
|
Effect eff = (Effect)loadAGC(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (e.getTagName().equals("clipping")) {
|
||||||
|
Effect eff = (Effect)loadClipping(e);
|
||||||
|
if (eff != null) {
|
||||||
|
group.addEffect(eff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Biquad loadBiquad(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
String type = root.getAttribute("type").toLowerCase();
|
||||||
|
Biquad bq = new Biquad();
|
||||||
|
|
||||||
|
if (type.equals("lowpass")) {
|
||||||
|
bq.setType(Biquad.Lowpass);
|
||||||
|
} else if (type.equals("highpass")) {
|
||||||
|
bq.setType(Biquad.Highpass);
|
||||||
|
} else if (type.equals("bandpass")) {
|
||||||
|
bq.setType(Biquad.Bandpass);
|
||||||
|
} else if (type.equals("notch")) {
|
||||||
|
bq.setType(Biquad.Notch);
|
||||||
|
} else if (type.equals("peak")) {
|
||||||
|
bq.setType(Biquad.Peak);
|
||||||
|
} else if (type.equals("lowshelf")) {
|
||||||
|
bq.setType(Biquad.Lowshelf);
|
||||||
|
} else if (type.equals("highshelf")) {
|
||||||
|
bq.setType(Biquad.Highshelf);
|
||||||
|
} else {
|
||||||
|
Debug.d("Bad Biquad type:", type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bq.setQ(Utils.s2d(root.getAttribute("q")));
|
||||||
|
bq.setFc(Utils.s2d(root.getAttribute("fc")));
|
||||||
|
bq.setPeakGain(Utils.s2d(root.getAttribute("gain")));
|
||||||
|
return bq;
|
||||||
|
}
|
||||||
|
public static DelayLine loadDelayLine(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
DelayLine line = new DelayLine();
|
||||||
|
|
||||||
|
NodeList list = root.getChildNodes();
|
||||||
|
if (Utils.s2b(root.getAttribute("wetonly"))) {
|
||||||
|
line.setWetOnly(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < list.getLength(); i++) {
|
||||||
|
Node n = list.item(i);
|
||||||
|
if (n instanceof Element) {
|
||||||
|
Element e = (Element)n;
|
||||||
|
if (e.getTagName().equals("delay")) {
|
||||||
|
int samples = Utils.s2i(e.getAttribute("samples"));
|
||||||
|
double gain = Utils.s2d(e.getAttribute("gain"));
|
||||||
|
double pan = Utils.s2d(e.getAttribute("pan"));
|
||||||
|
DelayLineStore store = line.addDelayLine(samples, gain, pan);
|
||||||
|
|
||||||
|
|
||||||
|
NodeList inner = e.getChildNodes();
|
||||||
|
for (int j = 0; j < inner.getLength(); j++) {
|
||||||
|
Node in = inner.item(j);
|
||||||
|
if (in instanceof Element) {
|
||||||
|
Element ie = (Element)in;
|
||||||
|
|
||||||
|
if (ie.getTagName().equals("biquad")) {
|
||||||
|
Effect eff = (Effect)loadBiquad(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("delayline")) {
|
||||||
|
Effect eff = (Effect)loadDelayLine(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("pan")) {
|
||||||
|
Effect eff = (Effect)loadPan(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("amplifier")) {
|
||||||
|
Effect eff = (Effect)loadAmplifier(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("chain")) {
|
||||||
|
Effect eff = (Effect)loadChain(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("group")) {
|
||||||
|
Effect eff = (Effect)loadEffectGroup(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("lfo")) {
|
||||||
|
Effect eff = (Effect)loadLFO(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("agc")) {
|
||||||
|
Effect eff = (Effect)loadAGC(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
} else if (ie.getTagName().equals("clipping")) {
|
||||||
|
Effect eff = (Effect)loadClipping(ie);
|
||||||
|
if (eff != null) {
|
||||||
|
store.addEffect(eff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Amplifier loadAmplifier(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
Amplifier a = new Amplifier(Utils.s2d(root.getAttribute("gain")));
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Chain loadChain(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
Chain c = new Chain(root.getAttribute("src"));
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pan loadPan(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
Pan p = new Pan(Utils.s2d(root.getAttribute("pan")));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Clipping loadClipping(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
Clipping c = new Clipping(Utils.s2d(root.getAttribute("clip")));
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LFO loadLFO(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
double f = Utils.s2d(root.getAttribute("frequency"));
|
||||||
|
double d = Utils.s2d(root.getAttribute("depth"));
|
||||||
|
double p = Utils.s2d(root.getAttribute("phase"));
|
||||||
|
double dty = Math.PI;
|
||||||
|
String waveform = root.getAttribute("waveform");
|
||||||
|
if (waveform == null) {
|
||||||
|
waveform = "sine";
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = LFO.SINE;
|
||||||
|
|
||||||
|
switch (waveform.toLowerCase()) {
|
||||||
|
case "sine": w = LFO.SINE; break;
|
||||||
|
case "cosine": w = LFO.COSINE; break;
|
||||||
|
case "square": w = LFO.SQUARE; break;
|
||||||
|
case "triangle": w = LFO.TRIANGLE; break;
|
||||||
|
case "sawtooth": w = LFO.SAWTOOTH; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int m = LFO.ADD;
|
||||||
|
|
||||||
|
String mode = root.getAttribute("mode");
|
||||||
|
|
||||||
|
if (mode == null) {
|
||||||
|
mode = "add";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mode.toLowerCase()) {
|
||||||
|
case "add": m = LFO.ADD; break;
|
||||||
|
case "replace": m = LFO.REPLACE; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.getAttribute("duty") != null) {
|
||||||
|
int di = Utils.s2i(root.getAttribute("duty")); // 0-100;
|
||||||
|
dty = (Math.PI * 2) * ((double)di / 100d);
|
||||||
|
}
|
||||||
|
return new LFO(f, d, p, w, dty, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AGC loadAGC(Element root) {
|
||||||
|
Debug.trace();
|
||||||
|
double ceiling = Utils.s2d(root.getAttribute("ceiling"));
|
||||||
|
double limit = Utils.s2d(root.getAttribute("limit"));
|
||||||
|
double attack = Utils.s2d(root.getAttribute("attack"));
|
||||||
|
double decay = Utils.s2d(root.getAttribute("decay"));
|
||||||
|
if (ceiling < 0.0001d) {
|
||||||
|
ceiling = 0.708d; // -3dB
|
||||||
|
}
|
||||||
|
if (limit < 0.0001d) {
|
||||||
|
limit = 1d; // No gain
|
||||||
|
}
|
||||||
|
AGC agc = new AGC(ceiling, attack, decay, limit);
|
||||||
|
return agc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,4 +43,5 @@ public class Icons {
|
|||||||
static public final ImageIcon queued = new ImageIcon(Icons.class.getResource("icons/queued.png"));
|
static public final ImageIcon queued = new ImageIcon(Icons.class.getResource("icons/queued.png"));
|
||||||
static public final ImageIcon processing = new ImageIcon(Icons.class.getResource("icons/processing.png"));
|
static public final ImageIcon processing = new ImageIcon(Icons.class.getResource("icons/processing.png"));
|
||||||
static public final ImageIcon close = new ImageIcon(Icons.class.getResource("icons/close.png"));
|
static public final ImageIcon close = new ImageIcon(Icons.class.getResource("icons/close.png"));
|
||||||
|
static public final ImageIcon refresh = new ImageIcon(Icons.class.getResource("icons/refresh.png"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class MainToolBar extends JToolBar {
|
|||||||
|
|
||||||
saveBook = new JButtonSpacePlay(Icons.save, "Save Book", new ActionListener() {
|
saveBook = new JButtonSpacePlay(Icons.save, "Save Book", new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
root.saveBookStructure();
|
root.saveAllBooks();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
add(saveBook);
|
add(saveBook);
|
||||||
@@ -65,7 +65,7 @@ public class MainToolBar extends JToolBar {
|
|||||||
|
|
||||||
recordRoomNoise = new JButtonSpacePlay(Icons.recordRoom, "Record Room Noise", new ActionListener() {
|
recordRoomNoise = new JButtonSpacePlay(Icons.recordRoom, "Record Room Noise", new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
root.recordRoomNoise();
|
root.getBook().recordRoomNoise();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
add(recordRoomNoise);
|
add(recordRoomNoise);
|
||||||
@@ -108,7 +108,7 @@ public class MainToolBar extends JToolBar {
|
|||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
JToggleButton b = (JToggleButton)e.getSource();
|
JToggleButton b = (JToggleButton)e.getSource();
|
||||||
if (b.isSelected()) {
|
if (b.isSelected()) {
|
||||||
if (!root.enableMicrophone()) {
|
if (!Microphone.start()) {
|
||||||
b.setSelected(false);
|
b.setSelected(false);
|
||||||
} else {
|
} else {
|
||||||
if (bgCol == null) {
|
if (bgCol == null) {
|
||||||
@@ -117,7 +117,7 @@ public class MainToolBar extends JToolBar {
|
|||||||
b.setBackground(Color.RED);
|
b.setBackground(Color.RED);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
root.disableMicrophone();
|
Microphone.stop();
|
||||||
if (bgCol != null) {
|
if (bgCol != null) {
|
||||||
b.setBackground(bgCol);
|
b.setBackground(bgCol);
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/uk/co/majenko/audiobookrecorder/Microphone.java
Normal file
77
src/uk/co/majenko/audiobookrecorder/Microphone.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.AudioSystem;
|
||||||
|
import javax.sound.sampled.Mixer;
|
||||||
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
import javax.sound.sampled.TargetDataLine;
|
||||||
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
|
public class Microphone {
|
||||||
|
|
||||||
|
public static TargetDataLine device = null;
|
||||||
|
public static AudioInputStream stream = null;
|
||||||
|
|
||||||
|
public static boolean start() {
|
||||||
|
Debug.trace();
|
||||||
|
AudioFormat format = Options.getAudioFormat();
|
||||||
|
Mixer.Info mixer = Options.getRecordingMixer();
|
||||||
|
|
||||||
|
device = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
device = AudioSystem.getTargetDataLine(format, mixer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
device = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device == null) {
|
||||||
|
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = new AudioInputStream(device);
|
||||||
|
|
||||||
|
try {
|
||||||
|
device.open();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
device = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop() {
|
||||||
|
Debug.trace();
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
device.stop();
|
||||||
|
device.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
device = null;
|
||||||
|
stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AudioInputStream getStream() {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TargetDataLine getDevice() {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void flush() {
|
||||||
|
if (device != null) {
|
||||||
|
device.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -121,7 +121,7 @@ public class OpenBookPanel extends JPanel {
|
|||||||
c = c.getParent();
|
c = c.getParent();
|
||||||
}
|
}
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
System.err.println("Could not get option pane!");
|
Debug.d("Could not get option pane!");
|
||||||
} else {
|
} else {
|
||||||
JOptionPane op = (JOptionPane)c;
|
JOptionPane op = (JOptionPane)c;
|
||||||
op.setValue(JOptionPane.OK_OPTION);
|
op.setValue(JOptionPane.OK_OPTION);
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ public class Options extends JDialog {
|
|||||||
panel.add(t, constraint);
|
panel.add(t, constraint);
|
||||||
|
|
||||||
for (KVPair p : options) {
|
for (KVPair p : options) {
|
||||||
|
if (p == null) continue;
|
||||||
if (p.key.equals(def)) {
|
if (p.key.equals(def)) {
|
||||||
o.setSelectedItem(p);
|
o.setSelectedItem(p);
|
||||||
}
|
}
|
||||||
@@ -495,49 +496,37 @@ public class Options extends JDialog {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
static KVPair<String, String>[] getRecordingMixerList() {
|
static KVPair<String, String>[] getRecordingMixerList() {
|
||||||
TreeSet<KVPair<String, String>> list = new TreeSet<KVPair<String, String>>();
|
return getMixerList(TargetDataLine.class);
|
||||||
|
|
||||||
AudioFormat stereoFormat = new AudioFormat(44100f, 16, 2, true, false);
|
|
||||||
AudioFormat monoFormat = new AudioFormat(44100f, 16, 1, true, false);
|
|
||||||
|
|
||||||
DataLine.Info stereoDIF = new DataLine.Info(TargetDataLine.class, stereoFormat);
|
|
||||||
DataLine.Info monoDIF = new DataLine.Info(TargetDataLine.class, monoFormat);
|
|
||||||
|
|
||||||
Mixer.Info[] info = AudioSystem.getMixerInfo();
|
|
||||||
for (Mixer.Info i : info) {
|
|
||||||
Mixer m = AudioSystem.getMixer(i);
|
|
||||||
|
|
||||||
boolean supported = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
m.getLine(stereoDIF);
|
|
||||||
supported = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
m.getLine(monoDIF);
|
|
||||||
supported = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supported) {
|
|
||||||
KVPair<String, String> p = new KVPair<String, String>(i.getName(), i.getName()); //i.getDescription());
|
|
||||||
list.add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.toArray(new KVPair[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static KVPair[] getPlaybackMixerList() {
|
static KVPair[] getPlaybackMixerList() {
|
||||||
TreeSet<KVPair<String, String>> list = new TreeSet<KVPair<String, String>>();
|
return getMixerList(SourceDataLine.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
static KVPair[] getMixerList(Class<?> cl) {
|
||||||
|
ArrayList<KVPair<String, String>> list = new ArrayList<KVPair<String, String>>();
|
||||||
|
|
||||||
AudioFormat stereoFormat = new AudioFormat(44100f, 16, 2, true, false);
|
AudioFormat stereoFormat = new AudioFormat(44100f, 16, 2, true, false);
|
||||||
AudioFormat monoFormat = new AudioFormat(44100f, 16, 1, true, false);
|
AudioFormat monoFormat = new AudioFormat(44100f, 16, 1, true, false);
|
||||||
|
|
||||||
DataLine.Info stereoDIF = new DataLine.Info(SourceDataLine.class, stereoFormat);
|
ArrayList<AudioFormat> validFormats = new ArrayList<AudioFormat>();
|
||||||
DataLine.Info monoDIF = new DataLine.Info(SourceDataLine.class, monoFormat);
|
|
||||||
|
validFormats.add(new AudioFormat(44100f, 16, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(44100f, 16, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(44100f, 24, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(44100f, 24, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(48000f, 16, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(48000f, 16, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(48000f, 24, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(48000f, 24, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(96000f, 16, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(96000f, 16, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(96000f, 24, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(96000f, 24, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(192000f, 16, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(192000f, 16, 2, true, false));
|
||||||
|
validFormats.add(new AudioFormat(192000f, 24, 1, true, false));
|
||||||
|
validFormats.add(new AudioFormat(192000f, 24, 2, true, false));
|
||||||
|
|
||||||
Mixer.Info[] info = AudioSystem.getMixerInfo();
|
Mixer.Info[] info = AudioSystem.getMixerInfo();
|
||||||
for (Mixer.Info i : info) {
|
for (Mixer.Info i : info) {
|
||||||
@@ -545,19 +534,14 @@ public class Options extends JDialog {
|
|||||||
|
|
||||||
boolean supported = false;
|
boolean supported = false;
|
||||||
|
|
||||||
|
for (AudioFormat valid : validFormats) {
|
||||||
try {
|
try {
|
||||||
m.getLine(stereoDIF);
|
m.getLine(new DataLine.Info(cl, valid));
|
||||||
supported = true;
|
supported = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
m.getLine(monoDIF);
|
|
||||||
supported = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (supported) {
|
if (supported) {
|
||||||
KVPair<String, String> p = new KVPair<String, String>(i.getName(), i.getName()); //i.getDescription());
|
KVPair<String, String> p = new KVPair<String, String>(i.getName(), i.getName()); //i.getDescription());
|
||||||
list.add(p);
|
list.add(p);
|
||||||
@@ -575,10 +559,11 @@ public class Options extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static KVPair[] getSampleRateList() {
|
static KVPair[] getSampleRateList() {
|
||||||
KVPair[] l = new KVPair[3];
|
KVPair[] l = new KVPair[4];
|
||||||
l[0] = new KVPair<String, String>("44100", "44100");
|
l[0] = new KVPair<String, String>("44100", "44100");
|
||||||
l[1] = new KVPair<String, String>("48000", "48000");
|
l[1] = new KVPair<String, String>("48000", "48000");
|
||||||
l[2] = new KVPair<String, String>("96000", "96000");
|
l[2] = new KVPair<String, String>("96000", "96000");
|
||||||
|
l[3] = new KVPair<String, String>("192000", "192000");
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,17 +707,17 @@ public class Options extends JDialog {
|
|||||||
} else if (value instanceof Boolean) {
|
} else if (value instanceof Boolean) {
|
||||||
set(key, (Boolean)value);
|
set(key, (Boolean)value);
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Bad type for key " + key);
|
Debug.d("Bad type for key", key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void storePreferences() {
|
void storePreferences() {
|
||||||
set("audio.recording.device", ((KVPair)mixerList.getSelectedItem()).key);
|
if (mixerList.getSelectedItem() != null) set("audio.recording.device", ((KVPair)mixerList.getSelectedItem()).key);
|
||||||
set("audio.recording.channels", ((KVPair)channelList.getSelectedItem()).key);
|
if (channelList.getSelectedItem() != null) set("audio.recording.channels", ((KVPair)channelList.getSelectedItem()).key);
|
||||||
set("audio.recording.samplerate", ((KVPair)rateList.getSelectedItem()).key);
|
if (rateList.getSelectedItem() != null) set("audio.recording.samplerate", ((KVPair)rateList.getSelectedItem()).key);
|
||||||
set("audio.recording.resolution", ((KVPair)bitDepth.getSelectedItem()).key);
|
if (bitDepth.getSelectedItem() != null) set("audio.recording.resolution", ((KVPair)bitDepth.getSelectedItem()).key);
|
||||||
set("audio.recording.trim", ((KVPair)trimMethod.getSelectedItem()).key);
|
if (trimMethod.getSelectedItem() != null) set("audio.recording.trim", ((KVPair)trimMethod.getSelectedItem()).key);
|
||||||
set("audio.playback.device", ((KVPair)playbackList.getSelectedItem()).key);
|
if (playbackList.getSelectedItem() != null) set("audio.playback.device", ((KVPair)playbackList.getSelectedItem()).key);
|
||||||
set("path.storage", storageFolder.getText());
|
set("path.storage", storageFolder.getText());
|
||||||
set("path.archive", archiveFolder.getText());
|
set("path.archive", archiveFolder.getText());
|
||||||
set("path.ffmpeg", ffmpegLocation.getText());
|
set("path.ffmpeg", ffmpegLocation.getText());
|
||||||
@@ -742,9 +727,9 @@ public class Options extends JDialog {
|
|||||||
set("catenation.short-sentence", shortSentenceGap.getValue());
|
set("catenation.short-sentence", shortSentenceGap.getValue());
|
||||||
set("catenation.post-paragraph", postParagraphGap.getValue());
|
set("catenation.post-paragraph", postParagraphGap.getValue());
|
||||||
set("catenation.post-section", postSectionGap.getValue());
|
set("catenation.post-section", postSectionGap.getValue());
|
||||||
set("audio.export.bitrate", ((KVPair)bitRate.getSelectedItem()).key);
|
if (bitRate.getSelectedItem() != null) set("audio.export.bitrate", ((KVPair)bitRate.getSelectedItem()).key);
|
||||||
set("audio.export.channels", ((KVPair)channels.getSelectedItem()).key);
|
if (channels.getSelectedItem() != null) set("audio.export.channels", ((KVPair)channels.getSelectedItem()).key);
|
||||||
set("audio.export.samplerate", ((KVPair)exportRate.getSelectedItem()).key);
|
if (exportRate.getSelectedItem() != null) set("audio.export.samplerate", ((KVPair)exportRate.getSelectedItem()).key);
|
||||||
set("process.sphinx", enableParsing.isSelected());
|
set("process.sphinx", enableParsing.isSelected());
|
||||||
set("process.command", speechCommand.getText());
|
set("process.command", speechCommand.getText());
|
||||||
set("process.threads", workerThreads.getValue());
|
set("process.threads", workerThreads.getValue());
|
||||||
@@ -752,8 +737,8 @@ public class Options extends JDialog {
|
|||||||
set("cache.size", cacheSize.getValue());
|
set("cache.size", cacheSize.getValue());
|
||||||
set("audio.recording.trim.fft", fftThreshold.getValue());
|
set("audio.recording.trim.fft", fftThreshold.getValue());
|
||||||
set("audio.recording.variance", maxGainVariance.getValue());
|
set("audio.recording.variance", maxGainVariance.getValue());
|
||||||
set("audio.recording.trim.blocksize", ((KVPair)fftBlockSize.getSelectedItem()).key);
|
if (fftBlockSize.getSelectedItem() != null) set("audio.recording.trim.blocksize", ((KVPair)fftBlockSize.getSelectedItem()).key);
|
||||||
set("audio.playback.blocksize", ((KVPair)playbackBlockSize.getSelectedItem()).key);
|
if (playbackBlockSize.getSelectedItem() != null) set("audio.playback.blocksize", ((KVPair)playbackBlockSize.getSelectedItem()).key);
|
||||||
|
|
||||||
set("scripts.startup", startupScript.getText());
|
set("scripts.startup", startupScript.getText());
|
||||||
|
|
||||||
@@ -810,9 +795,10 @@ public class Options extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static KVPair[] getResolutionList() {
|
public static KVPair[] getResolutionList() {
|
||||||
KVPair[] pairs = new KVPair[2];
|
KVPair[] pairs = new KVPair[3];
|
||||||
pairs[0] = new KVPair<String, String>("16", "16 Bit");
|
pairs[0] = new KVPair<String, String>("16", "16 Bit");
|
||||||
pairs[1] = new KVPair<String, String>("24", "24 Bit");
|
pairs[1] = new KVPair<String, String>("24", "24 Bit");
|
||||||
|
pairs[2] = new KVPair<String, String>("32", "32 Bit");
|
||||||
return pairs;
|
return pairs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import javax.xml.transform.dom.DOMSource;
|
|||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
import org.w3c.dom.Attr;
|
import org.w3c.dom.Attr;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Text;
|
import org.w3c.dom.Text;
|
||||||
import javax.sound.sampled.TargetDataLine;
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import javax.sound.sampled.AudioInputStream;
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
import javax.sound.sampled.AudioFileFormat;
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -34,6 +36,8 @@ import javax.sound.sampled.AudioSystem;
|
|||||||
import javax.sound.sampled.AudioFileFormat;
|
import javax.sound.sampled.AudioFileFormat;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -88,6 +92,8 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
AudioInputStream inputStream;
|
AudioInputStream inputStream;
|
||||||
AudioFormat storedFormat = null;
|
AudioFormat storedFormat = null;
|
||||||
|
|
||||||
|
Book parentBook = null;
|
||||||
|
|
||||||
double runtime = -1d;
|
double runtime = -1d;
|
||||||
|
|
||||||
double[][] audioData = null;
|
double[][] audioData = null;
|
||||||
@@ -96,6 +102,8 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
|
|
||||||
double[] fftProfile = null;
|
double[] fftProfile = null;
|
||||||
|
|
||||||
|
TreeMap<Integer, Double> gainPoints = null;
|
||||||
|
|
||||||
RecordingThread recordingThread;
|
RecordingThread recordingThread;
|
||||||
|
|
||||||
boolean effectEthereal = false;
|
boolean effectEthereal = false;
|
||||||
@@ -129,17 +137,20 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
try {
|
try {
|
||||||
running = true;
|
running = true;
|
||||||
recording = true;
|
recording = true;
|
||||||
byte[] buf = new byte[1024]; //AudiobookRecorder.window.microphone.getBufferSize()];
|
|
||||||
|
final int numFrames = 512;
|
||||||
|
final int bufSize = numFrames * format.getFrameSize();
|
||||||
|
byte[] buf = new byte[bufSize]; //AudiobookRecorder.window.microphone.getBufferSize()];
|
||||||
FileOutputStream fos = new FileOutputStream(tempFile);
|
FileOutputStream fos = new FileOutputStream(tempFile);
|
||||||
int len = 0;
|
int len = 0;
|
||||||
AudiobookRecorder.window.microphone.flush();
|
Microphone.flush();
|
||||||
int nr = 0;
|
int nr = 0;
|
||||||
while (recording) {
|
while (recording) {
|
||||||
nr = AudiobookRecorder.window.microphoneStream.read(buf, 0, buf.length);
|
nr = Microphone.getStream().read(buf, 0, buf.length);
|
||||||
len += nr;
|
len += nr;
|
||||||
fos.write(buf, 0, nr);
|
fos.write(buf, 0, nr);
|
||||||
}
|
}
|
||||||
nr = AudiobookRecorder.window.microphoneStream.read(buf, 0, buf.length);
|
nr = Microphone.getStream().read(buf, 0, buf.length);
|
||||||
len += nr;
|
len += nr;
|
||||||
fos.write(buf, 0, nr);
|
fos.write(buf, 0, nr);
|
||||||
fos.close();
|
fos.close();
|
||||||
@@ -218,19 +229,49 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
peak = Utils.s2d(Book.getTextNode(root, "peak", "-1.000"));
|
peak = Utils.s2d(Book.getTextNode(root, "peak", "-1.000"));
|
||||||
isDetected = Utils.s2b(Book.getTextNode(root, "detected"));
|
isDetected = Utils.s2b(Book.getTextNode(root, "detected"));
|
||||||
|
|
||||||
|
gainPoints = new TreeMap<Integer, Double>();
|
||||||
|
Element gp = Book.getNode(root, "gainpoints");
|
||||||
|
if (gp != null) {
|
||||||
|
NodeList points = gp.getElementsByTagName("gainpoint");
|
||||||
|
|
||||||
|
for (int i = 0; i < points.getLength(); i++) {
|
||||||
|
Element point = (Element)points.item(i);
|
||||||
|
int loc = Utils.s2i(point.getAttribute("location"));
|
||||||
|
double g = Utils.s2d(point.getAttribute("gain"));
|
||||||
|
gainPoints.put(loc, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (text == null) text = id;
|
if (text == null) text = id;
|
||||||
if (text.equals("")) text = id;
|
if (text.equals("")) text = id;
|
||||||
|
|
||||||
if ((crossStartOffset == -1) || (crossEndOffset == -1)) {
|
if (id.equals("room-noise")) return;
|
||||||
updateCrossings();
|
|
||||||
|
if (startOffset >= sampleSize) startOffset = 0;
|
||||||
|
if (endOffset >= sampleSize) endOffset = sampleSize - 1;
|
||||||
|
if (crossStartOffset >= sampleSize) crossStartOffset = 0;
|
||||||
|
if (crossEndOffset >= sampleSize) crossEndOffset = sampleSize - 1;
|
||||||
|
|
||||||
|
if (crossStartOffset == -1) {
|
||||||
|
crossStartOffset = startOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runtime <= 0.01d) getLength();
|
if (crossEndOffset == -1) {
|
||||||
|
crossEndOffset = endOffset;
|
||||||
|
}
|
||||||
|
// if ((crossStartOffset == -1) || (crossEndOffset == -1)) {
|
||||||
|
// updateCrossings();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (runtime <= 0.01d) getLength();
|
||||||
|
if (runtime <= 0.001d) {
|
||||||
|
runtime = crossEndOffset - crossStartOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean startRecording() {
|
public boolean startRecording() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
if (AudiobookRecorder.window.microphone == null) {
|
if (Microphone.getDevice() == null) {
|
||||||
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Microphone not started. Start the microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Microphone not started. Start the microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -242,6 +283,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
Thread rc = new Thread(recordingThread);
|
Thread rc = new Thread(recordingThread);
|
||||||
rc.setDaemon(true);
|
rc.setDaemon(true);
|
||||||
rc.start();
|
rc.start();
|
||||||
|
AudiobookRecorder.window.centralPanel.setFlash(true);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -256,6 +298,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AudiobookRecorder.window.centralPanel.setFlash(false);
|
||||||
|
|
||||||
CacheManager.removeFromCache(this);
|
CacheManager.removeFromCache(this);
|
||||||
|
|
||||||
@@ -332,7 +375,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double[] roomNoiseProfile = AudiobookRecorder.window.getRoomNoiseSentence().getFFTProfile();
|
double[] roomNoiseProfile = getBook().getRoomNoiseSentence().getFFTProfile();
|
||||||
|
|
||||||
int fftSize = Options.getInteger("audio.recording.trim.blocksize");
|
int fftSize = Options.getInteger("audio.recording.trim.blocksize");
|
||||||
|
|
||||||
@@ -462,7 +505,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
double[][] samples;
|
double[][] samples;
|
||||||
samples = getProcessedAudioData();
|
samples = getProcessedAudioData();
|
||||||
if (samples == null) return;
|
if (samples == null) return;
|
||||||
double noiseFloor = AudiobookRecorder.window.getNoiseFloor();
|
double noiseFloor = getBook().getNoiseFloor();
|
||||||
noiseFloor *= 1.1;
|
noiseFloor *= 1.1;
|
||||||
|
|
||||||
// Find start
|
// Find start
|
||||||
@@ -525,7 +568,10 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
|
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
File b = new File(AudiobookRecorder.window.getBookFolder(), "files");
|
Debug.d("Get file for", id);
|
||||||
|
Book book = getBook();
|
||||||
|
if (book == null) return null;
|
||||||
|
File b = new File(book.getLocation(), "files");
|
||||||
if (!b.exists()) {
|
if (!b.exists()) {
|
||||||
b.mkdirs();
|
b.mkdirs();
|
||||||
}
|
}
|
||||||
@@ -534,7 +580,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
|
|
||||||
public File getTempFile() {
|
public File getTempFile() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
File b = new File(AudiobookRecorder.window.getBookFolder(), "files");
|
File b = new File(getBook().getLocation(), "files");
|
||||||
if (!b.exists()) {
|
if (!b.exists()) {
|
||||||
b.mkdirs();
|
b.mkdirs();
|
||||||
}
|
}
|
||||||
@@ -790,7 +836,25 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getStartTime() {
|
||||||
|
double time = 0;
|
||||||
|
DefaultMutableTreeNode prev = getPreviousSibling();
|
||||||
|
while (prev != null) {
|
||||||
|
if (prev instanceof Sentence) {
|
||||||
|
Sentence ps = (Sentence)prev;
|
||||||
|
time += ps.getLength();
|
||||||
|
time += ps.getPostGap() / 1000d;
|
||||||
|
prev = prev.getPreviousSibling();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* Get the length of the sample in seconds */
|
/* Get the length of the sample in seconds */
|
||||||
|
@Override
|
||||||
public double getLength() {
|
public double getLength() {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
if (runtime > 0.01d) return runtime;
|
if (runtime > 0.01d) return runtime;
|
||||||
@@ -808,6 +872,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
public Sentence cloneSentence() throws IOException {
|
public Sentence cloneSentence() throws IOException {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
Sentence sentence = new Sentence();
|
Sentence sentence = new Sentence();
|
||||||
|
sentence.setParentBook(getBook());
|
||||||
sentence.setPostGap(getPostGap());
|
sentence.setPostGap(getPostGap());
|
||||||
if (!id.equals(text)) {
|
if (!id.equals(text)) {
|
||||||
sentence.setText(text);
|
sentence.setText(text);
|
||||||
@@ -957,10 +1022,15 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
Debug.trace();
|
Debug.trace();
|
||||||
ExternalEditor ed = new ExternalEditor(this);
|
ExternalEditor ed = new ExternalEditor(this);
|
||||||
ed.run();
|
ed.run();
|
||||||
|
CacheManager.removeFromCache(this);
|
||||||
|
runtime = -1;
|
||||||
|
sampleSize = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void backup() throws IOException {
|
public void backup() throws IOException {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
|
if (getFile() == null) return;
|
||||||
|
if (!getFile().exists()) return;
|
||||||
File whereto = getFile().getParentFile();
|
File whereto = getFile().getParentFile();
|
||||||
String name = getFile().getName();
|
String name = getFile().getName();
|
||||||
|
|
||||||
@@ -1000,6 +1070,10 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
if (command == null) return;
|
if (command == null) return;
|
||||||
if (command.equals("")) return;
|
if (command.equals("")) return;
|
||||||
|
|
||||||
|
Debug.d("Starting size:", sampleSize);
|
||||||
|
Debug.d("Start offset:", startOffset, crossStartOffset);
|
||||||
|
Debug.d("End offset:", endOffset, crossEndOffset);
|
||||||
|
|
||||||
String[] parts = command.split("::");
|
String[] parts = command.split("::");
|
||||||
|
|
||||||
ArrayList<String> args = new ArrayList<String>();
|
ArrayList<String> args = new ArrayList<String>();
|
||||||
@@ -1042,6 +1116,17 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CacheManager.removeFromCache(Sentence.this);
|
CacheManager.removeFromCache(Sentence.this);
|
||||||
|
runtime = -1;
|
||||||
|
sampleSize = -1;
|
||||||
|
loadFile();
|
||||||
|
Debug.d("Ending size:", sampleSize);
|
||||||
|
if (startOffset >= sampleSize) startOffset = 0;
|
||||||
|
if (endOffset >= sampleSize) endOffset = sampleSize - 1;
|
||||||
|
crossStartOffset = -1;
|
||||||
|
crossEndOffset = -1;
|
||||||
|
updateCrossings();
|
||||||
|
Debug.d("Start offset:", startOffset, crossStartOffset);
|
||||||
|
Debug.d("End offset:", endOffset, crossEndOffset);
|
||||||
AudiobookRecorder.window.updateWaveform(true);
|
AudiobookRecorder.window.updateWaveform(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1366,6 +1451,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
|
|
||||||
synchronized public double[][] getProcessedAudioData(boolean effectsEnabled, boolean applyGain) {
|
synchronized public double[][] getProcessedAudioData(boolean effectsEnabled, boolean applyGain) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
|
Book book = getBook();
|
||||||
loadFile();
|
loadFile();
|
||||||
if (processedAudio != null) {
|
if (processedAudio != null) {
|
||||||
return processedAudio;
|
return processedAudio;
|
||||||
@@ -1380,9 +1466,9 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
// Add processing in here.
|
// Add processing in here.
|
||||||
|
|
||||||
|
|
||||||
String def = AudiobookRecorder.window.getDefaultEffectsChain();
|
String def = getBook().getDefaultEffect();
|
||||||
if ((def != null) && (AudiobookRecorder.window.effects != null)) {
|
if ((def != null) && (book.effects != null)) {
|
||||||
Effect eff = AudiobookRecorder.window.effects.get(def);
|
Effect eff = book.effects.get(def);
|
||||||
|
|
||||||
if (effectsEnabled) {
|
if (effectsEnabled) {
|
||||||
if (eff != null) {
|
if (eff != null) {
|
||||||
@@ -1393,7 +1479,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
if (effectChain != null) {
|
if (effectChain != null) {
|
||||||
// Don't double up the default chain
|
// Don't double up the default chain
|
||||||
if (!effectChain.equals(def)) {
|
if (!effectChain.equals(def)) {
|
||||||
eff = AudiobookRecorder.window.effects.get(effectChain);
|
eff = book.effects.get(effectChain);
|
||||||
if (eff != null) {
|
if (eff != null) {
|
||||||
eff.init(getAudioFormat().getFrameRate());
|
eff.init(getAudioFormat().getFrameRate());
|
||||||
eff.process(processedAudio);
|
eff.process(processedAudio);
|
||||||
@@ -1411,6 +1497,12 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double[] gc = calculateGains();
|
||||||
|
for (int i = 0; i < processedAudio[LEFT].length; i++) {
|
||||||
|
processedAudio[LEFT][i] *= gc[i];
|
||||||
|
processedAudio[RIGHT][i] *= gc[i];
|
||||||
|
}
|
||||||
|
|
||||||
return processedAudio;
|
return processedAudio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1593,6 +1685,17 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
sentenceNode.appendChild(Book.makeTextNode(doc, "time", getLength()));
|
sentenceNode.appendChild(Book.makeTextNode(doc, "time", getLength()));
|
||||||
sentenceNode.appendChild(Book.makeTextNode(doc, "peak", getPeak()));
|
sentenceNode.appendChild(Book.makeTextNode(doc, "peak", getPeak()));
|
||||||
sentenceNode.appendChild(Book.makeTextNode(doc, "detected", beenDetected()));
|
sentenceNode.appendChild(Book.makeTextNode(doc, "detected", beenDetected()));
|
||||||
|
Element gp = doc.createElement("gainpoints");
|
||||||
|
if (gainPoints != null) {
|
||||||
|
for (Integer loc : gainPoints.keySet()) {
|
||||||
|
Double g = gainPoints.get(loc);
|
||||||
|
Element p = doc.createElement("gainpoint");
|
||||||
|
p.setAttribute("location", String.format("%d", loc));
|
||||||
|
p.setAttribute("gain", String.format("%.3g", g));
|
||||||
|
gp.appendChild(p);
|
||||||
|
}
|
||||||
|
sentenceNode.appendChild(gp);
|
||||||
|
}
|
||||||
return sentenceNode;
|
return sentenceNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1617,9 +1720,15 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
return notes;
|
return notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSelect() {
|
public void onSelect(BookTreeNode target) {
|
||||||
Debug.trace();
|
Debug.trace();
|
||||||
|
AudiobookRecorder.setSelectedSentence(this);
|
||||||
AudiobookRecorder.window.setSentenceNotes(notes);
|
AudiobookRecorder.window.setSentenceNotes(notes);
|
||||||
|
TreeNode p = getParent();
|
||||||
|
if (p instanceof BookTreeNode) {
|
||||||
|
BookTreeNode btn = (BookTreeNode)p;
|
||||||
|
btn.onSelect(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadTree() {
|
void reloadTree() {
|
||||||
@@ -1692,4 +1801,103 @@ public class Sentence extends BookTreeNode implements Cacheable {
|
|||||||
reloadTree();
|
reloadTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Book getBook() {
|
||||||
|
if (parentBook != null) {
|
||||||
|
Debug.d("Returning parent book");
|
||||||
|
return parentBook; // Override for room noise which isn't attached to a book tree
|
||||||
|
}
|
||||||
|
Chapter c = (Chapter)getParent();
|
||||||
|
if (c == null) {
|
||||||
|
Debug.d("No parent found");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Debug.d("Chapter: ", c.toString());
|
||||||
|
return c.getBook();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentBook(Book b) {
|
||||||
|
parentBook = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshAllData() {
|
||||||
|
runtime = -1d;
|
||||||
|
peak = -1d;
|
||||||
|
sampleSize = -1;
|
||||||
|
audioData = null;
|
||||||
|
processedAudio = null;
|
||||||
|
fftProfile = null;
|
||||||
|
CacheManager.removeFromCache(this);
|
||||||
|
getProcessedAudioData();
|
||||||
|
getLength();
|
||||||
|
crossStartOffset = -1;
|
||||||
|
crossEndOffset = -1;
|
||||||
|
updateCrossings();
|
||||||
|
getPeakDB();
|
||||||
|
reloadTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeMap<Integer, Double> getGainPoints() {
|
||||||
|
Debug.trace();
|
||||||
|
if (gainPoints == null) {
|
||||||
|
gainPoints = new TreeMap<Integer, Double>();
|
||||||
|
}
|
||||||
|
return gainPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGainPoint(Integer loc, Double g) {
|
||||||
|
if (gainPoints == null) {
|
||||||
|
gainPoints = new TreeMap<Integer, Double>();
|
||||||
|
}
|
||||||
|
gainPoints.put(loc, g);
|
||||||
|
CacheManager.removeFromCache(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeGainPoint(Integer loc) {
|
||||||
|
gainPoints.remove(loc);
|
||||||
|
CacheManager.removeFromCache(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adjustGainPoint(Integer loc, Double adj) {
|
||||||
|
if (gainPoints == null) {
|
||||||
|
gainPoints = new TreeMap<Integer, Double>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Double gp = gainPoints.get(loc);
|
||||||
|
if (gp == null) return;
|
||||||
|
gp += adj;
|
||||||
|
gainPoints.put(loc, gp);
|
||||||
|
CacheManager.removeFromCache(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[] calculateGains() {
|
||||||
|
double[] gains = new double[sampleSize];
|
||||||
|
|
||||||
|
double y = 1.0d;
|
||||||
|
int x1 = 0;
|
||||||
|
|
||||||
|
if (gainPoints == null) {
|
||||||
|
for (int x = 0; x < sampleSize; x++) {
|
||||||
|
gains[x] = 1.0d;
|
||||||
|
}
|
||||||
|
return gains;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Integer loc : gainPoints.keySet()) {
|
||||||
|
int x2 = loc;
|
||||||
|
double y2 = gainPoints.get(loc);
|
||||||
|
|
||||||
|
int range = x2 - x1;
|
||||||
|
double diff = y2 - y;
|
||||||
|
double ystep = diff / (double)range;
|
||||||
|
for (int x = 0; x < range; x++) {
|
||||||
|
y += ystep;
|
||||||
|
gains[x1 + x] = y;
|
||||||
|
}
|
||||||
|
x1 = x2;
|
||||||
|
}
|
||||||
|
for (int x = x1; x < sampleSize; x++) {
|
||||||
|
gains[x] = y;
|
||||||
|
}
|
||||||
|
return gains;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import java.awt.Graphics2D;
|
|||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
public static Image getScaledImage(Image srcImg, int w, int h){
|
public static Image getScaledImage(Image srcImg, int w, int h){
|
||||||
@@ -70,7 +73,7 @@ public class Utils {
|
|||||||
long t = System.currentTimeMillis();
|
long t = System.currentTimeMillis();
|
||||||
long d = t - millis;
|
long d = t - millis;
|
||||||
millis = t;
|
millis = t;
|
||||||
System.err.println(String.format("%10d - %10s : %8d | %8d | %8d", d, tag,
|
Debug.d(String.format("%10d - %10s : %8d | %8d | %8d", d, tag,
|
||||||
Runtime.getRuntime().totalMemory(),
|
Runtime.getRuntime().totalMemory(),
|
||||||
Runtime.getRuntime().maxMemory(),
|
Runtime.getRuntime().maxMemory(),
|
||||||
Runtime.getRuntime().freeMemory()
|
Runtime.getRuntime().freeMemory()
|
||||||
@@ -84,4 +87,49 @@ public class Utils {
|
|||||||
String time = df.format(d);
|
String time = df.format(d);
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double[] stereoToMono(double[][] in) {
|
||||||
|
double[] out = new double[in[Sentence.LEFT].length];
|
||||||
|
|
||||||
|
for (int i = 0; i < in[Sentence.LEFT].length; i++) {
|
||||||
|
out[i] = mix(in[Sentence.LEFT][i], in[Sentence.RIGHT][i]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double mix(double a, double b) {
|
||||||
|
double out;
|
||||||
|
|
||||||
|
if ((a < 0) && (b < 0)) {
|
||||||
|
out = (a + b) - (a * b);
|
||||||
|
} else if ((a > 0) && (b > 0)) {
|
||||||
|
out = (a + b) - (a * b);
|
||||||
|
} else {
|
||||||
|
out = a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyFolder(File from, File to) throws IOException {
|
||||||
|
File[] files = from.listFiles();
|
||||||
|
|
||||||
|
for (File source : files) {
|
||||||
|
File destination = new File(to, source.getName());
|
||||||
|
if (destination.exists()) {
|
||||||
|
if (!destination.isDirectory()) {
|
||||||
|
destination.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (source.isDirectory()) {
|
||||||
|
if (!destination.exists()) {
|
||||||
|
destination.mkdirs();
|
||||||
|
}
|
||||||
|
copyFolder(source, destination);
|
||||||
|
} else {
|
||||||
|
Files.copy(source.toPath(), destination.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ public class VersionChecker implements Runnable {
|
|||||||
|
|
||||||
if (Utils.s2i(installedVersion) >= Utils.s2i(availableVersion)) return;
|
if (Utils.s2i(installedVersion) >= Utils.s2i(availableVersion)) return;
|
||||||
|
|
||||||
System.err.println("Version installed: " + installed);
|
|
||||||
System.err.println("Version available: " + available);
|
|
||||||
System.err.println("URL: " + website);
|
|
||||||
|
|
||||||
JButton upgrade = new JButton("A new version is available.");
|
JButton upgrade = new JButton("A new version is available.");
|
||||||
upgrade.addActionListener(new ActionListener() {
|
upgrade.addActionListener(new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
|
|||||||
@@ -2,17 +2,20 @@ package uk.co.majenko.audiobookrecorder;
|
|||||||
|
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.awt.event.MouseMotionListener;
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.awt.event.MouseWheelListener;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Cursor;
|
import java.awt.Cursor;
|
||||||
|
|
||||||
public class Waveform extends JPanel implements MouseListener, MouseMotionListener {
|
public class Waveform extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener {
|
||||||
|
|
||||||
double[][] samples = null;
|
Sentence sentence = null;
|
||||||
|
|
||||||
int leftMarker = 0;
|
int leftMarker = 0;
|
||||||
int rightMarker = 0;
|
int rightMarker = 0;
|
||||||
@@ -29,6 +32,8 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
|
|||||||
boolean displayCut = false;
|
boolean displayCut = false;
|
||||||
boolean displaySplit = false;
|
boolean displaySplit = false;
|
||||||
|
|
||||||
|
boolean displayGainCurve = false;
|
||||||
|
|
||||||
int dragging = 0;
|
int dragging = 0;
|
||||||
|
|
||||||
int step = 1;
|
int step = 1;
|
||||||
@@ -39,19 +44,29 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
|
|||||||
|
|
||||||
String loadedId = null;
|
String loadedId = null;
|
||||||
|
|
||||||
ArrayList<MarkerDragListener> markerDragListeners;
|
|
||||||
|
|
||||||
public Waveform() {
|
public Waveform() {
|
||||||
super();
|
super();
|
||||||
addMouseListener(this);
|
addMouseListener(this);
|
||||||
addMouseMotionListener(this);
|
addMouseMotionListener(this);
|
||||||
markerDragListeners = new ArrayList<MarkerDragListener>();
|
addMouseWheelListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMarkerDragListener(MarkerDragListener l) {
|
public void setSentence(Sentence s) {
|
||||||
if (markerDragListeners.indexOf(l) == -1) {
|
sentence = s;
|
||||||
markerDragListeners.add(l);
|
playMarker = 0;
|
||||||
|
displayCut = false;
|
||||||
|
displaySplit = false;
|
||||||
|
updateMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateMarkers() {
|
||||||
|
if (sentence != null) {
|
||||||
|
leftMarker = sentence.getStartOffset();
|
||||||
|
rightMarker = sentence.getEndOffset();
|
||||||
|
leftAltMarker = sentence.getStartCrossing();
|
||||||
|
rightAltMarker = sentence.getEndCrossing();
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics g) {
|
||||||
@@ -82,7 +97,8 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
|
|||||||
|
|
||||||
double scale = (h/2);
|
double scale = (h/2);
|
||||||
|
|
||||||
if (samples != null) {
|
if (sentence != null) {
|
||||||
|
double[][] samples = sentence.getDoubleAudioData(true);
|
||||||
|
|
||||||
int num = samples[Sentence.LEFT].length;
|
int num = samples[Sentence.LEFT].length;
|
||||||
step = num / zoomFactor / w;
|
step = num / zoomFactor / w;
|
||||||
@@ -173,51 +189,30 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
|
|||||||
for (int i = 0; i < h; i += 2) {
|
for (int i = 0; i < h; i += 2) {
|
||||||
g.drawLine((playMarker - offset) / step, i, (playMarker - offset) / step, i);
|
g.drawLine((playMarker - offset) / step, i, (playMarker - offset) / step, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (displayGainCurve) {
|
||||||
|
int x1 = 0;
|
||||||
|
double y1 = 1.0;
|
||||||
|
g.setColor(new Color(200, 200, 200));
|
||||||
|
TreeMap<Integer, Double> points = sentence.getGainPoints();
|
||||||
|
for (Integer loc : points.keySet()) {
|
||||||
|
int x2 = loc;
|
||||||
|
double y2 = points.get(loc);
|
||||||
|
|
||||||
|
g.fillRect((x1 - offset) / step - 1, h - (int)((double)h / 2.0 * y1) - 1, 3, 3);
|
||||||
|
|
||||||
|
g.drawLine((x1 - offset) / step, h - (int)((double)h / 2.0 * y1), (x2 - offset) / step, h - (int)((double)h / 2.0 * y2));
|
||||||
|
x1 = x2;
|
||||||
|
y1 = y2;
|
||||||
|
}
|
||||||
|
g.fillRect((x1 - offset) / step - 1, h - (int)((double)h / 2.0 * y1) - 1, 3, 3);
|
||||||
|
g.drawLine((x1 - offset) / step, h - (int)((double)h / 2.0 * y1), (num - offset) / step, h - (int)((double)h / 2.0 * y1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAltMarkers(int l, int r) {
|
public void setDisplayGainCurve(boolean b) {
|
||||||
leftAltMarker = l;
|
displayGainCurve = b;
|
||||||
rightAltMarker = r;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMarkers(int l, int r) {
|
|
||||||
leftMarker = l;
|
|
||||||
rightMarker = r;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLeftAltMarker(int l) {
|
|
||||||
leftAltMarker = l;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRightAltMarker(int r) {
|
|
||||||
rightAltMarker = 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(double[][] s) {
|
|
||||||
samples = s;
|
|
||||||
playMarker = 0;
|
|
||||||
displayCut = false;
|
|
||||||
displaySplit = false;
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,24 +259,103 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
|
|||||||
|
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
if (dragging == 1) {
|
if (dragging == 1) {
|
||||||
MarkerDragEvent evt = new MarkerDragEvent(this, leftMarker);
|
sentence.setStartOffset(leftMarker);
|
||||||
for (MarkerDragListener l : markerDragListeners) {
|
sentence.updateCrossings();
|
||||||
l.leftMarkerMoved(evt);
|
updateMarkers();
|
||||||
}
|
|
||||||
} else if (dragging == 2) {
|
} else if (dragging == 2) {
|
||||||
MarkerDragEvent evt = new MarkerDragEvent(this, rightMarker);
|
sentence.setEndOffset(rightMarker);
|
||||||
for (MarkerDragListener l : markerDragListeners) {
|
sentence.updateCrossings();
|
||||||
l.rightMarkerMoved(evt);
|
updateMarkers();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dragging = 0;
|
dragging = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||||
|
if (sentence == null) return;
|
||||||
|
if (sentence.isLocked()) return;
|
||||||
|
|
||||||
|
if (displayGainCurve) {
|
||||||
|
int x = e.getX() * step + offset;
|
||||||
|
int f = -1;
|
||||||
|
int diff = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
TreeMap<Integer, Double> gc = sentence.getGainPoints();
|
||||||
|
for (Integer loc : gc.keySet()) {
|
||||||
|
int d = Math.abs(loc - x);
|
||||||
|
if (d < diff) {
|
||||||
|
diff = d;
|
||||||
|
f = loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff / step < 5) {
|
||||||
|
sentence.adjustGainPoint(f, (0 - e.getWheelRotation()) / 100d);
|
||||||
|
repaint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int val = ((int)AudiobookRecorder.window.gainPercent.getValue()) - e.getWheelRotation();
|
||||||
|
if (val < 1) val = 1;
|
||||||
|
AudiobookRecorder.window.gainPercent.setValue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (displayGainCurve) {
|
||||||
|
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||||
|
Dimension size = getSize();
|
||||||
|
|
||||||
|
int w = size.width;
|
||||||
|
int h = size.height;
|
||||||
|
|
||||||
|
int x = e.getX() * step + offset;
|
||||||
|
double y = (double)(h - e.getY()) / (double)h * 2.0;
|
||||||
|
|
||||||
|
sentence.addGainPoint(x, y);
|
||||||
|
repaint();
|
||||||
|
} else if (e.getButton() == MouseEvent.BUTTON2) {
|
||||||
|
Dimension size = getSize();
|
||||||
|
|
||||||
|
int w = size.width;
|
||||||
|
int h = size.height;
|
||||||
|
|
||||||
|
int x = e.getX() * step + offset;
|
||||||
|
double y = 1.0d;
|
||||||
|
|
||||||
|
sentence.addGainPoint(x, y);
|
||||||
|
repaint();
|
||||||
|
} else if (e.getButton() == MouseEvent.BUTTON3) {
|
||||||
|
int x = e.getX() * step + offset;
|
||||||
|
int f = -1;
|
||||||
|
int diff = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
TreeMap<Integer, Double> gc = sentence.getGainPoints();
|
||||||
|
for (Integer loc : gc.keySet()) {
|
||||||
|
int d = Math.abs(loc - x);
|
||||||
|
if (d < diff) {
|
||||||
|
diff = d;
|
||||||
|
f = loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sentence.removeGainPoint(f);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mouseMoved(MouseEvent e) {
|
public void mouseMoved(MouseEvent e) {
|
||||||
int x = e.getX();
|
int x = e.getX();
|
||||||
|
int y = e.getY();
|
||||||
|
|
||||||
|
if (displayGainCurve) {
|
||||||
|
|
||||||
|
} else if (displayCut) {
|
||||||
|
if ((x >= ((cutExit - offset)/step) - 10) && (x <= ((cutExit - offset)/step) + 10)) {
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if ((x >= ((leftMarker - offset)/step) - 10) && (x <= ((leftMarker - offset)/step) + 10)) {
|
if ((x >= ((leftMarker - offset)/step) - 10) && (x <= ((leftMarker - offset)/step) + 10)) {
|
||||||
setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
|
setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
|
||||||
return;
|
return;
|
||||||
@@ -296,14 +370,7 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayCut) {
|
|
||||||
if ((x >= ((cutExit - offset)/step) - 10) && (x <= ((cutExit - offset)/step) + 10)) {
|
|
||||||
setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user