// 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 info.Constants; import info.GUIConstants; import info.SysInfo; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; import java.util.Stack; import javax.sound.sampled.UnsupportedAudioFileException; import util.GiveMessage; import util.OSPath; import components.MyFrame; import components.MyMenu; import components.annotations.Annotation; import components.annotations.AnnotationDisplay; import components.annotations.AnnotationFileParser; import components.audiofiles.AudioFile; import components.audiofiles.AudioFileDisplay; import components.waveform.WaveformBuffer; import components.waveform.WaveformDisplay; import components.wordpool.WordpoolDisplay; import components.wordpool.WordpoolFileParser; import edu.upenn.psych.memory.nativestatelessplayer.NativeStatelessPlayer; import edu.upenn.psych.memory.precisionplayer.PrecisionPlayer; /** * Static-only class that stores the eseential state of the program. * * @author Yuvi Masory, Apurva Jatakia */ public class CurAudio { private static AudioMaster master; private static MyPrecisionListener precisionListener; private static File curAudioFile; private static PrecisionPlayer player; private static long chunkSize; private static int desiredLoudness = 100; private static long framePosition; private static long[] lastFrameArray; private static long[] firstFrameArray; private static int totalNumOfChunks; private static Stack<Long> playHistory = new Stack<Long>(); private static WaveformBuffer waveformBuffer; private static final String audioClosedMessage = "Audio Not Open. You must check first"; private static final String badStateString = "ERROR: potential violation of guarantee that either master and player are both null, or neither is"; /** * Prevent instantiation. */ private CurAudio() { } /** * Switches all of the program's state, including display, wordpool/annotation/file lists to the provided file. * * This is the only thread-safe place to switch program state from one audio file to another. * * @param file The audio file to swtich to, or <code>null</code> to reset the program. */ public static void switchFile(AudioFile file) { reset(); if(file == null) { MyFrame.getInstance().setTitle(GUIConstants.defaultFrameTitle); } else { curAudioFile = file; // create AudioMaster and handle bad formats/files master = null; boolean success = false; try { master = new AudioMaster(file); success = true; } catch(FileNotFoundException e) { e.printStackTrace(); GiveMessage.errorMessage("Audio file not found!"); } catch (UnsupportedAudioFileException e) { e.printStackTrace(); GiveMessage.errorMessage("Unsupported audio format!\n" + e.getMessage()); } catch(IOException e) { e.printStackTrace(); GiveMessage.errorMessage("Error opening audio file!"); } if(!success) { switchFile(null); System.err.println("audio switch not successfull. resetting current audio."); return; } // change frame title to display current file info MyFrame.getInstance().setTitle( GUIConstants.defaultFrameTitle + " - " + curAudioFile.getPath()); chunkSize = SysInfo.sys.chunkSizeInSeconds * (long)master.frameRate(); totalNumOfChunks = (int)Math.ceil((double)master.durationInFrames()/(double)chunkSize); lastFrameArray = new long[totalNumOfChunks]; firstFrameArray = new long[totalNumOfChunks]; for(int i = 0; i < firstFrameArray.length; i++) { firstFrameArray[i] = chunkSize * i; if(i == firstFrameArray.length - 1) { lastFrameArray[i] = master.durationInFrames() - 1; } else { lastFrameArray[i] = (chunkSize) * (i + 1) - 1; } } // prepare playback PrecisionPlayer pp = null; try { pp = new NativeStatelessPlayer(); } catch (Throwable e1) { e1.printStackTrace(); GiveMessage.errorMessage("Cannot load audio system.\nYou may need to reinstall " + Constants.programName + "."); reset(); } precisionListener = new MyPrecisionListener(); pp.addListener(precisionListener); pp.setLoudness(getDesiredLoudness()); setPlayer(pp); success = false; try { pp.open(file.getAbsolutePath()); success = true; } catch(FileNotFoundException e) { e.printStackTrace(); GiveMessage.errorMessage("Audio file not found!"); } catch (UnsupportedAudioFileException e) { e.printStackTrace(); GiveMessage.errorMessage("Unsupported audio format!\n" + e.getMessage()); } catch(IOException e) { e.printStackTrace(); GiveMessage.errorMessage("Error opening audio file!"); } if(!success) { switchFile(null); System.err.println("PrecisionPlayer.open() not successfull. resetting current audio."); return; } // add words from lst file to display File lstFile = new File(OSPath.basename(file.getAbsolutePath()) + "." + Constants.lstFileExtension); if(lstFile.exists()) { try { //refuse inter-version resuming of files // BufferedReader reader = new BufferedReader(new FileReader(lstFile)); // String curLine; // while((curLine = reader.readLine()) != null) { // // } WordpoolDisplay.distinguishAsLst(WordpoolFileParser.parse(lstFile, true)); } catch(IOException e) { e.printStackTrace(); } } // fill up annotation table with existing annotations File tmpFile = new File(OSPath.basename(file.getAbsolutePath()) + "." + Constants.temporaryAnnotationFileExtension); if(tmpFile.exists()) { List<Annotation> tmpAnns = AnnotationFileParser.parse(tmpFile); AnnotationDisplay.addAnnotations(tmpAnns); } // start new video buffers waveformBuffer = new WaveformBuffer(); waveformBuffer.start(); WaveformDisplay.getInstance().startRefreshes(); } MyMenu.updateActions(); } /** * Reset the program's state by killing any threads associated with the current * audio file and clearing any data in memory associated with the current file. */ private static void reset() { AnnotationDisplay.removeAllAnnotations(); //stop waveform display WaveformDisplay.getInstance().stopRefreshes(); //stop audio playback if(player != null) { player.stop(); } //try to terminate buffer if(waveformBuffer != null && waveformBuffer.isAlive()) { boolean terminateSuccess = false; try { if(waveformBuffer.terminateThread(250) == false) { terminateSuccess = false; } else { terminateSuccess = true; } } catch(InterruptedException e) { terminateSuccess = false; } if(terminateSuccess == false) { System.err.println("could not stop buffer: " + waveformBuffer); } } WordpoolDisplay.clearText(); WordpoolDisplay.undistinguishAllWords(); waveformBuffer = null; player = null; master = null; precisionListener = null; curAudioFile = null; lastFrameArray = null; firstFrameArray = null; chunkSize = 0; framePosition = 0; totalNumOfChunks = 0; playHistory.clear(); AudioFileDisplay.getInstance().repaint(); AnnotationDisplay.getInstance().repaint(); MyFrame.getInstance().requestFocusInWindow(); } public static long popLastPlayPos() { if(playHistory.isEmpty()) { return -1; } else { return playHistory.pop(); } } public static boolean hasLastPlayPos() { return playHistory.isEmpty() == false; } public static void pushPlayPos(long playPos) { playHistory.push(playPos); } /** * Returns current waveform chunk index. */ public static int lookupChunkNum(long currentFrame) { return (int) (currentFrame / chunkSize); } /** * Returns the last waveform chunk index of the current audio file. */ public static int lastChunkNum() { return totalNumOfChunks - 1; } /** * Returns the index of the first audio frame of the provided chunk, * relative to the entire audio file. */ public static long firstFrameOfChunk(int chunkNum) { if(chunkNum < 0 || chunkNum > firstFrameArray.length - 1) { return -1; } return firstFrameArray[chunkNum]; } /** * Returns the index of the last audio frame of the provided chunk, * relative to the entire audio file. */ public static long lastFrameOfChunk(int chunkNum) { if(chunkNum < 0 || chunkNum > lastFrameArray.length - 1) { return -1; } return lastFrameArray[chunkNum]; } /** * Determines whether audio is currently open, or whether the program is in * its blank state. * * @return <code>true</code> iff audio is open */ public static boolean audioOpen() { if(master != null) { if(getPlayer() == null) { throw new IllegalStateException(badStateString); } else { return true; } } else { if(player != null) { throw new IllegalStateException(badStateString); } else { return false; } } } /** * Returns the current <code>AudioMaster</code>. * * @throws IllegalStateException If audio is not open */ public static AudioMaster getMaster() { if(master == null) { throw new IllegalStateException(audioClosedMessage); } if(player != null) { return master; } else { throw new IllegalStateException(badStateString); } } public static MyPrecisionListener getListener() { return precisionListener; } /** * Returns the current <code>PrecisionPlayer</code> that is used for audio playback. * * @throws IllegalStateException If audio is not open */ public static PrecisionPlayer getPlayer() { if(player == null) { throw new IllegalStateException(audioClosedMessage); } if(master != null) { return player; } else { throw new IllegalStateException(badStateString); } } /** * Returns the absolute path of the currently open audio file. * * @throws IllegalStateException If audio is not open */ public static String getCurrentAudioFileAbsolutePath() { if(audioOpen()) { return curAudioFile.getAbsolutePath(); } else { throw new IllegalStateException(audioClosedMessage); } } private static void setPlayer(PrecisionPlayer player) { CurAudio.player = player; } /** * Returns the "hearing frame." * * @throws IllegalStateException If audio is not open */ public static long getAudioProgress() { if(audioOpen()) { return framePosition; } else { throw new IllegalStateException(audioClosedMessage); } } /** * Sets the program's opinion of the current "hearing frame". * * @param frame The "hearing frame" */ public static void setAudioProgressWithoutUpdatingActions(long frame) { if(audioOpen()) { framePosition = frame; } else { throw new IllegalStateException(audioClosedMessage); } } public static void setAudioProgressAndUpdateActions(long frame) { if(audioOpen()) { framePosition = frame; MyMenu.updateActions(); } else { throw new IllegalStateException(audioClosedMessage); } } /** * Set the desired loudness for current and future audio playback. * * Should take effect immediately. * * @param val Loudness on a 0-100 scale, linear to human perception of loudness */ public static void updateDesiredAudioLoudness(int val) { desiredLoudness = val; if(master != null) { player.setLoudness(val); } } /** * Returns the desired loudness for current and future audio playback. * * @return Loudness on a 0-100 scale, linear to human perception of loudness */ public static int getDesiredLoudness() { return desiredLoudness; } }