Implemented new effect chain system instead of EQ

This commit is contained in:
2019-01-15 22:43:02 +00:00
parent fd19f5befe
commit 927eb3e1ff
24 changed files with 1012 additions and 2562 deletions
@@ -0,0 +1,45 @@
package uk.co.majenko.audiobookrecorder;
import java.util.ArrayList;
import javax.swing.tree.*;
public class Amplifier extends DefaultMutableTreeNode implements Effect {
double gain;
public Amplifier() {
gain = 1.0d;
}
public Amplifier(double g) {
gain = g;
}
public String getName() {
return "Amplifier (" + gain + ")";
}
public ArrayList<Effect> getChildEffects() {
return null;
}
public double process(double sample) {
return sample * gain;
}
public double getGain() {
return gain;
}
public void setGain(double g) {
gain = g;
}
public String toString() {
return getName();
}
public void dump() {
System.out.println(toString());
}
public void init(double sf) {
}
}
@@ -17,6 +17,9 @@ import java.nio.file.Files;
import java.util.zip.*;
import javax.swing.filechooser.*;
import javax.imageio.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.*;
public class AudiobookRecorder extends JFrame {
@@ -25,6 +28,9 @@ public class AudiobookRecorder extends JFrame {
public static final int PLAYBACK_CHUNK_SIZE = 256; // Was 1024
static Properties config = new Properties();
HashMap<String, EffectGroup> effects;
String defaultEffectChain = "none";
MainToolBar toolBar;
@@ -82,13 +88,12 @@ public class AudiobookRecorder extends JFrame {
JSpinner gainPercent;
JCheckBox locked;
JCheckBox attention;
JCheckBox ethereal;
JButtonSpacePlay reprocessAudioFFT;
JButtonSpacePlay reprocessAudioPeak;
JButtonSpacePlay normalizeAudio;
JComboBox<String> eqProfile;
JComboBox<KVPair<String,String>> effectChain;
Thread playingThread = null;
@@ -367,7 +372,7 @@ public class AudiobookRecorder extends JFrame {
public void actionPerformed(ActionEvent e) {
if (selectedSentence != null) {
selectedSentence.autoTrimSampleFFT();
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setData(selectedSentence.getDoubleAudioData());
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
postSentenceGap.setValue(selectedSentence.getPostGap());
@@ -380,7 +385,7 @@ public class AudiobookRecorder extends JFrame {
public void actionPerformed(ActionEvent e) {
if (selectedSentence != null) {
selectedSentence.autoTrimSamplePeak();
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setData(selectedSentence.getDoubleAudioData());
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
postSentenceGap.setValue(selectedSentence.getPostGap());
@@ -393,7 +398,7 @@ public class AudiobookRecorder extends JFrame {
public void actionPerformed(ActionEvent e) {
if (selectedSentence != null) {
selectedSentence.normalize();
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setData(selectedSentence.getDoubleAudioData());
}
}
});
@@ -419,7 +424,7 @@ public class AudiobookRecorder extends JFrame {
JSpinner ob = (JSpinner)e.getSource();
if (selectedSentence != null) {
selectedSentence.setGain((Integer)ob.getValue() / 100d);
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setData(selectedSentence.getDoubleAudioData());
}
}
});
@@ -484,27 +489,6 @@ public class AudiobookRecorder extends JFrame {
controlsTop.add(attention);
ethereal = new JCheckBox("Ethereal voice");
ethereal.setFocusable(false);
ethereal.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JCheckBox c = (JCheckBox)e.getSource();
if (c.isSelected()) {
if (selectedSentence != null) {
selectedSentence.setEthereal(true);
}
} else {
if (selectedSentence != null) {
selectedSentence.setEthereal(false);
}
}
bookTreeModel.reload(selectedSentence);
}
});
controlsTop.add(ethereal);
controlsTop.add(Box.createHorizontalGlue());
controlsTop.add(new JLabel("Post gap:"));
controlsTop.add(postSentenceGap);
@@ -531,19 +515,18 @@ public class AudiobookRecorder extends JFrame {
});
controlsRight.add(zoomOut);
controlsBottom.add(new JLabel("EQ Profile: "));
controlsBottom.add(new JLabel("Effects Chain: "));
String[] profiles = new String[2];
profiles[0] = "Default";
profiles[1] = "Phone";
effectChain = new JComboBox<KVPair<String, String>>();
controlsBottom.add(effectChain);
eqProfile = new JComboBox<String>(profiles);
controlsBottom.add(eqProfile);
eqProfile.addActionListener(new ActionListener() {
effectChain.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (selectedSentence != null) {
selectedSentence.setEQProfile(eqProfile.getSelectedIndex());
int i = effectChain.getSelectedIndex();
KVPair<String, String> p = effectChain.getItemAt(i);
selectedSentence.setEffectChain(p.getKey());
updateWaveform();
}
}
});
@@ -1197,34 +1180,32 @@ public class AudiobookRecorder extends JFrame {
);
tabs.add("Data", info);
JPanel effects = new JPanel();
effects.setLayout(new GridBagLayout());
JPanel effectsPanel = new JPanel();
effectsPanel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
effects.add(new JLabel("Ethereal Iterations:"), c);
effectsPanel.add(new JLabel("Default Effect:"), c);
c.gridx = 1;
JSpinner ethIt = new JSpinner(new SteppedNumericSpinnerModel(1, 10, 1, book.getInteger("effects.ethereal.iterations")));
effects.add(ethIt, c);
c.gridx = 0;
c.gridy++;
effects.add(new JLabel("Ethereal Attenuation:"), c);
c.gridx = 1;
JSpinner ethAt = new JSpinner(new SteppedNumericSpinnerModel(0, 100, 1, book.getInteger("effects.ethereal.attenuation")));
effects.add(ethAt, c);
c.gridx = 0;
c.gridy++;
JComboBox<KVPair<String, String>> defEff = new JComboBox<KVPair<String, String>>();
int selEff = -1;
int i = 0;
for (String k : effects.keySet()) {
if (k.equals(defaultEffectChain)) {
selEff = i;
}
KVPair<String, String> p = new KVPair<String, String>(k, effects.get(k).toString());
defEff.addItem(p);
i++;
}
effects.add(new JLabel("Ethereal Offset:"), c);
c.gridx = 1;
JSpinner ethOf = new JSpinner(new SteppedNumericSpinnerModel(0, 2000, 10, book.getInteger("effects.ethereal.offset")));
effects.add(ethOf, c);
c.gridx = 0;
c.gridy++;
defEff.setSelectedIndex(selEff);
effectsPanel.add(defEff, c);
tabs.add("Effects", effects);
tabs.add("Effects", effectsPanel);
int r = JOptionPane.showConfirmDialog(AudiobookRecorder.this, tabs, "Edit Book", JOptionPane.OK_CANCEL_OPTION);
if (r != JOptionPane.OK_OPTION) return;
@@ -1235,9 +1216,9 @@ public class AudiobookRecorder extends JFrame {
String com = info.getComment();
String acx = info.getACX();
book.set("effects.ethereal.iterations", (Integer)ethIt.getValue());
book.set("effects.ethereal.attenuation", (Integer)ethAt.getValue());
book.set("effects.ethereal.offset", (Integer)ethOf.getValue());
i = defEff.getSelectedIndex();
KVPair<String, String> de = defEff.getItemAt(i);
defaultEffectChain = de.getKey();
book.setAuthor(aut);
book.setGenre(gen);
@@ -1519,10 +1500,6 @@ public class AudiobookRecorder extends JFrame {
File config = new File(bookRoot, "audiobook.abk");
Properties prefs = new Properties();
prefs.setProperty("effects.ethereal.iterations", book.get("effects.ethereal.iterations"));
prefs.setProperty("effects.ethereal.offset", book.get("effects.ethereal.offset"));
prefs.setProperty("effects.ethereal.attenuation", book.get("effects.ethereal.attenuation"));
prefs.setProperty("book.name", book.getName());
prefs.setProperty("book.author", book.getAuthor());
prefs.setProperty("book.genre", book.getGenre());
@@ -1533,11 +1510,7 @@ public class AudiobookRecorder extends JFrame {
prefs.setProperty("audio.recording.resolution", "" + book.getResolution());
prefs.setProperty("audio.recording.channels", "" + book.getChannels());
for (int e = 0; e < book.equaliser.length; e++) {
for (int i = 0; i < 31; i++) {
prefs.setProperty(String.format("audio.eq.profiles.%d.%d", e, i), String.format("%.3f", book.equaliser[e].getChannel(i)));
}
}
prefs.setProperty("audio.effect.default", defaultEffectChain);
for (Enumeration o = book.children(); o.hasMoreElements();) {
@@ -1557,9 +1530,8 @@ public class AudiobookRecorder extends JFrame {
prefs.setProperty(String.format("%s.sentence.%08d.end-offset", keybase, i), Integer.toString(snt.getEndOffset()));
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()));
prefs.setProperty(String.format("%s.sentence.%08d.eqprofile", keybase, i), Integer.toString(snt.getEQProfile()));
prefs.setProperty(String.format("%s.sentence.%08d.effect", keybase, i), snt.getEffectChain());
i++;
}
}
@@ -1585,9 +1557,11 @@ public class AudiobookRecorder extends JFrame {
FileInputStream fis = new FileInputStream(f);
prefs.loadFromXML(fis);
File r = f.getParentFile();
loadEffects();
buildBook(prefs);
File r = f.getParentFile();
File cf = new File(r, "coverart.png");
if (!cf.exists()) {
cf = new File(r, "coverart.jpg");
@@ -1616,6 +1590,11 @@ public class AudiobookRecorder extends JFrame {
book.setComment(prefs.getProperty("book.comment"));
book.setACX(prefs.getProperty("book.acx"));
defaultEffectChain = prefs.getProperty("audio.effect.default");
if (defaultEffectChain == null) {
defaultEffectChain = "none";
}
int sr = Utils.s2i(prefs.getProperty("audio.recording.samplerate"));
if (sr == 0) {
sr = Options.getInteger("audio.recording.samplerate");
@@ -1634,17 +1613,6 @@ public class AudiobookRecorder extends JFrame {
}
book.setResolution(res);
for (int e = 0; e < book.equaliser.length; e++) {
for (int i = 0; i < 31; i++) {
if (prefs.getProperty(String.format("audio.eq.profiles.%d.%d", e, i)) == null) {
book.equaliser[e].setChannel(i, Options.getFloat("audio.eq." + i));
} else {
book.equaliser[e].setChannel(i, Utils.s2f(prefs.getProperty(String.format("audio.eq.profiles.%d.%d", e, i))));
}
}
}
bookTreeModel = new DefaultTreeModel(book);
bookTree = new JTree(bookTreeModel);
bookTree.setEditable(true);
@@ -1664,7 +1632,7 @@ public class AudiobookRecorder extends JFrame {
if (n instanceof Sentence) {
Sentence s = (Sentence)n;
selectedSentence = s;
sampleWaveform.setData(s.getAudioData());
sampleWaveform.setData(s.getDoubleAudioData());
sampleWaveform.setMarkers(s.getStartOffset(), s.getEndOffset());
s.updateCrossings();
sampleWaveform.setAltMarkers(s.getStartCrossing(), s.getEndCrossing());
@@ -1672,8 +1640,8 @@ public class AudiobookRecorder extends JFrame {
gainPercent.setValue((int)(s.getGain() * 100d));
locked.setSelected(s.isLocked());
attention.setSelected(s.getAttentionFlag());
ethereal.setSelected(s.getEthereal());
eqProfile.setSelectedIndex(s.getEQProfile());
setEffectChain(s.getEffectChain());
postSentenceGap.setEnabled(!s.isLocked());
gainPercent.setEnabled(!s.isLocked());
@@ -1684,10 +1652,8 @@ public class AudiobookRecorder extends JFrame {
sampleWaveform.clearData();
postSentenceGap.setValue(0);
gainPercent.setValue(100);
eqProfile.setSelectedIndex(0);
locked.setSelected(false);
attention.setSelected(false);
ethereal.setSelected(false);
}
}
});
@@ -1727,9 +1693,8 @@ public class AudiobookRecorder extends JFrame {
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))));
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))));
s.setEQProfile(Utils.s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.eqprofile", i))));
s.setEffectChain(prefs.getProperty(String.format("chapter.audition.sentence.%08d.effect", i)));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1749,9 +1714,8 @@ public class AudiobookRecorder extends JFrame {
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))));
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))));
s.setEQProfile(Utils.s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.eqprofile", i))));
s.setEffectChain(prefs.getProperty(String.format("chapter.open.sentence.%08d.effect", i)));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1777,9 +1741,8 @@ public class AudiobookRecorder extends JFrame {
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))));
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))));
s.setEQProfile(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.eqprofile", cno, i))));
s.setEffectChain(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.effect", cno, i)));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
}
@@ -1800,9 +1763,8 @@ public class AudiobookRecorder extends JFrame {
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))));
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))));
s.setEQProfile(Utils.s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.eqprofile", i))));
s.setEffectChain(prefs.getProperty(String.format("chapter.close.sentence.%08d.effect", i)));
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
}
@@ -1852,28 +1814,27 @@ public class AudiobookRecorder extends JFrame {
return bf;
}
public int getNoiseFloor() {
public double getNoiseFloor() {
if (roomNoise == null) return 0;
int[] samples = roomNoise.getAudioData();
double[] samples = roomNoise.getDoubleAudioData();
if (samples == null) {
return 0;
}
int ms = 0;
double ms = 0;
for (int i = 0; i < samples.length; i++) {
if (Math.abs(samples[i]) > ms) {
ms = Math.abs(samples[i]);
}
}
ms *= 10;
ms /= 7;
ms *= 10d;
ms /= 7d;
return ms;
}
public int getNoiseFloorDB() {
int nf = getNoiseFloor();
if (nf == 0) return 0;
double r = nf / 32767d;
double r = getNoiseFloor();
if (r == 0) return 0;
double l10 = Math.log10(r);
double db = 20d * l10;
@@ -1912,13 +1873,15 @@ public class AudiobookRecorder extends JFrame {
try {
AudioFormat format = s.getAudioFormat();
AudioFormat sampleformat = s.getAudioFormat();
AudioFormat format = new AudioFormat(sampleformat.getSampleRate(), 16, 1, true, false);
play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer());
play.open(format);
play.start();
bookTree.scrollPathToVisible(new TreePath(s.getPath()));
data = s.getRawAudioData();
data = s.getPCMData();
for (int pos = 0; pos < data.length; pos += PLAYBACK_CHUNK_SIZE) {
sampleWaveform.setPlayMarker(pos / format.getFrameSize());
int l = data.length - pos;
@@ -2004,7 +1967,8 @@ public class AudiobookRecorder extends JFrame {
try {
AudioFormat format = s.getAudioFormat();
AudioFormat sampleformat = s.getAudioFormat();
AudioFormat format = new AudioFormat(sampleformat.getSampleRate(), 16, 1, true, false);
play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer());
play.open(format);
play.start();
@@ -2022,7 +1986,7 @@ public class AudiobookRecorder extends JFrame {
data = getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter")));
play.write(data, 0, data.length);
}
data = s.getRawAudioData();
data = s.getPCMData();
for (int pos = 0; pos < data.length; pos += PLAYBACK_CHUNK_SIZE) {
sampleWaveform.setPlayMarker(pos / format.getFrameSize());
int l = data.length - pos;
@@ -2091,7 +2055,7 @@ public class AudiobookRecorder extends JFrame {
roomNoise.setStartOffset(start);
roomNoise.setEndOffset(end);
byte[] data = roomNoise.getRawAudioData();
byte[] data = roomNoise.getPCMData();
return data;
}
@@ -2108,23 +2072,9 @@ public class AudiobookRecorder extends JFrame {
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");
JTabbedPane tabs = new JTabbedPane();
equaliserWindow.add(tabs);
for (int i = 0; i < book.equaliser.length; i++) {
tabs.add(book.equaliser[i].getName(), new JScrollPane(book.equaliser[i]));
}
equaliserWindow.pack();
}
equaliserWindow.setVisible(true);
equaliserWindow.setLocationRelativeTo(this);
}
public boolean enableMicrophone() {
AudioFormat format = Options.getAudioFormat();
System.err.println(format);
Mixer.Info mixer = Options.getRecordingMixer();
@@ -2375,6 +2325,33 @@ public class AudiobookRecorder extends JFrame {
}
}
// Now grab any used effects that aren't already part of the book folder
ArrayList<String> usedEffects = book.getUsedEffects();
for (String ef : usedEffects) {
File inBook = new File(bookDir, ef + ".eff");
if (!inBook.exists()) {
File sys = new File(storageDir, "System");
File sysFile = new File(sys, ef + ".eff");
if (sysFile.exists()) {
ZipEntry entry = new ZipEntry(name + "/" + ef + ".eff");
entry.setSize(sysFile.length());
entry.setTime(sysFile.lastModified());
zos.putNextEntry(entry);
FileInputStream fis = new FileInputStream(sysFile);
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = fis.read(buffer, 0, 1024)) != -1) {
zos.write(buffer, 0, bytesRead);
}
fis.close();
zos.closeEntry();
}
}
}
zos.flush();
zos.close();
@@ -2530,7 +2507,194 @@ public class AudiobookRecorder extends JFrame {
public void updateWaveform() {
if (selectedSentence != null) {
sampleWaveform.setData(selectedSentence.getAudioData());
sampleWaveform.setData(selectedSentence.getDoubleAudioData());
}
}
public void loadEffects() {
effects = new HashMap<String,EffectGroup>();
loadEffectsFromFolder(new File(Options.get("path.storage"), "System"));
if (book != null) {
loadEffectsFromFolder(new File(Options.get("path.storage"), book.getName()));
}
updateEffectChains();
}
public void loadEffectsFromFolder(File dir) {
if (dir == null) return;
File[] files = dir.listFiles();
for (File f : files) {
if (f.getName().endsWith(".eff")) {
EffectGroup g = loadEffect(f);
if (g != null) {
String fn = f.getName().replace(".eff","");
effects.put(fn, g);
}
}
}
}
public EffectGroup loadEffect(File xml) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(xml);
Element root = document.getDocumentElement();
if (root.getTagName().equals("effect")) {
EffectGroup g = loadEffectGroup(root);
return g;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public EffectGroup loadEffectGroup(Element root) {
EffectGroup group = new EffectGroup(root.getAttribute("name"));
NodeList kids = root.getChildNodes();
for (int i = 0; i < kids.getLength(); i++) {
Node kid = kids.item(i);
if (kid instanceof Element) {
Element e = (Element)kid;
if (e.getTagName().equals("biquad")) {
Effect eff = (Effect)loadBiquad(e);
if (eff != null) {
group.addEffect(eff);
}
} else if (e.getTagName().equals("delayline")) {
Effect eff = (Effect)loadDelayLine(e);
if (eff != null) {
group.addEffect(eff);
}
} else if (e.getTagName().equals("amplifier")) {
Effect eff = (Effect)loadAmplifier(e);
if (eff != null) {
group.addEffect(eff);
}
} else if (e.getTagName().equals("group")) {
Effect eff = (Effect)loadEffectGroup(e);
if (eff != null) {
group.addEffect(eff);
}
}
}
}
return group;
}
public Biquad loadBiquad(Element root) {
String type = root.getAttribute("type").toLowerCase();
Biquad bq = new Biquad();
if (type.equals("lowpass")) {
bq.setType(Biquad.Lowpass);
} else if (type.equals("highpass")) {
bq.setType(Biquad.Highpass);
} else if (type.equals("bandpass")) {
bq.setType(Biquad.Bandpass);
} else if (type.equals("notch")) {
bq.setType(Biquad.Notch);
} else if (type.equals("peak")) {
bq.setType(Biquad.Peak);
} else if (type.equals("lowshelf")) {
bq.setType(Biquad.Lowshelf);
} else if (type.equals("highshelf")) {
bq.setType(Biquad.Highshelf);
} else {
System.err.println("Bad Biquad type: " + type);
return null;
}
bq.setQ(Utils.s2d(root.getAttribute("q")));
bq.setFc(Utils.s2d(root.getAttribute("fc")));
bq.setPeakGain(Utils.s2d(root.getAttribute("gain")));
return bq;
}
public DelayLine loadDelayLine(Element root) {
DelayLine line = new DelayLine();
NodeList list = root.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node n = list.item(i);
if (n instanceof Element) {
Element e = (Element)n;
if (e.getTagName().equals("delay")) {
int samples = Utils.s2i(e.getAttribute("samples"));
double gain = Utils.s2d(e.getAttribute("gain"));
DelayLineStore store = line.addDelayLine(samples, gain);
NodeList inner = e.getChildNodes();
for (int j = 0; j < inner.getLength(); j++) {
Node in = inner.item(j);
if (in instanceof Element) {
Element ie = (Element)in;
if (ie.getTagName().equals("biquad")) {
Effect eff = (Effect)loadBiquad(ie);
if (eff != null) {
store.addEffect(eff);
}
} else if (ie.getTagName().equals("delayline")) {
Effect eff = (Effect)loadDelayLine(ie);
if (eff != null) {
store.addEffect(eff);
}
} else if (ie.getTagName().equals("amplifier")) {
Effect eff = (Effect)loadAmplifier(ie);
if (eff != null) {
store.addEffect(eff);
}
} else if (ie.getTagName().equals("group")) {
Effect eff = (Effect)loadEffectGroup(ie);
if (eff != null) {
store.addEffect(eff);
}
}
}
}
}
}
}
return line;
}
public Amplifier loadAmplifier(Element root) {
Amplifier a = new Amplifier(Utils.s2d(root.getAttribute("gain")));
return a;
}
public void updateEffectChains() {
while (effectChain.getItemCount() > 0) {
effectChain.removeItemAt(0);
}
for (String k : effects.keySet()) {
Effect e = effects.get(k);
KVPair<String, String> p = new KVPair<String, String>(k, e.toString());
effectChain.addItem(p);
}
}
public void setEffectChain(String key) {
for (int i = 0; i < effectChain.getItemCount(); i++) {
KVPair<String, String> p = effectChain.getItemAt(i);
if (p.getKey().equals(key)) {
effectChain.setSelectedIndex(i);
updateWaveform();
return;
}
}
if (effects.get(defaultEffectChain) != null) {
setEffectChain(defaultEffectChain);
updateWaveform();
} else {
effectChain.setSelectedIndex(0);
updateWaveform();
}
}
}
+67 -21
View File
@@ -1,6 +1,6 @@
package uk.co.majenko.audiobookrecorder;
// Biquad.h
// Biquad.java
//
// Created by Nigel Redmon on 11/24/12
// EarLevel Engineering: earlevel.com
@@ -18,38 +18,44 @@ package uk.co.majenko.audiobookrecorder;
// for your own purposes, free or commercial.
//
public class Biquad {
public static final int bq_type_lowpass = 0;
public static final int bq_type_highpass = 1;
public static final int bq_type_bandpass = 2;
public static final int bq_type_notch = 3;
public static final int bq_type_peak = 4;
public static final int bq_type_lowshelf = 5;
public static final int bq_type_highshelf = 6;
import java.util.ArrayList;
import javax.swing.tree.*;
public class Biquad extends DefaultMutableTreeNode implements Effect {
public static final int Lowpass = 0;
public static final int Highpass = 1;
public static final int Bandpass = 2;
public static final int Notch = 3;
public static final int Peak = 4;
public static final int Lowshelf = 5;
public static final int Highshelf = 6;
int type;
double a0, a1, a2, b1, b2;
double Fc, Q, peakGain;
double z1, z2;
double sampleFrequency;
public Biquad() {
type = bq_type_lowpass;
type = Lowpass;
a0 = 1.0d;
a1 = 0.0d;
a2 = 0.0d;
b1 = 0.0d;
b2 = 0.0d;
Fc = 0.50d;
Fc = 440d;
Q = 0.707d;
peakGain = 0.0d;
z1 = 0.0d;
z2 = 0.0d;
sampleFrequency = 44100d;
}
public Biquad(int type, double Fc, double Q, double peakGainDB) {
setBiquad(type, Fc, Q, peakGainDB);
z1 = 0.0;
z2 = 0.0;
sampleFrequency = 44100d;
}
public void setType(int typei) {
@@ -79,20 +85,27 @@ public class Biquad {
setPeakGain(peakGainDB);
}
public float process(float in) {
public double process(double in) {
double out = in * a0 + z1;
z1 = in * a1 + z2 - b1 * out;
z2 = in * a2 - b2 * out;
return (float)out;
return out;
}
public void init(double sf) {
sampleFrequency = sf;
z1 = 0d;
z2 = 0d;
calcBiquad();
}
void calcBiquad() {
double norm;
double V = Math.pow(10, Math.abs(peakGain) / 20.0);
double K = Math.tan(Math.PI * Fc);
double K = Math.tan(Math.PI * (Fc/sampleFrequency));
switch (type) {
case bq_type_lowpass:
case Lowpass:
norm = 1d / (1d + K / Q + K * K);
a0 = K * K * norm;
a1 = 2d * a0;
@@ -101,7 +114,7 @@ public class Biquad {
b2 = (1d - K / Q + K * K) * norm;
break;
case bq_type_highpass:
case Highpass:
norm = 1d / (1d + K / Q + K * K);
a0 = 1d * norm;
a1 = -2d * a0;
@@ -110,7 +123,7 @@ public class Biquad {
b2 = (1d - K / Q + K * K) * norm;
break;
case bq_type_bandpass:
case Bandpass:
norm = 1d / (1d + K / Q + K * K);
a0 = K / Q * norm;
a1 = 0d;
@@ -119,7 +132,7 @@ public class Biquad {
b2 = (1d - K / Q + K * K) * norm;
break;
case bq_type_notch:
case Notch:
norm = 1d / (1d + K / Q + K * K);
a0 = (1d + K * K) * norm;
a1 = 2d * (K * K - 1d) * norm;
@@ -128,7 +141,7 @@ public class Biquad {
b2 = (1d - K / Q + K * K) * norm;
break;
case bq_type_peak:
case Peak:
if (peakGain >= 0d) { // boost
norm = 1d / (1d + 1d/Q * K + K * K);
a0 = (1d + V/Q * K + K * K) * norm;
@@ -146,7 +159,7 @@ public class Biquad {
b2 = (1d - V/Q * K + K * K) * norm;
}
break;
case bq_type_lowshelf:
case Lowshelf:
if (peakGain >= 0) { // boost
norm = 1d / (1 + Math.sqrt(2d) * K + K * K);
a0 = (1d + Math.sqrt(2d*V) * K + V * K * K) * norm;
@@ -164,7 +177,7 @@ public class Biquad {
b2 = (1d - Math.sqrt(2d*V) * K + V * K * K) * norm;
}
break;
case bq_type_highshelf:
case Highshelf:
if (peakGain >= 0d) { // boost
norm = 1d / (1d + Math.sqrt(2d) * K + K * K);
a0 = (V + Math.sqrt(2d*V) * K + K * K) * norm;
@@ -186,4 +199,37 @@ public class Biquad {
return;
}
public String getName() {
String n = "Biquad Filter (";
switch (type) {
case Lowpass: n += "Lowpass"; break;
case Highpass: n += "Highpass"; break;
case Bandpass: n += "Bandpass"; break;
case Notch: n += "Notch"; break;
case Peak: n += "Peak"; break;
case Lowshelf: n += "Lowshelf"; break;
case Highshelf: n += "Highshelf"; break;
}
n += ", Fc=";
n += Fc;
n += ", Q=";
n += Q;
n += ", Gain=";
n += peakGain;
n += "dB)";
return n;
}
public ArrayList<Effect> getChildEffects() {
return null;
}
public String toString() {
return getName();
}
public void dump() {
System.out.println(toString());
}
}
+26 -7
View File
@@ -8,7 +8,6 @@ import java.util.*;
import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import davaguine.jeq.core.IIRControls;
import javax.sound.sampled.*;
public class Book extends DefaultMutableTreeNode {
@@ -25,10 +24,6 @@ public class Book extends DefaultMutableTreeNode {
ImageIcon icon;
public Equaliser[] equaliser = new Equaliser[2];
float[] eqChannels = new float[31];
Properties prefs;
public Book(Properties p, String bookname) {
@@ -36,8 +31,6 @@ public class Book extends DefaultMutableTreeNode {
prefs = p;
name = bookname;
equaliser[0] = new Equaliser("Default");
equaliser[1] = new Equaliser("Phone");
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name);
}
@@ -187,4 +180,30 @@ public class Book extends DefaultMutableTreeNode {
public void set(String key, Integer value) {
prefs.setProperty(key, "" + value);
}
public File getBookFolder() {
File dir = new File(Options.get("path.storage"), name);
return dir;
}
public ArrayList<String> getUsedEffects() {
ArrayList<String> out = new ArrayList<String>();
for (Enumeration o = children(); o.hasMoreElements();) {
Object ob = (Object)o.nextElement();
if (ob instanceof Chapter) {
Chapter c = (Chapter)ob;
ArrayList<String> effs = c.getUsedEffects();
for (String ef : effs) {
if (out.indexOf(ef) == -1) {
out.add(ef);
}
}
}
}
return out;
}
}
@@ -31,10 +31,6 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
icn.add(Overlays.important, OverlayIcon.TOP_RIGHT);
}
if (s.getEthereal()) {
icn.add(Overlays.filter, OverlayIcon.BOTTOM_RIGHT);
}
ret.setIcon(icn);
} else if (value instanceof Chapter) {
@@ -121,7 +121,8 @@ public class Chapter extends DefaultMutableTreeNode {
attributes.setAudioAttributes(audioAttributes);
AudioFormat format = AudiobookRecorder.window.roomNoise.getAudioFormat();
AudioFormat sampleformat = AudiobookRecorder.window.roomNoise.getAudioFormat();
AudioFormat format = new AudioFormat(sampleformat.getSampleRate(), 16, 1, true, false);
byte[] data;
int fullLength = 0;
@@ -149,7 +150,7 @@ public class Chapter extends DefaultMutableTreeNode {
kidno++;
if (exportDialog != null) exportDialog.setProgress(kidno * 1000 / kids);
Sentence snt = (Sentence)s.nextElement();
data = snt.getRawAudioData();
data = snt.getPCMData();
fullLength += data.length;
fos.write(data);
@@ -214,4 +215,22 @@ public class Chapter extends DefaultMutableTreeNode {
return totalTime;
}
public ArrayList<String> getUsedEffects() {
ArrayList<String> out = new ArrayList<String>();
for (Enumeration o = children(); o.hasMoreElements();) {
Object ob = (Object)o.nextElement();
if (ob instanceof Sentence) {
Sentence s = (Sentence)ob;
String ec = s.getEffectChain();
if (out.indexOf(ec) == -1) {
out.add(ec);
}
}
}
return out;
}
}
@@ -0,0 +1,66 @@
package uk.co.majenko.audiobookrecorder;
import java.util.ArrayList;
import javax.swing.tree.*;
public class DelayLine extends DefaultMutableTreeNode implements Effect {
ArrayList<DelayLineStore> delayLines;
public DelayLine() {
delayLines = new ArrayList<DelayLineStore>();
}
public String getName() {
return "Delay Line (" + delayLines.size() + " lines)";
}
public double process(double sample) {
double s = sample;
for (DelayLineStore d : delayLines) {
double echo = d.pass(sample);
s = mix(s, echo);
}
return s;
}
double mix(double a, double b) {
if ((a < 0) && (b < 0)) {
return (a + b) - (a * b);
}
if ((a > 0) && (b > 0)) {
return (a + b) - (a * b);
}
return a + b;
}
public DelayLineStore addDelayLine(int samples, double gain) {
DelayLineStore s = new DelayLineStore(samples, gain);
delayLines.add(s);
return s;
}
public ArrayList<Effect> getChildEffects() {
return null;
}
public String toString() {
return getName();
}
public void dump() {
System.out.println(toString());
for (DelayLineStore s : delayLines) {
s.dump();
}
}
public void init(double sf) {
for (DelayLineStore s : delayLines) {
s.init(sf);
s.purge();
}
}
}
@@ -0,0 +1,81 @@
package uk.co.majenko.audiobookrecorder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.ArrayList;
public class DelayLineStore {
ArrayBlockingQueue<Double> fifo;
double gain;
int numSamples;
ArrayList<Effect> effects;
public DelayLineStore(int s, double g) {
fifo = new ArrayBlockingQueue<Double>(s);
for (int i = 0; i < s; i++) {
fifo.add(0d);
}
numSamples = s;
gain = g;
effects = new ArrayList<Effect>();
}
public double pass(double s) {
try {
for (Effect e : effects) {
s = e.process(s);
}
double v = s * gain;
double t = fifo.poll();
fifo.add(v);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return 0d;
}
public void setSamples(int s) {
fifo = new ArrayBlockingQueue<Double>(s);
for (int i = 0; i < s; i++) {
fifo.add(0d);
}
numSamples = s;
}
public void setGain(double g) {
gain = g;
}
public int getSamples() {
return numSamples;
}
public double getGain() {
return gain;
}
public void purge() {
fifo.clear();
for (int i = 0; i < numSamples; i++) {
fifo.add(0d);
}
}
public void addEffect(Effect e) {
effects.add(e);
}
public void init(double sf) {
for (Effect e : effects) {
e.init(sf);
}
}
public void dump() {
System.out.println(" Samples: " + numSamples + ", gain: " + gain);
for (Effect e : effects) {
e.dump();
}
}
}
@@ -0,0 +1,11 @@
package uk.co.majenko.audiobookrecorder;
import java.util.ArrayList;
public interface Effect {
public double process(double sample);
public String getName();
public ArrayList<Effect> getChildEffects();
public void dump();
public void init(double sr);
}
@@ -0,0 +1,72 @@
package uk.co.majenko.audiobookrecorder;
import java.util.ArrayList;
import javax.swing.tree.*;
public class EffectGroup extends DefaultMutableTreeNode implements Effect {
String name;
ArrayList<Effect> effects;
public EffectGroup(String n) {
name = n;
effects = new ArrayList<Effect>();
}
public EffectGroup() {
name = "Unnamed Group";
effects = new ArrayList<Effect>();
}
public double process(double sample) {
double out = sample;
for (Effect e : effects) {
out = e.process(out);
}
return out;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void addEffect(Effect e) {
effects.add(e);
}
public void clearEffects() {
effects.clear();
}
public void removeEffect(Effect e) {
effects.remove(e);
}
public void removeEffect(int n) {
effects.remove(n);
}
public ArrayList<Effect> getChildEffects() {
return effects;
}
public String toString() {
return name;
}
public void dump() {
System.out.println(toString() + " >> ");
for (Effect e : effects) {
e.dump();
}
}
public void init(double sf) {
for (Effect e : effects) {
e.init(sf);
}
}
}
@@ -1,106 +0,0 @@
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[];
String name;
static final double[] frequencies = {
20d, 25d, 31.5d, 40d, 50d, 63d, 80d, 100d, 125d, 160d, 200d,
250d, 315d, 400d, 500d, 630d, 800d, 1000d, 1250d, 1600d, 2000d,
2500d, 3150d, 4000d, 5000d, 6300d, 8000d, 10000d, 12500d, 16000d,
20000d
};
public Equaliser(String n) {
super();
name = n;
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(frequencies[i]);
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());
}
}
});
JButton load = new JButton("Load from default");
load.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 31; i++) {
channels[i].setValue(Options.getFloat("audio.eq." + i));
}
}
});
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
buttons.add(smooth);
buttons.add(def);
buttons.add(load);
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());
}
}
}
public String getName() {
return name;
}
}
@@ -1,96 +0,0 @@
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;
JLabel frequency;
public EqualiserChannel(double freq) {
super();
value = 0;
slider = new JSlider(-120, 120, 0);
textbox = new JTextField();
String suffix = "Hz";
if (freq > 1000) {
freq /= 1000;
suffix = "kHz";
}
String ftxt = String.format("%.4f", freq);
while (ftxt.endsWith("0")) {
ftxt = ftxt.substring(0, ftxt.length() - 1);
}
if (ftxt.endsWith(".")) {
ftxt = ftxt.substring(0, ftxt.length() - 1);
}
frequency = new JLabel(ftxt + suffix);
setLayout(new BorderLayout());
add(frequency, BorderLayout.NORTH);
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));
}
}
@@ -86,9 +86,9 @@ public class MainToolBar extends JToolBar {
add(stopPlaying);
addSeparator();
eq = new JButtonSpacePlay(Icons.eq, "Equaliser", new ActionListener() {
eq = new JButtonSpacePlay(Icons.eq, "Reload Effects", new ActionListener() {
public void actionPerformed(ActionEvent e) {
root.showEqualiser();
root.loadEffects();
}
});
@@ -8,6 +8,7 @@ import java.awt.event.*;
import java.util.*;
import java.util.prefs.*;
import java.io.*;
import javax.swing.tree.*;
public class Options extends JDialog {
@@ -15,6 +16,8 @@ public class Options extends JDialog {
GridBagConstraints constraint;
public static ArrayList<EffectGroup> effectChains;
JComboBox<KVPair> mixerList;
JComboBox<KVPair> playbackList;
JComboBox<KVPair> channelList;
@@ -42,8 +45,6 @@ public class Options extends JDialog {
JTextField externalEditor;
Equaliser equaliser;
JTextArea startupScript;
ArrayList<JTextField[]> processorList;
@@ -332,11 +333,23 @@ public class Options extends JDialog {
addSeparator(optionsPanel);
tabs.add("Options", new JScrollPane(optionsPanel));
equaliser = new Equaliser("Default");
for (int i = 0; i < 31; i++) {
equaliser.setChannel(i, Options.getFloat("audio.eq." + i));
}
tabs.add("Default EQ", new JScrollPane(equaliser));
JPanel effectChains = new JPanel();
effectChains.setLayout(new BorderLayout());
JPanel effectDetails = new JPanel() {
public Dimension getPreferredSize() {
return new Dimension(200, 400);
}
};
DefaultTreeModel m = new DefaultTreeModel(new DefaultMutableTreeNode("Effect Chains"));
JTree effectChainTree = new JTree(m);
effectChains.add(effectChainTree, BorderLayout.CENTER);
effectChains.add(effectDetails, BorderLayout.EAST);
tabs.add("Effects Chains", new JScrollPane(effectChains));
JPanel startScript = new JPanel();
startScript.setLayout(new BorderLayout());
@@ -440,6 +453,7 @@ public class Options extends JDialog {
setVisible(true);
}
@SuppressWarnings("unchecked")
static KVPair<String, String>[] getRecordingMixerList() {
TreeSet<KVPair<String, String>> list = new TreeSet<KVPair<String, String>>();
@@ -570,38 +584,6 @@ public class Options extends JDialog {
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", "");
defaultPrefs.put("effects.ethereal.offset", "50");
@@ -639,6 +621,14 @@ public class Options extends JDialog {
return 0;
}
public static Double getDouble(String key) {
try {
Double f = Double.parseDouble(get(key));
return f;
} catch (Exception e) {
}
return 0.0d;
}
public static Float getFloat(String key) {
try {
Float f = Float.parseFloat(get(key));
@@ -719,10 +709,6 @@ public class Options extends JDialog {
set("effects.ethereal.iterations", etherealIterations.getValue());
set("effects.ethereal.attenuation", etherealAttenuation.getValue());
for (int i = 0; i < 31; i++) {
set("audio.eq." + i, equaliser.getChannel(i));
}
set("scripts.startup", startupScript.getText());
int procNo = 0;
@@ -778,8 +764,9 @@ public class Options extends JDialog {
}
public static KVPair[] getResolutionList() {
KVPair[] pairs = new KVPair[1];
KVPair[] pairs = new KVPair[2];
pairs[0] = new KVPair<String, String>("16", "16 Bit");
pairs[1] = new KVPair<String, String>("24", "24 Bit");
return pairs;
}
@@ -789,4 +776,52 @@ public class Options extends JDialog {
pairs[1] = new KVPair<String, String>("fft", "FFT Analysis");
return pairs;
}
public static void createEffectChains() {
effectChains = new ArrayList<EffectGroup>();
for (int i = 0; i < 999999; i++) {
if (get("effects." + i + ".name") == null) {
EffectGroup e = new EffectGroup(get("effects." + i + ".name"));
for (int j = 0; i < 999999; j++) {
String type = get("effects." + i + ".children." + j + ".type");
if (type == null) break;
if (type.equals("biquad")) {
int bqt = getInteger("effects." + i + ".children." + j + ".filtertype");
double fc = getDouble("effects." + i + ".children." + j + ".fc");
double q = getDouble("effects." + i + ".children." + j + ".q");
double gain = getDouble("effects." + i + ".children." + j + ".gain");
Biquad b = new Biquad(bqt, fc, q, gain);
e.addEffect(b);
continue;
}
if (type.equals("amplifier")) {
double gain = getDouble("effects." + i + ".children." + j + ".gain");
Amplifier a = new Amplifier(gain);
e.addEffect(a);
continue;
}
if (type.equals("delayline")) {
DelayLine l = new DelayLine();
for (int c = 0; c < 999999; c++) {
if (get("effects." + i + ".children." + j + ".lines." + c + ".samples") == null) break;
int samples = getInteger("effects." + i + ".children." + j + ".lines." + c + ".samples");
double gain = getDouble("effects." + i + ".children." + j + ".lines." + c + ".gain");
l.addDelayLine(samples, gain);
}
e.addEffect(l);
continue;
}
}
effectChains.add(e);
break;
}
}
}
}
@@ -0,0 +1,28 @@
package uk.co.majenko.audiobookrecorder;
public class Sample {
double left;
double right;
public Sample(double m) {
left = m;
right = m;
}
public Sample(double l, double r) {
left = l;
right = r;
}
public double getLeft() {
return left;
}
public double getRight() {
return right;
}
public double getMono() {
return (left + right) / 2.0;
}
}
+210 -310
View File
@@ -9,8 +9,6 @@ import java.io.*;
import java.nio.file.*;
import javax.swing.tree.*;
import javax.sound.sampled.*;
import davaguine.jeq.spi.EqualizerInputStream;
import davaguine.jeq.core.IIRControls;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -47,7 +45,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
boolean inSample;
boolean attention = false;
int eqProfile = 0;
String effectChain = null;
double gain = 1.0d;
@@ -69,7 +67,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
AudioFormat storedFormat = null;
double storedLength = -1d;
int[] storedAudioData = null;
double[] audioData = null;
RecordingThread recordingThread;
@@ -89,6 +87,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
tempFile = tf;
wavFile = wf;
format = af;
System.err.println(format);
}
public void run() {
@@ -163,7 +162,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
recordingThread = new RecordingThread(getTempFile(), getFile(), AudiobookRecorder.window.book.getAudioFormat());
recordingThread = new RecordingThread(getTempFile(), getFile(), Options.getAudioFormat());
Thread rc = new Thread(recordingThread);
rc.setDaemon(true);
@@ -181,7 +180,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
}
storedAudioData = null;
audioData = null;
storedFormat = null;
storedLength = -1;
@@ -205,7 +204,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
double[] real = new double[FFTBuckets];
double[] imag = new double[FFTBuckets];
int[] samples = getAudioData();
double[] samples = getProcessedAudioData();
int slices = (samples.length / FFTBuckets) + 1;
double[][] out = new double[slices][];
@@ -215,7 +214,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
for (int i = 0; i < samples.length; i += FFTBuckets) {
for (int j = 0; j < FFTBuckets; j++) {
if (i + j < samples.length) {
real[j] = samples[i+j] / 32768d;
real[j] = samples[i+j];
imag[j] = 0;
} else {
real[j] = 0;
@@ -234,7 +233,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void autoTrimSampleFFT() {
crossStartOffset = -1;
crossEndOffset = -1;
int[] samples = getAudioData();
double[] samples = getProcessedAudioData();
if (samples == null) return;
int blocks = samples.length / 4096 + 1;
@@ -248,7 +247,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
for (int j = 0; j < 4096; j++) {
if (i + j < samples.length) {
real[j] = samples[i+j] / 32768d;
real[j] = samples[i+j];
imag[j] = 0;
} else {
real[j] = 0;
@@ -316,9 +315,9 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void autoTrimSamplePeak() {
crossStartOffset = -1;
crossEndOffset = -1;
int[] samples = getAudioData();
double[] samples = getProcessedAudioData();
if (samples == null) return;
int noiseFloor = AudiobookRecorder.window.getNoiseFloor();
double noiseFloor = AudiobookRecorder.window.getNoiseFloor();
// Find start
for (int i = 0; i < samples.length; i++) {
@@ -425,91 +424,6 @@ 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();
int bytes = frameSize / chans;
byte[] frame = new byte[frameSize];
int[] samples = new int[(int)len];
for (long fno = 0; fno < len; fno++) {
s.read(frame);
int sample = 0;
if (chans == 2) { // Stereo
int left = (frame[1] << 8) | frame[0];
int right = (frame[3] << 8) | frame[2];
sample = (left + right) / 2;
} else {
sample = (frame[1] << 8) | frame[0];
}
if (amplify) {
double amped = (double)sample * gain;
samples[(int)fno] = (int)amped;
} else {
samples[(int)fno] = sample;
}
}
return samples;
}
public int[] getAudioData() {
if (storedAudioData != null) {
return storedAudioData;
}
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = getAudioFormat();
int[] samples = null;
switch (format.getSampleSizeInBits()) {
case 16:
samples = getAudioDataS16LE(s, format);
break;
}
s.close();
sampleSize = samples.length;
storedAudioData = samples;
CacheManager.addToCache(this);
return samples;
} catch (Exception e) {
}
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;
}
@@ -559,33 +473,11 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public int getSampleSize() {
if (sampleSize == -1) {
getAudioData();
loadFile();
}
return sampleSize;
}
// Open the audio file as an AudioInputStream and automatically
// skip the first startOffset frames.
public EqualizerInputStream getAudioStream() {
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
EqualizerInputStream eq = new EqualizerInputStream(s, 31);
AudioFormat format = getAudioFormat();
IIRControls controls = eq.getControls();
AudiobookRecorder.window.book.equaliser[eqProfile].apply(controls, format.getChannels());
int frameSize = format.getFrameSize();
eq.skip(frameSize * startOffset);
return eq;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public AudioFormat getAudioFormat() {
if (storedFormat != null) return storedFormat;
@@ -600,150 +492,6 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
return null;
}
public byte[] getRawAudioData() {
File f = getFile();
try {
updateCrossings();
AudioInputStream s = AudioSystem.getAudioInputStream(f);
EqualizerInputStream eq = new EqualizerInputStream(s, 31);
AudioFormat format = getAudioFormat();
IIRControls controls = eq.getControls();
AudiobookRecorder.window.book.equaliser[eqProfile].apply(controls, format.getChannels());
int frameSize = format.getFrameSize();
int length = crossEndOffset - crossStartOffset;
int bytesToRead = length * frameSize;
byte[] data = new byte[bytesToRead];
byte[] buf = new byte[65536];
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--;
}
}
data = postProcessData(data);
return data;
} catch (Exception e) {
e.printStackTrace();
}
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();
int bytesPerChannel = frameSize / channels;
int frames = data.length / frameSize;
int byteNo = 0;
double fpms = (double)format.getFrameRate() / 1000d;
double doubleOffset = fpms * (double) AudiobookRecorder.window.book.getInteger("effects.ethereal.offset");
int offset = (int)doubleOffset;
double attenuation = 1d - ((double)AudiobookRecorder.window.book.getInteger("effects.ethereal.attenuation") / 100d);
int copies = AudiobookRecorder.window.book.getInteger("effects.ethereal.iterations");
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;
int used = 0;
for (int j = 0; j < copies; j++) {
if (i + (j * offset) < frames) {
used++;
int lx = data[(i + (j * offset)) * frameSize] >= 0 ? data[(i + (j * offset)) * frameSize] : 256 + data[(i + (j * offset)) * frameSize];
int hx = data[((i + (j * offset)) * frameSize) + 1] >= 0 ? data[((i + (j * offset)) * frameSize) + 1] : 256 + data[((i + (j * offset)) * frameSize) + 1];
int futureSample = (hx << 8) | lx;
if ((futureSample & 0x8000) == 0x8000) futureSample |= 0xFFFF0000;
double futureDouble = (double)futureSample;
for (int k = 0; k < copies; k++) {
futureDouble *= attenuation;
}
sampleDouble = mix(sampleDouble, futureDouble);
}
}
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;
}
public void recognise() {
AudiobookRecorder.window.havenQueue.submit(Sentence.this);
@@ -766,8 +514,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public void clearCache() {
storedAudioData = null;
// System.gc();
audioData = null;
}
public boolean lockedInCache() {
@@ -775,7 +522,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public int findNearestZeroCrossing(int pos, int range) {
int[] data = getAudioData();
double[] data = getProcessedAudioData();
if (data == null) return 0;
if (data.length == 0) return 0;
@@ -785,8 +532,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
int backwards = pos;
int forwards = pos;
int backwardsPrev = data[backwards];
int forwardsPrev = data[forwards];
double backwardsPrev = data[backwards];
double forwardsPrev = data[forwards];
while (backwards > 0 || forwards < data.length-2) {
@@ -864,6 +611,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
havenStatus = i;
}
@SuppressWarnings("deprecation")
public boolean postHavenData() {
String apiKey = Options.get("process.haven.apikey");
if (apiKey == null || apiKey.equals("")) return false;
@@ -978,36 +726,15 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
}
public void setEthereal(boolean e) {
effectEthereal = e;
}
public boolean getEthereal() {
return effectEthereal;
}
public double mix(double a, double b) {
double z;
double fa, fb, fz;
fa = a + 32768d;
fb = b + 32768d;
if (fa < 32768d && fb < 32768d) {
fz = (fa * fb) / 32768d;
} else {
fz = (2d * (fa + fb)) - ((fa * fb) / 32768d) - 65536d;
}
z = fz - 32768d;
return z;
}
public int getPeakValue() {
int[] samples = getUnprocessedAudioData();
public double getPeakValue() {
double oldGain = gain;
gain = 1.0d;
double[] samples = getProcessedAudioData();
gain = oldGain;
if (samples == null) {
return 0;
}
int ms = 0;
double ms = 0;
for (int i = 0; i < samples.length; i++) {
if (Math.abs(samples[i]) > ms) {
ms = Math.abs(samples[i]);
@@ -1017,9 +744,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
}
public int getHeadroom() {
int nf = getPeakValue();
if (nf == 0) return 0;
double r = nf / 32767d;
double r = getPeakValue();
if (r == 0) return 0;
double l10 = Math.log10(r);
double db = 20d * l10;
@@ -1039,20 +765,12 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
public void normalize() {
if (locked) return;
int max = getPeakValue();
double d = 23192d / max;
if (d > 1.1d) d = 1.1d;
double max = getPeakValue();
double d = 0.708 / max;
if (d > 1d) d = 1d;
setGain(d);
}
public int getEQProfile() {
return eqProfile;
}
public void setEQProfile(int e) {
eqProfile = e;
}
class ExternalEditor implements Runnable {
Sentence sentence;
ExternalEditor(Sentence s) {
@@ -1219,4 +937,186 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
AudiobookRecorder.window.updateWaveform();
}
public double[] getDoubleDataS16LE(AudioInputStream s, AudioFormat format) throws IOException {
long len = s.getFrameLength();
int frameSize = format.getFrameSize();
int chans = format.getChannels();
int bytes = frameSize / chans;
byte[] frame = new byte[frameSize];
double[] samples = new double[(int)len];
for (long fno = 0; fno < len; fno++) {
s.read(frame);
int sample = 0;
if (chans == 2) { // Stereo
int ll = frame[0] >= 0 ? frame[0] : 256 + frame[0];
int lh = frame[1] >= 0 ? frame[1] : 256 + frame[1];
int rl = frame[2] >= 0 ? frame[2] : 256 + frame[2];
int rh = frame[3] >= 0 ? frame[3] : 256 + frame[3];
int left = (lh << 8) | ll;
int right = (rh << 8) | rl;
if ((left & 0x8000) == 0x8000) left |= 0xFFFF0000;
if ((right & 0x8000) == 0x8000) right |= 0xFFFF0000;
sample = (left + right) / 2;
} else {
int l = frame[0] >= 0 ? frame[0] : 256 + frame[0];
int h = frame[1] >= 0 ? frame[1] : 256 + frame[1];
sample = (h << 8) | l;
if ((sample & 0x8000) == 0x8000) sample |= 0xFFFF0000;
}
samples[(int)fno] = (double)sample / 32768d;
}
return samples;
}
public double[] getDoubleDataS24LE(AudioInputStream s, AudioFormat format) throws IOException {
long len = s.getFrameLength();
int frameSize = format.getFrameSize();
int chans = format.getChannels();
int bytes = frameSize / chans;
byte[] frame = new byte[frameSize];
double[] samples = new double[(int)len];
for (long fno = 0; fno < len; fno++) {
s.read(frame);
int sample = 0;
if (chans == 2) { // Stereo
int ll = frame[0] >= 0 ? frame[0] : 256 + frame[0];
int lm = frame[1] >= 0 ? frame[1] : 256 + frame[1];
int lh = frame[2] >= 0 ? frame[2] : 256 + frame[2];
int rl = frame[3] >= 0 ? frame[3] : 256 + frame[3];
int rm = frame[4] >= 0 ? frame[4] : 256 + frame[4];
int rh = frame[5] >= 0 ? frame[5] : 256 + frame[5];
int left = (lh << 16) | (lm << 8) | ll;
int right = (rh << 16) | (rm << 8) | rl;
if ((left & 0x800000) == 0x800000) left |= 0xFF000000;
if ((right & 0x800000) == 0x800000) right |= 0xFF000000;
sample = (left + right) / 2;
} else {
int l = frame[0] >= 0 ? frame[0] : 256 + frame[0];
int m = frame[1] >= 0 ? frame[1] : 256 + frame[1];
int h = frame[2] >= 0 ? frame[2] : 256 + frame[2];
sample = (h << 16) | (m << 8) | l;
if ((sample & 0x800000) == 0x800000) sample |= 0xFF000000;
}
samples[(int)fno] = (double)sample / 8388608d;
}
return samples;
}
public void loadFile() {
if (audioData != null) return;
File f = getFile();
try {
AudioInputStream s = AudioSystem.getAudioInputStream(f);
AudioFormat format = getAudioFormat();
double[] samples = null;
switch (format.getSampleSizeInBits()) {
case 16:
samples = getDoubleDataS16LE(s, format);
break;
case 24:
samples = getDoubleDataS24LE(s, format);
break;
}
s.close();
sampleSize = samples.length;
audioData = samples;
CacheManager.addToCache(this);
} catch (Exception e) {
}
}
public double[] getProcessedAudioData() {
loadFile();
if (audioData == null) return null;
double[] samples = new double[audioData.length];
for (int i = 0; i < audioData.length; i++) {
samples[i] = audioData[i];
}
// Add processing in here.
if (effectChain == null) effectChain = AudiobookRecorder.window.defaultEffectChain;
Effect eff = AudiobookRecorder.window.effects.get(effectChain);
if (eff == null) {
effectChain = AudiobookRecorder.window.defaultEffectChain;
eff = AudiobookRecorder.window.effects.get(effectChain);
}
if (eff != null) {
eff.init(getAudioFormat().getFrameRate());
for (int i = 0; i < samples.length; i++) {
samples[i] = eff.process(samples[i]);
}
}
// Cuts out the computer hum.
// Biquad bq = new Biquad(Biquad.Notch, 140d/44100d, 20.0d, -50);
// DelayLine dl = new DelayLine();
// dl.addDelayLine(2205, 0.8);
// dl.addDelayLine(4410, 0.4);
// dl.addDelayLine(6615, 0.2);
// Add final master gain stage
for (int i = 0; i < samples.length; i++) {
samples[i] = samples[i] * gain;
}
return samples;
}
public double[] getDoubleAudioData() {
return getProcessedAudioData();
}
public double[] getCroppedAudioData() {
double[] inSamples = getDoubleAudioData();
if (inSamples == null) return null;
updateCrossings();
int length = crossEndOffset - crossStartOffset;
double[] samples = new double[length];
for (int i = 0; i < length; i++) {
samples[i] = inSamples[crossStartOffset + i];
}
return samples;
}
public byte[] getPCMData() {
double[] croppedData = getCroppedAudioData();
if (croppedData == null) return null;
int length = croppedData.length;
byte[] pcmData = new byte[length * 2];
for (int i = 0; i < length; i++) {
double sd = croppedData[i] * 32768d;
int si = (int)sd;
if (si > 32767) si = 32767;
if (si < -32768) si = -32768;
pcmData[i * 2] = (byte)(si & 0xFF);
pcmData[(i * 2) + 1] = (byte)((si & 0xFF00) >> 8);
}
return pcmData;
}
public void setEffectChain(String key) {
effectChain = key;
}
public String getEffectChain() {
if (effectChain == null) return AudiobookRecorder.window.defaultEffectChain;
return effectChain;
}
}
@@ -9,7 +9,7 @@ import javax.sound.sampled.*;
public class Waveform extends JPanel implements MouseListener, MouseMotionListener {
int[] samples = null;
double[] samples = null;
int leftMarker = 0;
int rightMarker = 0;
@@ -67,7 +67,7 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
g.drawLine(x, h/4*3, x, h/4*3);
}
int scale = 32768/(h/2);
double scale = (h/2);
if (samples != null) {
@@ -81,15 +81,15 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
for (int n = 0; n < w; n++) {
int hcnt = 0;
long have = 0;
int hmax = 0;
double have = 0;
double hmax = 0;
int lcnt = 0;
int lave = 0;
int lmax = 0;
double lave = 0;
double lmax = 0;
for (int o = 0; o < step; o++) {
int sample = samples[offset + (n * step) + o];
double sample = samples[offset + (n * step) + o];
if (sample >= 0) {
have += sample;
hcnt++;
@@ -107,27 +107,27 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
boolean clip = false;
if (lmax > 32000) { // -3dB == 23198?
if (lmax > 0.708) { // -3dB == 23198?
clip = true;
}
if (hmax > 32000) { // -3dB
if (hmax > 0.708) { // -3dB
clip = true;
}
hmax /= scale;
lmax /= scale;
have /= scale;
lave /= scale;
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.drawLine(n, (int)(h/2 + lmax), n, (int)(h/2 - hmax));
g.setColor(new Color(0, 100, 255));
g.drawLine(n, h/2 + (int)lave, n, h/2 - (int)have);
g.drawLine(n, (int)(h/2 + lave), n, (int)(h/2 - have));
}
g.setColor(new Color(255, 0, 0, 32));
@@ -186,7 +186,7 @@ public class Waveform extends JPanel implements MouseListener, MouseMotionListen
repaint();
}
public void setData(int[] s) {
public void setData(double[] s) {
samples = s;
playMarker = 0;
repaint();