/*
* EsdSourceDataLine.java
*
* This file is part of Tritonus: http://www.tritonus.org/
*/
/*
* Copyright (c) 1999, 2000 by Matthias Pfisterer
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
package org.tritonus.sampled.mixer.esd;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import org.tritonus.share.TDebug;
import org.tritonus.lowlevel.esd.Esd;
import org.tritonus.lowlevel.esd.EsdStream;
import org.tritonus.share.sampled.TConversionTool;
import org.tritonus.share.sampled.mixer.TMixer;
import org.tritonus.share.sampled.mixer.TBaseDataLine;
public class EsdSourceDataLine
extends TBaseDataLine
implements SourceDataLine
{
private EsdStream m_esdStream;
private boolean m_bSwapBytes;
private byte[] m_abSwapBuffer;
/*
* Only used if m_bSwapBytes is true.
*/
private int m_nBytesPerSample;
/*
* Used to store the muted state.
*/
private boolean m_bMuted;
/*
* Used to store the gain while the channel is muted.
*/
private float m_fGain;
/*
* Used to store the pan while the channel is muted.
*/
private float m_fPan;
// TODO: has info object to change if format or buffer size are changed later?
// no, but it has to represent the mixer's capabilities. So a fixed info per mixer.
public EsdSourceDataLine(TMixer mixer, AudioFormat format, int nBufferSize)
throws LineUnavailableException
{
super(mixer,
new DataLine.Info(SourceDataLine.class,
format,
nBufferSize));
addControl(new EsdSourceDataLineGainControl());
addControl(new EsdSourceDataLinePanControl());
addControl(new EsdSourceDataLineMuteControl());
/*
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.<init>(): buffer size: " + nBufferSize);
}
*/
}
protected void openImpl()
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.openImpl(): called.");
}
/*
* Checks that a format is set.
* Sets the buffer size to a default value if not
* already set.
*/
checkOpen();
AudioFormat format = getFormat();
AudioFormat.Encoding encoding = format.getEncoding();
boolean bBigEndian = format.isBigEndian();
m_bSwapBytes = false;
if (format.getSampleSizeInBits() == 16 && bBigEndian)
{
m_bSwapBytes = true;
bBigEndian = false;
}
else if (format.getSampleSizeInBits() == 8 &&
encoding.equals(AudioFormat.Encoding.PCM_SIGNED))
{
m_bSwapBytes = true;
encoding = AudioFormat.Encoding.PCM_UNSIGNED;
}
/*
* Ugly hack, should fade as soon as possible.
* IDEA: if swapping IS necessary here, isolate the "detection" of
* big-endian architectures into a seperate class. Perhaps have a
* property with a list of big-endian architecture names, so that
* support can be extended to other architectures without changes
* in the source code.
*/
// TODO: does 8 bit work? (perhaps problem inside esd?)
if (System.getProperty("os.arch").equals("ppc")
&& format.getSampleSizeInBits() == 16)
{
m_bSwapBytes ^= true;
}
if (m_bSwapBytes)
{
format = new AudioFormat(encoding,
format.getSampleRate(),
format.getSampleSizeInBits(),
format.getChannels(),
format.getFrameSize(),
format.getFrameRate(),
bBigEndian);
m_nBytesPerSample = format.getFrameSize() / format.getChannels();
}
int nOutFormat = Esd.ESD_STREAM | Esd.ESD_PLAY | EsdUtils.getEsdFormat(format);
m_esdStream = new EsdStream();
m_esdStream.open(nOutFormat,
(int) format.getSampleRate());
}
public int available()
{
// TODO:
return -1;
}
// TODO: check if should block
public int write(byte[] abData, int nOffset, int nLength)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.write(): called.");
}
if (m_bSwapBytes)
{
if (m_abSwapBuffer == null || m_abSwapBuffer.length < nOffset + nLength)
{
m_abSwapBuffer = new byte[nOffset + nLength];
}
TConversionTool.changeOrderOrSign(
abData, nOffset,
m_abSwapBuffer, nOffset,
nLength, m_nBytesPerSample);
abData = m_abSwapBuffer;
}
if (nLength > 0 && !isActive())
{
start();
}
int nRemaining = nLength;
while (nRemaining > 0 && isOpen())
{
synchronized (this)
{
/*
while ((availableWrite() == 0 || isPaused()) && isOpen())
{
try
{
wait();
}
catch (InterruptedException e)
{
if (TDebug.TraceAllExceptions)
{
TDebug.out(e);
}
}
}
*/
if (!isOpen())
{
return nLength - nRemaining;
}
// TODO: check return
int nWritten = m_esdStream.write(abData, nOffset, nRemaining);
nOffset += nWritten;
nRemaining -= nWritten;
}
}
return nLength;
}
protected void closeImpl()
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.closeImpl(): called.");
}
m_esdStream.close();
}
public void drain()
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.drain(): called.");
}
// TODO:
}
public void flush()
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.flush(): called.");
}
// TODO:
}
/**
* fGain is logarithmic!!
*/
protected void setGain(float fGain)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.setGain(): gain: " + fGain);
}
m_fGain = fGain;
if (! m_bMuted)
{
setGainImpl();
}
}
/**
*/
protected void setPan(float fPan)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.setPan(): pan: " + fPan);
}
m_fPan = fPan;
if (! m_bMuted)
{
setGainImpl();
}
}
/**
*/
protected void setMuted(boolean bMuted)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.setMuted(): muted: " + bMuted);
}
m_bMuted = bMuted;
if (m_bMuted)
{
// m_esdStream.setVolume(0, 0);
}
else
{
setGainImpl();
}
}
/**
*/
private void setGainImpl()
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLine.setGainImpl(): called: ");
}
/*
float fLeftDb = m_fGain + m_fPan * 15.0F;
float fRightDb = m_fGain - m_fPan * 15.0F;
float fLeftLinear = (float) TVolumeUtils.log2lin(fLeftDb);
float fRightLinear = (float) TVolumeUtils.log2lin(fRightDb);
*/
// m_esdStream.setVolume((int) (fLeftLinear * 256),
// (int) (fRightLinear * 256));
}
// IDEA: move inner classes to TBaseDataLine
public class EsdSourceDataLineGainControl
extends FloatControl
{
/*
* These variables should be static. However, Java 1.1
* doesn't allow this. So they aren't.
*/
private /*static*/ final float MAX_GAIN = 24.0F;
private /*static*/ final float MIN_GAIN = -96.0F;
/*package*/ EsdSourceDataLineGainControl()
{
super(FloatControl.Type.MASTER_GAIN, // or VOLUME ?
-96.0F, // MIN_GAIN,
24.0F, // MAX_GAIN,
0.01F, // precision
0, // update period?
0.0F, // initial value
"dB",
"-96.0",
"",
"+24.0");
// m_bMuted = false; // should be included in a compund control?
}
public void setValue(float fGain)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLineGainControl.setValue(): gain: " + fGain);
}
float fOldGain = getValue();
super.setValue(fGain);
if (Math.abs(fOldGain - getValue()) > 1.0E-9)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLineGainControl.setValue(): really changing gain");
}
EsdSourceDataLine.this.setGain(getValue());
}
}
}
// IDEA: move inner classes to TBaseDataLine
public class EsdSourceDataLinePanControl
extends FloatControl
{
/*package*/ EsdSourceDataLinePanControl()
{
super(FloatControl.Type.PAN,
-1.0F, // MIN_GAIN,
1.0F, // MAX_GAIN,
0.01F, // precision
0, // update period?
0.0F, // initial value
"??",
"left",
"center",
"right");
}
public void setValue(float fPan)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLinePanControl.setValue(): pan: " + fPan);
}
float fOldPan = getValue();
super.setValue(fPan);
if (Math.abs(fOldPan - getValue()) > 1.0E-9)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLinePanControl.setValue(): really changing pan");
}
EsdSourceDataLine.this.setPan(getValue());
}
}
}
public class EsdSourceDataLineMuteControl
extends BooleanControl
{
/*package*/ EsdSourceDataLineMuteControl()
{
super(BooleanControl.Type.MUTE,
false,
"muted",
"unmuted");
}
public void setValue(boolean bMuted)
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLineMuteControl.setValue(): muted: " + bMuted);
}
if (bMuted != getValue())
{
if (TDebug.TraceSourceDataLine)
{
TDebug.out("EsdSourceDataLineMuteControl.setValue(): really changing mute status");
}
super.setValue(bMuted);
EsdSourceDataLine.this.setMuted(getValue());
}
}
/*
public boolean getMute()
{
return m_bMuted;
}
public void setMute(boolean bMuted)
{
if (bMuted != getMute())
{
m_bMuted = bMuted;
if (getMute())
{
EsdSourceDataLine.this.setGain(getMinimum());
}
else
{
EsdSourceDataLine.this.setGain(getGain());
}
}
}
*/
}
}
/*** EsdSourceDataLine.java ***/