annotate src/samer/audio/MultiFileAudioStream.java @ 8:5e3cbbf173aa tip

Reorganise some more
author samer
date Fri, 05 Apr 2019 22:41:58 +0100
parents 5df24c91468d
children
rev   line source
samer@1 1 /*
samer@1 2 * FileSource.java
samer@1 3 *
samer@1 4 * Copyright (c) 2000, Samer Abdallah, King's College London.
samer@1 5 * All rights reserved.
samer@1 6 *
samer@1 7 * This software is provided AS iS and WITHOUT ANY WARRANTY;
samer@1 8 * without even the implied warranty of MERCHANTABILITY or
samer@1 9 * FITNESS FOR A PARTICULAR PURPOSE.
samer@1 10 */
samer@1 11
samer@1 12 package samer.audio;
samer@1 13
samer@1 14 import samer.core.*;
samer@1 15 import samer.core.types.*;
samer@1 16 import samer.core.util.*;
samer@1 17 import samer.tools.*;
samer@1 18 import javax.sound.sampled.*;
samer@1 19 import javax.swing.*;
samer@1 20 import java.io.*;
samer@1 21 import java.util.*;
samer@1 22 import java.nio.ByteBuffer;
samer@1 23
samer@1 24 /**
samer@1 25 */
samer@1 26
samer@1 27
samer@1 28 public abstract class MultiFileAudioStream extends InputStream
samer@1 29 {
samer@1 30 List<File> list=null;
samer@1 31 ListIterator<File> it=null;
samer@1 32 int channelsToMix;
samer@1 33 File curFile=null;
samer@1 34 InputStream in=null;
samer@1 35 AudioFormat format=null, inFormat=null;
samer@1 36 boolean loop=false;
samer@1 37
samer@1 38 /**
samer@1 39 * Construct a FileSource initialised with current file and loop initialised
samer@1 40 * from the current Environment. No exception is thrown if the current file
samer@1 41 * cannot be opened, however, you must be sure to set it to a valid file
samer@1 42 * (or to set a playlist) before trying to read any samples.
samer@1 43 */
samer@1 44 public MultiFileAudioStream(AudioFormat target, List<File> files)
samer@1 45 {
samer@1 46 list=files;
samer@1 47 format=target;
samer@1 48 }
samer@1 49
samer@1 50 public AudioFormat getFormat() { return format; }
samer@1 51
samer@1 52 /** Set playlist to all WAV files in given directory */
samer@1 53 public List<File> directory(File dir, String ext) {
samer@1 54 return Arrays.asList(dir.listFiles(getFileFilter(ext)));
samer@1 55 }
samer@1 56
samer@1 57 private static java.io.FileFilter getFileFilter(final String ext) {
samer@1 58 return new java.io.FileFilter() {
samer@1 59 public boolean accept(File file) {
samer@1 60 return file.getName().toLowerCase().endsWith(ext);
samer@1 61 }
samer@1 62 };
samer@1 63 }
samer@1 64
samer@1 65 public synchronized List<File> getPlaylist() { return list; }
samer@1 66
samer@1 67 /** Go back to start of playlist. Next block of samples will be from head
samer@1 68 * of first file in playlist. Will throw an exception if there is no playlist */
samer@1 69 public synchronized void rewind() throws IOException {
samer@1 70 it=list.listIterator(); next();
samer@1 71 }
samer@1 72
samer@1 73 /** Move to head of next file in playlist */
samer@1 74 public synchronized void next() throws IOException
samer@1 75 {
samer@1 76 boolean wasOpen=isOpen();
samer@1 77
samer@1 78 if (wasOpen) close();
samer@1 79 if (!it.hasNext()) {
samer@1 80 if (!loop) throw new EOFException();
samer@1 81 it=list.listIterator();
samer@1 82 if (!it.hasNext()) throw new IOException("no files in playlist");
samer@1 83 }
samer@1 84 curFile=it.next();
samer@1 85 if (wasOpen) openCurrent();
samer@1 86 }
samer@1 87
samer@1 88 /** Move to head of previous file in playlist */
samer@1 89 public synchronized void prev() throws Exception
samer@1 90 {
samer@1 91 boolean wasOpen=isOpen();
samer@1 92 if (wasOpen) close();
samer@1 93 if (!it.hasPrevious()) {
samer@1 94 if (!loop) throw new EOFException();
samer@1 95 it=list.listIterator(list.size());
samer@1 96 if (!it.hasPrevious()) throw new Exception("no files in playlist");
samer@1 97 }
samer@1 98 curFile=it.previous();
samer@1 99 if (wasOpen) openCurrent();
samer@1 100 }
samer@1 101
samer@1 102 public boolean isOpen() { return in!=null; }
samer@1 103
samer@1 104 /** Closes current input stream */
samer@1 105 public synchronized void close() {
samer@1 106 try {
samer@1 107 if (in!=null) {
samer@1 108 Shell.trace("Closing audio stream...");
samer@1 109 in.close(); in=null;
samer@1 110 }
samer@1 111 }
samer@1 112 catch (IOException ex) {}
samer@1 113 }
samer@1 114
samer@1 115 /** Opens the playlist starting with the first file. */
samer@1 116 public synchronized void open() throws Exception { rewind(); if (!isOpen()) openCurrent(); }
samer@1 117
samer@1 118 /** Opens the current file. Next read will returns samples
samer@1 119 * from head of given file. If setFormat() was called previously, then
samer@1 120 * we will attempt to do format conversion. */
samer@1 121 private synchronized void openCurrent() throws IOException
samer@1 122 {
samer@1 123 try {
samer@1 124 File file=curFile;
samer@1 125 Shell.trace("\nFileSource:Opening audio file "+file);
samer@1 126
samer@1 127 AudioInputStream s=AudioSystem.getAudioInputStream(file);
samer@1 128 AudioFormat fmt1, af=s.getFormat();
samer@1 129
samer@1 130 Shell.trace(" format: "+af);
samer@1 131
samer@1 132 // convert to target format if required
samer@1 133 if (format!=null) {
samer@1 134 if (!format.equals(af)) {
samer@1 135 Shell.trace(" converting to "+format);
samer@1 136 if (af.getChannels()>format.getChannels()) {
samer@1 137 int frameSize = af.getChannels()*format.getSampleSizeInBits()/8;
samer@1 138 Shell.trace(" channels mix down required.");
samer@1 139 fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(),
samer@1 140 format.getSampleSizeInBits(),
samer@1 141 af.getChannels(), frameSize, format.getFrameRate(), format.isBigEndian());
samer@1 142
samer@1 143 channelsToMix = af.getChannels();
samer@1 144 } else {
samer@1 145 channelsToMix = 0;
samer@1 146 fmt1=format;
samer@1 147 }
samer@1 148 Shell.trace(" converting via "+fmt1);
samer@1 149 s=convertFormat(s,af,fmt1);
samer@1 150 inFormat = fmt1;
samer@1 151 } else {
samer@1 152 Shell.trace(" no formation conversion required");
samer@1 153 channelsToMix = 0;
samer@1 154 inFormat = af;
samer@1 155 }
samer@1 156 } else {
samer@1 157 Shell.trace(" using stream native format");
samer@1 158 channelsToMix = 0;
samer@1 159 inFormat = af;
samer@1 160 }
samer@1 161 in=s;
samer@1 162 }
samer@1 163 catch (Exception ex) { throw new IOException("Failed to open audio file"); }
samer@1 164 }
samer@1 165
samer@1 166 /** Returns number of bytes available in current file */
samer@1 167 public int available() {
samer@1 168 try {
samer@1 169 return in.available();
samer@1 170 }
samer@1 171 catch (Exception ex) { return 0; }
samer@1 172 }
samer@1 173
samer@1 174 /** Returns a Task which copies samples as doubles into the given
samer@1 175 * buffer between the given positions. */
samer@1 176 public int read(byte [] buf, int off, int len) throws IOException {
samer@1 177
samer@1 178 // loop until len samples copied into dbuf
samer@1 179 int rem=len, pos=off;
samer@1 180 while (rem>0) {
samer@1 181 int chunk = in.read(buf, pos, rem);
samer@1 182 if (chunk > 0) { // append this chunk to output
samer@1 183 pos+=chunk; rem-=chunk;
samer@1 184 } else if (it!=null) next(); // next file if there is one
samer@1 185 else if (!loop) throw new EOFException(); // not looping and no more files
samer@1 186 else rewind(); // back to first file
samer@1 187 }
samer@1 188 return len;
samer@1 189 }
samer@1 190
samer@1 191
samer@1 192 private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception
samer@1 193 {
samer@1 194 Shell.trace("\nconvertFormat:");
samer@1 195 Shell.trace(" | source: "+fin.toString());
samer@1 196 Shell.trace(" | target: "+fout.toString());
samer@1 197
samer@1 198 if (fin.equals(fout)) return sin;
samer@1 199 else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) {
samer@1 200 try {
samer@1 201 Shell.trace(" | Trying direct (PCM) from "+fin.getEncoding().toString());
samer@1 202 return AudioSystem.getAudioInputStream(fout,sin);
samer@1 203 }
samer@1 204 catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); }
samer@1 205
samer@1 206 AudioFormat fint = new AudioFormat( // PCM
samer@1 207 fout.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 208 fin.getChannels(), true, fout.isBigEndian());
samer@1 209 Shell.trace(" | Trying PCM conversion via "+fint.toString());
samer@1 210 return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin));
samer@1 211 } else {
samer@1 212 // First, check for MP3 - if so, cannot convert number of channels
samer@1 213 if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) {
samer@1 214 Shell.trace(" | Trying decoding from "+fin.getEncoding().toString());
samer@1 215 return AudioSystem.getAudioInputStream(fout,sin);
samer@1 216 } else {
samer@1 217 AudioFormat fint = new AudioFormat(
samer@1 218 fin.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 219 fin.getChannels(), true, fout.isBigEndian());
samer@1 220 Shell.trace(" | Trying recursive via "+fint.toString());
samer@1 221 return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout);
samer@1 222 }
samer@1 223 }
samer@1 224 }
samer@1 225 }
samer@1 226