Compare commits

...

19 Commits

Author SHA1 Message Date
c34f909b21 Released checkpoint version 2018-09-26 15:40:05 +01:00
955227e9e3 Complete overhaul of recording to allow better recording from microphones that require a warmup and self-calibration period 2018-09-26 14:26:44 +01:00
e626da0c5e Revamped equaliser and added to book settings 2018-09-25 16:57:51 +01:00
6adb22b9ac Added curve smoothing button to EQ 2018-09-24 18:53:10 +01:00
a51af45e50 Released 0.0.7 checkpoint 2018-09-24 15:57:11 +01:00
2a52f8ccfb Added audio EQ 2018-09-24 15:47:51 +01:00
36c8cc0a26 Improved export facility and added export single chapter 2018-09-24 10:27:25 +01:00
c9bada7721 Added progress dialog while exporting #14 2018-09-23 17:51:54 +01:00
3036ef801b Add scroll sentence into view when playing all #9 2018-09-23 15:45:35 +01:00
c591025b4f Add lock and unlock all #15 2018-09-23 15:42:16 +01:00
ae22c8c86d Added Linux exe stub 2018-09-17 10:12:34 +01:00
eade8ef47d Various cleanups as recommended by FindBug 2018-09-16 20:58:07 +01:00
c74de99835 Included os name in exe filenames 2018-09-16 20:57:34 +01:00
1c056c9e7b Added zero crossing detection #7 2018-09-16 15:34:49 +01:00
43d80c00d9 Added sentence cache system 2018-09-16 13:55:19 +01:00
9fb3d1f816 Merge chapters (#1) 2018-09-16 12:20:48 +01:00
9ee4acfc8d Moved todo into github issues 2018-09-15 22:37:52 +01:00
7f4303df40 Added alert to todo 2018-09-15 22:30:07 +01:00
8170f3c905 Added file chooser boxes for options 2018-09-15 19:16:08 +01:00
40 changed files with 3134 additions and 387 deletions

5
.gitignore vendored
View File

@@ -2,5 +2,6 @@
._*
bin
AudiobookRecorder.jar
AudiobookRecorder.dmg
AudiobookRecorder.exe
AudiobookRecorder-linux
AudiobookRecorder-osx.dmg
AudiobookRecorder-win.exe

View File

@@ -35,3 +35,4 @@ Third party software
* JAVE ffmpeg wrapper: http://www.sauronsoftware.it/projects/jave/
* JTattoo: http://www.sauronsoftware.it/projects/jave/
* Icons from, or based on, Oxygen: https://github.com/KDE/oxygen-icons
* JEQ, the Java Equaliser: https://sourceforge.net/projects/jeq/

View File

@@ -1,9 +0,0 @@
ToDo
====
* Merge chapters
* Identify and export 5 minute retail sample
* Identify and export 15 minute checkpoint
* Archive (Zip and delete) audiobooks
* File path browser buttons in options
* Help menu with "About" window

View File

@@ -51,7 +51,7 @@
<target name="macapp" depends="build">
<mkdir dir="tmp"/>
<mkdir dir="tmp"/>
<jarbundler
name="AudiobookRecorder"
@@ -77,7 +77,7 @@
<arg value="-r" />
<arg value="-apple" />
<arg value="-o" />
<arg value="AudiobookRecorder.dmg" />
<arg value="AudiobookRecorder-osx.dmg" />
<arg value="-dir-mode" />
<arg value="0755" />
<arg value="-file-mode" />
@@ -89,10 +89,18 @@
</target>
<target name="winapp" depends="build">
<launch4j configFile="dist/windows/windows.xml" outfile="AudiobookRecorder.exe" />
<launch4j configFile="dist/windows/windows.xml" outfile="AudiobookRecorder-win.exe" />
</target>
<target name="dist" depends="macapp,winapp" />
<target name="linuxapp" depends="build">
<concat destfile="AudiobookRecorder-linux" binary="true">
<file file="dist/linux/stub" />
<file file="AudiobookRecorder.jar" />
</concat>
<chmod perm="0755" file="AudiobookRecorder-linux" />
</target>
<target name="dist" depends="macapp,winapp,linuxapp" />
<target name="tag">
<exec executable="git">
@@ -130,23 +138,32 @@
<arg value="-f"/> <arg value="AudiobookRecorder.jar"/>
<arg value="-n"/> <arg value="AudiobookRecorder.jar"/>
</exec>
<echo>Uploading AudiobookRecorder.dmg</echo>
<echo>Uploading AudiobookRecorder-osx.dmg</echo>
<exec executable="github-release">
<arg value="upload"/>
<arg value="-u"/> <arg value="MajenkoProjects"/>
<arg value="-r"/> <arg value="AudiobookRecorder"/>
<arg value="-t"/> <arg value="v${version}"/>
<arg value="-f"/> <arg value="AudiobookRecorder.dmg"/>
<arg value="-n"/> <arg value="AudiobookRecorder.dmg"/>
<arg value="-f"/> <arg value="AudiobookRecorder-osx.dmg"/>
<arg value="-n"/> <arg value="AudiobookRecorder-osx.dmg"/>
</exec>
<echo>Uploading AudiobookRecorder.exe</echo>
<echo>Uploading AudiobookRecorder-win.exe</echo>
<exec executable="github-release">
<arg value="upload"/>
<arg value="-u"/> <arg value="MajenkoProjects"/>
<arg value="-r"/> <arg value="AudiobookRecorder"/>
<arg value="-t"/> <arg value="v${version}"/>
<arg value="-f"/> <arg value="AudiobookRecorder.exe"/>
<arg value="-n"/> <arg value="AudiobookRecorder.exe"/>
<arg value="-f"/> <arg value="AudiobookRecorder-win.exe"/>
<arg value="-n"/> <arg value="AudiobookRecorder-win.exe"/>
</exec>
<echo>Uploading AudiobookRecorder-linux</echo>
<exec executable="github-release">
<arg value="upload"/>
<arg value="-u"/> <arg value="MajenkoProjects"/>
<arg value="-r"/> <arg value="AudiobookRecorder"/>
<arg value="-t"/> <arg value="v${version}"/>
<arg value="-f"/> <arg value="AudiobookRecorder-linux"/>
<arg value="-n"/> <arg value="AudiobookRecorder-linux"/>
</exec>
</target>

10
dist/linux/stub vendored Normal file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
MYSELF=`which "$0" 2>/dev/null`
[ $? -gt 0 -a -f "$0" ] && MYSELF="./$0"
java=java
if test -n "$JAVA_HOME"; then
java="$JAVA_HOME/bin/java"
fi
java_args=-Xmx1g
exec "$java" $java_args -jar $MYSELF "$@"
exit 1

View File

@@ -1 +1 @@
version=0.0.6
version=0.0.8

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,445 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.core;
import java.io.IOException;
import java.io.InputStream;
/**
* The EqualizerInputStream class
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class EqualizerInputStream extends InputStream {
private InputStream stream;
private IIR iir;
private final static int BUFFER_SIZE = 65536;
private byte[] inbuf = new byte[BUFFER_SIZE];
private int[] workbuf = new int[BUFFER_SIZE];
private byte[] outbuf = new byte[BUFFER_SIZE];
private int inpos = 0;
private int inlen = 0;
private int outpos = 0;
private int outlen = 0;
private boolean signed;
private int samplesize;
private boolean bigendian;
/**
* Constructs new EqualizerInputStream object
*
* @param stream is an input stream for pcm data
* @param samplerate is a sample rate of input data
* @param channels is the number of channels
* @param signed indicates that the data is signed
* @param samplesize is the sample bit size of data
* @param bigendian indicates that the dat is in "big endian" encoding
* @param bands is the number of bands
*/
public EqualizerInputStream(InputStream stream, float samplerate, int channels, boolean signed, int samplesize, boolean bigendian, int bands) {
this.stream = stream;
this.iir = new IIR(bands, samplerate, channels);
this.signed = signed;
this.samplesize = samplesize;
this.bigendian = bigendian;
if (!isParamsSupported(samplerate, channels, samplesize, bands))
throw new IllegalArgumentException("Unsupported sample bit size");
}
/**
* Returns Controls of equalizer
*
* @return Controls of equalizer
*/
public IIRControls getControls() {
return iir.getControls();
}
/**
* This is special method for checking of supported parameters of equalizer
*
* @param bands is the number of bands
* @param samplerate is the sample rate of data
* @param channels is the number of channels
* @param samplesize is the size of sample in bits
* @return true if parameters are supported
*/
public static boolean isParamsSupported(float samplerate, int channels, int samplesize, int bands) {
switch (samplesize) {
case 8:
case 16:
case 24:
break;
default:
return false;
}
return IIR.isParamsSupported(bands, samplerate, channels);
}
private boolean fillInBuffer() throws IOException {
if (inpos != 0 && inlen > 0)
System.arraycopy(inbuf, inpos, inbuf, 0, inlen);
inpos = 0;
int num;
boolean eof = false;
while (inlen != inbuf.length) {
num = stream.read(inbuf, inlen, inbuf.length - inlen);
if (num < 0) {
eof = true;
break;
}
inlen += num;
}
return eof;
}
private void fillOutBuffer() {
if (outpos != 0 && outlen > 0)
System.arraycopy(outbuf, outpos, outbuf, 0, outlen);
outpos = 0;
int len = outbuf.length - outlen;
len = inlen < len ? inlen : len;
len = convertToInt(len);
if (len > 0) {
iir.iir(workbuf, len);
len = convertToByte(outbuf, outlen, len);
outlen += len;
}
}
private int convertToInt(int length) {
int l = length;
int temp;
byte a1[];
switch (samplesize) {
case 8: {
if (length > 0) {
System.arraycopy(inbuf, 0, workbuf, 0, length);
inpos += length;
inlen -= length;
}
break;
}
case 16: {
l = length >> 1;
if (l > 0) {
if (bigendian)
for (int i = 0; i < l; i++) {
temp = (((a1 = inbuf)[inpos++] & 0xff) << 8) | (a1[inpos++] & 0xff);
workbuf[i] = signed && temp > 32767 ? temp - 65536 : temp;
}
else
for (int i = 0; i < l; i++) {
temp = ((a1 = inbuf)[inpos++] & 0xff) | ((a1[inpos++] & 0xff) << 8);
workbuf[i] = signed && temp > 32767 ? temp - 65536 : temp;
}
inlen -= inpos;
}
break;
}
case 24: {
l = length / 3;
if (l > 0) {
if (bigendian)
for (int i = 0; i < l; i++) {
temp = ((a1 = inbuf)[inpos++] & 0xff) | ((a1[inpos++] & 0xff) << 8) | ((a1[inpos++] & 0xff) << 16);
workbuf[i] = signed && temp > 8388607 ? temp - 16777216 : temp;
}
else
for (int i = 0; i < l; i++) {
temp = (((a1 = inbuf)[inpos++] & 0xff) << 16) | ((a1[inpos++] & 0xff) << 8) | (a1[inpos++] & 0xff);
workbuf[i] = signed && temp > 8388607 ? temp - 16777216 : temp;
}
inlen -= inpos;
}
break;
}
}
return l;
}
private int wrap8Bit(int data) {
if (data > 127)
data = 127;
else if (data < -128)
data = -128;
if (data < 0)
data += 256;
return data;
}
private int wrap16Bit(int data) {
if (data > 32767)
data = 32767;
else if (data < -32768)
data = -32768;
if (data < 0)
data += 65536;
return data;
}
private int wrap24Bit(int data) {
if (data > 8388607)
data = 8388607;
else if (data < -8388608)
data = -8388608;
if (data < 0)
data += 16777216;
return data;
}
private int convertToByte(byte[] b, int off, int length) {
int p = off;
int d;
switch (samplesize) {
case 8: {
for (int i = 0; i < length; i++)
b[p++] = (byte) (wrap8Bit(workbuf[i]) & 0xff);
break;
}
case 16: {
if (bigendian) {
for (int i = 0; i < length; i++) {
d = wrap16Bit(workbuf[i]);
b[p++] = (byte) ((d & 0xff00) >> 8);
b[p++] = (byte) (d & 0xff);
}
} else {
for (int i = 0; i < length; i++) {
d = wrap16Bit(workbuf[i]);
b[p++] = (byte) (d & 0xff);
b[p++] = (byte) ((d & 0xff00) >> 8);
}
}
break;
}
case 24: {
if (bigendian) {
for (int i = 0; i < length; i++) {
d = wrap24Bit(workbuf[i]);
b[p++] = (byte) (d & 0xff);
b[p++] = (byte) ((d & 0xff00) >> 8);
b[p++] = (byte) ((d & 0xff0000) >> 16);
}
} else {
for (int i = 0; i < length; i++) {
d = wrap24Bit(workbuf[i]);
b[p++] = (byte) ((d & 0xff0000) >> 16);
b[p++] = (byte) ((d & 0xff00) >> 8);
b[p++] = (byte) (d & 0xff);
}
}
break;
}
}
return p - off;
}
/**
* Returns the number of bytes that can be read (or skipped over) from
* this input stream without blocking by the next caller of a method for
* this input stream. The next caller might be the same thread or
* another thread.
*
* @return the number of bytes that can be read from this input stream
* without blocking.
* @throws IOException if an I/O error occurs.
*/
public int available() throws IOException {
return outlen;
}
/**
* Closes this input stream and releases any system resources associated
* with the stream.
*
* @throws IOException if an I/O error occurs.
*/
public void close() throws IOException {
stream.close();
}
/**
* <p> The <code>mark</code> method of <code>EqualizerInputStream</code> does
* nothing.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
*/
public synchronized void mark(int readlimit) {
}
/**
* Tests if this input stream supports the <code>mark</code> and
* <code>reset</code> methods. Whether or not <code>mark</code> and
* <code>reset</code> are supported is an invariant property of a
* particular input stream instance. The <code>markSupported</code> method
* of <code>EqualizerInputStream</code> returns <code>false</code>.
*
* @return false
*/
public boolean markSupported() {
return false;
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @throws IOException if an I/O error occurs.
*/
public int read() throws IOException {
if (outlen == 0) {
boolean eof = fillInBuffer();
fillOutBuffer();
if (outlen == 0 && eof)
return -1;
if (outlen == 0 && !eof)
throw new IOException("Impossible state");
}
int b = outbuf[outpos++];
outlen--;
return b;
}
/**
* Reads up to <code>len</code> bytes of data from the input stream into
* an array of bytes. An attempt is made to read as many as
* <code>len</code> bytes, but a smaller number may be read.
* The number of bytes actually read is returned as an integer.
* <p/>
* <p> This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
* <p/>
* <p> If <code>b</code> is <code>null</code>, a
* <code>NullPointerException</code> is thrown.
* <p/>
* <p> If <code>off</code> is negative, or <code>len</code> is negative, or
* <code>off+len</code> is greater than the length of the array
* <code>b</code>, then an <code>IndexOutOfBoundsException</code> is
* thrown.
* <p/>
* <p> If <code>len</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at end of
* file, the value <code>-1</code> is returned; otherwise, at least one
* byte is read and stored into <code>b</code>.
* <p/>
* <p> The first byte read is stored into element <code>b[off]</code>, the
* next one into <code>b[off+1]</code>, and so on. The number of bytes read
* is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
* bytes actually read; these bytes will be stored in elements
* <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[off+</code><i>k</i><code>]</code> through
* <code>b[off+len-1]</code> unaffected.
* <p/>
* <p> In every case, elements <code>b[0]</code> through
* <code>b[off]</code> and elements <code>b[off+len]</code> through
* <code>b[b.length-1]</code> are unaffected.
* <p/>
* <p> If the first byte cannot be read for any reason other than end of
* file, then an <code>IOException</code> is thrown. In particular, an
* <code>IOException</code> is thrown if the input stream has been closed.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if <code>b</code> is <code>null</code>.
*/
public int read(byte[] b, int off, int len) throws IOException {
if (outlen < len) {
boolean eof = fillInBuffer();
fillOutBuffer();
if (outlen == 0 && eof)
return -1;
if (outlen == 0 && !eof)
throw new IOException("Impossible state");
}
len = outlen < len ? outlen : len;
if (len > 0) {
System.arraycopy(outbuf, outpos, b, off, len);
outpos += len;
outlen -= len;
}
return len;
}
/**
* <p>The method <code>reset</code> for class <code>EqualizerInputStream</code>
* does nothing except throw an <code>IOException</code>.
*
* @throws IOException as an indication that the mark feature doesn't supported by EqualizerInputStream.
*/
public void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
/**
* Skips over and discards <code>n</code> bytes of data from this input
* stream. The <code>skip</code> method may, for a variety of reasons, end
* up skipping over some smaller number of bytes, possibly <code>0</code>.
* This may result from any of a number of conditions; reaching end of file
* before <code>n</code> bytes have been skipped is only one possibility.
* The actual number of bytes skipped is returned. If <code>n</code> is
* negative, no bytes are skipped.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @throws IOException if an I/O error occurs.
*/
public long skip(long n) throws IOException {
int l;
if (n <= outlen) {
outpos += n;
outlen -= n;
return n;
}
n -= outlen;
l = outlen;
outlen = 0;
outpos = 0;
if (n <= inlen) {
inpos += n;
inlen -= n;
return l + n;
}
n -= inlen;
l += inlen;
inlen = 0;
inpos = 0;
return stream.skip(n) + l;
}
}

View File

@@ -0,0 +1,285 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.core;
/**
* Generic wrapper around IIR algorithm.
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class IIR extends IIRBase {
/**
* Max number of channels supported
*/
public final static int EQ_MAX_CHANNELS = 2;
/**
* Max bands supported by the code
*/
public final static int EQ_MAX_BANDS = 31;
/**
* Supported sample rates
*/
public final static float EQ_11025_RATE = 11025;
public final static float EQ_22050_RATE = 22050;
public final static float EQ_44100_RATE = 44100;
public final static float EQ_48000_RATE = 48000;
/**
* Supported number of bands
*/
public final static int EQ_10_BANDS = 10;
public final static int EQ_15_BANDS = 15;
public final static int EQ_25_BANDS = 25;
public final static int EQ_31_BANDS = 31;
/* Indexes for the history arrays
* These have to be kept between calls to this function
* hence they are static */
private int i;
private int j;
private int k;
/* History for two filters */
private XYData[][] dataHistory = new XYData[EQ_MAX_BANDS][EQ_MAX_CHANNELS];
private XYData[][] dataHistory2 = new XYData[EQ_MAX_BANDS][EQ_MAX_CHANNELS];
/* Coefficients */
private IIRCoefficients[] iircf;
/* Equalizer config */
private IIRControls eqcfg;
/* rate */
private float rate;
/* channels */
private int channels;
/* bands */
private int bands;
/**
* Constructs equalizer with given config
*
* @param bands is the number of bands to be used
* @param rate is the sample rate of equalizer
* @param channels is the number of channels
*/
public IIR(int bands, float rate, int channels) {
this.rate = rate;
this.channels = channels;
this.bands = bands;
this.eqcfg = new IIRControls(bands, channels);
if (!isParamsSupported(bands, rate, channels))
throw new IllegalArgumentException("Unsupported parameters");
initIIR();
}
/**
* Returns Controls of equalizer
*
* @return Controls of equalizer
*/
public IIRControls getControls() {
return eqcfg;
}
/**
* This is special method for checking of supported parameters of equalizer
*
* @param bands is the number of bands
* @param rate is the sample rate of data
* @param channels is the number of channels
* @return true if parameters are supported
*/
public static boolean isParamsSupported(int bands, float rate, int channels) {
if (rate != EQ_11025_RATE && rate != EQ_22050_RATE && rate != EQ_44100_RATE && rate != EQ_48000_RATE)
return false;
switch (bands) {
case EQ_10_BANDS:
case EQ_15_BANDS:
case EQ_25_BANDS:
case EQ_31_BANDS:
break;
default:
return false;
}
switch (channels) {
case 1:
case 2:
break;
default:
return false;
}
return (rate != EQ_11025_RATE && rate != EQ_22050_RATE) || bands == EQ_10_BANDS;
}
/* Init the filters */
private void initIIR() {
setFilters();
for (int ii = 0; ii < EQ_MAX_BANDS; ii++)
for (int jj = 0; jj < EQ_MAX_CHANNELS; jj++) {
dataHistory[ii][jj] = new XYData();
dataHistory2[ii][jj] = new XYData();
}
i = 0;
j = 2;
k = 1;
}
private void setFilters() {
if (rate == EQ_11025_RATE)
iircf = iir_cf10_11k_11025;
else if (rate == EQ_22050_RATE)
iircf = iir_cf10_22k_22050;
else if (rate == EQ_44100_RATE) {
switch (bands) {
case 31:
iircf = iir_cf31_44100;
break;
case 25:
iircf = iir_cf25_44100;
break;
case 15:
iircf = iir_cf15_44100;
break;
default:
iircf = iir_cf10_44100;
break;
}
} else if (rate == EQ_48000_RATE) {
switch (bands) {
case 31:
iircf = iir_cf31_48000;
break;
case 25:
iircf = iir_cf25_48000;
break;
case 15:
iircf = iir_cf15_48000;
break;
default:
iircf = iir_cf10_48000;
break;
}
}
}
/**
* Clear filter history.
*/
public void cleanHistory() {
/* Zero the history arrays */
for (int ii = 0; ii < EQ_MAX_BANDS; ii++)
for (int jj = 0; jj < EQ_MAX_CHANNELS; jj++) {
dataHistory[ii][jj].zero();
dataHistory2[ii][jj].zero();
}
i = 0;
j = 2;
k = 1;
}
/**
* Main filtering method.
*
* @param data - data to be filtered
* @param length - length of data in buffer
*/
public void iir(int[] data, int length) {
int index, band, channel;
float eqpreamp[] = eqcfg.getPreamp();
float eqbands[][] = eqcfg.getBands();
double pcm, out;
/**
* IIR filter equation is
* y[n] = 2 * (alpha*(x[n]-x[n-2]) + gamma*y[n-1] - beta*y[n-2])
*
* NOTE: The 2 factor was introduced in the coefficients to save
* a multiplication
*
* This algorithm cascades two filters to get nice filtering
* at the expense of extra CPU cycles
*/
IIRCoefficients tempcf;
XYData tempd;
for (index = 0; index < length; index += channels) {
/* For each channel */
for (channel = 0; channel < channels; channel++) {
/* Preamp gain */
pcm = data[index + channel] * eqpreamp[channel];
out = 0f;
/* For each band */
for (band = 0; band < bands; band++) {
/* Store Xi(n) */
tempd = dataHistory[band][channel];
tempd.x[i] = pcm;
/* Calculate and store Yi(n) */
tempcf = iircf[band];
tempd.y[i] =
(
/* = alpha * [x(n)-x(n-2)] */
tempcf.alpha * (pcm - tempd.x[k])
/* + gamma * y(n-1) */
+ tempcf.gamma * tempd.y[j]
/* - beta * y(n-2) */
- tempcf.beta * tempd.y[k]
);
/*
* The multiplication by 2.0 was 'moved' into the coefficients to save
* CPU cycles here */
/* Apply the gain */
out += (tempd.y[i] * eqbands[band][channel]); // * 2.0;
} /* For each band */
/* Volume stuff
Scale down original PCM sample and add it to the filters
output. This substitutes the multiplication by 0.25
Go back to use the floating point multiplication before the
conversion to give more dynamic range
*/
out += (pcm * 0.25);
/* Normalize the output */
out *= 4;
/* Round and convert to integer */
data[index + channel] = (int) out;
} /* For each channel */
i++;
j++;
k++;
/* Wrap around the indexes */
if (i == 3)
i = 0;
else if (j == 3)
j = 0;
else
k = 0;
}/* For each pair of samples */
}
}

