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;
+ }
+ }
+}