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  * Reverb effect based on allpass/comb filters. First audio is send to 8
  31  * parelled comb filters and then mixed together and then finally send thru 3
  32  * different allpass filters.
  33  *
  34  * @author Karl Helgason
  35  */
  36 public final class SoftReverb implements SoftAudioProcessor {
  37 
  38     private static final class Delay {
  39 
  40         private float[] delaybuffer;
  41         private int rovepos = 0;
  42 
  43         Delay() {
  44             delaybuffer = null;
  45         }
  46 
  47         public void setDelay(int delay) {
  48             if (delay == 0)
  49                 delaybuffer = null;
  50             else
  51                 delaybuffer = new float[delay];
  52             rovepos = 0;
  53         }
  54 
  55         public void processReplace(float[] inout) {
  56             if (delaybuffer == null)
  57                 return;
  58             int len = inout.length;
  59             int rnlen = delaybuffer.length;
  60             int rovepos = this.rovepos;
  61 
  62             for (int i = 0; i < len; i++) {
  63                 float x = inout[i];
  64                 inout[i] = delaybuffer[rovepos];
  65                 delaybuffer[rovepos] = x;
  66                 if (++rovepos == rnlen)
  67                     rovepos = 0;
  68             }
  69             this.rovepos = rovepos;
  70         }
  71     }
  72 
  73     private static final class AllPass {
  74 
  75         private final float[] delaybuffer;
  76         private final int delaybuffersize;
  77         private int rovepos = 0;
  78         private float feedback;
  79 
  80         AllPass(int size) {
  81             delaybuffer = new float[size];
  82             delaybuffersize = size;
  83         }
  84 
  85         public void setFeedBack(float feedback) {
  86             this.feedback = feedback;
  87         }
  88 
  89         public void processReplace(float inout[]) {
  90             int len = inout.length;
  91             int delaybuffersize = this.delaybuffersize;
  92             int rovepos = this.rovepos;
  93             for (int i = 0; i < len; i++) {
  94                 float delayout = delaybuffer[rovepos];
  95                 float input = inout[i];
  96                 inout[i] = delayout - input;
  97                 delaybuffer[rovepos] = input + delayout * feedback;
  98                 if (++rovepos == delaybuffersize)
  99                     rovepos = 0;
 100             }
 101             this.rovepos = rovepos;
 102         }
 103 
 104         public void processReplace(float in[], float out[]) {
 105             int len = in.length;
 106             int delaybuffersize = this.delaybuffersize;
 107             int rovepos = this.rovepos;
 108             for (int i = 0; i < len; i++) {
 109                 float delayout = delaybuffer[rovepos];
 110                 float input = in[i];
 111                 out[i] = delayout - input;
 112                 delaybuffer[rovepos] = input + delayout * feedback;
 113                 if (++rovepos == delaybuffersize)
 114                     rovepos = 0;
 115             }
 116             this.rovepos = rovepos;
 117         }
 118     }
 119 
 120     private static final class Comb {
 121 
 122         private final float[] delaybuffer;
 123         private final int delaybuffersize;
 124         private int rovepos = 0;
 125         private float feedback;
 126         private float filtertemp = 0;
 127         private float filtercoeff1 = 0;
 128         private float filtercoeff2 = 1;
 129 
 130         Comb(int size) {
 131             delaybuffer = new float[size];
 132             delaybuffersize = size;
 133         }
 134 
 135         public void setFeedBack(float feedback) {
 136             this.feedback = feedback;
 137             filtercoeff2 = (1 - filtercoeff1)* feedback;
 138         }
 139 
 140         public void processMix(float in[], float out[]) {
 141             int len = in.length;
 142             int delaybuffersize = this.delaybuffersize;
 143             int rovepos = this.rovepos;
 144             float filtertemp = this.filtertemp;
 145             float filtercoeff1 = this.filtercoeff1;
 146             float filtercoeff2 = this.filtercoeff2;
 147             for (int i = 0; i < len; i++) {
 148                 float delayout = delaybuffer[rovepos];
 149                 // One Pole Lowpass Filter
 150                 filtertemp = (delayout * filtercoeff2)
 151                         + (filtertemp * filtercoeff1);
 152                 out[i] += delayout;
 153                 delaybuffer[rovepos] = in[i] + filtertemp;
 154                 if (++rovepos == delaybuffersize)
 155                     rovepos = 0;
 156             }
 157             this.filtertemp  = filtertemp;
 158             this.rovepos = rovepos;
 159         }
 160 
 161         public void processReplace(float in[], float out[]) {
 162             int len = in.length;
 163             int delaybuffersize = this.delaybuffersize;
 164             int rovepos = this.rovepos;
 165             float filtertemp = this.filtertemp;
 166             float filtercoeff1 = this.filtercoeff1;
 167             float filtercoeff2 = this.filtercoeff2;
 168             for (int i = 0; i < len; i++) {
 169                 float delayout = delaybuffer[rovepos];
 170                 // One Pole Lowpass Filter
 171                 filtertemp = (delayout * filtercoeff2)
 172                         + (filtertemp * filtercoeff1);
 173                 out[i] = delayout;
 174                 delaybuffer[rovepos] = in[i] + filtertemp;
 175                 if (++rovepos == delaybuffersize)
 176                     rovepos = 0;
 177             }
 178             this.filtertemp  = filtertemp;
 179             this.rovepos = rovepos;
 180         }
 181 
 182         public void setDamp(float val) {
 183             filtercoeff1 = val;
 184             filtercoeff2 = (1 - filtercoeff1)* feedback;
 185         }
 186     }
 187     private float roomsize;
 188     private float damp;
 189     private float gain = 1;
 190     private Delay delay;
 191     private Comb[] combL;
 192     private Comb[] combR;
 193     private AllPass[] allpassL;
 194     private AllPass[] allpassR;
 195     private float[] input;
 196     private float[] out;
 197     private float[] pre1;
 198     private float[] pre2;
 199     private float[] pre3;
 200     private boolean denormal_flip = false;
 201     private boolean mix = true;
 202     private SoftAudioBuffer inputA;
 203     private SoftAudioBuffer left;
 204     private SoftAudioBuffer right;
 205     private boolean dirty = true;
 206     private float dirty_roomsize;
 207     private float dirty_damp;
 208     private float dirty_predelay;
 209     private float dirty_gain;
 210     private float samplerate;
 211     private boolean light = true;
 212 
 213     public void init(float samplerate, float controlrate) {
 214         this.samplerate = samplerate;
 215 
 216         double freqscale = ((double) samplerate) / 44100.0;
 217         // freqscale = 1.0/ freqscale;
 218 
 219         int stereospread = 23;
 220 
 221         delay = new Delay();
 222 
 223         combL = new Comb[8];
 224         combR = new Comb[8];
 225         combL[0] = new Comb((int) (freqscale * (1116)));
 226         combR[0] = new Comb((int) (freqscale * (1116 + stereospread)));
 227         combL[1] = new Comb((int) (freqscale * (1188)));
 228         combR[1] = new Comb((int) (freqscale * (1188 + stereospread)));
 229         combL[2] = new Comb((int) (freqscale * (1277)));
 230         combR[2] = new Comb((int) (freqscale * (1277 + stereospread)));
 231         combL[3] = new Comb((int) (freqscale * (1356)));
 232         combR[3] = new Comb((int) (freqscale * (1356 + stereospread)));
 233         combL[4] = new Comb((int) (freqscale * (1422)));
 234         combR[4] = new Comb((int) (freqscale * (1422 + stereospread)));
 235         combL[5] = new Comb((int) (freqscale * (1491)));
 236         combR[5] = new Comb((int) (freqscale * (1491 + stereospread)));
 237         combL[6] = new Comb((int) (freqscale * (1557)));
 238         combR[6] = new Comb((int) (freqscale * (1557 + stereospread)));
 239         combL[7] = new Comb((int) (freqscale * (1617)));
 240         combR[7] = new Comb((int) (freqscale * (1617 + stereospread)));
 241 
 242         allpassL = new AllPass[4];
 243         allpassR = new AllPass[4];
 244         allpassL[0] = new AllPass((int) (freqscale * (556)));
 245         allpassR[0] = new AllPass((int) (freqscale * (556 + stereospread)));
 246         allpassL[1] = new AllPass((int) (freqscale * (441)));
 247         allpassR[1] = new AllPass((int) (freqscale * (441 + stereospread)));
 248         allpassL[2] = new AllPass((int) (freqscale * (341)));
 249         allpassR[2] = new AllPass((int) (freqscale * (341 + stereospread)));
 250         allpassL[3] = new AllPass((int) (freqscale * (225)));
 251         allpassR[3] = new AllPass((int) (freqscale * (225 + stereospread)));
 252 
 253         for (int i = 0; i < allpassL.length; i++) {
 254             allpassL[i].setFeedBack(0.5f);
 255             allpassR[i].setFeedBack(0.5f);
 256         }
 257 
 258         /* Init other settings */
 259         globalParameterControlChange(new int[]{0x01 * 128 + 0x01}, 0, 4);
 260 
 261     }
 262 
 263     public void setInput(int pin, SoftAudioBuffer input) {
 264         if (pin == 0)
 265             inputA = input;
 266     }
 267 
 268     public void setOutput(int pin, SoftAudioBuffer output) {
 269         if (pin == 0)
 270             left = output;
 271         if (pin == 1)
 272             right = output;
 273     }
 274 
 275     public void setMixMode(boolean mix) {
 276         this.mix = mix;
 277     }
 278 
 279     private boolean silent = true;
 280 
 281     public void processAudio() {
 282         boolean silent_input = this.inputA.isSilent();
 283         if(!silent_input)
 284             silent = false;
 285         if(silent)
 286         {
 287             if (!mix) {
 288                 left.clear();
 289                 right.clear();
 290             }
 291             return;
 292         }
 293 
 294         float[] inputA = this.inputA.array();
 295         float[] left = this.left.array();
 296         float[] right = this.right == null ? null : this.right.array();
 297 
 298         int numsamples = inputA.length;
 299         if (input == null || input.length < numsamples)
 300             input = new float[numsamples];
 301 
 302         float again = gain * 0.018f / 2;
 303 
 304         denormal_flip = !denormal_flip;
 305         if(denormal_flip)
 306             for (int i = 0; i < numsamples; i++)
 307                 input[i] = inputA[i] * again + 1E-20f;
 308         else
 309             for (int i = 0; i < numsamples; i++)
 310                 input[i] = inputA[i] * again - 1E-20f;
 311 
 312         delay.processReplace(input);
 313 
 314         if(light && (right != null))
 315         {
 316             if (pre1 == null || pre1.length < numsamples)
 317             {
 318                 pre1 = new float[numsamples];
 319                 pre2 = new float[numsamples];
 320                 pre3 = new float[numsamples];
 321             }
 322 
 323             for (int i = 0; i < allpassL.length; i++)
 324                 allpassL[i].processReplace(input);
 325 
 326             combL[0].processReplace(input, pre3);
 327             combL[1].processReplace(input, pre3);
 328 
 329             combL[2].processReplace(input, pre1);
 330             for (int i = 4; i < combL.length-2; i+=2)
 331                 combL[i].processMix(input, pre1);
 332 
 333             combL[3].processReplace(input, pre2);;
 334             for (int i = 5; i < combL.length-2; i+=2)
 335                 combL[i].processMix(input, pre2);
 336 
 337             if (!mix)
 338             {
 339                 Arrays.fill(right, 0);
 340                 Arrays.fill(left, 0);
 341             }
 342             for (int i = combR.length-2; i < combR.length; i++)
 343                 combR[i].processMix(input, right);
 344             for (int i = combL.length-2; i < combL.length; i++)
 345                 combL[i].processMix(input, left);
 346 
 347             for (int i = 0; i < numsamples; i++)
 348             {
 349                 float p = pre1[i] - pre2[i];
 350                 float m = pre3[i];
 351                 left[i] += m + p;
 352                 right[i] += m - p;
 353             }
 354         }
 355         else
 356         {
 357             if (out == null || out.length < numsamples)
 358                 out = new float[numsamples];
 359 
 360             if (right != null) {
 361                 if (!mix)
 362                     Arrays.fill(right, 0);
 363                 allpassR[0].processReplace(input, out);
 364                 for (int i = 1; i < allpassR.length; i++)
 365                     allpassR[i].processReplace(out);
 366                 for (int i = 0; i < combR.length; i++)
 367                     combR[i].processMix(out, right);
 368             }
 369 
 370             if (!mix)
 371                 Arrays.fill(left, 0);
 372             allpassL[0].processReplace(input, out);
 373             for (int i = 1; i < allpassL.length; i++)
 374                 allpassL[i].processReplace(out);
 375             for (int i = 0; i < combL.length; i++)
 376                 combL[i].processMix(out, left);
 377         }
 378 
 379 
 380 
 381 
 382 
 383 
 384         if (silent_input) {
 385             silent = true;
 386             for (int i = 0; i < numsamples; i++)
 387             {
 388                 float v = left[i];
 389                 if(v > 1E-10 || v < -1E-10)
 390                 {
 391                     silent = false;
 392                     break;
 393                 }
 394             }
 395         }
 396 
 397     }
 398 
 399     public void globalParameterControlChange(int[] slothpath, long param,
 400             long value) {
 401         if (slothpath.length == 1) {
 402             if (slothpath[0] == 0x01 * 128 + 0x01) {
 403 
 404                 if (param == 0) {
 405                     if (value == 0) {
 406                         // Small Room A small size room with a length
 407                         // of 5m or so.
 408                         dirty_roomsize = (1.1f);
 409                         dirty_damp = (5000);
 410                         dirty_predelay = (0);
 411                         dirty_gain = (4);
 412                         dirty = true;
 413                     }
 414                     if (value == 1) {
 415                         // Medium Room A medium size room with a length
 416                         // of 10m or so.
 417                         dirty_roomsize = (1.3f);
 418                         dirty_damp = (5000);
 419                         dirty_predelay = (0);
 420                         dirty_gain = (3);
 421                         dirty = true;
 422                     }
 423                     if (value == 2) {
 424                         // Large Room A large size room suitable for
 425                         // live performances.
 426                         dirty_roomsize = (1.5f);
 427                         dirty_damp = (5000);
 428                         dirty_predelay = (0);
 429                         dirty_gain = (2);
 430                         dirty = true;
 431                     }
 432                     if (value == 3) {
 433                         // Medium Hall A medium size concert hall.
 434                         dirty_roomsize = (1.8f);
 435                         dirty_damp = (24000);
 436                         dirty_predelay = (0.02f);
 437                         dirty_gain = (1.5f);
 438                         dirty = true;
 439                     }
 440                     if (value == 4) {
 441                         // Large Hall A large size concert hall
 442                         // suitable for a full orchestra.
 443                         dirty_roomsize = (1.8f);
 444                         dirty_damp = (24000);
 445                         dirty_predelay = (0.03f);
 446                         dirty_gain = (1.5f);
 447                         dirty = true;
 448                     }
 449                     if (value == 8) {
 450                         // Plate A plate reverb simulation.
 451                         dirty_roomsize = (1.3f);
 452                         dirty_damp = (2500);
 453                         dirty_predelay = (0);
 454                         dirty_gain = (6);
 455                         dirty = true;
 456                     }
 457                 } else if (param == 1) {
 458                     dirty_roomsize = ((float) (Math.exp((value - 40) * 0.025)));
 459                     dirty = true;
 460                 }
 461 
 462             }
 463         }
 464     }
 465 
 466     public void processControlLogic() {
 467         if (dirty) {
 468             dirty = false;
 469             setRoomSize(dirty_roomsize);
 470             setDamp(dirty_damp);
 471             setPreDelay(dirty_predelay);
 472             setGain(dirty_gain);
 473         }
 474     }
 475 
 476     public void setRoomSize(float value) {
 477         roomsize = 1 - (0.17f / value);
 478 
 479         for (int i = 0; i < combL.length; i++) {
 480             combL[i].feedback = roomsize;
 481             combR[i].feedback = roomsize;
 482         }
 483     }
 484 
 485     public void setPreDelay(float value) {
 486         delay.setDelay((int)(value * samplerate));
 487     }
 488 
 489     public void setGain(float gain) {
 490         this.gain = gain;
 491     }
 492 
 493     public void setDamp(float value) {
 494         double x = (value / samplerate) * (2 * Math.PI);
 495         double cx = 2 - Math.cos(x);
 496         damp = (float)(cx - Math.sqrt(cx * cx - 1));
 497         if (damp > 1)
 498             damp = 1;
 499         if (damp < 0)
 500             damp = 0;
 501 
 502         // damp = value * 0.4f;
 503         for (int i = 0; i < combL.length; i++) {
 504             combL[i].setDamp(damp);
 505             combR[i].setDamp(damp);
 506         }
 507 
 508     }
 509 
 510     public void setLightMode(boolean light)
 511     {
 512         this.light = light;
 513     }
 514 }
 515