// 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.precisionplayer; import java.io.FileNotFoundException; import java.io.IOException; import javax.sound.sampled.UnsupportedAudioFileException; /** * Specification for an audio playback system appropriate for high-precision tasks, such as linguistic annotation. * * <h3>Overview</h3> * A PrecisionPlayer keeps track of two kinds of audio playback: main playback and short-interval playback. * Main playback is fully controlled, allowing stopping, changing of loudness, etc., and represents the user moving through an audio file. * Short interval playback, in contrast, cannot be stopped once started, nor is there any guarantee that loudness of short intervals can be changed once started. * It is anticipated that users will replay short intervals around the main playback point as they make judgments about * their audio annotations. * * <h3>Goals</h3> * The overriding optimization goal of any implementation (after minimum guarantees are met) * should be to minimize the latency of the play and stop methods, and to increase the accuracy of the stop method's * return value. * * <h3>Precision</h3> * Play methods are exact. That is to say, the frames they play meet the parameters of the method call exactly. * The stop method returns an approximate value, a concession to the realities of audio software and hardware buffering. * This is why there is no resume() function: in almost any implementation the resumption frame cannot be guaranteed identical * to the frame reported by the pause/stop method. * * <h3>Error Handling</h3> * Implementations should handle exceptions internally, other than when the exceptions are declared thrown in this interface. * This is especially crucial for native code implementations. * Error reporting is handled through reporting <code>PrecisionPlayer.EventCode.ERROR</code> events to registered listeners. * * <h3>Audio Formats</h3> * Implementations should strive to support at least the sampled sound audio types supported by Sun's reference implementation of * Java Sound: AIFF, AU, WAV, 8 or 16 bit samples, mono or stereo, 8KHz-48KHz sampling, with linear, a-law, or mu-law PCM. * Frames are identified with zero-based numbering. * * <h4>Implementation-specific Behavior</h4> * Implementers should minimize the use of methods not defined here. * Implementation-specific information should ideally be gathered by providing overloaded versions of the constructor, but a default * constructor must always be provided. * * @author Yuvi Masory */ public interface PrecisionPlayer { /** * Status of the player. * * <p><code>BUSY</code>: * Indicates the Player will not be able to minimize latency if one of the play functions is called now. * Play calls will still succeed when Player is <code>BUSY</code>, but it may take longer than usual for the audio playback to begin. * Status should be <code>BUSY</code> if <code>open()</code> has not yet been called. * * <p><code>READY</code>: * Indicates the Player is ready to play back audio with minimum latency. * * <p><code>PLAYING</code>: * Indicates that main playback is currently in progress. */ public static enum Status {BUSY, READY, PLAYING}; /** * Opens and initializes the provided file. * * <p>Any initialization an implementation needs to perform prior to main or short-interval playback should be performed now. * After a file has been opened the <code>playAt()</code> and <code>playShortInterval()</code> methods should start audio playback with the smallest possible latency. * * <p>May be implemented in-thread or concurrently. Concurrent implementations may choose to set status to <code>BUSY</code> while this method runs. * However, <code>playAt()</code> must succeed regardless of whether the open thread is still running. * * <p>Open is intended to be called only once per <code>PrecisionPlayer</code>. If it is called again, behavior is not guaranteed. * * @param fileName The path of the audio file to be opened * @throws FileNotFoundException If the provided file cannot be located * @throws IOException If the provided file cannot be read * @throws UnsupportedAudioFileException If the provided file is not of a supported format */ public void open(String fileName) throws FileNotFoundException, IOException, UnsupportedAudioFileException; /** * Starts main playback at provided frame. * * Has no effect if main playback is already in progress, i.e. if status is <code>PLAYINGK/code>, or if <code>open()</code> has not yet been called. * * <p>Implementations should seek to reduce latency to a minimum. * * <p>Must be implemented concurrently, i.e. method must return as soon as possible, not waiting for playback to finish. * * @param frame The audio frame from which to start playback * @throws IllegalArgumentException If the provided frame is not present the audio file */ public void playAt(long frame) throws IllegalArgumentException; public void playAt(long startFrame, long endFrame) throws IllegalArgumentException; /** * Starts short-interval playback at <code>startFrame</code>, and stops playing at <code>endFrame</code> instead of at natural end of media. * * Has No effect if main playback is in progress, i.e. if status is <code>PLAYING</code>, or if <code>open()</code> has not yet been called. * * <p>Implementations should seek to reduce latency to a minimum. * * <p>The <code>stop()</code> method should have no impact on short-interval playback. <code>setLoudness()</code> is not guaranteed to have any effect * on short-interval playback once started. That is an implementation-specific decision. * Implementations should also consider that users will often play the same short interval many times consecutively. * * <p>To assist implementations using Sun's implementation of Java Sound, the difference between startFrame and endFrame * must be less than or equal to 1048576, the limit imposed by <code>com.sun.media.sound.MixerClip</code>. * <code>MixerClip</code> also requires that frame size be no larger than 4, but this seems to be a general aspect of Sun's implementation, * so that should not introduce a further restriction to Sun-based implementations. * * <p>Must be implemented concurrently, i.e. method must return as soon as possible, not waiting for playback to finish. * * @param startFrame The audio frame from which to start playback * @param endFrame The audio frame from which to terminate playback * @throws IllegalArgumentException If endFrame >= startFrame, or if either is not in the boundary of the audio file */ public void playShortInterval(long startFrame, long endFrame) throws IllegalArgumentException; /** * Stops main playback and returns "hearing" frame. * * Returns -1 if main playback is not in progress, i.e. if status is not <code>PLAYING</code>, or if <code>open()</code> has not yet been called. * * <p>First and foremost, implementations should seek to be as accurate as possible in returning the current "hearing" frame. * The hearing frame is defined to be the last frame the user perceived. * Due to the nature of audio hardware and software buffering, exactness in determining the hearing frame is impossible. * This interface does not specify any guaranteed level of exactness. * * <p>Second, implementations should seek to minimize latency (in this case the time between the stop action and the end of audio playback). * * <p>Must be implemented in-thread, so that when the method returns, all stopping code has already been run. * * @return The "hearing" frame at the time playback is stopped or -1 if main playback is not in progress.. */ public long stop(); /** * Gives warning that <code>playAt(int)</code> may soon be called. * * <p>To assist an implementation's ability to reduce latency of <code>playAt(int)</code> calls, this method informs an implementation that * <code>playAt(frame)</code> may be may soon be called. * There is no guarantee <code>playAt(frame)</code> will be called. * An implementation need not do anything in this method, as <code>playAt(int)</code> should always work, although with possibly greater latency. * * <p>Unlike <code>playAt(frame)</code>, does not throw an exception if <tt>frame</tt> is not within the audio file. * * <p>May be implemented in-thread or concurrently. * * @param frame The predicted argument to an upcoming <code>playAt(int)</code> call. */ public void queuePlayAt(long frame); /** * Gives warning that <code>playInterval(startFrame, endFrame)</code> may soon be called. * * <p>To assist an implementation's ability to reduce latency of <code>playInterval(int, int)</code> calls, this method informs an implementation that * <code>playInterval(startFrame, endFrame)</code> may soon be called. * There is no guarantee <code>playInterval(startFrame, endFrame)</code> will be called. * An implementation need not do anything in this method, as <code>playInterfval(int, int)</code> should always work, although with possibly greater latency. * * <p>Unlike <code>playAt(frame)</code>, does not throw an exception if <tt>frame</tt> is not within the audio file. * * <p>May be implemented in-thread or concurrently. * * @param startFrame The predicted startFrame argument to the next <code>queueShortInterval(int, int)</code> call * @param endFrame The predicted endFrame argument to the next <code>queueShortInterval(int, int)</code> call */ public void queueShortInterval(long startFrame, long endFrame); /** * Adjusts the perceived loudness of main playback. * * <p>The parameter values should be in a linear relationship with perceived loudness. * For example, a parameter of 50 should sound twice as loud as 25. * Can be called at any point, whether or not main playback is in progress, or <code>open()</code> has been called. * * <p>May be implemented in-thread or concurrently. * * @param loudness An integer between 0 and 100, where 0 is mute and 100 is maximum loudness */ public void setLoudness(int loudness); /** * Returns the perceived loudness of main playback. * * <p>The parameter values should be in a linear relationship with perceived loudness. * For example, a parameter of 50 should sound twice as loud as 25. * Can be called at any point, whether or not main playback is in progress, or <code>open()</code> has been called. * * <p>May be implemented in-thread or concurrently. * * @return An integer between 0 and 100, where 0 is mute and 100 is maximum loudness */ public int getLoudness(); /** * Returns one of the status codes defined in this interface. * * See documentation of status code fields for more information. * * @return One of the status enums defined in this interface */ public Status getStatus(); /** * Adds a <code>PrecisionListener</code> to receive notifications from this <code>PrecisionPlayer</code>. * * Does nothing if argument is null. * Does not take effect until the next play call. * * @param listener The <code>PrecisionListener</code> to receive notifications from this <code>PrecisionPlayer</code>. */ public void addListener(PrecisionListener listener); /** * Indicates whether <code>getLoudness()</code> and <code>setLoudness()</code> calls will be effective. * * This is a concession to APIs like RtAudio that do not provide a way to change line gains short of manipulating the audio * samples directly. * * @return <code>true</code> iff this <code>PrecisionPlayer</code> supports adjustment and querying of playback loudness */ public boolean isLoudnessControlSupported(); }