1/*
2 * Copyright (c) 2008, 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.io.IOException;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.List;
32
33import javax.sound.sampled.AudioFormat;
34import javax.sound.sampled.AudioSystem;
35import javax.sound.sampled.BooleanControl;
36import javax.sound.sampled.Control;
37import javax.sound.sampled.Control.Type;
38import javax.sound.sampled.DataLine;
39import javax.sound.sampled.FloatControl;
40import javax.sound.sampled.LineEvent;
41import javax.sound.sampled.LineListener;
42
43/**
44 * General software mixing line.
45 *
46 * @author Karl Helgason
47 */
48public abstract class SoftMixingDataLine implements DataLine {
49
50    public static final FloatControl.Type CHORUS_SEND = new FloatControl.Type(
51            "Chorus Send") {
52    };
53
54    protected static final class AudioFloatInputStreamResampler extends
55            AudioFloatInputStream {
56
57        private final AudioFloatInputStream ais;
58
59        private final AudioFormat targetFormat;
60
61        private float[] skipbuffer;
62
63        private SoftAbstractResampler resampler;
64
65        private final float[] pitch = new float[1];
66
67        private final float[] ibuffer2;
68
69        private final float[][] ibuffer;
70
71        private float ibuffer_index = 0;
72
73        private int ibuffer_len = 0;
74
75        private int nrofchannels = 0;
76
77        private float[][] cbuffer;
78
79        private final int buffer_len = 512;
80
81        private final int pad;
82
83        private final int pad2;
84
85        private final float[] ix = new float[1];
86
87        private final int[] ox = new int[1];
88
89        private float[][] mark_ibuffer = null;
90
91        private float mark_ibuffer_index = 0;
92
93        private int mark_ibuffer_len = 0;
94
95        public AudioFloatInputStreamResampler(AudioFloatInputStream ais,
96                AudioFormat format) {
97            this.ais = ais;
98            AudioFormat sourceFormat = ais.getFormat();
99            targetFormat = new AudioFormat(sourceFormat.getEncoding(), format
100                    .getSampleRate(), sourceFormat.getSampleSizeInBits(),
101                    sourceFormat.getChannels(), sourceFormat.getFrameSize(),
102                    format.getSampleRate(), sourceFormat.isBigEndian());
103            nrofchannels = targetFormat.getChannels();
104            Object interpolation = format.getProperty("interpolation");
105            if (interpolation != null && (interpolation instanceof String)) {
106                String resamplerType = (String) interpolation;
107                if (resamplerType.equalsIgnoreCase("point"))
108                    this.resampler = new SoftPointResampler();
109                if (resamplerType.equalsIgnoreCase("linear"))
110                    this.resampler = new SoftLinearResampler2();
111                if (resamplerType.equalsIgnoreCase("linear1"))
112                    this.resampler = new SoftLinearResampler();
113                if (resamplerType.equalsIgnoreCase("linear2"))
114                    this.resampler = new SoftLinearResampler2();
115                if (resamplerType.equalsIgnoreCase("cubic"))
116                    this.resampler = new SoftCubicResampler();
117                if (resamplerType.equalsIgnoreCase("lanczos"))
118                    this.resampler = new SoftLanczosResampler();
119                if (resamplerType.equalsIgnoreCase("sinc"))
120                    this.resampler = new SoftSincResampler();
121            }
122            if (resampler == null)
123                resampler = new SoftLinearResampler2(); // new
124            // SoftLinearResampler2();
125            pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();
126            pad = resampler.getPadding();
127            pad2 = pad * 2;
128            ibuffer = new float[nrofchannels][buffer_len + pad2];
129            ibuffer2 = new float[nrofchannels * buffer_len];
130            ibuffer_index = buffer_len + pad;
131            ibuffer_len = buffer_len;
132        }
133
134        @Override
135        public int available() throws IOException {
136            return 0;
137        }
138
139        @Override
140        public void close() throws IOException {
141            ais.close();
142        }
143
144        @Override
145        public AudioFormat getFormat() {
146            return targetFormat;
147        }
148
149        @Override
150        public long getFrameLength() {
151            return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength();
152        }
153
154        @Override
155        public void mark(int readlimit) {
156            ais.mark((int) (readlimit * pitch[0]));
157            mark_ibuffer_index = ibuffer_index;
158            mark_ibuffer_len = ibuffer_len;
159            if (mark_ibuffer == null) {
160                mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];
161            }
162            for (int c = 0; c < ibuffer.length; c++) {
163                float[] from = ibuffer[c];
164                float[] to = mark_ibuffer[c];
165                for (int i = 0; i < to.length; i++) {
166                    to[i] = from[i];
167                }
168            }
169        }
170
171        @Override
172        public boolean markSupported() {
173            return ais.markSupported();
174        }
175
176        private void readNextBuffer() throws IOException {
177
178            if (ibuffer_len == -1)
179                return;
180
181            for (int c = 0; c < nrofchannels; c++) {
182                float[] buff = ibuffer[c];
183                int buffer_len_pad = ibuffer_len + pad2;
184                for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) {
185                    buff[ix] = buff[i];
186                }
187            }
188
189            ibuffer_index -= (ibuffer_len);
190
191            ibuffer_len = ais.read(ibuffer2);
192            if (ibuffer_len >= 0) {
193                while (ibuffer_len < ibuffer2.length) {
194                    int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length
195                            - ibuffer_len);
196                    if (ret == -1)
197                        break;
198                    ibuffer_len += ret;
199                }
200                Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);
201                ibuffer_len /= nrofchannels;
202            } else {
203                Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);
204            }
205
206            int ibuffer2_len = ibuffer2.length;
207            for (int c = 0; c < nrofchannels; c++) {
208                float[] buff = ibuffer[c];
209                for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) {
210                    buff[ix] = ibuffer2[i];
211                }
212            }
213
214        }
215
216        @Override
217        public int read(float[] b, int off, int len) throws IOException {
218
219            if (cbuffer == null || cbuffer[0].length < len / nrofchannels) {
220                cbuffer = new float[nrofchannels][len / nrofchannels];
221            }
222            if (ibuffer_len == -1)
223                return -1;
224            if (len < 0)
225                return 0;
226            int remain = len / nrofchannels;
227            int destPos = 0;
228            int in_end = ibuffer_len;
229            while (remain > 0) {
230                if (ibuffer_len >= 0) {
231                    if (ibuffer_index >= (ibuffer_len + pad))
232                        readNextBuffer();
233                    in_end = ibuffer_len + pad;
234                }
235
236                if (ibuffer_len < 0) {
237                    in_end = pad2;
238                    if (ibuffer_index >= in_end)
239                        break;
240                }
241
242                if (ibuffer_index < 0)
243                    break;
244                int preDestPos = destPos;
245                for (int c = 0; c < nrofchannels; c++) {
246                    ix[0] = ibuffer_index;
247                    ox[0] = destPos;
248                    float[] buff = ibuffer[c];
249                    resampler.interpolate(buff, ix, in_end, pitch, 0,
250                            cbuffer[c], ox, len / nrofchannels);
251                }
252                ibuffer_index = ix[0];
253                destPos = ox[0];
254                remain -= destPos - preDestPos;
255            }
256            for (int c = 0; c < nrofchannels; c++) {
257                int ix = 0;
258                float[] buff = cbuffer[c];
259                for (int i = c; i < b.length; i += nrofchannels) {
260                    b[i] = buff[ix++];
261                }
262            }
263            return len - remain * nrofchannels;
264        }
265
266        @Override
267        public void reset() throws IOException {
268            ais.reset();
269            if (mark_ibuffer == null)
270                return;
271            ibuffer_index = mark_ibuffer_index;
272            ibuffer_len = mark_ibuffer_len;
273            for (int c = 0; c < ibuffer.length; c++) {
274                float[] from = mark_ibuffer[c];
275                float[] to = ibuffer[c];
276                for (int i = 0; i < to.length; i++) {
277                    to[i] = from[i];
278                }
279            }
280
281        }
282
283        @Override
284        public long skip(long len) throws IOException {
285            if (len > 0)
286                return 0;
287            if (skipbuffer == null)
288                skipbuffer = new float[1024 * targetFormat.getFrameSize()];
289            float[] l_skipbuffer = skipbuffer;
290            long remain = len;
291            while (remain > 0) {
292                int ret = read(l_skipbuffer, 0, (int) Math.min(remain,
293                        skipbuffer.length));
294                if (ret < 0) {
295                    if (remain == len)
296                        return ret;
297                    break;
298                }
299                remain -= ret;
300            }
301            return len - remain;
302
303        }
304
305    }
306
307    private final class Gain extends FloatControl {
308
309        private Gain() {
310
311            super(FloatControl.Type.MASTER_GAIN, -80f, 6.0206f, 80f / 128.0f,
312                    -1, 0.0f, "dB", "Minimum", "", "Maximum");
313        }
314
315        @Override
316        public void setValue(float newValue) {
317            super.setValue(newValue);
318            calcVolume();
319        }
320    }
321
322    private final class Mute extends BooleanControl {
323
324        private Mute() {
325            super(BooleanControl.Type.MUTE, false, "True", "False");
326        }
327
328        @Override
329        public void setValue(boolean newValue) {
330            super.setValue(newValue);
331            calcVolume();
332        }
333    }
334
335    private final class ApplyReverb extends BooleanControl {
336
337        private ApplyReverb() {
338            super(BooleanControl.Type.APPLY_REVERB, false, "True", "False");
339        }
340
341        @Override
342        public void setValue(boolean newValue) {
343            super.setValue(newValue);
344            calcVolume();
345        }
346
347    }
348
349    private final class Balance extends FloatControl {
350
351        private Balance() {
352            super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1,
353                    0.0f, "", "Left", "Center", "Right");
354        }
355
356        @Override
357        public void setValue(float newValue) {
358            super.setValue(newValue);
359            calcVolume();
360        }
361
362    }
363
364    private final class Pan extends FloatControl {
365
366        private Pan() {
367            super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1,
368                    0.0f, "", "Left", "Center", "Right");
369        }
370
371        @Override
372        public void setValue(float newValue) {
373            super.setValue(newValue);
374            balance_control.setValue(newValue);
375        }
376
377        @Override
378        public float getValue() {
379            return balance_control.getValue();
380        }
381
382    }
383
384    private final class ReverbSend extends FloatControl {
385
386        private ReverbSend() {
387            super(FloatControl.Type.REVERB_SEND, -80f, 6.0206f, 80f / 128.0f,
388                    -1, -80f, "dB", "Minimum", "", "Maximum");
389        }
390
391        @Override
392        public void setValue(float newValue) {
393            super.setValue(newValue);
394            balance_control.setValue(newValue);
395        }
396
397    }
398
399    private final class ChorusSend extends FloatControl {
400
401        private ChorusSend() {
402            super(CHORUS_SEND, -80f, 6.0206f, 80f / 128.0f, -1, -80f, "dB",
403                    "Minimum", "", "Maximum");
404        }
405
406        @Override
407        public void setValue(float newValue) {
408            super.setValue(newValue);
409            balance_control.setValue(newValue);
410        }
411
412    }
413
414    private final Gain gain_control = new Gain();
415
416    private final Mute mute_control = new Mute();
417
418    private final Balance balance_control = new Balance();
419
420    private final Pan pan_control = new Pan();
421
422    private final ReverbSend reverbsend_control = new ReverbSend();
423
424    private final ChorusSend chorussend_control = new ChorusSend();
425
426    private final ApplyReverb apply_reverb = new ApplyReverb();
427
428    private final Control[] controls;
429
430    float leftgain = 1;
431
432    float rightgain = 1;
433
434    float eff1gain = 0;
435
436    float eff2gain = 0;
437
438    List<LineListener> listeners = new ArrayList<>();
439
440    final Object control_mutex;
441
442    SoftMixingMixer mixer;
443
444    DataLine.Info info;
445
446    protected abstract void processControlLogic();
447
448    protected abstract void processAudioLogic(SoftAudioBuffer[] buffers);
449
450    SoftMixingDataLine(SoftMixingMixer mixer, DataLine.Info info) {
451        this.mixer = mixer;
452        this.info = info;
453        this.control_mutex = mixer.control_mutex;
454
455        controls = new Control[] { gain_control, mute_control, balance_control,
456                pan_control, reverbsend_control, chorussend_control,
457                apply_reverb };
458        calcVolume();
459    }
460
461    final void calcVolume() {
462        synchronized (control_mutex) {
463            double gain = Math.pow(10.0, gain_control.getValue() / 20.0);
464            if (mute_control.getValue())
465                gain = 0;
466            leftgain = (float) gain;
467            rightgain = (float) gain;
468            if (mixer.getFormat().getChannels() > 1) {
469                // -1 = Left, 0 Center, 1 = Right
470                double balance = balance_control.getValue();
471                if (balance > 0)
472                    leftgain *= (1 - balance);
473                else
474                    rightgain *= (1 + balance);
475
476            }
477        }
478
479        eff1gain = (float) Math.pow(10.0, reverbsend_control.getValue() / 20.0);
480        eff2gain = (float) Math.pow(10.0, chorussend_control.getValue() / 20.0);
481
482        if (!apply_reverb.getValue()) {
483            eff1gain = 0;
484        }
485    }
486
487    final void sendEvent(LineEvent event) {
488        if (listeners.size() == 0)
489            return;
490        LineListener[] listener_array = listeners
491                .toArray(new LineListener[listeners.size()]);
492        for (LineListener listener : listener_array) {
493            listener.update(event);
494        }
495    }
496
497    @Override
498    public final void addLineListener(LineListener listener) {
499        synchronized (control_mutex) {
500            listeners.add(listener);
501        }
502    }
503
504    @Override
505    public final void removeLineListener(LineListener listener) {
506        synchronized (control_mutex) {
507            listeners.add(listener);
508        }
509    }
510
511    @Override
512    public final javax.sound.sampled.Line.Info getLineInfo() {
513        return info;
514    }
515
516    @Override
517    public final Control getControl(Type control) {
518        if (control != null) {
519            for (int i = 0; i < controls.length; i++) {
520                if (controls[i].getType() == control) {
521                    return controls[i];
522                }
523            }
524        }
525        throw new IllegalArgumentException("Unsupported control type : "
526                + control);
527    }
528
529    @Override
530    public final Control[] getControls() {
531        return Arrays.copyOf(controls, controls.length);
532    }
533
534    @Override
535    public final boolean isControlSupported(Type control) {
536        if (control != null) {
537            for (int i = 0; i < controls.length; i++) {
538                if (controls[i].getType() == control) {
539                    return true;
540                }
541            }
542        }
543        return false;
544    }
545}
546