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:
Douglas Gillespie 2022-11-10 15:20:15 +00:00
parent 61dd6ef4d1
commit 87cd2cd41f
10 changed files with 628 additions and 40 deletions

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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) {

View 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;
}
}

View 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);
}

View 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);
}
}
}

View File

@ -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");

View File

@ -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();
}
}
}

View 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;
}
}

View 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;
}
}