From 84914923be8d2345b9db12858751dc7b81b4715e Mon Sep 17 00:00:00 2001
From: Aaron Cuevas Lopez <aacuelo@teleco.upv.es>
Date: Wed, 13 Apr 2016 04:05:28 +0200
Subject: [PATCH] Save timestamps in kwd files (WIP)

---
 .../RecordEngine/HDF5FileFormat.cpp           | 23 ++++-
 .../KWIKFormat/RecordEngine/HDF5FileFormat.h  |  2 +
 .../KWIKFormat/RecordEngine/HDF5Recording.cpp | 88 +++++++++++++------
 .../KWIKFormat/RecordEngine/HDF5Recording.h   |  6 +-
 .../RecordNode/OriginalRecording.cpp          |  2 +-
 .../Processors/RecordNode/OriginalRecording.h |  2 +-
 Source/Processors/RecordNode/RecordEngine.h   |  2 +-
 7 files changed, 95 insertions(+), 30 deletions(-)

diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp
index a5de05f57..e2c449ba0 100644
--- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp
+++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp
@@ -40,6 +40,10 @@
 #define SPIKE_CHUNK_YSIZE 40
 #endif
 
+#ifndef TIMESTAMP_CHUNK_SIZE
+#define TIMESTAMP_CHUNK_SIZE 4
+#endif
+
 #define MAX_TRANSFORM_SIZE 512
 
 #define MAX_STR_SIZE 256
@@ -675,13 +679,21 @@ void KWDFile::startNewRecording(int recordingNumber, int nChannels, HDF5Recordin
     CHECK_ERROR(createGroup(recordPath+"/application_data"));
    // CHECK_ERROR(setAttributeArray(F32,info->bitVolts.getRawDataPointer(),info->bitVolts.size(),recordPath+"/application_data",String("channel_bit_volts")));
 	bitVoltsSet = createDataSet(F32, info->bitVolts.size(), 0, recordPath + "/application_data/channel_bit_volts");
-	bitVoltsSet->writeDataBlock(info->bitVolts.size(), F32, info->bitVolts.getRawDataPointer());
+	if (bitVoltsSet.get())
+		bitVoltsSet->writeDataBlock(info->bitVolts.size(), F32, info->bitVolts.getRawDataPointer());
+	else
+		std::cerr << "Error creating bitvolts data set" << std::endl;
 	
     CHECK_ERROR(setAttribute(U8,&mSample,recordPath+"/application_data",String("is_multiSampleRate_data")));
     CHECK_ERROR(setAttributeArray(F32,info->channelSampleRates.getRawDataPointer(),info->channelSampleRates.size(),recordPath+"/application_data",String("channel_sample_rates")));
     recdata = createDataSet(I16,0,nChannels,CHUNK_XSIZE,recordPath+"/data");
     if (!recdata.get())
         std::cerr << "Error creating data set" << std::endl;
+
+	tsData = createDataSet(I64, 0, nChannels, TIMESTAMP_CHUNK_SIZE, recordPath + "/application_data/timestamps");
+	if (!tsData.get())
+		std::cerr << "Error creating timestamps data set" << std::endl;
+
     curChan = nChannels;
 }
 
@@ -694,6 +706,7 @@ void KWDFile::stopRecording()
     CHECK_ERROR(setAttributeArray(U32,samples.getRawDataPointer(),samples.size(),path,"valid_samples"));
     //ScopedPointer does the deletion and destructors the closings
     recdata = nullptr;
+	tsData = nullptr;
 }
 
 int KWDFile::createFileStructure()
@@ -728,6 +741,14 @@ void KWDFile::writeRowData(int16* data, int nSamples, int channel)
 	}
 }
 
+void KWDFile::writeTimestamps(int64* ts, int nTs, int channel)
+{
+	if (channel >= 0 && channel < nChannels)
+	{
+		CHECK_ERROR(tsData->writeDataRow(channel, nTs, I64, ts));
+	}
+}
+
 //KWE File
 
 KWEFile::KWEFile(String basename) : HDF5FileBase()
diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h
index ca0f190f3..f98748373 100644
--- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h
+++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h
@@ -128,6 +128,7 @@ public:
     void writeBlockData(int16* data, int nSamples);
     void writeRowData(int16* data, int nSamples);
 	void writeRowData(int16* data, int nSamples, int channel);
+	void writeTimestamps(int64* ts, int nTs, int channel);
     String getFileName();
 
 protected:
@@ -140,6 +141,7 @@ private:
     String filename;
     bool multiSample;
     ScopedPointer<HDF5RecordingData> recdata;
