mirror of
https://github.com/PAMGuard/PAMGuard.git
synced 2024-11-21 22:52:22 +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.SwingConstants;
|
||||
|
||||
import org.pamguard.x3.sud.Chunk;
|
||||
import org.pamguard.x3.sud.SudFileListener;
|
||||
|
||||
import dataGram.DatagramManager;
|
||||
import dataMap.OfflineDataMapPoint;
|
||||
import dataMap.filemaps.OfflineFileServer;
|
||||
@ -49,12 +52,12 @@ import simulatedAcquisition.SimProcess;
|
||||
import asiojni.ASIOSoundSystem;
|
||||
import asiojni.NewAsioSoundSystem;
|
||||
import nidaqdev.NIDAQProcess;
|
||||
import nidaqdev.networkdaq.NINetworkDaq;
|
||||
import Acquisition.filedate.FileDate;
|
||||
import Acquisition.filedate.StandardFileDate;
|
||||
import Acquisition.layoutFX.AquisitionGUIFX;
|
||||
import Acquisition.offlineFuncs.OfflineWavFileServer;
|
||||
import Acquisition.rona.RonaOfflineFileServer;
|
||||
import Acquisition.sud.SUDNotificationManager;
|
||||
import Array.ArrayManager;
|
||||
import Array.PamArray;
|
||||
import Array.Preamplifier;
|
||||
@ -132,7 +135,8 @@ public class AcquisitionControl extends PamControlledUnit implements PamSettings
|
||||
* The JavaFX GUI for the sound acquisition module.
|
||||
*/
|
||||
private AquisitionGUIFX aquisitionGUIFX;
|
||||
|
||||
|
||||
private SUDNotificationManager sudNotificationManager;
|
||||
|
||||
/**
|
||||
* Main control unit for audio data acquisition.
|
||||
@ -833,5 +837,16 @@ public class AcquisitionControl extends PamControlledUnit implements PamSettings
|
||||
public String getModuleSummary(boolean 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.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
@ -31,6 +33,10 @@ import javax.swing.border.TitledBorder;
|
||||
import org.jflac.FLACDecoder;
|
||||
import org.jflac.frame.Frame;
|
||||
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.metadata.StreamInfo;
|
||||
import org.jflac.sound.spi.FlacEncoding;
|
||||
@ -49,13 +55,11 @@ import Acquisition.filedate.FileDateDialogStrip;
|
||||
import Acquisition.filedate.FileDateObserver;
|
||||
import Acquisition.pamAudio.PamAudioFileManager;
|
||||
import Acquisition.pamAudio.PamAudioFileFilter;
|
||||
import Acquisition.pamAudio.PamAudioSystem;
|
||||
import PamController.PamControlledUnitSettings;
|
||||
import PamController.PamController;
|
||||
import PamController.PamSettingManager;
|
||||
import PamController.PamSettings;
|
||||
import PamDetection.RawDataUnit;
|
||||
import PamModel.SMRUEnable;
|
||||
import PamUtils.PamCalendar;
|
||||
import PamUtils.PamFileChooser;
|
||||
import PamView.dialog.PamLabel;
|
||||
@ -151,6 +155,9 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
*/
|
||||
private PamWarning fileWarning;
|
||||
|
||||
private SudAudioInputStream sudAudioInputStream;
|
||||
|
||||
private SudListener sudListener;
|
||||
|
||||
public FileInputSystem(AcquisitionControl acquisitionControl) {
|
||||
this.acquisitionControl = acquisitionControl;
|
||||
@ -177,7 +184,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
}
|
||||
return daqDialog;
|
||||
}
|
||||
|
||||
|
||||
protected JPanel createDaqDialogPanel() {
|
||||
|
||||
JPanel p = new JPanel();
|
||||
@ -216,18 +223,18 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
fileDateStrip.addObserver(this);
|
||||
p.add(fileDateStrip.getDialogComponent(), constraints);
|
||||
|
||||
// if (SMRUEnable.isEnable()) {
|
||||
// if (SMRUEnable.isEnable()) {
|
||||
// no reason to hide this option from users.
|
||||
constraints.gridy++;
|
||||
constraints.gridx = 0;
|
||||
constraints.gridwidth = 1;
|
||||
addComponent(p, new JLabel("Skip initial"), constraints);
|
||||
constraints.gridx++;
|
||||
addComponent(p, skipSecondsField = new JTextField(4), constraints);
|
||||
constraints.gridx++;
|
||||
addComponent(p, new JLabel("seconds"), constraints);
|
||||
constraints.anchor = GridBagConstraints.EAST;
|
||||
// }
|
||||
constraints.gridy++;
|
||||
constraints.gridx = 0;
|
||||
constraints.gridwidth = 1;
|
||||
addComponent(p, new JLabel("Skip initial"), constraints);
|
||||
constraints.gridx++;
|
||||
addComponent(p, skipSecondsField = new JTextField(4), constraints);
|
||||
constraints.gridx++;
|
||||
addComponent(p, new JLabel("seconds"), constraints);
|
||||
constraints.anchor = GridBagConstraints.EAST;
|
||||
// }
|
||||
|
||||
// addComponent(p, new JLabel("File date :"), constraints);
|
||||
// constraints.gridx++;
|
||||
@ -251,7 +258,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
// would hold a null. The system type is used by the getParameterSet method to decide
|
||||
// whether or not to include the parameters in the XML output
|
||||
if (fileInputParameters.systemType==null) fileInputParameters.systemType=getSystemType();
|
||||
|
||||
|
||||
fillFileList();
|
||||
|
||||
if (repeat != null) {
|
||||
@ -289,7 +296,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
fileInputParameters.recentFiles.trimToSize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (repeat == null) {
|
||||
fileInputParameters.repeatLoop = false;
|
||||
}
|
||||
@ -306,7 +313,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -337,7 +344,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
protected void selectFile() {
|
||||
//IshmaelDetector.MatchFiltParamsDialog copies a bunch of this. If you
|
||||
//modifiy this, please check that too.
|
||||
|
||||
|
||||
String currFile = (String) fileNameCombo.getSelectedItem();
|
||||
// seems to just support aif and wav files at the moment
|
||||
// Type[] audioTypes = AudioSystem.getAudioFileTypes();
|
||||
@ -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();
|
||||
// fileLength = file.length();
|
||||
fileSamples = audioStream.getFrameLength();
|
||||
@ -477,7 +488,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
public long getSkipStartFileTime() {
|
||||
return fileInputParameters.skipStartFileTime;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canPlayBack(float sampleRate) {
|
||||
// TODO Auto-generated method stub
|
||||
@ -538,7 +549,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
}
|
||||
|
||||
public File getCurrentFile() {
|
||||
System.out.println("fileInputParameters: " + fileInputParameters);
|
||||
// System.out.println("fileInputParameters: " + fileInputParameters);
|
||||
if (fileInputParameters.recentFiles == null) return null;
|
||||
if (fileInputParameters.recentFiles.size() < 1) return null;
|
||||
String fileName = fileInputParameters.recentFiles.get(0);
|
||||
@ -575,12 +586,25 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
audioFormat = audioStream.getFormat();
|
||||
|
||||
|
||||
if (audioFormat==null) {
|
||||
System.err.println("AudioFormat was null: " + currentFile.getAbsolutePath());
|
||||
return false;
|
||||
@ -592,7 +616,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
|
||||
acquisitionControl.getAcquisitionProcess().setSampleRate(audioFormat.getSampleRate(), true);
|
||||
fileInputParameters.bitDepth = audioFormat.getSampleSizeInBits();
|
||||
|
||||
|
||||
loadByteConverter(audioFormat);
|
||||
|
||||
} catch (UnsupportedAudioFileException ex) {
|
||||
@ -608,18 +632,27 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
return true;
|
||||
}
|
||||
|
||||
private class SudListener implements SudFileListener {
|
||||
|
||||
@Override
|
||||
public void chunkProcessed(int chunkID, Chunk sudChunk) {
|
||||
acquisitionControl.getSUDNotificationManager().chunkProcessed(chunkID, sudChunk);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean runFileAnalysis() {
|
||||
// keep a reference to where data will be put.
|
||||
this.newDataUnits = acquisitionControl.getDaqProcess().getNewDataQueue();
|
||||
|
||||
// if (this.newDataUnits == null) {
|
||||
// System.err.println("newDataUnits: == null: ");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (this.newDataUnits == null) {
|
||||
// System.err.println("newDataUnits: == null: ");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (!prepareInputFile() && getCurrentFile()!=null) {
|
||||
|
||||
|
||||
String audioFileStr = getCurrentFile()==null? "Null File": getCurrentFile().getAbsolutePath();
|
||||
String title = "Error loading audio file";
|
||||
String msg = "<html><p>There was an error trying to access the audio file </p><b> " +
|
||||
@ -647,14 +680,14 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
this.startTimeMS = PamCalendar.getTimeInMillis();
|
||||
|
||||
if (audioFormat==null) {
|
||||
|
||||
|
||||
String audioFileStr = getCurrentFile()==null? "Null File": getCurrentFile().getAbsolutePath();
|
||||
|
||||
System.err.println("FileInputSystem: runFileAnalysis: AudioFile format is null: " + audioFileStr);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
nChannels = audioFormat.getChannels();
|
||||
|
||||
acquisitionControl.getDaqProcess().setSampleRate(sampleRate = audioFormat.getSampleRate(), true);
|
||||
@ -997,6 +1030,9 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
}
|
||||
}
|
||||
if (audioStream != null) {
|
||||
if (audioStream instanceof SudAudioInputStream) {
|
||||
acquisitionControl.getSUDNotificationManager().sudStreamClosed();
|
||||
}
|
||||
try {
|
||||
audioStream.close();
|
||||
audioStream = null;
|
||||
@ -1124,7 +1160,7 @@ public class FileInputSystem extends DaqSystem implements ActionListener, PamSe
|
||||
}
|
||||
|
||||
public void sayEta(long timeMs) {
|
||||
|
||||
|
||||
if (etaLabel==null) return;
|
||||
|
||||
if (timeMs < 0) {
|
||||
|
@ -13,6 +13,7 @@ import java.util.TimeZone;
|
||||
import Acquisition.KETime;
|
||||
import Acquisition.layoutFX.FileDatePane;
|
||||
import Acquisition.layoutFX.StandardFileDatePane;
|
||||
import Acquisition.sud.SUDFileTime;
|
||||
import PamController.PamControlledUnitSettings;
|
||||
import PamController.PamSettingManager;
|
||||
import PamController.PamSettings;
|
||||
@ -157,6 +158,11 @@ public class StandardFileDate implements FileDate, PamSettings {
|
||||
if (settings.isUseBespokeFormat() && settings.getForcedDateFormat() != null) {
|
||||
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
|
||||
@ -168,6 +174,7 @@ public class StandardFileDate implements FileDate, PamSettings {
|
||||
setLastFormat("D3 time from xml file");
|
||||
return dTagTime;
|
||||
}
|
||||
|
||||
|
||||
long stTime = SoundTrapTime.getSoundTrapTime(file, settings.getDateTimeFormatToUse());
|
||||
if (stTime != Long.MIN_VALUE) {
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -673,11 +673,6 @@ final public class PamModel implements PamModelInterface, PamSettings {
|
||||
+ "bearings to source, group into click trains, etc.");
|
||||
mi.setModulesMenuGroup(detectorsGroup);
|
||||
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.addDependency(new PamDependency(RawDataUnit.class, "clickDetector.ClickControl"));
|
||||
@ -729,6 +724,11 @@ final public class PamModel implements PamModelInterface, PamSettings {
|
||||
mi.setToolTipText("Generalised Power Law Detector for tonal sounds");
|
||||
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.addDependency(new PamDependency(FFTDataUnit.class, "fftManager.PamFFTControl"));
|
||||
// mi.setToolTipText("Simple demo detector for programmers");
|
||||
|
@ -30,8 +30,11 @@ import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import Acquisition.AcquisitionControl;
|
||||
import Acquisition.sud.SUDNotificationManager;
|
||||
import PamController.PamController;
|
||||
import PamguardMVC.PamRawDataBlock;
|
||||
import clickDetector.ClickControl;
|
||||
import soundtrap.sud.SudFileDWVHandler;
|
||||
|
||||
/**
|
||||
* @author mo55
|
||||
@ -45,6 +48,9 @@ public class STClickControl extends ClickControl {
|
||||
* The private sound acquisition module linked to the Soundtrap click data
|
||||
*/
|
||||
private AcquisitionControl rawSource;
|
||||
|
||||
private SudFileDWVHandler sudFileDWVHandler;
|
||||
|
||||
|
||||
/**
|
||||
* @param name
|
||||
@ -54,6 +60,9 @@ public class STClickControl extends ClickControl {
|
||||
|
||||
// create a private acquisition control that only this module can see
|
||||
rawSource = new AcquisitionControl("Private Sound Acq for Soundtrap Click Detector");
|
||||
|
||||
sudFileDWVHandler = new SudFileDWVHandler(this);
|
||||
sudFileDWVHandler.subscribeSUD();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -106,6 +115,26 @@ public class STClickControl extends ClickControl {
|
||||
|
||||
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