-
Aaron Cuevas Lopez authoredAaron Cuevas Lopez authored
OriginalRecording.cpp 14.92 KiB
/*
------------------------------------------------------------------
This file is part of the Open Ephys GUI
Copyright (C) 2013 Florian Franzen
------------------------------------------------------------------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "OriginalRecording.h"
#include "../Audio/AudioComponent.h"
OriginalRecording::OriginalRecording() : separateFiles(true), eventFile(nullptr),
recordingNumber(0), zeroBuffer(1, 50000), blockIndex(0)
{
continuousDataIntegerBuffer = new int16[10000];
continuousDataFloatBuffer = new float[10000];
recordMarker = new char[10];
for (int i = 0; i < 9; i++)
{
recordMarker[i] = i;
}
recordMarker[9] = 255;
zeroBuffer.clear();
}
OriginalRecording::~OriginalRecording()
{
//Cleanup just in case
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;
delete recordMarker;
}
void OriginalRecording::addChannel(int index, Channel* chan)
{
//Just populate the file array with null so we can address it by index afterwards
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)
{
this->recordingNumber = recordingNumber;
openFile(rootFolder,nullptr);
for (int i = 0; i < fileArray.size(); i++)
{
if (getChannel(i)->getRecordState())
{
openFile(rootFolder,getChannel(i));
}
}
for (int i = 0; i < spikeFileArray.size(); i++)
{
openSpikeFile(rootFolder,getSpikeElectrode(i));
}
blockIndex = 0;
}
void OriginalRecording::openFile(File rootFolder, Channel* ch)
{
FILE* chFile;
bool isEvent;
String fullPath(rootFolder.getFullPathName() + rootFolder.separatorString);
std::cout << "OPENING FILE: " << fullPath << std::endl;
isEvent = (ch == nullptr) ? true : false;
if (isEvent)
fullPath += "all_channels.events";
else
fullPath += getFileName(ch);
File f = File(fullPath);
bool fileExists = f.exists();
diskWriteLock.enter();
chFile = fopen(fullPath.toUTF8(), "ab");
if (!fileExists)
{
// create and write header
std::cout << "Writing header." << std::endl;
String header = generateHeader(ch);
//std::cout << header << std::endl;
std::cout << "File ID: " << chFile << ", number of bytes: " << header.getNumBytesAsUTF8() << std::endl;
fwrite(header.toUTF8(), 1, header.getNumBytesAsUTF8(), chFile);
std::cout << "Wrote header." << std::endl;
std::cout << "Block index: " << blockIndex << std::endl;
}
else
{
std::cout << "File already exists, just opening." << std::endl;
}
diskWriteLock.exit();
if (isEvent)
eventFile = chFile;
else
fileArray.set(ch->recordIndex,chFile);
}
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;
filename += ch->nodeId;
filename += "_";
filename += ch->name;
if (separateFiles)
{
filename += "_";
filename += recordingNumber;
}
filename += ".continuous";
return filename;
}
String OriginalRecording::generateHeader(Channel* ch)
{
String header = "header.format = 'Open Ephys Data Format'; \n";
header += "header.version = 0.2;";
header += "header.header_bytes = ";
header += String(HEADER_SIZE);
header += ";\n";
if (ch == nullptr)
{
header += "header.description = 'each record contains one 64-bit timestamp, one 16-bit sample position, one uint8 event type, one uint8 processor ID, one uint8 event ID, one uint8 event channel, and one uint16 recordingNumber'; \n";
}
else
{
header += "header.description = 'each record contains one 64-bit timestamp, one 16-bit sample count (N), 1 uint16 recordingNumber, N 16-bit samples, and one 10-byte record marker (0 1 2 3 4 5 6 7 8 255)'; \n";
}
header += "header.date_created = '";
header += generateDateString();
header += "';\n";
header += "header.channel = '";
header += (ch != nullptr) ? ch->name : "Events";
header += "';\n";
if (ch == nullptr)
{
header += "header.channelType = 'Event';\n";
}
else
{
header += "header.channelType = 'Continuous';\n";
}
header += "header.sampleRate = ";
// all channels need to have the same sample rate under the current scheme
header += String(getChannel(0)->sampleRate);
header += ";\n";
header += "header.blockLength = ";
header += BLOCK_LENGTH;
header += ";\n";
header += "header.bufferSize = ";
header += getAudioComponent()->getBufferSize();
header += ";\n";
header += "header.bitVolts = ";
header += (ch != nullptr) ? String(ch->bitVolts) : "1";
header += ";\n";
header = header.paddedRight(' ', HEADER_SIZE);
//std::cout << header << std::endl;
return header;
}
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
// std::cout << "Received event!" << std::endl;
if (eventFile == nullptr)
return;
const uint8* dataptr = event.getRawData();
if (event.getNoteNumber() > 0) // processor ID > 0
{
uint64 samplePos = (uint64) samplePosition;
int64 eventTimestamp = timestamp + samplePos; // add the sample position to the buffer timestamp
diskWriteLock.enter();
fwrite(&eventTimestamp, // ptr
8, // size of each element
1, // count
eventFile); // ptr to FILE object
fwrite(&samplePos, // ptr
2, // size of each element
1, // count
eventFile); // ptr to FILE object
// write 1st four bytes of event (type, nodeId, eventId, eventChannel)
fwrite(dataptr, 1, 4, eventFile);
// write recording number
fwrite(&recordingNumber, // ptr
2, // size of each element
1, // count
eventFile); // ptr to FILE object
diskWriteLock.exit();
}
}
void OriginalRecording::writeData(AudioSampleBuffer& buffer, int nSamples)
{
int samplesWritten = 0;
while (samplesWritten < nSamples) // there are still unwritten samples in the buffer
{
int numSamplesToWrite = nSamples - samplesWritten; // samples remaining in the buffer
if (blockIndex + numSamplesToWrite < BLOCK_LENGTH) // we still have space in this block
{
for (int i = 0; i < buffer.getNumChannels(); i++)
{
if (getChannel(i)->getRecordState())
{
// write buffer to disk!
writeContinuousBuffer(buffer.getReadPointer(i,samplesWritten),
numSamplesToWrite,
i);
}
}
// update our variables
samplesWritten += numSamplesToWrite;
timestamp += numSamplesToWrite;
blockIndex += numSamplesToWrite;
}
else // there's not enough space left in this block for all remaining samples
{
numSamplesToWrite = BLOCK_LENGTH - blockIndex;
for (int i = 0; i < buffer.getNumChannels(); i++)
{
if (getChannel(i)->getRecordState())
{
// write buffer to disk!
writeContinuousBuffer(buffer.getReadPointer(i,samplesWritten),
numSamplesToWrite,
i);
//std::cout << "Record channel " << i << std::endl;
}
}
// update our variables
samplesWritten += numSamplesToWrite;
timestamp += numSamplesToWrite;
blockIndex = 0; // back to the beginning of the block
}
}
}
void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, int channel)
{
// check to see if the file exists
if (fileArray[channel] == nullptr)
return;
// scale the data back into the range of int16
float scaleFactor = float(0x7fff) * getChannel(channel)->bitVolts;
for (int n = 0; n < nSamples; n++)
{
*(continuousDataFloatBuffer+n) = *(data+n) / scaleFactor;
}
AudioDataConverters::convertFloatToInt16BE(continuousDataFloatBuffer, continuousDataIntegerBuffer, nSamples);
if (blockIndex == 0)
{
writeTimestampAndSampleCount(fileArray[channel]);
}
diskWriteLock.enter();
size_t count = fwrite(continuousDataIntegerBuffer, // ptr
2, // size of each element
nSamples, // count
fileArray[channel]); // ptr to FILE object
jassert(count == nSamples); // make sure all the data was written
diskWriteLock.exit();
if (blockIndex + nSamples == BLOCK_LENGTH)
{
writeRecordMarker(fileArray[channel]);
}
}
void OriginalRecording::writeTimestampAndSampleCount(FILE* file)
{
diskWriteLock.enter();
uint16 samps = BLOCK_LENGTH;
fwrite(×tamp, // ptr
8, // size of each element
1, // count
file); // ptr to FILE object
fwrite(&samps, // ptr
2, // size of each element
1, // count
file); // ptr to FILE object
fwrite(&recordingNumber, // ptr
2, // size of each element
1, // count
file); // ptr to FILE object
diskWriteLock.exit();
}
void OriginalRecording::writeRecordMarker(FILE* file)
{
// write a 10-byte marker indicating the end of a record
diskWriteLock.enter();
fwrite(recordMarker, // ptr
1, // size of each element
10, // count
file); // ptr to FILE object
diskWriteLock.exit();
}
void OriginalRecording::closeFiles()
{
for (int i = 0; i < fileArray.size(); i++)
{
if (fileArray[i] != nullptr)
{
if (blockIndex < BLOCK_LENGTH)
{
// fill out the rest of the current buffer
writeContinuousBuffer(zeroBuffer.getReadPointer(0), BLOCK_LENGTH - blockIndex, i);
diskWriteLock.enter();
fclose(fileArray[i]);
fileArray.set(i,nullptr);
diskWriteLock.exit();
}
}
}
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();
fclose(eventFile);
eventFile = nullptr;
diskWriteLock.exit();
}
blockIndex = 0;
}
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();
}