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 
Delay()44         Delay() {
45             delaybuffer = null;
46         }
47 
setDelay(int delay)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 
processReplace(float[] inout)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 
AllPass(int size)81         AllPass(int size) {
82             delaybuffer = new float[size];
83             delaybuffersize = size;
84         }
85 
setFeedBack(float feedback)86         public void setFeedBack(float feedback) {
87             this.feedback = feedback;
88         }
89 
processReplace(float[] inout)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 
processReplace(float[] in, float[] out)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 
Comb(int size)131         Comb(int size) {
132             delaybuffer = new float[size];
133             delaybuffersize = size;
134         }
135 
setFeedBack(float feedback)136         public void setFeedBack(float feedback) {
137             this.feedback = feedback;
138             filtercoeff2 = (1 - filtercoeff1)* feedback;
139         }
140 
processMix(float[] in, float[] out)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 
processReplace(float[] in, float[] out)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 
setDamp(float val)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
init(float samplerate, float controlrate)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
setInput(int pin, SoftAudioBuffer input)266     public void setInput(int pin, SoftAudioBuffer input) {
267         if (pin == 0)
268             inputA = input;
269     }
270 
271     @Override
setOutput(int pin, SoftAudioBuffer output)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
setMixMode(boolean mix)280     public void setMixMode(boolean mix) {
281         this.mix = mix;
282     }
283 
284     private boolean silent = true;
285 
286     @Override
processAudio()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
globalParameterControlChange(int[] slothpath, long param, long value)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
processControlLogic()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 
setRoomSize(float value)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 
setPreDelay(float value)488     public void setPreDelay(float value) {
489         delay.setDelay((int)(value * samplerate));
490     }
491 
setGain(float gain)492     public void setGain(float gain) {
493         this.gain = gain;
494     }
495 
setDamp(float value)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 
setLightMode(boolean light)512     public void setLightMode(boolean light)
513     {
514         this.light = light;
515     }
516 }
517 
518