view 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 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;

/**
*/


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);
			}
		}
	}
}