Index: trunk/src/org/openstreetmap/josm/actions/OpenAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/OpenAction.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/actions/OpenAction.java	(revision 572)
@@ -91,8 +91,10 @@
 			GpxLayer gpxLayer = new GpxLayer(r.data, fn);
 			Main.main.addLayer(gpxLayer);
-            MarkerLayer ml = new MarkerLayer(r.data, tr("Markers from {0}", fn), file, gpxLayer);
-            if (ml.data.size() > 0) {
-            	Main.main.addLayer(ml);
-            }
+			if (Main.pref.getBoolean("marker.makeautomarkers", true)) {
+				MarkerLayer ml = new MarkerLayer(r.data, tr("Markers from {0}", fn), file, gpxLayer);
+				if (ml.data.size() > 0) {
+					Main.main.addLayer(ml);
+				}
+			}
 
 		} else {
Index: trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java	(revision 572)
@@ -26,4 +26,5 @@
 			amount = 10.0;
 		}
+		this.putValue("help", "Action/Back");
 	}
 
Index: trunk/src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java	(revision 572)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/PlayHeadDragMode.java	(revision 572)
@@ -0,0 +1,117 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.actions.mapmode;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+import javax.swing.Timer;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.Border;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.SelectAction.Mode;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Singleton marker class to track position of audio.
+ * 
+ * @author david.earl
+ *
+ */
+public class PlayHeadDragMode extends MapMode {
+
+	private boolean dragging = false;
+	private Point mousePos = null;
+	private Point mouseStart = null;
+	private PlayHeadMarker playHeadMarker = null;
+	
+	public PlayHeadDragMode(PlayHeadMarker m) {
+		super("play head drag", "playheaddrag", "play head trag", 0, Main.map, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+		playHeadMarker = m;
+	}
+	
+	@Override public void enterMode() {
+		super.enterMode();
+		Main.map.mapView.addMouseListener(this);
+		Main.map.mapView.addMouseMotionListener(this);
+	}
+
+	@Override public void exitMode() {
+		super.exitMode();
+		Main.map.mapView.removeMouseListener(this);
+		Main.map.mapView.removeMouseMotionListener(this);
+	}
+
+	@Override public void mousePressed(MouseEvent ev) {
+		mouseStart = mousePos = ev.getPoint();
+	}
+
+	@Override public void mouseDragged(MouseEvent ev) {
+		if (mouseStart == null || mousePos == null) return;
+		if ((ev.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) return;
+		Point p = ev.getPoint();
+		if (p == null) return;
+		if (! dragging) {
+			if (p.distance(mouseStart) < 3) return;
+			playHeadMarker.startDrag();
+			dragging = true;
+		}
+		if (p.distance(mousePos) == 0) return;
+		playHeadMarker.drag(Main.map.mapView.getEastNorth(ev.getX(), ev.getY()));
+		mousePos = p;
+	}
+
+	@Override public void mouseReleased(MouseEvent ev) {
+		Point p = ev.getPoint();
+		mouseStart = null;
+		if (ev.getButton() != MouseEvent.BUTTON1 || p == null || ! dragging)
+			return;
+		boolean shift = (ev.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
+		EastNorth en = Main.map.mapView.getEastNorth(ev.getX(), ev.getY());
+		if (! shift) {
+			playHeadMarker.reposition(en);
+		} else {
+			playHeadMarker.synchronize(en);
+		}
+		mousePos = null;	
+		dragging = false;
+
+	/*
+ 		boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
+		boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
+	 */
+	}
+
+	@Override public String getModeHelpText() {
+		return tr("Drag play head and release near track to play audio from there; SHIFT+release to synchronize audio at that point.");
+	}
+}
Index: trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 572)
@@ -36,4 +36,6 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer.ModifiedChangedListener;
+import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
 
 /**
@@ -67,4 +69,8 @@
 	private ArrayList<Layer> layers = new ArrayList<Layer>();
 	/**
+	 * The play head marker: there is only one of these so it isn't in any specific layer
+	 */
+	public PlayHeadMarker playHeadMarker = null;
+	/**
 	 * Direct link to the edit layer (if any) in the layers list.
 	 */
@@ -132,5 +138,7 @@
 			});
 		}
-
+		if (layer instanceof MarkerLayer && playHeadMarker == null)
+			playHeadMarker = PlayHeadMarker.create();
+		
 		layers.add(layers.size(), layer);
 
@@ -211,4 +219,8 @@
 		if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
 			g.drawRect(x1, y1, x2-x1+1, y2-y1+1);
+		
+		if (playHeadMarker != null)
+			playHeadMarker.paint(g, this);
+
 		super.paint(g);
 	}
Index: trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 572)
@@ -24,4 +24,5 @@
 import java.net.URLConnection;
 import java.net.UnknownHostException;
+import java.util.Iterator;
 
 import javax.swing.AbstractAction;
@@ -160,7 +161,7 @@
 		});
 
-		JMenuItem applyAudio = new JMenuItem(tr("Make Sampled Audio Layer"), ImageProvider.get("applyaudio"));
-		applyAudio.putClientProperty("help", "Action/MakeSampledAudioLayer");
-		applyAudio.addActionListener(new ActionListener() {
+		JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
+		importAudio.putClientProperty("help", "ImportAudio");
+		importAudio.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
 				String dir = Main.pref.get("markers.lastaudiodirectory");
@@ -182,5 +183,5 @@
 				if (sel == null)
 					return;
-				applyAudio(sel);
+				importAudio(sel);
 				Main.map.repaint();
 			}
@@ -245,5 +246,5 @@
 				line,
 				tagimage,
-				applyAudio,
+				importAudio,
 				markersFromNamedTrackpoints,
 				new JMenuItem(new ConvertToDataLayerAction()),
@@ -458,5 +459,4 @@
 	}
 
-
 	public class ConvertToDataLayerAction extends AbstractAction {
 		public ConvertToDataLayerAction() {
@@ -485,54 +485,89 @@
 		}
 	}
-
+	
 	/**
-	 * 
-	 *
+	 * Makes a new marker layer derived from this GpxLayer containing at least one 
+	 * audio marker which the given audio file is associated with.
+	 * Markers are derived from the following
+	 * (a) explict waypoints in the GPX layer, or
+	 * (b) named trackpoints in the GPX layer, or
+	 * (c) (in future) voice recognised markers in the sound recording
+	 * (d) a single marker at the beginning of the track
+	 * @param wavFile : the file to be associated with the markers in the new marker layer
 	 */
-	private void applyAudio(File wavFile) {
+	private void importAudio(File wavFile) {
 		String uri = "file:".concat(wavFile.getAbsolutePath());
-	    double audioGapSecs = 15.0; 
-		try {
-			audioGapSecs = Double.parseDouble(Main.pref.get("marker.audiosampleminsecs", Double.toString(audioGapSecs)));
-		} catch (NumberFormatException e) {
-		}
-	    double audioGapMetres = 75.0; 
-		try {
-			audioGapMetres = Double.parseDouble(Main.pref.get("marker.audiosampleminmetres", Double.toString(audioGapMetres)));
-		} catch (NumberFormatException e) {
-		}
-		double audioGapRadians = (audioGapMetres / 40041455.0 /* circumference of Earth in metres */) * 2.0 * Math.PI;
-		double audioGapRadiansSquared = audioGapRadians * audioGapRadians;
-		double firstTime = -1.0;
-	    double prevOffset = - (audioGapSecs + 1.0); // first point always exceeds time difference
-	    WayPoint prevPoint = null;
-
-	    MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Sampled audio markers from {0}", name), associatedFile, me);
+	    MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
+
+	    // (a) try explicit waypoints - unless suppressed
+	    if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) && 
+	    	data.waypoints != null && 
+	    	! data.waypoints.isEmpty())
+	    {
+	    	double firstTime = -1.0;
+	    	for (WayPoint w : data.waypoints) {
+	    		if (firstTime < 0.0) firstTime = w.time;
+	    		double offset = w.time - firstTime;
+	    		String name = w.attr.containsKey("name") ? w.getString("name") :
+	    			w.attr.containsKey("desc") ? w.getString("desc") :
+	    			AudioMarker.inventName(offset);
+	    			AudioMarker am = AudioMarker.create(w.latlon, 
+						name, uri, ml, w.time, offset);
+				ml.data.add(am);	    		
+	    	}
+	    }
+
+	    // (b) use explicitly named track points, again unless suppressed
+	    if (ml.data.isEmpty() && 
+	    	Main.pref.getBoolean("marker.namedtrackpoints") && 
+	    	data.tracks != null && 
+	    	! data.tracks.isEmpty())
+	    {
+	    	double firstTime = -1.0;
+	    	for (GpxTrack track : data.tracks) {
+	    		for (Collection<WayPoint> seg : track.trackSegs) {
+	    			for (WayPoint w : seg) {
+	    				String name;
+	    				if (w.attr.containsKey("name"))
+	    					name = w.getString("name");
+	    				else if (w.attr.containsKey("desc"))
+	    					name = w.getString("desc");
+	    				else
+	    					continue;
+	    	    		if (firstTime < 0.0) firstTime = w.time;
+	    	    		double offset = w.time - firstTime;
+	    				AudioMarker am = AudioMarker.create(w.latlon, 
+	    						name, uri, ml, w.time, offset);
+	    				ml.data.add(am);	    		
+	    			}
+	    		}
+	    	}
+	    }
+
+	    // (c) analyse audio for spoken markers here, in due course
 	    
