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 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 final class SoftChorus implements SoftAudioProcessor { 36 37 private static class VariableDelay { 38 39 private final float[] delaybuffer; 40 private int rovepos = 0; 41 private float gain = 1; 42 private float rgain = 0; 43 private float delay = 0; 44 private float lastdelay = 0; 45 private float feedback = 0; 46 47 VariableDelay(int maxbuffersize) { 48 delaybuffer = new float[maxbuffersize]; 49 } 50 51 public void setDelay(float delay) { 52 this.delay = delay; 53 } 54 55 public void setFeedBack(float feedback) { 56 this.feedback = feedback; 57 } 58 59 public void setGain(float gain) { 60 this.gain = gain; 61 } 62 63 public void setReverbSendGain(float rgain) { 64 this.rgain = rgain; 65 } 66 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 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 double phase = 1; 119 private double phase_step = 0; 120 private double depth = 0; 121 private VariableDelay vdelay; 122 private final double samplerate; 123 private final double controlrate; 124 125 LFODelay(double samplerate, double controlrate) { 126 this.samplerate = samplerate; 127 this.controlrate = controlrate; 128 // vdelay = new VariableDelay((int)(samplerate*4)); 129 vdelay = new VariableDelay((int) ((this.depth + 10) * 2)); 130 131 } 132 133 public void setDepth(double depth) { 134 this.depth = depth * samplerate; 135 vdelay = new VariableDelay((int) ((this.depth + 10) * 2)); 136 } 137 138 public void setRate(double rate) { 139 double g = (Math.PI * 2) * (rate / controlrate); 140 phase_step = g; 141 } 142 143 public void setPhase(double phase) { 144 this.phase = phase; 145 } 146 147 public void setFeedBack(float feedback) { 148 vdelay.setFeedBack(feedback); 149 } 150 151 public void setGain(float gain) { 152 vdelay.setGain(gain); 153 } 154 155 public void setReverbSendGain(float rgain) { 156 vdelay.setReverbSendGain(rgain); 157 } 158 159 public void processMix(float[] in, float[] out, float[] rout) { 160 phase += phase_step; 161 while(phase > (Math.PI * 2)) phase -= (Math.PI * 2); 162 vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2))); 163 vdelay.processMix(in, out, rout); 164 } 165 166 public void processReplace(float[] in, float[] out, float[] rout) { 167 phase += phase_step; 168 while(phase > (Math.PI * 2)) phase -= (Math.PI * 2); 169 vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2))); 170 vdelay.processReplace(in, out, rout); 171 172 } 173 } 174 private boolean mix = true; 175 private SoftAudioBuffer inputA; 176 private SoftAudioBuffer left; 177 private SoftAudioBuffer right; 178 private SoftAudioBuffer reverb; 179 private LFODelay vdelay1L; 180 private LFODelay vdelay1R; 181 private float rgain = 0; 182 private boolean dirty = true; 183 private double dirty_vdelay1L_rate; 184 private double dirty_vdelay1R_rate; 185 private double dirty_vdelay1L_depth; 186 private double dirty_vdelay1R_depth; 187 private float dirty_vdelay1L_feedback; 188 private float dirty_vdelay1R_feedback; 189 private float dirty_vdelay1L_reverbsendgain; 190 private float dirty_vdelay1R_reverbsendgain; 191 private float controlrate; 192 193 public void init(float samplerate, float controlrate) { 194 this.controlrate = controlrate; 195 vdelay1L = new LFODelay(samplerate, controlrate); 196 vdelay1R = new LFODelay(samplerate, controlrate); 197 vdelay1L.setGain(1.0f); // % 198 vdelay1R.setGain(1.0f); // % 199 vdelay1L.setPhase(0.5 * Math.PI); 200 vdelay1R.setPhase(0); 201 202 globalParameterControlChange(new int[]{0x01 * 128 + 0x02}, 0, 2); 203 } 204 205 public void globalParameterControlChange(int[] slothpath, long param, 206 long value) { 207 if (slothpath.length == 1) { 208 if (slothpath[0] == 0x01 * 128 + 0x02) { 209 if (param == 0) { // Chorus Type 210 switch ((int)value) { 211 case 0: // Chorus 1 0 (0%) 3 (0.4Hz) 5 (1.9ms) 0 (0%) 212 globalParameterControlChange(slothpath, 3, 0); 213 globalParameterControlChange(slothpath, 1, 3); 214 globalParameterControlChange(slothpath, 2, 5); 215 globalParameterControlChange(slothpath, 4, 0); 216 break; 217 case 1: // Chorus 2 5 (4%) 9 (1.1Hz) 19 (6.3ms) 0 (0%) 218 globalParameterControlChange(slothpath, 3, 5); 219 globalParameterControlChange(slothpath, 1, 9); 220 globalParameterControlChange(slothpath, 2, 19); 221 globalParameterControlChange(slothpath, 4, 0); 222 break; 223 case 2: // Chorus 3 8 (6%) 3 (0.4Hz) 19 (6.3ms) 0 (0%) 224 globalParameterControlChange(slothpath, 3, 8); 225 globalParameterControlChange(slothpath, 1, 3); 226 globalParameterControlChange(slothpath, 2, 19); 227 globalParameterControlChange(slothpath, 4, 0); 228 break; 229 case 3: // Chorus 4 16 (12%) 9 (1.1Hz) 16 (5.3ms) 0 (0%) 230 globalParameterControlChange(slothpath, 3, 16); 231 globalParameterControlChange(slothpath, 1, 9); 232 globalParameterControlChange(slothpath, 2, 16); 233 globalParameterControlChange(slothpath, 4, 0); 234 break; 235 case 4: // FB Chorus 64 (49%) 2 (0.2Hz) 24 (7.8ms) 0 (0%) 236 globalParameterControlChange(slothpath, 3, 64); 237 globalParameterControlChange(slothpath, 1, 2); 238 globalParameterControlChange(slothpath, 2, 24); 239 globalParameterControlChange(slothpath, 4, 0); 240 break; 241 case 5: // Flanger 112 (86%) 1 (0.1Hz) 5 (1.9ms) 0 (0%) 242 globalParameterControlChange(slothpath, 3, 112); 243 globalParameterControlChange(slothpath, 1, 1); 244 globalParameterControlChange(slothpath, 2, 5); 245 globalParameterControlChange(slothpath, 4, 0); 246 break; 247 default: 248 break; 249 } 250 } else if (param == 1) { // Mod Rate 251 dirty_vdelay1L_rate = (value * 0.122); 252 dirty_vdelay1R_rate = (value * 0.122); 253 dirty = true; 254 } else if (param == 2) { // Mod Depth 255 dirty_vdelay1L_depth = ((value + 1) / 3200.0); 256 dirty_vdelay1R_depth = ((value + 1) / 3200.0); 257 dirty = true; 258 } else if (param == 3) { // Feedback 259 dirty_vdelay1L_feedback = (value * 0.00763f); 260 dirty_vdelay1R_feedback = (value * 0.00763f); 261 dirty = true; 262 } 263 if (param == 4) { // Send to Reverb 264 rgain = value * 0.00787f; 265 dirty_vdelay1L_reverbsendgain = (value * 0.00787f); 266 dirty_vdelay1R_reverbsendgain = (value * 0.00787f); 267 dirty = true; 268 } 269 270 } 271 } 272 } 273 274 public void processControlLogic() { 275 if (dirty) { 276 dirty = false; 277 vdelay1L.setRate(dirty_vdelay1L_rate); 278 vdelay1R.setRate(dirty_vdelay1R_rate); 279 vdelay1L.setDepth(dirty_vdelay1L_depth); 280 vdelay1R.setDepth(dirty_vdelay1R_depth); 281 vdelay1L.setFeedBack(dirty_vdelay1L_feedback); 282 vdelay1R.setFeedBack(dirty_vdelay1R_feedback); 283 vdelay1L.setReverbSendGain(dirty_vdelay1L_reverbsendgain); 284 vdelay1R.setReverbSendGain(dirty_vdelay1R_reverbsendgain); 285 } 286 } 287 double silentcounter = 1000; 288 289 public void processAudio() { 290 291 if (inputA.isSilent()) { 292 silentcounter += 1 / controlrate; 293 294 if (silentcounter > 1) { 295 if (!mix) { 296 left.clear(); 297 right.clear(); 298 } 299 return; 300 } 301 } else 302 silentcounter = 0; 303 304 float[] inputA = this.inputA.array(); 305 float[] left = this.left.array(); 306 float[] right = this.right == null ? null : this.right.array(); 307 float[] reverb = rgain != 0 ? this.reverb.array() : null; 308 309 if (mix) { 310 vdelay1L.processMix(inputA, left, reverb); 311 if (right != null) 312 vdelay1R.processMix(inputA, right, reverb); 313 } else { 314 vdelay1L.processReplace(inputA, left, reverb); 315 if (right != null) 316 vdelay1R.processReplace(inputA, right, reverb); 317 } 318 } 319 320 public void setInput(int pin, SoftAudioBuffer input) { 321 if (pin == 0) 322 inputA = input; 323 } 324 325 public void setMixMode(boolean mix) { 326 this.mix = mix; 327 } 328 329 public void setOutput(int pin, SoftAudioBuffer output) { 330 if (pin == 0) 331 left = output; 332 if (pin == 1) 333 right = output; 334 if (pin == 2) 335 reverb = output; 336 } 337 }