Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 12326)
@@ -13,5 +13,5 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.Shortcut;
 
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java	(revision 12326)
@@ -7,5 +7,5 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.Shortcut;
 
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java	(revision 12326)
@@ -12,5 +12,5 @@
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.Shortcut;
 
Index: /trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java	(revision 12326)
@@ -13,5 +13,5 @@
 import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.Utils;
Index: /trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 12326)
@@ -70,5 +70,5 @@
 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
 import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
Index: /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java	(revision 12326)
@@ -30,5 +30,5 @@
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
-import org.openstreetmap.josm.tools.AudioUtil;
+import org.openstreetmap.josm.io.audio.AudioUtil;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
Index: /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(revision 12326)
@@ -12,5 +12,5 @@
 import org.openstreetmap.josm.data.gpx.GpxLink;
 import org.openstreetmap.josm.data.gpx.WayPoint;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
 
Index: /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 12326)
@@ -49,5 +49,5 @@
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
Index: /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java	(revision 12325)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java	(revision 12326)
@@ -24,5 +24,5 @@
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
-import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
 
 /**
Index: /trunk/src/org/openstreetmap/josm/io/audio/AudioPlayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/audio/AudioPlayer.java	(revision 12326)
+++ /trunk/src/org/openstreetmap/josm/io/audio/AudioPlayer.java	(revision 12326)
@@ -0,0 +1,413 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.audio;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GraphicsEnvironment;
+import java.io.IOException;
+import java.net.URL;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.SourceDataLine;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Creates and controls a separate audio player thread.
+ *
+ * @author David Earl &lt;david@frankieandshadow.com&gt;
+ * @since 12326 (move to new package)
+ * @since 547
+ */
+public final class AudioPlayer extends Thread {
+
+    private static volatile AudioPlayer audioPlayer;
+
+    private enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }
+
+    private enum Command { PLAY, PAUSE }
+
+    private enum Result { WAITING, OK, FAILED }
+
+    private State state;
+    private URL playingUrl;
+    private final double leadIn; // seconds
+    private final double calibration; // ratio of purported duration of samples to true duration
+    private double position; // seconds
+    private double bytesPerSecond;
+    private static long chunk = 4000; /* bytes */
+    private double speed = 1.0;
+
+    /**
+     * Passes information from the control thread to the playing thread
+     */
+    private class Execute {
+        private Command command;
+        private Result result;
+        private Exception exception;
+        private URL url;
+        private double offset; // seconds
+        private double speed; // ratio
+
+        /*
+         * Called to execute the commands in the other thread
+         */
+        protected void play(URL url, double offset, double speed) throws InterruptedException, IOException {
+            this.url = url;
+            this.offset = offset;
+            this.speed = speed;
+            command = Command.PLAY;
+            result = Result.WAITING;
+            send();
+        }
+
+        protected void pause() throws InterruptedException, IOException {
+            command = Command.PAUSE;
+            send();
+        }
+
+        private void send() throws InterruptedException, IOException {
+            result = Result.WAITING;
+            interrupt();
+            while (result == Result.WAITING) {
+                sleep(10);
+            }
+            if (result == Result.FAILED)
+                throw new IOException(exception);
+        }
+
+        private void possiblyInterrupt() throws InterruptedException {
+            if (interrupted() || result == Result.WAITING)
+                throw new InterruptedException();
+        }
+
+        protected void failed(Exception e) {
+            exception = e;
+            result = Result.FAILED;
+            state = State.NOTPLAYING;
+        }
+
+        protected void ok(State newState) {
+            result = Result.OK;
+            state = newState;
+        }
+
+        protected double offset() {
+            return offset;
+        }
+
+        protected double speed() {
+            return speed;
+        }
+
+        protected URL url() {
+            return url;
+        }
+
+        protected Command command() {
+            return command;
+        }
+    }
+
+    private final Execute command;
+
+    /**
+     * Plays a WAV audio file from the beginning. See also the variant which doesn't
+     * start at the beginning of the stream
+     * @param url The resource to play, which must be a WAV file or stream
+     * @throws InterruptedException thread interrupted
+     * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
+     */
+    public static void play(URL url) throws InterruptedException, IOException {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        if (instance != null)
+            instance.command.play(url, 0.0, 1.0);
+    }
+
+    /**
+     * Plays a WAV audio file from a specified position.
+     * @param url The resource to play, which must be a WAV file or stream
+     * @param seconds The number of seconds into the audio to start playing
+     * @throws InterruptedException thread interrupted
+     * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
+     */
+    public static void play(URL url, double seconds) throws InterruptedException, IOException {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        if (instance != null)
+            instance.command.play(url, seconds, 1.0);
+    }
+
+    /**
+     * Plays a WAV audio file from a specified position at variable speed.
+     * @param url The resource to play, which must be a WAV file or stream
+     * @param seconds The number of seconds into the audio to start playing
+     * @param speed Rate at which audio playes (1.0 = real time, &gt; 1 is faster)
+     * @throws InterruptedException thread interrupted
+     * @throws IOException audio fault exception, e.g. can't open stream,  unhandleable audio format
+     */
+    public static void play(URL url, double seconds, double speed) throws InterruptedException, IOException {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        if (instance != null)
+            instance.command.play(url, seconds, speed);
+    }
+
+    /**
+     * Pauses the currently playing audio stream. Does nothing if nothing playing.
+     * @throws InterruptedException thread interrupted
+     * @throws IOException audio fault exception, e.g. can't open stream,  unhandleable audio format
+     */
+    public static void pause() throws InterruptedException, IOException {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        if (instance != null)
+            instance.command.pause();
+    }
+
+    /**
+     * To get the Url of the playing or recently played audio.
+     * @return url - could be null
+     */
+    public static URL url() {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        return instance == null ? null : instance.playingUrl;
+    }
+
+    /**
+     * Whether or not we are paused.
+     * @return boolean whether or not paused
+     */
+    public static boolean paused() {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        return instance == null ? false : (instance.state == State.PAUSED);
+    }
+
+    /**
+     * Whether or not we are playing.
+     * @return boolean whether or not playing
+     */
+    public static boolean playing() {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        return instance == null ? false : (instance.state == State.PLAYING);
+    }
+
+    /**
+     * How far we are through playing, in seconds.
+     * @return double seconds
+     */
+    public static double position() {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        return instance == null ? -1 : instance.position;
+    }
+
+    /**
+     * Speed at which we will play.
+     * @return double, speed multiplier
+     */
+    public static double speed() {
+        AudioPlayer instance = AudioPlayer.getInstance();
+        return instance == null ? -1 : instance.speed;
+    }
+
+    /**
+     * Returns the singleton object, and if this is the first time, creates it along with
+     * the thread to support audio
+     * @return the unique instance
+     */
+    private static AudioPlayer getInstance() {
+        if (audioPlayer != null)
+            return audioPlayer;
+        try {
+            audioPlayer = new AudioPlayer();
+            return audioPlayer;
+        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
+            Main.error(ex);
+            return null;
+        }
+    }
+
+    /**
+     * Resets the audio player.
+     */
+    public static void reset() {
+        if (audioPlayer != null) {
+            try {
+                pause();
+            } catch (InterruptedException | IOException e) {
+                Main.warn(e);
+            }
+            audioPlayer.playingUrl = null;
+        }
+    }
+
+    private AudioPlayer() {
+        state = State.INITIALIZING;
+        command = new Execute();
+        playingUrl = null;
+        leadIn = Main.pref.getDouble("audio.leadin", 1.0 /* default, seconds */);
+        calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
+        start();
+        while (state == State.INITIALIZING) {
+            yield();
+        }
+    }
+
+    /**
+     * Starts the thread to actually play the audio, per Thread interface
+     * Not to be used as public, though Thread interface doesn't allow it to be made private
+     */
+    @Override public void run() {
+        /* code running in separate thread */
+
+        playingUrl = null;
+        AudioInputStream audioInputStream = null;
+        SourceDataLine audioOutputLine = null;
+        AudioFormat audioFormat;
+        byte[] abData = new byte[(int) chunk];
+
+        for (;;) {
+            try {
+                switch (state) {
+                    case INITIALIZING:
+                        // we're ready to take interrupts
+                        state = State.NOTPLAYING;
+                        break;
+                    case NOTPLAYING:
+                    case PAUSED:
+                        sleep(200);
+                        break;
+                    case PLAYING:
+                        command.possiblyInterrupt();
+                        for (;;) {
+                            int nBytesRead = 0;
+                            if (audioInputStream != null) {
+                                nBytesRead = audioInputStream.read(abData, 0, abData.length);
+                                position += nBytesRead / bytesPerSecond;
+                            }
+                            command.possiblyInterrupt();
+                            if (nBytesRead < 0 || audioInputStream == null || audioOutputLine == null) {
+                                break;
+                            }
+                            audioOutputLine.write(abData, 0, nBytesRead); // => int nBytesWritten
+                            command.possiblyInterrupt();
+                        }
+                        // end of audio, clean up
+                        if (audioOutputLine != null) {
+                            audioOutputLine.drain();
+                            audioOutputLine.close();
+                        }
+                        audioOutputLine = null;
+                        Utils.close(audioInputStream);
+                        audioInputStream = null;
+                        playingUrl = null;
+                        state = State.NOTPLAYING;
+                        command.possiblyInterrupt();
+                        break;
+                    default: // Do nothing
+                }
+            } catch (InterruptedException e) {
+                interrupted(); // just in case we get an interrupt
+                State stateChange = state;
+                state = State.INTERRUPTED;
+                try {
+                    switch (command.command()) {
+                        case PLAY:
+                            double offset = command.offset();
+                            speed = command.speed();
+                            if (playingUrl != command.url() ||
+                                    stateChange != State.PAUSED ||
+                                    offset != 0) {
+                                if (audioInputStream != null) {
+                                    Utils.close(audioInputStream);
+                                }
+                                playingUrl = command.url();
+                                audioInputStream = AudioSystem.getAudioInputStream(playingUrl);
+                                audioFormat = audioInputStream.getFormat();
+                                long nBytesRead;
+                                position = 0.0;
+                                offset -= leadIn;
+                                double calibratedOffset = offset * calibration;
+                                bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
+                                * audioFormat.getFrameSize() /* bytes per frame */;
+                                if (speed * bytesPerSecond > 256_000.0) {
+                                    speed = 256_000 / bytesPerSecond;
+                                }
+                                if (calibratedOffset > 0.0) {
+                                    long bytesToSkip = (long) (calibratedOffset /* seconds (double) */ * bytesPerSecond);
+                                    // skip doesn't seem to want to skip big chunks, so reduce it to smaller ones
+                                    while (bytesToSkip > chunk) {
+                                        nBytesRead = audioInputStream.skip(chunk);
+                                        if (nBytesRead <= 0)
+                                            throw new IOException(tr("This is after the end of the recording"));
+                                        bytesToSkip -= nBytesRead;
+                                    }
+                                    while (bytesToSkip > 0) {
+                                        long skippedBytes = audioInputStream.skip(bytesToSkip);
+                                        bytesToSkip -= skippedBytes;
+                                        if (skippedBytes == 0) {
+                                            // Avoid inifinite loop
+                                            Main.warn("Unable to skip bytes from audio input stream");
+                                            bytesToSkip = 0;
+                                        }
+                                    }
+                                    position = offset;
+                                }
+                                if (audioOutputLine != null) {
+                                    audioOutputLine.close();
+                                }
+                                audioFormat = new AudioFormat(audioFormat.getEncoding(),
+                                        audioFormat.getSampleRate() * (float) (speed * calibration),
+                                        audioFormat.getSampleSizeInBits(),
+                                        audioFormat.getChannels(),
+                                        audioFormat.getFrameSize(),
+                                        audioFormat.getFrameRate() * (float) (speed * calibration),
+                                        audioFormat.isBigEndian());
+                                DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
+                                audioOutputLine = (SourceDataLine) AudioSystem.getLine(info);
+                                audioOutputLine.open(audioFormat);
+                                audioOutputLine.start();
+                            }
+                            stateChange = State.PLAYING;
+                            break;
+                        case PAUSE:
+                            stateChange = State.PAUSED;
+                            break;
+                        default: // Do nothing
+                    }
+                    command.ok(stateChange);
+                } catch (LineUnavailableException | IOException | UnsupportedAudioFileException |
+                        SecurityException | IllegalArgumentException startPlayingException) {
+                    Main.error(startPlayingException);
+                    command.failed(startPlayingException); // sets state
+                }
+            } catch (IOException e) {
+                state = State.NOTPLAYING;
+                Main.error(e);
+            }
+        }
+    }
+
+    /**
+     * Shows a popup audio error message for the given exception.
+     * @param ex The exception used as error reason. Cannot be {@code null}.
+     */
+    public static void audioMalfunction(Exception ex) {
+        String msg = ex.getMessage();
+        if (msg == null)
+            msg = tr("unspecified reason");
+        else
+            msg = tr(msg);
+        Main.error(msg);
+        if (!GraphicsEnvironment.isHeadless()) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    "<html><p>" + msg + "</p></html>",
+                    tr("Error playing sound"), JOptionPane.ERROR_MESSAGE);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/audio/AudioUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/audio/AudioUtil.java	(revision 12326)
+++ /trunk/src/org/openstreetmap/josm/io/audio/AudioUtil.java	(revision 12326)
@@ -0,0 +1,48 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.audio;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * Utils functions for audio.
+ *
+ * @author David Earl &lt;david@frankieandshadow.com&gt;
+ * @since 12326 (move to new package)
+ * @since 1462
+ */
+public final class AudioUtil {
+
+    private AudioUtil() {
+        // Hide default constructor for utils classes
+    }
+
+    /**
+     * Returns calibrated length of recording in seconds.
+     * @param wavFile the recording file (WAV format)
+     * @return the calibrated length of recording in seconds.
+     */
+    public static double getCalibratedDuration(File wavFile) {
+        try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
+                new URL("file:".concat(wavFile.getAbsolutePath())))) {
+            AudioFormat audioFormat = audioInputStream.getFormat();
+            long filesize = wavFile.length();
+            double bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
+                * audioFormat.getFrameSize() /* bytes per frame */;
+            double naturalLength = filesize / bytesPerSecond;
+            double calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
+            return naturalLength / calibration;
+        } catch (UnsupportedAudioFileException | IOException e) {
+            Main.debug(e);
+            return 0.0;
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/audio/package-info.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/audio/package-info.java	(revision 12326)
+++ /trunk/src/org/openstreetmap/josm/io/audio/package-info.java	(revision 12326)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides the classes for Audio mapping features.
+ */
+package org.openstreetmap.josm.io.audio;
Index: unk/src/org/openstreetmap/josm/tools/AudioPlayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/AudioPlayer.java	(revision 12325)
+++ 	(revision )
@@ -1,410 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.tools;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.GraphicsEnvironment;
-import java.io.IOException;
-import java.net.URL;
-
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioInputStream;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.DataLine;
-import javax.sound.sampled.LineUnavailableException;
-import javax.sound.sampled.SourceDataLine;
-import javax.sound.sampled.UnsupportedAudioFileException;
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.Main;
-
-/**
- * Creates and controls a separate audio player thread.
- *
- * @author David Earl &lt;david@frankieandshadow.com&gt;
- * @since 547
- */
-public final class AudioPlayer extends Thread {
-
-    private static volatile AudioPlayer audioPlayer;
-
-    private enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }
-
-    private enum Command { PLAY, PAUSE }
-
-    private enum Result { WAITING, OK, FAILED }
-
-    private State state;
-    private URL playingUrl;
-    private final double leadIn; // seconds
-    private final double calibration; // ratio of purported duration of samples to true duration
-    private double position; // seconds
-    private double bytesPerSecond;
-    private static long chunk = 4000; /* bytes */
-    private double speed = 1.0;
-
-    /**
-     * Passes information from the control thread to the playing thread
-     */
-    private class Execute {
-        private Command command;
-        private Result result;
-        private Exception exception;
-        private URL url;
-        private double offset; // seconds
-        private double speed; // ratio
-
-        /*
-         * Called to execute the commands in the other thread
-         */
-        protected void play(URL url, double offset, double speed) throws InterruptedException, IOException {
-            this.url = url;
-            this.offset = offset;
-            this.speed = speed;
-            command = Command.PLAY;
-            result = Result.WAITING;
-            send();
-        }
-
-        protected void pause() throws InterruptedException, IOException {
-            command = Command.PAUSE;
-            send();
-        }
-
-        private void send() throws InterruptedException, IOException {
-            result = Result.WAITING;
-            interrupt();
-            while (result == Result.WAITING) {
-                sleep(10);
-            }
-            if (result == Result.FAILED)
-                throw new IOException(exception);
-        }
-
-        private void possiblyInterrupt() throws InterruptedException {
-            if (interrupted() || result == Result.WAITING)
-                throw new InterruptedException();
-        }
-
-        protected void failed(Exception e) {
-            exception = e;
-            result = Result.FAILED;
-            state = State.NOTPLAYING;
-        }
-
-        protected void ok(State newState) {
-            result = Result.OK;
-            state = newState;
-        }
-
-        protected double offset() {
-            return offset;
-        }
-
-        protected double speed() {
-            return speed;
-        }
-
-        protected URL url() {
-            return url;
-        }
-
-        protected Command command() {
-            return command;
-        }
-    }
-
-    private final Execute command;
-
-    /**
-     * Plays a WAV audio file from the beginning. See also the variant which doesn't
-     * start at the beginning of the stream
-     * @param url The resource to play, which must be a WAV file or stream
-     * @throws InterruptedException thread interrupted
-     * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
-     */
-    public static void play(URL url) throws InterruptedException, IOException {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        if (instance != null)
-            instance.command.play(url, 0.0, 1.0);
-    }
-
-    /**
-     * Plays a WAV audio file from a specified position.
-     * @param url The resource to play, which must be a WAV file or stream
-     * @param seconds The number of seconds into the audio to start playing
-     * @throws InterruptedException thread interrupted
-     * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
-     */
-    public static void play(URL url, double seconds) throws InterruptedException, IOException {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        if (instance != null)
-            instance.command.play(url, seconds, 1.0);
-    }
-
-    /**
-     * Plays a WAV audio file from a specified position at variable speed.
-     * @param url The resource to play, which must be a WAV file or stream
-     * @param seconds The number of seconds into the audio to start playing
-     * @param speed Rate at which audio playes (1.0 = real time, &gt; 1 is faster)
-     * @throws InterruptedException thread interrupted
-     * @throws IOException audio fault exception, e.g. can't open stream,  unhandleable audio format
-     */
-    public static void play(URL url, double seconds, double speed) throws InterruptedException, IOException {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        if (instance != null)
-            instance.command.play(url, seconds, speed);
-    }
-
-    /**
-     * Pauses the currently playing audio stream. Does nothing if nothing playing.
-     * @throws InterruptedException thread interrupted
-     * @throws IOException audio fault exception, e.g. can't open stream,  unhandleable audio format
-     */
-    public static void pause() throws InterruptedException, IOException {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        if (instance != null)
-            instance.command.pause();
-    }
-
-    /**
-     * To get the Url of the playing or recently played audio.
-     * @return url - could be null
-     */
-    public static URL url() {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        return instance == null ? null : instance.playingUrl;
-    }
-
-    /**
-     * Whether or not we are paused.
-     * @return boolean whether or not paused
-     */
-    public static boolean paused() {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        return instance == null ? false : (instance.state == State.PAUSED);
-    }
-
-    /**
-     * Whether or not we are playing.
-     * @return boolean whether or not playing
-     */
-    public static boolean playing() {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        return instance == null ? false : (instance.state == State.PLAYING);
-    }
-
-    /**
-     * How far we are through playing, in seconds.
-     * @return double seconds
-     */
-    public static double position() {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        return instance == null ? -1 : instance.position;
-    }
-
-    /**
-     * Speed at which we will play.
-     * @return double, speed multiplier
-     */
-    public static double speed() {
-        AudioPlayer instance = AudioPlayer.getInstance();
-        return instance == null ? -1 : instance.speed;
-    }
-
-    /**
-     * Returns the singleton object, and if this is the first time, creates it along with
-     * the thread to support audio
-     * @return the unique instance
-     */
-    private static AudioPlayer getInstance() {
-        if (audioPlayer != null)
-            return audioPlayer;
-        try {
-            audioPlayer = new AudioPlayer();
-            return audioPlayer;
-        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
-            Main.error(ex);
-            return null;
-        }
-    }
-
-    /**
-     * Resets the audio player.
-     */
-    public static void reset() {
-        if (audioPlayer != null) {
-            try {
-                pause();
-            } catch (InterruptedException | IOException e) {
-                Main.warn(e);
-            }
-            audioPlayer.playingUrl = null;
-        }
-    }
-
-    private AudioPlayer() {
-        state = State.INITIALIZING;
-        command = new Execute();
-        playingUrl = null;
-        leadIn = Main.pref.getDouble("audio.leadin", 1.0 /* default, seconds */);
-        calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
-        start();
-        while (state == State.INITIALIZING) {
-            yield();
-        }
-    }
-
-    /**
-     * Starts the thread to actually play the audio, per Thread interface
-     * Not to be used as public, though Thread interface doesn't allow it to be made private
-     */
-    @Override public void run() {
-        /* code running in separate thread */
-
-        playingUrl = null;
-        AudioInputStream audioInputStream = null;
-        SourceDataLine audioOutputLine = null;
-        AudioFormat audioFormat;
-        byte[] abData = new byte[(int) chunk];
-
-        for (;;) {
-            try {
-                switch (state) {
-                    case INITIALIZING:
-                        // we're ready to take interrupts
-                        state = State.NOTPLAYING;
-                        break;
-                    case NOTPLAYING:
-                    case PAUSED:
-                        sleep(200);
-                        break;
-                    case PLAYING:
-                        command.possiblyInterrupt();
-                        for (;;) {
-                            int nBytesRead = 0;
-                            if (audioInputStream != null) {
-                                nBytesRead = audioInputStream.read(abData, 0, abData.length);
-                                position += nBytesRead / bytesPerSecond;
-                            }
-                            command.possiblyInterrupt();
-                            if (nBytesRead < 0 || audioInputStream == null || audioOutputLine == null) {
-                                break;
-                            }
-                            audioOutputLine.write(abData, 0, nBytesRead); // => int nBytesWritten
-                            command.possiblyInterrupt();
-                        }
-                        // end of audio, clean up
-                        if (audioOutputLine != null) {
-                            audioOutputLine.drain();
-                            audioOutputLine.close();
-                        }
-                        audioOutputLine = null;
-                        Utils.close(audioInputStream);
-                        audioInputStream = null;
-                        playingUrl = null;
-                        state = State.NOTPLAYING;
-                        command.possiblyInterrupt();
-                        break;
-                    default: // Do nothing
-                }
-            } catch (InterruptedException e) {
-                interrupted(); // just in case we get an interrupt
-                State stateChange = state;
-                state = State.INTERRUPTED;
-                try {
-                    switch (command.command()) {
-                        case PLAY:
-                            double offset = command.offset();
-                            speed = command.speed();
-                            if (playingUrl != command.url() ||
-                                    stateChange != State.PAUSED ||
-                                    offset != 0) {
-                                if (audioInputStream != null) {
-                                    Utils.close(audioInputStream);
-                                }
-                                playingUrl = command.url();
-                                audioInputStream = AudioSystem.getAudioInputStream(playingUrl);
-                                audioFormat = audioInputStream.getFormat();
-                                long nBytesRead;
-                                position = 0.0;
-                                offset -= leadIn;
-                                double calibratedOffset = offset * calibration;
-                                bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
-                                * audioFormat.getFrameSize() /* bytes per frame */;
-                                if (speed * bytesPerSecond > 256_000.0) {
-                                    speed = 256_000 / bytesPerSecond;
-                                }
-                                if (calibratedOffset > 0.0) {
-                                    long bytesToSkip = (long) (calibratedOffset /* seconds (double) */ * bytesPerSecond);
-                                    // skip doesn't seem to want to skip big chunks, so reduce it to smaller ones
-                                    while (bytesToSkip > chunk) {
-                                        nBytesRead = audioInputStream.skip(chunk);
-                                        if (nBytesRead <= 0)
-                                            throw new IOException(tr("This is after the end of the recording"));
-                                        bytesToSkip -= nBytesRead;
-                                    }
-                                    while (bytesToSkip > 0) {
-                                        long skippedBytes = audioInputStream.skip(bytesToSkip);
-                                        bytesToSkip -= skippedBytes;
-                                        if (skippedBytes == 0) {
-                                            // Avoid inifinite loop
-                                            Main.warn("Unable to skip bytes from audio input stream");
-                                            bytesToSkip = 0;
-                                        }
-                                    }
-                                    position = offset;
-                                }
-                                if (audioOutputLine != null) {
-                                    audioOutputLine.close();
-                                }
-                                audioFormat = new AudioFormat(audioFormat.getEncoding(),
-                                        audioFormat.getSampleRate() * (float) (speed * calibration),
-                                        audioFormat.getSampleSizeInBits(),
-                                        audioFormat.getChannels(),
-                                        audioFormat.getFrameSize(),
-                                        audioFormat.getFrameRate() * (float) (speed * calibration),
-                                        audioFormat.isBigEndian());
-                                DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
-                                audioOutputLine = (SourceDataLine) AudioSystem.getLine(info);
-                                audioOutputLine.open(audioFormat);
-                                audioOutputLine.start();
-                            }
-                            stateChange = State.PLAYING;
-                            break;
-                        case PAUSE:
-                            stateChange = State.PAUSED;
-                            break;
-                        default: // Do nothing
-                    }
-                    command.ok(stateChange);
-                } catch (LineUnavailableException | IOException | UnsupportedAudioFileException |
-                        SecurityException | IllegalArgumentException startPlayingException) {
-                    Main.error(startPlayingException);
-                    command.failed(startPlayingException); // sets state
-                }
-            } catch (IOException e) {
-                state = State.NOTPLAYING;
-                Main.error(e);
-            }
-        }
-    }
-
-    /**
-     * Shows a popup audio error message for the given exception.
-     * @param ex The exception used as error reason. Cannot be {@code null}.
-     */
-    public static void audioMalfunction(Exception ex) {
-        String msg = ex.getMessage();
-        if (msg == null)
-            msg = tr("unspecified reason");
-        else
-            msg = tr(msg);
-        Main.error(msg);
-        if (!GraphicsEnvironment.isHeadless()) {
-            JOptionPane.showMessageDialog(Main.parent,
-                    "<html><p>" + msg + "</p></html>",
-                    tr("Error playing sound"), JOptionPane.ERROR_MESSAGE);
-        }
-    }
-}
Index: unk/src/org/openstreetmap/josm/tools/AudioUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/AudioUtil.java	(revision 12325)
+++ 	(revision )
@@ -1,47 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.tools;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioInputStream;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.UnsupportedAudioFileException;
-
-import org.openstreetmap.josm.Main;
-
-/**
- * Utils functions for audio.
- *
- * @author David Earl &lt;david@frankieandshadow.com&gt;
- * @since 1462
- */
-public final class AudioUtil {
-
-    private AudioUtil() {
-        // Hide default constructor for utils classes
-    }
-
-    /**
-     * Returns calibrated length of recording in seconds.
-     * @param wavFile the recording file (WAV format)
-     * @return the calibrated length of recording in seconds.
-     */
-    public static double getCalibratedDuration(File wavFile) {
-        try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
-                new URL("file:".concat(wavFile.getAbsolutePath())))) {
-            AudioFormat audioFormat = audioInputStream.getFormat();
-            long filesize = wavFile.length();
-            double bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
-                * audioFormat.getFrameSize() /* bytes per frame */;
-            double naturalLength = filesize / bytesPerSecond;
-            double calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
-            return naturalLength / calibration;
-        } catch (UnsupportedAudioFileException | IOException e) {
-            Main.debug(e);
-            return 0.0;
-        }
-    }
-}
