Mercurial > hg > jslab
view src/samer/audio/FileSource.java @ 1:5df24c91468d
Oh my what a mess.
author | samer |
---|---|
date | Fri, 05 Apr 2019 16:26:00 +0100 |
parents | bf79fb79ee13 |
children |
line wrap: on
line source
/* * FileSource.java * * Copyright (c) 2000, Samer Abdallah, King's College London. * All rights reserved. * * This software is provided AS iS and WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ package samer.audio; import samer.core.*; import samer.core.types.*; import samer.core.util.*; import samer.tools.*; import javax.sound.sampled.*; import javax.swing.*; import java.io.*; import java.util.*; import java.nio.ByteBuffer; /** An AudioSource that read from multiple audio files. Can read any format for which the appropriate JavaSound plug-in is installed on your system, eg WAV, AU, MP3, OGG. The implementation of AudioSource.reader() returns a Task which gets samples from the audio files, going through the play list one by one. If the loop flag is true, then samples are returned indefinitely by looping through the playlist; otherwise, an EOFException is thrown if an attempt is made to read beyond the end of the last file. FileSource is a Viewable called "playlist", and an Agent with commands next, prev, reopen, view, select, loop. Properties read from current environment: <dl> <dt>current<dd>Current file (String) <dt>loop<dd>Loop playlist? (Boolean) [true] </dl> */ public class FileSource extends Viewable implements AudioSource, Agent { List<File> list=null; ListIterator<File> it=null; boolean loop, buffered=true; int channelsToMix; VFile curFile; InputStream in=null; AudioFormat format=null, inFormat=null; byte[] byte_buf=null; int chunk=0, bytesPerSample=0; /** * Construct a FileSource initialised with current file and loop initialised * from the current Environment. No exception is thrown if the current file * cannot be opened, however, you must be sure to set it to a valid file * (or to set a playlist) before trying to read any samples. */ public FileSource() { super("playlist"); Shell.push(node); curFile=new VFile("current","",0); loop=Shell.getBoolean("loop",true); Shell.pop(); setAgent(this); Shell.registerViewable(this); } // AudioSource interface methods public void dispose() { close(); Shell.deregisterViewable(this); curFile.dispose(); } public int getChannels() { return format.getChannels(); } public float getRate() { return format.getFrameRate(); } /** Returns current file */ public File getFile() { return curFile.getFile(); } public void setBuffering(boolean b) { buffered=b; } /** The actual format of the stream in. May not be same as target format. **/ public AudioFormat getStreamFormat() { return inFormat; } /** The requested audio format. **/ public AudioFormat getTargetFormat() { return format; } public void setTargetFormat(AudioFormat f) { format=f; } /** If true then loop playlist, otherwise, an Exception will be thrown * when the end of the playlist is reached. If there is no playlist, then * looping applies to the current file only. */ public void setLoop(boolean f) { loop=f; } /** Set playlist to all WAV files in given directory */ public void setDirectory(File dir, String ext) { setPlaylist(Arrays.asList(dir.listFiles(getFileFilter(ext)))); } public synchronized List<File> getPlaylist() { return list; } public synchronized void setPlaylist(List<File> l) { list=l; it=list.listIterator(); } private synchronized void setFile(File f) { curFile.setFile(f); } /** Go back to start of playlist. Next block of samples will be from head * of first file in playlist. Will throw an exception if there is no playlist */ public synchronized void rewind() throws Exception { it=list.listIterator(); next(); } /** Move to head of next file in playlist */ public synchronized void next() throws Exception { boolean wasOpen=isOpen(); if (wasOpen) close(); if (!it.hasNext()) { if (!loop) throw new EOFException(); it=list.listIterator(); if (!it.hasNext()) throw new Exception("no files in playlist"); } setFile(it.next()); if (wasOpen) openCurrent(); } /** Move to head of previous file in playlist */ public synchronized void prev() throws Exception { boolean wasOpen=isOpen(); if (wasOpen) close(); if (!it.hasPrevious()) { if (!loop) throw new EOFException(); it=list.listIterator(list.size()); if (!it.hasPrevious()) throw new Exception("no files in playlist"); } setFile(it.previous()); if (wasOpen) openCurrent(); } public boolean isOpen() { return in!=null; } /** Closes current input stream */ public synchronized void close() { try { if (in!=null) { Shell.trace("Closing audio stream..."); in.close(); in=null; byte_buf=null; } } catch (IOException ex) {} } /** Opens the playlist starting with the first file. */ public synchronized void open() throws Exception { rewind(); if (!isOpen()) openCurrent(); } /** Opens the current file. Next read will returns samples * from head of given file. If setFormat() was called previously, then * we will attempt to do format conversion. */ private synchronized void openCurrent() throws Exception { File file=curFile.getFile(); Shell.trace("\nFileSource:Opening audio file "+file); AudioInputStream s=AudioSystem.getAudioInputStream(file); AudioFormat fmt1, af=s.getFormat(); Shell.trace(" format: "+af); // convert to target format if required if (format!=null) { if (!format.equals(af)) { Shell.trace(" converting to "+format); if (af.getChannels()>format.getChannels()) { int frameSize = af.getChannels()*format.getSampleSizeInBits()/8; Shell.trace(" channels mix down required."); fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(), format.getSampleSizeInBits(), af.getChannels(), frameSize, format.getFrameRate(), format.isBigEndian()); channelsToMix = af.getChannels(); } else { channelsToMix = 0; fmt1=format; } Shell.trace(" converting via "+fmt1); s=convertFormat(s,af,fmt1); inFormat = fmt1; } else { Shell.trace(" no formation conversion required"); channelsToMix = 0; inFormat = af; } } else { Shell.trace(" using stream native format"); channelsToMix = 0; inFormat = af; } in=s; if (buffered) in = new BufferedInputStream(in,64*1024); // If we have a reader task, then update the byte buffer if (chunk>0) setByteBuffer(); } /** Returns number of bytes available in current file */ public int available() throws Exception { return in.available(); } /** Reopen current file, so that next read will be from head of file. */ public synchronized void reopen() throws Exception { close(); openCurrent(); } private void setBlockSize(int s) { chunk=s; } private void setByteBuffer() { bytesPerSample = 2*(channelsToMix>0 ? channelsToMix : 1); byte_buf = new byte[chunk*bytesPerSample]; } public int readInto(ByteBuffer buf, int offset, int len) throws IOException { return in.read(buf.array(), offset, len); } /** Returns a Task which copies samples as doubles into the given * buffer between the given positions. */ public Task reader(final double [] dbuf, final int off, final int len) { setBlockSize(len); if (in!=null) setByteBuffer(); return new AnonymousTask() { public void run() throws Exception { synchronized (FileSource.this) { // loop until len samples copied into dbuf int rem=len, pos=off; while (rem>0) { int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample); if (bytesRead > 0) { // append this chunk to output int count=bytesRead/bytesPerSample; if (channelsToMix>0) { Util.shortToDoubleMixDown(byte_buf,dbuf,pos,count,channelsToMix); } else { Util.shortToDouble(byte_buf,dbuf,pos,count); } pos+=count; rem-=count; } else if (it!=null) next(); // next file if there is one else if (!loop) throw new EOFException(); // not looping and no more files else reopen(); // back to first file // if (rem>0) Shell.trace("Read "+bytesRead+" bytes, need "+rem+" more samples."); } } } }; } /** Returns a Task which copies samples as floats into the given * buffer between the given positions. */ public Task reader(final float [] dbuf, final int off, final int len) { setBlockSize(len); if (in!=null) setByteBuffer(); return new AnonymousTask() { public synchronized void run() throws Exception { synchronized (FileSource.this) { // loop until len samples copied into dbuf int rem=len, pos=off; while (rem>0) { int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample); if (bytesRead > 0) { int count=bytesRead/bytesPerSample; if (channelsToMix>0) { Util.shortToFloatMixDown(byte_buf,dbuf,pos,count,channelsToMix); } else { Util.shortToFloat(byte_buf,dbuf,pos,count); } pos+=count; rem-=count; } else if (it!=null) next(); else if (!loop) throw new EOFException(); else reopen(); } } } }; } // Agent public void getCommands(Agent.Registry r) { r.add("prev").add("reopen").add("next").add("view") .add("select").add("loop",loop); } public void execute(String cmd, Environment env) throws Exception { if (cmd.equals("next")) next(); else if (cmd.equals("prev")) prev(); else if (cmd.equals("reopen")) reopen(); else if (cmd.equals("view")) { if (list!=null) { Shell.expose( new JScrollPane(new JList(list.toArray())), "playlist"); } } else if (cmd.equals("select")) { JFileChooser dlg = new JFileChooser("."); dlg.setMultiSelectionEnabled(true); dlg.setFileSelectionMode(dlg.FILES_AND_DIRECTORIES); if (list!=null && !list.isEmpty()) { dlg.setCurrentDirectory(list.get(0)); dlg.setSelectedFiles(list.toArray(new File[0])); } dlg.setDialogTitle("Select audio files"); if (dlg.showDialog(null, "OK")==JFileChooser.APPROVE_OPTION) { File first=dlg.getSelectedFile(); if (first.isDirectory()) setDirectory(first,".wav"); else setPlaylist(Arrays.asList(dlg.getSelectedFiles())); } } else if (cmd.equals("loop")) { loop=X._bool(env.datum(),!loop); } } // Viewable public Viewer getViewer() { return new FileSourceViewer(); } class FileSourceViewer extends DefaultViewer implements Agent { public FileSourceViewer() { super(FileSource.this); add(curFile); add(Shell.createButtonsFor(this)); } public void getCommands(Agent.Registry r) { r.add("prev").add("reopen").add("next").add("view").add("select"); } public void execute(String cmd, Environment env) throws Exception { FileSource.this.execute(cmd,env); } }; private static java.io.FileFilter getFileFilter(final String ext) { return new java.io.FileFilter() { public boolean accept(File file) { return file.getName().toLowerCase().endsWith(ext); } }; } private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception { Shell.trace("\nconvertFormat:"); Shell.trace(" | source: "+fin.toString()); Shell.trace(" | target: "+fout.toString()); if (fin.equals(fout)) return sin; else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) { try { Shell.trace(" | Trying direct (PCM) from "+fin.getEncoding().toString()); return AudioSystem.getAudioInputStream(fout,sin); } catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); } AudioFormat fint = new AudioFormat( // PCM fout.getSampleRate(), fout.getSampleSizeInBits(), fin.getChannels(), true, fout.isBigEndian()); Shell.trace(" | Trying PCM conversion via "+fint.toString()); return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin)); } else { // First, check for MP3 - if so, cannot convert number of channels if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) { Shell.trace(" | Trying decoding from "+fin.getEncoding().toString()); return AudioSystem.getAudioInputStream(fout,sin); } else { AudioFormat fint = new AudioFormat( fin.getSampleRate(), fout.getSampleSizeInBits(), fin.getChannels(), true, fout.isBigEndian()); Shell.trace(" | Trying recursive via "+fint.toString()); return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout); } } } }