1 /* 2 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.util.Arrays; 29 30 /** 31 * A chorus effect made using LFO and variable delay. One for each channel 32 * (left,right), with different starting phase for stereo effect. 33 * 34 * @author Karl Helgason 35 */ 36 public final class SoftChorus implements SoftAudioProcessor { 37 38 private static class VariableDelay { 39 40 private final float[] delaybuffer; 41 private int rovepos = 0; 42 private float gain = 1; 43 private float rgain = 0; 44 private float delay = 0; 45 private float lastdelay = 0; 46 private float feedback = 0; 47 48 VariableDelay(int maxbuffersize) { 49 delaybuffer = new float[maxbuffersize]; 50 } 51 52 public void setDelay(float delay) { 53 this.delay = delay; 54 } 55 56 public void setFeedBack(float feedback) { 57 this.feedback = feedback; 58 } 59 60 public void setGain(float gain) { 61 this.gain = gain; 62 } 63 64 public void setReverbSendGain(float rgain) { 65 this.rgain = rgain; 66 } 67 68 public void processMix(float[] in, float[] out, float[] rout) { 69 float gain = this.gain; 70 float delay = this.delay; 71 float feedback = this.feedback; 72 73 float[] delaybuffer = this.delaybuffer; 74 int len = in.length; 75 float delaydelta = (delay - lastdelay) / len; 76 int rnlen = delaybuffer.length; 77 int rovepos = this.rovepos; 78 79 if (rout == null) 80 for (int i = 0; i < len; i++) { 81 float r = rovepos - (lastdelay + 2) + rnlen; 82 int ri = (int) r; 83 float s = r - ri; 84 float a = delaybuffer[ri % rnlen]; 85 float b = delaybuffer[(ri + 1) % rnlen]; 86 float o = a * (1 - s) + b * (s); 87 out[i] += o * gain; 88 delaybuffer[rovepos] = in[i] + o * feedback; 89 rovepos = (rovepos + 1) % rnlen; 90 lastdelay += delaydelta; 91 } 92 else 93 for (int i = 0; i < len; i++) { 94 float r = rovepos - (lastdelay + 2) + rnlen; 95 int ri = (int) r; 96 float s = r - ri; 97 float a = delaybuffer[ri % rnlen]; 98 float b = delaybuffer[(ri + 1) % rnlen]; 99 float o = a * (1 - s) + b * (s); 100 out[i] += o * gain; 101 rout[i] += o * rgain; 102 delaybuffer[rovepos] = in[i] + o * feedback; 103 rovepos = (rovepos + 1) % rnlen; 104 lastdelay += delaydelta; 105 } 106 this.rovepos = rovepos; 107 lastdelay = delay; 108 } 109 110 public void processReplace(float[] in, float[] out, float[] rout) { 111 Arrays.fill(out, 0); 112 Arrays.fill(rout, 0); 113 processMix(in, out, rout); 114 } 115 } 116 117 private static class LFODelay { 118 119 private double phase = 1; 120 private double phase_step = 0; 121 private double depth = 0; 122 private VariableDelay vdelay; 123 private final double samplerate; 124 private final double controlrate; 125 126 LFODelay(double samplerate, double controlrate) { 127 this.samplerate = samplerate; 128 this.controlrate = controlrate; 129 // vdelay = new VariableDelay((int)(samplerate*4)); 130 vdelay = new VariableDelay((int) ((this.depth + 10) * 2)); 131 132 } 133 134 public void setDepth(double depth) { 135 this.depth = depth * samplerate; 136 vdelay = new VariableDelay((int) ((this.depth + 10) * 2)); 137 } 138 139 public void setRate(double rate) { 140 double g = (Math.PI * 2) * (rate / controlrate); 141 phase_step = g; 142 } 143 144 public void setPhase(double phase) { 145 this.phase = phase; 146 } 147 148 public void setFeedBack(float feedback) { 149 vdelay.setFeedBack(feedback); 150 } 151 152 public void setGain(float gain) { 153 vdelay.setGain(gain); 154 } 155 156 public void setReverbSendGain(float rgain) { 157 vdelay.setReverbSendGain(rgain); 158 } 159 160 public void processMix(float[] in, float[] out, float[] rout) { 161 phase += phase_step; 162 while(phase > (Math.PI * 2)) phase -= (Math.PI * 2); 163 vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2))); 164 vdelay.processMix(in, out, rout); 165 } 166 167 public void processReplace(float[] in, float[] out, float[] rout) { 168 phase += phase_step; 169 while(phase > (Math.PI * 2)) phase -= (Math.PI * 2); 170 vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2))); 171 vdelay.processReplace(in, out, rout); 172 173 } 174 } 175 private boolean mix = true; 176 private SoftAudioBuffer inputA; 177 private SoftAudioBuffer left; 178 private SoftAudioBuffer right; 179 private SoftAudioBuffer reverb; 180 private LFODelay vdelay1L; 181 private LFODelay vdelay1R; 182 private float rgain = 0; 183 private boolean dirty = true; 184 private double dirty_vdelay1L_rate; 185 private double dirty_vdelay1R_rate; 186 private double dirty_vdelay1L_depth; 187 private double dirty_vdelay1R_depth; 188 private float dirty_vdelay1L_feedback; 189 private float dirty_vdelay1R_feedback; 190 private float dirty_vdelay1L_reverbsendgain; 191 private float dirty_vdelay1R_reverbsendgain; 192 private float controlrate; 193 194 @Override 195 public void init(float samplerate, float controlrate) { 196 this.controlrate = controlrate; 197 vdelay1L = new LFODelay(samplerate, controlrate); 198 vdelay1R = new LFODelay(samplerate, controlrate); 199 vdelay1L.setGain(1.0f); // % 200 vdelay1R.setGain(1.0f); // % 201 vdelay1L.setPhase(0.5 * Math.PI); 202 vdelay1R.setPhase(0); 203 204 globalParameterControlChange(new int[]{0x01 * 128 + 0x02}, 0, 2); 205 } 206 207 @Override 208 public void globalParameterControlChange(int[] slothpath, long param, 209 long value) { 210 if (slothpath.length == 1) { 211 if (slothpath[0] == 0x01 * 128 + 0x02) { 212 if (param == 0) { // Chorus Type 213 switch ((int)value) { 214 case 0: // Chorus 1 0 (0%) 3 (0.4Hz) 5 (1.9ms) 0 (0%) 215 globalParameterControlChange(slothpath, 3, 0); 216 globalParameterControlChange(slothpath, 1, 3); 217 globalParameterControlChange(slothpath, 2, 5); 218 globalParameterControlChange(slothpath, 4, 0); 219 break; 220 case 1: // Chorus 2 5 (4%) 9 (1.1Hz) 19 (6.3ms) 0 (0%) 221 globalParameterControlChange(slothpath, 3, 5); 222 globalParameterControlChange(slothpath, 1, 9); 223 globalParameterControlChange(slothpath, 2, 19); 224 globalParameterControlChange(slothpath, 4, 0); 225 break; 226 case 2: // Chorus 3 8 (6%) 3 (0.4Hz) 19 (6.3ms) 0 (0%) 227 globalParameterControlChange(slothpath, 3, 8); 228 globalParameterControlChange(slothpath, 1, 3); 229 globalParameterControlChange(slothpath, 2, 19); 230 globalParameterControlChange(slothpath, 4, 0); 231 break; 232 case 3: // Chorus 4 16 (12%) 9 (1.1Hz) 16 (5.3ms) 0 (0%) 233 globalParameterControlChange(slothpath, 3, 16); 234 globalParameterControlChange(slothpath, 1, 9); 235 globalParameterControlChange(slothpath, 2, 16); 236 globalParameterControlChange(slothpath, 4, 0); 237 break; 238 case 4: // FB Chorus 64 (49%) 2 (0.2Hz) 24 (7.8ms) 0 (0%) 239 globalParameterControlChange(slothpath, 3, 64); 240 globalParameterControlChange(slothpath, 1, 2); 241 globalParameterControlChange(slothpath, 2, 24); 242 globalParameterControlChange(slothpath, 4, 0); 243 break; 244 case 5: // Flanger 112 (86%) 1 (0.1Hz) 5 (1.9ms) 0 (0%) 245 globalParameterControlChange(slothpath, 3, 112); 246 globalParameterControlChange(slothpath, 1, 1); 247 globalParameterControlChange(slothpath, 2, 5); 248 globalParameterControlChange(slothpath, 4, 0); 249 break; 250 default: 251 break; 252 } 253 } else if (param == 1) { // Mod Rate 254 dirty_vdelay1L_rate = (value * 0.122); 255 dirty_vdelay1R_rate = (value * 0.122); 256 dirty = true; 257 } else if (param == 2) { // Mod Depth 258 dirty_vdelay1L_depth = ((value + 1) / 3200.0); 259 dirty_vdelay1R_depth = ((value + 1) / 3200.0); 260 dirty = true; 261 } else if (param == 3) { // Feedback 262 dirty_vdelay1L_feedback = (value * 0.00763f); 263 dirty_vdelay1R_feedback = (value * 0.00763f); 264 dirty = true; 265 } 266 if (param == 4) { // Send to Reverb 267 rgain = value * 0.00787f; 268 dirty_vdelay1L_reverbsendgain = (value * 0.00787f); 269 dirty_vdelay1R_reverbsendgain = (value * 0.00787f); 270 dirty = true; 271 } 272 273 } 274 } 275 } 276 277 @Override 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 293 @Override 294 public void processAudio() { 295 296 if (inputA.isSilent()) { 297 silentcounter += 1 / controlrate; 298 299 if (silentcounter > 1) { 300 if (!mix) { 301 left.clear(); 302 right.clear(); 303 } 304 return; 305 } 306 } else 307 silentcounter = 0; 308 309 float[] inputA = this.inputA.array(); 310 float[] left = this.left.array(); 311 float[] right = this.right == null ? null : this.right.array(); 312 float[] reverb = rgain != 0 ? this.reverb.array() : null; 313 314 if (mix) { 315 vdelay1L.processMix(inputA, left, reverb); 316 if (right != null) 317 vdelay1R.processMix(inputA, right, reverb); 318 } else { 319 vdelay1L.processReplace(inputA, left, reverb); 320 if (right != null) 321 vdelay1R.processReplace(inputA, right, reverb); 322 } 323 } 324 325 @Override 326 public void setInput(int pin, SoftAudioBuffer input) { 327 if (pin == 0) 328 inputA = input; 329 } 330 331 @Override 332 public void setMixMode(boolean mix) { 333 this.mix = mix; 334 } 335 336 @Override 337 public void setOutput(int pin, SoftAudioBuffer output) { 338 if (pin == 0) 339 left = output; 340 if (pin == 1) 341 right = output; 342 if (pin == 2) 343 reverb = output; 344 } 345 }