diff --git a/Source/Processors/OriginalRecording.cpp b/Source/Processors/OriginalRecording.cpp index 3252146e2697e9cda450fa2b562cf01dbe5bf644..d8455eeaedc3c2f29b30e06ac3663495894b17e4 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 8755e0ca0e4299a223c110320d31ecb885a18221..876278316d60a209c1493dbe9ec7df632bb497ed 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 c2d1cadbb5336bdbe7b42c7bb1060ece2e144c74..6450fb14aa6e6475e876fcc3f0ba3f63a51dcff1 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 67abe072aa7a42c444e736e0e3f1cb392f21db0a..da5afdf15e4093550967f2e80ea3d41f7a5ee342 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 ccaad9a3491a92f317944717d499520a4c7225f5..cbbaac5bf15f70d256cf2b5535b4ac1b0c0143f5 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 f4a7cc70e2c9cdff0a81d41c94e779851bbe5fdf..2291db99ac016509610d71e7f3cbeef899290240 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 6543f0292ba116fb83852b55806e5c88c167d657..c9d518db35848d1d0513c4e5b486879332d5dd77 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 b6909c2f63091653d36cba159bf4ef99c4840747..3fa0c364b97b33a31d1efa93a750b3379a8e285d 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);