-		for (GpxTrack track : data.tracks) {
-			for (Collection<WayPoint> seg : track.trackSegs) {
-				for (WayPoint point : seg) {
-					double time = point.time;
-					if (firstTime < 0.0)
-						firstTime = time;
-					double offset = time - firstTime;
-					if (prevPoint == null ||
-						(offset - prevOffset > audioGapSecs &&
-						/* note: distance is misleading: it actually gives distance _squared_ */
-						point.eastNorth.distance(prevPoint.eastNorth) > audioGapRadiansSquared))
-					{
-						
-						AudioMarker am = AudioMarker.create(point.latlon, 
-								AudioMarker.inventName(offset), uri, ml, time, offset);
-						ml.data.add(am);
-						prevPoint = point;
-						prevOffset = offset;
-					}
-				}
-			}
-		}
-
-        if (ml.data.size() > 0) {
+	    // (d) simply add a single marker at the start of the track
+	    if (ml.data.isEmpty() && 
+	    	data.tracks != null && 
+		    ! data.tracks.isEmpty())
+		{
+	    	for (GpxTrack track : data.tracks) {
+	    		for (Collection<WayPoint> seg : track.trackSegs) {
+	    			for (WayPoint w : seg) {
+	    	    		AudioMarker am = AudioMarker.create(w.latlon, 
+	    						tr("start"), uri, ml, w.time, 0.0);
+	    				ml.data.add(am);	    		
+	    				break;
+	    			}
+	    			break;
+	    		}
+	    		break;
+	    	}
+		}
+	    
+        if (! ml.data.isEmpty()) {
         	Main.main.addLayer(ml);
+        } else {
+			JOptionPane.showMessageDialog(Main.parent, tr("Nothing available to associate audio with."));
         }
 	}
Index: trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java	(revision 572)
@@ -68,16 +68,13 @@
 		
 	/**
-	 * Starts playing the audio associated with the marker: used in response to pressing
-	 * the marker as well as indirectly 
-	 *
+	 * Starts playing the audio associated with the marker offset by the given amount 
+	 * @param after : seconds after marker where playing should start
 	 */
-	public void play() {
+	public void play(double after) {
 		try {
 			// first enable tracing the audio along the track
-			if (Main.pref.getBoolean("marker.traceaudio", true) && parentLayer != null) {
-				parentLayer.traceAudio();
-			}
+			Main.map.mapView.playHeadMarker.animate();
 
-			AudioPlayer.play(audioUrl, offset + syncOffset);
+			AudioPlayer.play(audioUrl, offset + syncOffset + after);
 			recentlyPlayedMarker = this;
 		} catch (Exception e) {
@@ -85,4 +82,11 @@
 		}
 	}
+
+	/**
+	 * Starts playing the audio associated with the marker: used in response to pressing
+	 * the marker as well as indirectly 
+	 *
+	 */
+	public void play() { play(0.0); }
 
 	public void adjustOffset(double adjustment) {
Index: trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java	(revision 572)
@@ -62,5 +62,5 @@
 public class Marker implements ActionListener {
 
-	public final EastNorth eastNorth;
+	public EastNorth eastNorth;
 	public final String text;
 	public final Icon symbol;
Index: trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java	(revision 572)
@@ -44,4 +44,5 @@
 import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
+import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -67,5 +68,6 @@
 	private boolean mousePressed = false;
 	public GpxLayer fromLayer = null;
-	private Rectangle audioTracer = null;
+	
+	/*
 	private Icon audioTracerIcon = null;
 	private EastNorth playheadPosition = null;
@@ -73,5 +75,5 @@
 	private static double audioAnimationInterval = 0.0; // seconds
 	private static double playheadTime = -1.0;
-
+	 */
 	public MarkerLayer(GpxData indata, String name, File associatedFile, GpxLayer fromLayer) {
 		
@@ -161,74 +163,4 @@
 			}
 		}
-
-		if (audioTracer != null) {
-			Point screen = Main.map.mapView.getPoint(playheadPosition);
-			audioTracer.setLocation(screen.x, screen.y);
-			audioTracerIcon.paintIcon(Main.map.mapView, g, screen.x, screen.y);
-		}
-	}
-
-	protected void traceAudio() {
-		if (timer == null) {
-			audioAnimationInterval = Double.parseDouble(Main.pref.get("marker.audioanimationinterval", "1")); //milliseconds
-			timer = new Timer((int)(audioAnimationInterval * 1000.0), new ActionListener() {
-				public void actionPerformed(ActionEvent e) {
-					timerAction();
-				}
-			});
-			timer.start();
-		}
-	}
-	
-	/**
-	 * callback for AudioPlayer when position changes 
-	 * @param position seconds into the audio stream
-	 */
-	public void timerAction() {
-		AudioMarker recentlyPlayedMarker = AudioMarker.recentlyPlayedMarker();
-		if (recentlyPlayedMarker == null)
-			return;
-		double audioTime = recentlyPlayedMarker.time + 
-			AudioPlayer.position() - 
-			recentlyPlayedMarker.offset -
-			recentlyPlayedMarker.syncOffset;
-		if (Math.abs(audioTime- playheadTime) < audioAnimationInterval)
-			return;
-		if (fromLayer == null)
-			return;
-		/* find the pair of track points for this position (adjusted by the syncOffset)
-		 * and interpolate between them 
-		 */
-		WayPoint w1 = null;
-		WayPoint w2 = null;
-
-		for (GpxTrack track : fromLayer.data.tracks) {
-			for (Collection<WayPoint> trackseg : track.trackSegs) {
-				for (Iterator<WayPoint> it = trackseg.iterator(); it.hasNext();) {
-					WayPoint w = it.next();
-					if (audioTime < w.time) {
-						w2 = w;
-						break;
-					}
-					w1 = w;
-				}
-				if (w2 != null) break;
-			}
-			if (w2 != null) break;
-		}
-		
-		if (w1 == null)
-			return;
-		playheadPosition = w2 == null ? 
-			w1.eastNorth : 
-			w1.eastNorth.interpolate(w2.eastNorth, 
-					(audioTime - w1.time)/(w2.time - w1.time));
-		
-		if (audioTracer == null) {
-			audioTracerIcon = ImageProvider.getIfAvailable("markers",Main.pref.get("marker.audiotracericon", "audio-tracer"));
-			audioTracer = new Rectangle(0, 0, audioTracerIcon.getIconWidth(), audioTracerIcon.getIconHeight());			
-		}
-		playheadTime = audioTime;
-		Main.map.mapView.repaint();
 	}
 
@@ -249,19 +181,4 @@
 		for (Marker mkr : data)
 			v.visit(mkr.eastNorth);
-	}
-
-	public void applyAudio(File wavFile) {
-		String uri = "file:".concat(wavFile.getAbsolutePath());
-		Collection<Marker> markers = new ArrayList<Marker>();
-	    for (Marker mkr : data) {
-	    	AudioMarker audioMarker = mkr.audioMarkerFromMarker(uri);
-	    	if (audioMarker == null) {
-	    		markers.add(mkr);
-	    	} else { 
-	            markers.add(audioMarker);
-	    	}
-	    }
-	    data.clear();
-	    data.addAll(markers);
 	}
 
