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 }