From 3fbf2d31cba036b4922b4aeaa0d9edabfbdb9f7a Mon Sep 17 00:00:00 2001
From: Aaron Cuevas Lopez <aacuelo@teleco.upv.es>
Date: Fri, 29 Aug 2014 21:01:22 +0200
Subject: [PATCH] Add Spike Recording to OriginalRecording Engine

---
 Source/Processors/OriginalRecording.cpp | 123 +++++++++++++++
 Source/Processors/OriginalRecording.h   |   6 +
 Source/Processors/RecordEngine.cpp      |   9 +-
 Source/Processors/RecordEngine.h        |  14 ++
 Source/Processors/RecordNode.cpp        |  28 ++++
 Source/Processors/RecordNode.h          |  12 ++
 Source/Processors/SpikeDisplayNode.cpp  | 191 ++----------------------
 Source/Processors/SpikeDisplayNode.h    |  26 ++--
 8 files changed, 217 insertions(+), 192 deletions(-)

diff --git a/Source/Processors/OriginalRecording.cpp b/Source/Processors/OriginalRecording.cpp
index 3252146e2..d8455eeae 100644
--- a/Source/Processors/OriginalRecording.cpp
+++ b/Source/Processors/OriginalRecording.cpp
@@ -46,6 +46,10 @@ OriginalRecording::~OriginalRecording()
     for (int i=0; i < fileArray.size(); i++)
     {
         if (fileArray[i] != nullptr) fclose(fileArray[i]);
+    }
+	for (int i=0; i < spikeFileArray.size(); i++)
+    {
+        if (spikeFileArray[i] != nullptr) fclose(spikeFileArray[i]);
     }
 	delete continuousDataFloatBuffer;
 	delete continuousDataIntegerBuffer;
@@ -58,9 +62,15 @@ void OriginalRecording::addChannel(int index, Channel* chan)
     fileArray.add(nullptr);
 }
 
+void OriginalRecording::addSpikeElectrode(int index, SpikeRecordInfo* elec)
+{
+	spikeFileArray.add(nullptr);
+}
+
 void OriginalRecording::resetChannels()
 {
     fileArray.clear();
+	spikeFileArray.clear();
 }
 
 void OriginalRecording::openFiles(File rootFolder, int recordingNumber)
@@ -74,6 +84,10 @@ void OriginalRecording::openFiles(File rootFolder, int recordingNumber)
             openFile(rootFolder,getChannel(i));
         }
     }
+	for (int i = 0; i < spikeFileArray.size(); i++)
+	{
+		openSpikeFile(rootFolder,getSpikeElectrode(i));
+	}
 	blockIndex = 0;
 }
 
@@ -129,6 +143,34 @@ void OriginalRecording::openFile(File rootFolder, Channel* ch)
 
 }
 
+void OriginalRecording::openSpikeFile(File rootFolder, SpikeRecordInfo* elec)
+{
+
+	FILE* spFile;
+	String fullPath(rootFolder.getFullPathName() + rootFolder.separatorString);
+	fullPath += elec->name.removeCharacters(" ");
+	fullPath += ".spikes";
+
+	std::cout << "OPENING FILE: " << fullPath << std::endl;
+
+	File f = File(fullPath);
+
+    bool fileExists = f.exists();
+
+    diskWriteLock.enter();
+
+	spFile = fopen(fullPath.toUTF8(),"ab");
+
+	if (!fileExists)
+	{
+		String header = generateSpikeHeader(elec);
+		fwrite(header.toUTF8(), 1, header.getNumBytesAsUTF8(), spFile);
+	}
+	diskWriteLock.exit();
+	spikeFileArray.set(elec->recordIndex,spFile);
+
+}
+
 String OriginalRecording::getFileName(Channel* ch)
 {
     String filename;
@@ -208,6 +250,39 @@ String OriginalRecording::generateHeader(Channel* ch)
 
 }
 