View File

@@ -0,0 +1,413 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.core;
/**
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class IIRBase {
/* BETA, ALPHA, GAMMA */
public final static IIRCoefficients iir_cf10_11k_11025[] = {
/* 31 Hz*/
new IIRCoefficients(9.8758524689e-01, 6.2073765555e-03, 1.9872750693e+00),
/* 62 Hz*/
new IIRCoefficients(9.7532461998e-01, 1.2337690008e-02, 1.9740916593e+00),
/* 125 Hz*/
new IIRCoefficients(9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00),
/* 250 Hz*/
new IIRCoefficients(9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00),
/* 500 Hz*/
new IIRCoefficients(8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00),
/* 1k Hz*/
new IIRCoefficients(6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00),
/* 2k Hz*/
new IIRCoefficients(4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01),
/* 3k Hz*/
new IIRCoefficients(3.1012671838e-01, 3.4493664081e-01, -1.8141012760e-01),
/* 4k Hz*/
new IIRCoefficients(2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01),
/* 5.5k Hz*/
new IIRCoefficients(3.3453245058e-01, 3.3273377471e-01, -1.3344985880e+00)
};
public final static IIRCoefficients iir_cf10_22k_22050[] = {
/* 31 Hz*/
new IIRCoefficients(9.9377323686e-01, 3.1133815717e-03, 1.9936954495e+00),
/* 62 Hz*/
new IIRCoefficients(9.8758524689e-01, 6.2073765555e-03, 1.9872750693e+00),
/* 125 Hz*/
new IIRCoefficients(9.7512812040e-01, 1.2435939802e-02, 1.9738753198e+00),
/* 250 Hz*/
new IIRCoefficients(9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00),
/* 500 Hz*/
new IIRCoefficients(9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00),
/* 1k Hz*/
new IIRCoefficients(8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00),
/* 2k Hz*/
new IIRCoefficients(6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00),
/* 4k Hz*/
new IIRCoefficients(4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01),
/* 8k Hz*/
new IIRCoefficients(2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01),
/* 11k Hz*/
new IIRCoefficients(3.3453245058e-01, 3.3273377471e-01, -1.3344985880e+00)
};
public final static IIRCoefficients iir_cf10_44100[] = {
/* 31 Hz*/
new IIRCoefficients(9.9688176273e-01, 1.5591186337e-03, 1.9968622855e+00),
/* 62 Hz*/
new IIRCoefficients(9.9377323686e-01, 3.1133815717e-03, 1.9936954495e+00),
/* 125 Hz*/
new IIRCoefficients(9.8748575691e-01, 6.2571215431e-03, 1.9871705722e+00),
/* 250 Hz*/
new IIRCoefficients(9.7512812040e-01, 1.2435939802e-02, 1.9738753198e+00),
/* 500 Hz*/
new IIRCoefficients(9.5087485437e-01, 2.4562572817e-02, 1.9459267562e+00),
/* 1k Hz*/
new IIRCoefficients(9.0416308662e-01, 4.7918456688e-02, 1.8848691023e+00),
/* 2k Hz*/
new IIRCoefficients(8.1751373987e-01, 9.1243130064e-02, 1.7442229115e+00),
/* 4k Hz*/
new IIRCoefficients(6.6840529852e-01, 1.6579735074e-01, 1.4047189863e+00),
/* 8k Hz*/
new IIRCoefficients(4.4858358977e-01, 2.7570820511e-01, 6.0517475334e-01),
/* 16k Hz*/
new IIRCoefficients(2.4198119087e-01, 3.7900940457e-01, -8.0845085113e-01)
};
public final static IIRCoefficients iir_cf10_48000[] = {
/* 31 Hz*/
new IIRCoefficients(9.9713475915e-01, 1.4326204244e-03, 1.9971183163e+00),
/* 62 Hz*/
new IIRCoefficients(9.9427771143e-01, 2.8611442874e-03, 1.9942120343e+00),
/* 125 Hz*/
new IIRCoefficients(9.8849666727e-01, 5.7516663664e-03, 1.9882304829e+00),
/* 250 Hz*/
new IIRCoefficients(9.7712566171e-01, 1.1437169144e-02, 1.9760670839e+00),
/* 500 Hz*/
new IIRCoefficients(9.5477456091e-01, 2.2612719547e-02, 1.9505892385e+00),
/* 1k Hz*/
new IIRCoefficients(9.1159452679e-01, 4.4202736607e-02, 1.8952405706e+00),
/* 2k Hz*/
new IIRCoefficients(8.3100647694e-01, 8.4496761532e-02, 1.7686164442e+00),
/* 4k Hz*/
new IIRCoefficients(6.9062328809e-01, 1.5468835596e-01, 1.4641227157e+00),
/* 8k Hz*/
new IIRCoefficients(4.7820368352e-01, 2.6089815824e-01, 7.3910184176e-01),
/* 16k Hz*/
new IIRCoefficients(2.5620076154e-01, 3.7189961923e-01, -6.2810038077e-01)
};
public final static IIRCoefficients iir_cf15_44100[] = {
/* 25 Hz*/
new IIRCoefficients(9.9834072702e-01, 8.2963648917e-04, 1.9983280505e+00),
/* 40 Hz*/
new IIRCoefficients(9.9734652663e-01, 1.3267366865e-03, 1.9973140908e+00),
/* 63 Hz*/
new IIRCoefficients(9.9582396353e-01, 2.0880182333e-03, 1.9957435641e+00),
/* 100 Hz*/
new IIRCoefficients(9.9337951306e-01, 3.3102434709e-03, 1.9931771947e+00),
/* 160 Hz*/
new IIRCoefficients(9.8942832039e-01, 5.2858398053e-03, 1.9889114258e+00),
/* 250 Hz*/
new IIRCoefficients(9.8353109588e-01, 8.2344520610e-03, 1.9822729654e+00),
/* 400 Hz*/
new IIRCoefficients(9.7378088082e-01, 1.3109559588e-02, 1.9705764276e+00),
/* 630 Hz*/
new IIRCoefficients(9.5901979676e-01, 2.0490101620e-02, 1.9511333590e+00),
/* 1k Hz*/
new IIRCoefficients(9.3574903986e-01, 3.2125480071e-02, 1.9161350100e+00),
/* 1.6k Hz*/
new IIRCoefficients(8.9923630641e-01, 5.0381846793e-02, 1.8501014162e+00),
/* 2.5k Hz*/
new IIRCoefficients(8.4722457681e-01, 7.6387711593e-02, 1.7312785699e+00),
/* 4k Hz*/
new IIRCoefficients(7.6755471307e-01, 1.1622264346e-01, 1.4881981417e+00),
/* 6.3k Hz*/
new IIRCoefficients(6.6125377473e-01, 1.6937311263e-01, 1.0357747868e+00),
/* 10k Hz*/
new IIRCoefficients(5.2683267950e-01, 2.3658366025e-01, 2.2218349322e-01),
/* 16k Hz*/
new IIRCoefficients(4.0179628792e-01, 2.9910185604e-01, -9.1248032613e-01)
};
public final static IIRCoefficients iir_cf15_48000[] = {
/* 25 Hz*/
new IIRCoefficients(9.9847546664e-01, 7.6226668143e-04, 1.9984647656e+00),
/* 40 Hz*/
new IIRCoefficients(9.9756184654e-01, 1.2190767289e-03, 1.9975344645e+00),
/* 63 Hz*/
new IIRCoefficients(9.9616261379e-01, 1.9186931041e-03, 1.9960947369e+00),
/* 100 Hz*/
new IIRCoefficients(9.9391578543e-01, 3.0421072865e-03, 1.9937449618e+00),
/* 160 Hz*/
new IIRCoefficients(9.9028307215e-01, 4.8584639242e-03, 1.9898465702e+00),
/* 250 Hz*/
new IIRCoefficients(9.8485897264e-01, 7.5705136795e-03, 1.9837962543e+00),
/* 400 Hz*/
new IIRCoefficients(9.7588512657e-01, 1.2057436715e-02, 1.9731772447e+00),
/* 630 Hz*/
new IIRCoefficients(9.6228521814e-01, 1.8857390928e-02, 1.9556164694e+00),
/* 1k Hz*/
new IIRCoefficients(9.4080933132e-01, 2.9595334338e-02, 1.9242054384e+00),
/* 1.6k Hz*/
new IIRCoefficients(9.0702059196e-01, 4.6489704022e-02, 1.8653476166e+00),
/* 2.5k Hz*/
new IIRCoefficients(8.5868004289e-01, 7.0659978553e-02, 1.7600401337e+00),
/* 4k Hz*/
new IIRCoefficients(7.8409610788e-01, 1.0795194606e-01, 1.5450725522e+00),
/* 6.3k Hz*/
new IIRCoefficients(6.8332861002e-01, 1.5833569499e-01, 1.1426447155e+00),
/* 10k Hz*/
new IIRCoefficients(5.5267518228e-01, 2.2366240886e-01, 4.0186190803e-01),
/* 16k Hz*/
new IIRCoefficients(4.1811888447e-01, 2.9094055777e-01, -7.0905944223e-01)
};
public final static IIRCoefficients iir_cf25_44100[] = {
/* 20 Hz*/
new IIRCoefficients(9.9934037157e-01, 3.2981421662e-04, 1.9993322545e+00),
/* 31.5 Hz*/
new IIRCoefficients(9.9896129025e-01, 5.1935487310e-04, 1.9989411587e+00),
/* 40 Hz*/
new IIRCoefficients(9.9868118265e-01, 6.5940867495e-04, 1.9986487252e+00),
/* 50 Hz*/
new IIRCoefficients(9.9835175161e-01, 8.2412419683e-04, 1.9983010452e+00),
/* 80 Hz*/
new IIRCoefficients(9.9736411067e-01, 1.3179446674e-03, 1.9972343673e+00),
/* 100 Hz*/
new IIRCoefficients(9.9670622662e-01, 1.6468866919e-03, 1.9965035707e+00),
/* 125 Hz*/
new IIRCoefficients(9.9588448566e-01, 2.0577571681e-03, 1.9955679690e+00),
/* 160 Hz*/
new IIRCoefficients(9.9473519326e-01, 2.6324033689e-03, 1.9942169198e+00),
/* 250 Hz*/
new IIRCoefficients(9.9178600786e-01, 4.1069960678e-03, 1.9905226414e+00),
/* 315 Hz*/
new IIRCoefficients(9.8966154150e-01, 5.1692292513e-03, 1.9876580847e+00),
/* 400 Hz*/
new IIRCoefficients(9.8689036168e-01, 6.5548191616e-03, 1.9836646251e+00),
/* 500 Hz*/
new IIRCoefficients(9.8364027156e-01, 8.1798642207e-03, 1.9786090689e+00),
/* 800 Hz*/
new IIRCoefficients(9.7395577681e-01, 1.3022111597e-02, 1.9611472340e+00),
/* 1k Hz*/
new IIRCoefficients(9.6755437936e-01, 1.6222810321e-02, 1.9476180811e+00),
/* 1.25k Hz*/
new IIRCoefficients(9.5961458750e-01, 2.0192706249e-02, 1.9286193446e+00),
/* 1.6k Hz*/
new IIRCoefficients(9.4861481164e-01, 2.5692594182e-02, 1.8982024567e+00),
/* 2.5k Hz*/
new IIRCoefficients(9.2095325455e-01, 3.9523372724e-02, 1.8003794694e+00),
/* 3.15k Hz*/
new IIRCoefficients(9.0153642498e-01, 4.9231787512e-02, 1.7132251201e+00),
/* 4k Hz*/
new IIRCoefficients(8.7685876255e-01, 6.1570618727e-02, 1.5802270232e+00),
/* 5k Hz*/
new IIRCoefficients(8.4886734822e-01, 7.5566325889e-02, 1.3992391376e+00),
/* 8k Hz*/
new IIRCoefficients(7.7175298860e-01, 1.1412350570e-01, 7.4018523020e-01),
/* 10k Hz*/
new IIRCoefficients(7.2627049462e-01, 1.3686475269e-01, 2.5120552756e-01),
/* 12.5k Hz*/
new IIRCoefficients(6.7674787974e-01, 1.6162606013e-01, -3.4978377639e-01),
/* 16k Hz*/
new IIRCoefficients(6.2482197550e-01, 1.8758901225e-01, -1.0576558797e+00),
/* 20k Hz*/
new IIRCoefficients(6.1776148240e-01, 1.9111925880e-01, -1.5492465594e+00)
};
public final static IIRCoefficients iir_cf25_48000[] = {
/* 20 Hz*/
new IIRCoefficients(9.9939388451e-01, 3.0305774630e-04, 1.9993870327e+00),
/* 31.5 Hz*/
new IIRCoefficients(9.9904564663e-01, 4.7717668529e-04, 1.9990286528e+00),
/* 40 Hz*/
new IIRCoefficients(9.9878827195e-01, 6.0586402557e-04, 1.9987608731e+00),
/* 50 Hz*/
new IIRCoefficients(9.9848556942e-01, 7.5721528829e-04, 1.9984427652e+00),
/* 80 Hz*/
new IIRCoefficients(9.9757801538e-01, 1.2109923088e-03, 1.9974684869e+00),
/* 100 Hz*/
new IIRCoefficients(9.9697343933e-01, 1.5132803374e-03, 1.9968023538e+00),
/* 125 Hz*/
new IIRCoefficients(9.9621823598e-01, 1.8908820086e-03, 1.9959510180e+00),
/* 160 Hz*/
new IIRCoefficients(9.9516191728e-01, 2.4190413595e-03, 1.9947243453e+00),
/* 250 Hz*/
new IIRCoefficients(9.9245085008e-01, 3.7745749576e-03, 1.9913840669e+00),
/* 315 Hz*/
new IIRCoefficients(9.9049749914e-01, 4.7512504310e-03, 1.9888056233e+00),
/* 400 Hz*/
new IIRCoefficients(9.8794899744e-01, 6.0255012789e-03, 1.9852245824e+00),
/* 500 Hz*/
new IIRCoefficients(9.8495930023e-01, 7.5203498850e-03, 1.9807093500e+00),
/* 800 Hz*/
new IIRCoefficients(9.7604570090e-01, 1.1977149551e-02, 1.9652207158e+00),
/* 1k Hz*/
new IIRCoefficients(9.7014963927e-01, 1.4925180364e-02, 1.9532947360e+00),
/* 1.25k Hz*/
new IIRCoefficients(9.6283181641e-01, 1.8584091793e-02, 1.9366149237e+00),
/* 1.6k Hz*/
new IIRCoefficients(9.5268463224e-01, 2.3657683878e-02, 1.9100137880e+00),
/* 2.5k Hz*/
new IIRCoefficients(9.2711765003e-01, 3.6441174983e-02, 1.8248457659e+00),
/* 3.15k Hz*/
new IIRCoefficients(9.0912548757e-01, 4.5437256213e-02, 1.7491177803e+00),
/* 4k Hz*/
new IIRCoefficients(8.8619860800e-01, 5.6900696000e-02, 1.6334959111e+00),
/* 5k Hz*/
new IIRCoefficients(8.6010264114e-01, 6.9948679430e-02, 1.4757186436e+00),
/* 8k Hz*/
new IIRCoefficients(7.8757448309e-01, 1.0621275845e-01, 8.9378724155e-01),
/* 10k Hz*/
new IIRCoefficients(7.4415362476e-01, 1.2792318762e-01, 4.5142017567e-01),
/* 12.5k Hz*/
new IIRCoefficients(6.9581428034e-01, 1.5209285983e-01, -1.1091156053e-01),
/* 16k Hz*/
new IIRCoefficients(6.4120506488e-01, 1.7939746756e-01, -8.2060253244e-01),
/* 20k Hz*/
new IIRCoefficients(6.0884213704e-01, 1.9557893148e-01, -1.3932981614e+00)
};
public final static IIRCoefficients iir_cf31_44100[] = {
/* 20 Hz*/
new IIRCoefficients(9.9934037157e-01, 3.2981421662e-04, 1.9993322545e+00),
/* 25 Hz*/
new IIRCoefficients(9.9917555233e-01, 4.1222383516e-04, 1.9991628705e+00),
/* 31.5 Hz*/
new IIRCoefficients(9.9896129025e-01, 5.1935487310e-04, 1.9989411587e+00),
/* 40 Hz*/
new IIRCoefficients(9.9868118265e-01, 6.5940867495e-04, 1.9986487252e+00),
/* 50 Hz*/
new IIRCoefficients(9.9835175161e-01, 8.2412419683e-04, 1.9983010452e+00),
/* 63 Hz*/
new IIRCoefficients(9.9792365217e-01, 1.0381739160e-03, 1.9978431682e+00),
/* 80 Hz*/
new IIRCoefficients(9.9736411067e-01, 1.3179446674e-03, 1.9972343673e+00),
/* 100 Hz*/
new IIRCoefficients(9.9670622662e-01, 1.6468866919e-03, 1.9965035707e+00),
/* 125 Hz*/
new IIRCoefficients(9.9588448566e-01, 2.0577571681e-03, 1.9955679690e+00),
/* 160 Hz*/
new IIRCoefficients(9.9473519326e-01, 2.6324033689e-03, 1.9942169198e+00),
/* 200 Hz*/
new IIRCoefficients(9.9342335280e-01, 3.2883236020e-03, 1.9926141028e+00),
/* 250 Hz*/
new IIRCoefficients(9.9178600786e-01, 4.1069960678e-03, 1.9905226414e+00),
/* 315 Hz*/
new IIRCoefficients(9.8966154150e-01, 5.1692292513e-03, 1.9876580847e+00),
/* 400 Hz*/
new IIRCoefficients(9.8689036168e-01, 6.5548191616e-03, 1.9836646251e+00),
/* 500 Hz*/
new IIRCoefficients(9.8364027156e-01, 8.1798642207e-03, 1.9786090689e+00),
/* 630 Hz*/
new IIRCoefficients(9.7943153305e-01, 1.0284233476e-02, 1.9714629236e+00),
/* 800 Hz*/
new IIRCoefficients(9.7395577681e-01, 1.3022111597e-02, 1.9611472340e+00),
/* 1k Hz*/
new IIRCoefficients(9.6755437936e-01, 1.6222810321e-02, 1.9476180811e+00),
/* 1.25k Hz*/
new IIRCoefficients(9.5961458750e-01, 2.0192706249e-02, 1.9286193446e+00),
/* 1.6k Hz*/
new IIRCoefficients(9.4861481164e-01, 2.5692594182e-02, 1.8982024567e+00),
/* 2k Hz*/
new IIRCoefficients(9.3620971896e-01, 3.1895140519e-02, 1.8581325022e+00),
/* 2.5k Hz*/
new IIRCoefficients(9.2095325455e-01, 3.9523372724e-02, 1.8003794694e+00),
/* 3.15k Hz*/
new IIRCoefficients(9.0153642498e-01, 4.9231787512e-02, 1.7132251201e+00),
/* 4k Hz*/
new IIRCoefficients(8.7685876255e-01, 6.1570618727e-02, 1.5802270232e+00),
/* 5k Hz*/
new IIRCoefficients(8.4886734822e-01, 7.5566325889e-02, 1.3992391376e+00),
/* 6.3k Hz*/
new IIRCoefficients(8.1417575446e-01, 9.2912122771e-02, 1.1311200817e+00),
/* 8k Hz*/
new IIRCoefficients(7.7175298860e-01, 1.1412350570e-01, 7.4018523020e-01),
/* 10k Hz*/
new IIRCoefficients(7.2627049462e-01, 1.3686475269e-01, 2.5120552756e-01),
/* 12.5k Hz*/
new IIRCoefficients(6.7674787974e-01, 1.6162606013e-01, -3.4978377639e-01),
/* 16k Hz*/
new IIRCoefficients(6.2482197550e-01, 1.8758901225e-01, -1.0576558797e+00),
/* 20k Hz*/
new IIRCoefficients(6.1776148240e-01, 1.9111925880e-01, -1.5492465594e+00)
};
public final static IIRCoefficients iir_cf31_48000[] = {
/* 20 Hz*/
new IIRCoefficients(9.9939388451e-01, 3.0305774630e-04, 1.9993870327e+00),
/* 25 Hz*/
new IIRCoefficients(9.9924247917e-01, 3.7876041632e-04, 1.9992317740e+00),
/* 31.5 Hz*/
new IIRCoefficients(9.9904564663e-01, 4.7717668529e-04, 1.9990286528e+00),
/* 40 Hz*/
new IIRCoefficients(9.9878827195e-01, 6.0586402557e-04, 1.9987608731e+00),
/* 50 Hz*/
new IIRCoefficients(9.9848556942e-01, 7.5721528829e-04, 1.9984427652e+00),
/* 63 Hz*/
new IIRCoefficients(9.9809219264e-01, 9.5390367779e-04, 1.9980242502e+00),
/* 80 Hz*/
new IIRCoefficients(9.9757801538e-01, 1.2109923088e-03, 1.9974684869e+00),
/* 100 Hz*/
new IIRCoefficients(9.9697343933e-01, 1.5132803374e-03, 1.9968023538e+00),
/* 125 Hz*/
new IIRCoefficients(9.9621823598e-01, 1.8908820086e-03, 1.9959510180e+00),
/* 160 Hz*/
new IIRCoefficients(9.9516191728e-01, 2.4190413595e-03, 1.9947243453e+00),
/* 200 Hz*/
new IIRCoefficients(9.9395607757e-01, 3.0219612131e-03, 1.9932727986e+00),
/* 250 Hz*/
new IIRCoefficients(9.9245085008e-01, 3.7745749576e-03, 1.9913840669e+00),
/* 315 Hz*/
new IIRCoefficients(9.9049749914e-01, 4.7512504310e-03, 1.9888056233e+00),
/* 400 Hz*/
new IIRCoefficients(9.8794899744e-01, 6.0255012789e-03, 1.9852245824e+00),
/* 500 Hz*/
new IIRCoefficients(9.8495930023e-01, 7.5203498850e-03, 1.9807093500e+00),
/* 630 Hz*/
new IIRCoefficients(9.8108651246e-01, 9.4567437704e-03, 1.9743538683e+00),
/* 800 Hz*/
new IIRCoefficients(9.7604570090e-01, 1.1977149551e-02, 1.9652207158e+00),
/* 1k Hz*/
new IIRCoefficients(9.7014963927e-01, 1.4925180364e-02, 1.9532947360e+00),
/* 1.25k Hz*/
new IIRCoefficients(9.6283181641e-01, 1.8584091793e-02, 1.9366149237e+00),
/* 1.6k Hz*/
new IIRCoefficients(9.5268463224e-01, 2.3657683878e-02, 1.9100137880e+00),
/* 2k Hz*/
new IIRCoefficients(9.4122788957e-01, 2.9386055213e-02, 1.8750821533e+00),
/* 2.5k Hz*/
new IIRCoefficients(9.2711765003e-01, 3.6441174983e-02, 1.8248457659e+00),
/* 3.15k Hz*/
new IIRCoefficients(9.0912548757e-01, 4.5437256213e-02, 1.7491177803e+00),
/* 4k Hz*/
new IIRCoefficients(8.8619860800e-01, 5.6900696000e-02, 1.6334959111e+00),
/* 5k Hz*/
new IIRCoefficients(8.6010264114e-01, 6.9948679430e-02, 1.4757186436e+00),
/* 6.3k Hz*/
new IIRCoefficients(8.2760520925e-01, 8.6197395374e-02, 1.2405797786e+00),
/* 8k Hz*/
new IIRCoefficients(7.8757448309e-01, 1.0621275845e-01, 8.9378724155e-01),
/* 10k Hz*/
new IIRCoefficients(7.4415362476e-01, 1.2792318762e-01, 4.5142017567e-01),
/* 12.5k Hz*/
new IIRCoefficients(6.9581428034e-01, 1.5209285983e-01, -1.1091156053e-01),
/* 16k Hz*/
new IIRCoefficients(6.4120506488e-01, 1.7939746756e-01, -8.2060253244e-01),
/* 20k Hz*/
new IIRCoefficients(6.0884213704e-01, 1.9557893148e-01, -1.3932981614e+00),
};
}

