Added ethereal voice effect
This commit is contained in:
@@ -77,6 +77,7 @@ public class AudiobookRecorder extends JFrame {
|
||||
JSpinner postSentenceGap;
|
||||
JCheckBox locked;
|
||||
JCheckBox attention;
|
||||
JCheckBox ethereal;
|
||||
|
||||
JButtonSpacePlay reprocessAudioFFT;
|
||||
JButtonSpacePlay reprocessAudioPeak;
|
||||
@@ -443,6 +444,27 @@ 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);
|
||||
@@ -1015,6 +1037,7 @@ public class AudiobookRecorder extends JFrame {
|
||||
JMenuItem editData = new JMenuItem("Edit Data...");
|
||||
editData.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
BookInfoPanel info = new BookInfoPanel(
|
||||
book.getName(),
|
||||
book.getAuthor(),
|
||||
@@ -1022,7 +1045,38 @@ public class AudiobookRecorder extends JFrame {
|
||||
book.getComment(),
|
||||
book.getACX()
|
||||
);
|
||||
int r = JOptionPane.showConfirmDialog(AudiobookRecorder.this, info, "Edit Book", JOptionPane.OK_CANCEL_OPTION);
|
||||
tabs.add("Data", info);
|
||||
|
||||
JPanel effects = new JPanel();
|
||||
effects.setLayout(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
|
||||
effects.add(new JLabel("Ethereal Iterations:"), 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++;
|
||||
|
||||
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++;
|
||||
|
||||
tabs.add("Effects", effects);
|
||||
|
||||
int r = JOptionPane.showConfirmDialog(AudiobookRecorder.this, tabs, "Edit Book", JOptionPane.OK_CANCEL_OPTION);
|
||||
if (r != JOptionPane.OK_OPTION) return;
|
||||
|
||||
String tit = info.getTitle();
|
||||
@@ -1031,6 +1085,10 @@ 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());
|
||||
|
||||
book.setAuthor(aut);
|
||||
book.setGenre(gen);
|
||||
book.setComment(com);
|
||||
@@ -1297,6 +1355,9 @@ 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());
|
||||
@@ -1330,6 +1391,7 @@ 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");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@@ -1379,7 +1441,7 @@ public class AudiobookRecorder extends JFrame {
|
||||
|
||||
public void buildBook(Properties prefs) {
|
||||
|
||||
book = new Book(prefs.getProperty("book.name"));
|
||||
book = new Book(prefs, prefs.getProperty("book.name"));
|
||||
|
||||
book.setAuthor(prefs.getProperty("book.author"));
|
||||
book.setGenre(prefs.getProperty("book.genre"));
|
||||
@@ -1439,6 +1501,7 @@ public class AudiobookRecorder extends JFrame {
|
||||
postSentenceGap.setValue(s.getPostGap());
|
||||
locked.setSelected(s.isLocked());
|
||||
attention.setSelected(s.getAttentionFlag());
|
||||
ethereal.setSelected(s.getEthereal());
|
||||
|
||||
postSentenceGap.setEnabled(!s.isLocked());
|
||||
reprocessAudioFFT.setEnabled(!s.isLocked());
|
||||
@@ -1449,6 +1512,7 @@ public class AudiobookRecorder extends JFrame {
|
||||
postSentenceGap.setValue(0);
|
||||
locked.setSelected(false);
|
||||
attention.setSelected(false);
|
||||
ethereal.setSelected(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1488,6 +1552,7 @@ 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))));
|
||||
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||
}
|
||||
|
||||
@@ -1507,6 +1572,7 @@ 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))));
|
||||
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||
}
|
||||
|
||||
@@ -1531,7 +1597,8 @@ public class AudiobookRecorder extends JFrame {
|
||||
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))));
|
||||
s.setAttentionFlag(Utils.s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.attention", 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))));
|
||||
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||
}
|
||||
}
|
||||
@@ -1552,6 +1619,7 @@ 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))));
|
||||
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,12 @@ public class Book extends DefaultMutableTreeNode {
|
||||
|
||||
float[] eqChannels = new float[31];
|
||||
|
||||
public Book(String bookname) {
|
||||
Properties prefs;
|
||||
|
||||
public Book(Properties p, String bookname) {
|
||||
super(bookname);
|
||||
|
||||
prefs = p;
|
||||
name = bookname;
|
||||
equaliser = new Equaliser();
|
||||
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name);
|
||||
@@ -164,4 +168,22 @@ public class Book extends DefaultMutableTreeNode {
|
||||
public AudioFormat getAudioFormat() {
|
||||
return new AudioFormat(getSampleRate(), getResolution(), getChannels(), true, false);
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
if (prefs.getProperty(key) == null) { return Options.get(key); }
|
||||
return prefs.getProperty(key);
|
||||
}
|
||||
|
||||
public Integer getInteger(String key) {
|
||||
if (prefs.getProperty(key) == null) { return Options.getInteger(key); }
|
||||
return Utils.s2i(prefs.getProperty(key));
|
||||
}
|
||||
|
||||
public void set(String key, String value) {
|
||||
prefs.setProperty(key, value);
|
||||
}
|
||||
|
||||
public void set(String key, Integer value) {
|
||||
prefs.setProperty(key, "" + value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ public class Options extends JDialog {
|
||||
JCheckBox enableParsing;
|
||||
JSpinner cacheSize;
|
||||
|
||||
JSpinner etherealIterations;
|
||||
JSpinner etherealAttenuation;
|
||||
JSpinner etherealOffset;
|
||||
|
||||
JTextField havenApiKey;
|
||||
|
||||
Equaliser equaliser;
|
||||
@@ -319,6 +323,26 @@ public class Options extends JDialog {
|
||||
startupScript = new JTextArea(get("scripts.startup"));
|
||||
startScript.add(startupScript, BorderLayout.CENTER);
|
||||
tabs.add("Startup Script", startScript);
|
||||
|
||||
|
||||
|
||||
JPanel effects = new JPanel();
|
||||
effects.setLayout(new GridBagLayout());
|
||||
constraint.gridx = 0;
|
||||
constraint.gridy = 0;
|
||||
constraint.gridwidth = 1;
|
||||
constraint.gridheight = 1;
|
||||
|
||||
etherealOffset = addSpinner(effects, "Ethereal Voice Offset", 0, 2000, 10, getInteger("effects.ethereal.offset"), "ms");
|
||||
etherealIterations = addSpinner(effects, "Ethereal Voice Iterations", 1, 10, 1, getInteger("effects.ethereal.iterations"), "");
|
||||
etherealAttenuation = addSpinner(effects, "Ethereal Voice Attenuation", 0, 100, 1, getInteger("effects.ethereal.attenuation"), "%");
|
||||
|
||||
tabs.add("Effects", effects);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
add(tabs, BorderLayout.CENTER);
|
||||
|
||||
setTitle("Options");
|
||||
@@ -525,6 +549,10 @@ public class Options extends JDialog {
|
||||
|
||||
defaultPrefs.put("scripts.startup", "");
|
||||
|
||||
defaultPrefs.put("effects.ethereal.offset", "50");
|
||||
defaultPrefs.put("effects.ethereal.iterations", "3");
|
||||
defaultPrefs.put("effects.ethereal.attenuation", "50");
|
||||
|
||||
if (prefs == null) {
|
||||
prefs = Preferences.userNodeForPackage(AudiobookRecorder.class);
|
||||
}
|
||||
@@ -631,6 +659,10 @@ public class Options extends JDialog {
|
||||
set("process.haven.apikey", havenApiKey.getText());
|
||||
set("cache.size", cacheSize.getValue());
|
||||
|
||||
set("effects.ethereal.offset", etherealOffset.getValue());
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
|
||||
RecordingThread recordingThread;
|
||||
|
||||
boolean effectEthereal = false;
|
||||
|
||||
static class RecordingThread implements Runnable {
|
||||
|
||||
boolean running = false;
|
||||
@@ -594,6 +596,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
bytesToRead--;
|
||||
}
|
||||
}
|
||||
|
||||
data = postProcessData(data);
|
||||
|
||||
return data;
|
||||
} catch (Exception e) {
|
||||
@@ -602,6 +606,69 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] postProcessData(byte[] data) {
|
||||
if (effectEthereal) {
|
||||
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;
|
||||
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public void recognise() {
|
||||
AudiobookRecorder.window.havenQueue.submit(Sentence.this);
|
||||
}
|
||||
@@ -834,4 +901,29 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user