+String OriginalRecording::generateSpikeHeader(SpikeRecordInfo* elec)
+{
+	String header = "header.format = 'Open Ephys Data Format'; \n";
+    header += "header.version = 0.2;";
+    header += "header.header_bytes = ";
+    header += String(HEADER_SIZE);
+    header += ";\n";
+
+    header += "header.description = 'Each record contains 1 uint8 eventType, 1 uint64 timestamp, 1 uint16 electrodeID, 1 uint16 numChannels (n), 1 uint16 numSamples (m), n*m uint16 samples, n uint16 channelGains, n uint16 thresholds, and 1 uint16 recordingNumber'; \n";
+
+    header += "header.date_created = '";
+    header += generateDateString();
+    header += "';\n";
+
+    header += "header.electrode = '";
+    header += elec->name;
+    header += "';\n";
+
+    header += "header.num_channels = ";
+    header += elec->numChannels;
+    header += ";\n";
+
+    header += "header.sampleRate = ";
+    header += String(elec->sampleRate);
+    header += ";\n";
+
+    header = header.paddedRight(' ', HEADER_SIZE);
+
+    //std::cout << header << std::endl;
+
+    return header;
+}
+
 void OriginalRecording::writeEvent(MidiMessage& event, int samplePosition)
 {
     // find file and write samples to disk
@@ -399,6 +474,16 @@ void OriginalRecording::closeFiles()
                 }
 		}
 	}
+	for (int i = 0; i < spikeFileArray.size(); i++)
+	{
+		if (spikeFileArray[i] != nullptr)
+		{
+			diskWriteLock.enter();
+			fclose(spikeFileArray[i]);
+			spikeFileArray.set(i,nullptr);
+			diskWriteLock.exit();
+		}
+	}
 	if (eventFile != nullptr)
 	{
 		diskWriteLock.enter();
@@ -412,4 +497,42 @@ void OriginalRecording::closeFiles()
 void OriginalRecording::updateTimeStamp(int64 timestamp)
 {
 	this->timestamp = timestamp;
+}
+
+void OriginalRecording::writeSpike(const SpikeObject& spike, int electrodeIndex)
+{
+	uint8_t spikeBuffer[MAX_SPIKE_BUFFER_LEN];
+
+	if (spikeFileArray[electrodeIndex] == nullptr)
+		return;
+
+	packSpike(&spike, spikeBuffer, MAX_SPIKE_BUFFER_LEN);
+
+    int totalBytes = spike.nSamples * spike.nChannels * 2 + // account for samples
+                     spike.nChannels * 4 +            // acount for threshold and gain
+                     15;                        // 15 bytes in every SpikeObject
+
+
+    // format:
+    // 1 byte of event type (always = 4 for spikes)
+    // 8 bytes for 64-bit timestamp
+    // 2 bytes for 16-bit electrode ID
+    // 2 bytes for 16-bit number of channels (n)
+    // 2 bytes for 16-bit number of samples (m)
+    // 2*n*m bytes for 16-bit samples
+    // 2*n bytes for 16-bit gains
+    // 2*n bytes for 16-bit thresholds
+    
+   // const MessageManagerLock mmLock;
+    
+    diskWriteLock.enter();
+
+	fwrite(spikeBuffer, 1, totalBytes, spikeFileArray[electrodeIndex]);
+    
+    fwrite(&recordingNumber,                         // ptr
+       2,                               // size of each element
+       1,                               // count
+       spikeFileArray[electrodeIndex]); // ptr to FILE object
+
+    diskWriteLock.exit();
 }
\ No newline at end of file
diff --git a/Source/Processors/OriginalRecording.h b/Source/Processors/OriginalRecording.h
index 8755e0ca0..876278316 100644
--- a/Source/Processors/OriginalRecording.h
+++ b/Source/Processors/OriginalRecording.h
@@ -45,6 +45,8 @@ public:
 	void addChannel(int index, Channel* chan);
 	void resetChannels();
 	void updateTimeStamp(int64 timestamp);
+	void addSpikeElectrode(int index, SpikeRecordInfo* elec);
+	void writeSpike(const SpikeObject& spike, int electrodeIndex);
 
 private:
 	String getFileName(Channel* ch);
@@ -54,6 +56,9 @@ private:
 	void writeTimestampAndSampleCount(FILE* file);
 	void writeRecordMarker(FILE* file);
 
+	void openSpikeFile(File rootFolder, SpikeRecordInfo* elec);
+	String generateSpikeHeader(SpikeRecordInfo* elec);
+
 	bool separateFiles;
 	int blockIndex;
 	int recordingNumber;
@@ -76,6 +81,7 @@ private:
 
 	FILE* eventFile;
 	Array<FILE*> fileArray;
+	Array<FILE*> spikeFileArray;
 	CriticalSection diskWriteLock;
 
 	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OriginalRecording);
