Added gain control and normalization, along with peak display

This commit is contained in:
2018-11-13 13:26:54 +00:00
parent 024626019d
commit 14e6709c29
6 changed files with 249 additions and 61 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

View File

@@ -75,12 +75,14 @@ public class AudiobookRecorder extends JFrame {
JScrollBar sampleScroll;
JSpinner postSentenceGap;
JSpinner gainPercent;
JCheckBox locked;
JCheckBox attention;
JCheckBox ethereal;
JButtonSpacePlay reprocessAudioFFT;
JButtonSpacePlay reprocessAudioPeak;
JButtonSpacePlay normalizeAudio;
Thread playingThread = null;
@@ -363,6 +365,7 @@ public class AudiobookRecorder extends JFrame {
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
postSentenceGap.setValue(selectedSentence.getPostGap());
gainPercent.setValue((int)(selectedSentence.getGain() * 100d));
}
}
});
@@ -375,12 +378,23 @@ public class AudiobookRecorder extends JFrame {
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
postSentenceGap.setValue(selectedSentence.getPostGap());
gainPercent.setValue((int)(selectedSentence.getGain() * 100d));
}
}
});
normalizeAudio = new JButtonSpacePlay(Icons.normalize, "Normalize audio", new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (selectedSentence != null) {
selectedSentence.normalize();
sampleWaveform.setData(selectedSentence.getAudioData());
}
}
});
postSentenceGap = new JSpinner(new SteppedNumericSpinnerModel(0, 5000, 100, 0));
postSentenceGap.setPreferredSize(new Dimension(75, 20));
postSentenceGap.setPreferredSize(new Dimension(50, 20));
postSentenceGap.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
@@ -391,6 +405,18 @@ public class AudiobookRecorder extends JFrame {
}
});
gainPercent = new JSpinner(new SteppedNumericSpinnerModel(0, 500, 1, 100));
gainPercent.setPreferredSize(new Dimension(50, 20));
gainPercent.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSpinner ob = (JSpinner)e.getSource();
if (selectedSentence != null) {
selectedSentence.setGain((Integer)ob.getValue() / 100d);
sampleWaveform.setData(selectedSentence.getAudioData());
}
}
});
JToolBar controlsTop = new JToolBar(JToolBar.HORIZONTAL);
JToolBar controlsLeft = new JToolBar(JToolBar.VERTICAL);
@@ -402,6 +428,7 @@ public class AudiobookRecorder extends JFrame {
controlsLeft.add(reprocessAudioFFT);
controlsLeft.add(reprocessAudioPeak);
controlsLeft.add(normalizeAudio);
locked = new JCheckBox("Phrase locked");
locked.setFocusable(false);
@@ -469,6 +496,11 @@ public class AudiobookRecorder extends JFrame {
controlsTop.add(new JLabel("Post gap:"));
controlsTop.add(postSentenceGap);
controlsTop.add(new JLabel("ms"));
controlsTop.add(new JLabel("Gain:"));
controlsTop.add(gainPercent);
controlsTop.add(new JLabel("%"));
controlsTop.add(Box.createHorizontalGlue());
@@ -1013,7 +1045,19 @@ public class AudiobookRecorder extends JFrame {
}
});
JMenuObject normalizeAll = new JMenuObject("Normalize chapter", c, new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter c = (Chapter)o.getObject();
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
snt.normalize();
}
}
});
menu.add(convertAll);
menu.add(normalizeAll);
menu.addSeparator();
menu.add(moveUp);
menu.add(moveDown);
@@ -1392,6 +1436,7 @@ public class AudiobookRecorder extends JFrame {
prefs.setProperty(String.format("%s.sentence.%08d.locked", keybase, i), snt.isLocked() ? "true" : "false");
prefs.setProperty(String.format("%s.sentence.%08d.attention", keybase, i), snt.getAttentionFlag() ? "true" : "false");
prefs.setProperty(String.format("%s.sentence.%08d.ethereal", keybase, i), snt.getEthereal() ? "true" : "false");
prefs.setProperty(String.format("%s.sentence.%08d.gain", keybase, i), String.format("%.8f", snt.getGain()));
i++;
}
}
@@ -1499,6 +1544,7 @@ public class AudiobookRecorder extends JFrame {
s.updateCrossings();
sampleWaveform.setAltMarkers(s.getStartCrossing(), s.getEndCrossing());
postSentenceGap.setValue(s.getPostGap());
gainPercent.setValue((int)(s.getGain() * 100d));
locked.setSelected(s.isLocked());
attention.setSelected(s.getAttentionFlag());
ethereal.setSelected(s.getEthereal());
@@ -1510,6 +1556,7 @@ public class AudiobookRecorder extends JFrame {
selectedSentence = null;
sampleWaveform.clearData();
postSentenceGap.setValue(0);
gainPercent.setValue(100);
locked.setSelected(false);
attention.setSelected(false);
ethereal.setSelected(false);
@@ -1553,6 +1600,7 @@ public class AudiobookRecorder extends JFrame {
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.locked", i))));
s.setAttentionFlag(Utils.s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.attention", i))));
s.setEthereal(Utils.s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.ethereal", i))));
s.setGain(Utils.s2d(prefs.getProperty(String.format("chapter.audition.sentence.%08d.gain", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1573,6 +1621,7 @@ public class AudiobookRecorder extends JFrame {
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.locked", i))));
s.setAttentionFlag(Utils.s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.attention", i))));
s.setEthereal(Utils.s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.ethereal", i))));
s.setGain(Utils.s2d(prefs.getProperty(String.format("chapter.open.sentence.%08d.gain", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1599,6 +1648,7 @@ public class AudiobookRecorder extends JFrame {
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.locked", cno, i))));
s.setAttentionFlag(Utils.s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.attention", cno, i))));
s.setEthereal(Utils.s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.ethereal", cno, i))));
s.setGain(Utils.s2d(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.gain", cno, i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
}
@@ -1620,6 +1670,7 @@ public class AudiobookRecorder extends JFrame {
s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.locked", i))));
s.setAttentionFlag(Utils.s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.attention", i))));
s.setEthereal(Utils.s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.ethereal", i))));
s.setGain(Utils.s2d(prefs.getProperty(String.format("chapter.close.sentence.%08d.gain", i))));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}

View File

@@ -32,4 +32,5 @@ public class Icons {
static public final ImageIcon zoomOut = new ImageIcon(Icons.class.getResource("icons/zoom-out.png"));
static public final ImageIcon dollar = new ImageIcon(Icons.class.getResource("icons/dollar.png"));
static public final ImageIcon attention = new ImageIcon(Icons.class.getResource("icons/attention.png"));
static public final ImageIcon normalize = new ImageIcon(Icons.class.getResource("icons/normalize.png"));
}

View File

@@ -47,6 +47,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
boolean inSample;
boolean attention = false;
double gain = 1.0d;
String havenJobId = "";
// 0: Not processed
@@ -422,6 +424,10 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public int[] getAudioDataS16LE(AudioInputStream s, AudioFormat format) throws IOException {
return getAudioDataS16LE(s, format, true);
}
public int[] getAudioDataS16LE(AudioInputStream s, AudioFormat format, boolean amplify) throws IOException {
long len = s.getFrameLength();
int frameSize = format.getFrameSize();
int chans = format.getChannels();
@@ -441,8 +447,13 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
} else {
sample = (frame[1] << 8) | frame[0];
}
if (amplify) {
double amped = (double)sample * gain;
samples[(int)fno] = (int)amped;
} else {
samples[(int)fno] = sample;
}
}
return samples;
}
@@ -475,6 +486,28 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
return null;
}
public int[] getUnprocessedAudioData() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = getAudioFormat();
int[] samples = null;
switch (format.getSampleSizeInBits()) {
case 16:
samples = getAudioDataS16LE(s, format, false);
break;
}
s.close();
return samples;
} catch (Exception e) {
}
return null;
}
public int getStartCrossing() {
return crossStartOffset;
}
@@ -606,8 +639,53 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
return null;
}
byte[] adjustGain(byte[] data) {
AudioFormat format = getAudioFormat();
int frameSize = format.getFrameSize();
int channels = format.getChannels();
int bytesPerChannel = frameSize / channels;
int frames = data.length / frameSize;
int byteNo = 0;
byte[] out = new byte[data.length];
for (int i = 0; i < frames; i++) {
if (channels == 1) {
int l = data[i * frameSize] >= 0 ? data[i * frameSize] : 256 + data[i * frameSize];
int h = data[(i * frameSize) + 1] >= 0 ? data[(i * frameSize) + 1] : 256 + data[(i * frameSize) + 1];
int sample = (h << 8) | l;
if ((sample & 0x8000) == 0x8000) sample |= 0xFFFF0000;
double sampleDouble = (double)sample;
sampleDouble *= gain;
sample = (int)sampleDouble;
if (sample > 32767) sample = 32767;
if (sample < -32768) sample = -32768;
out[i * frameSize] = (byte)(sample & 0xFF);
out[(i * frameSize) + 1] = (byte)((sample & 0xFF00) >> 8);
} else {
return data;
}
}
return out;
}
byte[] postProcessData(byte[] data) {
data = adjustGain(data);
if (effectEthereal) {
data = processEtherealEffect(data);
}
return data;
}
byte[] processEtherealEffect(byte[] data) {
AudioFormat format = getAudioFormat();
int frameSize = format.getFrameSize();
int channels = format.getChannels();
@@ -663,10 +741,6 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
return out;
} else {
return data;
}
}
public void recognise() {
@@ -691,7 +765,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void clearCache() {
storedAudioData = null;
System.gc();
// System.gc();
}
public boolean lockedInCache() {
@@ -926,4 +1000,44 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
return z;
}
public int getPeakValue() {
int[] samples = getUnprocessedAudioData();
if (samples == null) {
return 0;
}
int ms = 0;
for (int i = 0; i < samples.length; i++) {
if (Math.abs(samples[i]) > ms) {
ms = Math.abs(samples[i]);
}
}
return ms;
}
public int getHeadroom() {
int nf = getPeakValue();
if (nf == 0) return 0;
double r = nf / 32767d;
double l10 = Math.log10(r);
double db = 20d * l10;
return (int)db;
}
public void setGain(double g) {
if (g <= 0.0001d) g = 1.0d;
if (g == gain) return;
gain = g;
clearCache();
}
public double getGain() {
return gain;
}
public void normalize() {
int max = getPeakValue();
setGain(23192d / max);
}
}

View File

@@ -47,6 +47,14 @@ public class Utils {
return 0.0f;
}
public static double s2d(String s) {
try {
return Double.parseDouble(s);
} catch (Exception e) {
}
return 0.0d;
}
public static void browse(String url) {
if (Desktop.isDesktopSupported()) {
try {

View File

@@ -105,12 +105,26 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
if (hcnt > 0) have /= hcnt;
if (lcnt > 0) lave /= lcnt;
boolean clip = false;
if (lmax > 32000) { // -3dB == 23198?
clip = true;
}
if (hmax > 32000) { // -3dB
clip = true;
}
hmax /= scale;
lmax /= scale;
have /= scale;
lave /= scale;
if (clip) {
g.setColor(new Color(200, 20, 0));
} else {
g.setColor(new Color(0, 20, 200));
}
g.drawLine(n, h/2 + lmax, n, h/2 - hmax);
g.setColor(new Color(0, 100, 255));
g.drawLine(n, h/2 + (int)lave, n, h/2 - (int)have);