+	ScopedPointer<HDF5RecordingData> tsData;
 
     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(KWDFile);
 };
diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp
index 3806adccc..c5d545c4e 100644
--- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp
+++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp
@@ -23,6 +23,8 @@
 
 #include "HDF5Recording.h"
 #define MAX_BUFFER_SIZE 10000
+#define CHANNEL_TIMESTAMP_PREALLOC_SIZE 16
+#define TIMESTAMP_EACH_NSAMPLES 1024
 
 HDF5Recording::HDF5Recording() : processorIndex(-1), hasAcquired(false)
 {
@@ -37,7 +39,7 @@ HDF5Recording::~HDF5Recording()
     delete intBuffer;
 }
 
-String HDF5Recording::getEngineID()
+String HDF5Recording::getEngineID() const
 {
     return "KWIK";
 }
@@ -73,6 +75,8 @@ void HDF5Recording::resetChannels()
     processorMap.clear();
     infoArray.clear();
 	recordedChanToKWDChan.clear();
+	channelLeftOverSamples.clear();
+	channelTimestampArray.clear();
     if (spikesFile)
         spikesFile->resetChannels();
 }
@@ -129,31 +133,11 @@ void HDF5Recording::openFiles(File rootFolder, int experimentNumber, int recordi
 		int procPos = processorRecPos[index];
 		recordedChanToKWDChan.add(procPos);
 		processorRecPos.set(index, procPos+1);
+		channelTimestampArray.add(new Array<int64>);
+		channelTimestampArray.getLast()->ensureStorageAllocated(CHANNEL_TIMESTAMP_PREALLOC_SIZE);
+		channelLeftOverSamples.add(0);
 	} 
-#if 0
-    for (int i = 0; i < processorMap.size(); i++)
-    {
-        int index = processorMap[i];
-        if (getChannel(i)->getRecordState())
-        {
-			if (!fileArray[index]->isOpen())
-            {
-                fileArray[index]->initFile(getChannel(i)->nodeId,basepath);
-                if (hasAcquired)
-                    infoArray[index]->start_time = (*timestamps)[getChannel(i)->sourceNodeId]; //the timestamps of the first channel
-                else
-                    infoArray[index]->start_time = 0;
-            }
-			channelsPerProcessor.set(index, channelsPerProcessor[index] + 1);
-            bitVoltsArray[index]->add(getChannel(i)->bitVolts);
-            sampleRatesArray[index]->add(getChannel(i)->sampleRate);
-            if (getChannel(i)->sampleRate != infoArray[index]->sample_rate)
-            {
-                infoArray[index]->multiSample = true;
-            }
-        }
-    }
-#endif
+
     for (int i = 0; i < fileArray.size(); i++)
     {
 		if ((!fileArray[i]->isOpen()) && (fileArray[i]->isReadyToOpen()))
@@ -196,6 +180,18 @@ void HDF5Recording::closeFiles()
         }
 		channelsPerProcessor.set(i, 0);
     }
+	recordedChanToKWDChan.clear();
+	channelTimestampArray.clear();
+	channelLeftOverSamples.clear();
+}
+
+void HDF5Recording::startChannelBlock()
+{
+	int nCh = channelTimestampArray.size();
+	for (int i = 0; i < nCh; ++i)
+	{
+		channelTimestampArray[i]->clearQuick();
+	}
 }
 
 void HDF5Recording::writeData(int writeChannel, int realChannel, const float* buffer, int size)