diff --git a/Source/Processors/RecordEngine.cpp b/Source/Processors/RecordEngine.cpp
index c2d1cadbb..6450fb14a 100644
--- a/Source/Processors/RecordEngine.cpp
+++ b/Source/Processors/RecordEngine.cpp
@@ -43,4 +43,11 @@ String RecordEngine::generateDateString()
 	return getProcessorGraph()->getRecordNode()->generateDateString();
 }
 
-void RecordEngine::updateTimeStamp(int64 timestamp) {}
\ No newline at end of file
+SpikeRecordInfo* RecordEngine::getSpikeElectrode(int index)
+{
+	return getProcessorGraph()->getRecordNode()->getSpikeElectrode(index);
+}
+
+void RecordEngine::updateTimeStamp(int64 timestamp) {}
+
+void RecordEngine::registerSpikeSource(GenericProcessor* processor) {}
\ No newline at end of file
diff --git a/Source/Processors/RecordEngine.h b/Source/Processors/RecordEngine.h
index 67abe072a..da5afdf15 100644
--- a/Source/Processors/RecordEngine.h
+++ b/Source/Processors/RecordEngine.h
@@ -27,6 +27,16 @@
 #include "../../JuceLibraryCode/JuceHeader.h"
 #include "Channel.h"
 #include "GenericProcessor.h"
+#include "Visualization\SpikeObject.h"
+
+struct SpikeRecordInfo
+{
+	String name;
+	int numChannels;
+	int sampleRate;
+
+	int recordIndex;
+};
 
 class RecordNode;
 
@@ -41,12 +51,16 @@ public:
 	virtual void writeData(AudioSampleBuffer& buffer, int nSamples) =0;
 	virtual void writeEvent(MidiMessage& event, int samplePosition) =0;
 	virtual void addChannel(int index, Channel* chan) =0;
+	virtual void addSpikeElectrode(int index, SpikeRecordInfo* elec) =0;
+	virtual void writeSpike(const SpikeObject& spike, int electrodeIndex) =0;
 	virtual void registerProcessor(GenericProcessor* processor);
+	virtual void registerSpikeSource(GenericProcessor* processor);
 	virtual void resetChannels();
 	virtual void updateTimeStamp(int64 timestamp);
 
 protected:
 	Channel* getChannel(int index);
+	SpikeRecordInfo* getSpikeElectrode(int index);
 	String generateDateString();
 
 private:
diff --git a/Source/Processors/RecordNode.cpp b/Source/Processors/RecordNode.cpp
index ccaad9a34..cbbaac5bf 100755
--- a/Source/Processors/RecordNode.cpp
+++ b/Source/Processors/RecordNode.cpp
@@ -49,6 +49,8 @@ RecordNode::RecordNode()
 
     recordingNumber = 0;
 
+	spikeElectrodeIndex = 0;
+
     // 128 inputs, 0 outputs
     setPlayConfigDetails(getNumInputs(),getNumOutputs(),44100.0,128);
 
@@ -106,9 +108,12 @@ void RecordNode::resetConnections()
     //std::cout << "Resetting connections" << std::endl;
     nextAvailableChannel = 0;
     wasConnected = false;
+	spikeElectrodeIndex = 0;
 
     channelPointers.clear();
     eventChannelPointers.clear();
+	spikeElectrodePointers.clear();
+
 	EVERY_ENGINE->resetChannels();
 
 }
