338 lines
13 KiB
Java
338 lines
13 KiB
Java
/*
|
|
* 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;
|
|
|
|
/**
|
|
* A chorus effect made using LFO and variable delay. One for each channel
|
|
* (left,right), with different starting phase for stereo effect.
|
|
*
|
|
* @author Karl Helgason
|
|
*/
|
|
public final class SoftChorus implements SoftAudioProcessor {
|
|
|
|
private static class VariableDelay {
|
|
|
|
private final float[] delaybuffer;
|
|
private int rovepos = 0;
|
|
private float gain = 1;
|
|
private float rgain = 0;
|
|
private float delay = 0;
|
|
private float lastdelay = 0;
|
|
private float feedback = 0;
|
|
|
|
VariableDelay(int maxbuffersize) {
|
|
delaybuffer = new float[maxbuffersize];
|
|
}
|
|
|
|
public void setDelay(float delay) {
|
|
this.delay = delay;
|
|
}
|
|
|
|
public void setFeedBack(float feedback) {
|
|
this.feedback = feedback;
|
|
}
|
|
|
|
public void setGain(float gain) {
|
|
this.gain = gain;
|
|
}
|
|
|
|
public void setReverbSendGain(float rgain) {
|
|
this.rgain = rgain;
|
|
}
|
|
|
|
public void processMix(float[] in, float[] out, float[] rout) {
|
|
float gain = this.gain;
|
|
float delay = this.delay;
|
|
float feedback = this.feedback;
|
|
|
|
float[] delaybuffer = this.delaybuffer;
|
|
int len = in.length;
|
|
float delaydelta = (delay - lastdelay) / len;
|
|
int rnlen = delaybuffer.length;
|
|
int rovepos = this.rovepos;
|
|
|
|
if (rout == null)
|
|
for (int i = 0; i < len; i++) {
|
|
float r = rovepos - (lastdelay + 2) + rnlen;
|
|
int ri = (int) r;
|
|
float s = r - ri;
|
|
float a = delaybuffer[ri % rnlen];
|
|
float b = delaybuffer[(ri + 1) % rnlen];
|
|
float o = a * (1 - s) + b * (s);
|
|
out[i] += o * gain;
|
|
delaybuffer[rovepos] = in[i] + o * feedback;
|
|
rovepos = (rovepos + 1) % rnlen;
|
|
lastdelay += delaydelta;
|
|
}
|
|
else
|
|
for (int i = 0; i < len; i++) {
|
|
float r = rovepos - (lastdelay + 2) + rnlen;
|
|
int ri = (int) r;
|
|
float s = r - ri;
|
|
float a = delaybuffer[ri % rnlen];
|
|
float b = delaybuffer[(ri + 1) % rnlen];
|
|
float o = a * (1 - s) + b * (s);
|
|
out[i] += o * gain;
|
|
rout[i] += o * rgain;
|
|
delaybuffer[rovepos] = in[i] + o * feedback;
|
|
rovepos = (rovepos + 1) % rnlen;
|
|
lastdelay += delaydelta;
|
|
}
|
|
this.rovepos = rovepos;
|
|
lastdelay = delay;
|
|
}
|
|
|
|
public void processReplace(float[] in, float[] out, float[] rout) {
|
|
Arrays.fill(out, 0);
|
|
Arrays.fill(rout, 0);
|
|
processMix(in, out, rout);
|
|
}
|
|
}
|
|
|
|
private static class LFODelay {
|
|
|
|
private double phase = 1;
|
|
private double phase_step = 0;
|
|
private double depth = 0;
|
|
private VariableDelay vdelay;
|
|
private final double samplerate;
|
|
private final double controlrate;
|
|
|
|
LFODelay(double samplerate, double controlrate) {
|
|
this.samplerate = samplerate;
|
|
this.controlrate = controlrate;
|
|
// vdelay = new VariableDelay((int)(samplerate*4));
|
|
vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
|
|
|
|
}
|
|
|
|
public void setDepth(double depth) {
|
|
this.depth = depth * samplerate;
|
|
vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
|
|
}
|
|
|
|
public void setRate(double rate) {
|
|
double g = (Math.PI * 2) * (rate / controlrate);
|
|
phase_step = g;
|
|
}
|
|
|
|
public void setPhase(double phase) {
|
|
this.phase = phase;
|
|
}
|
|
|
|
public void setFeedBack(float feedback) {
|
|
vdelay.setFeedBack(feedback);
|
|
}
|
|
|
|
public void setGain(float gain) {
|
|
vdelay.setGain(gain);
|
|
}
|
|
|
|
public void setReverbSendGain(float rgain) {
|
|
vdelay.setReverbSendGain(rgain);
|
|
}
|
|
|
|
public void processMix(float[] in, float[] out, float[] rout) {
|
|
phase += phase_step;
|
|
while(phase > (Math.PI * 2)) phase -= (Math.PI * 2);
|
|
vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2)));
|
|
vdelay.processMix(in, out, rout);
|
|
}
|
|
|
|
public void processReplace(float[] in, float[] out, float[] rout) {
|
|
phase += phase_step;
|
|
while(phase > (Math.PI * 2)) phase -= (Math.PI * 2);
|
|
vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2)));
|
|
vdelay.processReplace(in, out, rout);
|
|
|
|
}
|
|
}
|
|
private boolean mix = true;
|
|
private SoftAudioBuffer inputA;
|
|
private SoftAudioBuffer left;
|
|
private SoftAudioBuffer right;
|
|
private SoftAudioBuffer reverb;
|
|
private LFODelay vdelay1L;
|
|
private LFODelay vdelay1R;
|
|
private float rgain = 0;
|
|
private boolean dirty = true;
|
|
private double dirty_vdelay1L_rate;
|
|
private double dirty_vdelay1R_rate;
|
|
private double dirty_vdelay1L_depth;
|
|
private double dirty_vdelay1R_depth;
|
|
private float dirty_vdelay1L_feedback;
|
|
private float dirty_vdelay1R_feedback;
|
|
private float dirty_vdelay1L_reverbsendgain;
|
|
private float dirty_vdelay1R_reverbsendgain;
|
|
private float controlrate;
|
|
|
|
public void init(float samplerate, float controlrate) {
|
|
this.controlrate = controlrate;
|
|
vdelay1L = new LFODelay(samplerate, controlrate);
|
|
vdelay1R = new LFODelay(samplerate, controlrate);
|
|
vdelay1L.setGain(1.0f); // %
|
|
vdelay1R.setGain(1.0f); // %
|
|
vdelay1L.setPhase(0.5 * Math.PI);
|
|
vdelay1R.setPhase(0);
|
|
|
|
globalParameterControlChange(new int[]{0x01 * 128 + 0x02}, 0, 2);
|
|
}
|
|
|
|
public void globalParameterControlChange(int[] slothpath, long param,
|
|
long value) {
|
|
if (slothpath.length == 1) {
|
|
if (slothpath[0] == 0x01 * 128 + 0x02) {
|
|
if (param == 0) { // Chorus Type
|
|
switch ((int)value) {
|
|
case 0: // Chorus 1 0 (0%) 3 (0.4Hz) 5 (1.9ms) 0 (0%)
|
|
globalParameterControlChange(slothpath, 3, 0);
|
|
globalParameterControlChange(slothpath, 1, 3);
|
|
globalParameterControlChange(slothpath, 2, 5);
|
|
globalParameterControlChange(slothpath, 4, 0);
|
|
break;
|
|
case 1: // Chorus 2 5 (4%) 9 (1.1Hz) 19 (6.3ms) 0 (0%)
|
|
globalParameterControlChange(slothpath, 3, 5);
|
|
globalParameterControlChange(slothpath, 1, 9);
|
|
globalParameterControlChange(slothpath, 2, 19);
|
|
globalParameterControlChange(slothpath, 4, 0);
|
|
break;
|
|
case 2: // Chorus 3 8 (6%) 3 (0.4Hz) 19 (6.3ms) 0 (0%)
|
|
globalParameterControlChange(slothpath, 3, 8);
|
|
globalParameterControlChange(slothpath, 1, 3);
|
|
globalParameterControlChange(slothpath, 2, 19);
|
|
globalParameterControlChange(slothpath, 4, 0);
|
|
break;
|
|
case 3: // Chorus 4 16 (12%) 9 (1.1Hz) 16 (5.3ms) 0 (0%)
|
|
globalParameterControlChange(slothpath, 3, 16);
|
|
globalParameterControlChange(slothpath, 1, 9);
|
|
globalParameterControlChange(slothpath, 2, 16);
|
|
globalParameterControlChange(slothpath, 4, 0);
|
|
break;
|
|
case 4: // FB Chorus 64 (49%) 2 (0.2Hz) 24 (7.8ms) 0 (0%)
|
|
globalParameterControlChange(slothpath, 3, 64);
|
|
globalParameterControlChange(slothpath, 1, 2);
|
|
globalParameterControlChange(slothpath, 2, 24);
|
|
globalParameterControlChange(slothpath, 4, 0);
|
|
break;
|
|
case 5: // Flanger 112 (86%) 1 (0.1Hz) 5 (1.9ms) 0 (0%)
|
|
globalParameterControlChange(slothpath, 3, 112);
|
|
globalParameterControlChange(slothpath, 1, 1);
|
|
globalParameterControlChange(slothpath, 2, 5);
|
|
globalParameterControlChange(slothpath, 4, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (param == 1) { // Mod Rate
|
|
dirty_vdelay1L_rate = (value * 0.122);
|
|
dirty_vdelay1R_rate = (value * 0.122);
|
|
dirty = true;
|
|
} else if (param == 2) { // Mod Depth
|
|
dirty_vdelay1L_depth = ((value + 1) / 3200.0);
|
|
dirty_vdelay1R_depth = ((value + 1) / 3200.0);
|
|
dirty = true;
|
|
} else if (param == 3) { // Feedback
|
|
dirty_vdelay1L_feedback = (value * 0.00763f);
|
|
dirty_vdelay1R_feedback = (value * 0.00763f);
|
|
dirty = true;
|
|
}
|
|
if (param == 4) { // Send to Reverb
|
|
rgain = value * 0.00787f;
|
|
dirty_vdelay1L_reverbsendgain = (value * 0.00787f);
|
|
dirty_vdelay1R_reverbsendgain = (value * 0.00787f);
|
|
dirty = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
public void processControlLogic() {
|
|
if (dirty) {
|
|
dirty = false;
|
|
vdelay1L.setRate(dirty_vdelay1L_rate);
|
|
vdelay1R.setRate(dirty_vdelay1R_rate);
|
|
vdelay1L.setDepth(dirty_vdelay1L_depth);
|
|
vdelay1R.setDepth(dirty_vdelay1R_depth);
|
|
vdelay1L.setFeedBack(dirty_vdelay1L_feedback);
|
|
vdelay1R.setFeedBack(dirty_vdelay1R_feedback);
|
|
vdelay1L.setReverbSendGain(dirty_vdelay1L_reverbsendgain);
|
|
vdelay1R.setReverbSendGain(dirty_vdelay1R_reverbsendgain);
|
|
}
|
|
}
|
|
double silentcounter = 1000;
|
|
|
|
public void processAudio() {
|
|
|
|
if (inputA.isSilent()) {
|
|
silentcounter += 1 / controlrate;
|
|
|
|
if (silentcounter > 1) {
|
|
if (!mix) {
|
|
left.clear();
|
|
right.clear();
|
|
}
|
|
return;
|
|
}
|
|
} else
|
|
silentcounter = 0;
|
|
|
|
float[] inputA = this.inputA.array();
|
|
float[] left = this.left.array();
|
|
float[] right = this.right == null ? null : this.right.array();
|
|
float[] reverb = rgain != 0 ? this.reverb.array() : null;
|
|
|
|
if (mix) {
|
|
vdelay1L.processMix(inputA, left, reverb);
|
|
if (right != null)
|
|
vdelay1R.processMix(inputA, right, reverb);
|
|
} else {
|
|
vdelay1L.processReplace(inputA, left, reverb);
|
|
if (right != null)
|
|
vdelay1R.processReplace(inputA, right, reverb);
|
|
}
|
|
}
|
|
|
|
public void setInput(int pin, SoftAudioBuffer input) {
|
|
if (pin == 0)
|
|
inputA = input;
|
|
}
|
|
|
|
public void setMixMode(boolean mix) {
|
|
this.mix = mix;
|
|
}
|
|
|
|
public void setOutput(int pin, SoftAudioBuffer output) {
|
|
if (pin == 0)
|
|
left = output;
|
|
if (pin == 1)
|
|
right = output;
|
|
if (pin == 2)
|
|
reverb = output;
|
|
}
|
|
}
|