/*
 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.media.sound;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

SourceDataLine implemention for the SoftMixingMixer.
Author:Karl Helgason
/** * SourceDataLine implemention for the SoftMixingMixer. * * @author Karl Helgason */
public final class SoftMixingSourceDataLine extends SoftMixingDataLine implements SourceDataLine { private boolean open = false; private AudioFormat format = new AudioFormat(44100.0f, 16, 2, true, false); private int framesize; private int bufferSize = -1; private float[] readbuffer; private boolean active = false; private byte[] cycling_buffer; private int cycling_read_pos = 0; private int cycling_write_pos = 0; private int cycling_avail = 0; private long cycling_framepos = 0; private AudioFloatInputStream afis; private static class NonBlockingFloatInputStream extends AudioFloatInputStream { AudioFloatInputStream ais; NonBlockingFloatInputStream(AudioFloatInputStream ais) { this.ais = ais; } public int available() throws IOException { return ais.available(); } public void close() throws IOException { ais.close(); } public AudioFormat getFormat() { return ais.getFormat(); } public long getFrameLength() { return ais.getFrameLength(); } public void mark(int readlimit) { ais.mark(readlimit); } public boolean markSupported() { return ais.markSupported(); } public int read(float[] b, int off, int len) throws IOException { int avail = available(); if (len > avail) { int ret = ais.read(b, off, avail); Arrays.fill(b, off + ret, off + len, 0); return len; } return ais.read(b, off, len); } public void reset() throws IOException { ais.reset(); } public long skip(long len) throws IOException { return ais.skip(len); } } SoftMixingSourceDataLine(SoftMixingMixer mixer, DataLine.Info info) { super(mixer, info); } public int write(byte[] b, int off, int len) { if (!isOpen()) return 0; if (len % framesize != 0) throw new IllegalArgumentException( "Number of bytes does not represent an integral number of sample frames."); if (off < 0) { throw new ArrayIndexOutOfBoundsException(off); } if ((long)off + (long)len > (long)b.length) { throw new ArrayIndexOutOfBoundsException(b.length); } byte[] buff = cycling_buffer; int buff_len = cycling_buffer.length; int l = 0; while (l != len) { int avail; synchronized (cycling_buffer) { int pos = cycling_write_pos; avail = cycling_avail; while (l != len) { if (avail == buff_len) break; buff[pos++] = b[off++]; l++; avail++; if (pos == buff_len) pos = 0; } cycling_avail = avail; cycling_write_pos = pos; if (l == len) return l; } if (avail == buff_len) { try { Thread.sleep(1); } catch (InterruptedException e) { return l; } if (!isRunning()) return l; } } return l; } // // BooleanControl.Type.APPLY_REVERB // BooleanControl.Type.MUTE // EnumControl.Type.REVERB // // FloatControl.Type.SAMPLE_RATE // FloatControl.Type.REVERB_SEND // FloatControl.Type.VOLUME // FloatControl.Type.PAN // FloatControl.Type.MASTER_GAIN // FloatControl.Type.BALANCE private boolean _active = false; private AudioFormat outputformat; private int out_nrofchannels; private int in_nrofchannels; private float _rightgain; private float _leftgain; private float _eff1gain; private float _eff2gain; protected void processControlLogic() { _active = active; _rightgain = rightgain; _leftgain = leftgain; _eff1gain = eff1gain; _eff2gain = eff2gain; } protected void processAudioLogic(SoftAudioBuffer[] buffers) { if (_active) { float[] left = buffers[SoftMixingMainMixer.CHANNEL_LEFT].array(); float[] right = buffers[SoftMixingMainMixer.CHANNEL_RIGHT].array(); int bufferlen = buffers[SoftMixingMainMixer.CHANNEL_LEFT].getSize(); int readlen = bufferlen * in_nrofchannels; if (readbuffer == null || readbuffer.length < readlen) { readbuffer = new float[readlen]; } int ret = 0; try { ret = afis.read(readbuffer); if (ret != in_nrofchannels) Arrays.fill(readbuffer, ret, readlen, 0); } catch (IOException e) { } int in_c = in_nrofchannels; for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { left[i] += readbuffer[ix] * _leftgain; } if (out_nrofchannels != 1) { if (in_nrofchannels == 1) { for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { right[i] += readbuffer[ix] * _rightgain; } } else { for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { right[i] += readbuffer[ix] * _rightgain; } } } if (_eff1gain > 0.0001) { float[] eff1 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT1] .array(); for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { eff1[i] += readbuffer[ix] * _eff1gain; } if (in_nrofchannels == 2) { for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { eff1[i] += readbuffer[ix] * _eff1gain; } } } if (_eff2gain > 0.0001) { float[] eff2 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT2] .array(); for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { eff2[i] += readbuffer[ix] * _eff2gain; } if (in_nrofchannels == 2) { for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { eff2[i] += readbuffer[ix] * _eff2gain; } } } } } public void open() throws LineUnavailableException { open(format); } public void open(AudioFormat format) throws LineUnavailableException { if (bufferSize == -1) bufferSize = ((int) (format.getFrameRate() / 2)) * format.getFrameSize(); open(format, bufferSize); } public void open(AudioFormat format, int bufferSize) throws LineUnavailableException { LineEvent event = null; if (bufferSize < format.getFrameSize() * 32) bufferSize = format.getFrameSize() * 32; synchronized (control_mutex) { if (!isOpen()) { if (!mixer.isOpen()) { mixer.open(); mixer.implicitOpen = true; } event = new LineEvent(this, LineEvent.Type.OPEN, 0); this.bufferSize = bufferSize - bufferSize % format.getFrameSize(); this.format = format; this.framesize = format.getFrameSize(); this.outputformat = mixer.getFormat(); out_nrofchannels = outputformat.getChannels(); in_nrofchannels = format.getChannels(); open = true; mixer.getMainMixer().openLine(this); cycling_buffer = new byte[framesize * bufferSize]; cycling_read_pos = 0; cycling_write_pos = 0; cycling_avail = 0; cycling_framepos = 0; InputStream cycling_inputstream = new InputStream() { public int read() throws IOException { byte[] b = new byte[1]; int ret = read(b); if (ret < 0) return ret; return b[0] & 0xFF; } public int available() throws IOException { synchronized (cycling_buffer) { return cycling_avail; } } public int read(byte[] b, int off, int len) throws IOException { synchronized (cycling_buffer) { if (len > cycling_avail) len = cycling_avail; int pos = cycling_read_pos; byte[] buff = cycling_buffer; int buff_len = buff.length; for (int i = 0; i < len; i++) { b[off++] = buff[pos]; pos++; if (pos == buff_len) pos = 0; } cycling_read_pos = pos; cycling_avail -= len; cycling_framepos += len / framesize; } return len; } }; afis = AudioFloatInputStream .getInputStream(new AudioInputStream( cycling_inputstream, format, AudioSystem.NOT_SPECIFIED)); afis = new NonBlockingFloatInputStream(afis); if (Math.abs(format.getSampleRate() - outputformat.getSampleRate()) > 0.000001) afis = new AudioFloatInputStreamResampler(afis, outputformat); } else { if (!format.matches(getFormat())) { throw new IllegalStateException( "Line is already open with format " + getFormat() + " and bufferSize " + getBufferSize()); } } } if (event != null) sendEvent(event); } public int available() { synchronized (cycling_buffer) { return cycling_buffer.length - cycling_avail; } } public void drain() { while (true) { int avail; synchronized (cycling_buffer) { avail = cycling_avail; } if (avail != 0) return; try { Thread.sleep(1); } catch (InterruptedException e) { return; } } } public void flush() { synchronized (cycling_buffer) { cycling_read_pos = 0; cycling_write_pos = 0; cycling_avail = 0; } } public int getBufferSize() { synchronized (control_mutex) { return bufferSize; } } public AudioFormat getFormat() { synchronized (control_mutex) { return format; } } public int getFramePosition() { return (int) getLongFramePosition(); } public float getLevel() { return AudioSystem.NOT_SPECIFIED; } public long getLongFramePosition() { synchronized (cycling_buffer) { return cycling_framepos; } } public long getMicrosecondPosition() { return (long) (getLongFramePosition() * (1000000.0 / (double) getFormat() .getSampleRate())); } public boolean isActive() { synchronized (control_mutex) { return active; } } public boolean isRunning() { synchronized (control_mutex) { return active; } } public void start() { LineEvent event = null; synchronized (control_mutex) { if (isOpen()) { if (active) return; active = true; event = new LineEvent(this, LineEvent.Type.START, getLongFramePosition()); } } if (event != null) sendEvent(event); } public void stop() { LineEvent event = null; synchronized (control_mutex) { if (isOpen()) { if (!active) return; active = false; event = new LineEvent(this, LineEvent.Type.STOP, getLongFramePosition()); } } if (event != null) sendEvent(event); } public void close() { LineEvent event = null; synchronized (control_mutex) { if (!isOpen()) return; stop(); event = new LineEvent(this, LineEvent.Type.CLOSE, getLongFramePosition()); open = false; mixer.getMainMixer().closeLine(this); } if (event != null) sendEvent(event); } public boolean isOpen() { synchronized (control_mutex) { return open; } } }