@@ -436,4 +441,27 @@ Channel* RecordNode::getDataChannel(int index)
 void RecordNode::registerRecordEngine(RecordEngine* engine)
 {
 	engineArray.add(engine);
+}
+
+void RecordNode::registerSpikeSource(GenericProcessor* processor)
+{
+	EVERY_ENGINE->registerSpikeSource(processor);
+}
+
+int RecordNode::addSpikeElectrode(SpikeRecordInfo* elec)
+{
+	elec->recordIndex=spikeElectrodeIndex;
+	spikeElectrodePointers.add(elec);
+	EVERY_ENGINE->addSpikeElectrode(spikeElectrodeIndex,elec);
+	return spikeElectrodeIndex++;
+}
+
+void RecordNode::writeSpike(SpikeObject& spike, int electrodeIndex)
+{
+	EVERY_ENGINE->writeSpike(spike,electrodeIndex);
+}
+
+SpikeRecordInfo* RecordNode::getSpikeElectrode(int index)
+{
+	return spikeElectrodePointers[index];
 }
\ No newline at end of file
diff --git a/Source/Processors/RecordNode.h b/Source/Processors/RecordNode.h
index f4a7cc70e..2291db99a 100755
--- a/Source/Processors/RecordNode.h
+++ b/Source/Processors/RecordNode.h
@@ -114,6 +114,14 @@ public:
 
 	void registerRecordEngine(RecordEngine* engine);
 
+	void registerSpikeSource(GenericProcessor* processor);
+
+	int addSpikeElectrode(SpikeRecordInfo* elec);
+
+	void writeSpike(SpikeObject& spike, int electrodeIndex);
+
+	SpikeRecordInfo* getSpikeElectrode(int index);
+
     /** Signals when to create a new data directory when recording starts.*/
     bool newDirectoryNeeded;
 
@@ -160,6 +168,10 @@ private:
     /** Pointers to all event channels */
     Array<Channel*> eventChannelPointers;
 
+	OwnedArray<SpikeRecordInfo> spikeElectrodePointers;
+
+	int spikeElectrodeIndex;;
+
     /** Generates a default directory name, based on the current date and time */
     String generateDirectoryName();
 
diff --git a/Source/Processors/SpikeDisplayNode.cpp b/Source/Processors/SpikeDisplayNode.cpp
index 6543f0292..c9d518db3 100755
--- a/Source/Processors/SpikeDisplayNode.cpp
+++ b/Source/Processors/SpikeDisplayNode.cpp
@@ -30,14 +30,9 @@
 
 
 SpikeDisplayNode::SpikeDisplayNode()
-    : GenericProcessor("Spike Viewer"), displayBufferSize(5),  redrawRequested(false), isRecording(false),
-	  signalFilesShouldClose(false)
+    : GenericProcessor("Spike Viewer"), displayBufferSize(5),  redrawRequested(false),
+	isRecording(false)
 {
- 
-
-    spikeBuffer = new uint8_t[MAX_SPIKE_BUFFER_LEN]; // MAX_SPIKE_BUFFER_LEN defined in SpikeObject.h
-
-    recordingNumber = -1;
 
 }
 
@@ -60,7 +55,7 @@ void SpikeDisplayNode::updateSettings()
     //std::cout << "Setting num inputs on SpikeDisplayNode to " << getNumInputs() << std::endl;
 
     electrodes.clear();
-
+	getProcessorGraph()->getRecordNode()->registerSpikeSource(this);
     for (int i = 0; i < eventChannels.size(); i++)
     {
         if ((eventChannels[i]->eventType < 999) && (eventChannels[i]->eventType > SPIKE_BASE_CODE))
@@ -77,14 +72,12 @@ void SpikeDisplayNode::updateSettings()
                 elec.displayThresholds.add(0);
                 elec.detectorThresholds.add(0);
             }
-            
+
             electrodes.add(elec);
+
         }
     }
 
-    recordNode = getProcessorGraph()->getRecordNode();
-//    diskWriteLock = recordNode->getLock(); TODO: move spike writing to record engine
-
 }
 
 // void SpikeDisplayNode::updateVisualizer()
