1 /* 2 * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 package com.sun.media.sound; 26 27 import java.util.Arrays; 28 29 /** 30 * A chorus effect made using LFO and variable delay. One for each channel 31 * (left,right), with different starting phase for stereo effect. 32 * 33 * @author Karl Helgason 34 */ 35 public class SoftChorus implements SoftAudioProcessor { 36 37 private static class VariableDelay { 38 39 private float[] delaybuffer; 40 private int rovepos = 0; 41 private volatile float gain = 1; 42 private volatile float rgain = 0; 43 private volatile float delay = 0; 44 private float lastdelay = 0; 45 private volatile float feedback = 0; 46 VariableDelay(int maxbuffersize)47 public VariableDelay(int maxbuffersize) { 48 delaybuffer = new float[maxbuffersize]; 49 } 50 setDelay(float delay)51 public void setDelay(float delay) { 52 this.delay = delay; 53 } 54 setFeedBack(float feedback)55 public void setFeedBack(float feedback) { 56 this.feedback = feedback; 57 } 58 setGain(float gain)59 public void setGain(float gain) { 60 this.gain = gain; 61 } 62 setReverbSendGain(float rgain)63 public void setReverbSendGain(float rgain) { 64 this.rgain = rgain; 65 } 66 processMix(float[] in, float[] out, float[] rout)67 public void processMix(float[] in, float[] out, float[] rout) { 68 float gain = this.gain; 69 float delay = this.delay; 70 float feedback = this.feedback; 71 72 float[] delaybuffer = this.delaybuffer; 73 int len = in.length; 74 float delaydelta = (delay - lastdelay) / len; 75 int rnlen = delaybuffer.length; 76 int rovepos = this.rovepos; 77 78 if (rout == null) 79 for (int i = 0; i < len; i++) { 80 float r = rovepos - (lastdelay + 2) + rnlen; 81 int ri = (int) r; 82 float s = r - ri; 83 float a = delaybuffer[ri % rnlen]; 84 float b = delaybuffer[(ri + 1) % rnlen]; 85 float o = a * (1 - s) + b * (s); 86 out[i] += o * gain; 87 delaybuffer[rovepos] = in[i] + o * feedback; 88 rovepos = (rovepos + 1) % rnlen; 89 lastdelay += delaydelta; 90 } 91 else 92 for (int i = 0; i < len; i++) { 93 float r = rovepos - (lastdelay + 2) + rnlen; 94 int ri = (int) r; 95 float s = r - ri; 96 float a = delaybuffer[ri % rnlen]; 97 float b = delaybuffer[(ri + 1) % rnlen]; 98 float o = a * (1 - s) + b * (s); 99 out[i] += o * gain; 100 rout[i] += o * rgain; 101 delaybuffer[rovepos] = in[i] + o * feedback; 102 rovepos = (rovepos + 1) % rnlen; 103 lastdelay += delaydelta; 104 } 105 this.rovepos = rovepos; 106 lastdelay = delay; 107 } 108 processReplace(float[] in, float[] out, float[] rout)109 public void processReplace(float[] in, float[] out, float[] rout) { 110 Arrays.fill(out, 0); 111 Arrays.fill(rout, 0); 112 processMix(in, out, rout); 113 } 114 } 115 116 private static class LFODelay { 117 118 private volatile double c_cos_delta; 119 private volatile double c_sin_delta; 120 private double c_cos = 1; 121 private double c_sin = 0; 122 private double depth = 0; 123 private VariableDelay vdelay; 124 private double samplerate; 125 private double controlrate; 126 LFODelay(double samplerate, double controlrate)127 public LFODelay(double samplerate, double controlrate) { 128 this.samplerate = samplerate; 129 this.controlrate = controlrate; 130 // vdelay = new VariableDelay((int)(samplerate*4)); 131 vdelay = new VariableDelay((int) ((this.depth + 10) * 2)); 132 133 } 134 setDepth(double depth)135 public void setDepth(double depth) { 136 this.depth = depth * samplerate; 137 vdelay = new VariableDelay((int) ((this.depth + 10) * 2)); 138 } 139 setRate(double rate)140 public void setRate(double rate) { 141 double g = (Math.PI * 2) * (rate / controlrate); 142 c_cos_delta = Math.cos(g); 143 c_sin_delta = Math.sin(g); 144 } 145 setPhase(double phase)146 public void setPhase(double phase) { 147 c_cos = Math.cos(phase); 148 c_sin = Math.sin(phase); 149 } 150 setFeedBack(float feedback)151 public void setFeedBack(float feedback) { 152 vdelay.setFeedBack(feedback); 153 } 154 setGain(float gain)155 public void setGain(float gain) { 156 vdelay.setGain(gain); 157 } 158 setReverbSendGain(float rgain)159 public void setReverbSendGain(float rgain) { 160 vdelay.setReverbSendGain(rgain); 161 } 162 processMix(float[] in, float[] out, float[] rout)163 public void processMix(float[] in, float[] out, float[] rout) { 164 c_cos = c_cos * c_cos_delta - c_sin * c_sin_delta; 165 c_sin = c_cos * c_sin_delta + c_sin * c_cos_delta; 166 vdelay.setDelay((float) (depth * 0.5 * (c_cos + 2))); 167 vdelay.processMix(in, out, rout); 168 } 169 processReplace(float[] in, float[] out, float[] rout)170 public void processReplace(float[] in, float[] out, float[] rout) { 171 c_cos = c_cos * c_cos_delta - c_sin * c_sin_delta; 172 c_sin = c_cos * c_sin_delta + c_sin * c_cos_delta; 173 vdelay.setDelay((float) (depth * 0.5 * (c_cos + 2))); 174 vdelay.processReplace(in, out, rout); 175 176 } 177 } 178 private boolean mix = true; 179 private SoftAudioBuffer inputA; 180 private SoftAudioBuffer left; 181 private SoftAudioBuffer right; 182 private SoftAudioBuffer reverb; 183 private LFODelay vdelay1L; 184 private LFODelay vdelay1R; 185 private float rgain = 0; 186 private boolean dirty = true; 187 private double dirty_vdelay1L_rate; 188 private double dirty_vdelay1R_rate; 189 private double dirty_vdelay1L_depth; 190 private double dirty_vdelay1R_depth; 191 private float dirty_vdelay1L_feedback; 192 private float dirty_vdelay1R_feedback; 193 private float dirty_vdelay1L_reverbsendgain; 194 private float dirty_vdelay1R_reverbsendgain; 195 private float controlrate; 196 init(float samplerate, float controlrate)197 public void init(float samplerate, float controlrate) { 198 this.controlrate = controlrate; 199 vdelay1L = new LFODelay(samplerate, controlrate); 200 vdelay1R = new LFODelay(samplerate, controlrate); 201 vdelay1L.setGain(1.0f); // % 202 vdelay1R.setGain(1.0f); // % 203 vdelay1L.setPhase(0.5 * Math.PI); 204 vdelay1R.setPhase(0); 205 206 globalParameterControlChange(new int[]{0x01 * 128 + 0x02}, 0, 2); 207 } 208 globalParameterControlChange(int[] slothpath, long param, long value)209 public void globalParameterControlChange(int[] slothpath, long param, 210 long value) { 211 if (slothpath.length == 1) { 212 if (slothpath[0] == 0x01 * 128 + 0x02) { 213 if (param == 0) { // Chorus Type 214 switch ((int)value) { 215 case 0: // Chorus 1 0 (0%) 3 (0.4Hz) 5 (1.9ms) 0 (0%) 216 globalParameterControlChange(slothpath, 3, 0); 217 globalParameterControlChange(slothpath, 1, 3); 218 globalParameterControlChange(slothpath, 2, 5); 219 globalParameterControlChange(slothpath, 4, 0); 220 break; 221 case 1: // Chorus 2 5 (4%) 9 (1.1Hz) 19 (6.3ms) 0 (0%) 222 globalParameterControlChange(slothpath, 3, 5); 223 globalParameterControlChange(slothpath, 1, 9); 224 globalParameterControlChange(slothpath, 2, 19); 225 globalParameterControlChange(slothpath, 4, 0); 226 break; 227 case 2: // Chorus 3 8 (6%) 3 (0.4Hz) 19 (6.3ms) 0 (0%) 228 globalParameterControlChange(slothpath, 3, 8); 229 globalParameterControlChange(slothpath, 1, 3); 230 globalParameterControlChange(slothpath, 2, 19); 231 globalParameterControlChange(slothpath, 4, 0); 232 break; 233 case 3: // Chorus 4 16 (12%) 9 (1.1Hz) 16 (5.3ms) 0 (0%) 234 globalParameterControlChange(slothpath, 3, 16); 235 globalParameterControlChange(slothpath, 1, 9); 236 globalParameterControlChange(slothpath, 2, 16); 237 globalParameterControlChange(slothpath, 4, 0); 238 break; 239 case 4: // FB Chorus 64 (49%) 2 (0.2Hz) 24 (7.8ms) 0 (0%) 240 globalParameterControlChange(slothpath, 3, 64); 241 globalParameterControlChange(slothpath, 1, 2); 242 globalParameterControlChange(slothpath, 2, 24); 243 globalParameterControlChange(slothpath, 4, 0); 244 break; 245 case 5: // Flanger 112 (86%) 1 (0.1Hz) 5 (1.9ms) 0 (0%) 246 globalParameterControlChange(slothpath, 3, 112); 247 globalParameterControlChange(slothpath, 1, 1); 248 globalParameterControlChange(slothpath, 2, 5); 249 globalParameterControlChange(slothpath, 4, 0); 250 break; 251 default: 252 break; 253 } 254 } else if (param == 1) { // Mod Rate 255 dirty_vdelay1L_rate = (value * 0.122); 256 dirty_vdelay1R_rate = (value * 0.122); 257 dirty = true; 258 } else if (param == 2) { // Mod Depth 259 dirty_vdelay1L_depth = ((value + 1) / 3200.0); 260 dirty_vdelay1R_depth = ((value + 1) / 3200.0); 261 dirty = true; 262 } else if (param == 3) { // Feedback 263 dirty_vdelay1L_feedback = (value * 0.00763f); 264 dirty_vdelay1R_feedback = (value * 0.00763f); 265 dirty = true; 266 } 267 if (param == 4) { // Send to Reverb 268 rgain = value * 0.00787f; 269 dirty_vdelay1L_reverbsendgain = (value * 0.00787f); 270 dirty_vdelay1R_reverbsendgain = (value * 0.00787f); 271 dirty = true; 272 } 273 274 } 275 } 276 } 277 processControlLogic()278 public void processControlLogic() { 279 if (dirty) { 280 dirty = false; 281 vdelay1L.setRate(dirty_vdelay1L_rate); 282 vdelay1R.setRate(dirty_vdelay1R_rate); 283 vdelay1L.setDepth(dirty_vdelay1L_depth); 284 vdelay1R.setDepth(dirty_vdelay1R_depth); 285 vdelay1L.setFeedBack(dirty_vdelay1L_feedback); 286 vdelay1R.setFeedBack(dirty_vdelay1R_feedback); 287 vdelay1L.setReverbSendGain(dirty_vdelay1L_reverbsendgain); 288 vdelay1R.setReverbSendGain(dirty_vdelay1R_reverbsendgain); 289 } 290 } 291 double silentcounter = 1000; 292 processAudio()293 public void processAudio() { 294 295 if (inputA.isSilent()) { 296 silentcounter += 1 / controlrate; 297 298 if (silentcounter > 1) { 299 if (!mix) { 300 left.clear(); 301 right.clear(); 302 } 303 return; 304 } 305 } else 306 silentcounter = 0; 307 308 float[] inputA = this.inputA.array(); 309 float[] left = this.left.array(); 310 float[] right = this.right == null ? null : this.right.array(); 311 float[] reverb = rgain != 0 ? this.reverb.array() : null; 312 313 if (mix) { 314 vdelay1L.processMix(inputA, left, reverb); 315 if (right != null) 316 vdelay1R.processMix(inputA, right, reverb); 317 } else { 318 vdelay1L.processReplace(inputA, left, reverb); 319 if (right != null) 320 vdelay1R.processReplace(inputA, right, reverb); 321 } 322 } 323 setInput(int pin, SoftAudioBuffer input)324 public void setInput(int pin, SoftAudioBuffer input) { 325 if (pin == 0) 326 inputA = input; 327 } 328 setMixMode(boolean mix)329 public void setMixMode(boolean mix) { 330 this.mix = mix; 331 } 332 setOutput(int pin, SoftAudioBuffer output)333 public void setOutput(int pin, SoftAudioBuffer output) { 334 if (pin == 0) 335 left = output; 336 if (pin == 1) 337 right = output; 338 if (pin == 2) 339 reverb = output; 340 } 341 } 342