mirror of
https://github.com/PAMGuard/PAMGuard.git
synced 2024-11-22 07:02:29 +00:00
Working SUD click extractor
Seems to be mostly working and can generate PAMGuard click files automatically when processing SUD files using the soundtrap click detector
This commit is contained in:
parent
61dd6ef4d1
commit
87cd2cd41f
@ -38,6 +38,9 @@ import javax.swing.JMenuItem;
|
|||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
|
|
||||||
|
import org.pamguard.x3.sud.Chunk;
|
||||||
|
import org.pamguard.x3.sud.SudFileListener;
|
||||||
|
|
||||||
import dataGram.DatagramManager;
|
import dataGram.DatagramManager;
|
||||||
import dataMap.OfflineDataMapPoint;
|
import dataMap.OfflineDataMapPoint;
|
||||||
import dataMap.filemaps.OfflineFileServer;
|
import dataMap.filemaps.OfflineFileServer;
|
||||||
@ -49,12 +52,12 @@ import simulatedAcquisition.SimProcess;
|
|||||||
import asiojni.ASIOSoundSystem;
|
import asiojni.ASIOSoundSystem;
|
||||||
import asiojni.NewAsioSoundSystem;
|
import asiojni.NewAsioSoundSystem;
|
||||||
import nidaqdev.NIDAQProcess;
|
import nidaqdev.NIDAQProcess;
|
||||||
import nidaqdev.networkdaq.NINetworkDaq;
|
|
||||||
import Acquisition.filedate.FileDate;
|
import Acquisition.filedate.FileDate;
|
||||||
import Acquisition.filedate.StandardFileDate;
|
import Acquisition.filedate.StandardFileDate;
|
||||||
import Acquisition.layoutFX.AquisitionGUIFX;
|
import Acquisition.layoutFX.AquisitionGUIFX;
|
||||||
import Acquisition.offlineFuncs.OfflineWavFileServer;
|
import Acquisition.offlineFuncs.OfflineWavFileServer;
|
||||||
import Acquisition.rona.RonaOfflineFileServer;
|
import Acquisition.rona.RonaOfflineFileServer;
|
||||||
|
import Acquisition.sud.SUDNotificationManager;
|
||||||
import Array.ArrayManager;
|
import Array.ArrayManager;
|
||||||
import Array.PamArray;
|
import Array.PamArray;
|
||||||
import Array.Preamplifier;
|
import Array.Preamplifier;
|
||||||
@ -133,6 +136,7 @@ public class AcquisitionControl extends PamControlledUnit implements PamSettings
|
|||||||
*/
|
*/
|
||||||
private AquisitionGUIFX aquisitionGUIFX;
|
private AquisitionGUIFX aquisitionGUIFX;
|
||||||
|
|
||||||
|
private SUDNotificationManager sudNotificationManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main control unit for audio data acquisition.
|
* Main control unit for audio data acquisition.
|
||||||
@ -834,4 +838,15 @@ public class AcquisitionControl extends PamControlledUnit implements PamSettings
|
|||||||
return getDaqProcess().getRawDataBlock().getSummaryString(clear);
|
return getDaqProcess().getRawDataBlock().getSummaryString(clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SUD processing notification manager.
|
||||||
|
* @return SUD processing notification manager.
|
||||||
|
*/
|
||||||
|
public SUDNotificationManager getSUDNotificationManager() {
|
||||||
|
if (sudNotificationManager == null) {
|
||||||
|
sudNotificationManager = new SUDNotificationManager();
|
||||||
|
}
|
||||||
|
return sudNotificationManager;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import java.awt.GridBagLayout;
|
|||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@ -31,6 +33,10 @@ import javax.swing.border.TitledBorder;
|
|||||||
import org.jflac.FLACDecoder;
|
import org.jflac.FLACDecoder;
|
||||||
import org.jflac.frame.Frame;
|
import org.jflac.frame.Frame;
|
||||||
import org.jflac.util.ByteData;
|
import org.jflac.util.ByteData;
|
||||||
|
import org.pamguard.x3.sud.Chunk;
|
||||||
|
import org.pamguard.x3.sud.SudAudioInputStream;
|
||||||
|
import org.pamguard.x3.sud.SudFileListener;
|
||||||
|
import org.pamguard.x3.sud.SudParams;
|
||||||
import org.jflac.PCMProcessor;
|
import org.jflac.PCMProcessor;
|
||||||
import org.jflac.metadata.StreamInfo;
|
import org.jflac.metadata.StreamInfo;
|
||||||
import org.jflac.sound.spi.FlacEncoding;
|
import org.jflac.sound.spi.FlacEncoding;
|
||||||
@ -49,13 +55,11 @@ import Acquisition.filedate.FileDateDialogStrip;
|
|||||||
import Acquisition.filedate.FileDateObserver;
|
import Acquisition.filedate.FileDateObserver;
|
||||||
import Acquisition.pamAudio.PamAudioFileManager;
|
import Acquisition.pamAudio.PamAudioFileManager;
|
||||||
import Acquisition.pamAudio.PamAudioFileFilter;
|
import Acquisition.pamAudio.PamAudioFileFilter;
|
||||||
import Acquisition.pamAudio.PamAudioSystem;
|
|
||||||
import PamController.PamControlledUnitSettings;
|
import PamController.PamControlledUnitSettings;
|
||||||
import PamController.PamController;
|
import PamController.PamController;
|
||||||
import PamController.PamSettingManager;
|
import PamController.PamSettingManager;
|
||||||
import PamController.PamSettings;
|
import PamController.PamSettings;
|
||||||
import PamDetection.RawDataUnit;
|
import PamDetection.RawDataUnit;
|
||||||
import PamModel.SMRUEnable;
|
|
||||||
import PamUtils.PamCalendar;
|
import PamUtils.PamCalendar;
|
||||||
import PamUtils.PamFileChooser;
|
import PamUtils.PamFileChooser;
|
||||||
import PamView.dialog.PamLabel;
|
import PamView.dialog.PamLabel;
|
||||||
@ -151,6 +155,9 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
*/
|
*/
|
||||||
private PamWarning fileWarning;
|
private PamWarning fileWarning;
|
||||||
|
|
||||||
|
private SudAudioInputStream sudAudioInputStream;
|
||||||
|
|
||||||
|
private SudListener sudListener;
|
||||||
|
|
||||||
public FileInputSystem(AcquisitionControl acquisitionControl) {
|
public FileInputSystem(AcquisitionControl acquisitionControl) {
|
||||||
this.acquisitionControl = acquisitionControl;
|
this.acquisitionControl = acquisitionControl;
|
||||||
@ -216,7 +223,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
fileDateStrip.addObserver(this);
|
fileDateStrip.addObserver(this);
|
||||||
p.add(fileDateStrip.getDialogComponent(), constraints);
|
p.add(fileDateStrip.getDialogComponent(), constraints);
|
||||||
|
|
||||||
// if (SMRUEnable.isEnable()) {
|
// if (SMRUEnable.isEnable()) {
|
||||||
// no reason to hide this option from users.
|
// no reason to hide this option from users.
|
||||||
constraints.gridy++;
|
constraints.gridy++;
|
||||||
constraints.gridx = 0;
|
constraints.gridx = 0;
|
||||||
@ -227,7 +234,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
constraints.gridx++;
|
constraints.gridx++;
|
||||||
addComponent(p, new JLabel("seconds"), constraints);
|
addComponent(p, new JLabel("seconds"), constraints);
|
||||||
constraints.anchor = GridBagConstraints.EAST;
|
constraints.anchor = GridBagConstraints.EAST;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// addComponent(p, new JLabel("File date :"), constraints);
|
// addComponent(p, new JLabel("File date :"), constraints);
|
||||||
// constraints.gridx++;
|
// constraints.gridx++;
|
||||||
@ -421,6 +428,10 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if (audioStream instanceof SudAudioInputStream) {
|
||||||
|
acquisitionControl.getSUDNotificationManager().interpretNewFile(newFile, (SudAudioInputStream) audioStream);
|
||||||
|
}
|
||||||
|
|
||||||
AudioFormat audioFormat = audioStream.getFormat();
|
AudioFormat audioFormat = audioStream.getFormat();
|
||||||
// fileLength = file.length();
|
// fileLength = file.length();
|
||||||
fileSamples = audioStream.getFrameLength();
|
fileSamples = audioStream.getFrameLength();
|
||||||
@ -538,7 +549,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
public File getCurrentFile() {
|
public File getCurrentFile() {
|
||||||
System.out.println("fileInputParameters: " + fileInputParameters);
|
// System.out.println("fileInputParameters: " + fileInputParameters);
|
||||||
if (fileInputParameters.recentFiles == null) return null;
|
if (fileInputParameters.recentFiles == null) return null;
|
||||||
if (fileInputParameters.recentFiles.size() < 1) return null;
|
if (fileInputParameters.recentFiles.size() < 1) return null;
|
||||||
String fileName = fileInputParameters.recentFiles.get(0);
|
String fileName = fileInputParameters.recentFiles.get(0);
|
||||||
@ -575,6 +586,19 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
|
|
||||||
audioStream = PamAudioFileManager.getInstance().getAudioInputStream(currentFile);
|
audioStream = PamAudioFileManager.getInstance().getAudioInputStream(currentFile);
|
||||||
|
|
||||||
|
if (audioStream instanceof SudAudioInputStream) {
|
||||||
|
sudAudioInputStream = (SudAudioInputStream) audioStream;
|
||||||
|
if (sudListener == null) {
|
||||||
|
sudListener = new SudListener();
|
||||||
|
}
|
||||||
|
sudAudioInputStream.addSudFileListener(sudListener);
|
||||||
|
// sudAudioInputStream.ad
|
||||||
|
acquisitionControl.getSUDNotificationManager().newSudInputStream(sudAudioInputStream);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sudAudioInputStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (audioStream == null) {
|
if (audioStream == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -608,15 +632,24 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SudListener implements SudFileListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void chunkProcessed(int chunkID, Chunk sudChunk) {
|
||||||
|
acquisitionControl.getSUDNotificationManager().chunkProcessed(chunkID, sudChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean runFileAnalysis() {
|
public boolean runFileAnalysis() {
|
||||||
// keep a reference to where data will be put.
|
// keep a reference to where data will be put.
|
||||||
this.newDataUnits = acquisitionControl.getDaqProcess().getNewDataQueue();
|
this.newDataUnits = acquisitionControl.getDaqProcess().getNewDataQueue();
|
||||||
|
|
||||||
// if (this.newDataUnits == null) {
|
// if (this.newDataUnits == null) {
|
||||||
// System.err.println("newDataUnits: == null: ");
|
// System.err.println("newDataUnits: == null: ");
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (!prepareInputFile() && getCurrentFile()!=null) {
|
if (!prepareInputFile() && getCurrentFile()!=null) {
|
||||||
|
|
||||||
@ -997,6 +1030,9 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (audioStream != null) {
|
if (audioStream != null) {
|
||||||
|
if (audioStream instanceof SudAudioInputStream) {
|
||||||
|
acquisitionControl.getSUDNotificationManager().sudStreamClosed();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
audioStream.close();
|
audioStream.close();
|
||||||
audioStream = null;
|
audioStream = null;
|
||||||
|
@ -13,6 +13,7 @@ import java.util.TimeZone;
|
|||||||
import Acquisition.KETime;
|
import Acquisition.KETime;
|
||||||
import Acquisition.layoutFX.FileDatePane;
|
import Acquisition.layoutFX.FileDatePane;
|
||||||
import Acquisition.layoutFX.StandardFileDatePane;
|
import Acquisition.layoutFX.StandardFileDatePane;
|
||||||
|
import Acquisition.sud.SUDFileTime;
|
||||||
import PamController.PamControlledUnitSettings;
|
import PamController.PamControlledUnitSettings;
|
||||||
import PamController.PamSettingManager;
|
import PamController.PamSettingManager;
|
||||||
import PamController.PamSettings;
|
import PamController.PamSettings;
|
||||||
@ -158,6 +159,11 @@ public class StandardFileDate implements FileDate, PamSettings {
|
|||||||
return forcedDataFormat(file, settings.getForcedDateFormat());
|
return forcedDataFormat(file, settings.getForcedDateFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long sudTime = SUDFileTime.getSUDFileTime(file);
|
||||||
|
if (sudTime != Long.MIN_VALUE) {
|
||||||
|
return sudTime;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dtag files have an accompanying XML file which
|
* Dtag files have an accompanying XML file which
|
||||||
* has the same name. Try to find such a file and get
|
* has the same name. Try to find such a file and get
|
||||||
@ -169,6 +175,7 @@ public class StandardFileDate implements FileDate, PamSettings {
|
|||||||
return dTagTime;
|
return dTagTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
long stTime = SoundTrapTime.getSoundTrapTime(file, settings.getDateTimeFormatToUse());
|
long stTime = SoundTrapTime.getSoundTrapTime(file, settings.getDateTimeFormatToUse());
|
||||||
if (stTime != Long.MIN_VALUE) {
|
if (stTime != Long.MIN_VALUE) {
|
||||||
setLastFormat("Soundtrap file format \"" + settings.getDateTimeFormatToUse() + "\"");
|
setLastFormat("Soundtrap file format \"" + settings.getDateTimeFormatToUse() + "\"");
|
||||||
|
86
src/Acquisition/sud/SUDFileTime.java
Normal file
86
src/Acquisition/sud/SUDFileTime.java
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package Acquisition.sud;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.pamguard.x3.sud.ChunkHeader;
|
||||||
|
import org.pamguard.x3.sud.SudAudioInputStream;
|
||||||
|
import org.pamguard.x3.sud.SudFileMap;
|
||||||
|
import org.pamguard.x3.sud.SudParams;
|
||||||
|
|
||||||
|
import PamUtils.PamCalendar;
|
||||||
|
|
||||||
|
public class SUDFileTime {
|
||||||
|
|
||||||
|
private static long sudTime;
|
||||||
|
|
||||||
|
private static String lastFilePath = "";
|
||||||
|
/**
|
||||||
|
* Temp measure to get the time from the first available sud record.
|
||||||
|
* @param file
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static long getSUDFileTime(File file) {
|
||||||
|
if (file == null || file.exists() == false) {
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
if (file.getName().toLowerCase().endsWith(".sud") == false) {
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
String filePath = file.getAbsolutePath();
|
||||||
|
if (filePath.equals(lastFilePath)) {
|
||||||
|
return sudTime;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Open the sud file and read it until the first chunk arrive, get the time
|
||||||
|
* from there and close it again. I don't really see another way.
|
||||||
|
*/
|
||||||
|
long t1 = System.currentTimeMillis();
|
||||||
|
sudTime = Long.MIN_VALUE;
|
||||||
|
SudParams sudParams = new SudParams();
|
||||||
|
sudParams.saveMeta = false;
|
||||||
|
sudParams.saveWav = false;
|
||||||
|
try {
|
||||||
|
SudAudioInputStream sudAudioInputStream = SudAudioInputStream.openInputStream(file, sudParams, false);
|
||||||
|
if (sudAudioInputStream == null) {
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
SudFileMap sudMap = sudAudioInputStream.getSudMap();
|
||||||
|
if (sudMap == null) {
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
long t = sudMap.getFirstChunkTime();
|
||||||
|
if (t != 0) {
|
||||||
|
sudTime = t;
|
||||||
|
}
|
||||||
|
// sudAudioInputStream.addSudFileListener((chunkID, sudChunk)->{
|
||||||
|
// ChunkHeader chunkHead = sudChunk.chunkHeader;
|
||||||
|
// if (chunkHead == null || sudTime != Long.MIN_VALUE) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// long millis = (long) chunkHead.TimeS*1000 + (long) chunkHead.TimeOffsetUs/1000;
|
||||||
|
// if (millis > 0) {
|
||||||
|
// sudTime = millis;
|
||||||
|
// lastFilePath = filePath;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// while (sudAudioInputStream.available() > 0 && sudTime == Long.MIN_VALUE) {
|
||||||
|
//
|
||||||
|
// //note this is reading bytes of uncompressed continuous recordings only.
|
||||||
|
// sudAudioInputStream.read();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
sudAudioInputStream.close();
|
||||||
|
long t2 = System.currentTimeMillis();
|
||||||
|
System.out.printf("SUD file time %s extracted in %d milliseconds\n", PamCalendar.formatDBDateTime(sudTime), t2-t1);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error getting time from SUD file: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sudTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
36
src/Acquisition/sud/SUDNotificationHandler.java
Normal file
36
src/Acquisition/sud/SUDNotificationHandler.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package Acquisition.sud;
|
||||||
|
|
||||||
|
import org.pamguard.x3.sud.Chunk;
|
||||||
|
import org.pamguard.x3.sud.SudAudioInputStream;
|
||||||
|
import org.pamguard.x3.sud.SudFileListener;
|
||||||
|
import org.pamguard.x3.sud.SudProgressListener;
|
||||||
|
|
||||||
|
public interface SUDNotificationHandler extends SudFileListener, SudProgressListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A new SUD file input stream has been opened.
|
||||||
|
* @param sudAudioInputStream
|
||||||
|
*/
|
||||||
|
public void newSudInputStream(SudAudioInputStream sudAudioInputStream);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SUD stream has closed.
|
||||||
|
*/
|
||||||
|
public void sudStreamClosed();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void progress(double arg0, int arg1, int arg2);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void chunkProcessed(int chunkId, Chunk sudChunk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification that a new file or folder is selected. This is called when a file
|
||||||
|
* or folder is selected in the dialog, NOT when acquisition starts, so is a good
|
||||||
|
* opportunity for the SUD Click Detector to work out channel maps and sample rates.
|
||||||
|
* @param newFile
|
||||||
|
* @param sudAudioStream
|
||||||
|
*/
|
||||||
|
public void interpretNewFile(String newFile, SudAudioInputStream sudAudioStream);
|
||||||
|
|
||||||
|
}
|
66
src/Acquisition/sud/SUDNotificationManager.java
Normal file
66
src/Acquisition/sud/SUDNotificationManager.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package Acquisition.sud;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.pamguard.x3.sud.Chunk;
|
||||||
|
import org.pamguard.x3.sud.SudAudioInputStream;
|
||||||
|
import org.pamguard.x3.sud.SudFileListener;
|
||||||
|
import org.pamguard.x3.sud.SudProgressListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to handle appropriate notifications for SUD files, which go a bit
|
||||||
|
* beyond what's handled in the chunk notifications.
|
||||||
|
* @author dg50
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SUDNotificationManager implements SUDNotificationHandler {
|
||||||
|
|
||||||
|
private ArrayList<SUDNotificationHandler> handlers = new ArrayList();
|
||||||
|
|
||||||
|
public void addNotificationHandler(SUDNotificationHandler sudNotificationHandler) {
|
||||||
|
if (handlers.contains(sudNotificationHandler)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlers.add(sudNotificationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeNotificationHandler(SUDNotificationHandler sudNotificationHandler) {
|
||||||
|
return handlers.remove(sudNotificationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newSudInputStream(SudAudioInputStream sudAudioInputStream) {
|
||||||
|
for (SUDNotificationHandler handler : handlers) {
|
||||||
|
handler.newSudInputStream(sudAudioInputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sudStreamClosed() {
|
||||||
|
for (SUDNotificationHandler handler : handlers) {
|
||||||
|
handler.sudStreamClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void progress(double arg0, int arg1, int arg2) {
|
||||||
|
for (SUDNotificationHandler handler : handlers) {
|
||||||
|
handler.progress(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void chunkProcessed(int chunkId, Chunk sudChunk) {
|
||||||
|
for (SUDNotificationHandler handler : handlers) {
|
||||||
|
handler.chunkProcessed(chunkId, sudChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void interpretNewFile(String newFile, SudAudioInputStream sudAudioStream) {
|
||||||
|
for (SUDNotificationHandler handler : handlers) {
|
||||||
|
handler.interpretNewFile(newFile, sudAudioStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -674,11 +674,6 @@ final public class PamModel implements PamModelInterface, PamSettings {
|
|||||||
mi.setModulesMenuGroup(detectorsGroup);
|
mi.setModulesMenuGroup(detectorsGroup);
|
||||||
mi.addGUICompatabilityFlag(PamGUIManager.FX);
|
mi.addGUICompatabilityFlag(PamGUIManager.FX);
|
||||||
|
|
||||||
mi = PamModuleInfo.registerControlledUnit("soundtrap.STClickControl", "SoundTrap Click Detector");
|
|
||||||
mi.setModulesMenuGroup(detectorsGroup);
|
|
||||||
mi.setToolTipText("Click Detector module for Soundtrap detector data");
|
|
||||||
mi.setHidden(isViewer == false);
|
|
||||||
|
|
||||||
mi = PamModuleInfo.registerControlledUnit("clickTrainDetector.ClickTrainControl", "Click Train Detector");
|
mi = PamModuleInfo.registerControlledUnit("clickTrainDetector.ClickTrainControl", "Click Train Detector");
|
||||||
mi.addDependency(new PamDependency(RawDataUnit.class, "clickDetector.ClickControl"));
|
mi.addDependency(new PamDependency(RawDataUnit.class, "clickDetector.ClickControl"));
|
||||||
mi.setToolTipText("Searches for click trains in detected clicks.");
|
mi.setToolTipText("Searches for click trains in detected clicks.");
|
||||||
@ -729,6 +724,11 @@ final public class PamModel implements PamModelInterface, PamSettings {
|
|||||||
mi.setToolTipText("Generalised Power Law Detector for tonal sounds");
|
mi.setToolTipText("Generalised Power Law Detector for tonal sounds");
|
||||||
mi.setModulesMenuGroup(detectorsGroup);
|
mi.setModulesMenuGroup(detectorsGroup);
|
||||||
|
|
||||||
|
mi = PamModuleInfo.registerControlledUnit("soundtrap.STClickControl", "SoundTrap Click Detector");
|
||||||
|
mi.setModulesMenuGroup(detectorsGroup);
|
||||||
|
mi.setToolTipText("Click Detector module for Soundtrap detector data only");
|
||||||
|
// mi.setHidden(isViewer == false);
|
||||||
|
|
||||||
// mi = PamModuleInfo.registerControlledUnit("WorkshopDemo.WorkshopController", "Workshop Demo Detector");
|
// mi = PamModuleInfo.registerControlledUnit("WorkshopDemo.WorkshopController", "Workshop Demo Detector");
|
||||||
// mi.addDependency(new PamDependency(FFTDataUnit.class, "fftManager.PamFFTControl"));
|
// mi.addDependency(new PamDependency(FFTDataUnit.class, "fftManager.PamFFTControl"));
|
||||||
// mi.setToolTipText("Simple demo detector for programmers");
|
// mi.setToolTipText("Simple demo detector for programmers");
|
||||||
|
@ -30,8 +30,11 @@ import javax.swing.JMenu;
|
|||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
|
|
||||||
import Acquisition.AcquisitionControl;
|
import Acquisition.AcquisitionControl;
|
||||||
|
import Acquisition.sud.SUDNotificationManager;
|
||||||
|
import PamController.PamController;
|
||||||
import PamguardMVC.PamRawDataBlock;
|
import PamguardMVC.PamRawDataBlock;
|
||||||
import clickDetector.ClickControl;
|
import clickDetector.ClickControl;
|
||||||
|
import soundtrap.sud.SudFileDWVHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author mo55
|
* @author mo55
|
||||||
@ -46,6 +49,9 @@ public class STClickControl extends ClickControl {
|
|||||||
*/
|
*/
|
||||||
private AcquisitionControl rawSource;
|
private AcquisitionControl rawSource;
|
||||||
|
|
||||||
|
private SudFileDWVHandler sudFileDWVHandler;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name
|
* @param name
|
||||||
*/
|
*/
|
||||||
@ -54,6 +60,9 @@ public class STClickControl extends ClickControl {
|
|||||||
|
|
||||||
// create a private acquisition control that only this module can see
|
// create a private acquisition control that only this module can see
|
||||||
rawSource = new AcquisitionControl("Private Sound Acq for Soundtrap Click Detector");
|
rawSource = new AcquisitionControl("Private Sound Acq for Soundtrap Click Detector");
|
||||||
|
|
||||||
|
sudFileDWVHandler = new SudFileDWVHandler(this);
|
||||||
|
sudFileDWVHandler.subscribeSUD();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -107,5 +116,25 @@ public class STClickControl extends ClickControl {
|
|||||||
return newMenu;
|
return newMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pamStart() {
|
||||||
|
sudFileDWVHandler.pamStart();
|
||||||
|
super.pamStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pamStop() {
|
||||||
|
sudFileDWVHandler.pamStop();
|
||||||
|
super.pamStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyModelChanged(int changeType) {
|
||||||
|
super.notifyModelChanged(changeType);
|
||||||
|
if (changeType == PamController.INITIALIZATION_COMPLETE) {
|
||||||
|
sudFileDWVHandler.subscribeSUD();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
59
src/soundtrap/sud/BCLDetectionChunk.java
Normal file
59
src/soundtrap/sud/BCLDetectionChunk.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package soundtrap.sud;
|
||||||
|
|
||||||
|
import org.pamguard.x3.sud.Chunk;
|
||||||
|
|
||||||
|
public class BCLDetectionChunk {
|
||||||
|
|
||||||
|
private long javaMillis;
|
||||||
|
private boolean isDetection;
|
||||||
|
private String bclRecord;
|
||||||
|
private Chunk sudChunk;
|
||||||
|
private long javaMicros;
|
||||||
|
|
||||||
|
public BCLDetectionChunk(long javaMillis, long javaMicros, boolean isDetection, String bclRecord, Chunk sudChunk) {
|
||||||
|
super();
|
||||||
|
this.javaMillis = javaMillis;
|
||||||
|
this.javaMicros = javaMicros;
|
||||||
|
this.isDetection = isDetection;
|
||||||
|
this.bclRecord = bclRecord;
|
||||||
|
this.sudChunk = sudChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the javaMillis
|
||||||
|
*/
|
||||||
|
public long getJavaMillis() {
|
||||||
|
return javaMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the isDetection
|
||||||
|
*/
|
||||||
|
public boolean isDetection() {
|
||||||
|
return isDetection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the bclRecord
|
||||||
|
*/
|
||||||
|
public String getBclRecord() {
|
||||||
|
return bclRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the sudChunk
|
||||||
|
*/
|
||||||
|
public Chunk getSudChunk() {
|
||||||
|
return sudChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the javaMicros
|
||||||
|
*/
|
||||||
|
public long getJavaMicros() {
|
||||||
|
return javaMicros;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
254
src/soundtrap/sud/SudFileDWVHandler.java
Normal file
254
src/soundtrap/sud/SudFileDWVHandler.java
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package soundtrap.sud;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.pamguard.x3.sud.Chunk;
|
||||||
|
import org.pamguard.x3.sud.SudAudioInputStream;
|
||||||
|
import org.pamguard.x3.sud.SudDataInputStream;
|
||||||
|
|
||||||
|
import Acquisition.AcquisitionControl;
|
||||||
|
import Acquisition.sud.SUDNotificationHandler;
|
||||||
|
import Acquisition.sud.SUDNotificationManager;
|
||||||
|
import PamController.PamController;
|
||||||
|
import PamUtils.PamCalendar;
|
||||||
|
import clickDetector.ClickDetection;
|
||||||
|
import clickDetector.ClickDetector;
|
||||||
|
import clickDetector.ClickDetector.ChannelGroupDetector;
|
||||||
|
import soundtrap.STClickControl;
|
||||||
|
import wavFiles.ByteConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to handle BCL and DWV data from a SUD file during sud file processing.
|
||||||
|
* SUD files are the compressed data files from soundtraps. The latest PAMGuard
|
||||||
|
* version contains a audioinputstream for SUD files, making it possible to
|
||||||
|
* process them withoug having to first inflate them to wav files. During
|
||||||
|
* 'normal mode' processing, this class will subscribe to packets from the SUD
|
||||||
|
* files, unpack what would normally be BCL and DWV data and generate
|
||||||
|
* appropriate binary output files.
|
||||||
|
*
|
||||||
|
* @author dg50
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SudFileDWVHandler implements SUDNotificationHandler {
|
||||||
|
|
||||||
|
private STClickControl stClickControl;
|
||||||
|
private SudAudioInputStream sudAudioInputStream;
|
||||||
|
|
||||||
|
private int txtCount = 0;
|
||||||
|
|
||||||
|
private List<Chunk> clickChunks = new LinkedList();
|
||||||
|
|
||||||
|
private List<BCLDetectionChunk> bclChunks = new LinkedList();
|
||||||
|
|
||||||
|
int bclDetCount, bclNoiseCount, dwvCount, processedChunks;
|
||||||
|
private long effortStart;
|
||||||
|
private double sampleRate;
|
||||||
|
private ByteConverter byteConverter;
|
||||||
|
private ClickDetector clickDetector;
|
||||||
|
private ChannelGroupDetector channelGroupDetector;
|
||||||
|
|
||||||
|
public SudFileDWVHandler(STClickControl stClickControl) {
|
||||||
|
this.stClickControl = stClickControl;
|
||||||
|
clickDetector = stClickControl.getClickDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newSudInputStream(SudAudioInputStream sudAudioInputStream) {
|
||||||
|
this.sudAudioInputStream = sudAudioInputStream;
|
||||||
|
interpretNewFile(null, sudAudioInputStream);
|
||||||
|
bclDetCount = bclNoiseCount = dwvCount = processedChunks = 0;
|
||||||
|
bclChunks.clear();
|
||||||
|
clickChunks.clear();
|
||||||
|
byteConverter = ByteConverter.createByteConverter(sudAudioInputStream.getFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void interpretNewFile(String newFile, SudAudioInputStream sudAudioInputStream) {
|
||||||
|
// this is the wav sample rate, not the detector sample rate, so don't use it
|
||||||
|
// sampleRate = sudAudioInputStream.getFormat().getFrameRate();
|
||||||
|
// this is the right one
|
||||||
|
sampleRate = sudAudioInputStream.getSudMap().clickDetSampleRate;
|
||||||
|
stClickControl.findRawDataBlock().setChannelMap(1);
|
||||||
|
stClickControl.findRawDataBlock().setSampleRate((float) sampleRate, true);
|
||||||
|
stClickControl.getSTAcquisition().acquisitionParameters.sampleRate = (float) sampleRate;
|
||||||
|
// System.out.printf("Open input stream fs = %3.1f\n", sampleRate);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sudStreamClosed() {
|
||||||
|
System.out.printf("SUD input stream closed, %d DWV, %d bcl Detectins and %d BCL Noise, %d chunks processed\n",
|
||||||
|
dwvCount, bclDetCount, bclNoiseCount, processedChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void progress(double arg0, int arg1, int arg2) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void chunkProcessed(int chunkID, Chunk sudChunk) {
|
||||||
|
if (sudAudioInputStream == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String chunkName = "Unknown";
|
||||||
|
int chunkSize = sudChunk.buffer.length;
|
||||||
|
if (sudAudioInputStream.getChunkIDString(chunkID).equals("wav")) {
|
||||||
|
if (sudAudioInputStream.isChunkIDWav(chunkID)) {
|
||||||
|
chunkName = "RECORDINGS";
|
||||||
|
// System.out.printf("Chunk ID %d, size %d, type %s\n", chunkID, chunkSize,
|
||||||
|
// chunkName);
|
||||||
|
// System.out.println("ID: " + chunkID + " This is raw data from detected
|
||||||
|
// CLICKS: " + sudAudioInputStream.getChunkIDString(chunkID));
|
||||||
|
} else {
|
||||||
|
chunkName = "CLICKS";
|
||||||
|
processClickChunk(chunkID, sudChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sudAudioInputStream.getChunkIDString(chunkID).equals("csv")) {
|
||||||
|
// System.out.println("CSV data the bytes convert directly to comma delimted data");
|
||||||
|
}
|
||||||
|
if (sudAudioInputStream.getChunkIDString(chunkID).equals("txt")) {
|
||||||
|
processTextChunk(chunkID, sudChunk);
|
||||||
|
txtCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pairwise byte swap, i.e. change endianness of int16's. Won't work for
|
||||||
|
* anything else.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
public void swapEndian(byte[] data) {
|
||||||
|
for (int i = 0; i < data.length; i += 2) {
|
||||||
|
byte b = data[i];
|
||||||
|
data[i] = data[i + 1];
|
||||||
|
data[i + 1] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTextChunk(int chunkID, Chunk sudChunk) {
|
||||||
|
SudDataInputStream dis = new SudDataInputStream(new ByteArrayInputStream(sudChunk.getBuffer()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (dis.available() > 0) {
|
||||||
|
long rtime = (dis.readInt()) & 0xffffffffL;
|
||||||
|
long mticks = (dis.readInt()) & 0xffffffffL;
|
||||||
|
int n = dis.readUnsignedShort();
|
||||||
|
|
||||||
|
long javaMillis = (long) rtime * 1000 + mticks / 1000;
|
||||||
|
long javaMicros = (long) rtime * 1000000 + mticks;
|
||||||
|
|
||||||
|
byte[] b = new byte[(n % 2 == 1) ? n + 1 : n];
|
||||||
|
dis.read(b);
|
||||||
|
|
||||||
|
swapEndian(b);
|
||||||
|
|
||||||
|
String s = new String(b, "UTF-8");
|
||||||
|
String[] bits = s.split(",");
|
||||||
|
if (bits[0].equals("E")) {
|
||||||
|
// start or end record.
|
||||||
|
boolean isStart = bits[1].equals("1");
|
||||||
|
if (isStart) {
|
||||||
|
effortStart = javaMicros;
|
||||||
|
}
|
||||||
|
dwvEffort(javaMillis, isStart);
|
||||||
|
} else if (bits[0].equals("D")) {
|
||||||
|
boolean isDet = bits[1].equals("1");
|
||||||
|
if (isDet) {
|
||||||
|
bclDetCount++;
|
||||||
|
String bclText = String.format("%9d,%6d,%s has %d bits", rtime, mticks, s, bits.length);
|
||||||
|
BCLDetectionChunk bclChunk = new BCLDetectionChunk(javaMillis, javaMicros, isDet, bclText,
|
||||||
|
sudChunk);
|
||||||
|
bclChunks.add(bclChunk);
|
||||||
|
} else {
|
||||||
|
bclNoiseCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
convertChunks();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert bcl and dwv data to click detections.
|
||||||
|
*/
|
||||||
|
private void convertChunks() {
|
||||||
|
int nProc = 0;
|
||||||
|
while (bclChunks.size() > 0 && clickChunks.size() > 0) {
|
||||||
|
BCLDetectionChunk bclChunk = bclChunks.remove(0);
|
||||||
|
Chunk dwvChunk = clickChunks.remove(0);
|
||||||
|
makeClick(bclChunk, dwvChunk);
|
||||||
|
nProc++;
|
||||||
|
processedChunks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeClick(BCLDetectionChunk bclChunk, Chunk dwvChunk) {
|
||||||
|
long elapsedSamples = (long) ((bclChunk.getJavaMicros() - effortStart) * (sampleRate / 1e6));
|
||||||
|
|
||||||
|
byte[] rawData = dwvChunk.getBuffer();
|
||||||
|
int nBytes = rawData.length;
|
||||||
|
int nSamples = nBytes / Short.BYTES;
|
||||||
|
double[][] wavData = new double[1][nSamples];
|
||||||
|
byteConverter.bytesToDouble(rawData, wavData, nBytes);
|
||||||
|
|
||||||
|
// channelGroupDetector = clickDetector.getChannelGroupDetector(0);
|
||||||
|
ClickDetection click = new ClickDetection(1, elapsedSamples, nSamples, clickDetector, channelGroupDetector, 1);
|
||||||
|
click.setWaveData(wavData);
|
||||||
|
|
||||||
|
clickDetector.getClickDataBlock().addPamData(click);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dwvEffort(long javaMillis, boolean isStart) {
|
||||||
|
System.out.printf("DWV Effort %s at %s\n", isStart ? "Start" : "End", PamCalendar.formatDBDateTime(javaMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processClickChunk(int chunkID, Chunk sudChunk) {
|
||||||
|
clickChunks.add(sudChunk);
|
||||||
|
dwvCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pamStart() {
|
||||||
|
subscribeSUD();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pamStop() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean subscribeSUD() {
|
||||||
|
if (stClickControl.isViewer()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AcquisitionControl daq = (AcquisitionControl) PamController.getInstance()
|
||||||
|
.findControlledUnit(AcquisitionControl.unitType);
|
||||||
|
if (daq == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SUDNotificationManager sudManager = daq.getSUDNotificationManager();
|
||||||
|
if (sudManager == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sudManager.addNotificationHandler(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user