View File

@@ -0,0 +1,36 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.core;
/**
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class IIRCoefficients {
public double beta;
public double alpha;
public double gamma;
public IIRCoefficients(double beta, double alpha, double gamma) {
this.beta = beta;
this.alpha = alpha;
this.gamma = gamma;
}
}

View File

@@ -0,0 +1,214 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.core;
/**
* Constols of equalizer
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class IIRControls {
/**
* Volume gain
* values should be between 0.0 and 1.0
*/
private float preamp[];
/**
* Gain for each band
* values should be between -0.2 and 1.0
*/
private float bands[][];
/**
* Creates new IIRControls object for given number of bands
*
* @param bandsnum is the number of bands
* @param channels is the number of channels
*/
public IIRControls(int bandsnum, int channels) {
preamp = new float[channels];
bands = new float[bandsnum][channels];
for (int j = 0; j < channels; j++) {
preamp[j] = 1.0f;
for (int i = 0; i < bandsnum; i++)
bands[i][j] = 0f;
}
}
/**
* Returns the maximum value for band control
*
* @return the maximum value for band control
*/
public float getMaximumBandValue() {
return 1.0f;
}
/**
* Returns the minimum value for band control
*
* @return the minimum value for band control
*/
public float getMinimumBandValue() {
return -0.2f;
}
/**
* Returns the maximum value for band control (in Db)
*
* @return the maximum value for band control
*/
public float getMaximumBandDbValue() {
return 12;
}
/**
* Returns the minimum value for band control (in Db)
*
* @return the minimum value for band control
*/
public float getMinimumBandDbValue() {
return -12f;
}
/**
* Returns the maximum value for preamp control
*
* @return the maximum value for preamp control
*/
public float getMaximumPreampValue() {
return 1.0f;
}
/**
* Returns the minimum value for preamp control
*
* @return the minimum value for preamp control
*/
public float getMinimumPreampValue() {
return 0f;
}
/**
* Returns the maximum value for preamp control (in Db)
*
* @return the maximum value for preamp control
*/
public float getMaximumPreampDbValue() {
return 12f;
}
/**
* Returns the minimum value for preamp control (in Db)
*
* @return the minimum value for preamp control
*/
public float getMinimumPreampDbValue() {
return -12f;
}
/**
* Returns bands array
*
* @return bands array
*/
float[][] getBands() {
return bands;
}
/**
* Returns preamp array
*
* @return preamp array
*/
float[] getPreamp() {
return preamp;
}
/**
* Returns value of control for given band and channel
*
* @param band is the index of band
* @param channel is the index of channel
* @return the value
*/
public float getBandValue(int band, int channel) {
return bands[band][channel];
}
/**
* Setter for value of control for given band and channel
*
* @param band is the index of band
* @param channel is the index of channel
* @param value is the new value
*/
public void setBandValue(int band, int channel, float value) {
bands[band][channel] = value;
}
/**
* Setter for value of control for given band and channel (in Db)
*
* @param band is the index of band
* @param channel is the index of channel
* @param value is the new value
*/
public void setBandDbValue(int band, int channel, float value) {
/* Map the gain and preamp values */
/* -12dB .. 12dB mapping */
bands[band][channel] = (float) (2.5220207857061455181125E-01 *
Math.exp(8.0178361802353992349168E-02 * value)
- 2.5220207852836562523180E-01);
}
/**
* Returns value of preamp control for given channel
*
* @param channel is the index of channel
* @return the value
*/
public float getPreampValue(int channel) {
return preamp[channel];
}
/**
* Setter for value of preamp control for given channel
*
* @param channel is the index of channel
* @param value is the new value
*/
public void setPreampValue(int channel, float value) {
preamp[channel] = value;
}
/**
* Setter for value of preamp control for given channel (in Db)
*
* @param channel is the index of channel
* @param value is the new value
*/
public void setPreampDbValue(int channel, float value) {
/* -12dB .. 12dB mapping */
preamp[channel] = (float) (9.9999946497217584440165E-01 *
Math.exp(6.9314738656671842642609E-02 * value)
+ 3.7119444716771825623636E-07);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.core;
/**
* Structure for storing XYData of equalizer.
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class XYData {
/**
* X data
*/
public double x[] = new double[3]; /* x[n], x[n-1], x[n-2] */
/**
* Y data
*/
public double y[] = new double[3]; /* y[n], y[n-1], y[n-2] */
/**
* Constructs new XYData object
*/
public XYData() {
zero();
}
/**
* Fills all content with zero
*/
public void zero() {
for (int i = 0; i < 3; i++) {
x[i] = 0;
y[i] = 0;
}
}
}

