// 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 edu.upenn.psych.memory.nativestatelessplayer; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; import edu.upenn.psych.memory.precisionplayer.PrecisionEvent; import edu.upenn.psych.memory.precisionplayer.PrecisionEventLauncher; import edu.upenn.psych.memory.precisionplayer.PrecisionListener; import edu.upenn.psych.memory.precisionplayer.PrecisionPlayer; public class NativeStatelessPlayer implements PrecisionPlayer { private PrecisionPlayer.Status status; private List<PrecisionListener> listeners; private File audioFile; private long lastFrame; private long previousStartFrame; private NativeStatelessPlaybackThread mainThread; private NativeStatelessPlaybackThread shortThread; private final LibPennTotalRecall lib = LibPennTotalRecall.instance; /** * Creates an new player, with status <code>BUSY</code>. */ public NativeStatelessPlayer() { status = PrecisionPlayer.Status.BUSY; listeners = new ArrayList<PrecisionListener>(); // System.out.println("using: " + lib.getLibraryName() + ", revision " + lib.getLibraryRevisionNumber()); } public void open(String fileName) throws FileNotFoundException, IOException, UnsupportedAudioFileException { audioFile = new File(fileName); status = PrecisionPlayer.Status.READY; notifyEvent(PrecisionEvent.EventCode.OPENED, -1, null); AudioInputStream ais = AudioSystem.getAudioInputStream(audioFile); AudioFormat format = ais.getFormat(); if(format.getChannels() > 1) { throw new UnsupportedAudioFileException(getClass() + " only supports mono audio at present"); } if(format.getFrameSize() != 2) { throw new UnsupportedAudioFileException(getClass() + " only supports 16-bit audio at present"); } lastFrame = ais.getFrameLength() - 1; } private void playAt(long startFrame, long endFrame, List<PrecisionListener> players) { try { if(startFrame < 0) { startFrame = 0; } if(endFrame <= startFrame) { System.err.println("endFrame cannot be <= startFrame (" + endFrame + ", " + startFrame + ")"); return; } if(audioFile != null) { if((mainThread == null || mainThread.isAlive() == false) && (shortThread == null || shortThread.isAlive() == false)) { NativeStatelessPlaybackThread nThread = new NativeStatelessPlaybackThread(lib, this, audioFile, startFrame, endFrame, players); if(players != null) { previousStartFrame = startFrame; mainThread = nThread; status = PrecisionPlayer.Status.PLAYING; mainThread.start(); notifyEvent(PrecisionEvent.EventCode.PLAYING, startFrame, null); } else { shortThread = nThread; shortThread.start(); } } else { System.err.println("I won't start another playback thread when one is already running"); } } else { System.err.println("you must open() a player before calling a play function"); } } catch(Throwable t) { notifyEvent(PrecisionEvent.EventCode.ERROR, -1, t.getMessage()); } } public long stop() { try { if(audioFile != null && status == PrecisionPlayer.Status.PLAYING) { if(mainThread != null) { long framesPlayed = mainThread.stopPlayback(); long absoluteFrame = previousStartFrame + framesPlayed; status = PrecisionPlayer.Status.READY; notifyEvent(PrecisionEvent.EventCode.STOPPED, absoluteFrame, null); return absoluteFrame; } else { return -1; } } else { return -1; } } catch(Throwable t) { t.printStackTrace(); } return -1; } /* simple overridable functions */ public void playAt(long frame) throws IllegalArgumentException { playAt(frame, lastFrame); } public void kill() { stop(); } public void playAt(long startFrame, long endFrame) throws IllegalArgumentException { playAt(startFrame, endFrame, listeners); } public void playShortInterval(long startFrame, long endFrame) throws IllegalArgumentException { playAt(startFrame, endFrame, null); } public PrecisionPlayer.Status getStatus() { return status; } /** * Adds a new listener to receive updates from this player. * * @param listener The listener to receive events */ public void addListener(PrecisionListener listener) { listeners.add(listener); } /* default/empty but overridable implementations */ /** * Program currently has no volume slider, so just return false. */ public boolean isLoudnessControlSupported() { return false; } public int getLoudness() { return 100; } public void setLoudness(int loudness) {} public void queueShortInterval(long startFrame, long endFrame) {} public void queuePlayAt(long frame) {} /* custom methods */ /** * Launches notification in a new thread. * * @param code The event code * @param frame The audio frame of the event */ private void notifyEvent(PrecisionEvent.EventCode code, long frame, String errorMessage) { PrecisionEventLauncher trigger = new PrecisionEventLauncher(code, frame, errorMessage, listeners); trigger.start(); } public void setStatus(Status status) { this.status = status; } }