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 final static class Delay {
39 
40         private float[] delaybuffer;
41         private int rovepos = 0;
42 
Delay()43         Delay() {
44             delaybuffer = null;
45         }
46 
setDelay(int delay)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 
processReplace(float[] inout)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 final static class AllPass {
74 
75         private final float[] delaybuffer;
76         private final int delaybuffersize;
77         private int rovepos = 0;
78         private float feedback;
79 
AllPass(int size)80         AllPass(int size) {
81             delaybuffer = new float[size];
82             delaybuffersize = size;
83         }
84 
setFeedBack(float feedback)85         public void setFeedBack(float feedback) {
86             this.feedback = feedback;
87         }
88 
processReplace(float inout[])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 
processReplace(float in[], float out[])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 final static 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 
Comb(int size)130         Comb(int size) {
131             delaybuffer = new float[size];
132             delaybuffersize = size;
133         }
134 
setFeedBack(float feedback)135         public void setFeedBack(float feedback) {
136             this.feedback = feedback;
137             filtercoeff2 = (1 - filtercoeff1)* feedback;
138         }
139 
processMix(float in[], float out[])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 
processReplace(float in[], float out[])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 
setDamp(float val)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 
init(float samplerate, float controlrate)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 
setInput(int pin, SoftAudioBuffer input)263     public void setInput(int pin, SoftAudioBuffer input) {
264         if (pin == 0)
265             inputA = input;
266     }
267 
setOutput(int pin, SoftAudioBuffer output)268     public void setOutput(int pin, SoftAudioBuffer output) {
269         if (pin == 0)
270             left = output;
271         if (pin == 1)
272             right = output;
273     }
274 
setMixMode(boolean mix)275     public void setMixMode(boolean mix) {
276         this.mix = mix;
277     }
278 
279     private boolean silent = true;
280 
processAudio()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 
globalParameterControlChange(int[] slothpath, long param, long value)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 
processControlLogic()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 
setRoomSize(float value)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 
setPreDelay(float value)485     public void setPreDelay(float value) {
486         delay.setDelay((int)(value * samplerate));
487     }
488 
setGain(float gain)489     public void setGain(float gain) {
490         this.gain = gain;
491     }
492 
setDamp(float value)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 
setLightMode(boolean light)510     public void setLightMode(boolean light)
511     {
512         this.light = light;
513     }
514 }
515 
516