View File

@@ -0,0 +1,265 @@
/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* 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.
*----------------------------------------------------------------------
*/
package davaguine.jeq.spi;
import davaguine.jeq.core.IIRControls;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import java.io.IOException;
/**
* The EqualizerInputStream input stream
* Author: Dmitry Vaguine
* Date: 02.05.2004
* Time: 12:00:29
*/
public class EqualizerInputStream extends AudioInputStream {
private davaguine.jeq.core.EqualizerInputStream eq;
/**
* Constructs new audio stream
*
* @param stream input stream with audio data
* @param bands is the number of bands
*/
public EqualizerInputStream(AudioInputStream stream, int bands) {
super(stream, stream.getFormat(), stream.getFrameLength());
AudioFormat format = stream.getFormat();
if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) && !!format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED))
throw new IllegalArgumentException("Unsupported encoding");
eq = new davaguine.jeq.core.EqualizerInputStream(stream,
format.getSampleRate(),
format.getChannels(),
format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED),
format.getSampleSizeInBits(),
format.isBigEndian(),
bands);
}
/**
* Returns Controls of equalizer
*
* @return Controls of equalizer
*/
public IIRControls getControls() {
return eq.getControls();
}
/**
* This is special method helps to determine supported audio format
*
* @param format is an audio format
* @param bands is the number of bands
* @return true if params supported
*/
public static boolean isParamsSupported(AudioFormat format, int bands) {
if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) && !!format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED))
return false;
return davaguine.jeq.core.EqualizerInputStream.isParamsSupported(
format.getSampleRate(),
format.getChannels(),
format.getSampleSizeInBits(),
bands);
}
/**
* Returns the number of bytes that can be read (or skipped over) from
* this input stream without blocking by the next caller of a method for
* this input stream. The next caller might be the same thread or
* another thread.
*
* @return the number of bytes that can be read from this input stream
* without blocking.
* @throws java.io.IOException if an I/O error occurs.
*/
public int available() throws IOException {
return eq.available();
}
/**
* Closes this input stream and releases any system resources associated
* with the stream.
*
* @throws IOException if an I/O error occurs.
*/
public void close() throws IOException {
eq.close();
}
/**
* <p> The <code>mark</code> method of <code>EqualizerInputStream</code> does
* nothing.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
*/
public synchronized void mark(int readlimit) {
eq.mark(readlimit);
}
/**
* Tests if this input stream supports the <code>mark</code> and
* <code>reset</code> methods. Whether or not <code>mark</code> and
* <code>reset</code> are supported is an invariant property of a
* particular input stream instance. The <code>markSupported</code> method
* of <code>EqualizerInputStream</code> returns <code>false</code>.
*
* @return false
*/
public boolean markSupported() {
return eq.markSupported();
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @throws IOException if an I/O error occurs.
*/
public int read() throws IOException {
return eq.read();
}
/**
* Reads some number of bytes from the input stream and stores them into
* the buffer array <code>b</code>. The number of bytes actually read is
* returned as an integer. This method blocks until input data is
* available, end of file is detected, or an exception is thrown.
* <p/>
* <p> If <code>b</code> is <code>null</code>, a
* <code>NullPointerException</code> is thrown. If the length of
* <code>b</code> is zero, then no bytes are read and <code>0</code> is
* returned; otherwise, there is an attempt to read at least one byte. If
* no byte is available because the stream is at end of file, the value
* <code>-1</code> is returned; otherwise, at least one byte is read and
* stored into <code>b</code>.
* <p/>
* <p> The first byte read is stored into element <code>b[0]</code>, the
* next one into <code>b[1]</code>, and so on. The number of bytes read is,
* at most, equal to the length of <code>b</code>. Let <i>k</i> be the
* number of bytes actually read; these bytes will be stored in elements
* <code>b[0]</code> through <code>b[</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[</code><i>k</i><code>]</code> through
* <code>b[b.length-1]</code> unaffected.
* <p/>
* <p> If the first byte cannot be read for any reason other than end of
* file, then an <code>IOException</code> is thrown. In particular, an
* <code>IOException</code> is thrown if the input stream has been closed.
* <p/>
* <p> The <code>read(b)</code> method for class <code>EqualizerInputStream</code>
* has the same effect as: <pre><code> read(b, 0, b.length) </code></pre>
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> is there is no more data because the end of
* the stream has been reached.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if <code>b</code> is <code>null</code>.
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from the input stream into
* an array of bytes. An attempt is made to read as many as
* <code>len</code> bytes, but a smaller number may be read.
* The number of bytes actually read is returned as an integer.
* <p/>
* <p> This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
* <p/>
* <p> If <code>b</code> is <code>null</code>, a
* <code>NullPointerException</code> is thrown.
* <p/>
* <p> If <code>off</code> is negative, or <code>len</code> is negative, or
* <code>off+len</code> is greater than the length of the array
* <code>b</code>, then an <code>IndexOutOfBoundsException</code> is
* thrown.
* <p/>
* <p> If <code>len</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at end of
* file, the value <code>-1</code> is returned; otherwise, at least one
* byte is read and stored into <code>b</code>.
* <p/>
* <p> The first byte read is stored into element <code>b[off]</code>, the
* next one into <code>b[off+1]</code>, and so on. The number of bytes read
* is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
* bytes actually read; these bytes will be stored in elements
* <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
* leaving elements <code>b[off+</code><i>k</i><code>]</code> through
* <code>b[off+len-1]</code> unaffected.
* <p/>
* <p> In every case, elements <code>b[0]</code> through
* <code>b[off]</code> and elements <code>b[off+len]</code> through
* <code>b[b.length-1]</code> are unaffected.
* <p/>
* <p> If the first byte cannot be read for any reason other than end of
* file, then an <code>IOException</code> is thrown. In particular, an
* <code>IOException</code> is thrown if the input stream has been closed.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if <code>b</code> is <code>null</code>.
*/
public int read(byte[] b, int off, int len) throws IOException {
return eq.read(b, off, len);
}
/**
* <p>The method <code>reset</code> for class <code>EqualizerInputStream</code>
* does nothing except throw an <code>IOException</code>.
*
* @throws IOException as an indication that the mark feature doesn't supported by EqualizerInputStream.
*/
public void reset() throws IOException {
eq.reset();
}
/**
* Skips over and discards <code>n</code> bytes of data from this input
* stream. The <code>skip</code> method may, for a variety of reasons, end
* up skipping over some smaller number of bytes, possibly <code>0</code>.
* This may result from any of a number of conditions; reaching end of file
* before <code>n</code> bytes have been skipped is only one possibility.
* The actual number of bytes skipped is returned. If <code>n</code> is
* negative, no bytes are skipped.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @throws IOException if an I/O error occurs.
*/
public long skip(long n) throws IOException {
return eq.skip(n);
}
}

View File

