|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package com.sun.media.sound; |
|
|
|
import java.util.Arrays; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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) ((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) { |
|
switch ((int)value) { |
|
case 0: |
|
globalParameterControlChange(slothpath, 3, 0); |
|
globalParameterControlChange(slothpath, 1, 3); |
|
globalParameterControlChange(slothpath, 2, 5); |
|
globalParameterControlChange(slothpath, 4, 0); |
|
break; |
|
case 1: |
|
globalParameterControlChange(slothpath, 3, 5); |
|
globalParameterControlChange(slothpath, 1, 9); |
|
globalParameterControlChange(slothpath, 2, 19); |
|
globalParameterControlChange(slothpath, 4, 0); |
|
break; |
|
case 2: |
|
globalParameterControlChange(slothpath, 3, 8); |
|
globalParameterControlChange(slothpath, 1, 3); |
|
globalParameterControlChange(slothpath, 2, 19); |
|
globalParameterControlChange(slothpath, 4, 0); |
|
break; |
|
case 3: |
|
globalParameterControlChange(slothpath, 3, 16); |
|
globalParameterControlChange(slothpath, 1, 9); |
|
globalParameterControlChange(slothpath, 2, 16); |
|
globalParameterControlChange(slothpath, 4, 0); |
|
break; |
|
case 4: |
|
globalParameterControlChange(slothpath, 3, 64); |
|
globalParameterControlChange(slothpath, 1, 2); |
|
globalParameterControlChange(slothpath, 2, 24); |
|
globalParameterControlChange(slothpath, 4, 0); |
|
break; |
|
case 5: |
|
globalParameterControlChange(slothpath, 3, 112); |
|
globalParameterControlChange(slothpath, 1, 1); |
|
globalParameterControlChange(slothpath, 2, 5); |
|
globalParameterControlChange(slothpath, 4, 0); |
|
break; |
|
default: |
|
break; |
|
} |
|
} else if (param == 1) { |
|
dirty_vdelay1L_rate = (value * 0.122); |
|
dirty_vdelay1R_rate = (value * 0.122); |
|
dirty = true; |
|
} else if (param == 2) { |
|
dirty_vdelay1L_depth = ((value + 1) / 3200.0); |
|
dirty_vdelay1R_depth = ((value + 1) / 3200.0); |
|
dirty = true; |
|
} else if (param == 3) { |
|
dirty_vdelay1L_feedback = (value * 0.00763f); |
|
dirty_vdelay1R_feedback = (value * 0.00763f); |
|
dirty = true; |
|
} |
|
if (param == 4) { |
|
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; |
|
} |
|
} |