diff src/samer/audio/MultiFileAudioStream.java @ 1:5df24c91468d

Oh my what a mess.
author samer
date Fri, 05 Apr 2019 16:26:00 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/samer/audio/MultiFileAudioStream.java	Fri Apr 05 16:26:00 2019 +0100
@@ -0,0 +1,226 @@
+/*
+ *	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;
+
+/**
+*/
+
+
+public abstract class MultiFileAudioStream extends InputStream 
+{
+	List<File>				list=null;
+	ListIterator<File>	it=null;
+	int						channelsToMix;
+	File						curFile=null;
+	InputStream				in=null;
+	AudioFormat				format=null, inFormat=null;
+	boolean              loop=false;
+
+	/**
+	 * 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 MultiFileAudioStream(AudioFormat target, List<File> files)
+	{
+		list=files;
+		format=target;
+	}
+
+	public AudioFormat getFormat() { return format; }
+
+	/** Set playlist to all WAV files in given directory */
+	public List<File> directory(File dir, String ext) {
+		return Arrays.asList(dir.listFiles(getFileFilter(ext))); 
+	}
+
+	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);
+			}
+		};
+	}
+
+	public synchronized List<File> getPlaylist() { return list; }
+
+	/** 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 IOException {
+		it=list.listIterator(); next();
+	}
+
+	/** Move to head of next file in playlist */
+	public synchronized void next() throws IOException
+	{
+		boolean wasOpen=isOpen();
+
+		if (wasOpen) close();
+		if (!it.hasNext()) {
+			if (!loop) throw new EOFException();
+			it=list.listIterator();
+			if (!it.hasNext()) throw new IOException("no files in playlist");
+		}
+		curFile=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");
+		}
+		curFile=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; 
+			}
+		}
+		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 IOException
+	{
+		try {
+			File file=curFile;
+			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;
+		}
+		catch (Exception ex) { throw new IOException("Failed to open audio file"); }
+	}
+
+	/** Returns number of bytes available in current file */
+	public int available() { 
+		try {
+			return in.available(); 
+		}
+		catch (Exception ex) { return 0; }
+	}
+
+	/** Returns a Task which copies samples as doubles into the given
+	 *  buffer between the given positions. */
+	public int read(byte [] buf, int off, int len) throws IOException {
+
+		// loop until len samples copied into dbuf
+		int rem=len, pos=off;
+		while (rem>0) {
+			int chunk = in.read(buf, pos, rem);
+			if (chunk > 0) { // append this chunk to output
+				pos+=chunk; rem-=chunk;
+			} else if (it!=null) next(); // next file if there is one
+			else if (!loop) throw new EOFException(); // not looping and no more files
+			else rewind(); // back to first file
+		}
+		return len;
+	}
+
+
+	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);
+			}
+		}
+	}
+}
+