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