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