Skip to content
Snippets Groups Projects
NWBRecording.cpp 6.58 KiB
/*
 ------------------------------------------------------------------

 This file is part of the Open Ephys GUI
 Copyright (C) 2014 Open Ephys

 ------------------------------------------------------------------

 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 "NWBRecording.h"
#define MAX_BUFFER_SIZE 40960
 
 using namespace NWBRecording;
 
 NWBRecordEngine::NWBRecordEngine() 
 {
	 
	 tsBuffer.malloc(MAX_BUFFER_SIZE);
 }
 
 NWBRecordEngine::~NWBRecordEngine()
 {
 }
 
 String NWBRecordEngine::getEngineID() const
 {
	 return "NWB"; //a text identifier
 }
 
 void NWBRecordEngine::openFiles(File rootFolder, int experimentNumber, int recordingNumber)
 {
	 
	 //Called when acquisition starts, to open the files
	 String basepath = rootFolder.getFullPathName() + rootFolder.separatorString + "experiment_" + String(experimentNumber) + ".nwb";
	 
	 recordFile = new NWBFile(basepath, CoreServices::getGUIVersion(), identifierText);
	 recordFile->setXmlText(getLatestSettingsXml());

	 int recProcs = getNumRecordedProcessors();

	 datasetIndexes.insertMultiple(0, 0, getNumRecordedChannels());
	 writeChannelIndexes.insertMultiple(0, 0, getNumRecordedChannels());

	 //Generate the continuous datasets info array, seeking for different combinations of recorded processor and source processor
	 int lastId = 0;
	 for (int proc = 0; proc < recProcs; proc++)
	 {
		 const RecordProcessorInfo& procInfo = getProcessorInfo(proc);
		 int recChans = procInfo.recordedChannels.size();
		 for (int chan = 0; chan < recChans; chan++)
		 {
			 int recordedChan = procInfo.recordedChannels[chan];
			 int realChan = getRealChannel(recordedChan);
			 const DataChannel* channelInfo = getDataChannel(realChan);
			 int sourceId = channelInfo->getSourceNodeID();
			 int sourceSubIdx = channelInfo->getSubProcessorIdx();
			 int nInfoArrays = continuousChannels.size();
			 bool found = false;
			 for (int i = lastId; i < nInfoArrays; i++)
			 {
				 if (sourceId == continuousChannels.getReference(i)[0]->getSourceNodeID() && sourceSubIdx == continuousChannels.getReference(i)[0]->getSubProcessorIdx())
				 {
					 //A dataset for the current processor from the current source is already present
					 writeChannelIndexes.set(recordedChan, continuousChannels.getReference(i).size());
					 continuousChannels.getReference(i).add(getDataChannel(realChan));
					 datasetIndexes.set(recordedChan, i);
					 found = true;
					 break;
				 }
			 }
			 if (!found) //a new dataset must be created
			 {
				 ContinuousGroup newGroup;
				 newGroup.add(getDataChannel(realChan));
				 continuousChannels.add(newGroup);
				 datasetIndexes.set(recordedChan, nInfoArrays);
				 writeChannelIndexes.set(recordedChan, 0);
			 }

		 }
		 lastId = continuousChannels.size();
	 }
	 int nEvents = getNumRecordedEvents();
	 for (int i = 0; i < nEvents; i++)
		 eventChannels.add(getEventChannel(i));

	 int nSpikes = getNumRecordedSpikes();
	 for (int i = 0; i < nSpikes; i++)
		 spikeChannels.add(getSpikeChannel(i));

	 //open the file
	 recordFile->open(getNumRecordedChannels() + continuousChannels.size() + eventChannels.size() + spikeChannels.size()); //total channels + timestamp arrays, to create a big enough buffer

	 //create the recording
	 recordFile->startNewRecording(recordingNumber, continuousChannels, eventChannels, spikeChannels);
	
 }

 
 void NWBRecordEngine::closeFiles()
 {
	 //Called when acquisition stops. Should close the files and leave the processor in a reset status
	 recordFile->stopRecording();
	 recordFile->close();
	 recordFile = nullptr;
	 resetChannels();
 }


 
 void NWBRecordEngine::resetChannels()
 {
	 spikeChannels.clear();
	 eventChannels.clear();
	 continuousChannels.clear();
	 datasetIndexes.clear();
	 writeChannelIndexes.clear();
	 tsBuffer.malloc(MAX_BUFFER_SIZE);
	 bufferSize = MAX_BUFFER_SIZE;
 }
 
 void NWBRecordEngine::writeData(int writeChannel, int realChannel, const float* buffer, int size)
 {

	 recordFile->writeData(datasetIndexes[writeChannel], writeChannelIndexes[writeChannel], size, buffer, getDataChannel(realChannel)->getBitVolts());
	 /* All channels in a dataset have the same number of samples and share timestamps. But since this method is called 
		asynchronously, the timestamps might not be in sync during acquisition, so we chose a channel and write the
		timestamps when writing that channel's data */
	 if (writeChannelIndexes[writeChannel] == 0)
	 {
		 int64 baseTS = getTimestamp(writeChannel);
		 double fs = getDataChannel(realChannel)->getSampleRate();
		 //Let's hope that the compiler is smart enough to vectorize this. 
		 for (int i = 0; i < size; i++)
		 {
			 tsBuffer[i] = (baseTS + i) / fs;
		 }
		 recordFile->writeTimestamps(datasetIndexes[writeChannel], size, tsBuffer);
	 }
		 
 }
 
void NWBRecordEngine::writeEvent(int eventIndex, const MidiMessage& event) 
{
	const EventChannel* channel = getEventChannel(eventIndex);
	EventPtr eventStruct = Event::deserializeFromMessage(event, channel);

	recordFile->writeEvent(eventIndex, channel, eventStruct);
}

void NWBRecordEngine::writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float sourceSampleRate, String text)
{
	recordFile->writeTimestampSyncText(sourceID, timestamp, sourceSampleRate, text);
}

void NWBRecordEngine::addSpikeElectrode(int index,const  SpikeChannel* elec) 
{
}

void NWBRecordEngine::writeSpike(int electrodeIndex, const SpikeEvent* spike) 
{
	const SpikeChannel* channel = getSpikeChannel(electrodeIndex);

	recordFile->writeSpike(electrodeIndex, channel, spike);
}

RecordEngineManager* NWBRecordEngine::getEngineManager()
{
	//static factory that instantiates the engine manager, which allows to configure recording options among other things. See OriginalRecording to see how to create options for a record engine
	RecordEngineManager* man = new RecordEngineManager("NWB", "NWB", &(engineFactory<NWBRecordEngine>));
	EngineParameter* param;
	param = new EngineParameter(EngineParameter::STR, 0, "Identifier Text", String::empty);
	man->addParameter(param);
	return man;
	
}

void NWBRecordEngine::setParameter(EngineParameter& parameter)
{
	strParameter(0, identifierText);
}