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
26package com.sun.media.sound;
27
28import 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 */
36public 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
48        VariableDelay(int maxbuffersize) {
49            delaybuffer = new float[maxbuffersize];
50        }
51
52        public void setDelay(float delay) {
53            this.delay = delay;
54        }
55
56        public void setFeedBack(float feedback) {
57            this.feedback = feedback;
58        }
59
60        public void setGain(float gain) {
61            this.gain = gain;
62        }
63
64        public void setReverbSendGain(float rgain) {
65            this.rgain = rgain;
66        }
67
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
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
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
134        public void setDepth(double depth) {
135            this.depth = depth * samplerate;
136            vdelay = new VariableDelay((int) ((this.depth + 10) * 2));
137        }
138
139        public void setRate(double rate) {
140            double g = (Math.PI * 2) * (rate / controlrate);
141            phase_step = g;
142        }
143
144        public void setPhase(double phase) {
145            this.phase = phase;
146        }
147
148        public void setFeedBack(float feedback) {
149            vdelay.setFeedBack(feedback);
150        }
151
152        public void setGain(float gain) {
153            vdelay.setGain(gain);
154        }
155
156        public void setReverbSendGain(float rgain) {
157            vdelay.setReverbSendGain(rgain);
158        }
159
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
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
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
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
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
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
326    public void setInput(int pin, SoftAudioBuffer input) {
327        if (pin == 0)
328            inputA = input;
329    }
330
331    @Override
332    public void setMixMode(boolean mix) {
333        this.mix = mix;
334    }
335
336    @Override
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