@@ -96,6 +89,16 @@ bool SpikeDisplayNode::enable()
 {
     std::cout << "SpikeDisplayNode::enable()" << std::endl;
     SpikeDisplayEditor* editor = (SpikeDisplayEditor*) getEditor();
+	for (int i = 0; i < electrodes.size(); i ++)
+	{
+		Electrode& elec = electrodes.getReference(i);
+		SpikeRecordInfo *recElec = new SpikeRecordInfo();
+		recElec->name = elec.name;
+		recElec->numChannels = elec.numChannels;
+		recElec->sampleRate = settings.sampleRate;
+		elec.recordIndex = getProcessorGraph()->getRecordNode()->addSpikeElectrode(recElec);
+	}
+
     editor->enable();
     return true;
 
@@ -171,27 +174,11 @@ void SpikeDisplayNode::setParameter(int param, float val)
     if (param == 0) // stop recording
     {
         isRecording = false;
-        signalFilesShouldClose = true;
 
     } else if (param == 1) // start recording
     {
         isRecording = true;
 
-        dataDirectory = recordNode->getDataDirectory();
-
-        if (dataDirectory.getFullPathName().length() == 0)
-        {
-            // temporary fix in case nothing is returned by the record node.
-            dataDirectory = File::getSpecialLocation(File::userHomeDirectory); 
-        }
-
-        baseDirectory = dataDirectory.getFullPathName();
-
-        for (int i = 0; i < getNumElectrodes(); i++)
-        {
-            openFile(i);
-        }
-
     } else if (param == 2) // redraw
     {
         redrawRequested = true;
@@ -207,16 +194,6 @@ void SpikeDisplayNode::process(AudioSampleBuffer& buffer, MidiBuffer& events, in
 
     checkForEvents(events); // automatically calls 'handleEvent
 
-    if (signalFilesShouldClose)
-    {
-        for (int i = 0; i < getNumElectrodes(); i++)
-        {
-            closeFile(i);
-        }
-
-        signalFilesShouldClose = false;
-    }
-
     if (redrawRequested)
     {
         // update incoming thresholds
@@ -298,7 +275,7 @@ void SpikeDisplayNode::handleEvent(int eventType, MidiMessage& event, int sample
                     // save spike
                     if (isRecording)
                     {
-                        writeSpike(newSpike, electrodeNum);
+						getProcessorGraph()->getRecordNode()->writeSpike(newSpike,e.recordIndex);
                     }
                 }
 
@@ -327,139 +304,3 @@ bool SpikeDisplayNode::checkThreshold(int chan, float thresh, SpikeObject& s)
 
     return false;
 }
-
-void SpikeDisplayNode::openFile(int i)
-{
-
-    String filename = baseDirectory;
-    filename += File::separator;
-    filename += getNameForElectrode(i).removeCharacters(" ");
-    filename += ".spikes";
-
-    std::cout << "OPENING FILE: " << filename << std::endl;
-
-    File fileToUse = File(filename);
-
-    diskWriteLock->enter();
-    //const MessageManagerLock mmLock;
-
-    Electrode& e = electrodes.getReference(i);
-
-    FILE* file;
-    
-    if (!fileToUse.exists())
-    {
-        // open it and write header
-
-        if (i == 0)
-        {
-            recordingNumber = 0;
-        }
-
-        file = fopen(filename.toUTF8(), "ab");
-        String header = generateHeader(i);
-        fwrite(header.toUTF8(), 1, header.getNumBytesAsUTF8(), file);
-
-    }
-    else
-    {
-        // append it
-         if (i == 0)
-        {
-            recordingNumber++;
-        }
-
-        file = fopen(filename.toUTF8(), "ab");
-    }
-    
-    diskWriteLock->exit();
-
-    e.file = file;
-}
-
-void SpikeDisplayNode::closeFile(int i)
-{
-
-    Electrode& e = electrodes.getReference(i);
-
-    std::cout << "CLOSING FILE for " << e.name << std::endl;
-    
-    diskWriteLock->enter();
-    
-    if (e.file != NULL)
-    {
-        fclose(e.file);
-    }
-    
-    diskWriteLock->exit();
-
-}
-
-void SpikeDisplayNode::writeSpike(const SpikeObject& s, int i)
-{
-
-    packSpike(&s, spikeBuffer, MAX_SPIKE_BUFFER_LEN);
-
-    int totalBytes = s.nSamples * s.nChannels * 2 + // account for samples
-                     s.nChannels * 4 +            // acount for threshold and gain
-                     15;                        // 15 bytes in every SpikeObject
-
-
-    // format:
-    // 1 byte of event type (always = 4 for spikes)
-    // 8 bytes for 64-bit timestamp
-    // 2 bytes for 16-bit electrode ID
-    // 2 bytes for 16-bit number of channels (n)
-    // 2 bytes for 16-bit number of samples (m)
-    // 2*n*m bytes for 16-bit samples
-    // 2*n bytes for 16-bit gains
-    // 2*n bytes for 16-bit thresholds
-    
-   // const MessageManagerLock mmLock;
-    
-    diskWriteLock->enter();
-
-    fwrite(spikeBuffer, 1, totalBytes, electrodes[i].file);
-    
-    fwrite(&recordingNumber,                         // ptr
-       2,                               // size of each element
-       1,                               // count
-       electrodes[i].file); // ptr to FILE object
-
-    diskWriteLock->exit();
-
-
-}
-
-String SpikeDisplayNode::generateHeader(int electrodeNum)
-{
-    String header = "header.format = 'Open Ephys Data Format'; \n";
-    header += "header.version = 0.2;";
-    header += "header.header_bytes = ";
-    header += String(HEADER_SIZE);
-    header += ";\n";
-
-    header += "header.description = 'Each record contains 1 uint8 eventType, 1 uint64 timestamp, 1 uint16 electrodeID, 1 uint16 numChannels (n), 1 uint16 numSamples (m), n*m uint16 samples, n uint16 channelGains, n uint16 thresholds, and 1 uint16 recordingNumber'; \n";
-
-    header += "header.date_created = '";
-    header += recordNode->generateDateString();
-    header += "';\n";
-
-    header += "header.electrode = '";
-    header += electrodes[electrodeNum].name;
-    header += "';\n";
-
-    header += "header.num_channels = ";
-    header += electrodes[electrodeNum].numChannels;
-    header += ";\n";
-
-    header += "header.sampleRate = ";
-    header += String(settings.sampleRate);
-    header += ";\n";
-
-    header = header.paddedRight(' ', HEADER_SIZE);
-
-    //std::cout << header << std::endl;
-
-    return header;
-}
\ No newline at end of file
diff --git a/Source/Processors/SpikeDisplayNode.h b/Source/Processors/SpikeDisplayNode.h
index b6909c2f6..3fa0c364b 100755
--- a/Source/Processors/SpikeDisplayNode.h
+++ b/Source/Processors/SpikeDisplayNode.h
@@ -95,7 +95,7 @@ private:
 
         SpikePlot* spikePlot;
 
-        FILE* file;
+        int recordIndex;
 
     };
 
@@ -104,24 +104,18 @@ private:
     int displayBufferSize;
     bool redrawRequested;
 
-    // methods for recording:
-    void openFile(int index);
-    void closeFile(int index);
-    void writeSpike(const SpikeObject& s, int index);
-    String generateHeader(int index);
-
     // members for recording
     bool isRecording;
-    bool signalFilesShouldClose;
-    RecordNode* recordNode;
-    String baseDirectory;
-    File dataDirectory;
-    uint8_t* spikeBuffer;
-    SpikeObject currentSpike;
-
-    uint16 recordingNumber;
+ //   bool signalFilesShouldClose;
+ //   RecordNode* recordNode;
+ //   String baseDirectory;
+ //   File dataDirectory;
+ //   uint8_t* spikeBuffer;
+ //   SpikeObject currentSpike;
+
+ //   uint16 recordingNumber;
     
-    CriticalSection* diskWriteLock;
+//    CriticalSection* diskWriteLock;
 
     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SpikeDisplayNode);
 
-- 
GitLab