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  * A chorus effect made using LFO and variable delay. One for each channel
31  * (left,right), with different starting phase for stereo effect.
32  *
33  * @author Karl Helgason
34  */
35 public final class SoftChorus implements SoftAudioProcessor {
36 
37     private static class VariableDelay {
38 
39         private final float[] delaybuffer;
40         private int rovepos = 0;
41         private float gain = 1;
42         private float rgain = 0;
43         private float delay = 0;
44         private float lastdelay = 0;
45         private float feedback = 0;
46 
VariableDelay(int maxbuffersize)47         VariableDelay(int maxbuffersize) {
48             delaybuffer = new float[maxbuffersize];
49         }
50 
setDelay(float delay)51         public void setDelay(float delay) {
52             this.delay = delay;
53         }
54 
setFeedBack(float feedback)55         public void setFeedBack(float feedback) {
56             this.feedback = feedback;
57         }
58 
setGain(float gain)59         public void setGain(float gain) {
60             this.gain = gain;
61         }
62 
setReverbSendGain(float rgain)63         public void setReverbSendGain(float rgain) {
64             this.rgain = rgain;
65         }
66 
processMix(float[] in, float[] out, float[] rout)67         public void processMix(float[] in, float[] out, float[] rout) {
68             float gain = this.gain;
69             float delay = this.delay;
70             float feedback = this.feedback;
71 
72             float[] delaybuffer = this.delaybuffer;
73             int len = in.length;
74             float delaydelta = (delay - lastdelay) / len;
75             int rnlen = delaybuffer.length;
76             int rovepos = this.rovepos;
77 
78             if (rout == null)
79                 for (int i = 0; i < len; i++) {
80                     float r = rovepos - (lastdelay + 2) + rnlen;
81                     int ri = (int) r;
82                     float s = r - ri;
83                     float a = delaybuffer[ri % rnlen];
84                     float b = delaybuffer[(ri + 1) % rnlen];
85                     float o = a * (1 - s) + b * (s);
86                     out[i] += o * gain;
87                     delaybuffer[rovepos] = in[i] + o * feedback;
88                     rovepos = (rovepos + 1) % rnlen;
89                     lastdelay += delaydelta;
90                 }
91             else
92                 for (int i = 0; i < len; i++) {
93                     float r = rovepos - (lastdelay + 2) + rnlen;
94                     int ri = (int) r;
95                     float s = r - ri;
96                     float a = delaybuffer[ri % rnlen];
97                     float b = delaybuffer[(ri + 1) % rnlen];
98                     float o = a * (1 - s) + b * (s);
99                     out[i] += o * gain;
100                     rout[i] += o * rgain;
101                     delaybuffer[rovepos] = in[i] + o * feedback;
102                     rovepos = (rovepos + 1) % rnlen;
103                     lastdelay += delaydelta;
104                 }
105             this.rovepos = rovepos;
106             lastdelay = delay;
107         }
108 
processReplace(float[] in, float[] out, float[] rout)109         public void processReplace(float[] in, float[] out, float[] rout) {
110             Arrays.fill(out, 0);
111             Arrays.fill(rout, 0);
112             processMix(in, out, rout);
113         }
114     }
115 
116     private static class LFODelay {
117 
118         private double phase = 1;
119         private double phase_step = 0;
120         private double depth = 0;
121         private VariableDelay vdelay;
122         private final double samplerate;
123         private final double controlrate;
124 
LFODelay(double samplerate, double controlrate)125         LFODelay(double samplerate, double controlrate) {
126             this.samplerate = samplerate;
127             this.controlrate = controlrate;
128             // vdelay = new VariableDelay((int)(samplerate*4));
129             vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
130 
131         }
132 
setDepth(double depth)133         public void setDepth(double depth) {
134             this.depth = depth * samplerate;
135             vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
136         }
137 
setRate(double rate)138         public void setRate(double rate) {
139             double g = (Math.PI * 2) * (rate / controlrate);
140             phase_step = g;
141         }
142 
setPhase(double phase)143         public void setPhase(double phase) {
144             this.phase = phase;
145         }
146 
setFeedBack(float feedback)147         public void setFeedBack(float feedback) {
148             vdelay.setFeedBack(feedback);
149         }
150 
setGain(float gain)151         public void setGain(float gain) {
152             vdelay.setGain(gain);
153         }
154 
setReverbSendGain(float rgain)155         public void setReverbSendGain(float rgain) {
156             vdelay.setReverbSendGain(rgain);
157         }
158 
processMix(float[] in, float[] out, float[] rout)159         public void processMix(float[] in, float[] out, float[] rout) {
160             phase += phase_step;
161             while(phase > (Math.PI * 2)) phase -= (Math.PI * 2);
162             vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2)));
163             vdelay.processMix(in, out, rout);
164         }
165 
processReplace(float[] in, float[] out, float[] rout)166         public void processReplace(float[] in, float[] out, float[] rout) {
167             phase += phase_step;
168             while(phase > (Math.PI * 2)) phase -= (Math.PI * 2);
169             vdelay.setDelay((float) (depth * 0.5 * (Math.cos(phase) + 2)));
170             vdelay.processReplace(in, out, rout);
171 
172         }
173     }
174     private boolean mix = true;
175     private SoftAudioBuffer inputA;
176     private SoftAudioBuffer left;
177     private SoftAudioBuffer right;
178     private SoftAudioBuffer reverb;
179     private LFODelay vdelay1L;
180     private LFODelay vdelay1R;
181     private float rgain = 0;
182     private boolean dirty = true;
183     private double dirty_vdelay1L_rate;
184     private double dirty_vdelay1R_rate;
185     private double dirty_vdelay1L_depth;
186     private double dirty_vdelay1R_depth;
187     private float dirty_vdelay1L_feedback;
188     private float dirty_vdelay1R_feedback;
189     private float dirty_vdelay1L_reverbsendgain;
190     private float dirty_vdelay1R_reverbsendgain;
191     private float controlrate;
192 
init(float samplerate, float controlrate)193     public void init(float samplerate, float controlrate) {
194         this.controlrate = controlrate;
195         vdelay1L = new LFODelay(samplerate, controlrate);
196         vdelay1R = new LFODelay(samplerate, controlrate);
197         vdelay1L.setGain(1.0f); // %
198         vdelay1R.setGain(1.0f); // %
199         vdelay1L.setPhase(0.5 * Math.PI);
200         vdelay1R.setPhase(0);
201 
202         globalParameterControlChange(new int[]{0x01 * 128 + 0x02}, 0, 2);
203     }
204 
globalParameterControlChange(int[] slothpath, long param, long value)205     public void globalParameterControlChange(int[] slothpath, long param,
206             long value) {
207         if (slothpath.length == 1) {
208             if (slothpath[0] == 0x01 * 128 + 0x02) {
209                 if (param == 0) { // Chorus Type
210                     switch ((int)value) {
211                     case 0: // Chorus 1 0 (0%) 3 (0.4Hz) 5 (1.9ms) 0 (0%)
212                         globalParameterControlChange(slothpath, 3, 0);
213                         globalParameterControlChange(slothpath, 1, 3);
214                         globalParameterControlChange(slothpath, 2, 5);
215                         globalParameterControlChange(slothpath, 4, 0);
216                         break;
217                     case 1: // Chorus 2 5 (4%) 9 (1.1Hz) 19 (6.3ms) 0 (0%)
218                         globalParameterControlChange(slothpath, 3, 5);
219                         globalParameterControlChange(slothpath, 1, 9);
220                         globalParameterControlChange(slothpath, 2, 19);
221                         globalParameterControlChange(slothpath, 4, 0);
222                         break;
223                     case 2: // Chorus 3 8 (6%) 3 (0.4Hz) 19 (6.3ms) 0 (0%)
224                         globalParameterControlChange(slothpath, 3, 8);
225                         globalParameterControlChange(slothpath, 1, 3);
226                         globalParameterControlChange(slothpath, 2, 19);
227                         globalParameterControlChange(slothpath, 4, 0);
228                         break;
229                     case 3: // Chorus 4 16 (12%) 9 (1.1Hz) 16 (5.3ms) 0 (0%)
230                         globalParameterControlChange(slothpath, 3, 16);
231                         globalParameterControlChange(slothpath, 1, 9);
232                         globalParameterControlChange(slothpath, 2, 16);
233                         globalParameterControlChange(slothpath, 4, 0);
234                         break;
235                     case 4: // FB Chorus 64 (49%) 2 (0.2Hz) 24 (7.8ms) 0 (0%)
236                         globalParameterControlChange(slothpath, 3, 64);
237                         globalParameterControlChange(slothpath, 1, 2);
238                         globalParameterControlChange(slothpath, 2, 24);
239                         globalParameterControlChange(slothpath, 4, 0);
240                         break;
241                     case 5: // Flanger 112 (86%) 1 (0.1Hz) 5 (1.9ms) 0 (0%)
242                         globalParameterControlChange(slothpath, 3, 112);
243                         globalParameterControlChange(slothpath, 1, 1);
244                         globalParameterControlChange(slothpath, 2, 5);
245                         globalParameterControlChange(slothpath, 4, 0);
246                         break;
247                     default:
248                         break;
249                     }
250                 } else if (param == 1) { // Mod Rate
251                     dirty_vdelay1L_rate = (value * 0.122);
252                     dirty_vdelay1R_rate = (value * 0.122);
253                     dirty = true;
254                 } else if (param == 2) { // Mod Depth
255                     dirty_vdelay1L_depth = ((value + 1) / 3200.0);
256                     dirty_vdelay1R_depth = ((value + 1) / 3200.0);
257                     dirty = true;
258                 } else if (param == 3) { // Feedback
259                     dirty_vdelay1L_feedback = (value * 0.00763f);
260                     dirty_vdelay1R_feedback = (value * 0.00763f);
261                     dirty = true;
262                 }
263                 if (param == 4) { // Send to Reverb
264                     rgain = value * 0.00787f;
265                     dirty_vdelay1L_reverbsendgain = (value * 0.00787f);
266                     dirty_vdelay1R_reverbsendgain = (value * 0.00787f);
267                     dirty = true;
268                 }
269 
270             }
271         }
272     }
273 
processControlLogic()274     public void processControlLogic() {
275         if (dirty) {
276             dirty = false;
277             vdelay1L.setRate(dirty_vdelay1L_rate);
278             vdelay1R.setRate(dirty_vdelay1R_rate);
279             vdelay1L.setDepth(dirty_vdelay1L_depth);
280             vdelay1R.setDepth(dirty_vdelay1R_depth);
281             vdelay1L.setFeedBack(dirty_vdelay1L_feedback);
282             vdelay1R.setFeedBack(dirty_vdelay1R_feedback);
283             vdelay1L.setReverbSendGain(dirty_vdelay1L_reverbsendgain);
284             vdelay1R.setReverbSendGain(dirty_vdelay1R_reverbsendgain);
285         }
286     }
287     double silentcounter = 1000;
288 
processAudio()289     public void processAudio() {
290 
291         if (inputA.isSilent()) {
292             silentcounter += 1 / controlrate;
293 
294             if (silentcounter > 1) {
295                 if (!mix) {
296                     left.clear();
297                     right.clear();
298                 }
299                 return;
300             }
301         } else
302             silentcounter = 0;
303 
304         float[] inputA = this.inputA.array();
305         float[] left = this.left.array();
306         float[] right = this.right == null ? null : this.right.array();
307         float[] reverb = rgain != 0 ? this.reverb.array() : null;
308 
309         if (mix) {
310             vdelay1L.processMix(inputA, left, reverb);
311             if (right != null)
312                 vdelay1R.processMix(inputA, right, reverb);
313         } else {
314             vdelay1L.processReplace(inputA, left, reverb);
315             if (right != null)
316                 vdelay1R.processReplace(inputA, right, reverb);
317         }
318     }
319 
setInput(int pin, SoftAudioBuffer input)320     public void setInput(int pin, SoftAudioBuffer input) {
321         if (pin == 0)
322             inputA = input;
323     }
324 
setMixMode(boolean mix)325     public void setMixMode(boolean mix) {
326         this.mix = mix;
327     }
328 
setOutput(int pin, SoftAudioBuffer output)329     public void setOutput(int pin, SoftAudioBuffer output) {
330         if (pin == 0)
331             left = output;
332         if (pin == 1)
333             right = output;
334         if (pin == 2)
335             reverb = output;
336     }
337 }
338