Skip to content
Snippets Groups Projects
Commit 3fbf2d31 authored by Aaron Cuevas Lopez's avatar Aaron Cuevas Lopez
Browse files

Add Spike Recording to OriginalRecording Engine

parent 58d9c9cd
No related branches found
No related tags found
No related merge requests found
......@@ -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
......@@ -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);
......
......@@ -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
......@@ -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:
......
......@@ -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
......@@ -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();
......
......@@ -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
......@@ -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);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment