diff --git a/proposal/myrmidon/src/java/org/apache/antlib/sound/AntSoundPlayer.java b/proposal/myrmidon/src/java/org/apache/antlib/sound/AntSoundPlayer.java new file mode 100644 index 000000000..ccbd371c5 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/antlib/sound/AntSoundPlayer.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.antlib.sound; + +import java.io.File; +import java.io.IOException; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import org.apache.avalon.framework.logger.LogEnabled; +import org.apache.avalon.framework.logger.Logger; +import org.apache.myrmidon.listeners.AbstractProjectListener; +import org.apache.myrmidon.listeners.LogEvent; +import org.apache.myrmidon.listeners.ProjectEvent; + +/** + * This class is designed to be used by any AntTask that requires audio output. + * It implements the BuildListener interface to listen for BuildEvents and could + * be easily extended to provide audio output upon any specific build events + * occuring. I have only tested this with .WAV and .AIFF sound file formats. + * Both seem to work fine. + * + * @author Nick Pellow + * @version $Revision$, $Date$ + */ +public class AntSoundPlayer + extends AbstractProjectListener + implements LineListener, LogEnabled +{ + private File m_fileSuccess; + private int m_loopsSuccess; + private Long m_durationSuccess; + + private File m_fileFail; + private int m_loopsFail; + private Long m_durationFail; + + private Logger m_logger; + + /** + * Provide component with a logger. + * + * @param logger the logger + */ + public void enableLogging( final Logger logger ) + { + m_logger = logger; + } + + protected final Logger getLogger() + { + return m_logger; + } + + /** + * Notify listener of projectFinished event. + */ + public void projectFinished( final ProjectEvent event ) + { + success(); + } + + /** + * Notify listener of log message event. + */ + public void log( final LogEvent event ) + { + if( event.getThrowable() != null ) + { + failure(); + } + } + + /** + * @param fileFail The feature to be added to the BuildFailedSound attribute + * @param loopsFail The feature to be added to the BuildFailedSound + * attribute + * @param durationFail The feature to be added to the BuildFailedSound + * attribute + */ + public void addBuildFailedSound( File fileFail, int loopsFail, Long durationFail ) + { + m_fileFail = fileFail; + m_loopsFail = loopsFail; + m_durationFail = durationFail; + } + + /** + * @param loops the number of times the file should be played when the build + * is successful + * @param duration the number of milliseconds the file should be played when + * the build is successful + * @param file The feature to be added to the BuildSuccessfulSound attribute + */ + public void addBuildSuccessfulSound( File file, int loops, Long duration ) + { + m_fileSuccess = file; + m_loopsSuccess = loops; + m_durationSuccess = duration; + } + + /** + * This is implemented to listen for any line events and closes the clip if + * required. + */ + public void update( LineEvent event ) + { + if( event.getType().equals( LineEvent.Type.STOP ) ) + { + Line line = event.getLine(); + line.close(); + } + else if( event.getType().equals( LineEvent.Type.CLOSE ) ) + { + /* + * There is a bug in JavaSound 0.90 (jdk1.3beta). + * It prevents correct termination of the VM. + * So we have to exit ourselves. + */ + //System.exit(0); + } + } + + protected void success() + { + if( null != m_fileSuccess ) + { + // build successfull! + play( m_fileSuccess, m_loopsSuccess, m_durationSuccess ); + } + } + + protected void failure() + { + if( null != m_fileFail ) + { + play( m_fileFail, m_loopsFail, m_durationFail ); + } + } + + /** + * Plays the file for duration milliseconds or loops. + */ + private void play( File file, int loops, Long duration ) + { + Clip audioClip = null; + + AudioInputStream audioInputStream = null; + + try + { + audioInputStream = AudioSystem.getAudioInputStream( file ); + } + catch( UnsupportedAudioFileException uafe ) + { + final String message = "Audio format is not yet supported: " + uafe.getMessage(); + getLogger().info( message ); + } + catch( IOException ioe ) + { + ioe.printStackTrace(); + } + + if( audioInputStream != null ) + { + AudioFormat format = audioInputStream.getFormat(); + DataLine.Info info = new DataLine.Info( Clip.class, format, + AudioSystem.NOT_SPECIFIED ); + try + { + audioClip = (Clip)AudioSystem.getLine( info ); + audioClip.addLineListener( this ); + audioClip.open( audioInputStream ); + } + catch( LineUnavailableException e ) + { + final String message = "The sound device is currently unavailable"; + getLogger().info( message ); + return; + } + catch( IOException e ) + { + e.printStackTrace(); + } + + if( duration != null ) + { + playClip( audioClip, duration.longValue() ); + } + else + { + playClip( audioClip, loops ); + } + audioClip.drain(); + audioClip.close(); + } + else + { + final String message = "Can't get data from file " + file.getName(); + getLogger().info( message ); + } + } + + private void playClip( Clip clip, int loops ) + { + + clip.loop( loops ); + while( clip.isRunning() ) + { + } + } + + private void playClip( Clip clip, long duration ) + { + clip.loop( Clip.LOOP_CONTINUOUSLY ); + try + { + Thread.sleep( duration ); + } + catch( InterruptedException e ) + { + } + } +} + diff --git a/proposal/myrmidon/src/java/org/apache/antlib/sound/BuildAlert.java b/proposal/myrmidon/src/java/org/apache/antlib/sound/BuildAlert.java new file mode 100644 index 000000000..4a11eae86 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/antlib/sound/BuildAlert.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.antlib.sound; + +import java.io.File; + +/** + * A class to be extended by any BuildAlert's that require the output of + * sound. + */ +public class BuildAlert +{ + private File m_source; + private int m_loops; + private Long m_duration; + + /** + * Sets the duration in milliseconds the file should be played. + * + * @param duration The new Duration value + */ + public void setDuration( Long duration ) + { + m_duration = duration; + } + + /** + * Sets the number of times the source file should be played. + * + * @param loops the number of loops to play the source file + */ + public void setLoops( int loops ) + { + m_loops = loops; + } + + /** + * Sets the location of the file to get the audio. + * + * @param source the name of a sound-file directory or of the audio file + */ + public void setSource( final File source ) + { + m_source = source; + } + + /** + * Gets the duration in milliseconds the file should be played. + * + * @return The Duration value + */ + public Long getDuration() + { + return m_duration; + } + + /** + * Sets the number of times the source file should be played. + * + * @return the number of loops to play the source file + */ + public int getLoops() + { + return m_loops; + } + + /** + * Gets the location of the file to get the audio. + * + * @return The Source value + */ + public File getSource() + { + return m_source; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/antlib/sound/Resources.properties b/proposal/myrmidon/src/java/org/apache/antlib/sound/Resources.properties new file mode 100644 index 000000000..d01821c59 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/antlib/sound/Resources.properties @@ -0,0 +1,4 @@ +sound.missing-success.error=No nested success element found. +sound.missing-failure.error=No nested failure element found. +sound.empty.dir.error=No files found in directory {0}. +sound.invalid-path.error={0}: invalid path. \ No newline at end of file diff --git a/proposal/myrmidon/src/java/org/apache/antlib/sound/SoundTask.java b/proposal/myrmidon/src/java/org/apache/antlib/sound/SoundTask.java new file mode 100644 index 000000000..c62a94cd4 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/antlib/sound/SoundTask.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.antlib.sound; + +import java.io.File; +import java.util.ArrayList; +import java.util.Random; +import org.apache.myrmidon.api.AbstractTask; +import org.apache.myrmidon.api.TaskException; +import org.apache.myrmidon.interfaces.workspace.Workspace; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * This is an example of an AntTask that makes of use of the AntSoundPlayer. + * There are three attributes to be set: source: the location of + * the audio file to be played duration: play the sound file + * continuously until "duration" milliseconds has expired loops: + * the number of times the sound file should be played until stopped I have only + * tested this with .WAV and .AIFF sound file formats. Both seem to work fine. + * plans for the future: - use the midi api to define sounds (or drum beat etc) + * in xml and have Ant play them back + * + * @ant:task name="sound-listener" + * @author Nick Pellow + * @author Peter Donald + * @version $Revision$, $Date$ + */ +public class SoundTask + extends AbstractTask +{ + private final static Resources REZ = + ResourceManager.getPackageResources( SoundTask.class ); + + private BuildAlert m_success; + private BuildAlert m_fail; + + public void addFail( final BuildAlert fail ) + { + m_fail = fail; + } + + public void addSuccess( final BuildAlert success ) + { + m_success = success; + } + + public void execute() + throws TaskException + { + final AntSoundPlayer soundPlayer = new AntSoundPlayer(); + if( null == m_success ) + { + final String message = REZ.getString( "sound.missing-success.error" ); + getLogger().warn( message ); + } + else + { + final File source = getRandomSource( m_success ); + soundPlayer.addBuildSuccessfulSound( source, + m_success.getLoops(), + m_success.getDuration() ); + } + + if( null == m_fail ) + { + final String message = REZ.getString( "sound.missing-failure.error" ); + getLogger().warn( message ); + } + else + { + final File source = getRandomSource( m_fail ); + soundPlayer.addBuildFailedSound( source, + m_fail.getLoops(), + m_fail.getDuration() ); + } + + final Workspace workspace = (Workspace)getContext().getService( Workspace.class ); + workspace.addProjectListener( soundPlayer ); + } + + /** + * Gets the location of the file to get the audio. + */ + private File getRandomSource( final BuildAlert alert ) + throws TaskException + { + final File source = alert.getSource(); + // Check if source is a directory + if( source.exists() ) + { + if( source.isDirectory() ) + { + // get the list of files in the dir + final String[] entries = source.list(); + final ArrayList files = new ArrayList(); + for( int i = 0; i < entries.length; i++ ) + { + final File file = new File( source, entries[ i ] ); + if( file.isFile() ) + { + files.add( file ); + } + } + if( files.size() < 1 ) + { + final String message = REZ.getString( "sound.empty.dir.error", source ); + throw new TaskException( message ); + } + final int numfiles = files.size(); + // get a random number between 0 and the number of files + final Random random = new Random(); + final int x = random.nextInt( numfiles ); + // set the source to the file at that location + return (File)files.get( x ); + } + else + { + return null; + } + } + else + { + final String message = REZ.getString( "sound.invalid-path.error", source ); + getLogger().warn( message ); + return null; + } + } +}