package greenfoot.sound;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;


| Wraps a SourceDataLine to work around all the different bugs that happens on | different systems. | | To work around all the different problems listed below, this class minimises | the use of open and close and never uses drain. Instead we use the fact that | we know how long it takes to play a certain number of bytes and use this to | implement our own drain method. By using this timing we also don't have to | open and close on the line in order to reset the framecount of the line when | restarting, since it is not used anyway. | | | There are several inconsistencies between different platforms that means that | this class is more complicated than it really should be if everything worked | as it should. Below is listed the different problems observed on various | platforms: | | Windows XP on Poul's home PC (SP3, Sun JDK 1.6.11, SB Live Sound Card) and | Windows Vista on Poul's office PC (dell build in soundcard) and Windows XP on | Poul's office PC (SP2, dell onboard soundcard) | <ul> | <li>Line does not receive a stop signal when end of media has been reached.</li> | <li>Line is reported as active even when end of media has been reached. If | invoking stop, then start again, it seems to remain inactive though (this | does not generate a START event, only a stop)</li> | <li>The frame position reported by line.getLongFramePosition() is incorrect. | After reaching the last frame, it will, after a while, start over at frame | position 0 and count up to the last frame again. It will repeat this forever. | </li> | </ul> | | Linux on Poul's home PC (Ubuntu 8.10, Sun JDK 1.6.10, SB Live Sound Card): | <ul> | <li>Line does not receive a stop signal when end of media has been reached.</li> | <li>Line is reported as active even when end of media has been reached.</li> | <li>Hangs if line.drain() is used (need to confirm this, saw it a long time | ago, and it might have been because of timing issues resulting in drain() | being invoked on a stopped line)</li> | <li>The frame position reported by line.getLongFramePosition() is correct and | seems to be the only way of detecting when the end of the media has been | reached.</li> | </ul> | | | Linux on Poul's office PC (Ubuntu 8.10, Sun JDK 1.6.10 / 1.5.16, SB Live | Sound Card): | <ul> | <li>Repeatedly calling close and open makes it hang in close.</li> | <li>Haven't tested whether line.drain() works.</li> | | </ul> | | Mac (OS 10.5.6, JDK 1.5.0_16 | <ul> | <li>Closing and opening a line repeatedly crashes the JVM with this error. | Can be reproduced in the piano scenario if you quickly press the same button | about 10-20 times in row. (JDK 1.5 prints the error below, 1.6 just crashes | silently): | java(3382,0xb1b4e000) malloc: *** mmap(size=1073745920) failed (error | code=12) | error: can't allocate region | set a breakpoint in malloc_error_break to debug</li> | <li>It skips START events if the line is closed before we have received the | START event.</li> | </ul> | | @author Poul Henriksen | public class AudioLine { private static void printDebug(String s) { }
| Extra delay in ms added to the sleep time before stopping the sound. This | is just an extra buffer of time to make sure we don't close it too soon. | This helps avoid stopping the sound too soon which seems to happen on | some Linux systems. | private final static int EXTRA_SLEEP_DELAY = 50;
| The actual line that we wrap. I assume this object is thread-safe, | because it has methods that only makes sense in a multi-threaded | environment (drain()). | private volatile SourceDataLine line; private AudioFormat format;
| Total bytes written since playback started. | private long totalWritten;
| Whether the line is open. | private boolean open;
| Whether the line has been started. | private boolean started; private int masterVolume;
| Whether data is currently being written to the line (or blocked on | write). | private boolean writing;
| Whether the line is reset. As soon as data has been written, the line is | no longer reset. | private boolean reset;
| Keeps track of how much time have been spend on active playback. | private TimeTracker timeTracker; public AudioLine(SourceDataLine line, AudioFormat format) { this.line = line; this.format = format; timeTracker = new TimeTracker(); }
| Opens the line. If already open, this method does nothing. | | @throws LineUnavailableException if the line cannot be opened due to | resource restrictions | @throws IllegalArgumentException if <code>format</code> is not fully | specified or invalid | @throws SecurityException if the line cannot be opened due to security | restrictions | public synchronized void open() throws LineUnavailableException, IllegalArgumentException, IllegalStateException, SecurityException { if (!open) { line.open(format); open = true; reset = true; } }
| Closes the line. If the line is not open, this method does nothing. | public synchronized void close() { if (open) { open = false; reset(); line.close(); } }
| Starts the line. Can be used to resume playback that have been stopped. | Unlike SourceDataLine this method does NOT have to be called before | writing to the line. | public synchronized void start() { if (!started && open) { line.start(); started = true; if (getTimeLeft() > 0) { timeTracker.start(); } } }
| Stops the line. Playback will stop. It can later be resumed by calling | start. | public synchronized void stop() { if (open) { notifyAll(); started = false; line.stop(); timeTracker.pause(); } }
| Resets this line by stopping playback and clearing all the data in the | buffer. The line will remain open. If the line is not open, this method | does nothing. | public synchronized void reset() { printDebug("reset() start"); if (open) { if (started) { line.stop(); } if (!reset) { line.flush(); } totalWritten = 0; started = false; timeTracker.reset(); notifyAll(); } reset = true; printDebug("reset() end"); }
| Will attempt to write the given bytes to the line. This method might | block if it can't write it all at once. If the line is not open then this | method will return 0 immediately. | | @return The number of bytes written (different from len if the line was | stopped while writing). | public int write(byte[] b, int off, int len) { synchronized (this) { if (!open) { return 0; } writing = true; started = true; reset = false; timeTracker.start(); } line.start(); int written = line.write(b, off, len); synchronized (this) { notifyAll(); writing = false; if (!reset) { totalWritten += written; } else if (reset && open) { line.flush(); } return written; } }
| Wait for the line to finish playback. If this line is closed or reset it | will return immediately. If the line is stopped, this method will not | return until the line is started again. | | @return True if we successfully drained all the data, false if the line | was closed before playback finished. | public synchronized boolean drain() { printDebug("Draining start"); printDebug(" totalWritten: " + totalWritten); long timeLeft = getTimeLeft(); while (timeLeft > 0 && open){ printDebug(" timeLeft: " + timeLeft); if (started && timeLeft > 0) { try { wait(timeLeft); } catch (InterruptedException e) { } } else if (!started || writing) { try { wait(); } catch (InterruptedException e) { } } timeLeft = getTimeLeft(); } printDebug("Draining end: " + timeLeft); if (timeLeft > 0) { return false; } else { return true; } } public synchronized boolean isOpen() { return open; } private synchronized long getTimeLeft() { return SoundUtils.getTimeToPlayBytes(totalWritten, format) - timeTracker.getTimeTracked() + EXTRA_SLEEP_DELAY; } public long getLongFramePosition() { return line.getLongFramePosition(); } public synchronized void setVolume(int masterVolume) { this.masterVolume = masterVolume; try { open(); if (line != null) { if (line.isControlSupported(FloatControl.Type.MASTER_GAIN)) { FloatControl volume = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN); float val = SoundUtils.convertMinMax(masterVolume, volume.getMinimum(), volume.getMaximum()); volume.setValue(val); } } } catch (LineUnavailableException ex) { SoundExceptionHandler.handleLineUnavailableException(ex); } } public synchronized int getVolume() { return masterVolume; } }
top, use, map, class AudioLine

.   printDebug
.   AudioLine
.   open
.   close
.   start
.   stop
.   reset
.   write
.   drain
.   isOpen
.   getTimeLeft
.   getLongFramePosition
.   setVolume
.   getVolume




293 neLoCode + 105 LoComm