1 /*
2  * Copyright 2007 Sun Microsystems, Inc.  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.  Sun designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22  * CA 95054 USA or visit www.sun.com if you need additional information or
23  * have any 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 class SoftChorus implements SoftAudioProcessor {
36 
37     private static class VariableDelay {
38 
39         private float[] delaybuffer;
40         private int rovepos = 0;
41         private volatile float gain = 1;
42         private volatile float rgain = 0;
43         private volatile float delay = 0;
44         private float lastdelay = 0;
45         private volatile float feedback = 0;
46 
VariableDelay(int maxbuffersize)47         public 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 volatile double c_cos_delta;
119         private volatile double c_sin_delta;
120         private double c_cos = 1;
121         private double c_sin = 0;
122         private double depth = 0;
123         private VariableDelay vdelay;
124         private double samplerate;
125         private double controlrate;
126 
LFODelay(double samplerate, double controlrate)127         public LFODelay(double samplerate, double controlrate) {
128             this.samplerate = samplerate;
129             this.controlrate = controlrate;
130             // vdelay = new VariableDelay((int)(samplerate*4));
131             vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
132 
133         }
134 
setDepth(double depth)135         public void setDepth(double depth) {
136             this.depth = depth * samplerate;
137             vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
138         }
139 
setRate(double rate)140         public void setRate(double rate) {
141             double g = (Math.PI * 2) * (rate / controlrate);
142             c_cos_delta = Math.cos(g);
143             c_sin_delta = Math.sin(g);
144         }
145 
setPhase(double phase)146         public void setPhase(double phase) {
147             c_cos = Math.cos(phase);
148             c_sin = Math.sin(phase);
149         }
150 
setFeedBack(float feedback)151         public void setFeedBack(float feedback) {
152             vdelay.setFeedBack(feedback);
153         }
154 
setGain(float gain)155         public void setGain(float gain) {
156             vdelay.setGain(gain);
157         }
158 
setReverbSendGain(float rgain)159         public void setReverbSendGain(float rgain) {
160             vdelay.setReverbSendGain(rgain);
161         }
162 
processMix(float[] in, float[] out, float[] rout)163         public void processMix(float[] in, float[] out, float[] rout) {
164             c_cos = c_cos * c_cos_delta - c_sin * c_sin_delta;
165             c_sin = c_cos * c_sin_delta + c_sin * c_cos_delta;
166             vdelay.setDelay((float) (depth * 0.5 * (c_cos + 2)));
167             vdelay.processMix(in, out, rout);
168         }
169 
processReplace(float[] in, float[] out, float[] rout)170         public void processReplace(float[] in, float[] out, float[] rout) {
171             c_cos = c_cos * c_cos_delta - c_sin * c_sin_delta;
172             c_sin = c_cos * c_sin_delta + c_sin * c_cos_delta;
173             vdelay.setDelay((float) (depth * 0.5 * (c_cos + 2)));
174             vdelay.processReplace(in, out, rout);
175 
176         }
177     }
178     private boolean mix = true;
179     private SoftAudioBuffer inputA;
180     private SoftAudioBuffer left;
181     private SoftAudioBuffer right;
182     private SoftAudioBuffer reverb;
183     private LFODelay vdelay1L;
184     private LFODelay vdelay1R;
185     private float rgain = 0;
186     private boolean dirty = true;
187     private double dirty_vdelay1L_rate;
188     private double dirty_vdelay1R_rate;
189     private double dirty_vdelay1L_depth;
190     private double dirty_vdelay1R_depth;
191     private float dirty_vdelay1L_feedback;
192     private float dirty_vdelay1R_feedback;
193     private float dirty_vdelay1L_reverbsendgain;
194     private float dirty_vdelay1R_reverbsendgain;
195     private float controlrate;
196 
init(float samplerate, float controlrate)197     public void init(float samplerate, float controlrate) {
198         this.controlrate = controlrate;
199         vdelay1L = new LFODelay(samplerate, controlrate);
200         vdelay1R = new LFODelay(samplerate, controlrate);
201         vdelay1L.setGain(1.0f); // %
202         vdelay1R.setGain(1.0f); // %
203         vdelay1L.setPhase(0.5 * Math.PI);
204         vdelay1R.setPhase(0);
205 
206         globalParameterControlChange(new int[]{0x01 * 128 + 0x02}, 0, 2);
207     }
208 
globalParameterControlChange(int[] slothpath, long param, long value)209     public void globalParameterControlChange(int[] slothpath, long param,
210             long value) {
211         if (slothpath.length == 1) {
212             if (slothpath[0] == 0x01 * 128 + 0x02) {
213                 if (param == 0) { // Chorus Type
214                     switch ((int)value) {
215                     case 0: // Chorus 1 0 (0%) 3 (0.4Hz) 5 (1.9ms) 0 (0%)
216                         globalParameterControlChange(slothpath, 3, 0);
217                         globalParameterControlChange(slothpath, 1, 3);
218                         globalParameterControlChange(slothpath, 2, 5);
219                         globalParameterControlChange(slothpath, 4, 0);
220                         break;
221                     case 1: // Chorus 2 5 (4%) 9 (1.1Hz) 19 (6.3ms) 0 (0%)
222                         globalParameterControlChange(slothpath, 3, 5);
223                         globalParameterControlChange(slothpath, 1, 9);
224                         globalParameterControlChange(slothpath, 2, 19);
225                         globalParameterControlChange(slothpath, 4, 0);
226                         break;
227                     case 2: // Chorus 3 8 (6%) 3 (0.4Hz) 19 (6.3ms) 0 (0%)
228                         globalParameterControlChange(slothpath, 3, 8);
229                         globalParameterControlChange(slothpath, 1, 3);
230                         globalParameterControlChange(slothpath, 2, 19);
231                         globalParameterControlChange(slothpath, 4, 0);
232                         break;
233                     case 3: // Chorus 4 16 (12%) 9 (1.1Hz) 16 (5.3ms) 0 (0%)
234                         globalParameterControlChange(slothpath, 3, 16);
235                         globalParameterControlChange(slothpath, 1, 9);
236                         globalParameterControlChange(slothpath, 2, 16);
237                         globalParameterControlChange(slothpath, 4, 0);
238                         break;
239                     case 4: // FB Chorus 64 (49%) 2 (0.2Hz) 24 (7.8ms) 0 (0%)
240                         globalParameterControlChange(slothpath, 3, 64);
241                         globalParameterControlChange(slothpath, 1, 2);
242                         globalParameterControlChange(slothpath, 2, 24);
243                         globalParameterControlChange(slothpath, 4, 0);
244                         break;
245                     case 5: // Flanger 112 (86%) 1 (0.1Hz) 5 (1.9ms) 0 (0%)
246                         globalParameterControlChange(slothpath, 3, 112);
247                         globalParameterControlChange(slothpath, 1, 1);
248                         globalParameterControlChange(slothpath, 2, 5);
249                         globalParameterControlChange(slothpath, 4, 0);
250                         break;
251                     default:
252                         break;
253                     }
254                 } else if (param == 1) { // Mod Rate
255                     dirty_vdelay1L_rate = (value * 0.122);
256                     dirty_vdelay1R_rate = (value * 0.122);
257                     dirty = true;
258                 } else if (param == 2) { // Mod Depth
259                     dirty_vdelay1L_depth = ((value + 1) / 3200.0);
260                     dirty_vdelay1R_depth = ((value + 1) / 3200.0);
261                     dirty = true;
262                 } else if (param == 3) { // Feedback
263                     dirty_vdelay1L_feedback = (value * 0.00763f);
264                     dirty_vdelay1R_feedback = (value * 0.00763f);
265                     dirty = true;
266                 }
267                 if (param == 4) { // Send to Reverb
268                     rgain = value * 0.00787f;
269                     dirty_vdelay1L_reverbsendgain = (value * 0.00787f);
270                     dirty_vdelay1R_reverbsendgain = (value * 0.00787f);
271                     dirty = true;
272                 }
273 
274             }
275         }
276     }
277 
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 
processAudio()293     public void processAudio() {
294 
295         if (inputA.isSilent()) {
296             silentcounter += 1 / controlrate;
297 
298             if (silentcounter > 1) {
299                 if (!mix) {
300                     left.clear();
301                     right.clear();
302                 }
303                 return;
304             }
305         } else
306             silentcounter = 0;
307 
308         float[] inputA = this.inputA.array();
309         float[] left = this.left.array();
310         float[] right = this.right == null ? null : this.right.array();
311         float[] reverb = rgain != 0 ? this.reverb.array() : null;
312 
313         if (mix) {
314             vdelay1L.processMix(inputA, left, reverb);
315             if (right != null)
316                 vdelay1R.processMix(inputA, right, reverb);
317         } else {
318             vdelay1L.processReplace(inputA, left, reverb);
319             if (right != null)
320                 vdelay1R.processReplace(inputA, right, reverb);
321         }
322     }
323 
setInput(int pin, SoftAudioBuffer input)324     public void setInput(int pin, SoftAudioBuffer input) {
325         if (pin == 0)
326             inputA = input;
327     }
328 
setMixMode(boolean mix)329     public void setMixMode(boolean mix) {
330         this.mix = mix;
331     }
332 
setOutput(int pin, SoftAudioBuffer output)333     public void setOutput(int pin, SoftAudioBuffer output) {
334         if (pin == 0)
335             left = output;
336         if (pin == 1)
337             right = output;
338         if (pin == 2)
339             reverb = output;
340     }
341 }
342