|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package com.sun.media.sound; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
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.Clip; |
|
import javax.sound.sampled.DataLine; |
|
import javax.sound.sampled.LineEvent; |
|
import javax.sound.sampled.LineUnavailableException; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class SoftMixingClip extends SoftMixingDataLine implements Clip { |
|
|
|
private AudioFormat format; |
|
|
|
private int framesize; |
|
|
|
private byte[] data; |
|
|
|
private final InputStream datastream = 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 read(byte[] b, int off, int len) throws IOException { |
|
|
|
if (_loopcount != 0) { |
|
int bloopend = _loopend * framesize; |
|
int bloopstart = _loopstart * framesize; |
|
int pos = _frameposition * framesize; |
|
|
|
if (pos + len >= bloopend) |
|
if (pos < bloopend) { |
|
int offend = off + len; |
|
int o = off; |
|
while (off != offend) { |
|
if (pos == bloopend) { |
|
if (_loopcount == 0) |
|
break; |
|
pos = bloopstart; |
|
if (_loopcount != LOOP_CONTINUOUSLY) |
|
_loopcount--; |
|
} |
|
len = offend - off; |
|
int left = bloopend - pos; |
|
if (len > left) |
|
len = left; |
|
System.arraycopy(data, pos, b, off, len); |
|
off += len; |
|
} |
|
if (_loopcount == 0) { |
|
len = offend - off; |
|
int left = bloopend - pos; |
|
if (len > left) |
|
len = left; |
|
System.arraycopy(data, pos, b, off, len); |
|
off += len; |
|
} |
|
_frameposition = pos / framesize; |
|
return o - off; |
|
} |
|
} |
|
|
|
int pos = _frameposition * framesize; |
|
int left = bufferSize - pos; |
|
if (left == 0) |
|
return -1; |
|
if (len > left) |
|
len = left; |
|
System.arraycopy(data, pos, b, off, len); |
|
_frameposition += len / framesize; |
|
return len; |
|
} |
|
|
|
}; |
|
|
|
private int offset; |
|
|
|
private int bufferSize; |
|
|
|
private float[] readbuffer; |
|
|
|
private boolean open = false; |
|
|
|
private AudioFormat outputformat; |
|
|
|
private int out_nrofchannels; |
|
|
|
private int in_nrofchannels; |
|
|
|
private int frameposition = 0; |
|
|
|
private boolean frameposition_sg = false; |
|
|
|
private boolean active_sg = false; |
|
|
|
private int loopstart = 0; |
|
|
|
private int loopend = -1; |
|
|
|
private boolean active = false; |
|
|
|
private int loopcount = 0; |
|
|
|
private boolean _active = false; |
|
|
|
private int _frameposition = 0; |
|
|
|
private boolean loop_sg = false; |
|
|
|
private int _loopcount = 0; |
|
|
|
private int _loopstart = 0; |
|
|
|
private int _loopend = -1; |
|
|
|
private float _rightgain; |
|
|
|
private float _leftgain; |
|
|
|
private float _eff1gain; |
|
|
|
private float _eff2gain; |
|
|
|
private AudioFloatInputStream afis; |
|
|
|
SoftMixingClip(SoftMixingMixer mixer, DataLine.Info info) { |
|
super(mixer, info); |
|
} |
|
|
|
protected void processControlLogic() { |
|
|
|
_rightgain = rightgain; |
|
_leftgain = leftgain; |
|
_eff1gain = eff1gain; |
|
_eff2gain = eff2gain; |
|
|
|
if (active_sg) { |
|
_active = active; |
|
active_sg = false; |
|
} else { |
|
active = _active; |
|
} |
|
|
|
if (frameposition_sg) { |
|
_frameposition = frameposition; |
|
frameposition_sg = false; |
|
afis = null; |
|
} else { |
|
frameposition = _frameposition; |
|
} |
|
if (loop_sg) { |
|
_loopcount = loopcount; |
|
_loopstart = loopstart; |
|
_loopend = loopend; |
|
} |
|
|
|
if (afis == null) { |
|
afis = AudioFloatInputStream.getInputStream(new AudioInputStream( |
|
datastream, format, AudioSystem.NOT_SPECIFIED)); |
|
|
|
if (Math.abs(format.getSampleRate() - outputformat.getSampleRate()) > 0.000001) |
|
afis = new AudioFloatInputStreamResampler(afis, outputformat); |
|
} |
|
|
|
} |
|
|
|
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 == -1) { |
|
_active = false; |
|
return; |
|
} |
|
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.0002) { |
|
|
|
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.0002) { |
|
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 int getFrameLength() { |
|
return bufferSize / format.getFrameSize(); |
|
} |
|
|
|
public long getMicrosecondLength() { |
|
return (long) (getFrameLength() * (1000000.0 / (double) getFormat() |
|
.getSampleRate())); |
|
} |
|
|
|
public void loop(int count) { |
|
LineEvent event = null; |
|
|
|
synchronized (control_mutex) { |
|
if (isOpen()) { |
|
if (active) |
|
return; |
|
active = true; |
|
active_sg = true; |
|
loopcount = count; |
|
event = new LineEvent(this, LineEvent.Type.START, |
|
getLongFramePosition()); |
|
} |
|
} |
|
|
|
if (event != null) |
|
sendEvent(event); |
|
|
|
} |
|
|
|
public void open(AudioInputStream stream) throws LineUnavailableException, |
|
IOException { |
|
if (isOpen()) { |
|
throw new IllegalStateException("Clip is already open with format " |
|
+ getFormat() + " and frame lengh of " + getFrameLength()); |
|
} |
|
if (AudioFloatConverter.getConverter(stream.getFormat()) == null) |
|
throw new IllegalArgumentException("Invalid format : " |
|
+ stream.getFormat().toString()); |
|
|
|
if (stream.getFrameLength() != AudioSystem.NOT_SPECIFIED) { |
|
byte[] data = new byte[(int) stream.getFrameLength() |
|
* stream.getFormat().getFrameSize()]; |
|
int readsize = 512 * stream.getFormat().getFrameSize(); |
|
int len = 0; |
|
while (len != data.length) { |
|
if (readsize > data.length - len) |
|
readsize = data.length - len; |
|
int ret = stream.read(data, len, readsize); |
|
if (ret == -1) |
|
break; |
|
if (ret == 0) |
|
Thread.yield(); |
|
len += ret; |
|
} |
|
open(stream.getFormat(), data, 0, len); |
|
} else { |
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
byte[] b = new byte[512 * stream.getFormat().getFrameSize()]; |
|
int r = 0; |
|
while ((r = stream.read(b)) != -1) { |
|
if (r == 0) |
|
Thread.yield(); |
|
baos.write(b, 0, r); |
|
} |
|
open(stream.getFormat(), baos.toByteArray(), 0, baos.size()); |
|
} |
|
|
|
} |
|
|
|
public void open(AudioFormat format, byte[] data, int offset, int bufferSize) |
|
throws LineUnavailableException { |
|
synchronized (control_mutex) { |
|
if (isOpen()) { |
|
throw new IllegalStateException( |
|
"Clip is already open with format " + getFormat() |
|
+ " and frame lengh of " + getFrameLength()); |
|
} |
|
if (AudioFloatConverter.getConverter(format) == null) |
|
throw new IllegalArgumentException("Invalid format : " |
|
+ format.toString()); |
|
if (bufferSize % format.getFrameSize() != 0) |
|
throw new IllegalArgumentException( |
|
"Buffer size does not represent an integral number of sample frames!"); |
|
|
|
if (data != null) { |
|
this.data = Arrays.copyOf(data, data.length); |
|
} |
|
this.offset = offset; |
|
this.bufferSize = bufferSize; |
|
this.format = format; |
|
this.framesize = format.getFrameSize(); |
|
|
|
loopstart = 0; |
|
loopend = -1; |
|
loop_sg = true; |
|
|
|
if (!mixer.isOpen()) { |
|
mixer.open(); |
|
mixer.implicitOpen = true; |
|
} |
|
|
|
outputformat = mixer.getFormat(); |
|
out_nrofchannels = outputformat.getChannels(); |
|
in_nrofchannels = format.getChannels(); |
|
|
|
open = true; |
|
|
|
mixer.getMainMixer().openLine(this); |
|
} |
|
|
|
} |
|
|
|
public void setFramePosition(int frames) { |
|
synchronized (control_mutex) { |
|
frameposition_sg = true; |
|
frameposition = frames; |
|
} |
|
} |
|
|
|
public void setLoopPoints(int start, int end) { |
|
synchronized (control_mutex) { |
|
if (end != -1) { |
|
if (end < start) |
|
throw new IllegalArgumentException("Invalid loop points : " |
|
+ start + " - " + end); |
|
if (end * framesize > bufferSize) |
|
throw new IllegalArgumentException("Invalid loop points : " |
|
+ start + " - " + end); |
|
} |
|
if (start * framesize > bufferSize) |
|
throw new IllegalArgumentException("Invalid loop points : " |
|
+ start + " - " + end); |
|
if (0 < start) |
|
throw new IllegalArgumentException("Invalid loop points : " |
|
+ start + " - " + end); |
|
loopstart = start; |
|
loopend = end; |
|
loop_sg = true; |
|
} |
|
} |
|
|
|
public void setMicrosecondPosition(long microseconds) { |
|
setFramePosition((int) (microseconds * (((double) getFormat() |
|
.getSampleRate()) / 1000000.0))); |
|
} |
|
|
|
public int available() { |
|
return 0; |
|
} |
|
|
|
public void drain() { |
|
} |
|
|
|
public void flush() { |
|
} |
|
|
|
public int getBufferSize() { |
|
return bufferSize; |
|
} |
|
|
|
public AudioFormat getFormat() { |
|
return format; |
|
} |
|
|
|
public int getFramePosition() { |
|
synchronized (control_mutex) { |
|
return frameposition; |
|
} |
|
} |
|
|
|
public float getLevel() { |
|
return AudioSystem.NOT_SPECIFIED; |
|
} |
|
|
|
public long getLongFramePosition() { |
|
return getFramePosition(); |
|
} |
|
|
|
public long getMicrosecondPosition() { |
|
return (long) (getFramePosition() * (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; |
|
active_sg = true; |
|
loopcount = 0; |
|
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; |
|
active_sg = true; |
|
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() { |
|
return open; |
|
} |
|
|
|
public void open() throws LineUnavailableException { |
|
if (data == null) { |
|
throw new IllegalArgumentException( |
|
"Illegal call to open() in interface Clip"); |
|
} |
|
open(format, data, offset, bufferSize); |
|
} |
|
|
|
} |