samer@1: /* samer@1: * FileSource2.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: An AudioSource that read from multiple audio files. Can read any samer@1: format for which the appropriate JavaSound plug-in is installed on samer@1: your system, eg WAV, AU, MP3, OGG. The implementation of samer@1: AudioSource.reader() returns a Task which gets samples from the audio samer@1: files, going through the play list one by one. samer@1: An EOFException is thrown if an attempt is made samer@1: to read beyond the end of the last file. samer@1: samer@1: FileSource2 is a Viewable called "playlist", and an Agent with commands samer@1: next, view. samer@1: samer@1: */ samer@1: samer@1: public class FileSource2 extends Viewable implements AudioSource, Agent 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 targetFormat=null; samer@1: byte[] byte_buf=null; samer@1: int chunk=0, bytesPerSample=0; samer@1: samer@1: /** samer@1: * Construct a FileSource2. samer@1: */ samer@1: public FileSource2(AudioFormat targetFormat, List files) samer@1: { samer@1: super("playlist"); samer@1: samer@1: setAgent(this); samer@1: Shell.registerViewable(this); samer@1: this.targetFormat=targetFormat; samer@1: this.list=files; samer@1: it=list.listIterator(); samer@1: } samer@1: samer@1: // AudioSource interface methods samer@1: public void dispose() { samer@1: close(); samer@1: Shell.deregisterViewable(this); samer@1: } samer@1: samer@1: public int getChannels() { return targetFormat.getChannels(); } samer@1: public float getRate() { return targetFormat.getFrameRate(); } samer@1: samer@1: /** Returns current file */ samer@1: public File getFile() { return curFile; } samer@1: samer@1: /** The requested audio format. **/ samer@1: public AudioFormat getTargetFormat() { return targetFormat; } samer@1: samer@1: /** Set playlist to all WAV files in given directory */ samer@1: public static List directory(File dir, String ext) { samer@1: return Arrays.asList(dir.listFiles(getFileFilter(ext))); samer@1: } samer@1: samer@1: public synchronized List getPlaylist() { return list; } samer@1: samer@1: /** Move to head of next file in playlist */ samer@1: public synchronized void next() throws Exception samer@1: { samer@1: boolean wasOpen=isOpen(); samer@1: samer@1: if (wasOpen) close(); samer@1: if (!it.hasNext()) throw new EOFException(); samer@1: curFile=it.next(); 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; byte_buf=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 { samer@1: it=list.listIterator(); next(); samer@1: if (!isOpen()) openCurrent(); samer@1: } 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 Exception samer@1: { samer@1: File file=curFile; samer@1: Shell.trace("\nFileSource:Opening audio file "+file); samer@1: samer@1: AudioInputStream ain=AudioSystem.getAudioInputStream(file); samer@1: samer@1: // convert to target format if required samer@1: if (targetFormat==null) { samer@1: AudioFormat fin=ain.getFormat(); samer@1: ain=convertFormat(new AudioFormat( fin.getSampleRate(), 16, fin.getChannels(), true, false), ain); samer@1: } else { samer@1: ain=convertFormat(targetFormat, ain); samer@1: } samer@1: bytesPerSample=ain.getFormat().getSampleSizeInBits()/8; samer@1: if (bytesPerSample!=2) throw new Exception("Must be 16 bits per sample"); samer@1: samer@1: in = new BufferedInputStream(ain,64*1024); samer@1: samer@1: // If we have a reader task, then update the byte buffer samer@1: if (chunk>0) setByteBuffer(); samer@1: } samer@1: samer@1: /** Returns number of bytes available in current file */ samer@1: public int available() throws Exception { return in.available(); } samer@1: samer@1: private void setBlockSize(int s) { chunk=s; } samer@1: private void setByteBuffer() { samer@1: byte_buf = new byte[chunk*bytesPerSample]; samer@1: } samer@1: samer@1: public int readInto(ByteBuffer buf, int offset, int len) throws IOException { samer@1: return in.read(buf.array(), offset, len); 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 Task reader(final double [] dbuf, final int off, final int len) { samer@1: setBlockSize(len); samer@1: if (in!=null) setByteBuffer(); samer@1: samer@1: return new AnonymousTask() { samer@1: public void run() throws Exception { samer@1: synchronized (FileSource2.this) { samer@1: // loop until len samples copied into dbuf samer@1: int rem=len, pos=off; samer@1: while (rem>0) { samer@1: int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample); samer@1: if (bytesRead > 0) { // append this chunk to output samer@1: int count=bytesRead/bytesPerSample; samer@1: Util.shortToDouble(byte_buf,dbuf,pos,count); samer@1: pos+=count; rem-=count; samer@1: } else if (it!=null) next(); // next file if there is one samer@1: else throw new EOFException(); // not looping and no more files samer@1: } samer@1: } samer@1: } samer@1: }; samer@1: } samer@1: samer@1: /** Returns a Task which copies samples as floats into the given samer@1: * buffer between the given positions. */ samer@1: public Task reader(final float [] dbuf, final int off, final int len) { samer@1: setBlockSize(len); samer@1: if (in!=null) setByteBuffer(); samer@1: samer@1: return new AnonymousTask() { samer@1: public synchronized void run() throws Exception { samer@1: synchronized (FileSource2.this) { samer@1: // loop until len samples copied into dbuf samer@1: int rem=len, pos=off; samer@1: while (rem>0) { samer@1: int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample); samer@1: if (bytesRead > 0) { samer@1: int count=bytesRead/bytesPerSample; samer@1: Util.shortToFloat(byte_buf,dbuf,pos,count); samer@1: pos+=count; rem-=count; samer@1: } else if (it!=null) next(); samer@1: else throw new EOFException(); samer@1: } samer@1: } samer@1: } samer@1: }; samer@1: } samer@1: samer@1: // Agent samer@1: public void getCommands(Agent.Registry r) { samer@1: r.add("next").add("view"); samer@1: } samer@1: samer@1: public void execute(String cmd, Environment env) throws Exception { samer@1: if (cmd.equals("next")) next(); samer@1: else if (cmd.equals("view")) { samer@1: if (list!=null) { samer@1: Shell.expose( samer@1: new JScrollPane(new JList(list.toArray())), samer@1: "playlist"); samer@1: } samer@1: } samer@1: } samer@1: samer@1: // Viewable samer@1: public Viewer getViewer() { return new FileSourceViewer(); } samer@1: class FileSourceViewer extends DefaultViewer implements Agent { samer@1: public FileSourceViewer() { samer@1: super(FileSource2.this); samer@1: //add(curFile); samer@1: add(Shell.createButtonsFor(this)); samer@1: } samer@1: samer@1: public void getCommands(Agent.Registry r) { samer@1: r.add("next").add("view"); samer@1: } samer@1: public void execute(String cmd, Environment env) throws Exception { samer@1: FileSource2.this.execute(cmd,env); samer@1: } 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: private static AudioInputStream convertVia(AudioFormat fout, AudioInputStream sin, AudioFormat fint) throws Exception samer@1: { samer@1: Shell.trace(" | Trying recursive via "+fint.toString()); samer@1: AudioInputStream sint=AudioSystem.getAudioInputStream(fint,sin); samer@1: Shell.trace(" | Obtained "+sint.getFormat().toString()); samer@1: return convertFormat(fout, sint); samer@1: } samer@1: samer@1: private static AudioInputStream convertFormat(AudioFormat fout, AudioInputStream sin) throws Exception samer@1: { samer@1: AudioFormat fin=sin.getFormat(); samer@1: samer@1: if (fin.equals(fout)) return sin; 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.getEncoding()!=AudioFormat.Encoding.PCM_SIGNED) { samer@1: // first get into PCM encoding, then try recursive samer@1: try { samer@1: return convertVia( fout, sin, new AudioFormat( samer@1: fin.getSampleRate(), fout.getSampleSizeInBits(), samer@1: fin.getChannels(), true, fout.isBigEndian())); samer@1: } catch (IllegalArgumentException ex) { samer@1: Shell.trace("Direct conversion failed"); samer@1: } samer@1: return convertVia( fout, sin, new AudioFormat( samer@1: fin.getSampleRate(), fin.getSampleSizeInBits(), samer@1: fin.getChannels(), true, fout.isBigEndian())); samer@1: } samer@1: samer@1: if (fin.getChannels()!=fout.getChannels() || fin.getSampleSizeInBits()!=fout.getSampleSizeInBits()) { samer@1: // convert these before doing any sample rate conversion samer@1: return convertVia(fout, sin, new AudioFormat( samer@1: fin.getSampleRate(), fout.getSampleSizeInBits(), samer@1: fout.getChannels(), true, fout.isBigEndian())); samer@1: } samer@1: samer@1: // the only thing left is sample rate samer@1: return AudioSystem.getAudioInputStream(fout,sin); samer@1: } samer@1: } samer@1: