samer@1: /* samer@1: * FileSource.java samer@1: * samer@1: * Copyright (c) 2000, Samer Abdallah, King's College London. samer@1: * All rights reserved. samer@1: * samer@1: * This software is provided AS iS and WITHOUT ANY WARRANTY; samer@1: * without even the implied warranty of MERCHANTABILITY or samer@1: * FITNESS FOR A PARTICULAR PURPOSE. samer@1: */ samer@1: samer@1: package samer.audio; samer@1: samer@1: import samer.core.*; samer@1: import samer.core.types.*; samer@1: import samer.core.util.*; samer@1: import samer.tools.*; samer@1: import javax.sound.sampled.*; samer@1: import javax.swing.*; samer@1: import java.io.*; samer@1: import java.util.*; samer@1: import java.nio.ByteBuffer; samer@1: samer@1: /** samer@1: */ samer@1: samer@1: samer@1: public abstract class MultiFileAudioStream extends InputStream samer@1: { samer@1: List list=null; samer@1: ListIterator it=null; samer@1: int channelsToMix; samer@1: File curFile=null; samer@1: InputStream in=null; samer@1: AudioFormat format=null, inFormat=null; samer@1: boolean loop=false; samer@1: samer@1: /** samer@1: * Construct a FileSource initialised with current file and loop initialised samer@1: * from the current Environment. No exception is thrown if the current file samer@1: * cannot be opened, however, you must be sure to set it to a valid file samer@1: * (or to set a playlist) before trying to read any samples. samer@1: */ samer@1: public MultiFileAudioStream(AudioFormat target, List files) samer@1: { samer@1: list=files; samer@1: format=target; samer@1: } samer@1: samer@1: public AudioFormat getFormat() { return format; } samer@1: samer@1: /** Set playlist to all WAV files in given directory */ samer@1: public List directory(File dir, String ext) { samer@1: return Arrays.asList(dir.listFiles(getFileFilter(ext))); samer@1: } samer@1: samer@1: private static java.io.FileFilter getFileFilter(final String ext) { samer@1: return new java.io.FileFilter() { samer@1: public boolean accept(File file) { samer@1: return file.getName().toLowerCase().endsWith(ext); samer@1: } samer@1: }; samer@1: } samer@1: samer@1: public synchronized List getPlaylist() { return list; } samer@1: samer@1: /** Go back to start of playlist. Next block of samples will be from head samer@1: * of first file in playlist. Will throw an exception if there is no playlist */ samer@1: public synchronized void rewind() throws IOException { samer@1: it=list.listIterator(); next(); samer@1: } samer@1: samer@1: /** Move to head of next file in playlist */ samer@1: public synchronized void next() throws IOException samer@1: { samer@1: boolean wasOpen=isOpen(); samer@1: samer@1: if (wasOpen) close(); samer@1: if (!it.hasNext()) { samer@1: if (!loop) throw new EOFException(); samer@1: it=list.listIterator(); samer@1: if (!it.hasNext()) throw new IOException("no files in playlist"); samer@1: } samer@1: curFile=it.next(); samer@1: if (wasOpen) openCurrent(); samer@1: } samer@1: samer@1: /** Move to head of previous file in playlist */ samer@1: public synchronized void prev() throws Exception samer@1: { samer@1: boolean wasOpen=isOpen(); samer@1: if (wasOpen) close(); samer@1: if (!it.hasPrevious()) { samer@1: if (!loop) throw new EOFException(); samer@1: it=list.listIterator(list.size()); samer@1: if (!it.hasPrevious()) throw new Exception("no files in playlist"); samer@1: } samer@1: curFile=it.previous(); samer@1: if (wasOpen) openCurrent(); samer@1: } samer@1: samer@1: public boolean isOpen() { return in!=null; } samer@1: samer@1: /** Closes current input stream */ samer@1: public synchronized void close() { samer@1: try { samer@1: if (in!=null) { samer@1: Shell.trace("Closing audio stream..."); samer@1: in.close(); in=null; samer@1: } samer@1: } samer@1: catch (IOException ex) {} samer@1: } samer@1: samer@1: /** Opens the playlist starting with the first file. */ samer@1: public synchronized void open() throws Exception { rewind(); if (!isOpen()) openCurrent(); } samer@1: samer@1: /** Opens the current file. Next read will returns samples samer@1: * from head of given file. If setFormat() was called previously, then samer@1: * we will attempt to do format conversion. */ samer@1: private synchronized void openCurrent() throws IOException samer@1: { samer@1: try { samer@1: File file=curFile; samer@1: Shell.trace("\nFileSource:Opening audio file "+file); samer@1: samer@1: AudioInputStream s=AudioSystem.getAudioInputStream(file); samer@1: AudioFormat fmt1, af=s.getFormat(); samer@1: samer@1: Shell.trace(" format: "+af); samer@1: samer@1: // convert to target format if required samer@1: if (format!=null) { samer@1: if (!format.equals(af)) { samer@1: Shell.trace(" converting to "+format); samer@1: if (af.getChannels()>format.getChannels()) { samer@1: int frameSize = af.getChannels()*format.getSampleSizeInBits()/8; samer@1: Shell.trace(" channels mix down required."); samer@1: fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(), samer@1: format.getSampleSizeInBits(), samer@1: af.getChannels(), frameSize, format.getFrameRate(), format.isBigEndian()); samer@1: samer@1: channelsToMix = af.getChannels(); samer@1: } else { samer@1: channelsToMix = 0; samer@1: fmt1=format; samer@1: } samer@1: Shell.trace(" converting via "+fmt1); samer@1: s=convertFormat(s,af,fmt1); samer@1: inFormat = fmt1; samer@1: } else { samer@1: Shell.trace(" no formation conversion required"); samer@1: channelsToMix = 0; samer@1: inFormat = af; samer@1: } samer@1: } else { samer@1: Shell.trace(" using stream native format"); samer@1: channelsToMix = 0; samer@1: inFormat = af; samer@1: } samer@1: in=s; samer@1: } samer@1: catch (Exception ex) { throw new IOException("Failed to open audio file"); } samer@1: } samer@1: samer@1: /** Returns number of bytes available in current file */ samer@1: public int available() { samer@1: try { samer@1: return in.available(); samer@1: } samer@1: catch (Exception ex) { return 0; } samer@1: } samer@1: samer@1: /** Returns a Task which copies samples as doubles into the given samer@1: * buffer between the given positions. */ samer@1: public int read(byte [] buf, int off, int len) throws IOException { samer@1: samer@1: // loop until len samples copied into dbuf samer@1: int rem=len, pos=off; samer@1: while (rem>0) { samer@1: int chunk = in.read(buf, pos, rem); samer@1: if (chunk > 0) { // append this chunk to output samer@1: pos+=chunk; rem-=chunk; samer@1: } else if (it!=null) next(); // next file if there is one samer@1: else if (!loop) throw new EOFException(); // not looping and no more files samer@1: else rewind(); // back to first file samer@1: } samer@1: return len; samer@1: } samer@1: samer@1: samer@1: private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception samer@1: { samer@1: Shell.trace("\nconvertFormat:"); samer@1: Shell.trace(" | source: "+fin.toString()); samer@1: Shell.trace(" | target: "+fout.toString()); samer@1: samer@1: if (fin.equals(fout)) return sin; samer@1: else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) { samer@1: try { samer@1: Shell.trace(" | Trying direct (PCM) from "+fin.getEncoding().toString()); samer@1: return AudioSystem.getAudioInputStream(fout,sin); samer@1: } samer@1: catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); } samer@1: samer@1: AudioFormat fint = new AudioFormat( // PCM samer@1: fout.getSampleRate(), fout.getSampleSizeInBits(), samer@1: fin.getChannels(), true, fout.isBigEndian()); samer@1: Shell.trace(" | Trying PCM conversion via "+fint.toString()); samer@1: return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin)); samer@1: } else { samer@1: // First, check for MP3 - if so, cannot convert number of channels samer@1: if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) { samer@1: Shell.trace(" | Trying decoding from "+fin.getEncoding().toString()); samer@1: return AudioSystem.getAudioInputStream(fout,sin); samer@1: } else { samer@1: AudioFormat fint = new AudioFormat( samer@1: fin.getSampleRate(), fout.getSampleSizeInBits(), samer@1: fin.getChannels(), true, fout.isBigEndian()); samer@1: Shell.trace(" | Trying recursive via "+fint.toString()); samer@1: return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout); samer@1: } samer@1: } samer@1: } samer@1: } samer@1: