blob: c30aae03f6d19a05297da76614d6f36cb76b9114 [file] [log] [blame]
/*
* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.media.sound;
import java.util.Arrays;
/**
* Reverb effect based on allpass/comb filters. First audio is send to 8
* parelled comb filters and then mixed together and then finally send thru 3
* different allpass filters.
*
* @author Karl Helgason
*/
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];
// One Pole Lowpass Filter
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];
// One Pole Lowpass Filter
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);
}
/* Init other settings */
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
// of 5m or so.
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
// of 10m or so.
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
// live performances.
dirty_roomsize = (1.5f);
dirty_damp = (5000);
dirty_predelay = (0);
dirty_gain = (2);
dirty = true;
}
if (value == 3) {
// Medium Hall A medium size concert hall.
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
// suitable for a full orchestra.
dirty_roomsize = (1.8f);
dirty_damp = (24000);
dirty_predelay = (0.03f);
dirty_gain = (1.5f);
dirty = true;
}
if (value == 8) {
// Plate A plate reverb simulation.
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;
// damp = value * 0.4f;
for (int i = 0; i < combL.length; i++) {
combL[i].setDamp(damp);
combR[i].setDamp(damp);
}
}
public void setLightMode(boolean light)
{
this.light = light;
}
}