annotate src/samer/audio/FileSource2.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 * FileSource2.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 An AudioSource that read from multiple audio files. Can read any
samer@1 26 format for which the appropriate JavaSound plug-in is installed on
samer@1 27 your system, eg WAV, AU, MP3, OGG. The implementation of
samer@1 28 AudioSource.reader() returns a Task which gets samples from the audio
samer@1 29 files, going through the play list one by one.
samer@1 30 An EOFException is thrown if an attempt is made
samer@1 31 to read beyond the end of the last file.
samer@1 32
samer@1 33 FileSource2 is a Viewable called "playlist", and an Agent with commands
samer@1 34 next, view.
samer@1 35
samer@1 36 */
samer@1 37
samer@1 38 public class FileSource2 extends Viewable implements AudioSource, Agent
samer@1 39 {
samer@1 40 List<File> list=null;
samer@1 41 ListIterator<File> it=null;
samer@1 42 int channelsToMix;
samer@1 43 File curFile=null;
samer@1 44 InputStream in=null;
samer@1 45 AudioFormat targetFormat=null;
samer@1 46 byte[] byte_buf=null;
samer@1 47 int chunk=0, bytesPerSample=0;
samer@1 48
samer@1 49 /**
samer@1 50 * Construct a FileSource2.
samer@1 51 */
samer@1 52 public FileSource2(AudioFormat targetFormat, List<File> files)
samer@1 53 {
samer@1 54 super("playlist");
samer@1 55
samer@1 56 setAgent(this);
samer@1 57 Shell.registerViewable(this);
samer@1 58 this.targetFormat=targetFormat;
samer@1 59 this.list=files;
samer@1 60 it=list.listIterator();
samer@1 61 }
samer@1 62
samer@1 63 // AudioSource interface methods
samer@1 64 public void dispose() {
samer@1 65 close();
samer@1 66 Shell.deregisterViewable(this);
samer@1 67 }
samer@1 68
samer@1 69 public int getChannels() { return targetFormat.getChannels(); }
samer@1 70 public float getRate() { return targetFormat.getFrameRate(); }
samer@1 71
samer@1 72 /** Returns current file */
samer@1 73 public File getFile() { return curFile; }
samer@1 74
samer@1 75 /** The requested audio format. **/
samer@1 76 public AudioFormat getTargetFormat() { return targetFormat; }
samer@1 77
samer@1 78 /** Set playlist to all WAV files in given directory */
samer@1 79 public static List<File> directory(File dir, String ext) {
samer@1 80 return Arrays.asList(dir.listFiles(getFileFilter(ext)));
samer@1 81 }
samer@1 82
samer@1 83 public synchronized List<File> getPlaylist() { return list; }
samer@1 84
samer@1 85 /** Move to head of next file in playlist */
samer@1 86 public synchronized void next() throws Exception
samer@1 87 {
samer@1 88 boolean wasOpen=isOpen();
samer@1 89
samer@1 90 if (wasOpen) close();
samer@1 91 if (!it.hasNext()) throw new EOFException();
samer@1 92 curFile=it.next();
samer@1 93 if (wasOpen) openCurrent();
samer@1 94 }
samer@1 95
samer@1 96 public boolean isOpen() { return in!=null; }
samer@1 97
samer@1 98 /** Closes current input stream */
samer@1 99 public synchronized void close() {
samer@1 100 try {
samer@1 101 if (in!=null) {
samer@1 102 Shell.trace("Closing audio stream...");
samer@1 103 in.close(); in=null; byte_buf=null;
samer@1 104 }
samer@1 105 }
samer@1 106 catch (IOException ex) {}
samer@1 107 }
samer@1 108
samer@1 109 /** Opens the playlist starting with the first file. */
samer@1 110 public synchronized void open() throws Exception {
samer@1 111 it=list.listIterator(); next();
samer@1 112 if (!isOpen()) openCurrent();
samer@1 113 }
samer@1 114
samer@1 115 /** Opens the current file. Next read will returns samples
samer@1 116 * from head of given file. If setFormat() was called previously, then
samer@1 117 * we will attempt to do format conversion. */
samer@1 118 private synchronized void openCurrent() throws Exception
samer@1 119 {
samer@1 120 File file=curFile;
samer@1 121 Shell.trace("\nFileSource:Opening audio file "+file);
samer@1 122
samer@1 123 AudioInputStream ain=AudioSystem.getAudioInputStream(file);
samer@1 124
samer@1 125 // convert to target format if required
samer@1 126 if (targetFormat==null) {
samer@1 127 AudioFormat fin=ain.getFormat();
samer@1 128 ain=convertFormat(new AudioFormat( fin.getSampleRate(), 16, fin.getChannels(), true, false), ain);
samer@1 129 } else {
samer@1 130 ain=convertFormat(targetFormat, ain);
samer@1 131 }
samer@1 132 bytesPerSample=ain.getFormat().getSampleSizeInBits()/8;
samer@1 133 if (bytesPerSample!=2) throw new Exception("Must be 16 bits per sample");
samer@1 134
samer@1 135 in = new BufferedInputStream(ain,64*1024);
samer@1 136
samer@1 137 // If we have a reader task, then update the byte buffer
samer@1 138 if (chunk>0) setByteBuffer();
samer@1 139 }
samer@1 140
samer@1 141 /** Returns number of bytes available in current file */
samer@1 142 public int available() throws Exception { return in.available(); }
samer@1 143
samer@1 144 private void setBlockSize(int s) { chunk=s; }
samer@1 145 private void setByteBuffer() {
samer@1 146 byte_buf = new byte[chunk*bytesPerSample];
samer@1 147 }
samer@1 148
samer@1 149 public int readInto(ByteBuffer buf, int offset, int len) throws IOException {
samer@1 150 return in.read(buf.array(), offset, len);
samer@1 151 }
samer@1 152
samer@1 153 /** Returns a Task which copies samples as doubles into the given
samer@1 154 * buffer between the given positions. */
samer@1 155 public Task reader(final double [] dbuf, final int off, final int len) {
samer@1 156 setBlockSize(len);
samer@1 157 if (in!=null) setByteBuffer();
samer@1 158
samer@1 159 return new AnonymousTask() {
samer@1 160 public void run() throws Exception {
samer@1 161 synchronized (FileSource2.this) {
samer@1 162 // loop until len samples copied into dbuf
samer@1 163 int rem=len, pos=off;
samer@1 164 while (rem>0) {
samer@1 165 int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample);
samer@1 166 if (bytesRead > 0) { // append this chunk to output
samer@1 167 int count=bytesRead/bytesPerSample;
samer@1 168 Util.shortToDouble(byte_buf,dbuf,pos,count);
samer@1 169 pos+=count; rem-=count;
samer@1 170 } else if (it!=null) next(); // next file if there is one
samer@1 171 else throw new EOFException(); // not looping and no more files
samer@1 172 }
samer@1 173 }
samer@1 174 }
samer@1 175 };
samer@1 176 }
samer@1 177
samer@1 178 /** Returns a Task which copies samples as floats into the given
samer@1 179 * buffer between the given positions. */
samer@1 180 public Task reader(final float [] dbuf, final int off, final int len) {
samer@1 181 setBlockSize(len);
samer@1 182 if (in!=null) setByteBuffer();
samer@1 183
samer@1 184 return new AnonymousTask() {
samer@1 185 public synchronized void run() throws Exception {
samer@1 186 synchronized (FileSource2.this) {
samer@1 187 // loop until len samples copied into dbuf
samer@1 188 int rem=len, pos=off;
samer@1 189 while (rem>0) {
samer@1 190 int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample);
samer@1 191 if (bytesRead > 0) {
samer@1 192 int count=bytesRead/bytesPerSample;
samer@1 193 Util.shortToFloat(byte_buf,dbuf,pos,count);
samer@1 194 pos+=count; rem-=count;
samer@1 195 } else if (it!=null) next();
samer@1 196 else throw new EOFException();
samer@1 197 }
samer@1 198 }
samer@1 199 }
samer@1 200 };
samer@1 201 }
samer@1 202
samer@1 203 // Agent
samer@1 204 public void getCommands(Agent.Registry r) {
samer@1 205 r.add("next").add("view");
samer@1 206 }
samer@1 207
samer@1 208 public void execute(String cmd, Environment env) throws Exception {
samer@1 209 if (cmd.equals("next")) next();
samer@1 210 else if (cmd.equals("view")) {
samer@1 211 if (list!=null) {
samer@1 212 Shell.expose(
samer@1 213 new JScrollPane(new JList(list.toArray())),
samer@1 214 "playlist");
samer@1 215 }
samer@1 216 }
samer@1 217 }
samer@1 218
samer@1 219 // Viewable
samer@1 220 public Viewer getViewer() { return new FileSourceViewer(); }
samer@1 221 class FileSourceViewer extends DefaultViewer implements Agent {
samer@1 222 public FileSourceViewer() {
samer@1 223 super(FileSource2.this);
samer@1 224 //add(curFile);
samer@1 225 add(Shell.createButtonsFor(this));
samer@1 226 }
samer@1 227
samer@1 228 public void getCommands(Agent.Registry r) {
samer@1 229 r.add("next").add("view");
samer@1 230 }
samer@1 231 public void execute(String cmd, Environment env) throws Exception {
samer@1 232 FileSource2.this.execute(cmd,env);
samer@1 233 }
samer@1 234 };
samer@1 235
samer@1 236 private static java.io.FileFilter getFileFilter(final String ext) {
samer@1 237 return new java.io.FileFilter() {
samer@1 238 public boolean accept(File file) {
samer@1 239 return file.getName().toLowerCase().endsWith(ext);
samer@1 240 }
samer@1 241 };
samer@1 242 }
samer@1 243
samer@1 244 private static AudioInputStream convertVia(AudioFormat fout, AudioInputStream sin, AudioFormat fint) throws Exception
samer@1 245 {
samer@1 246 Shell.trace(" | Trying recursive via "+fint.toString());
samer@1 247 AudioInputStream sint=AudioSystem.getAudioInputStream(fint,sin);
samer@1 248 Shell.trace(" | Obtained "+sint.getFormat().toString());
samer@1 249 return convertFormat(fout, sint);
samer@1 250 }
samer@1 251
samer@1 252 private static AudioInputStream convertFormat(AudioFormat fout, AudioInputStream sin) throws Exception
samer@1 253 {
samer@1 254 AudioFormat fin=sin.getFormat();
samer@1 255
samer@1 256 if (fin.equals(fout)) return sin;
samer@1 257
samer@1 258 Shell.trace("\nconvertFormat:");
samer@1 259 Shell.trace(" | source: "+fin.toString());
samer@1 260 Shell.trace(" | target: "+fout.toString());
samer@1 261
samer@1 262 if (fin.getEncoding()!=AudioFormat.Encoding.PCM_SIGNED) {
samer@1 263 // first get into PCM encoding, then try recursive
samer@1 264 try {
samer@1 265 return convertVia( fout, sin, new AudioFormat(
samer@1 266 fin.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 267 fin.getChannels(), true, fout.isBigEndian()));
samer@1 268 } catch (IllegalArgumentException ex) {
samer@1 269 Shell.trace("Direct conversion failed");
samer@1 270 }
samer@1 271 return convertVia( fout, sin, new AudioFormat(
samer@1 272 fin.getSampleRate(), fin.getSampleSizeInBits(),
samer@1 273 fin.getChannels(), true, fout.isBigEndian()));
samer@1 274 }
samer@1 275
samer@1 276 if (fin.getChannels()!=fout.getChannels() || fin.getSampleSizeInBits()!=fout.getSampleSizeInBits()) {
samer@1 277 // convert these before doing any sample rate conversion
samer@1 278 return convertVia(fout, sin, new AudioFormat(
samer@1 279 fin.getSampleRate(), fout.getSampleSizeInBits(),
samer@1 280 fout.getChannels(), true, fout.isBigEndian()));
samer@1 281 }
samer@1 282
samer@1 283 // the only thing left is sample rate
samer@1 284 return AudioSystem.getAudioInputStream(fout,sin);
samer@1 285 }
samer@1 286 }
samer@1 287