|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package com.sun.media.sound; |
|
|
|
import java.util.Arrays; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class SoftReverb implements SoftAudioProcessor { |
|
|
|
private final static class Delay { |
|
|
|
private float[] delaybuffer; |
|
private int rovepos = 0; |
|
|
|
Delay() { |
|
delaybuffer = null; |
|
} |
|
|
|
public void setDelay(int delay) { |
|
if (delay == 0) |
|
delaybuffer = null; |
|
else |
|
delaybuffer = new float[delay]; |
|
rovepos = 0; |
|
} |
|
|
|
public void processReplace(float[] inout) { |
|
if (delaybuffer == null) |
|
return; |
|
int len = inout.length; |
|
int rnlen = delaybuffer.length; |
|
int rovepos = this.rovepos; |
|
|
|
for (int i = 0; i < len; i++) { |
|
float x = inout[i]; |
|
inout[i] = delaybuffer[rovepos]; |
|
delaybuffer[rovepos] = x; |
|
if (++rovepos == rnlen) |
|
rovepos = 0; |
|
} |
|
this.rovepos = rovepos; |
|
} |
|
} |
|
|
|
private final static class AllPass { |
|
|
|
private final float[] delaybuffer; |
|
private final int delaybuffersize; |
|
private int rovepos = 0; |
|
private float feedback; |
|
|
|
AllPass(int size) { |
|
delaybuffer = new float[size]; |
|
delaybuffersize = size; |
|
} |
|
|
|
public void setFeedBack(float feedback) { |
|
this.feedback = feedback; |
|
} |
|
|
|
public void processReplace(float inout[]) { |
|
int len = inout.length; |
|
int delaybuffersize = this.delaybuffersize; |
|
int rovepos = this.rovepos; |
|
for (int i = 0; i < len; i++) { |
|
float delayout = delaybuffer[rovepos]; |
|
float input = inout[i]; |
|
inout[i] = delayout - input; |
|
delaybuffer[rovepos] = input + delayout * feedback; |
|
if (++rovepos == delaybuffersize) |
|
rovepos = 0; |
|
} |
|
this.rovepos = rovepos; |
|
} |
|
|
|
public void processReplace(float in[], float out[]) { |
|
int len = in.length; |
|
int delaybuffersize = this.delaybuffersize; |
|
int rovepos = this.rovepos; |
|
for (int i = 0; i < len; i++) { |
|
float delayout = delaybuffer[rovepos]; |
|
float input = in[i]; |
|
out[i] = delayout - input; |
|
delaybuffer[rovepos] = input + delayout * feedback; |
|
if (++rovepos == delaybuffersize) |
|
rovepos = 0; |
|
} |
|
this.rovepos = rovepos; |
|
} |
|
} |
|
|
|
private final static class Comb { |
|
|
|
private final float[] delaybuffer; |
|
private final int delaybuffersize; |
|
private int rovepos = 0; |
|
private float feedback; |
|
private float filtertemp = 0; |
|
private float filtercoeff1 = 0; |
|
private float filtercoeff2 = 1; |
|
|
|
Comb(int size) { |
|
delaybuffer = new float[size]; |
|
delaybuffersize = size; |
|
} |
|
|
|
public void setFeedBack(float feedback) { |
|
this.feedback = feedback; |
|
filtercoeff2 = (1 - filtercoeff1)* feedback; |
|
} |
|
|
|
public void processMix(float in[], float out[]) { |
|
int len = in.length; |
|
int delaybuffersize = this.delaybuffersize; |
|
int rovepos = this.rovepos; |
|
float filtertemp = this.filtertemp; |
|
float filtercoeff1 = this.filtercoeff1; |
|
float filtercoeff2 = this.filtercoeff2; |
|
for (int i = 0; i < len; i++) { |
|
float delayout = delaybuffer[rovepos]; |
|
|
|
filtertemp = (delayout * filtercoeff2) |
|
+ (filtertemp * filtercoeff1); |
|
out[i] += delayout; |
|
delaybuffer[rovepos] = in[i] + filtertemp; |
|
if (++rovepos == delaybuffersize) |
|
rovepos = 0; |
|
} |
|
this.filtertemp = filtertemp; |
|
this.rovepos = rovepos; |
|
} |
|
|
|
public void processReplace(float in[], float out[]) { |
|
int len = in.length; |
|
int delaybuffersize = this.delaybuffersize; |
|
int rovepos = this.rovepos; |
|
float filtertemp = this.filtertemp; |
|
float filtercoeff1 = this.filtercoeff1; |
|
float filtercoeff2 = this.filtercoeff2; |
|
for (int i = 0; i < len; i++) { |
|
float delayout = delaybuffer[rovepos]; |
|
|
|
filtertemp = (delayout * filtercoeff2) |
|
+ (filtertemp * filtercoeff1); |
|
out[i] = delayout; |
|
delaybuffer[rovepos] = in[i] + filtertemp; |
|
if (++rovepos == delaybuffersize) |
|
rovepos = 0; |
|
} |
|
this.filtertemp = filtertemp; |
|
this.rovepos = rovepos; |
|
} |
|
|
|
public void setDamp(float val) { |
|
filtercoeff1 = val; |
|
filtercoeff2 = (1 - filtercoeff1)* feedback; |
|
} |
|
} |
|
private float roomsize; |
|
private float damp; |
|
private float gain = 1; |
|
private Delay delay; |
|
private Comb[] combL; |
|
private Comb[] combR; |
|
private AllPass[] allpassL; |
|
private AllPass[] allpassR; |
|
private float[] input; |
|
private float[] out; |
|
private float[] pre1; |
|
private float[] pre2; |
|
private float[] pre3; |
|
private boolean denormal_flip = false; |
|
private boolean mix = true; |
|
private SoftAudioBuffer inputA; |
|
private SoftAudioBuffer left; |
|
private SoftAudioBuffer right; |
|
private boolean dirty = true; |
|
private float dirty_roomsize; |
|
private float dirty_damp; |
|
private float dirty_predelay; |
|
private float dirty_gain; |
|
private float samplerate; |
|
private boolean light = true; |
|
|
|
public void init(float samplerate, float controlrate) { |
|
this.samplerate = samplerate; |
|
|
|
double freqscale = ((double) samplerate) / 44100.0; |
|
// freqscale = 1.0/ freqscale; |
|
|
|
int stereospread = 23; |
|
|
|
delay = new Delay(); |
|
|
|
combL = new Comb[8]; |
|
combR = new Comb[8]; |
|
combL[0] = new Comb((int) (freqscale * (1116))); |
|
combR[0] = new Comb((int) (freqscale * (1116 + stereospread))); |
|
combL[1] = new Comb((int) (freqscale * (1188))); |
|
combR[1] = new Comb((int) (freqscale * (1188 + stereospread))); |
|
combL[2] = new Comb((int) (freqscale * (1277))); |
|
combR[2] = new Comb((int) (freqscale * (1277 + stereospread))); |
|
combL[3] = new Comb((int) (freqscale * (1356))); |
|
combR[3] = new Comb((int) (freqscale * (1356 + stereospread))); |
|
combL[4] = new Comb((int) (freqscale * (1422))); |
|
combR[4] = new Comb((int) (freqscale * (1422 + stereospread))); |
|
combL[5] = new Comb((int) (freqscale * (1491))); |
|
combR[5] = new Comb((int) (freqscale * (1491 + stereospread))); |
|
combL[6] = new Comb((int) (freqscale * (1557))); |
|
combR[6] = new Comb((int) (freqscale * (1557 + stereospread))); |
|
combL[7] = new Comb((int) (freqscale * (1617))); |
|
combR[7] = new Comb((int) (freqscale * (1617 + stereospread))); |
|
|
|
allpassL = new AllPass[4]; |
|
allpassR = new AllPass[4]; |
|
allpassL[0] = new AllPass((int) (freqscale * (556))); |
|
allpassR[0] = new AllPass((int) (freqscale * (556 + stereospread))); |
|
allpassL[1] = new AllPass((int) (freqscale * (441))); |
|
allpassR[1] = new AllPass((int) (freqscale * (441 + stereospread))); |
|
allpassL[2] = new AllPass((int) (freqscale * (341))); |
|
allpassR[2] = new AllPass((int) (freqscale * (341 + stereospread))); |
|
allpassL[3] = new AllPass((int) (freqscale * (225))); |
|
allpassR[3] = new AllPass((int) (freqscale * (225 + stereospread))); |
|
|
|
for (int i = 0; i < allpassL.length; i++) { |
|
allpassL[i].setFeedBack(0.5f); |
|
allpassR[i].setFeedBack(0.5f); |
|
} |
|
|
|
|
|
globalParameterControlChange(new int[]{0x01 * 128 + 0x01}, 0, 4); |
|
|
|
} |
|
|
|
public void setInput(int pin, SoftAudioBuffer input) { |
|
if (pin == 0) |
|
inputA = input; |
|
} |
|
|
|
public void setOutput(int pin, SoftAudioBuffer output) { |
|
if (pin == 0) |
|
left = output; |
|
if (pin == 1) |
|
right = output; |
|
} |
|
|
|
public void setMixMode(boolean mix) { |
|
this.mix = mix; |
|
} |
|
|
|
private boolean silent = true; |
|
|
|
public void processAudio() { |
|
boolean silent_input = this.inputA.isSilent(); |
|
if(!silent_input) |
|
silent = false; |
|
if(silent) |
|
{ |
|
if (!mix) { |
|
left.clear(); |
|
right.clear(); |
|
} |
|
return; |
|
} |
|
|
|
float[] inputA = this.inputA.array(); |
|
float[] left = this.left.array(); |
|
float[] right = this.right == null ? null : this.right.array(); |
|
|
|
int numsamples = inputA.length; |
|
if (input == null || input.length < numsamples) |
|
input = new float[numsamples]; |
|
|
|
float again = gain * 0.018f / 2; |
|
|
|
denormal_flip = !denormal_flip; |
|
if(denormal_flip) |
|
for (int i = 0; i < numsamples; i++) |
|
input[i] = inputA[i] * again + 1E-20f; |
|
else |
|
for (int i = 0; i < numsamples; i++) |
|
input[i] = inputA[i] * again - 1E-20f; |
|
|
|
delay.processReplace(input); |
|
|
|
if(light && (right != null)) |
|
{ |
|
if (pre1 == null || pre1.length < numsamples) |
|
{ |
|
pre1 = new float[numsamples]; |
|
pre2 = new float[numsamples]; |
|
pre3 = new float[numsamples]; |
|
} |
|
|
|
for (int i = 0; i < allpassL.length; i++) |
|
allpassL[i].processReplace(input); |
|
|
|
combL[0].processReplace(input, pre3); |
|
combL[1].processReplace(input, pre3); |
|
|
|
combL[2].processReplace(input, pre1); |
|
for (int i = 4; i < combL.length-2; i+=2) |
|
combL[i].processMix(input, pre1); |
|
|
|
combL[3].processReplace(input, pre2);; |
|
for (int i = 5; i < combL.length-2; i+=2) |
|
combL[i].processMix(input, pre2); |
|
|
|
if (!mix) |
|
{ |
|
Arrays.fill(right, 0); |
|
Arrays.fill(left, 0); |
|
} |
|
for (int i = combR.length-2; i < combR.length; i++) |
|
combR[i].processMix(input, right); |
|
for (int i = combL.length-2; i < combL.length; i++) |
|
combL[i].processMix(input, left); |
|
|
|
for (int i = 0; i < numsamples; i++) |
|
{ |
|
float p = pre1[i] - pre2[i]; |
|
float m = pre3[i]; |
|
left[i] += m + p; |
|
right[i] += m - p; |
|
} |
|
} |
|
else |
|
{ |
|
if (out == null || out.length < numsamples) |
|
out = new float[numsamples]; |
|
|
|
if (right != null) { |
|
if (!mix) |
|
Arrays.fill(right, 0); |
|
allpassR[0].processReplace(input, out); |
|
for (int i = 1; i < allpassR.length; i++) |
|
allpassR[i].processReplace(out); |
|
for (int i = 0; i < combR.length; i++) |
|
combR[i].processMix(out, right); |
|
} |
|
|
|
if (!mix) |
|
Arrays.fill(left, 0); |
|
allpassL[0].processReplace(input, out); |
|
for (int i = 1; i < allpassL.length; i++) |
|
allpassL[i].processReplace(out); |
|
for (int i = 0; i < combL.length; i++) |
|
combL[i].processMix(out, left); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (silent_input) { |
|
silent = true; |
|
for (int i = 0; i < numsamples; i++) |
|
{ |
|
float v = left[i]; |
|
if(v > 1E-10 || v < -1E-10) |
|
{ |
|
silent = false; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
public void globalParameterControlChange(int[] slothpath, long param, |
|
long value) { |
|
if (slothpath.length == 1) { |
|
if (slothpath[0] == 0x01 * 128 + 0x01) { |
|
|
|
if (param == 0) { |
|
if (value == 0) { |
|
// Small Room A small size room with a length |
|
|
|
dirty_roomsize = (1.1f); |
|
dirty_damp = (5000); |
|
dirty_predelay = (0); |
|
dirty_gain = (4); |
|
dirty = true; |
|
} |
|
if (value == 1) { |
|
// Medium Room A medium size room with a length |
|
|
|
dirty_roomsize = (1.3f); |
|
dirty_damp = (5000); |
|
dirty_predelay = (0); |
|
dirty_gain = (3); |
|
dirty = true; |
|
} |
|
if (value == 2) { |
|
// Large Room A large size room suitable for |
|
|
|
dirty_roomsize = (1.5f); |
|
dirty_damp = (5000); |
|
dirty_predelay = (0); |
|
dirty_gain = (2); |
|
dirty = true; |
|
} |
|
if (value == 3) { |
|
|
|
dirty_roomsize = (1.8f); |
|
dirty_damp = (24000); |
|
dirty_predelay = (0.02f); |
|
dirty_gain = (1.5f); |
|
dirty = true; |
|
} |
|
if (value == 4) { |
|
// Large Hall A large size concert hall |
|
|
|
dirty_roomsize = (1.8f); |
|
dirty_damp = (24000); |
|
dirty_predelay = (0.03f); |
|
dirty_gain = (1.5f); |
|
dirty = true; |
|
} |
|
if (value == 8) { |
|
|
|
dirty_roomsize = (1.3f); |
|
dirty_damp = (2500); |
|
dirty_predelay = (0); |
|
dirty_gain = (6); |
|
dirty = true; |
|
} |
|
} else if (param == 1) { |
|
dirty_roomsize = ((float) (Math.exp((value - 40) * 0.025))); |
|
dirty = true; |
|
} |
|
|
|
} |
|
} |
|
} |
|
|
|
public void processControlLogic() { |
|
if (dirty) { |
|
dirty = false; |
|
setRoomSize(dirty_roomsize); |
|
setDamp(dirty_damp); |
|
setPreDelay(dirty_predelay); |
|
setGain(dirty_gain); |
|
} |
|
} |
|
|
|
public void setRoomSize(float value) { |
|
roomsize = 1 - (0.17f / value); |
|
|
|
for (int i = 0; i < combL.length; i++) { |
|
combL[i].feedback = roomsize; |
|
combR[i].feedback = roomsize; |
|
} |
|
} |
|
|
|
public void setPreDelay(float value) { |
|
delay.setDelay((int)(value * samplerate)); |
|
} |
|
|
|
public void setGain(float gain) { |
|
this.gain = gain; |
|
} |
|
|
|
public void setDamp(float value) { |
|
double x = (value / samplerate) * (2 * Math.PI); |
|
double cx = 2 - Math.cos(x); |
|
damp = (float)(cx - Math.sqrt(cx * cx - 1)); |
|
if (damp > 1) |
|
damp = 1; |
|
if (damp < 0) |
|
damp = 0; |
|
|
|
|
|
for (int i = 0; i < combL.length; i++) { |
|
combL[i].setDamp(damp); |
|
combR[i].setDamp(damp); |
|
} |
|
|
|
} |
|
|
|
public void setLightMode(boolean light) |
|
{ |
|
this.light = light; |
|
} |
|
} |
|
|