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