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