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 }