diff --git a/src/Acquisition/AcquisitionControl.java b/src/Acquisition/AcquisitionControl.java index 8fdd0826..3d198980 100644 --- a/src/Acquisition/AcquisitionControl.java +++ b/src/Acquisition/AcquisitionControl.java @@ -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; + } + } diff --git a/src/Acquisition/FileInputSystem.java b/src/Acquisition/FileInputSystem.java index 771af349..9ec48538 100644 --- a/src/Acquisition/FileInputSystem.java +++ b/src/Acquisition/FileInputSystem.java @@ -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 = "

There was an error trying to access the audio file

" + @@ -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) { diff --git a/src/Acquisition/filedate/StandardFileDate.java b/src/Acquisition/filedate/StandardFileDate.java index 8fa56de6..aa87e973 100644 --- a/src/Acquisition/filedate/StandardFileDate.java +++ b/src/Acquisition/filedate/StandardFileDate.java @@ -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) { diff --git a/src/Acquisition/sud/SUDFileTime.java b/src/Acquisition/sud/SUDFileTime.java new file mode 100644 index 00000000..b5e7ca2d --- /dev/null +++ b/src/Acquisition/sud/SUDFileTime.java @@ -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; + } + + + +} diff --git a/src/Acquisition/sud/SUDNotificationHandler.java b/src/Acquisition/sud/SUDNotificationHandler.java new file mode 100644 index 00000000..ed5ed429 --- /dev/null +++ b/src/Acquisition/sud/SUDNotificationHandler.java @@ -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); + +} diff --git a/src/Acquisition/sud/SUDNotificationManager.java b/src/Acquisition/sud/SUDNotificationManager.java new file mode 100644 index 00000000..28bed217 --- /dev/null +++ b/src/Acquisition/sud/SUDNotificationManager.java @@ -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 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); + } + } + +} diff --git a/src/PamModel/PamModel.java b/src/PamModel/PamModel.java index 153fa2eb..00be884a 100644 --- a/src/PamModel/PamModel.java +++ b/src/PamModel/PamModel.java @@ -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"); diff --git a/src/soundtrap/STClickControl.java b/src/soundtrap/STClickControl.java index 3d7bd711..2fbdde7d 100644 --- a/src/soundtrap/STClickControl.java +++ b/src/soundtrap/STClickControl.java @@ -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(); + } + } } diff --git a/src/soundtrap/sud/BCLDetectionChunk.java b/src/soundtrap/sud/BCLDetectionChunk.java new file mode 100644 index 00000000..eb9b15a7 --- /dev/null +++ b/src/soundtrap/sud/BCLDetectionChunk.java @@ -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; + } + + + +} diff --git a/src/soundtrap/sud/SudFileDWVHandler.java b/src/soundtrap/sud/SudFileDWVHandler.java new file mode 100644 index 00000000..57d61e82 --- /dev/null +++ b/src/soundtrap/sud/SudFileDWVHandler.java @@ -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 clickChunks = new LinkedList(); + + private List 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; + } + +}