/* * Copyright (c) 2007 - 2008 by Damien Di Fede <ddf@compartmental.net> * * 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. */ package ddf.minim.javasound; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.Control; import javax.sound.sampled.SourceDataLine; import org.tritonus.share.sampled.AudioUtils; import ddf.minim.AudioMetaData; import ddf.minim.Minim; import ddf.minim.MultiChannelBuffer; import ddf.minim.spi.AudioRecording; // TODO: there is so much here that is the same as JSBaseAudioRecordingStream // should find a way to share that code. // TODO (ddf): really need to talk about why this is deprecated and how to deal with it moving forward. /** @deprecated */ class JSAudioRecording implements AudioRecording, Runnable { private AudioMetaData meta; private byte[] samples; private Thread iothread; // reading stuff private boolean play; private boolean loop; private int numLoops; // loop begin is in milliseconds private int loopBegin; // loop end is in bytes private int loopEnd; private byte[] rawBytes; private int totalBytesRead; // see JSBaseAudioRecordingStream for a discussion of these. private boolean shouldRead; private int bytesWritten; // writing stuff protected AudioFormat format; private SourceDataLine line; private boolean finished; private JSMinim system; JSAudioRecording(JSMinim sys, byte[] samps, SourceDataLine sdl, AudioMetaData mdata) { system = sys; samples = samps; meta = mdata; format = sdl.getFormat(); finished = false; line = sdl; loop = false; play = false; numLoops = 0; loopBegin = 0; loopEnd = (int)AudioUtils.millis2BytesFrameAligned( meta.length(), format ); rawBytes = new byte[sdl.getBufferSize() / 8]; iothread = null; totalBytesRead = 0; bytesWritten = 0; shouldRead = true; } public void run() { while ( !finished ) { if ( play ) { if ( shouldRead ) { // read in a full buffer of bytes from the file if ( loop ) { readBytesLoop(); } else { readBytes(); } } // write to the line until all bytes are written writeBytes(); // take a nap Thread.yield(); } else { // we'll be interrupted if we should start playing again. sleep( 30000 ); } } // while ( !finished ) // flush the line before we close it. because that is polite. line.flush(); line.close(); line = null; } private void sleep(int millis) { try { Thread.sleep( millis ); } catch ( InterruptedException e ) { } } private synchronized void readBytes() { int samplesLeft = samples.length - totalBytesRead; if ( samplesLeft < rawBytes.length ) { readBytes( samplesLeft, 0 ); system.debug( "readBytes: filling rawBytes from " + samplesLeft + " to " + rawBytes.length + " with silence." ); byte silent = 0; // unsigned source means we need to make the silence the neutral // value, // which is exactly half as large as a byte can be. if ( format.getEncoding() == AudioFormat.Encoding.PCM_UNSIGNED ) { silent = (byte)0x80; } for ( int i = samplesLeft; i < rawBytes.length; i++ ) { rawBytes[i] = silent; } play = false; } else { readBytes( rawBytes.length, 0 ); } } private synchronized void readBytesLoop() { int toLoopEnd = loopEnd - totalBytesRead; if ( toLoopEnd <= 0 ) { // whoops, our loop end point got switched up setMillisecondPosition( loopBegin ); readBytesLoop(); return; } if ( toLoopEnd < rawBytes.length ) { readBytes( toLoopEnd, 0 ); if ( loop && numLoops == 0 ) { loop = false; play = false; } else if ( loop ) { setMillisecondPosition( loopBegin ); readBytes( rawBytes.length - toLoopEnd, toLoopEnd ); if ( numLoops != Minim.LOOP_CONTINUOUSLY ) { numLoops--; } } } else { readBytes( rawBytes.length, 0 ); } } // copy toRead bytes from samples to rawBytes, // starting at offet into rawBytes private void readBytes(int toRead, int offset) { System.arraycopy( samples, totalBytesRead, rawBytes, offset, toRead ); totalBytesRead += toRead; } private void writeBytes() { // the write call will block until the requested amount of bytes // is written, however the user might stop the line in the // middle of writing and then we get told how much was actually written. // because of that, we might not need to write the entire array when we // get here. int needToWrite = rawBytes.length - bytesWritten; int actualWrit = line.write( rawBytes, bytesWritten, needToWrite ); // if the total written is not equal to how much we needed to write // then we need to remember where we were so that we don't read more // until we finished writing our entire rawBytes array. if ( actualWrit != needToWrite ) { shouldRead = false; bytesWritten += actualWrit; } else { // if it all got written, we should continue reading // and we reset our bytesWritten value. shouldRead = true; bytesWritten = 0; } } public void play() { line.start(); loop = false; numLoops = 0; play = true; iothread.interrupt(); } public boolean isPlaying() { return play; } public void pause() { line.stop(); play = false; } public void loop(int n) { loop = true; numLoops = n; play = true; setMillisecondPosition( loopBegin ); line.start(); iothread.interrupt(); } public void open() { iothread = new Thread( this ); finished = false; iothread.start(); } public void close() { line.stop(); finished = true; try { iothread.join( 10 ); } catch ( InterruptedException e ) { e.printStackTrace(); } iothread = null; } public AudioFormat getFormat() { return format; } public int getLoopCount() { return numLoops; } public synchronized void setLoopPoints(int start, int stop) { if ( start <= 0 || start > stop ) { loopBegin = 0; } else { loopBegin = start; } if ( stop <= getMillisecondLength() && stop > start ) { loopEnd = (int)AudioUtils.millis2BytesFrameAligned( stop, format ); } else { loopEnd = (int)AudioUtils.millis2BytesFrameAligned( getMillisecondLength(), format ); } } public int getMillisecondPosition() { return (int)AudioUtils.bytes2Millis( totalBytesRead, format ); } public synchronized void setMillisecondPosition(int millis) { if ( millis <= 0 ) { totalBytesRead = 0; } else if ( millis > getMillisecondLength() ) { totalBytesRead = samples.length; } else { totalBytesRead = (int)AudioUtils.millis2BytesFrameAligned( millis, format ); } } public Control[] getControls() { return line.getControls(); } public AudioMetaData getMetaData() { return meta; } public int getMillisecondLength() { return meta.length(); } public int bufferSize() { return 0; } public float[] read() { return null; } public int read(MultiChannelBuffer buffer) { return 0; } }