@@ -41,6 +41,9 @@ public class Encoder {
private static final Pattern FORMAT_PATTERN = Pattern
.compile("^\\s*([D ])([E ])\\s+([\\w,]+)\\s+.+$");
private static final Pattern TIME_PATTERN = Pattern
.compile("^(\\d+):(\\d+):(\\d+)\\.(\\d+)$");
/**
* This regexp is used to parse the ffmpeg output about the included
* encoders/decoders.
@@ -895,26 +898,52 @@ public class Encoder {
if (listener != null) {
String time = (String) table.get("time");
if (time != null) {
int dot = time.indexOf('.');
if (dot > 0 && dot == time.length() - 2
&& duration > 0) {
String p1 = time.substring(0, dot);
String p2 = time.substring(dot + 1);
try {
long i1 = Long.parseLong(p1);
long i2 = Long.parseLong(p2);
progress = (i1 * 1000L)
+ (i2 * 100L);
int perm = (int) Math
.round((double) (progress * 1000L)
/ (double) duration);
if (perm > 1000) {
perm = 1000;
}
listener.progress(perm);
} catch (NumberFormatException e) {
;
}
Matcher tm = TIME_PATTERN.matcher(time);
if (tm.matches()) {
try {
long hours = Long.parseLong(tm.group(1));
long minutes = Long.parseLong(tm.group(2));
long seconds = Long.parseLong(tm.group(3));
long millis = Long.parseLong(tm.group(4));
progress = (hours * 3600L * 1000L) +
(minutes * 60L * 1000L) +
(seconds * 1000L) +
(millis * 100L);
int perm = (int) Math
.round((double) (progress * 1000L)
/ (double) duration);
if (perm > 1000) {
perm = 1000;
}
listener.progress(perm);
} catch (NumberFormatException e) {
;
}
} else {
int dot = time.indexOf('.');
if (dot > 0 && dot == time.length() - 2
&& duration > 0) {
String p1 = time.substring(0, dot);
String p2 = time.substring(dot + 1);
try {
long i1 = Long.parseLong(p1);
long i2 = Long.parseLong(p2);
progress = (i1 * 1000L)
+ (i2 * 100L);
int perm = (int) Math
.round((double) (progress * 1000L)
/ (double) duration);
if (perm > 1000) {
perm = 1000;
}
listener.progress(perm);
} catch (NumberFormatException e) {
;
}
}
}
}
}

View File

@@ -48,6 +48,8 @@ public class AudiobookRecorder extends JFrame {
JScrollPane mainScroll;
JDialog equaliserWindow = null;
Book book = null;
JTree bookTree;
@@ -83,7 +85,10 @@ public class AudiobookRecorder extends JFrame {
Random rng = new Random();
SourceDataLine play;
SourceDataLine play = null;
public TargetDataLine microphone = null;
public AudioInputStream microphoneStream = null;
public Configuration sphinxConfig;
public StreamSpeechRecognizer recognizer;
@@ -171,7 +176,7 @@ public class AudiobookRecorder extends JFrame {
toolsOptions = new JMenuItem("Options");
toolsOptions.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Options o = new Options(AudiobookRecorder.this);
new Options(AudiobookRecorder.this);
}
});
@@ -226,6 +231,10 @@ public class AudiobookRecorder extends JFrame {
Options.loadPreferences();
execScript(Options.get("scripts.startup"));
CacheManager.setCacheSize(Options.getInteger("cache.size"));
setLayout(new BorderLayout());
addWindowListener(new WindowAdapter() {
@@ -256,6 +265,7 @@ public class AudiobookRecorder extends JFrame {
selectedSentence.autoTrimSampleFFT();
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
startOffset.setValue(selectedSentence.getStartOffset());
endOffset.setValue(selectedSentence.getEndOffset());
postSentenceGap.setValue(selectedSentence.getPostGap());
@@ -271,6 +281,7 @@ public class AudiobookRecorder extends JFrame {
selectedSentence.autoTrimSamplePeak();
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
startOffset.setValue(selectedSentence.getStartOffset());
endOffset.setValue(selectedSentence.getEndOffset());
postSentenceGap.setValue(selectedSentence.getPostGap());
@@ -291,6 +302,8 @@ public class AudiobookRecorder extends JFrame {
if (selectedSentence != null) {
selectedSentence.setStartOffset((Integer)ob.getValue());
sampleWaveform.setLeftMarker((Integer)ob.getValue());
selectedSentence.updateStartCrossing();
sampleWaveform.setLeftAltMarker(selectedSentence.getStartCrossing());
}
}
});
@@ -301,6 +314,8 @@ public class AudiobookRecorder extends JFrame {
if (selectedSentence != null) {
selectedSentence.setEndOffset((Integer)ob.getValue());
sampleWaveform.setRightMarker((Integer)ob.getValue());
selectedSentence.updateEndCrossing();
sampleWaveform.setRightAltMarker(selectedSentence.getEndCrossing());
}
}
});
@@ -495,18 +510,30 @@ public class AudiobookRecorder extends JFrame {
centralPanel.getActionMap().put("startRecord", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
if (getNoiseFloor() == 0) {
alertNoRoomNoise();
return;
}
startRecording();
}
});
centralPanel.getActionMap().put("startRecordNewPara", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
if (getNoiseFloor() == 0) {
alertNoRoomNoise();
return;
}
startRecordingNewParagraph();
}
});
centralPanel.getActionMap().put("startRerecord", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
if (getNoiseFloor() == 0) {
alertNoRoomNoise();
return;
}
startReRecording();
}
});
@@ -559,7 +586,9 @@ public class AudiobookRecorder extends JFrame {
} catch (Exception e) {
e.printStackTrace();
}
AudiobookRecorder frame = new AudiobookRecorder();
new AudiobookRecorder();
}
public void createNewBook() {
@@ -737,7 +766,6 @@ public class AudiobookRecorder extends JFrame {
Sentence s = (Sentence)o.getObject();
if (!s.isLocked()) {
s.deleteFiles();
Chapter c = (Chapter)s.getParent();
bookTreeModel.removeNodeFromParent(s);
}
}
@@ -775,8 +803,51 @@ public class AudiobookRecorder extends JFrame {
JMenuObject peak = new JMenuObject("Auto-trim all (Peak)", c);
JMenuObject moveUp = new JMenuObject("Move Up", c);
JMenuObject moveDown = new JMenuObject("Move Down", c);
JMenu mergeWith = new JMenu("Merge chapter with");
JMenuObject lockAll = new JMenuObject("Lock all sentences", c);
JMenuObject unlockAll = new JMenuObject("Unlock all sentences", c);
JMenuObject exportChapter = new JMenuObject("Export chapter", c);
int idNumber = s2i(c.getId());
exportChapter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter chap = (Chapter)o.getObject();
ExportDialog ed = new ExportDialog("Exporting " + chap.getName());
ExportThread t = new ExportThread(chap, ed);
Thread nt = new Thread(t);
nt.start();
ed.setVisible(true);
}
});
for (Enumeration<Chapter> bc = book.children(); bc.hasMoreElements();) {
Chapter chp = bc.nextElement();
if (chp.getId().equals(c.getId())) {
continue;
}
JMenuObject2 m = new JMenuObject2(chp.getName(), c, chp);
m.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject2 ob = (JMenuObject2)e.getSource();
Chapter source = (Chapter)ob.getObject1();
Chapter target = (Chapter)ob.getObject2();
DefaultMutableTreeNode n = source.getFirstLeaf();
while (n instanceof Sentence) {
bookTreeModel.removeNodeFromParent(n);
bookTreeModel.insertNodeInto(n, target, target.getChildCount());
n = source.getFirstLeaf();
}
}
});
mergeWith.add(m);
}
int idNumber = Utils.s2i(c.getId());
moveUp.setEnabled(idNumber > 0);
moveDown.setEnabled(idNumber > 0);
@@ -788,10 +859,10 @@ public class AudiobookRecorder extends JFrame {
int pos = bookTreeModel.getIndexOfChild(book, chap);
if (pos > 0) pos--;
int id = s2i(chap.getId());
int id = Utils.s2i(chap.getId());
if (id > 0) {
Chapter prevChap = (Chapter)bookTreeModel.getChild(book, pos);
id = s2i(prevChap.getId());
id = Utils.s2i(prevChap.getId());
if (id > 0) {
bookTreeModel.removeNodeFromParent(chap);
bookTreeModel.insertNodeInto(chap, book, pos);
@@ -805,11 +876,11 @@ public class AudiobookRecorder extends JFrame {
Chapter chap = (Chapter)o.getObject();
int pos = bookTreeModel.getIndexOfChild(book, chap);
pos++;
int id = s2i(chap.getId());
int id = Utils.s2i(chap.getId());
if (id > 0) {
Chapter nextChap = (Chapter)bookTreeModel.getChild(book, pos);
if (nextChap != null) {
id = s2i(nextChap.getId());
id = Utils.s2i(nextChap.getId());
if (id > 0) {
bookTreeModel.removeNodeFromParent(chap);
bookTreeModel.insertNodeInto(chap, book, pos);
@@ -832,12 +903,49 @@ public class AudiobookRecorder extends JFrame {
}
});
lockAll.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter c = (Chapter)o.getObject();
for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
Sentence snt = s.nextElement();
snt.setLocked(true);
bookTreeModel.reload(snt);
}
}
});
unlockAll.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter c = (Chapter)o.getObject();
for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
Sentence snt = s.nextElement();
snt.setLocked(false);
bookTreeModel.reload(snt);
}
}
});
menu.add(moveUp);
menu.add(moveDown);
menu.addSeparator();
menu.add(mergeWith);
menu.addSeparator();
menu.add(peak);
menu.addSeparator();
menu.add(lockAll);
menu.add(unlockAll);
menu.addSeparator();
menu.add(exportChapter);
menu.show(bookTree, e.getX(), e.getY());
}
@@ -849,6 +957,11 @@ public class AudiobookRecorder extends JFrame {
if (recording != null) return;
if (book == null) return;
if (microphone == null) {
JOptionPane.showMessageDialog(this, "Microphone not started. Start microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
toolBar.disableBook();
toolBar.disableSentence();
@@ -877,6 +990,11 @@ public class AudiobookRecorder extends JFrame {
if (recording != null) return;
if (book == null) return;
if (microphone == null) {
JOptionPane.showMessageDialog(this, "Microphone not started. Start microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
toolBar.disableBook();
toolBar.disableSentence();
@@ -924,6 +1042,11 @@ public class AudiobookRecorder extends JFrame {
if (recording != null) return;
if (book == null) return;
if (microphone == null) {
JOptionPane.showMessageDialog(this, "Microphone not started. Start microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
toolBar.disableBook();
toolBar.disableSentence();
@@ -1047,6 +1170,10 @@ public class AudiobookRecorder extends JFrame {
prefs.setProperty("book.genre", book.getGenre());
prefs.setProperty("book.comment", book.getComment());
for (int i = 0; i < 31; i++) {
prefs.setProperty("audio.eq." + i, String.format("%.3f", book.equaliser.getChannel(i)));
}
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
Chapter c = o.nextElement();
@@ -1067,13 +1194,20 @@ public class AudiobookRecorder extends JFrame {
i++;
}
}
FileOutputStream fos = null;
try {
FileOutputStream fos = new FileOutputStream(config);
fos = new FileOutputStream(config);
prefs.storeToXML(fos, "Audiobook Recorder Description");
} catch (Exception e) {
e.printStackTrace();
}
if (fos != null) {
try {
fos.close();
} catch (Exception e) {
}
}
}
public void loadBookStructure(File f) {
@@ -1105,6 +1239,14 @@ public class AudiobookRecorder extends JFrame {
book.setGenre(prefs.getProperty("book.genre"));
book.setComment(prefs.getProperty("book.comment"));
for (int i = 0; i < 31; i++) {
if (prefs.getProperty("audio.eq." + i) == null) {
book.equaliser.setChannel(i, Options.getFloat("audio.eq." + i));
} else {
book.equaliser.setChannel(i, Utils.s2f(prefs.getProperty("audio.eq." + i)));
}
}
bookTreeModel = new DefaultTreeModel(book);
bookTree = new JTree(bookTreeModel);
bookTree.setEditable(true);
@@ -1126,6 +1268,8 @@ public class AudiobookRecorder extends JFrame {
selectedSentence = s;
sampleWaveform.setData(s.getAudioData());
sampleWaveform.setMarkers(s.getStartOffset(), s.getEndOffset());
s.updateCrossings();
sampleWaveform.setAltMarkers(s.getStartCrossing(), s.getEndCrossing());
startOffset.setValue(s.getStartOffset());
endOffset.setValue(s.getEndOffset());
postSentenceGap.setValue(s.getPostGap());
@@ -1192,38 +1336,38 @@ public class AudiobookRecorder extends JFrame {
Chapter c = new Chapter("audition", prefs.getProperty("chapter.audition.name"));
c.setPostGap(s2i(prefs.getProperty("chapter.audition.post-gap")));
c.setPreGap(s2i(prefs.getProperty("chapter.audition.pre-gap")));
c.setPostGap(Utils.s2i(prefs.getProperty("chapter.audition.post-gap")));
c.setPreGap(Utils.s2i(prefs.getProperty("chapter.audition.pre-gap")));
bookTreeModel.insertNodeInto(c, book, 0);
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.audition.sentence.%08d.id", i));
String text = prefs.getProperty(String.format("chapter.audition.sentence.%08d.text", i));
int gap = s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.post-gap", i)));
int gap = Utils.s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.post-gap", i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.start-offset", i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.end-offset", i))));
s.setLocked(s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.locked", i))));
s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.start-offset", i))));
s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.end-offset", i))));
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.locked", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
c = new Chapter("open", prefs.getProperty("chapter.open.name"));
c.setPostGap(s2i(prefs.getProperty("chapter.open.post-gap")));
c.setPreGap(s2i(prefs.getProperty("chapter.open.pre-gap")));
c.setPostGap(Utils.s2i(prefs.getProperty("chapter.open.post-gap")));
c.setPreGap(Utils.s2i(prefs.getProperty("chapter.open.pre-gap")));
bookTreeModel.insertNodeInto(c, book, 0);
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.open.sentence.%08d.id", i));
String text = prefs.getProperty(String.format("chapter.open.sentence.%08d.text", i));
int gap = s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.post-gap", i)));
int gap = Utils.s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.post-gap", i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.start-offset", i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.end-offset", i))));
s.setLocked(s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.locked", i))));
s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.start-offset", i))));
s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.end-offset", i))));
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.locked", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1234,39 +1378,39 @@ public class AudiobookRecorder extends JFrame {
if (cname == null) break;
c = new Chapter(String.format("%04d", cno), cname);
c.setPostGap(s2i(prefs.getProperty(String.format("chapter.%04d.post-gap", cno))));
c.setPreGap(s2i(prefs.getProperty(String.format("chapter.%04d.pre-gap", cno))));
c.setPostGap(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.post-gap", cno))));
c.setPreGap(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.pre-gap", cno))));
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.%04d.sentence.%08d.id", cno, i));
String text = prefs.getProperty(String.format("chapter.%04d.sentence.%08d.text", cno, i));
int gap = s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.post-gap", cno, i)));
int gap = Utils.s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.post-gap", cno, i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.start-offset", cno, i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.end-offset", cno, i))));
s.setLocked(s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.locked", cno, i))));
s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.start-offset", cno, i))));
s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.end-offset", cno, i))));
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.locked", cno, i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
}
c = new Chapter("close", prefs.getProperty("chapter.close.name"));
c.setPostGap(s2i(prefs.getProperty("chapter.close.post-gap")));
c.setPreGap(s2i(prefs.getProperty("chapter.close.pre-gap")));
c.setPostGap(Utils.s2i(prefs.getProperty("chapter.close.post-gap")));
c.setPreGap(Utils.s2i(prefs.getProperty("chapter.close.pre-gap")));
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
for (int i = 0; i < 100000000; i++) {
String id = prefs.getProperty(String.format("chapter.close.sentence.%08d.id", i));
String text = prefs.getProperty(String.format("chapter.close.sentence.%08d.text", i));
int gap = s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.post-gap", i)));
int gap = Utils.s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.post-gap", i)));
if (id == null) break;
Sentence s = new Sentence(id, text);
s.setPostGap(gap);
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.start-offset", i))));
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.end-offset", i))));
s.setLocked(s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.locked", i))));
s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.start-offset", i))));
s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.end-offset", i))));
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.locked", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1308,23 +1452,6 @@ public class AudiobookRecorder extends JFrame {
}
boolean s2b(String s) {
if (s == null) return false;
if (s.equals("true")) return true;
if (s.equals("t")) return true;
if (s.equals("yes")) return true;
if (s.equals("y")) return true;
return false;
}
int s2i(String s) {
try {
return Integer.parseInt(s);
} catch (Exception e) {
}
return 0;
}
public File getBookFolder() {
File bf = new File(Options.get("path.storage"), book.getName());
if (!bf.exists()) {
@@ -1391,140 +1518,54 @@ public class AudiobookRecorder extends JFrame {
}
class ExportThread implements Runnable {
ExportDialog exportDialog;
Chapter chapter;
public ExportThread(Chapter c, ExportDialog e) {
super();
exportDialog = e;
chapter = c;
}
@SuppressWarnings("unchecked")
public void run() {
try {
chapter.exportChapter(exportDialog);
} catch (Exception e) {
e.printStackTrace();
}
exportDialog.closeDialog();
}
}
@SuppressWarnings("unchecked")
public void exportAudio() {
try {
File bookRoot = new File(Options.get("path.storage"), book.getName());
if (!bookRoot.exists()) {
bookRoot.mkdirs();
}
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
Chapter c = o.nextElement();
ExportDialog ed = new ExportDialog("Exporting " + c.getName());
File export = new File(bookRoot, "export");
if (!export.exists()) {
export.mkdirs();
}
Encoder encoder;
String ffloc = Options.get("path.ffmpeg");
if (ffloc != null && !ffloc.equals("")) {
encoder = new Encoder(new FFMPEGLocator() {
public String getFFMPEGExecutablePath() {
return Options.get("path.ffmpeg");
}
});
} else {
encoder = new Encoder();
}
EncodingAttributes attributes = new EncodingAttributes();
AudioAttributes audioAttributes = new AudioAttributes();
audioAttributes.setCodec("libmp3lame");
audioAttributes.setBitRate(Options.getInteger("audio.export.bitrate"));
audioAttributes.setSamplingRate(Options.getInteger("audio.export.samplerate"));
audioAttributes.setChannels(new Integer(2));
attributes.setFormat("mp3");
attributes.setAudioAttributes(audioAttributes);
AudioFormat format = roomNoise.getAudioFormat();
byte[] data;
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
int fullLength = 0;
Chapter c = o.nextElement();
if (c.getChildCount() == 0) continue;
String name = c.getName();
File exportFile = new File(export, name + ".wax");
File wavFile = new File(export, name + ".wav");
FileOutputStream fos = new FileOutputStream(exportFile);
data = getRoomNoise(s2i(Options.get("catenation.pre-chapter")));
fullLength += data.length;
fos.write(data);
for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
Sentence snt = s.nextElement();
data = snt.getRawAudioData();
fullLength += data.length;
fos.write(data);
if (s.hasMoreElements()) {
data = getRoomNoise(snt.getPostGap());
} else {
data = getRoomNoise(s2i(Options.get("catenation.post-chapter")));
}
fullLength += data.length;
fos.write(data);
}
fos.close();
FileInputStream fis = new FileInputStream(exportFile);
AudioInputStream ais = new AudioInputStream(fis, format, fullLength);
fos = new FileOutputStream(wavFile);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos);
fos.flush();
fos.close();
fis.close();
exportFile.delete();
}
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
Chapter c = o.nextElement();
if (c.getChildCount() == 0) continue;
String name = c.getName();
File wavFile = new File(export, name + ".wav");
File mp3File = new File(export, name + "-untagged.mp3");
File taggedFile = new File(export, name + ".mp3");
System.err.println(attributes);
encoder.encode(wavFile, mp3File, attributes);
Mp3File id3 = new Mp3File(mp3File);
ID3v2 tags = new ID3v24Tag();
id3.setId3v2Tag(tags);
tags.setTrack(Integer.toString(s2i(c.getId()) - 0));
tags.setTitle(c.getName());
tags.setAlbum(book.getName());
tags.setArtist(book.getAuthor());
// ID3v2TextFrameData g = new ID3v2TextFrameData(false, new EncodedText(book.getGenre()));
// tags.addFrame(tags.createFrame("TCON", g.toBytes(), true));
tags.setComment(book.getComment());
id3.save(taggedFile.getAbsolutePath());
mp3File.delete();
wavFile.delete();
}
JOptionPane.showMessageDialog(this, "Book exported.", "Done", JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
e.printStackTrace();
ExportThread t = new ExportThread(c, ed);
Thread nt = new Thread(t);
nt.start();
ed.setVisible(true);
}
JOptionPane.showMessageDialog(this, "Book export finished", "Export finished", JOptionPane.PLAIN_MESSAGE);
}
public void playFromSelectedSentence() {
if (selectedSentence == null) return;
if (playing != null) return;
if (getNoiseFloor() == 0) {
alertNoRoomNoise();
return;
}
playing = selectedSentence;
toolBar.disableSentence();
toolBar.enableStop();
@@ -1542,6 +1583,7 @@ public class AudiobookRecorder extends JFrame {
play.start();
while (playing != null) {
bookTree.scrollPathToVisible(new TreePath(s.getPath()));
DefaultMutableTreeNode prev = s.getPreviousSibling();
boolean first = false;
if (prev == null) {
@@ -1550,7 +1592,7 @@ public class AudiobookRecorder extends JFrame {
first = true;
}
if (first) {
data = getRoomNoise(s2i(Options.get("catenation.pre-chapter")));
data = getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter")));
play.write(data, 0, data.length);
}
data = s.getRawAudioData();
@@ -1565,7 +1607,7 @@ public class AudiobookRecorder extends JFrame {
}
if (last) {
data = getRoomNoise(s2i(Options.get("catenation.post-chapter")));
data = getRoomNoise(Utils.s2i(Options.get("catenation.post-chapter")));
play.write(data, 0, data.length);
playing = null;
} else {
@@ -1573,7 +1615,9 @@ public class AudiobookRecorder extends JFrame {
play.write(data, 0, data.length);
}
s = (Sentence)next;
bookTree.setSelectionPath(new TreePath(s.getPath()));
if (s != null) {
bookTree.setSelectionPath(new TreePath(s.getPath()));
}
}
} catch (Exception e) {
playing = null;
@@ -1596,7 +1640,6 @@ public class AudiobookRecorder extends JFrame {
if (len == 0) return null;
AudioFormat f = roomNoise.getAudioFormat();
int frameSize = f.getFrameSize();
float sr = f.getSampleRate();
@@ -1620,4 +1663,82 @@ public class AudiobookRecorder extends JFrame {
}
playing = null;
}
public void alertNoRoomNoise() {
JOptionPane.showMessageDialog(this, "You must record room noise\nbefore recording or playback", "Error", JOptionPane.ERROR_MESSAGE);
}
public void showEqualiser() {
if (equaliserWindow == null) {
equaliserWindow = new JDialog();
equaliserWindow.setTitle("Equaliser");
equaliserWindow.add(book.equaliser);
equaliserWindow.pack();
}
equaliserWindow.setVisible(true);
}
public boolean enableMicrophone() {
AudioFormat format = new AudioFormat(
Options.getInteger("audio.recording.samplerate"),
16,
Options.getInteger("audio.recording.channels"),
true,
false
);
Mixer.Info mixer = Options.getRecordingMixer();
microphone = null;
try {
microphone = AudioSystem.getTargetDataLine(format, mixer);
} catch (Exception e) {
e.printStackTrace();
microphone = null;
return false;
}
if (microphone == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
microphoneStream = new AudioInputStream(microphone);
try {
microphone.open();
} catch (Exception e) {
e.printStackTrace();
microphone = null;
return false;
}
microphone.start();
return true;
}
public void disableMicrophone() {
try {
microphoneStream.close();
microphone.stop();
microphone.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void execScript(String s) {
if (s == null) return;
String[] lines = s.split("\n");
for (String line : lines) {
try {
Process p = Runtime.getRuntime().exec(line);
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@@ -8,6 +8,7 @@ import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import davaguine.jeq.core.IIRControls;
public class Book extends DefaultMutableTreeNode {
@@ -18,9 +19,14 @@ public class Book extends DefaultMutableTreeNode {
ImageIcon icon;
public Equaliser equaliser;
float[] eqChannels = new float[31];
public Book(String bookname) {
super(bookname);
name = bookname;
equaliser = new Equaliser();
}
public void setAuthor(String a) {

View File

@@ -10,12 +10,19 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
JLabel ret = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (value instanceof Sentence) {
Sentence s = (Sentence)value;
if (s.isLocked()) {
ret.setForeground(new Color(0x20, 0x00, 0x00));
ret.setIcon(Icons.locked);
} else if (s.getStartOffset() == 0) {
ret.setIcon(Icons.important);
} else {
ret.setIcon(Icons.sentence);
}
if (s.isInSample()) {
ret.setIcon(Icons.star);
}
} else if (value instanceof Chapter) {
ret.setIcon(Icons.chapter);
} else if (value instanceof Book) {

View File

@@ -0,0 +1,26 @@
package uk.co.majenko.audiobookrecorder;
import java.util.*;
public class CacheManager {
static ArrayList<Cacheable> cache = new ArrayList<Cacheable>();
static int cacheSize = 10;
public static void addToCache(Cacheable c) {
while (cache.size() >= cacheSize) {
Cacheable ob = cache.remove(0);
if (ob.lockedInCache()) {
cache.add(ob);
} else {
ob.clearCache();
}
}
cache.add(c);
}
public static void setCacheSize(int c) {
cacheSize = c;
}
}

View File

@@ -0,0 +1,6 @@
package uk.co.majenko.audiobookrecorder;
public interface Cacheable {
public abstract void clearCache();
public abstract boolean lockedInCache();
}

View File

@@ -8,6 +8,10 @@ import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import it.sauronsoftware.jave.*;
import com.mpatric.mp3agic.*;
import javax.sound.sampled.*;
public class Chapter extends DefaultMutableTreeNode {
@@ -71,4 +75,125 @@ public class Chapter extends DefaultMutableTreeNode {
public int getPostGap() {
return postGap;
}
@SuppressWarnings("unchecked")
public void exportChapter(ExportDialog exportDialog) throws
FileNotFoundException, IOException, InputFormatException, NotSupportedException,
EncoderException, UnsupportedTagException, InvalidDataException {
if (getChildCount() == 0) return;
Book book = AudiobookRecorder.window.book;
File bookRoot = new File(Options.get("path.storage"), book.getName());
if (!bookRoot.exists()) {
bookRoot.mkdirs();
}
File export = new File(bookRoot, "export");
if (!export.exists()) {
export.mkdirs();
}
Encoder encoder;
String ffloc = Options.get("path.ffmpeg");
if (ffloc != null && !ffloc.equals("")) {
encoder = new Encoder(new FFMPEGLocator() {
public String getFFMPEGExecutablePath() {
return Options.get("path.ffmpeg");
}
});
} else {
encoder = new Encoder();
}
EncodingAttributes attributes = new EncodingAttributes();
AudioAttributes audioAttributes = new AudioAttributes();
audioAttributes.setCodec("libmp3lame");
audioAttributes.setBitRate(Options.getInteger("audio.export.bitrate"));
audioAttributes.setSamplingRate(Options.getInteger("audio.export.samplerate"));
audioAttributes.setChannels(new Integer(2));
attributes.setFormat("mp3");
attributes.setAudioAttributes(audioAttributes);
AudioFormat format = AudiobookRecorder.window.roomNoise.getAudioFormat();
byte[] data;
int fullLength = 0;
int kids = getChildCount();
String name = getName();
if (exportDialog != null) exportDialog.setMessage("Exporting " + name);
if (exportDialog != null) exportDialog.setProgress(0);
File exportFile = new File(export, name + ".wax");
File wavFile = new File(export, name + ".wav");
File mp3File = new File(export, name + "-untagged.mp3");
File taggedFile = new File(export, name + ".mp3");
FileOutputStream fos = new FileOutputStream(exportFile);
data = AudiobookRecorder.window.getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter")));
fullLength += data.length;
fos.write(data);
int kidno = 0;
for (Enumeration<Sentence> s = children(); s.hasMoreElements();) {
kidno++;
if (exportDialog != null) exportDialog.setProgress(kidno * 1000 / kids);
Sentence snt = s.nextElement();
data = snt.getRawAudioData();
fullLength += data.length;
fos.write(data);
if (s.hasMoreElements()) {
data = AudiobookRecorder.window.getRoomNoise(snt.getPostGap());
} else {
data = AudiobookRecorder.window.getRoomNoise(Utils.s2i(Options.get("catenation.post-chapter")));
}
fullLength += data.length;
fos.write(data);
}
fos.close();
FileInputStream fis = new FileInputStream(exportFile);
AudioInputStream ais = new AudioInputStream(fis, format, fullLength);
fos = new FileOutputStream(wavFile);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos);
fos.flush();
fos.close();
fis.close();
exportFile.delete();
if (exportDialog != null) exportDialog.setMessage("Converting " + name);
if (exportDialog != null) {
encoder.encode(wavFile, mp3File, attributes, exportDialog);
} else {
encoder.encode(wavFile, mp3File, attributes);
}
Mp3File id3 = new Mp3File(mp3File);
ID3v2 tags = new ID3v24Tag();
id3.setId3v2Tag(tags);
tags.setTrack(Integer.toString(Utils.s2i(getId()) - 0));
tags.setTitle(name);
tags.setAlbum(book.getName());
tags.setArtist(book.getAuthor());
tags.setComment(book.getComment());
id3.save(taggedFile.getAbsolutePath());
mp3File.delete();
wavFile.delete();
}
}

View File

@@ -0,0 +1,84 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import javax.sound.sampled.*;
import davaguine.jeq.core.IIRControls;
public class Equaliser extends JPanel {
EqualiserChannel channels[];
public Equaliser() {
super();
channels = new EqualiserChannel[31];
setLayout(new BorderLayout());
JPanel inner = new JPanel();
inner.setLayout(new FlowLayout());
for (int i = 0; i < 31; i++) {
channels[i] = new EqualiserChannel();
inner.add(channels[i]);
}
add(inner, BorderLayout.CENTER);
JButton smooth = new JButton("Smooth curve");
smooth.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Float ave[] = new Float[31];
for (int i = 1; i < 30; i++) {
ave[i] = (channels[i-1].getValue() + channels[i].getValue() + channels[i+1].getValue()) / 3.0f;
}
for (int i = 1; i < 30; i++) {
channels[i].setValue(ave[i]);
}
}
});
JButton def = new JButton("Set as default");
def.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 31; i++) {
Options.set("audio.eq." + i, channels[i].getValue());
}
}
});
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
buttons.add(smooth);
buttons.add(def);
add(buttons, BorderLayout.SOUTH);
}
public float getChannel(int c) {
return channels[c].getValue();
}
public void setChannel(int c, float v) {
channels[c].setValue(v);
}
public void apply(IIRControls c, int chans) {
for (int i = 0; i < 31; i++) {
c.setBandDbValue(i, 0, channels[i].getValue());
if (chans == 2) {
c.setBandDbValue(i, 1, channels[i].getValue());
}
}
}
}

View File

@@ -0,0 +1,78 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import javax.sound.sampled.*;
public class EqualiserChannel extends JPanel {
float value;
JSlider slider;
JTextField textbox;
public EqualiserChannel() {
super();
value = 0;
slider = new JSlider(-120, 120, 0);
textbox = new JTextField();
setLayout(new BorderLayout());
slider.setOrientation(SwingConstants.VERTICAL);
add(slider, BorderLayout.CENTER);
textbox = new JTextField("0.0");
add(textbox, BorderLayout.SOUTH);
textbox.setPreferredSize(new Dimension(40, 20));
slider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
value = (float)slider.getValue() / 10.0f;
textbox.setText(String.format("%.1f", value));
}
});
textbox.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
textbox.selectAll();
}
public void focusLost(FocusEvent e) {
value = Utils.s2f(textbox.getText());
if (value < -12f) value = -12f;
if (value > 12f) value = 12f;
slider.setValue((int)(value * 10));
textbox.setText(String.format("%.1f", value));
}
});
textbox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
value = Utils.s2f(textbox.getText());
if (value < -12f) value = -12f;
if (value > 12f) value = 12f;
slider.setValue((int)(value * 10));
textbox.setText(String.format("%.1f", value));
}
});
}
public float getValue() {
return value;
}
public void setValue(float v) {
value = v;
slider.setValue((int)(value * 10));
textbox.setText(String.format("%.1f", value));
}
}

View File

@@ -0,0 +1,88 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import it.sauronsoftware.jave.*;
public class ExportDialog extends JDialog implements EncoderProgressListener {
JLabel message;
JLabel icon;
JProgressBar progress;
int spin = 0;
public ExportDialog(String m) {
super();
setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
message = new JLabel(m);
icon = new JLabel(Icons.spinner0);
progress = new JProgressBar(0, 1000);
setLayout(new BorderLayout());
add(message, BorderLayout.CENTER);
icon.setBorder(new EmptyBorder(10, 10, 10, 10));
add(icon, BorderLayout.WEST);
add(progress, BorderLayout.SOUTH);
setLocationRelativeTo(AudiobookRecorder.window);
setPreferredSize(new Dimension(300, 100));
pack();
setSize(new Dimension(300, 100));
// setVisible(true);
}
public void setMessage(String m) {
message.setText(m);
}
public void closeDialog() {
dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
public void spin() {
spin++;
if (spin == 4) spin = 0;
switch (spin) {
case 0: icon.setIcon(Icons.spinner0); break;
case 1: icon.setIcon(Icons.spinner1); break;
case 2: icon.setIcon(Icons.spinner2); break;
case 3: icon.setIcon(Icons.spinner3); break;
}
}
public void progress(int p) {
progress.setValue(500 + (p / 2));
progress.setString((50 + p / 20) + "%");
spin();
}
public void setProgress(int p) {
progress.setValue(p / 2);
progress.setString((p / 20) + "%");
spin();
}
public void message(String m) {
}
public void sourceInfo(MultimediaInfo i) {
}
}

View File

@@ -3,22 +3,29 @@ package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
public class Icons {
static public ImageIcon book = new ImageIcon(Icons.class.getResource("icons/book.png"));
static public ImageIcon chapter = new ImageIcon(Icons.class.getResource("icons/chapter.png"));
static public ImageIcon sentence = new ImageIcon(Icons.class.getResource("icons/sentence.png"));
static public ImageIcon play = new ImageIcon(Icons.class.getResource("icons/play.png"));
static public ImageIcon playon = new ImageIcon(Icons.class.getResource("icons/playon.png"));
static public ImageIcon stop = new ImageIcon(Icons.class.getResource("icons/stop.png"));
static public ImageIcon record = new ImageIcon(Icons.class.getResource("icons/record.png"));
static public ImageIcon openBook = new ImageIcon(Icons.class.getResource("icons/open.png"));
static public ImageIcon newBook = new ImageIcon(Icons.class.getResource("icons/new.png"));
static public ImageIcon newChapter = new ImageIcon(Icons.class.getResource("icons/new-chapter.png"));
static public ImageIcon recordRoom = new ImageIcon(Icons.class.getResource("icons/record-room.png"));
static public ImageIcon save = new ImageIcon(Icons.class.getResource("icons/save.png"));
static public ImageIcon redo = new ImageIcon(Icons.class.getResource("icons/redo.png"));
static public ImageIcon fft = new ImageIcon(Icons.class.getResource("icons/fft.png"));
static public ImageIcon peak = new ImageIcon(Icons.class.getResource("icons/peak.png"));
static public ImageIcon locked = new ImageIcon(Icons.class.getResource("icons/locked.png"));
static public ImageIcon appIcon = new ImageIcon(Icons.class.getResource("icons/appIcon.png"));
static public final ImageIcon book = new ImageIcon(Icons.class.getResource("icons/book.png"));
static public final ImageIcon chapter = new ImageIcon(Icons.class.getResource("icons/chapter.png"));
static public final ImageIcon sentence = new ImageIcon(Icons.class.getResource("icons/sentence.png"));
static public final ImageIcon play = new ImageIcon(Icons.class.getResource("icons/play.png"));
static public final ImageIcon playon = new ImageIcon(Icons.class.getResource("icons/playon.png"));
static public final ImageIcon stop = new ImageIcon(Icons.class.getResource("icons/stop.png"));
static public final ImageIcon record = new ImageIcon(Icons.class.getResource("icons/record.png"));
static public final ImageIcon openBook = new ImageIcon(Icons.class.getResource("icons/open.png"));
static public final ImageIcon newBook = new ImageIcon(Icons.class.getResource("icons/new.png"));
static public final ImageIcon newChapter = new ImageIcon(Icons.class.getResource("icons/new-chapter.png"));
static public final ImageIcon recordRoom = new ImageIcon(Icons.class.getResource("icons/record-room.png"));
static public final ImageIcon save = new ImageIcon(Icons.class.getResource("icons/save.png"));
static public final ImageIcon redo = new ImageIcon(Icons.class.getResource("icons/redo.png"));
static public final ImageIcon fft = new ImageIcon(Icons.class.getResource("icons/fft.png"));
static public final ImageIcon peak = new ImageIcon(Icons.class.getResource("icons/peak.png"));
static public final ImageIcon locked = new ImageIcon(Icons.class.getResource("icons/locked.png"));
static public final ImageIcon appIcon = new ImageIcon(Icons.class.getResource("icons/appIcon.png"));
static public final ImageIcon star = new ImageIcon(Icons.class.getResource("icons/star.png"));
static public final ImageIcon important = new ImageIcon(Icons.class.getResource("icons/important.png"));
static public final ImageIcon spinner0 = new ImageIcon(Icons.class.getResource("icons/spinner0.png"));
static public final ImageIcon spinner1 = new ImageIcon(Icons.class.getResource("icons/spinner1.png"));
static public final ImageIcon spinner2 = new ImageIcon(Icons.class.getResource("icons/spinner2.png"));
static public final ImageIcon spinner3 = new ImageIcon(Icons.class.getResource("icons/spinner3.png"));
static public final ImageIcon eq = new ImageIcon(Icons.class.getResource("icons/eq.png"));
static public final ImageIcon mic = new ImageIcon(Icons.class.getResource("icons/mic.png"));
}

View File

@@ -0,0 +1,20 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
public class JSliderOb extends JSlider {
Object object;
public JSliderOb(int a, int b, int c) {
super(a, b, c);
}
public void setObject(Object o) {
object = o;
}
public Object getObject() {
return object;
}
}

View File

@@ -0,0 +1,19 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
public class JTextFieldOb extends JTextField {
Object object;
public JTextFieldOb(String s) {
super(s);
}
public void setObject(Object o) {
object = o;
}
public Object getObject() {
return object;
}
}

View File

@@ -15,6 +15,8 @@ public class MainToolBar extends JToolBar {
JButton playSentence;
JButton playonSentence;
JButton stopPlaying;
JButton eq;
JToggleButton mic;
AudiobookRecorder root;
@@ -99,6 +101,37 @@ public class MainToolBar extends JToolBar {
});
add(stopPlaying);
addSeparator();
eq = new JButton(Icons.eq);
eq.setToolTipText("Equaliser");
eq.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.showEqualiser();
}
});
add(eq);
addSeparator();
mic = new JToggleButton(Icons.mic);
mic.setToolTipText("Enable/disable microphone");
mic.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JToggleButton b = (JToggleButton)e.getSource();
if (b.isSelected()) {
if (!root.enableMicrophone()) {
b.setSelected(false);
}
} else {
root.disableMicrophone();
}
}
});
add(mic);
setFloatable(false);
}

View File

@@ -14,20 +14,6 @@ public class OpenBookPanel extends JPanel {
JTable table;
class BookInfo {
public String name;
public String author;
public String genre;
public String comment;
public BookInfo(String n, String a, String g, String c) {
name = n;
author = a;
genre = g;
comment = c;
}
}
public class BookCellRenderer implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value == null) return null;
@@ -96,12 +82,15 @@ public class OpenBookPanel extends JPanel {
try {
File dir = new File(Options.get("path.storage"));
for (File b : dir.listFiles()) {
if (!b.isDirectory()) continue;
File xml = new File(b, "audiobook.abk");
if (xml.exists()) {
BookPanel book = new BookPanel(b);
model.addBook(book);
if (dir.exists() && dir.isDirectory()) {
for (File b : dir.listFiles()) {
if (b == null) continue;
if (!b.isDirectory()) continue;
File xml = new File(b, "audiobook.abk");
if (xml.exists()) {
BookPanel book = new BookPanel(b);
model.addBook(book);
}
}
}

View File

@@ -11,6 +11,8 @@ import java.io.*;
public class Options extends JDialog {
JTabbedPane tabs;
GridBagConstraints constraint;
JComboBox<KVPair> mixerList;
@@ -26,7 +28,11 @@ public class Options extends JDialog {
JComboBox<KVPair> bitRate;
JComboBox<KVPair> exportRate;
JCheckBox enableParsing;
JSpinner cacheSize;
Equaliser equaliser;
JTextArea startupScript;
static HashMap<String, String> defaultPrefs;
static Preferences prefs = null;
@@ -53,17 +59,29 @@ public class Options extends JDialog {
}
}
JComboBox<KVPair> addDropdown(String label, KVPair[] options, String def) {
class JButtonObject extends JButton {
Object object;
public JButtonObject(String s, Object o) {
super(s);
object = o;
}
public Object getObject() {
return object;
}
}
JComboBox<KVPair> addDropdown(JPanel panel, String label, KVPair[] options, String def) {
JLabel l = new JLabel(label);
constraint.gridx = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.anchor = GridBagConstraints.LINE_START;
add(l, constraint);
panel.add(l, constraint);
JComboBox<KVPair> o = new JComboBox<KVPair>(options);
constraint.gridx = 1;
add(o, constraint);
panel.add(o, constraint);
for (KVPair p : options) {
if (p.key.equals(def)) {
@@ -76,25 +94,67 @@ public class Options extends JDialog {
return o;
}
JTextField addFilePath(String label, String path) {
JTextField addFilePath(JPanel panel, String label, String path, boolean dironly) {
JLabel l = new JLabel(label);
constraint.gridx = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.anchor = GridBagConstraints.LINE_START;
add(l, constraint);
panel.add(l, constraint);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
JTextField a = new JTextField(path);
p.add(a, BorderLayout.CENTER);
JButton b = new JButton("...");
JButtonObject b = new JButtonObject("...", a);
if (dironly) {
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButtonObject o = (JButtonObject)e.getSource();
JTextField f = (JTextField)o.getObject();
JFileChooser fc = new JFileChooser();
File d = new File(f.getText());
if (d.exists() && d.isDirectory()) {
fc.setCurrentDirectory(d);
}
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int r = fc.showOpenDialog(Options.this);
if (r == JFileChooser.APPROVE_OPTION) {
f.setText(fc.getSelectedFile().getAbsolutePath());
}
}
});
} else {
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButtonObject o = (JButtonObject)e.getSource();
JTextField f = (JTextField)o.getObject();
JFileChooser fc = new JFileChooser();
File d = new File(f.getText());
if (d.exists() && d.isDirectory()) {
fc.setCurrentDirectory(d);
} else if (d.exists()) {
d = d.getParentFile();
if (d.exists() && d.isDirectory()) {
fc.setCurrentDirectory(d);
}
}
int r = fc.showOpenDialog(Options.this);
if (r == JFileChooser.APPROVE_OPTION) {
f.setText(fc.getSelectedFile().getAbsolutePath());
}
}
});
}
p.add(b, BorderLayout.EAST);
constraint.gridx = 1;
constraint.fill = GridBagConstraints.HORIZONTAL;
add(p, constraint);
panel.add(p, constraint);
constraint.fill = GridBagConstraints.NONE;
@@ -102,7 +162,7 @@ public class Options extends JDialog {
return a;
}
void addSeparator() {
void addSeparator(JPanel panel) {
constraint.gridx = 0;
constraint.gridwidth = 2;
@@ -115,20 +175,20 @@ public class Options extends JDialog {
constraint.fill = GridBagConstraints.HORIZONTAL;
constraint.insets = new Insets(10, 2, 10, 2);
add(p, constraint);
panel.add(p, constraint);
constraint.insets = new Insets(2, 2, 2, 2);
constraint.fill = GridBagConstraints.NONE;
constraint.gridwidth = 1;
constraint.gridy++;
}
JSpinner addSpinner(String label, int min, int max, int step, int value, String suffix) {
JSpinner addSpinner(JPanel panel, String label, int min, int max, int step, int value, String suffix) {
JLabel l = new JLabel(label);
constraint.gridx = 0;
constraint.gridwidth = 1;
constraint.gridheight = 1;
constraint.anchor = GridBagConstraints.LINE_START;
add(l, constraint);
panel.add(l, constraint);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
@@ -141,7 +201,7 @@ public class Options extends JDialog {
constraint.gridx = 1;
constraint.fill = GridBagConstraints.HORIZONTAL;
add(p, constraint);
panel.add(p, constraint);
constraint.fill = GridBagConstraints.NONE;
@@ -150,11 +210,11 @@ public class Options extends JDialog {
}
JCheckBox addCheckBox(String label, boolean state) {
JCheckBox addCheckBox(JPanel panel, String label, boolean state) {
constraint.gridx = 1;
JCheckBox cb = new JCheckBox(label);
cb.setSelected(state);
add(cb, constraint);
panel.add(cb, constraint);
constraint.gridy++;
return cb;
}
@@ -163,7 +223,13 @@ public class Options extends JDialog {
public Options(JFrame parent) {
loadPreferences(); // Just in case. It should do nothing.
setLayout(new GridBagLayout());
setLayout(new BorderLayout());
tabs = new JTabbedPane();
JPanel optionsPanel = new JPanel();
optionsPanel.setLayout(new GridBagLayout());
constraint = new GridBagConstraints();
@@ -174,38 +240,62 @@ public class Options extends JDialog {
constraint.insets = new Insets(2, 2, 2, 2);
addSeparator();
addSeparator(optionsPanel);
mixerList = addDropdown("Recording device:", getRecordingMixerList(), get("audio.recording.device"));
channelList = addDropdown("Channels:", getChannelCountList(), get("audio.recording.channels"));
rateList = addDropdown("Sample rate:", getSampleRateList(), get("audio.recording.samplerate"));
mixerList = addDropdown(optionsPanel, "Recording device:", getRecordingMixerList(), get("audio.recording.device"));
channelList = addDropdown(optionsPanel, "Channels:", getChannelCountList(), get("audio.recording.channels"));
rateList = addDropdown(optionsPanel, "Sample rate:", getSampleRateList(), get("audio.recording.samplerate"));
addSeparator();
addSeparator(optionsPanel);
playbackList = addDropdown("Playback device:", getPlaybackMixerList(), get("audio.playback.device"));
addSeparator();
storageFolder = addFilePath("Storage folder:", get("path.storage"));
playbackList = addDropdown(optionsPanel, "Playback device:", getPlaybackMixerList(), get("audio.playback.device"));
addSeparator(optionsPanel);
storageFolder = addFilePath(optionsPanel, "Storage folder:", get("path.storage"), true);
addSeparator();
addSeparator(optionsPanel);
preChapterGap = addSpinner("Default pre-chapter gap:", 0, 5000, 100, getInteger("catenation.pre-chapter"), "ms");
postChapterGap = addSpinner("Default post-chapter gap:", 0, 5000, 100, getInteger("catenation.post-chapter"), "ms");
postSentenceGap = addSpinner("Default post-sentence gap:", 0, 5000, 100, getInteger("catenation.post-sentence"), "ms");
postParagraphGap = addSpinner("Default post-paragraph gap:", 0, 5000, 100, getInteger("catenation.post-paragraph"), "ms");
preChapterGap = addSpinner(optionsPanel, "Default pre-chapter gap:", 0, 5000, 100, getInteger("catenation.pre-chapter"), "ms");
postChapterGap = addSpinner(optionsPanel, "Default post-chapter gap:", 0, 5000, 100, getInteger("catenation.post-chapter"), "ms");
postSentenceGap = addSpinner(optionsPanel, "Default post-sentence gap:", 0, 5000, 100, getInteger("catenation.post-sentence"), "ms");
postParagraphGap = addSpinner(optionsPanel, "Default post-paragraph gap:", 0, 5000, 100, getInteger("catenation.post-paragraph"), "ms");
addSeparator();
addSeparator(optionsPanel);
ffmpegLocation = addFilePath("FFMPEG location:", get("path.ffmpeg"));
bitRate = addDropdown("Export bitrate:", getBitrates(), get("audio.export.bitrate"));
exportRate = addDropdown("Export sample rate:", getSampleRateList(), get("audio.export.samplerate"));
ffmpegLocation = addFilePath(optionsPanel, "FFMPEG location:", get("path.ffmpeg"), false);
bitRate = addDropdown(optionsPanel, "Export bitrate:", getBitrates(), get("audio.export.bitrate"));
exportRate = addDropdown(optionsPanel, "Export sample rate:", getSampleRateList(), get("audio.export.samplerate"));
addSeparator();
addSeparator(optionsPanel);
enableParsing = addCheckBox("Enable automatic sphinx speech-to-text (**SLOW**)", getBoolean("process.sphinx"));
enableParsing = addCheckBox(optionsPanel, "Enable automatic sphinx speech-to-text (**SLOW**)", getBoolean("process.sphinx"));
addSeparator();
addSeparator(optionsPanel);
cacheSize = addSpinner(optionsPanel, "Cache size:", 0, 5000, 1, getInteger("cache.size"), "");
addSeparator(optionsPanel);
tabs.add("Options", optionsPanel);
equaliser = new Equaliser();
for (int i = 0; i < 31; i++) {
equaliser.setChannel(i, Options.getFloat("audio.eq." + i));
}
tabs.add("Default EQ", equaliser);
JPanel startScript = new JPanel();
startScript.setLayout(new BorderLayout());
startupScript = new JTextArea(get("scripts.startup"));
startScript.add(startupScript, BorderLayout.CENTER);
tabs.add("Startup Script", startScript);
add(tabs, BorderLayout.CENTER);
setTitle("Options");
@@ -239,7 +329,7 @@ public class Options extends JDialog {
constraint.anchor = GridBagConstraints.LINE_END;
add(box, constraint);
add(box, BorderLayout.SOUTH);
pack();
@@ -264,16 +354,14 @@ public class Options extends JDialog {
boolean supported = false;
Line l;
try {
l = m.getLine(stereoDIF);
m.getLine(stereoDIF);
supported = true;
} catch (Exception e) {
}
try {
l = m.getLine(monoDIF);
m.getLine(monoDIF);
supported = true;
} catch (Exception e) {
}
@@ -302,16 +390,14 @@ public class Options extends JDialog {
boolean supported = false;
Line l;
try {
l = m.getLine(stereoDIF);
m.getLine(stereoDIF);
supported = true;
} catch (Exception e) {
}
try {
l = m.getLine(monoDIF);
m.getLine(monoDIF);
supported = true;
} catch (Exception e) {
}
@@ -373,6 +459,42 @@ public class Options extends JDialog {
defaultPrefs.put("process.sphinx", "false");
defaultPrefs.put("cache.size", "100");
defaultPrefs.put("audio.eq.0", "0.00");
defaultPrefs.put("audio.eq.1", "0.00");
defaultPrefs.put("audio.eq.2", "0.00");
defaultPrefs.put("audio.eq.3", "0.00");
defaultPrefs.put("audio.eq.4", "0.00");
defaultPrefs.put("audio.eq.5", "0.00");
defaultPrefs.put("audio.eq.6", "0.00");
defaultPrefs.put("audio.eq.7", "0.00");
defaultPrefs.put("audio.eq.8", "0.00");
defaultPrefs.put("audio.eq.9", "0.00");
defaultPrefs.put("audio.eq.10", "0.00");
defaultPrefs.put("audio.eq.11", "0.00");
defaultPrefs.put("audio.eq.12", "0.00");
defaultPrefs.put("audio.eq.13", "0.00");
defaultPrefs.put("audio.eq.14", "0.00");
defaultPrefs.put("audio.eq.15", "0.00");
defaultPrefs.put("audio.eq.16", "0.00");
defaultPrefs.put("audio.eq.17", "0.00");
defaultPrefs.put("audio.eq.18", "0.00");
defaultPrefs.put("audio.eq.19", "-1.00");
defaultPrefs.put("audio.eq.20", "-2.00");
defaultPrefs.put("audio.eq.21", "-3.00");
defaultPrefs.put("audio.eq.22", "-4.00");
defaultPrefs.put("audio.eq.23", "-5.00");
defaultPrefs.put("audio.eq.24", "-6.00");
defaultPrefs.put("audio.eq.25", "-7.00");
defaultPrefs.put("audio.eq.26", "-8.00");
defaultPrefs.put("audio.eq.27", "-9.00");
defaultPrefs.put("audio.eq.28", "-10.00");
defaultPrefs.put("audio.eq.29", "-11.00");
defaultPrefs.put("audio.eq.30", "-12.00");
defaultPrefs.put("scripts.startup", "");
if (prefs == null) {
prefs = Preferences.userNodeForPackage(AudiobookRecorder.class);
}
@@ -404,6 +526,15 @@ public class Options extends JDialog {
return 0;
}
public static Float getFloat(String key) {
try {
Float f = Float.parseFloat(get(key));
return f;
} catch (Exception e) {
}
return 0.0f;
}
public static boolean getBoolean(String key) {
String v = get(key);
if (v == null) return false;
@@ -419,6 +550,36 @@ public class Options extends JDialog {
prefs.put(key, value);
}
public static void set(String key, Integer value) {
set(key, String.format("%d", value));
}
public static void set(String key, Boolean value) {
if (value) {
set(key, "true");
} else {
set(key, "false");
}
}
public static void set(String key, Float value) {
set(key, String.format("%.3f", value));
}
public static void set(String key, Object value) {
if (value instanceof Integer) {
set(key, (Integer)value);
} else if (value instanceof Float) {
set(key, (Float)value);
} else if (value instanceof String) {
set(key, (String)value);
} else if (value instanceof Boolean) {
set(key, (Boolean)value);
} else {
System.err.println("Bad type for key " + key);
}
}
void storePreferences() {
set("audio.recording.device", ((KVPair)mixerList.getSelectedItem()).key);
set("audio.recording.channels", ((KVPair)channelList.getSelectedItem()).key);
@@ -426,13 +587,20 @@ public class Options extends JDialog {
set("audio.playback.device", ((KVPair)playbackList.getSelectedItem()).key);
set("path.storage", storageFolder.getText());
set("path.ffmpeg", ffmpegLocation.getText());
set("catenation.pre-chapter", "" + preChapterGap.getValue());
set("catenation.post-chapter", "" + postChapterGap.getValue());
set("catenation.post-sentence", "" + postSentenceGap.getValue());
set("catenation.post-paragraph", "" + postParagraphGap.getValue());
set("catenation.pre-chapter", preChapterGap.getValue());
set("catenation.post-chapter", postChapterGap.getValue());
set("catenation.post-sentence", postSentenceGap.getValue());
set("catenation.post-paragraph", postParagraphGap.getValue());
set("audio.export.bitrate", ((KVPair)bitRate.getSelectedItem()).key);
set("audio.export.samplerate", ((KVPair)exportRate.getSelectedItem()).key);
set("process.sphinx", enableParsing.isSelected() ? "true" : "false");
set("process.sphinx", enableParsing.isSelected());
set("cache.size", cacheSize.getValue());
for (int i = 0; i < 31; i++) {
set("audio.eq." + i, equaliser.getChannel(i));
}
set("scripts.startup", startupScript.getText());
savePreferences();
}

View File

@@ -12,14 +12,18 @@ import javax.sound.sampled.*;
import edu.cmu.sphinx.api.*;
import edu.cmu.sphinx.decoder.adaptation.*;
import edu.cmu.sphinx.result.*;
import davaguine.jeq.spi.EqualizerInputStream;
import davaguine.jeq.core.IIRControls;
public class Sentence extends DefaultMutableTreeNode {
public class Sentence extends DefaultMutableTreeNode implements Cacheable {
String text;
String id;
int postGap;
int startOffset = 0;
int endOffset = 0;
int crossStartOffset = -1;
int crossEndOffset = -1;
int sampleSize = -1;
@@ -27,10 +31,79 @@ public class Sentence extends DefaultMutableTreeNode {
boolean recording;
boolean inSample;
TargetDataLine line;
AudioInputStream inputStream;
Thread recordingThread = null;
int[] storedAudioData = null;
RecordingThread recordingThread;
static class RecordingThread implements Runnable {
boolean running = false;
boolean recording = false;
File tempFile;
File wavFile;
AudioFormat format;
public RecordingThread(File tf, File wf, AudioFormat af) {
tempFile = tf;
wavFile = wf;
format = af;
}
public void run() {
try {
running = true;
recording = true;
byte[] buf = new byte[AudiobookRecorder.window.microphone.getBufferSize()];
FileOutputStream fos = new FileOutputStream(tempFile);
int len = 0;
AudiobookRecorder.window.microphone.flush();
int nr = 0;
while (recording) {
nr = AudiobookRecorder.window.microphoneStream.read(buf, 0, buf.length);
len += nr;
fos.write(buf, 0, nr);
}
nr = AudiobookRecorder.window.microphoneStream.read(buf, 0, buf.length);
len += nr;
fos.write(buf, 0, nr);
fos.close();
FileInputStream fis = new FileInputStream(tempFile);
AudioInputStream ais = new AudioInputStream(fis, format, len / format.getFrameSize());
fos = new FileOutputStream(wavFile);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos);
fos.close();
ais.close();
fis.close();
tempFile.delete();
recording = false;
running = false;
} catch (Exception e) {
e.printStackTrace();
running = false;
recording = false;
running = false;
}
}
public boolean isRunning() {
return running;
}
public void stopRecording() {
recording = false;
}
}
public Sentence() {
super("");
@@ -49,77 +122,34 @@ public class Sentence extends DefaultMutableTreeNode {
}
public boolean startRecording() {
AudioFormat format = new AudioFormat(
Options.getInteger("audio.recording.samplerate"),
16,
Options.getInteger("audio.recording.channels"),
true,
false
);
if (format == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
if (AudiobookRecorder.window.microphone == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Microphone not started. Start the microphone first.", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
Mixer.Info mixer = Options.getRecordingMixer();
line = null;
recordingThread = new RecordingThread(getTempFile(), getFile(), Options.getAudioFormat());
try {
line = AudioSystem.getTargetDataLine(format, mixer);
} catch (Exception e) {
e.printStackTrace();
}
Thread rc = new Thread(recordingThread);
rc.setDaemon(true);
rc.start();
if (line == null) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
inputStream = new AudioInputStream(line);
try {
line.open();
} catch (Exception e) {
e.printStackTrace();
return false;
}
line.start();
File audioFile = getFile();
recordingThread = new Thread(new Runnable() {
public void run() {
try {
AudioSystem.write(inputStream, AudioFileFormat.Type.WAVE, audioFile);
} catch (Exception e) {
e.printStackTrace();
}
}
});
recordingThread.setDaemon(true);
recordingThread.start();
recording = true;
return true;
}
public void stopRecording() {
try {
inputStream.close();
line.stop();
line.close();
} catch (Exception e) {
e.printStackTrace();
recordingThread.stopRecording();
while (recordingThread.isRunning()) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
}
recording = false;
storedAudioData = null;
if (!id.equals("room-noise")) {
autoTrimSampleFFT();
autoTrimSamplePeak();
if (Options.getBoolean("process.sphinx")) {
recognise();
}
@@ -127,6 +157,8 @@ public class Sentence extends DefaultMutableTreeNode {
}
public void autoTrimSampleFFT() {
crossStartOffset = -1;
crossEndOffset = -1;
int[] samples = getAudioData();
if (samples == null) return;
@@ -200,10 +232,13 @@ public class Sentence extends DefaultMutableTreeNode {
if (endOffset <= startOffset) endOffset = startOffset + 4096;
if (endOffset < 0) endOffset = 0;
if (endOffset >= samples.length) endOffset = samples.length;
updateCrossings();
}
public void autoTrimSamplePeak() {
crossStartOffset = -1;
crossEndOffset = -1;
int[] samples = getAudioData();
if (samples == null) return;
int noiseFloor = AudiobookRecorder.window.getNoiseFloor();
@@ -242,6 +277,7 @@ public class Sentence extends DefaultMutableTreeNode {
if (startOffset < 0) startOffset = 0;
if (endOffset >= samples.length) endOffset = samples.length-1;
updateCrossings();
}
public String getId() {
@@ -264,6 +300,14 @@ public class Sentence extends DefaultMutableTreeNode {
return new File(b, id + ".wav");
}
public File getTempFile() {
File b = new File(AudiobookRecorder.window.getBookFolder(), "files");
if (!b.exists()) {
b.mkdirs();
}
return new File(b, id + ".wax");
}
public void editText() {
String t = JOptionPane.showInputDialog(null, "Edit Text", text);
@@ -304,6 +348,9 @@ public class Sentence extends DefaultMutableTreeNode {
}
public int[] getAudioData() {
if (storedAudioData != null) {
return storedAudioData;
}
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
@@ -333,18 +380,48 @@ public class Sentence extends DefaultMutableTreeNode {
}
s.close();
sampleSize = samples.length;
storedAudioData = samples;
CacheManager.addToCache(this);
return samples;
} catch (Exception e) {
}
return null;
}
public int getStartCrossing() {
return crossStartOffset;
}
public int getStartOffset() {
return startOffset;
}
public void updateCrossings() {
updateStartCrossing();
updateEndCrossing();
}
public void updateStartCrossing() {
if (crossStartOffset == -1) {
crossStartOffset = findNearestZeroCrossing(startOffset, 4096);
}
}
public void updateEndCrossing() {
if (crossEndOffset == -1) {
crossEndOffset = findNearestZeroCrossing(endOffset, 4096);
}
}
public void setStartOffset(int o) {
startOffset = o;
if (startOffset != o) {
startOffset = o;
crossStartOffset = -1;
}
}
public int getEndCrossing() {
return crossEndOffset;
}
public int getEndOffset() {
@@ -352,7 +429,10 @@ public class Sentence extends DefaultMutableTreeNode {
}
public void setEndOffset(int o) {
endOffset = o;
if (endOffset != o) {
endOffset = o;
crossEndOffset = -1;
}
}
public int getSampleSize() {
@@ -364,17 +444,20 @@ public class Sentence extends DefaultMutableTreeNode {
// Open the audio file as an AudioInputStream and automatically
// skip the first startOffset frames.
public AudioInputStream getAudioStream() {
public EqualizerInputStream getAudioStream() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
long len = s.getFrameLength();
EqualizerInputStream eq = new EqualizerInputStream(s, 31);
AudioFormat format = eq.getFormat();
IIRControls controls = eq.getControls();
AudiobookRecorder.window.book.equaliser.apply(controls, format.getChannels());
int frameSize = format.getFrameSize();
s.skip(frameSize * startOffset);
eq.skip(frameSize * startOffset);
return s;
return eq;
} catch (Exception e) {
e.printStackTrace();
}
@@ -397,11 +480,18 @@ public class Sentence extends DefaultMutableTreeNode {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
long len = s.getFrameLength();
EqualizerInputStream eq = new EqualizerInputStream(s, 31);
AudioFormat format = eq.getFormat();
IIRControls controls = eq.getControls();
AudiobookRecorder.window.book.equaliser.apply(controls, format.getChannels());
int frameSize = format.getFrameSize();
int pos = startOffset * frameSize;
updateCrossings();
int pos = crossStartOffset * frameSize;
SourceDataLine play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer());
play.open(format);
@@ -410,10 +500,10 @@ public class Sentence extends DefaultMutableTreeNode {
byte[] buffer = new byte[1024];
s.skip(pos);
eq.skip(pos);
while (pos < endOffset * frameSize) {
int nr = s.read(buffer);
while (pos < crossEndOffset * frameSize) {
int nr = eq.read(buffer);
pos += nr;
play.write(buffer, 0, nr);
@@ -428,16 +518,34 @@ public class Sentence extends DefaultMutableTreeNode {
public byte[] getRawAudioData() {
File f = getFile();
try {
updateCrossings();
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = s.getFormat();
EqualizerInputStream eq = new EqualizerInputStream(s, 31);
AudioFormat format = eq.getFormat();
IIRControls controls = eq.getControls();
AudiobookRecorder.window.book.equaliser.apply(controls, format.getChannels());
int frameSize = format.getFrameSize();
int length = endOffset - startOffset;
byte[] data = new byte[length * frameSize];
int length = crossEndOffset - crossStartOffset;
s.skip(startOffset * frameSize);
int bytesToRead = length * frameSize;
byte[] data = new byte[bytesToRead];
byte[] buf = new byte[65536];
s.read(data);
int pos = 0;
eq.skip(crossStartOffset * frameSize);
while (bytesToRead > 0) {
int r = eq.read(buf, 0, bytesToRead > 65536 ? 65536 : bytesToRead);
for (int i = 0; i < r; i++) {
data[pos++] = buf[i];
bytesToRead--;
}
}
return data;
} catch (Exception e) {
e.printStackTrace();
@@ -487,8 +595,6 @@ public class Sentence extends DefaultMutableTreeNode {
}
System.err.println("Decimated from " + length + " to " + newLen);
ByteArrayInputStream bas = new ByteArrayInputStream(decimated);
recognizer.startRecognition(bas);
SpeechResult result;
@@ -520,4 +626,58 @@ public class Sentence extends DefaultMutableTreeNode {
public boolean isLocked() {
return locked;
}
public void setInSample(boolean s) {
inSample = s;
}
public boolean isInSample() {
return inSample;
}
public void clearCache() {
storedAudioData = null;
System.gc();
}
public boolean lockedInCache() {
return id.equals("room-noise");
}
public int findNearestZeroCrossing(int pos, int range) {
int[] data = getAudioData();
if (data == null) return 0;
if (pos < 0) pos = 0;
if (pos >= data.length) pos = data.length-1;
int backwards = pos;
int forwards = pos;
int backwardsPrev = data[backwards];
int forwardsPrev = data[forwards];
while (backwards > 0 || forwards < data.length-2) {
if (forwards < data.length-2) forwards++;
if (backwards > 0) backwards--;
if (backwardsPrev >= 0 && data[backwards] < 0) { // Found one!
return backwards;
}
if (forwardsPrev < 0 && data[forwards] >= 0) {
return forwards;
}
range--;
if (range == 0) {
return pos;
}
backwardsPrev = data[backwards];
forwardsPrev = data[forwards];
}
return pos;
}
}

View File

@@ -20,4 +20,30 @@ public class Utils {
return resizedImg;
}
public static boolean s2b(String s) {
if (s == null) return false;
if (s.equals("true")) return true;
if (s.equals("t")) return true;
if (s.equals("yes")) return true;
if (s.equals("y")) return true;
return false;
}
public static int s2i(String s) {
try {
return Integer.parseInt(s);
} catch (Exception e) {
}
return 0;
}
public static float s2f(String s) {
try {
return Float.parseFloat(s);
} catch (Exception e) {
}
return 0.0f;
}
}

View File

@@ -12,6 +12,9 @@ public class Waveform extends JPanel {
int leftMarker = 0;
int rightMarker = 0;
int leftAltMarker = 0;
int rightAltMarker = 0;
public Waveform() {
super();
}
@@ -87,15 +90,36 @@ public class Waveform extends JPanel {
g.setColor(new Color(255, 0, 0));
g.drawLine(leftMarker/step, 0, leftMarker/step, h);
g.drawLine(rightMarker/step, 0, rightMarker/step, h);
g.setColor(new Color(255, 255, 0));
g.drawLine(leftAltMarker/step, 0, leftAltMarker/step, h);
g.drawLine(rightAltMarker/step, 0, rightAltMarker/step, h);
}
}
public void setAltMarkers(int l, int r) {
leftAltMarker = l;
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();