// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>. // // TotalRecall is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 only. // // TotalRecall is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with TotalRecall. If not, see <http://www.gnu.org/licenses/>. package control; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; import components.audiofiles.AudioFile; /** * JavaSound distributes useful information about a file among sever classes in a way that can * be annoying if you don't have the API memorized. This class stores and determines everything * the program needs to know about an audio file. * * <p> * Audio checking policy: <code>AudioMaster</code> constructor throws exceptions concerning Java Sound's inability to handle * a file, as well as exceptions for this program's inability to handle a format. No other checking * needs to be conducted after an AudioMaster is successfully created. No other methods should * throw compatibility exceptions, but should instead suppress them with try/catch blocks if required by Java. * * <p> * Sample and frame rates are guaranteed the same. AudioMaster constructor rejects audio for which * they are different. * * <p> * The current <code>AudioMaster</code> should be used to perform any and all math conversions * related to the open audio file. * * @author Yuvi Masory * */ public class AudioMaster { private static final int BITS_PER_BYTE = 8; // from constructor private AudioFile audioFile; // from AudioInputStream private long numSampleFrames; // from AudioFormat private boolean isBigEndian; private int numChannels; private int sampleSizeInBits; private int frameSizeInBits; private float framesPerSecond; private float sampleRate; private AudioFormat.Encoding encoding; // computed private double durationInSeconds; /** * Incompatible file types are rejected, forcing error handling on whoever is instantiating the class. * * @param audioFile the file containing the audio * @throws FileNotFoundException * @throws UnsupportedAudioFileException If Java Sound or some other part of this program can't handle the file * @throws IOException */ public AudioMaster(AudioFile audioFile) throws FileNotFoundException, UnsupportedAudioFileException, IOException { this.audioFile = audioFile; // open an AudioInputStream AudioInputStream aiStream = AudioSystem.getAudioInputStream( new BufferedInputStream ( new FileInputStream(audioFile))); if(aiStream.getFormat().getFrameSize() != 2) { throw new UnsupportedAudioFileException("only 16-bit audio is supported"); } if(aiStream.getFormat().getChannels() != 1) { throw new UnsupportedAudioFileException("only mono audio is supported"); } // grab info from AudioInputStream numSampleFrames = aiStream.getFrameLength(); AudioFormat format = aiStream.getFormat(); // grab info from AudioFormat numChannels = format.getChannels(); framesPerSecond = format.getFrameRate(); sampleRate = format.getSampleRate(); sampleSizeInBits = format.getSampleSizeInBits(); isBigEndian = format.isBigEndian(); encoding = format.getEncoding(); // computed durationInSeconds = (double)numSampleFrames / (double)framesPerSecond; frameSizeInBits = sampleSizeInBits * numChannels; // printInfo(); if(sampleSizeInBits != 16) { throw new UnsupportedAudioFileException("Only 16 bit encodings supported."); } if(framesPerSecond != sampleRate) { //this is just a sanity check. in uncompressed audio sample and frame rates should be the same throw new UnsupportedAudioFileException("Frame rate doesn't equal sample rate. Unsupported."); } } @SuppressWarnings("unused") private void printInfo() { System.out.println(); System.out.println("-- AudioMaster --"); System.out.println("file name: " + audioFile.getName()); System.out.println("encoding: " + encoding); System.out.println("number of channels: " + numChannels); System.out.println("bigEndian: " + isBigEndian); System.out.println("sample size in bits: " + sampleSizeInBits); System.out.println("frames per second: " + framesPerSecond); System.out.println("sample rate: " + sampleRate); System.out.println("num frames: " + numSampleFrames); System.out.println("predicted duration: " + durationInSeconds() + " seconds"); System.out.println(); } public double durationInSeconds() { return durationInSeconds; } public long durationInFrames() { return numSampleFrames; } public int numChannels() { return numChannels; } public double frameRate() { return framesPerSecond; } public int sampleSizeInBits() { return sampleSizeInBits; } public int sampleSizeInBytes() { return sampleSizeInBits/BITS_PER_BYTE; } public int frameSizeInBits() { return frameSizeInBits; } public int frameSizeInBytes() { return frameSizeInBits/BITS_PER_BYTE; } public long millisToFrames(long millis) { return millisToFrames((double)millis); } public long millisToFrames(double millis) { double sec = millis/1000.; return (long) (sampleRate * sec); } public long nanosToFrames(long millis) { return millisToFrames(((double)millis)/1000000); } public double framesToMillis(long frames) { return (framesToSec(frames) * 1000); } public double framesToSec(long frames) { return frames * (1/((double)sampleRate)); } public long framesToBytes(long frames) { return frames * (frameSizeInBits/BITS_PER_BYTE); } public long secondsToFrames(double seconds) { return (long)(sampleRate * seconds); } public AudioFile getAudioFile() { return audioFile; } }