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