@@ -208,6 +204,48 @@ void HDF5Recording::writeData(int writeChannel, int realChannel, const float* bu
 	fileArray[index]->writeRowData(intBuffer, size, recordedChanToKWDChan[writeChannel]);
 //	int64 t2 = Time::getHighResolutionTicks();
 //	std::cout << "record time: " << float(t2 - t1) / float(Time::getHighResolutionTicksPerSecond()) << std::endl;
+	int64 sampleOffset = channelLeftOverSamples[writeChannel];
+	if (writeChannel == 0)
+		std::cout << "Write " << size << " off " << sampleOffset << " ts " << getTimestamp(realChannel) << " - ";
+	if (sampleOffset + size >= TIMESTAMP_EACH_NSAMPLES)
+	{
+		int64 currentTimestamp = getTimestamp(realChannel);
+		if (sampleOffset > 0)
+		{
+			currentTimestamp = getTimestamp(realChannel) + TIMESTAMP_EACH_NSAMPLES - sampleOffset;
+		}
+
+		for (int samp = 0; samp < size; samp += TIMESTAMP_EACH_NSAMPLES)
+		{
+			if (writeChannel == 0)
+				std::cout << "w: " << currentTimestamp << " ";
+			channelTimestampArray[writeChannel]->add(currentTimestamp);
+			currentTimestamp += TIMESTAMP_EACH_NSAMPLES;
+		}
+
+		channelLeftOverSamples.set(writeChannel, (size + sampleOffset) % TIMESTAMP_EACH_NSAMPLES);
+	}
+	else
+	{
+		channelLeftOverSamples.set(writeChannel, sampleOffset + size);
+	}
+	if (writeChannel == 0)
+		std::cout << std::endl;
+}
+
+void HDF5Recording::endChannelBlock()
+{
+	int nCh = channelTimestampArray.size();
+	for (int ch = 0; ch < nCh; ++ch)
+	{
+		int tsSize = channelTimestampArray[ch]->size();
+		if (tsSize > 0)
+		{
+			int realChan = getRealChannel(ch);
+			int index = processorMap[getChannel(realChan)->recordIndex];
+			fileArray[index]->writeTimestamps(channelTimestampArray[ch]->getRawDataPointer(), tsSize, ch);
+		}
+	}
 }
 
 void HDF5Recording::writeEvent(int eventType, const MidiMessage& event, int64 timestamp)
diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h
index a40f5f1e9..cc0ac27b9 100644
--- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h
+++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h
@@ -32,7 +32,7 @@ class HDF5Recording : public RecordEngine
 public:
     HDF5Recording();
     ~HDF5Recording();
-    String getEngineID();
+    String getEngineID() const override;
     void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override;
 	void closeFiles() override;
 	void writeData(int writeChannel, int realChannel, const float* buffer, int size) override;
@@ -43,6 +43,8 @@ public:
 	void registerProcessor(const GenericProcessor* processor) override;
 	void resetChannels() override;
 	void startAcquisition() override;
+	void startChannelBlock() override;
+	void endChannelBlock() override;
 
     static RecordEngineManager* getEngineManager();
 private:
@@ -54,6 +56,8 @@ private:
 	Array<int> recordedChanToKWDChan;
     OwnedArray<Array<float>> bitVoltsArray;
     OwnedArray<Array<float>> sampleRatesArray;
+	OwnedArray<Array<int64>> channelTimestampArray;
+	Array<int> channelLeftOverSamples;
     OwnedArray<KWDFile> fileArray;
     OwnedArray<HDF5RecordingInfo> infoArray;
     ScopedPointer<KWEFile> eventFile;
diff --git a/Source/Processors/RecordNode/OriginalRecording.cpp b/Source/Processors/RecordNode/OriginalRecording.cpp
index 5205d144e..7e5aab4d8 100644
--- a/Source/Processors/RecordNode/OriginalRecording.cpp
+++ b/Source/Processors/RecordNode/OriginalRecording.cpp
@@ -62,7 +62,7 @@ OriginalRecording::~OriginalRecording()
     delete recordMarker;*/
 }
 
-String OriginalRecording::getEngineID()
+String OriginalRecording::getEngineID() const
 {
     return "OPENEPHYS";
 }
diff --git a/Source/Processors/RecordNode/OriginalRecording.h b/Source/Processors/RecordNode/OriginalRecording.h
index 8a7e7dd3f..0aa252e12 100644
--- a/Source/Processors/RecordNode/OriginalRecording.h
+++ b/Source/Processors/RecordNode/OriginalRecording.h
@@ -45,7 +45,7 @@ public:
     ~OriginalRecording();
 
     void setParameter(EngineParameter& parameter);
-    String getEngineID();
+    String getEngineID() const override;
     void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override;
 	void closeFiles() override;
 	void writeData(int writeChannel, int realChannel, const float* buffer, int size) override;
diff --git a/Source/Processors/RecordNode/RecordEngine.h b/Source/Processors/RecordNode/RecordEngine.h
index 1be3109c1..a22271351 100644
--- a/Source/Processors/RecordNode/RecordEngine.h
+++ b/Source/Processors/RecordNode/RecordEngine.h
@@ -59,7 +59,7 @@ class PLUGIN_API RecordEngine
 public:
     RecordEngine();
     virtual ~RecordEngine();
-    virtual String getEngineID() =0;
+    virtual String getEngineID() const =0;
 
     /** All the public methods (except registerManager) are called by RecordNode or RecordingThread:
     When acquisition starts (in the specified order):
-- 
GitLab