@@ -293,36 +210,18 @@
 		});
 
-		JMenuItem applyaudio = new JMenuItem(tr("Apply Audio"), ImageProvider.get("applyaudio"));
-		applyaudio.putClientProperty("help", "Action/ApplyAudio");
-		applyaudio.addActionListener(new ActionListener(){
-			public void actionPerformed(ActionEvent e) {
-				String dir = Main.pref.get("markers.lastaudiodirectory");
-				JFileChooser fc = new JFileChooser(dir);
-				fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
-				fc.setAcceptAllFileFilterUsed(false);
-				fc.setFileFilter(new FileFilter(){
-					@Override public boolean accept(File f) {
-						return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
-					}
-					@Override public String getDescription() {
-						return tr("Wave Audio files (*.wav)");
-					}
-				});
-				fc.showOpenDialog(Main.parent);
-				File sel = fc.getSelectedFile();
-				if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
-					Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
-				if (sel == null)
-					return;
-				applyAudio(sel);
-				Main.map.repaint();
-			}
-		});
-
 		JMenuItem syncaudio = new JMenuItem(tr("Synchronize Audio"), ImageProvider.get("audio-sync"));
 		syncaudio.putClientProperty("help", "Action/SynchronizeAudio");
 		syncaudio.addActionListener(new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
-				adjustOffsetsOnAudioMarkers();
+				if (! AudioPlayer.paused()) {
+					JOptionPane.showMessageDialog(Main.parent,tr("You need to pause audio at the moment when you hear your synchronization cue."));
+					return;
+				}
+				AudioMarker recent = AudioMarker.recentlyPlayedMarker();
+				if (synchronizeAudioMarkers(recent)) {
+					JOptionPane.showMessageDialog(Main.parent, tr("Audio synchronized at point " + recent.text));
+				} else {
+					JOptionPane.showMessageDialog(Main.parent,tr("Unable to synchronize in layer being played."));
+				}
 			}
 		});
@@ -332,5 +231,13 @@
 		moveaudio.addActionListener(new ActionListener(){
 			public void actionPerformed(ActionEvent e) {
-				makeAudioMarkerAtPlayHead();
+				if (! AudioPlayer.paused()) {
+					JOptionPane.showMessageDialog(Main.parent,tr("You need to have paused audio at the point on the track where you want the marker."));
+					return;
+				}
+				PlayHeadMarker playHeadMarker = Main.map.mapView.playHeadMarker;
+				if (playHeadMarker == null)
+					return;
+				addAudioMarker(playHeadMarker.time, playHeadMarker.eastNorth);
+				Main.map.mapView.repaint();
 			}
 		});
@@ -344,5 +251,4 @@
 		components.add(new JSeparator());
 		components.add(syncaudio);
-		components.add(applyaudio);
 		if (Main.pref.getBoolean("marker.traceaudio", true)) {
 			components.add (moveaudio);
@@ -354,13 +260,6 @@
 	}
 
