diff --git a/pom.xml b/pom.xml
index 86d1ae16..1e597d64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.pamguard</groupId>
   <artifactId>Pamguard</artifactId>
-  <version>2.02.16</version>
+  <version>2.02.16a</version>
   <name>Pamguard</name>
   <description>Pamguard using Maven to control dependencies</description>
 	<url>www.pamguard.org</url>
diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java
index 1f17d80c..62896aaf 100644
--- a/src/PamController/PamguardVersionInfo.java
+++ b/src/PamController/PamguardVersionInfo.java
@@ -31,12 +31,12 @@ public class PamguardVersionInfo {
 	 * Version number, major version.minorversion.sub-release.
 	 * Note: can't go higher than sub-release 'f'
 	 */
-	static public final String version = "2.02.16";
+	static public final String version = "2.02.16a";
 	
 	/**
 	 * Release date
 	 */
-	static public final String date = "20 February 2025";
+	static public final String date = "April 2025";
 	
 //	/**
 //	 * Release type - Beta or Core
diff --git a/src/PamModel/PamModel.java b/src/PamModel/PamModel.java
index 928a552c..4f4dd769 100644
--- a/src/PamModel/PamModel.java
+++ b/src/PamModel/PamModel.java
@@ -467,7 +467,7 @@ final public class PamModel implements PamSettings {
 		mi.setToolTipText("Record observer monitoring effort");
 		mi.setModulesMenuGroup(utilitiesGroup);
 //		mi.setHidden(SMRUEnable.isEnable() == false);
-		mi.setToolTipText("Enables an observer to enter their name and infomation about which displays are being monitored");
+		mi.setToolTipText("Enables an observer to enter their name and information about which displays are being monitored");
 		mi.setMaxNumber(1);
 
 		mi = PamModuleInfo.registerControlledUnit(BackupManager.class.getName(), BackupManager.defaultName);
@@ -708,7 +708,7 @@ final public class PamModel implements PamSettings {
 
 		mi = PamModuleInfo.registerControlledUnit("envelopeTracer.EnvelopeControl", "Envelope Tracing");
 		mi.addDependency(new PamDependency(RawDataUnit.class, "Acquisition.AcquisitionControl"));	
-		mi.setToolTipText("");
+		mi.setToolTipText("Traces the envelope of audio data and outputs it as a new waveform");
 		mi.setModulesMenuGroup(processingGroup);
 		mi.setModulesMenuGroup(processingGroup);
 		mi.setHelpPoint("sound_processing/EnvelopeTrace/Docs/EnvelopeOverview.html");
diff --git a/src/PamUtils/LittleEndianDataInputStream.java b/src/PamUtils/LittleEndianDataInputStream.java
new file mode 100644
index 00000000..297ac7fa
--- /dev/null
+++ b/src/PamUtils/LittleEndianDataInputStream.java
@@ -0,0 +1,167 @@
+package PamUtils;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+/**
+ * Copied from https://www.peterfranza.com/2008/09/26/little-endian-input-stream/
+ * @author dg50
+ *
+ */
+public class LittleEndianDataInputStream extends InputStream implements DataInput {
+	 
+		public LittleEndianDataInputStream(InputStream in) {
+			this.in = in;
+			this.d = new DataInputStream(in);
+			w = new byte[8];
+		}
+	 
+		public int available() throws IOException {
+			return d.available();
+		}
+	 
+	 
+		public final short readShort() throws IOException
+		{
+			d.readFully(w, 0, 2);
+			return (short)(
+					(w[1]&0xff) << 8 |
+					(w[0]&0xff));
+		}
+	 
+		/**
+		 * Note, returns int even though it reads a short.
+		 */
+		 public final int readUnsignedShort() throws IOException
+		 {
+			 d.readFully(w, 0, 2);
+			 return (
+					 (w[1]&0xff) << 8 |
+					 (w[0]&0xff));
+		 }
+	 
+		 /**
+		  * like DataInputStream.readChar except little endian.
+		  */
+		 public final char readChar() throws IOException
+		 {
+			 d.readFully(w, 0, 2);
+			 return (char) (
+					 (w[1]&0xff) << 8 |
+					 (w[0]&0xff));
+		 }
+	 
+		 /**
+		  * like DataInputStream.readInt except little endian.
+		  */
+		 public final int readInt() throws IOException
+		 {
+			 d.readFully(w, 0, 4);
+			 return
+			 (w[3])      << 24 |
+			 (w[2]&0xff) << 16 |
+			 (w[1]&0xff) <<  8 |
+			 (w[0]&0xff);
+		 }
+
+		 /**
+		  * like DataInputStream.readUnsignedInt except little endian.
+		  */
+		 public final long readUnsignedInt() throws IOException
+		 {
+			 int v = readInt();
+			 return Integer.toUnsignedLong(v);
+//			 ByteArray.to
+//			 return
+//			 (w[3])      << 24 |
+//			 (w[2]&0xff) << 16 |
+//			 (w[1]&0xff) <<  8 |
+//			 (w[0]&0xff);
+			 
+		 }
+		 /**
+		  * like DataInputStream.readLong except little endian.
+		  */
+		 public final long readLong() throws IOException
+		 {
+			 d.readFully(w, 0, 8);
+			 return
+			 (long)(w[7])      << 56 | 
+			 (long)(w[6]&0xff) << 48 |
+			 (long)(w[5]&0xff) << 40 |
+			 (long)(w[4]&0xff) << 32 |
+			 (long)(w[3]&0xff) << 24 |
+			 (long)(w[2]&0xff) << 16 |
+			 (long)(w[1]&0xff) <<  8 |
+			 (long)(w[0]&0xff);
+		 }
+	 
+		 public final float readFloat() throws IOException {
+			 // still need to byteswap
+			 return Float.intBitsToFloat(readInt());
+		 }
+	 
+		 public final double readDouble() throws IOException {
+			 // still need to byteswap
+			 return Double.longBitsToDouble(readLong());
+		 }
+	 
+		 public final int read(byte b[], int off, int len) throws IOException {
+			 return in.read(b, off, len);
+		 }
+	 
+		 public final void readFully(byte b[]) throws IOException {
+			 d.readFully(b, 0, b.length);
+		 }
+	 
+		 public final void readFully(byte b[], int off, int len) throws IOException {
+			 d.readFully(b, off, len);
+		 }
+	 
+		 public final int skipBytes(int n) throws IOException {
+			 return d.skipBytes(n);
+		 }
+		 
+		 public final long skip(long n) throws IOException {
+			 return d.skipBytes((int) n);
+		 }
+	 
+		 public final boolean readBoolean() throws IOException {
+			 return d.readBoolean();
+		 }
+	 
+		 public final byte readByte() throws IOException {
+			 return d.readByte();
+		 }
+	 
+		 public int read() throws IOException {
+			 return in.read();
+		 }
+	 
+		 public final int readUnsignedByte() throws IOException {
+			 return d.readUnsignedByte();
+		 }
+	 
+		 @Deprecated
+		 public final String readLine() throws IOException {
+			 return d.readLine();
+		 }
+	 
+		 public final String readUTF() throws IOException {
+			 return d.readUTF();
+		 }
+	 
+		 public final void close() throws IOException {
+			 d.close();
+		 }
+	 
+		 private DataInputStream d; // to get at high level readFully methods of
+		 // DataInputStream
+		 private InputStream in; // to get at the low-level read methods of
+		 // InputStream
+		 private byte w[]; // work array for buffering input
+
+}
diff --git a/src/PamView/PamGui.java b/src/PamView/PamGui.java
index bf746071..48b0aabb 100644
--- a/src/PamView/PamGui.java
+++ b/src/PamView/PamGui.java
@@ -641,15 +641,18 @@ public class PamGui extends PamView implements WindowListener, PamSettings {
 
 		menuItem = new JMenuItem("Module Ordering ...");
 		menuItem.addActionListener(new menuModuleOrder());
+		menuItem.setToolTipText("Change the order of modules in the PAMGuard configuration");
 		orderModulesEnabler.addMenuItem(menuItem);
 		fileMenu.add(menuItem);
 
 		menuItem = new JMenuItem("Show Object List ...");
 		menuItem.addActionListener(new menuShowObjectList());
+		menuItem.setToolTipText("Show a list of data and detections currnetly held in memory for each module");
 		fileMenu.add(menuItem);
 
 		menuItem = new JMenuItem("Show Data Model ...");
 		menuItem.addActionListener(new menuShowObjectDiagram());
+		menuItem.setToolTipText("Show a graphical representation of modules and their interconnections");
 		fileMenu.add(menuItem);
 
 		if (!isViewer) {
diff --git a/src/PamView/dialog/PamDialog.java b/src/PamView/dialog/PamDialog.java
index 95fcba0f..44e4122f 100644
--- a/src/PamView/dialog/PamDialog.java
+++ b/src/PamView/dialog/PamDialog.java
@@ -620,6 +620,13 @@ abstract public class PamDialog extends JDialog {
 		return showWarning(warningTitle, warningText);
 	}
 
+	/**
+	 * Display a warning text. 
+	 * @param owner
+	 * @param warningTitle
+	 * @param warningText
+	 * @return
+	 */
 	public static boolean showWarning(Window owner, String warningTitle, String warningText) {
 		JOptionPane.showMessageDialog(owner, warningText, warningTitle, JOptionPane.ERROR_MESSAGE);
 		return false;
diff --git a/src/PamView/dialog/SourcePanel.java b/src/PamView/dialog/SourcePanel.java
index 7b6087db..90d1770b 100644
--- a/src/PamView/dialog/SourcePanel.java
+++ b/src/PamView/dialog/SourcePanel.java
@@ -1,7 +1,6 @@
 package PamView.dialog;
 
 import java.awt.BorderLayout;
-import java.awt.Dimension;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Window;
@@ -16,20 +15,14 @@ import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
-import javax.swing.JScrollPane;
 import javax.swing.border.TitledBorder;
 
-
-
-
-
 import PamController.PamController;
 import PamDetection.LocalisationInfo;
 import PamUtils.PamUtils;
 import PamguardMVC.PamConstants;
 import PamguardMVC.PamDataBlock;
 import PamguardMVC.PamDataUnit;
-import PamguardMVC.PamProcess;
 
 /**
  * Standard panel for dialogs that shows a list of
diff --git a/src/clickDetector/ClickDetector.java b/src/clickDetector/ClickDetector.java
index 3ce20376..bb14fef2 100644
--- a/src/clickDetector/ClickDetector.java
+++ b/src/clickDetector/ClickDetector.java
@@ -143,7 +143,7 @@ public class ClickDetector extends PamProcess {
 
 	private PamDataBlock<TriggerLevelDataUnit> triggerDataBlock;
 
-	private PamRawDataBlock doubleFilteredData;
+//	private PamRawDataBlock doubleFilteredData;
 
 	// protected PamDataBlock<ClickDetection> trackedClicks;
 	private PamDataBlock trackedClicks;
@@ -730,12 +730,12 @@ public class ClickDetector extends PamProcess {
 		// }
 
 		if ((newRawData.getChannelBitmap() & clickControl.clickParameters.getChannelBitmap()) == 0)
-			return;
+			return; // not a channel we're interested in
 		//
 		// if (obs == filteredDataBlock || obs == doubleFilteredData)
 		// return;
 
-		clickControl.newRawData(obs, newData);
+		clickControl.newRawData(obs, newData); // does nothing
 
 		// see if it's time to start a new file
 		// only do this here if it's not multithread
@@ -1496,7 +1496,9 @@ public class ClickDetector extends PamProcess {
 			int keepMillis = (int) (relSamplesToMilliseconds(requiredKeepSamples) * 2);
 			// int keepSeconds = Math.max(1, (int)
 			// relSamplesToMilliseconds(requiredKeepSamples)/1000);
-			keepMillis = Math.max(1000, keepMillis);
+			// add an extra second on 2025-04-01 to try to avoid null clicks. 
+			
+			keepMillis = Math.max(1000, keepMillis) + 1000;
 			// filteredDataBlock.setNaturalLifetime(keepSeconds);
 			finalDataSource = filteredDataBlock;
 			finalDataSource.setNaturalLifetimeMillis(keepMillis);
@@ -1642,7 +1644,7 @@ public class ClickDetector extends PamProcess {
 			/*
 			 * Waveform data ends up pointing either to the raw data, or the output of the
 			 * fist filter if there is one. new wavefformData is created every time (or
-			 * recycled from the data block) since we may beed to go back a while to find
+			 * recycled from the data block) since we may need to go back a while to find
 			 * data from a previous block
 			 */
 			double[][] waveformData = new double[nChannels][];
diff --git a/src/ravendata/RavenControl.java b/src/ravendata/RavenControl.java
index 79a2be15..4b817169 100644
--- a/src/ravendata/RavenControl.java
+++ b/src/ravendata/RavenControl.java
@@ -131,4 +131,11 @@ public class RavenControl extends PamControlledUnit implements PamSettings {
 		return true;
 	}
 
+	/**
+	 * @return the ravenParameters
+	 */
+	public RavenParameters getRavenParameters() {
+		return ravenParameters;
+	}
+
 }
diff --git a/src/ravendata/RavenParameters.java b/src/ravendata/RavenParameters.java
index 93792833..9e0d09e0 100644
--- a/src/ravendata/RavenParameters.java
+++ b/src/ravendata/RavenParameters.java
@@ -9,6 +9,8 @@ public class RavenParameters implements Serializable, Cloneable {
 
 	public String importFile;
 	
+	public double timeOffsetSeconds = 0;
+	
 	private ArrayList<RavenColumnInfo> extraColumns;
 
 	public ArrayList<RavenColumnInfo> getExtraColumns() {
diff --git a/src/ravendata/RavenProcess.java b/src/ravendata/RavenProcess.java
index 196116a4..a611fb81 100644
--- a/src/ravendata/RavenProcess.java
+++ b/src/ravendata/RavenProcess.java
@@ -92,7 +92,7 @@ public class RavenProcess extends PamProcess {
 		 * this as an option in future releases. 
 		 * Offset of 2843100 needed for mn23_055a tag data.
 		 */
-		long offsetMillis = 0;//2843100;
+		long offsetMillis = (long) (ravenControl.getRavenParameters().timeOffsetSeconds * 1000.);
 		
 		RavenDataRow prevRow = null;
 		for (RavenDataRow ravenRow : ravenData) {
diff --git a/src/ravendata/swing/RavenImportDialog.java b/src/ravendata/swing/RavenImportDialog.java
index af82e452..6f001899 100644
--- a/src/ravendata/swing/RavenImportDialog.java
+++ b/src/ravendata/swing/RavenImportDialog.java
@@ -10,6 +10,7 @@ import java.io.File;
 
 import javax.swing.JButton;
 import javax.swing.JFileChooser;
+import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTextField;
 import javax.swing.border.TitledBorder;
@@ -30,6 +31,7 @@ public class RavenImportDialog extends PamDialog {
 	
 	private JButton chooseButton;
 	
+	private JTextField timeOffset;
 
 
 	private RavenImportDialog(Window parentFrame) {
@@ -40,10 +42,30 @@ public class RavenImportDialog extends PamDialog {
 		ravenFile = new JTextField(80);
 		ravenFile.setEditable(false);
 		chooseButton = new JButton("Select ...");
+		
 		c.gridwidth = 2;
+		c.gridx = c.gridy = 0;
 		mainPanel.add(ravenFile, c);
+		
+		JPanel p2 = new JPanel(new GridBagLayout());
+		GridBagConstraints c2 = new PamGridBagContraints();
+		c2.gridx = 1;
+		c2.gridwidth = 1;
+		p2.add(chooseButton, c2);
+		
+		c2.gridx = 0;
+		c2.gridy++;
+		c2.gridwidth = 1;
+		p2.add(new JLabel("Time offset (s) ", JLabel.RIGHT), c2);
+		c2.gridx++;
+		p2.add(timeOffset = new JTextField(7), c2);
+		String tip = "Added to data as it's read from file";
+		timeOffset.setToolTipText(tip);
+		
+		c.gridwidth = 2;
 		c.gridy++;
-		mainPanel.add(new PamAlignmentPanel(chooseButton, BorderLayout.EAST), c);
+		mainPanel.add(new PamAlignmentPanel(p2, BorderLayout.EAST), c);
+		
 		
 		chooseButton.addActionListener(new ActionListener() {
 			@Override
@@ -80,6 +102,7 @@ public class RavenImportDialog extends PamDialog {
 	private void setParams(RavenParameters ravenParameters) {
 		this.ravenParameters = ravenParameters;
 		ravenFile.setText(ravenParameters.importFile);
+		timeOffset.setText(String.format("%5.3f", ravenParameters.timeOffsetSeconds));
 	}
 
 	@Override
@@ -94,6 +117,12 @@ public class RavenImportDialog extends PamDialog {
 			return showWarning(str);
 		}
 		ravenParameters.importFile = fn;
+		try {
+			ravenParameters.timeOffsetSeconds = Double.valueOf(timeOffset.getText());
+		}
+		catch (NumberFormatException e) {
+			return showWarning("Invalid time offset value. Must be a number");
+		}
 		return true;
 	}
 
diff --git a/src/wavFiles/WavHeader.java b/src/wavFiles/WavHeader.java
index 2d0a69c8..d0bfbb85 100644
--- a/src/wavFiles/WavHeader.java
+++ b/src/wavFiles/WavHeader.java
@@ -6,6 +6,8 @@ import java.util.ArrayList;
 import javax.sound.sampled.AudioFormat;
 
 import clickDetector.WindowsFile;
+import wavFiles.xwav.HarpHeader;
+import wavFiles.xwav.XWavException;
 
 public class WavHeader {
 
@@ -110,6 +112,18 @@ public class WavHeader {
 					windowsWavFile.seek(fmtEnd);
 					//			break;
 				}
+				else if (testString.equals("harp")) {
+					chunkSize = windowsWavFile.readWinInt();
+					headChunk = new byte[chunkSize];
+					windowsWavFile.read(headChunk);
+					try {
+						HarpHeader.readHarpHeader(headChunk);
+					} catch (XWavException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+//					wavHeadChunks.add(new WavHeadChunk(testString, headChunk));
+				}
 				else {
 					/*
 					 * As an example, SCRIPPS HARP .x.wav files have a chunk 
diff --git a/src/wavFiles/xwav/HarpHeader.java b/src/wavFiles/xwav/HarpHeader.java
new file mode 100644
index 00000000..433561ed
--- /dev/null
+++ b/src/wavFiles/xwav/HarpHeader.java
@@ -0,0 +1,112 @@
+package wavFiles.xwav;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Calendar;
+
+import PamUtils.LittleEndianDataInputStream;
+import PamUtils.PamCalendar;
+
+public class HarpHeader {
+
+	private HarpHeader() {
+		// TODO Auto-generated constructor stub
+	}
+	
+	/**
+	 * Unpack harp data junk from a xwav file. 
+	 * @param chunkData
+	 * @return
+	 */
+	public static HarpHeader readHarpHeader(byte[] chunkData) throws XWavException {
+		/*
+		 * Based on matlab code found at https://github.com/MarineBioAcousticsRC/Wav2XWav/blob/main/wrxwavhdX.m
+		 */
+		LittleEndianDataInputStream dis = new LittleEndianDataInputStream(new ByteArrayInputStream(chunkData));
+//		new LittleEnd
+		try {
+			int harpSize = chunkData.length;
+			int xhdVersion = dis.readUnsignedByte();
+			String firmwareVersion = readString(dis, 10);
+			String insId = readString(dis, 4);
+			String site = readString(dis, 4);
+			String experiment = readString(dis, 8);// could be 8 in example  
+			int diskSequenceNumber = dis.readUnsignedByte();
+			String diskSerialNumber = readString(dis, 8);
+			int numRF = dis.readUnsignedShort();
+			int longitude = dis.readInt(); // defo written as integers. guessing float*1e5. 
+			int latitude = dis.readInt();
+//			float longitude = dis.readFloat();
+//			float latitude = dis.readFloat();
+			int depth = dis.readShort();
+			// skip 8. 
+			dis.skip(8);
+			/*
+			 *  then read numRF chunks, each of which is 32 bytes. In this example, we
+			 *  have harpSize = 29752, so expecting about (29752-50)/32
+			 */
+			long lastT = 0;
+			for (int iRF = 0; iRF < numRF; iRF++) {
+				// time is from datevec, so it's year, month ... second in the first six
+				int[] dateVec = new int[7];
+				for (int i = 0; i < 6; i++) {
+					dateVec[i] = dis.readUnsignedByte();
+				}
+				dateVec[6] = dis.readUnsignedShort(); // number of millis.
+				long byteLoc = dis.readUnsignedInt();
+				long byteLength = dis.readUnsignedInt();
+				long writeLength = dis.readUnsignedInt();
+				long sampleRate = dis.readUnsignedInt();
+				int gain = dis.readUnsignedByte();
+				dis.skip(7);
+				long tMillis = dateVec2Millis(dateVec);
+//				if (lastT != 0) {
+//					System.out.printf("%s length %d = %3.3fs, step = %dms\n", PamCalendar.formatDBDateTime(tMillis, true), byteLength, 
+//							(double) byteLength / (double) sampleRate / 2., tMillis-lastT);
+//				}
+//				else {
+//					System.out.printf("%s length %d = %3.3fs\n", PamCalendar.formatDBDateTime(tMillis, true), byteLength, 
+//							(double) byteLength / (double) sampleRate / 2.);
+//				}
+				lastT = tMillis;
+			}
+			
+		} catch (IOException e) {
+			throw new XWavException(e.getMessage());
+		}
+		
+		
+		return null;
+	}
+	
+	/**
+	 * Convert datevec read from file to Java millis. 
+	 * @param dateVec
+	 */
+	private static long dateVec2Millis(int[] dateVec) {
+		// format is yy, mm, dd, hh, mm, ss, ms as int values. 
+		Calendar c =  Calendar.getInstance();
+		c.setTimeZone(PamCalendar.defaultTimeZone);
+		c.clear();
+		int yy = dateVec[0];
+		if (yy < 90) {
+			yy += 2000;
+		}
+		c.set(yy, dateVec[1]-1, dateVec[2], dateVec[3], dateVec[4], dateVec[5]);
+		long millis = c.getTimeInMillis() + dateVec[6];
+		return millis;
+	}
+	
+	private static String readString(LittleEndianDataInputStream dis, int bytes) throws XWavException {
+		byte[] data;
+		try {
+			data = dis.readNBytes(bytes);
+			String str = new String(data);
+			return str;
+		} catch (IOException e) {
+			throw new XWavException(e.getMessage());
+		}
+	}
+
+}
diff --git a/src/wavFiles/xwav/XWavException.java b/src/wavFiles/xwav/XWavException.java
new file mode 100644
index 00000000..7eb6b470
--- /dev/null
+++ b/src/wavFiles/xwav/XWavException.java
@@ -0,0 +1,34 @@
+package wavFiles.xwav;
+
+public class XWavException extends Exception {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public XWavException() {
+		// TODO Auto-generated constructor stub
+	}
+
+	public XWavException(String message) {
+		super(message);
+		// TODO Auto-generated constructor stub
+	}
+
+	public XWavException(Throwable cause) {
+		super(cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	public XWavException(String message, Throwable cause) {
+		super(message, cause);
+		// TODO Auto-generated constructor stub
+	}
+
+	public XWavException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+		// TODO Auto-generated constructor stub
+	}
+
+}