/* * 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 ***/