-	private void adjustOffsetsOnAudioMarkers() {
-		if (! AudioPlayer.paused()) {
-			JOptionPane.showMessageDialog(Main.parent,tr("You need to pause audio at the moment when you hear your synchronization cue."));
-			return;
-		}
-		Marker startMarker = AudioMarker.recentlyPlayedMarker();
-		boolean explicitMarker = true;
+	public boolean synchronizeAudioMarkers(AudioMarker startMarker) {
 		if (startMarker != null && ! data.contains(startMarker)) {
-			explicitMarker = false;
 			startMarker = null;
 		}
@@ -368,19 +267,17 @@
 			// find the first audioMarker in this layer
 			for (Marker m : data) {
-				if (m.getClass() == AudioMarker.class) {
-					startMarker = m;
+				if (m instanceof AudioMarker) {
+					startMarker = (AudioMarker) m;
 					break;
 				}
 			}
 		}
-		if (startMarker == null) {
-			// still no marker to work from - message?
-			JOptionPane.showMessageDialog(Main.parent,tr("No audio marker found in the layer to synchronize with."));
-			return;
-		}
+		if (startMarker == null)
+			return false;
+			
 		// apply adjustment to all subsequent audio markers in the layer
 		double adjustment = AudioPlayer.position() - startMarker.offset; // in seconds
 		boolean seenStart = false;
-		URL url = ((AudioMarker)startMarker).url();
+		URL url = startMarker.url();
 		for (Marker m : data) {
 			if (m == startMarker)
@@ -392,15 +289,8 @@
 			}
 		}
-		
-		JOptionPane.showMessageDialog(Main.parent, explicitMarker ? 
-			tr("Audio synchronized with most recently played marker and subsequent ones (that have the same sound track).") :
-			tr("Audio synchronized with audio markers in the layer (that have the same sound track as the first one)."));
-	}
-	
-	private void makeAudioMarkerAtPlayHead() {
-		if (! AudioPlayer.paused()) {
-			JOptionPane.showMessageDialog(Main.parent,tr("You need to pause audio at the point on the track where you want the marker."));
-			return;
-		}
+		return true;
+	}
+	
+	public AudioMarker addAudioMarker(double time, EastNorth en) {
 		// find first audio marker to get absolute start time
 		double offset = 0.0;
@@ -409,5 +299,5 @@
 			if (m.getClass() == AudioMarker.class) {
 				am = (AudioMarker)m;
-				offset = playheadTime - am.time;
+				offset = time - am.time;
 				break;
 			}
@@ -415,10 +305,10 @@
 		if (am == null) {
 			JOptionPane.showMessageDialog(Main.parent,tr("No existing audio markers in this layer to offset from."));
-			return;
+			return null;
 		}
 
 		// make our new marker
-		AudioMarker newAudioMarker = AudioMarker.create(Main.proj.eastNorth2latlon(playheadPosition), 
-			AudioMarker.inventName(offset), AudioPlayer.url().toString(), this, playheadTime, offset);
+		AudioMarker newAudioMarker = AudioMarker.create(Main.proj.eastNorth2latlon(en), 
+			AudioMarker.inventName(offset), AudioPlayer.url().toString(), this, time, offset);
 		
 		// insert it at the right place in a copy the collection
@@ -440,5 +330,4 @@
 				newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor				
 			newData.add(newAudioMarker); // insert at end
-			newAudioMarker = null;
 		}
 		
@@ -446,5 +335,5 @@
 		data.clear();
 		data.addAll(newData);
-		Main.map.mapView.repaint();
+		return newAudioMarker;
 	}
 	
