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.io.InputStream;
30import java.util.Arrays;
31
32import javax.sound.sampled.AudioFormat;
33import javax.sound.sampled.AudioInputStream;
34import javax.sound.sampled.AudioSystem;
35import javax.sound.sampled.DataLine;
36import javax.sound.sampled.LineEvent;
37import javax.sound.sampled.LineUnavailableException;
38import javax.sound.sampled.SourceDataLine;
39
40/**
41 * SourceDataLine implementation for the SoftMixingMixer.
42 *
43 * @author Karl Helgason
44 */
45public final class SoftMixingSourceDataLine extends SoftMixingDataLine
46        implements SourceDataLine {
47
48    private boolean open = false;
49
50    private AudioFormat format = new AudioFormat(44100.0f, 16, 2, true, false);
51
52    private int framesize;
53
54    private int bufferSize = -1;
55
56    private float[] readbuffer;
57
58    private boolean active = false;
59
60    private byte[] cycling_buffer;
61
62    private int cycling_read_pos = 0;
63
64    private int cycling_write_pos = 0;
65
66    private int cycling_avail = 0;
67
68    private long cycling_framepos = 0;
69
70    private AudioFloatInputStream afis;
71
72    private static class NonBlockingFloatInputStream extends
73            AudioFloatInputStream {
74        AudioFloatInputStream ais;
75
76        NonBlockingFloatInputStream(AudioFloatInputStream ais) {
77            this.ais = ais;
78        }
79
80        @Override
81        public int available() throws IOException {
82            return ais.available();
83        }
84
85        @Override
86        public void close() throws IOException {
87            ais.close();
88        }
89
90        @Override
91        public AudioFormat getFormat() {
92            return ais.getFormat();
93        }
94
95        @Override
96        public long getFrameLength() {
97            return ais.getFrameLength();
98        }
99
100        @Override
101        public void mark(int readlimit) {
102            ais.mark(readlimit);
103        }
104
105        @Override
106        public boolean markSupported() {
107            return ais.markSupported();
108        }
109
110        @Override
111        public int read(float[] b, int off, int len) throws IOException {
112            int avail = available();
113            if (len > avail) {
114                int ret = ais.read(b, off, avail);
115                Arrays.fill(b, off + ret, off + len, 0);
116                return len;
117            }
118            return ais.read(b, off, len);
119        }
120
121        @Override
122        public void reset() throws IOException {
123            ais.reset();
124        }
125
126        @Override
127        public long skip(long len) throws IOException {
128            return ais.skip(len);
129        }
130
131    }
132
133    SoftMixingSourceDataLine(SoftMixingMixer mixer, DataLine.Info info) {
134        super(mixer, info);
135    }
136
137    @Override
138    public int write(byte[] b, int off, int len) {
139        if (!isOpen())
140            return 0;
141        if (len % framesize != 0)
142            throw new IllegalArgumentException(
143                    "Number of bytes does not represent an integral number of sample frames.");
144        if (off < 0) {
145            throw new ArrayIndexOutOfBoundsException(off);
146        }
147        if ((long)off + (long)len > (long)b.length) {
148            throw new ArrayIndexOutOfBoundsException(b.length);
149        }
150
151        byte[] buff = cycling_buffer;
152        int buff_len = cycling_buffer.length;
153
154        int l = 0;
155        while (l != len) {
156            int avail;
157            synchronized (cycling_buffer) {
158                int pos = cycling_write_pos;
159                avail = cycling_avail;
160                while (l != len) {
161                    if (avail == buff_len)
162                        break;
163                    buff[pos++] = b[off++];
164                    l++;
165                    avail++;
166                    if (pos == buff_len)
167                        pos = 0;
168                }
169                cycling_avail = avail;
170                cycling_write_pos = pos;
171                if (l == len)
172                    return l;
173            }
174            if (avail == buff_len) {
175                try {
176                    Thread.sleep(1);
177                } catch (InterruptedException e) {
178                    return l;
179                }
180                if (!isRunning())
181                    return l;
182            }
183        }
184
185        return l;
186    }
187
188    //
189    // BooleanControl.Type.APPLY_REVERB
190    // BooleanControl.Type.MUTE
191    // EnumControl.Type.REVERB
192    //
193    // FloatControl.Type.SAMPLE_RATE
194    // FloatControl.Type.REVERB_SEND
195    // FloatControl.Type.VOLUME
196    // FloatControl.Type.PAN
197    // FloatControl.Type.MASTER_GAIN
198    // FloatControl.Type.BALANCE
199
200    private boolean _active = false;
201
202    private AudioFormat outputformat;
203
204    private int out_nrofchannels;
205
206    private int in_nrofchannels;
207
208    private float _rightgain;
209
210    private float _leftgain;
211
212    private float _eff1gain;
213
214    private float _eff2gain;
215
216    @Override
217    protected void processControlLogic() {
218        _active = active;
219        _rightgain = rightgain;
220        _leftgain = leftgain;
221        _eff1gain = eff1gain;
222        _eff2gain = eff2gain;
223    }
224
225    @Override
226    protected void processAudioLogic(SoftAudioBuffer[] buffers) {
227        if (_active) {
228            float[] left = buffers[SoftMixingMainMixer.CHANNEL_LEFT].array();
229            float[] right = buffers[SoftMixingMainMixer.CHANNEL_RIGHT].array();
230            int bufferlen = buffers[SoftMixingMainMixer.CHANNEL_LEFT].getSize();
231
232            int readlen = bufferlen * in_nrofchannels;
233            if (readbuffer == null || readbuffer.length < readlen) {
234                readbuffer = new float[readlen];
235            }
236            int ret = 0;
237            try {
238                ret = afis.read(readbuffer);
239                if (ret != in_nrofchannels)
240                    Arrays.fill(readbuffer, ret, readlen, 0);
241            } catch (IOException e) {
242            }
243
244            int in_c = in_nrofchannels;
245            for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
246                left[i] += readbuffer[ix] * _leftgain;
247            }
248            if (out_nrofchannels != 1) {
249                if (in_nrofchannels == 1) {
250                    for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
251                        right[i] += readbuffer[ix] * _rightgain;
252                    }
253                } else {
254                    for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) {
255                        right[i] += readbuffer[ix] * _rightgain;
256                    }
257                }
258
259            }
260
261            if (_eff1gain > 0.0001) {
262                float[] eff1 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT1]
263                        .array();
264                for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
265                    eff1[i] += readbuffer[ix] * _eff1gain;
266                }
267                if (in_nrofchannels == 2) {
268                    for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) {
269                        eff1[i] += readbuffer[ix] * _eff1gain;
270                    }
271                }
272            }
273
274            if (_eff2gain > 0.0001) {
275                float[] eff2 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT2]
276                        .array();
277                for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) {
278                    eff2[i] += readbuffer[ix] * _eff2gain;
279                }
280                if (in_nrofchannels == 2) {
281                    for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) {
282                        eff2[i] += readbuffer[ix] * _eff2gain;
283                    }
284                }
285            }
286
287        }
288    }
289
290    @Override
291    public void open() throws LineUnavailableException {
292        open(format);
293    }
294
295    @Override
296    public void open(AudioFormat format) throws LineUnavailableException {
297        if (bufferSize == -1)
298            bufferSize = ((int) (format.getFrameRate() / 2))
299                    * format.getFrameSize();
300        open(format, bufferSize);
301    }
302
303    @Override
304    public void open(AudioFormat format, int bufferSize)
305            throws LineUnavailableException {
306
307        LineEvent event = null;
308
309        if (bufferSize < format.getFrameSize() * 32)
310            bufferSize = format.getFrameSize() * 32;
311
312        synchronized (control_mutex) {
313
314            if (!isOpen()) {
315                if (!mixer.isOpen()) {
316                    mixer.open();
317                    mixer.implicitOpen = true;
318                }
319
320                event = new LineEvent(this, LineEvent.Type.OPEN, 0);
321
322                this.bufferSize = bufferSize - bufferSize
323                        % format.getFrameSize();
324                this.format = format;
325                this.framesize = format.getFrameSize();
326                this.outputformat = mixer.getFormat();
327                out_nrofchannels = outputformat.getChannels();
328                in_nrofchannels = format.getChannels();
329
330                open = true;
331
332                mixer.getMainMixer().openLine(this);
333
334                cycling_buffer = new byte[framesize * bufferSize];
335                cycling_read_pos = 0;
336                cycling_write_pos = 0;
337                cycling_avail = 0;
338                cycling_framepos = 0;
339
340                InputStream cycling_inputstream = new InputStream() {
341
342                    @Override
343                    public int read() throws IOException {
344                        byte[] b = new byte[1];
345                        int ret = read(b);
346                        if (ret < 0)
347                            return ret;
348                        return b[0] & 0xFF;
349                    }
350
351                    @Override
352                    public int available() throws IOException {
353                        synchronized (cycling_buffer) {
354                            return cycling_avail;
355                        }
356                    }
357
358                    @Override
359                    public int read(byte[] b, int off, int len)
360                            throws IOException {
361
362                        synchronized (cycling_buffer) {
363                            if (len > cycling_avail)
364                                len = cycling_avail;
365                            int pos = cycling_read_pos;
366                            byte[] buff = cycling_buffer;
367                            int buff_len = buff.length;
368                            for (int i = 0; i < len; i++) {
369                                b[off++] = buff[pos];
370                                pos++;
371                                if (pos == buff_len)
372                                    pos = 0;
373                            }
374                            cycling_read_pos = pos;
375                            cycling_avail -= len;
376                            cycling_framepos += len / framesize;
377                        }
378                        return len;
379                    }
380
381                };
382
383                afis = AudioFloatInputStream
384                        .getInputStream(new AudioInputStream(
385                                cycling_inputstream, format,
386                                AudioSystem.NOT_SPECIFIED));
387                afis = new NonBlockingFloatInputStream(afis);
388
389                if (Math.abs(format.getSampleRate()
390                        - outputformat.getSampleRate()) > 0.000001)
391                    afis = new AudioFloatInputStreamResampler(afis,
392                            outputformat);
393
394            } else {
395                if (!format.matches(getFormat())) {
396                    throw new IllegalStateException(
397                            "Line is already open with format " + getFormat()
398                                    + " and bufferSize " + getBufferSize());
399                }
400            }
401
402        }
403
404        if (event != null)
405            sendEvent(event);
406
407    }
408
409    @Override
410    public int available() {
411        synchronized (cycling_buffer) {
412            return cycling_buffer.length - cycling_avail;
413        }
414    }
415
416    @Override
417    public void drain() {
418        while (true) {
419            int avail;
420            synchronized (cycling_buffer) {
421                avail = cycling_avail;
422            }
423            if (avail != 0)
424                return;
425            try {
426                Thread.sleep(1);
427            } catch (InterruptedException e) {
428                return;
429            }
430        }
431    }
432
433    @Override
434    public void flush() {
435        synchronized (cycling_buffer) {
436            cycling_read_pos = 0;
437            cycling_write_pos = 0;
438            cycling_avail = 0;
439        }
440    }
441
442    @Override
443    public int getBufferSize() {
444        synchronized (control_mutex) {
445            return bufferSize;
446        }
447    }
448
449    @Override
450    public AudioFormat getFormat() {
451        synchronized (control_mutex) {
452            return format;
453        }
454    }
455
456    @Override
457    public int getFramePosition() {
458        return (int) getLongFramePosition();
459    }
460
461    @Override
462    public float getLevel() {
463        return AudioSystem.NOT_SPECIFIED;
464    }
465
466    @Override
467    public long getLongFramePosition() {
468        synchronized (cycling_buffer) {
469            return cycling_framepos;
470        }
471    }
472
473    @Override
474    public long getMicrosecondPosition() {
475        return (long) (getLongFramePosition() * (1000000.0 / (double) getFormat()
476                .getSampleRate()));
477    }
478
479    @Override
480    public boolean isActive() {
481        synchronized (control_mutex) {
482            return active;
483        }
484    }
485
486    @Override
487    public boolean isRunning() {
488        synchronized (control_mutex) {
489            return active;
490        }
491    }
492
493    @Override
494    public void start() {
495
496        LineEvent event = null;
497
498        synchronized (control_mutex) {
499            if (isOpen()) {
500                if (active)
501                    return;
502                active = true;
503                event = new LineEvent(this, LineEvent.Type.START,
504                        getLongFramePosition());
505            }
506        }
507
508        if (event != null)
509            sendEvent(event);
510    }
511
512    @Override
513    public void stop() {
514        LineEvent event = null;
515
516        synchronized (control_mutex) {
517            if (isOpen()) {
518                if (!active)
519                    return;
520                active = false;
521                event = new LineEvent(this, LineEvent.Type.STOP,
522                        getLongFramePosition());
523            }
524        }
525
526        if (event != null)
527            sendEvent(event);
528    }
529
530    @Override
531    public void close() {
532
533        LineEvent event = null;
534
535        synchronized (control_mutex) {
536            if (!isOpen())
537                return;
538            stop();
539
540            event = new LineEvent(this, LineEvent.Type.CLOSE,
541                    getLongFramePosition());
542
543            open = false;
544            mixer.getMainMixer().closeLine(this);
545        }
546
547        if (event != null)
548            sendEvent(event);
549
550    }
551
552    @Override
553    public boolean isOpen() {
554        synchronized (control_mutex) {
555            return open;
556        }
557    }
558}
559