| /* |
| * 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; |
| } |
| } |