Index: trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java	(revision 572)
+++ trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java	(revision 572)
@@ -0,0 +1,333 @@
+package org.openstreetmap.josm.gui.layer.markerlayer;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+import javax.swing.Timer;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.Border;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.actions.mapmode.PlayHeadDragMode;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.tools.AudioPlayer;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Singleton marker class to track position of audio.
+ * 
+ * @author David Earl<david@frankieandshadow.com>
+ *
+ */
+public class PlayHeadMarker extends Marker {
+
+	private Timer timer = null;
+	private double animationInterval = 0.0; // seconds
+	// private Rectangle audioTracer = null;
+	// private Icon audioTracerIcon = null;
+	static private PlayHeadMarker playHead = null;
+	private MapMode oldMode = null;
+	private EastNorth oldEastNorth;
+	private boolean enabled;
+	private boolean wasPlaying = false;
+	
+	public static PlayHeadMarker create() {
+		if (playHead == null) {
+			try {
+				playHead = new PlayHeadMarker();
+			} catch (Exception ex) {
+				return null;
+			}
+		}
+		return playHead;
+	}
+	
+	private PlayHeadMarker() {
+		super(new LatLon(0.0,0.0), "", 
+			  Main.pref.get("marker.audiotracericon", "audio-tracer"), 
+			  null, -1.0, 0.0);
+		enabled = Main.pref.getBoolean("marker.traceaudio", true);
+		if (! enabled) return;
+		Main.map.mapView.addMouseListener(new MouseAdapter() {
+			@Override public void mousePressed(MouseEvent ev) {
+				Point p = ev.getPoint();
+				if (ev.getButton() != MouseEvent.BUTTON1 || p == null)
+					return;
+				if (playHead.containsPoint(p)) {
+					/* when we get a click on the marker, we need to switch mode to avoid
+					 * getting confused with other drag operations (like select) */
+					oldMode = Main.map.mapMode;
+					oldEastNorth = eastNorth;
+					PlayHeadDragMode playHeadDragMode = new PlayHeadDragMode(playHead);
+					Main.map.selectMapMode(playHeadDragMode);
+					playHeadDragMode.mousePressed(ev);
+				}
+			}
+		});
+	}
+
+	@Override public boolean containsPoint(Point p) {
+		Point screen = Main.map.mapView.getPoint(eastNorth);
+		Rectangle r = new Rectangle(screen.x, screen.y, symbol.getIconWidth(), symbol.getIconHeight());
+		return r.contains(p);
+	}
+
+	/**
+	 * called back from drag mode to say when we started dragging for real 
+	 * (at least a short distance)
+	 */
+	public void startDrag() {
+		if (timer != null) 
+			timer.stop();
+		wasPlaying = AudioPlayer.playing();
+		if (wasPlaying) {
+			try { AudioPlayer.pause(); } 
+		    catch (Exception ex) { AudioPlayer.audioMalfunction(ex);}
+		}
+	}
+
+	/**
+	 * reinstate the old map mode after swuitching temporarily to do a play head drag 
+	 */
+	private void endDrag(boolean reset) {
+		if (reset)
+			eastNorth = oldEastNorth;
+		Main.map.selectMapMode(oldMode);
+		Main.map.mapView.repaint();
+		if (! wasPlaying) 
+			try { AudioPlayer.pause(); }
+			catch (Exception ex) { AudioPlayer.audioMalfunction(ex);}
+		timer.start();
+	}
+	
+	/**
+	 * apply the new position resulting from a drag in progress
+	 * @param en the new position in map terms 
+	 */
+	public void drag(EastNorth en) {
+		eastNorth = en;
+		Main.map.mapView.repaint();
+	}
+
+	/**
+	 * Find the closest track point within the pixelTolerance of the screen point pNear 
+	 * @param pNear : the point in screen coordinates near which to find a track point 
+	 * @param pixelTolerance : only accept the point if within this number of pixels of en
+	 * @return the nearest trackpoint or null if nothing nearby
+	 */
+	private WayPoint getClosestTrackPoint(Point pNear, double pixelTolerance) {
+		WayPoint cw = null;
+		AudioMarker recentlyPlayedMarker = AudioMarker.recentlyPlayedMarker();
+		if (recentlyPlayedMarker != null) {
+			/* Find the track point closest to letting go of the play head */ 
+			double minDistance = pixelTolerance;
+			GpxLayer trackLayer = recentlyPlayedMarker.parentLayer.fromLayer;
+			if (trackLayer.data.tracks != null) {
+				for (GpxTrack track : trackLayer.data.tracks) {
+					if (track.trackSegs != null) {
+						for (Collection<WayPoint> trackseg : track.trackSegs) {
+							for (Iterator<WayPoint> it = trackseg.iterator(); it.hasNext();) {
+								WayPoint w = it.next();
+								Point p = Main.map.mapView.getPoint(w.eastNorth);
+								double distance = p.distance(pNear);
+								if (distance <= minDistance) {
+									cw = w;
+									minDistance = distance;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		return cw;
+	}
+	
+	/**
+	 * reposition the play head at the point on the track nearest position given,
+	 * providing we are within reasonable distance from the track; otherwise reset to the
+	 * original position.
+	 * @param en the position to start looking from
+	 */
+	public void reposition(EastNorth en) {
+		eastNorth = en;
+		WayPoint cw = getClosestTrackPoint(Main.map.mapView.getPoint(en), 25.0);
+		AudioMarker ca = null; 
+		
+		/* Find the prior audio marker (there should always be one in the 
+		 * layer, even if it is only one at the start of the track) to 
+		 * offset the audio from */ 
+		if (cw != null) {
+			AudioMarker recent = AudioMarker.recentlyPlayedMarker(); 		
+			if (recent != null || recent.parentLayer != null) {
+				for (Marker m : recent.parentLayer.data) {
+					if (m instanceof AudioMarker) {
+						AudioMarker a = (AudioMarker) m;
+						if (a.time > cw.time)
+							break;
+						ca = a;
+					}
+				}
+			}
+		}
+
+		if (ca == null) {
+			/* Not close enough to track, or no audio marker found for some other reason */
+			JOptionPane.showMessageDialog(Main.parent, tr("You need to Drag the play head near to the GPX track whose associated sound track you were playing."));
+			endDrag(true);
+		} else {
+			eastNorth = cw.eastNorth;
+			ca.play(cw.time - ca.time);
+			endDrag(false);
+		}
+	}
+
+	/**
+	 * Synchronize the audio at the position where the play head was paused before 
+	 * dragging with the position on the track where it was dropped. 
+	 * If this is quite near an audio marker, we use that 
+	 * marker as the sync. location, otherwise we create a new marker at the 
+	 * trackpoint nearest the end point of the drag point to apply the 
+	 * sync to.
+	 * @param en : the EastNorth end point of the drag
+	 */
+	public void synchronize(EastNorth en) {
+		/* First, see if we dropped onto an existing audio marker in the layer being played */
+		Point startPoint = Main.map.mapView.getPoint(en);
+		AudioMarker ca = null; 
+		AudioMarker recent = AudioMarker.recentlyPlayedMarker(); 		
+		if (recent != null || recent.parentLayer != null) {
+			double closestAudioMarkerDistanceSquared = 1.0E100;
+			for (Marker m : AudioMarker.recentlyPlayedMarker().parentLayer.data) {
+				if (m instanceof AudioMarker) {
+					double distanceSquared = m.eastNorth.distance(en);
+					if (distanceSquared < closestAudioMarkerDistanceSquared) {
+						ca = (AudioMarker) m;
+						closestAudioMarkerDistanceSquared = distanceSquared;
+					}
+				}
+			}
+		}
+
+		/* We found the closest marker: did we actually hit it? */
+		if (ca != null && ! ca.containsPoint(startPoint)) ca = null;
+		
+		/* If we didn't hit an audio marker, we need to create one at the nearest track point */
+		if (ca == null) {
+			WayPoint cw = getClosestTrackPoint(startPoint, 10.0);
+			if (cw == null) {
+				JOptionPane.showMessageDialog(Main.parent, tr("You need to SHIFT-Drag the play head onto an audio marker or onto the track point where you want to synchronize."));
+				endDrag(true);
+				return;
+			}
+			ca = recent.parentLayer.addAudioMarker(cw.time, cw.eastNorth);
+		}
+
+		/* Actually do the synchronization */
+		if (recent.parentLayer.synchronizeAudioMarkers(ca)) {
+			JOptionPane.showMessageDialog(Main.parent, tr("Audio synchronized at point ") + ca.text);
+			eastNorth = ca.eastNorth;
+			endDrag(false);
+		} else {
+			JOptionPane.showMessageDialog(Main.parent,tr("Unable to synchronize in layer being played."));
+			endDrag(true);
+		}
+	}
+	
+	public void paint(Graphics g, MapView mv /*, boolean mousePressed */) {
+		if (time < 0.0) return;
+		Point screen = mv.getPoint(eastNorth);
+		// buttonRectangle.setLocation(screen.x+4, screen.y+2);
+		symbol.paintIcon(mv, g, screen.x, screen.y);
+	}
+
+	public void animate() {
+		if (! enabled) return;
+		if (timer == null) {
+			animationInterval = Double.parseDouble(Main.pref.get("marker.audioanimationinterval", "1")); //milliseconds
+			timer = new Timer((int)(animationInterval * 1000.0), new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					timerAction();
+				}
+			});
+			timer.setInitialDelay(0);
+		} else {
+			timer.stop();
+		}
+		timer.start();
+	}
+	
+	/**
+	 * callback for moving play head marker according to audio player position 
+	 */
+	public void timerAction() {
+		AudioMarker recentlyPlayedMarker = AudioMarker.recentlyPlayedMarker();
+		if (recentlyPlayedMarker == null)
+			return;
+		double audioTime = recentlyPlayedMarker.time + 
+			AudioPlayer.position() - 
+			recentlyPlayedMarker.offset -
+			recentlyPlayedMarker.syncOffset;
+		if (Math.abs(audioTime - time) < animationInterval)
+			return;
+		if (recentlyPlayedMarker.parentLayer == null) return;
+		GpxLayer trackLayer = recentlyPlayedMarker.parentLayer.fromLayer;
+		if (trackLayer == null)
+			return;
+		/* find the pair of track points for this position (adjusted by the syncOffset)
+		 * and interpolate between them 
+		 */
+		WayPoint w1 = null;
+		WayPoint w2 = null;
+
+		for (GpxTrack track : trackLayer.data.tracks) {
+			for (Collection<WayPoint> trackseg : track.trackSegs) {
+				for (Iterator<WayPoint> it = trackseg.iterator(); it.hasNext();) {
+					WayPoint w = it.next();
+					if (audioTime < w.time) {
+						w2 = w;
+						break;
+					}
+					w1 = w;
+				}
+				if (w2 != null) break;
+			}
+			if (w2 != null) break;
+		}
+		
+		if (w1 == null)
+			return;
+		eastNorth = w2 == null ? 
+			w1.eastNorth : 
+			w1.eastNorth.interpolate(w2.eastNorth, 
+					(audioTime - w1.time)/(w2.time - w1.time));
+		time = audioTime;
+		Main.map.mapView.repaint();
+	}
+}
Index: trunk/src/org/openstreetmap/josm/gui/preferences/AudioPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/AudioPreference.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/AudioPreference.java	(revision 572)
@@ -28,13 +28,10 @@
 public class AudioPreference implements PreferenceSetting {
 	private JCheckBox audioMenuVisible = new JCheckBox(tr("Display the Audio menu."));
-	/*
-	private JCheckBox audioToolbarVisible = new JCheckBox(tr("Display Audio control buttons on toolbar."));
-	*/
 	private JCheckBox markerButtonLabels = new JCheckBox(tr("Label audio (and image and web) markers."));
 	private JCheckBox markerAudioTraceVisible = new JCheckBox(tr("Display live audio trace."));
 	private JCheckBox markersNamedTrackpoints = new JCheckBox(tr("Create audio markers from named trackpoints."));
+	private JCheckBox makeAutoMarkers = new JCheckBox(tr("Create non-audio markers when reading GPX."));
+	private JCheckBox audioMarkersFromExplicitWaypoints = new JCheckBox(tr("Import audio uses explicit waypoints."));
 
-	private JTextField audioSampleMinSecs = new JTextField(8);
-	private JTextField audioSampleMinMetres = new JTextField(8);
 	private JTextField audioLeadIn = new JTextField(8);
 	private JTextField audioForwardBackAmount = new JTextField(8);
@@ -91,14 +88,28 @@
 		gui.audio.add(markersNamedTrackpoints, GBC.eol().insets(0,0,0,0));
 		
-		audioSampleMinSecs.setText(Main.pref.get("marker.audiosampleminsecs", "15"));
-		audioSampleMinSecs.setToolTipText(tr("Minimum time in seconds between audio samples when creating sampled audio markers from waypoints"));
-		gui.audio.add(new JLabel(tr("Min audio marker sample rate (seconds)")), GBC.std());
-		gui.audio.add(audioSampleMinSecs, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
-
-		audioSampleMinMetres.setText(Main.pref.get("marker.audiosampleminmetres", "75"));
-		audioSampleMinMetres.setToolTipText(tr("Minimum distance in metres between audio samples when creating sampled audio markers from waypoints"));
-		gui.audio.add(new JLabel(tr("Min audio marker sample rate (metres)")), GBC.std());
-		gui.audio.add(audioSampleMinMetres, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
-
+		// makeAutoMarkers
+		makeAutoMarkers.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				if (!makeAutoMarkers.isSelected())
+					makeAutoMarkers.setSelected(false);
+				makeAutoMarkers.setEnabled(makeAutoMarkers.isSelected());
+			}
+		});
+		makeAutoMarkers.setSelected(Main.pref.getBoolean("marker.makeautomarkers", true));
+		makeAutoMarkers.setToolTipText(tr("Automatically make a marker layer from any waypoints when opening a GPX layer."));
+		gui.audio.add(makeAutoMarkers, GBC.eol().insets(0,0,0,0));
+		
+		// audioMarkersFromExplicitWaypoints
+		audioMarkersFromExplicitWaypoints.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				if (!audioMarkersFromExplicitWaypoints.isSelected())
+					audioMarkersFromExplicitWaypoints.setSelected(false);
+				audioMarkersFromExplicitWaypoints.setEnabled(audioMarkersFromExplicitWaypoints.isSelected());
+			}
+		});
+		audioMarkersFromExplicitWaypoints.setSelected(Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true));
+		audioMarkersFromExplicitWaypoints.setToolTipText(tr("When importing audio, apply it to any waypoints in the GPX layer."));
+		gui.audio.add(audioMarkersFromExplicitWaypoints, GBC.eol().insets(0,0,0,0));
+		
 		audioForwardBackAmount.setText(Main.pref.get("audio.forwardbackamount", "10"));
 		audioForwardBackAmount.setToolTipText(tr("The number of seconds to jump forward or back when the relevant button is pressed"));
@@ -129,6 +140,6 @@
 		Main.pref.put("marker.buttonlabels", markerButtonLabels.isSelected());
 		Main.pref.put("marker.namedtrackpoints", markersNamedTrackpoints.isSelected());
-		Main.pref.put("marker.audiosampleminsecs", audioSampleMinSecs.getText());		
-		Main.pref.put("marker.audiosampleminmetres", audioSampleMinMetres.getText());		
+		Main.pref.put("marker.suppressautomarkers", makeAutoMarkers.isSelected());
+		Main.pref.put("marker.audiofromexplicitwaypoints", audioMarkersFromExplicitWaypoints.isSelected());
 		Main.pref.put("audio.forwardbackamount", audioForwardBackAmount.getText());		
 		Main.pref.put("audio.fastfwdmultiplier", audioFastForwardMultiplier.getText());		
Index: trunk/src/org/openstreetmap/josm/io/GpxReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/GpxReader.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/io/GpxReader.java	(revision 572)
@@ -241,10 +241,4 @@
 					currentState = states.pop();
 					currentTrackSeg.add(currentWayPoint);
-					if (Main.pref.getBoolean("marker.namedtrackpoints") && 
-						(currentWayPoint.attr.containsKey("name") || 
-							currentWayPoint.attr.containsKey("desc"))) 
-					{
-						currentData.waypoints.add(currentWayPoint);
-					}
 				} else if (qName.equals("wpt")) {
 					currentState = states.pop();
Index: trunk/src/org/openstreetmap/josm/tools/AudioPlayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/AudioPlayer.java	(revision 571)
+++ trunk/src/org/openstreetmap/josm/tools/AudioPlayer.java	(revision 572)
@@ -305,9 +305,9 @@
 								audioOutputLine.close();
 							audioFormat = new AudioFormat(audioFormat.getEncoding(), 
-										audioFormat.getSampleRate() * (float) speed, 
+										audioFormat.getSampleRate() * (float) (speed * calibration), 
 										audioFormat.getSampleSizeInBits(), 
 										audioFormat.getChannels(), 
 										audioFormat.getFrameSize(), 
-										audioFormat.getFrameRate() * (float) speed, 
+										audioFormat.getFrameRate() * (float) (speed * calibration), 
 										audioFormat.isBigEndian());
 							DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
