From 01c823e644c1e0aaba459fb62645077d15422fa2 Mon Sep 17 00:00:00 2001 From: Martin Spacek Date: Fri, 18 May 2018 02:10:53 +0200 Subject: [PATCH 01/97] Add BusseLabBinaryWriter plugin, copied from BinaryWriter plugin --- .../BusseLabBinaryWriter/BinaryRecording.cpp | 641 ++++++++++++++++++ .../BusseLabBinaryWriter/BinaryRecording.h | 101 +++ .../BusseLabBinaryWriter/FileMemoryBlock.h | 68 ++ Source/Plugins/BusseLabBinaryWriter/Makefile | 38 ++ .../Plugins/BusseLabBinaryWriter/NpyFile.cpp | 218 ++++++ Source/Plugins/BusseLabBinaryWriter/NpyFile.h | 68 ++ .../BusseLabBinaryWriter/OpenEphysLib.cpp | 70 ++ .../SequentialBlockFile.cpp | 167 +++++ .../SequentialBlockFile.h | 63 ++ 9 files changed, 1434 insertions(+) create mode 100644 Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp create mode 100644 Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h create mode 100644 Source/Plugins/BusseLabBinaryWriter/FileMemoryBlock.h create mode 100644 Source/Plugins/BusseLabBinaryWriter/Makefile create mode 100644 Source/Plugins/BusseLabBinaryWriter/NpyFile.cpp create mode 100644 Source/Plugins/BusseLabBinaryWriter/NpyFile.h create mode 100644 Source/Plugins/BusseLabBinaryWriter/OpenEphysLib.cpp create mode 100644 Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.cpp create mode 100644 Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.h diff --git a/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp new file mode 100644 index 0000000000..502cd770a7 --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp @@ -0,0 +1,641 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "BinaryRecording.h" + +#define MAX_BUFFER_SIZE 40960 + +using namespace BinaryRecordingEngine; + +BinaryRecording::BinaryRecording() +{ + m_scaledBuffer.malloc(MAX_BUFFER_SIZE); + m_intBuffer.malloc(MAX_BUFFER_SIZE); + m_tsBuffer.malloc(MAX_BUFFER_SIZE); +} + +BinaryRecording::~BinaryRecording() +{ + +} + +String BinaryRecording::getEngineID() const +{ + return "RAWBINARY"; +} + +String BinaryRecording::getProcessorString(const InfoObjectCommon* channelInfo) +{ + String fName = (channelInfo->getCurrentNodeName().replaceCharacter(' ', '_') + "-" + String(channelInfo->getCurrentNodeID())); + if (channelInfo->getCurrentNodeID() == channelInfo->getSourceNodeID()) //it is the channel source + { + fName += "." + String(channelInfo->getSubProcessorIdx()); + } + else + { + fName += "_" + String(channelInfo->getSourceNodeID()) + "." + String(channelInfo->getSubProcessorIdx()); + } + fName += File::separatorString; + return fName; +} + +void BinaryRecording::openFiles(File rootFolder, int experimentNumber, int recordingNumber) +{ + String basepath = rootFolder.getFullPathName() + rootFolder.separatorString + "experiment" + String(experimentNumber) + + File::separatorString + "recording" + String(recordingNumber + 1) + File::separatorString; + String contPath = basepath + "continuous" + File::separatorString; + //Open channel files + int nProcessors = getNumRecordedProcessors(); + + m_channelIndexes.insertMultiple(0, 0, getNumRecordedChannels()); + m_fileIndexes.insertMultiple(0, 0, getNumRecordedChannels()); + + Array indexedDataChannels; + Array indexedChannelCount; + Array jsonContinuousfiles; + Array jsonChannels; + StringArray continuousFileNames; + int lastId = 0; + for (int proc = 0; proc < nProcessors; proc++) + { + const RecordProcessorInfo& pInfo = getProcessorInfo(proc); + int recChans = pInfo.recordedChannels.size(); + + for (int chan = 0; chan < recChans; chan++) + { + int recordedChan = pInfo.recordedChannels[chan]; + int realChan = getRealChannel(recordedChan); + const DataChannel* channelInfo = getDataChannel(realChan); + int sourceId = channelInfo->getSourceNodeID(); + int sourceSubIdx = channelInfo->getSubProcessorIdx(); + int nInfoArrays = indexedDataChannels.size(); + bool found = false; + DynamicObject::Ptr jsonChan = new DynamicObject(); + jsonChan->setProperty("channel_name", channelInfo->getName()); + jsonChan->setProperty("description", channelInfo->getDescription()); + jsonChan->setProperty("identifier", channelInfo->getIdentifier()); + jsonChan->setProperty("history", channelInfo->getHistoricString()); + jsonChan->setProperty("bit_volts", channelInfo->getBitVolts()); + jsonChan->setProperty("units", channelInfo->getDataUnits()); + jsonChan->setProperty("source_processor_index", channelInfo->getSourceIndex()); + jsonChan->setProperty("recorded_processor_index", channelInfo->getCurrentNodeChannelIdx()); + createChannelMetaData(channelInfo, jsonChan); + for (int i = lastId; i < nInfoArrays; i++) + { + if (sourceId == indexedDataChannels[i]->getSourceNodeID() && sourceSubIdx == indexedDataChannels[i]->getSubProcessorIdx()) + { + unsigned int count = indexedChannelCount[i]; + m_channelIndexes.set(recordedChan, count); + m_fileIndexes.set(recordedChan, i); + indexedChannelCount.set(i, count + 1); + jsonChannels.getReference(i).append(var(jsonChan)); + found = true; + break; + } + } + if (!found) + { + String datPath = getProcessorString(channelInfo); + continuousFileNames.add(contPath + datPath + "continuous.dat"); + + ScopedPointer tFile = new NpyFile(contPath + datPath + "timestamps.npy", NpyType(BaseType::INT64,1)); + m_dataTimestampFiles.add(tFile.release()); + + m_fileIndexes.set(recordedChan, nInfoArrays); + m_channelIndexes.set(recordedChan, 0); + indexedChannelCount.add(1); + indexedDataChannels.add(channelInfo); + + Array jsonChanArray; + jsonChanArray.add(var(jsonChan)); + jsonChannels.add(var(jsonChanArray)); + DynamicObject::Ptr jsonFile = new DynamicObject(); + jsonFile->setProperty("folder_name", datPath.replace(File::separatorString, "/")); //to make it more system agnostic, replace separator with only one slash + jsonFile->setProperty("sample_rate", channelInfo->getSampleRate()); + jsonFile->setProperty("source_processor_name", channelInfo->getSourceName()); + jsonFile->setProperty("source_processor_id", channelInfo->getSourceNodeID()); + jsonFile->setProperty("source_processor_sub_idx", channelInfo->getSubProcessorIdx()); + jsonFile->setProperty("recorded_processor", channelInfo->getCurrentNodeName()); + jsonFile->setProperty("recorded_processor_id", channelInfo->getCurrentNodeID()); + jsonContinuousfiles.add(var(jsonFile)); + } + } + lastId = indexedDataChannels.size(); + } + int nFiles = continuousFileNames.size(); + for (int i = 0; i < nFiles; i++) + { + int numChannels = jsonChannels.getReference(i).size(); + ScopedPointer bFile = new SequentialBlockFile(numChannels, samplesPerBlock); + if (bFile->openFile(continuousFileNames[i])) + m_DataFiles.add(bFile.release()); + else + m_DataFiles.add(nullptr); + DynamicObject::Ptr jsonFile = jsonContinuousfiles.getReference(i).getDynamicObject(); + jsonFile->setProperty("num_channels", numChannels); + jsonFile->setProperty("channels", jsonChannels.getReference(i)); + } + + int nChans = getNumRecordedChannels(); + //Timestamps + Array procIDs; + for (int i = 0; i < nChans; i++) + { + if (i == 0) + std::cout << "Start timestamp: " << getTimestamp(i) << std::endl; + m_startTS.add(getTimestamp(i)); + } + + int nEvents = getNumRecordedEvents(); + String eventPath(basepath + "events" + File::separatorString); + Array jsonEventFiles; + + for (int ev = 0; ev < nEvents; ev++) + { + const EventChannel* chan = getEventChannel(ev); + String eventName = getProcessorString(chan); + NpyType type; + String dataFileName; + + switch (chan->getChannelType()) + { + case EventChannel::TEXT: + eventName += "TEXT_group"; + type = NpyType(BaseType::CHAR, chan->getLength()); + dataFileName = "text"; + break; + case EventChannel::TTL: + eventName += "TTL"; + type = NpyType(BaseType::INT16, 1); + dataFileName = "channel_states"; + break; + default: + eventName += "BINARY_group"; + type = NpyType(chan->getEquivalentMetaDataType(), chan->getLength()); + dataFileName = "data_array"; + break; + } + eventName += "_" + String(chan->getSourceIndex() + 1) + File::separatorString; + ScopedPointer rec = new EventRecording(); + + rec->mainFile = new NpyFile(eventPath + eventName + dataFileName + ".npy", type); + rec->timestampFile = new NpyFile(eventPath + eventName + "timestamps.npy", NpyType(BaseType::INT64, 1)); + rec->channelFile = new NpyFile(eventPath + eventName + "channels.npy", NpyType(BaseType::UINT16, 1)); + if (chan->getChannelType() == EventChannel::TTL && m_saveTTLWords) + { + rec->extraFile = new NpyFile(eventPath + eventName + "full_words.npy", NpyType(BaseType::UINT8, chan->getDataSize())); + } + + DynamicObject::Ptr jsonChannel = new DynamicObject(); + jsonChannel->setProperty("folder_name", eventName.replace(File::separatorString, "/")); + jsonChannel->setProperty("channel_name", chan->getName()); + jsonChannel->setProperty("description", chan->getDescription()); + jsonChannel->setProperty("identifier", chan->getIdentifier()); + jsonChannel->setProperty("sample_rate", chan->getSampleRate()); + jsonChannel->setProperty("type", jsonTypeValue(type.getType())); + jsonChannel->setProperty("num_channels", (int)chan->getNumChannels()); + jsonChannel->setProperty("source_processor", chan->getSourceName()); + createChannelMetaData(chan, jsonChannel); + + rec->metaDataFile = createEventMetadataFile(chan, eventPath + eventName + "metadata.npy", jsonChannel); + m_eventFiles.add(rec.release()); + jsonEventFiles.add(var(jsonChannel)); + } + + int nSpikes = getNumRecordedSpikes(); + Array indexedSpikes; + Array indexedChannels; + m_spikeFileIndexes.insertMultiple(0, 0, nSpikes); + m_spikeChannelIndexes.insertMultiple(0, 0, nSpikes); + String spikePath(basepath + "spikes" + File::separatorString); + Array jsonSpikeFiles; + Array jsonSpikeChannels; + std::map groupMap; + for (int sp = 0; sp < nSpikes; sp++) + { + const SpikeChannel* ch = getSpikeChannel(sp); + DynamicObject::Ptr jsonChannel = new DynamicObject(); + unsigned int numSpikeChannels = ch->getNumChannels(); + jsonChannel->setProperty("channel_name", ch->getName()); + jsonChannel->setProperty("description", ch->getDescription()); + jsonChannel->setProperty("identifier", ch->getIdentifier()); + Array jsonChannelInfo; + for (int i = 0; i < numSpikeChannels; i++) + { + SourceChannelInfo sourceInfo = ch->getSourceChannelInfo()[i]; + DynamicObject::Ptr jsonSpikeChInfo = new DynamicObject(); + jsonSpikeChInfo->setProperty("source_processor_id", sourceInfo.processorID); + jsonSpikeChInfo->setProperty("source_processor_sub_idx", sourceInfo.subProcessorID); + jsonSpikeChInfo->setProperty("source_processor_channel", sourceInfo.channelIDX); + jsonChannelInfo.add(var(jsonSpikeChInfo)); + } + jsonChannel->setProperty("source_channel_info", jsonChannelInfo); + createChannelMetaData(ch, jsonChannel); + + int nIndexed = indexedSpikes.size(); + bool found = false; + for (int i = 0; i < nIndexed; i++) + { + const SpikeChannel* ich = indexedSpikes[i]; + //identical channels (same data and metadata) from the same processor go to the same file + if (ch->getSourceNodeID() == ich->getSourceNodeID() && ch->getSubProcessorIdx() == ich->getSubProcessorIdx() && *ch == *ich) + { + found = true; + m_spikeFileIndexes.set(sp, i); + unsigned int numChans = indexedChannels[i]; + indexedChannels.set(i, numChans); + m_spikeChannelIndexes.set(sp, numChans + 1); + jsonSpikeChannels.getReference(i).append(var(jsonChannel)); + break; + } + } + + if (!found) + { + int fileIndex = m_spikeFiles.size(); + m_spikeFileIndexes.set(sp, fileIndex); + indexedSpikes.add(ch); + m_spikeChannelIndexes.set(sp, 0); + indexedChannels.add(1); + ScopedPointer rec = new EventRecording(); + + uint32 procID = GenericProcessor::getProcessorFullId(ch->getSourceNodeID(), ch->getSubProcessorIdx()); + int groupIndex = ++groupMap[procID]; + + String spikeName = getProcessorString(ch) + "spike_group_" + String(groupIndex) + File::separatorString; + + rec->mainFile = new NpyFile(spikePath + spikeName + "spike_waveforms.npy", NpyType(BaseType::INT16, ch->getTotalSamples()), ch->getNumChannels()); + rec->timestampFile = new NpyFile(spikePath + spikeName + "spike_times.npy", NpyType(BaseType::INT64, 1)); + rec->channelFile = new NpyFile(spikePath + spikeName + "spike_electrode_indices.npy", NpyType(BaseType::UINT16, 1)); + rec->extraFile = new NpyFile(spikePath + spikeName + "spike_clusters.npy", NpyType(BaseType::UINT16, 1)); + Array tsTypes; + + Array jsonChanArray; + jsonChanArray.add(var(jsonChannel)); + jsonSpikeChannels.add(var(jsonChanArray)); + DynamicObject::Ptr jsonFile = new DynamicObject(); + + jsonFile->setProperty("folder_name", spikeName.replace(File::separatorString,"/")); + jsonFile->setProperty("sample_rate", ch->getSampleRate()); + jsonFile->setProperty("source_processor", ch->getSourceName()); + jsonFile->setProperty("num_channels", (int)numSpikeChannels); + jsonFile->setProperty("pre_peak_samples", (int)ch->getPrePeakSamples()); + jsonFile->setProperty("post_peak_samples", (int)ch->getPostPeakSamples()); + + rec->metaDataFile = createEventMetadataFile(ch, spikePath + spikeName + "metadata.npy", jsonFile); + m_spikeFiles.add(rec.release()); + jsonSpikeFiles.add(var(jsonFile)); + } + } + int nSpikeFiles = jsonSpikeFiles.size(); + for (int i = 0; i < nSpikeFiles; i++) + { + int size = jsonSpikeChannels.getReference(i).size(); + DynamicObject::Ptr jsonFile = jsonSpikeFiles.getReference(i).getDynamicObject(); + jsonFile->setProperty("num_channels", size); + jsonFile->setProperty("channels", jsonSpikeChannels.getReference(i)); + } + + File syncFile = File(basepath + "sync_messages.txt"); + Result res = syncFile.create(); + if (res.failed()) + { + std::cerr << "Error creating sync text file:" << res.getErrorMessage() << std::endl; + } + else + { + m_syncTextFile = syncFile.createOutputStream(); + } + + m_recordingNum = recordingNumber; + + DynamicObject::Ptr jsonSettingsFile = new DynamicObject(); + jsonSettingsFile->setProperty("GUI version", CoreServices::getGUIVersion()); + jsonSettingsFile->setProperty("continuous", jsonContinuousfiles); + jsonSettingsFile->setProperty("events", jsonEventFiles); + jsonSettingsFile->setProperty("spikes", jsonSpikeFiles); + FileOutputStream settingsFileStream(File(basepath + "structure.oebin")); + + jsonSettingsFile->writeAsJSON(settingsFileStream, 2, false); +} + +NpyFile* BinaryRecording::createEventMetadataFile(const MetaDataEventObject* channel, String filename, DynamicObject* jsonFile) +{ + int nMetaData = channel->getEventMetaDataCount(); + if (nMetaData < 1) return nullptr; + + Array types; + Array jsonMetaData; + for (int i = 0; i < nMetaData; i++) + { + const MetaDataDescriptor* md = channel->getEventMetaDataDescriptor(i); + types.add(NpyType(md->getName(), md->getType(), md->getLength())); + DynamicObject::Ptr jsonValues = new DynamicObject(); + jsonValues->setProperty("name", md->getName()); + jsonValues->setProperty("description", md->getDescription()); + jsonValues->setProperty("identifier", md->getIdentifier()); + jsonValues->setProperty("type", jsonTypeValue(md->getType())); + jsonValues->setProperty("length", (int)md->getLength()); + jsonMetaData.add(var(jsonValues)); + } + if (jsonFile) + jsonFile->setProperty("event_metadata", jsonMetaData); + return new NpyFile(filename, types); +} + +template +void dataToVar(var& dataTo, const void* dataFrom, int length) +{ + const FROM* buffer = reinterpret_cast(dataFrom); + for (int i = 0; i < length; i++) + { + dataTo.append(static_cast(*(buffer + i))); + } +} + +void BinaryRecording::createChannelMetaData(const MetaDataInfoObject* channel, DynamicObject* jsonFile) +{ + int nMetaData = channel->getMetaDataCount(); + if (nMetaData < 1) return; + + Array jsonMetaData; + for (int i = 0; i < nMetaData; i++) + { + const MetaDataDescriptor* md = channel->getMetaDataDescriptor(i); + const MetaDataValue* mv = channel->getMetaDataValue(i); + DynamicObject::Ptr jsonValues = new DynamicObject(); + MetaDataDescriptor::MetaDataTypes type = md->getType(); + unsigned int length = md->getLength(); + jsonValues->setProperty("name", md->getName()); + jsonValues->setProperty("description", md->getDescription()); + jsonValues->setProperty("identifier", md->getIdentifier()); + jsonValues->setProperty("type", jsonTypeValue(type)); + jsonValues->setProperty("length", (int)length); + var val; + if (type == MetaDataDescriptor::CHAR) + { + String tmp; + mv->getValue(tmp); + val = tmp; + } + else + { + const void* buf = mv->getRawValuePointer(); + switch (type) + { + case MetaDataDescriptor::INT8: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::UINT8: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::INT16: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::UINT16: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::INT32: + dataToVar(val, buf, length); + break; + //A full uint32 doesn't fit in a regular int, so we increase size + case MetaDataDescriptor::UINT32: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::INT64: + dataToVar(val, buf, length); + break; + //This might overrun and end negative if the uint64 is really big, but there is no way to store a full uint64 in a var + case MetaDataDescriptor::UINT64: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::FLOAT: + dataToVar(val, buf, length); + break; + case MetaDataDescriptor::DOUBLE: + dataToVar(val, buf, length); + break; + default: + val = "invalid"; + } + } + jsonValues->setProperty("value", val); + jsonMetaData.add(var(jsonValues)); + } + jsonFile->setProperty("channel_metadata", jsonMetaData); +} + +void BinaryRecording::closeFiles() +{ + resetChannels(); +} + +void BinaryRecording::resetChannels() +{ + m_DataFiles.clear(); + m_channelIndexes.clear(); + m_fileIndexes.clear(); + m_dataTimestampFiles.clear(); + m_eventFiles.clear(); + m_spikeChannelIndexes.clear(); + m_spikeFileIndexes.clear(); + m_spikeFiles.clear(); + m_syncTextFile = nullptr; + + m_scaledBuffer.malloc(MAX_BUFFER_SIZE); + m_intBuffer.malloc(MAX_BUFFER_SIZE); + m_tsBuffer.malloc(MAX_BUFFER_SIZE); + m_bufferSize = MAX_BUFFER_SIZE; + m_startTS.clear(); +} + +void BinaryRecording::writeData(int writeChannel, int realChannel, const float* buffer, int size) +{ + if (size > m_bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. + { + std::cerr << "Write buffer overrun, resizing to" << size << std::endl; + m_bufferSize = size; + m_scaledBuffer.malloc(size); + m_intBuffer.malloc(size); + m_tsBuffer.malloc(size); + } + double multFactor = 1 / (float(0x7fff) * getDataChannel(realChannel)->getBitVolts()); + FloatVectorOperations::copyWithMultiply(m_scaledBuffer.getData(), buffer, multFactor, size); + AudioDataConverters::convertFloatToInt16LE(m_scaledBuffer.getData(), m_intBuffer.getData(), size); + int fileIndex = m_fileIndexes[writeChannel]; + m_DataFiles[fileIndex]->writeChannel(getTimestamp(writeChannel) - m_startTS[writeChannel], m_channelIndexes[writeChannel], m_intBuffer.getData(), size); + + if (m_channelIndexes[writeChannel] == 0) + { + int64 baseTS = getTimestamp(writeChannel); + //Let's hope that the compiler is smart enough to vectorize this. + for (int i = 0; i < size; i++) + { + m_tsBuffer[i] = (baseTS + i); + } + m_dataTimestampFiles[fileIndex]->writeData(m_tsBuffer, size*sizeof(int64)); + m_dataTimestampFiles[fileIndex]->increaseRecordCount(size); + } +} + + +void BinaryRecording::addSpikeElectrode(int index, const SpikeChannel* elec) +{ +} + +void BinaryRecording::writeEventMetaData(const MetaDataEvent* event, NpyFile* file) +{ + if (!file || !event) return; + int nMetaData = event->getMetadataValueCount(); + for (int i = 0; i < nMetaData; i++) + { + const MetaDataValue* val = event->getMetaDataValue(i); + file->writeData(val->getRawValuePointer(), val->getDataSize()); + } +} + +void BinaryRecording::writeEvent(int eventIndex, const MidiMessage& event) +{ + EventPtr ev = Event::deserializeFromMessage(event, getEventChannel(eventIndex)); + EventRecording* rec = m_eventFiles[eventIndex]; + if (!rec) return; + const EventChannel* info = getEventChannel(eventIndex); + int64 ts = ev->getTimestamp(); + rec->timestampFile->writeData(&ts, sizeof(int64)); + + uint16 chan = ev->getChannel() +1; + rec->channelFile->writeData(&chan, sizeof(uint16)); + + if (ev->getEventType() == EventChannel::TTL) + { + TTLEvent* ttl = static_cast(ev.get()); + int16 data = (ttl->getChannel()+1) * (ttl->getState() ? 1 : -1); + rec->mainFile->writeData(&data, sizeof(int16)); + if (rec->extraFile) + rec->extraFile->writeData(ttl->getTTLWordPointer(), info->getDataSize()); + } + else + { + rec->mainFile->writeData(ev->getRawDataPointer(), info->getDataSize()); + } + + writeEventMetaData(ev.get(), rec->metaDataFile); + increaseEventCounts(rec); +} + +void BinaryRecording::writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float, String text) +{ + if (!m_syncTextFile) + return; + m_syncTextFile->writeText(text + "\n", false, false); +} + + + +void BinaryRecording::writeSpike(int electrodeIndex, const SpikeEvent* spike) +{ + const SpikeChannel* channel = getSpikeChannel(electrodeIndex); + EventRecording* rec = m_spikeFiles[m_spikeFileIndexes[electrodeIndex]]; + uint16 spikeChannel = m_spikeChannelIndexes[electrodeIndex]; + + int totalSamples = channel->getTotalSamples() * channel->getNumChannels(); + + + if (totalSamples > m_bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. + { + std::cerr << "(spike) Write buffer overrun, resizing to" << totalSamples << std::endl; + m_bufferSize = totalSamples; + m_scaledBuffer.malloc(totalSamples); + m_intBuffer.malloc(totalSamples); + } + double multFactor = 1 / (float(0x7fff) * channel->getChannelBitVolts(0)); + FloatVectorOperations::copyWithMultiply(m_scaledBuffer.getData(), spike->getDataPointer(), multFactor, totalSamples); + AudioDataConverters::convertFloatToInt16LE(m_scaledBuffer.getData(), m_intBuffer.getData(), totalSamples); + rec->mainFile->writeData(m_intBuffer.getData(), totalSamples*sizeof(int16)); + + int64 ts = spike->getTimestamp(); + rec->timestampFile->writeData(&ts, sizeof(int64)); + + rec->channelFile->writeData(&spikeChannel, sizeof(uint16)); + + uint16 sortedID = spike->getSortedID(); + rec->extraFile->writeData(&sortedID, sizeof(uint16)); + writeEventMetaData(spike, rec->metaDataFile); + + increaseEventCounts(rec); +} + +void BinaryRecording::increaseEventCounts(EventRecording* rec) +{ + rec->mainFile->increaseRecordCount(); + rec->timestampFile->increaseRecordCount(); + if (rec->extraFile) rec->extraFile->increaseRecordCount(); + if (rec->channelFile) rec->channelFile->increaseRecordCount(); + if (rec->metaDataFile) rec->metaDataFile->increaseRecordCount(); +} + +RecordEngineManager* BinaryRecording::getEngineManager() +{ + RecordEngineManager* man = new RecordEngineManager("RAWBINARY", "Binary", &(engineFactory)); + EngineParameter* param; + param = new EngineParameter(EngineParameter::BOOL, 0, "Record TTL full words", true); + man->addParameter(param); + + return man; +} + +void BinaryRecording::setParameter(EngineParameter& parameter) +{ + boolParameter(0, m_saveTTLWords); +} + +String BinaryRecording::jsonTypeValue(BaseType type) +{ + switch (type) + { + case BaseType::CHAR: + return "string"; + case BaseType::INT8: + return "int8"; + case BaseType::UINT8: + return "uint8"; + case BaseType::INT16: + return "int16"; + case BaseType::UINT16: + return "uint16"; + case BaseType::INT32: + return "int32"; + case BaseType::UINT32: + return "uint32"; + case BaseType::INT64: + return "int64"; + case BaseType::UINT64: + return "uint64"; + case BaseType::FLOAT: + return "float"; + case BaseType::DOUBLE: + return "double"; + default: + return String::empty; + } +} diff --git a/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h new file mode 100644 index 0000000000..98d76badc2 --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h @@ -0,0 +1,101 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ +#ifndef BINARYRECORDING_H +#define BINARYRECORDING_H + +#include +#include "SequentialBlockFile.h" +#include "NpyFile.h" + +namespace BinaryRecordingEngine +{ + + class BinaryRecording : public RecordEngine + { + public: + BinaryRecording(); + ~BinaryRecording(); + + String getEngineID() const override; + void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; + void closeFiles() override; + void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; + void writeEvent(int eventIndex, const MidiMessage& event) override; + void resetChannels() override; + void addSpikeElectrode(int index, const SpikeChannel* elec) override; + void writeSpike(int electrodeIndex, const SpikeEvent* spike) override; + void writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float, String text) override; + void setParameter(EngineParameter& parameter) override; + + static RecordEngineManager* getEngineManager(); + + private: + + class EventRecording + { + public: + ScopedPointer mainFile; + ScopedPointer timestampFile; + ScopedPointer metaDataFile; + ScopedPointer channelFile; + ScopedPointer extraFile; + }; + + + NpyFile* createEventMetadataFile(const MetaDataEventObject* channel, String fileName, DynamicObject* jsonObject); + void createChannelMetaData(const MetaDataInfoObject* channel, DynamicObject* jsonObject); + void writeEventMetaData(const MetaDataEvent* event, NpyFile* file); + void increaseEventCounts(EventRecording* rec); + static String jsonTypeValue(BaseType type); + static String getProcessorString(const InfoObjectCommon* channelInfo); + + bool m_saveTTLWords{ true }; + + HeapBlock m_scaledBuffer; + HeapBlock m_intBuffer; + HeapBlock m_tsBuffer; + int m_bufferSize; + + OwnedArray m_DataFiles; + Array m_channelIndexes; + Array m_fileIndexes; + OwnedArray m_eventFiles; + OwnedArray m_spikeFiles; + OwnedArray m_dataTimestampFiles; + ScopedPointer m_syncTextFile; + + Array m_spikeFileIndexes; + Array m_spikeChannelIndexes; + + int m_recordingNum; + Array m_startTS; + + + //Compile-time constants + const int samplesPerBlock{ 4096 }; + + }; + +} + +#endif \ No newline at end of file diff --git a/Source/Plugins/BusseLabBinaryWriter/FileMemoryBlock.h b/Source/Plugins/BusseLabBinaryWriter/FileMemoryBlock.h new file mode 100644 index 0000000000..eb61a2fb7d --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/FileMemoryBlock.h @@ -0,0 +1,68 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#ifndef FILEMEMORYBLOCK_H +#define FILEMEMORYBLOCK_H + +#include + +namespace BinaryRecordingEngine +{ + + template + class FileMemoryBlock + { + public: + FileMemoryBlock(FileOutputStream* file, int blockSize, uint64 offset) : + m_data(blockSize, true), + m_file(file), + m_blockSize(blockSize), + m_offset(offset) + {}; + ~FileMemoryBlock() { + if (!m_flushed) + { + m_file->write(m_data, m_blockSize*sizeof(StorageType)); + } + }; + + inline uint64 getOffset() { return m_offset; } + inline StorageType* getData() { return m_data.getData(); } + void partialFlush(size_t size, bool markFlushed = true) + { + std::cout << "flushing last block " << size << std::endl; + m_file->write(m_data, size*sizeof(StorageType)); + if (markFlushed) + m_flushed = true; + } + + private: + HeapBlock m_data; + FileOutputStream* const m_file; + const int m_blockSize; + const uint64 m_offset; + bool m_flushed{ false }; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileMemoryBlock); + }; +} +#endif \ No newline at end of file diff --git a/Source/Plugins/BusseLabBinaryWriter/Makefile b/Source/Plugins/BusseLabBinaryWriter/Makefile new file mode 100644 index 0000000000..0c0c2f510c --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/Makefile @@ -0,0 +1,38 @@ + +LIBNAME := $(notdir $(CURDIR)) +OBJDIR := $(OBJDIR)/$(LIBNAME) +TARGET := $(LIBNAME).so + +SRC_DIR := ${shell find ./ -type d -print} +VPATH := $(SOURCE_DIRS) + +SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) +OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) + +BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) + +VPATH = $(SRC_DIR) + +.PHONY: objdir + +$(OUTDIR)/$(TARGET): objdir $(OBJ) + -@mkdir -p $(BINDIR) + -@mkdir -p $(LIBDIR) + -@mkdir -p $(OUTDIR) + @echo "Building $(TARGET)" + @$(BLDCMD) + +$(OBJDIR)/%.o : %.cpp + @echo "Compiling $<" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + + +objdir: + -@mkdir -p $(OBJDIR) + +clean: + @echo "Cleaning $(LIBNAME)" + -@rm -rf $(OBJDIR) + -@rm -f $(OUTDIR)/$(TARGET) + +-include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/BusseLabBinaryWriter/NpyFile.cpp b/Source/Plugins/BusseLabBinaryWriter/NpyFile.cpp new file mode 100644 index 0000000000..68b3f66d15 --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/NpyFile.cpp @@ -0,0 +1,218 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2017 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 . + +*/ + +#include "NpyFile.h" + +using namespace BinaryRecordingEngine; + +NpyFile::NpyFile(String path, const Array& typeList) +{ + + m_dim1 = 1; + m_dim2 = 1; + + /*If there is only one element on the list but is + an array, make this a multidimensional file. + */ + if (typeList.size() == 1) + { + NpyType type = typeList[0]; + if (type.getType() != BaseType::CHAR) //strings work different + m_dim1 = type.getTypeLength(); + } + + if (!openFile(path)) + return; + writeHeader(typeList); + +} + +NpyFile::NpyFile(String path, NpyType type, unsigned int dim) +{ + + if (!openFile(path)) + return; + + Array typeList; + typeList.add(type); + m_dim1 = dim; + m_dim2 = type.getTypeLength(); + writeHeader(typeList); + +} + +bool NpyFile::openFile(String path) +{ + File file(path); + Result res = file.create(); + if (res.failed()) + { + std::cerr << "Error creating file " << path << ":" << res.getErrorMessage() << std::endl; + return false; + } + m_file = file.createOutputStream(); + if (!m_file) + return false; + + m_okOpen = true; + return true; +} + +void NpyFile::writeHeader(const Array& typeList) +{ + bool multiValue = typeList.size() > 1; + String header = "{'descr': "; + header.preallocateBytes(100); + + if (multiValue) + header += "["; + + int nTypes = typeList.size(); + + for (int i = 0; i < nTypes; i++) + { + NpyType& type = typeList.getReference(i); + if (i > 0) header += ", "; + if (multiValue) + header += "('" + type.getName() + "', '" + type.getTypeString() + "', (" + String(type.getTypeLength()) + ",))"; + else + header += "'" + type.getTypeString() + "'"; + } + if (multiValue) + header += "]"; + header += ", 'fortran_order': False, 'shape': "; + + m_countPos = header.length() + 10; + header += "(1,), }"; + int padding = (int((header.length() + 30) / 16) + 1) * 16; + header = header.paddedRight(' ', padding); + header += '\n'; + + uint8 magicNum = 0x093; + m_file->write(&magicNum, sizeof(uint8)); + String magic = "NUMPY"; + uint16 len = header.length(); + m_file->write(magic.toUTF8(), magic.getNumBytesAsUTF8()); + uint16 ver = 0x0001; + m_file->write(&ver, sizeof(uint16)); + m_file->write(&len, sizeof(uint16)); + m_file->write(header.toUTF8(), len); +} + +NpyFile::~NpyFile() +{ + if (m_file->setPosition(m_countPos)) + { + String newShape = "("; + newShape.preallocateBytes(20); + newShape += String(m_recordCount) + ","; + if (m_dim1 > 1) + { + newShape += String(m_dim1) + ","; + } + if (m_dim2 > 1) + newShape += String(m_dim2); + newShape += "), }"; + m_file->write(newShape.toUTF8(), newShape.getNumBytesAsUTF8()); + } + else + { + std::cerr << "Error. Unable to seek to update header on file " << m_file->getFile().getFullPathName() << std::endl; + } +} + +void NpyFile::writeData(const void* data, size_t size) +{ + m_file->write(data, size); +} + +void NpyFile::increaseRecordCount(int count) +{ + m_recordCount += count; +} + + +NpyType::NpyType(String n, BaseType t, size_t l) + : name(n), type(t), length(l) +{ +} + +NpyType::NpyType(BaseType t, size_t l) + : name(String::empty), type(t), length(l) +{ +} + +NpyType::NpyType() + : name(String::empty), type(BaseType::INT8), length(1) +{ + +} + +String NpyType::getTypeString() const +{ + switch (type) + { + case BaseType::CHAR: + return "S" + String(length + 1); //account for the null separator + case BaseType::INT8: + return ". + +*/ + +#ifndef NPYFILE_H +#define NPYFILE_H + +#include + +namespace BinaryRecordingEngine +{ + + class NpyType + { + public: + NpyType(String, BaseType, size_t); + NpyType(BaseType, size_t); + NpyType(); + String getName() const; + String getTypeString() const; + int getTypeLength() const; + BaseType getType() const; + private: + String name; + BaseType type; + size_t length; + }; + + class NpyFile + { + public: + NpyFile(String path, const Array& typeList); + NpyFile(String path, NpyType type, unsigned int dim = 1); + ~NpyFile(); + void writeData(const void* data, size_t size); + void increaseRecordCount(int count = 1); + private: + bool openFile(String path); + void writeHeader(const Array& typeList); + ScopedPointer m_file; + bool m_okOpen{ false }; + int64 m_recordCount{ 0 }; + size_t m_countPos; + unsigned int m_dim1; + unsigned int m_dim2; + }; + +}; +#endif \ No newline at end of file diff --git a/Source/Plugins/BusseLabBinaryWriter/OpenEphysLib.cpp b/Source/Plugins/BusseLabBinaryWriter/OpenEphysLib.cpp new file mode 100644 index 0000000000..3eec861e00 --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/OpenEphysLib.cpp @@ -0,0 +1,70 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include +#include "BinaryRecording.h" +#include +#ifdef WIN32 +#include +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __attribute__((visibility("default"))) +#endif + + +using namespace Plugin; +#define NUM_PLUGINS 1 + +extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) +{ + info->apiVersion = PLUGIN_API_VER; + info->name = "Busse Lab Binary recording"; + info->libVersion = 1; + info->numPlugins = NUM_PLUGINS; +} + +extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) +{ + switch (index) + { + case 0: + info->type = Plugin::PLUGIN_TYPE_RECORD_ENGINE; + info->recordEngine.name = "Busse Lab Binary"; + info->recordEngine.creator = &(Plugin::createRecordEngine); + break; + default: + return -1; + } + + return 0; +} + +#ifdef WIN32 +BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, + IN DWORD nReason, + IN LPVOID Reserved) +{ + return TRUE; +} + +#endif diff --git a/Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.cpp b/Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.cpp new file mode 100644 index 0000000000..6638c72145 --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.cpp @@ -0,0 +1,167 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "SequentialBlockFile.h" + +using namespace BinaryRecordingEngine; + +SequentialBlockFile::SequentialBlockFile(int nChannels, int samplesPerBlock) : +m_file(nullptr), +m_nChannels(nChannels), +m_samplesPerBlock(samplesPerBlock), +m_blockSize(nChannels*samplesPerBlock), +m_lastBlockFill(0) +{ + m_memBlocks.ensureStorageAllocated(blockArrayInitSize); + for (int i = 0; i < nChannels; i++) + m_currentBlock.add(-1); +} + +SequentialBlockFile::~SequentialBlockFile() +{ + //Ensure that all remaining blocks are flushed in order. Keep the last one + int n = m_memBlocks.size(); + for (int i = 0; i < n - 1; i++) + { + m_memBlocks.remove(0); + } + + //manually flush the last one to avoid trailing zeroes + m_memBlocks[0]->partialFlush(m_lastBlockFill * m_nChannels); +} + +bool SequentialBlockFile::openFile(String filename) +{ + File file(filename); + Result res = file.create(); + if (res.failed()) + { + std::cerr << "Error creating file " << filename << ":" << res.getErrorMessage() << std::endl; + return false; + } + m_file = file.createOutputStream(streamBufferSize); + if (!m_file) + return false; + + m_memBlocks.add(new FileBlock(m_file, m_blockSize, 0)); + return true; +} + +bool SequentialBlockFile::writeChannel(uint64 startPos, int channel, int16* data, int nSamples) +{ + if (!m_file) + return false; + + int bIndex = m_memBlocks.size() - 1; + if ((bIndex < 0) || (m_memBlocks[bIndex]->getOffset() + m_samplesPerBlock) < (startPos + nSamples)) + allocateBlocks(startPos, nSamples); + + for (bIndex = m_memBlocks.size() - 1; bIndex >= 0; bIndex--) + { + if (m_memBlocks[bIndex]->getOffset() <= startPos) + break; + } + if (bIndex < 0) + { + std::cerr << "BINARY WRITER: Memory block unloaded ahead of time for chan " << channel << " start " << startPos << " ns " << nSamples << " first " << m_memBlocks[0]->getOffset() <getOffset(); + int startMemPos = startIdx*m_nChannels; + int dataIdx = 0; + int lastBlockIdx = m_memBlocks.size() - 1; + while (writtenSamples < nSamples) + { + int16* blockPtr = m_memBlocks[bIndex]->getData(); + int samplesToWrite = jmin((nSamples - writtenSamples), (m_samplesPerBlock - startIdx)); + for (int i = 0; i < samplesToWrite; i++) + { + //if (writtenSamples == 0 && *(data + dataIdx) == 0) + //{ + // std::cout << "Found a zero." << std::endl; + // break; + //} + + *(blockPtr + startMemPos + channel + i*m_nChannels) = *(data + dataIdx); + dataIdx++; + } + writtenSamples += samplesToWrite; + + //Update the last block fill index + size_t samplePos = startIdx + samplesToWrite; + if (bIndex == lastBlockIdx && samplePos > m_lastBlockFill) + { + m_lastBlockFill = samplePos; + } + + startIdx = 0; + startMemPos = 0; + bIndex++; + } + m_currentBlock.set(channel, bIndex - 1); //store the last block a channel was written in + return true; +} + +void SequentialBlockFile::allocateBlocks(uint64 startIndex, int numSamples) +{ + //First deallocate full blocks + //Search for the earliest unused block; + unsigned int minBlock = 0xFFFFFFFF; //large number; + for (int i = 0; i < m_nChannels; i++) + { + if (m_currentBlock[i] < minBlock) + minBlock = m_currentBlock[i]; + } + + //Update block indexes + for (int i = 0; i < m_nChannels; i++) + { + m_currentBlock.set(i, m_currentBlock[i] - minBlock); + } + + m_memBlocks.removeRange(0, minBlock); + + //for (int i = 0; i < minBlock; i++) + //{ + //Not the most efficient way, as it has to move back all the elements, but it's a simple array of pointers, so it's quick enough + // m_memBlocks.remove(0); + //} + + //Look for the last available position and calculate needed space + uint64 lastOffset = m_memBlocks.getLast()->getOffset(); + uint64 maxAddr = lastOffset + m_samplesPerBlock - 1; + uint64 newSpaceNeeded = numSamples - (maxAddr - startIndex); + int newBlocks = (newSpaceNeeded + m_samplesPerBlock - 1) / m_samplesPerBlock; //Fast ceiling division + + for (int i = 0; i < newBlocks; i++) + { + lastOffset += m_samplesPerBlock; + m_memBlocks.add(new FileBlock(m_file, m_blockSize, lastOffset)); + } + if (newBlocks > 0) + m_lastBlockFill = 0; //we've added some new blocks, so the last one will be empty +} + diff --git a/Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.h b/Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.h new file mode 100644 index 0000000000..a5b6e7e095 --- /dev/null +++ b/Source/Plugins/BusseLabBinaryWriter/SequentialBlockFile.h @@ -0,0 +1,63 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#ifndef SEQUENTIALBLOCKFILE_H +#define SEQUENTIALBLOCKFILE_H + +#include "FileMemoryBlock.h" + +namespace BinaryRecordingEngine +{ + + typedef FileMemoryBlock FileBlock; + + class SequentialBlockFile + { + public: + SequentialBlockFile(int nChannels, int samplesPerBlock); + ~SequentialBlockFile(); + + bool openFile(String filename); + bool writeChannel(uint64 startPos, int channel, int16* data, int nSamples); + + private: + ScopedPointer m_file; + const int m_nChannels; + const int m_samplesPerBlock; + const int m_blockSize; + OwnedArray m_memBlocks; + Array m_currentBlock; + size_t m_lastBlockFill; + + void allocateBlocks(uint64 startIndex, int numSamples); + + + //Compile-time parameters + const int streamBufferSize{ 0 }; + const int blockArrayInitSize{ 128 }; + + }; + +} + +#endif \ No newline at end of file From a6eebc6f85b1ffa64dda89f4af5fb2b8bf585a6f Mon Sep 17 00:00:00 2001 From: Martin Spacek Date: Fri, 18 May 2018 14:14:57 +0200 Subject: [PATCH 02/97] Remove all other recording formats to remove reliance on experimentNumber --- .../Plugins/BinaryWriter/BinaryRecording.cpp | 639 ----------------- Source/Plugins/BinaryWriter/BinaryRecording.h | 101 --- Source/Plugins/BinaryWriter/FileMemoryBlock.h | 68 -- Source/Plugins/BinaryWriter/Makefile | 38 - Source/Plugins/BinaryWriter/NpyFile.cpp | 218 ------ Source/Plugins/BinaryWriter/NpyFile.h | 68 -- Source/Plugins/BinaryWriter/OpenEphysLib.cpp | 70 -- .../BinaryWriter/SequentialBlockFile.cpp | 161 ----- .../BinaryWriter/SequentialBlockFile.h | 63 -- .../KWIKFormat/FileSource/KwikFileSource.cpp | 291 -------- .../KWIKFormat/FileSource/KwikFileSource.h | 70 -- Source/Plugins/KWIKFormat/Makefile | 41 -- Source/Plugins/KWIKFormat/OpenEphysLib.cpp | 77 -- .../RecordEngine/.HDF5Recording.cpp.swo | Bin 20480 -> 0 bytes .../KWIKFormat/RecordEngine/HDF5Recording.cpp | 293 -------- .../KWIKFormat/RecordEngine/HDF5Recording.h | 77 -- .../KWIKFormat/RecordEngine/KWIKFormat.cpp | 419 ----------- .../KWIKFormat/RecordEngine/KWIKFormat.h | 137 ---- Source/Plugins/NWBFormat/Makefile | 40 -- Source/Plugins/NWBFormat/NWBFormat.cpp | 674 ------------------ Source/Plugins/NWBFormat/NWBFormat.h | 103 --- Source/Plugins/NWBFormat/NWBRecording.cpp | 196 ----- Source/Plugins/NWBFormat/NWBRecording.h | 70 -- Source/Plugins/NWBFormat/OpenEphysLib.cpp | 70 -- 24 files changed, 3984 deletions(-) delete mode 100644 Source/Plugins/BinaryWriter/BinaryRecording.cpp delete mode 100644 Source/Plugins/BinaryWriter/BinaryRecording.h delete mode 100644 Source/Plugins/BinaryWriter/FileMemoryBlock.h delete mode 100644 Source/Plugins/BinaryWriter/Makefile delete mode 100644 Source/Plugins/BinaryWriter/NpyFile.cpp delete mode 100644 Source/Plugins/BinaryWriter/NpyFile.h delete mode 100644 Source/Plugins/BinaryWriter/OpenEphysLib.cpp delete mode 100644 Source/Plugins/BinaryWriter/SequentialBlockFile.cpp delete mode 100644 Source/Plugins/BinaryWriter/SequentialBlockFile.h delete mode 100644 Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp delete mode 100644 Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h delete mode 100644 Source/Plugins/KWIKFormat/Makefile delete mode 100644 Source/Plugins/KWIKFormat/OpenEphysLib.cpp delete mode 100644 Source/Plugins/KWIKFormat/RecordEngine/.HDF5Recording.cpp.swo delete mode 100644 Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp delete mode 100644 Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h delete mode 100644 Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp delete mode 100644 Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h delete mode 100644 Source/Plugins/NWBFormat/Makefile delete mode 100644 Source/Plugins/NWBFormat/NWBFormat.cpp delete mode 100644 Source/Plugins/NWBFormat/NWBFormat.h delete mode 100644 Source/Plugins/NWBFormat/NWBRecording.cpp delete mode 100644 Source/Plugins/NWBFormat/NWBRecording.h delete mode 100644 Source/Plugins/NWBFormat/OpenEphysLib.cpp diff --git a/Source/Plugins/BinaryWriter/BinaryRecording.cpp b/Source/Plugins/BinaryWriter/BinaryRecording.cpp deleted file mode 100644 index c75e5b8968..0000000000 --- a/Source/Plugins/BinaryWriter/BinaryRecording.cpp +++ /dev/null @@ -1,639 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#include "BinaryRecording.h" - -#define MAX_BUFFER_SIZE 40960 - -using namespace BinaryRecordingEngine; - -BinaryRecording::BinaryRecording() -{ - m_scaledBuffer.malloc(MAX_BUFFER_SIZE); - m_intBuffer.malloc(MAX_BUFFER_SIZE); - m_tsBuffer.malloc(MAX_BUFFER_SIZE); -} - -BinaryRecording::~BinaryRecording() -{ - -} - -String BinaryRecording::getEngineID() const -{ - return "RAWBINARY"; -} - -String BinaryRecording::getProcessorString(const InfoObjectCommon* channelInfo) -{ - String fName = (channelInfo->getCurrentNodeName().replaceCharacter(' ', '_') + "-" + String(channelInfo->getCurrentNodeID())); - if (channelInfo->getCurrentNodeID() == channelInfo->getSourceNodeID()) //it is the channel source - { - fName += "." + String(channelInfo->getSubProcessorIdx()); - } - else - { - fName += "_" + String(channelInfo->getSourceNodeID()) + "." + String(channelInfo->getSubProcessorIdx()); - } - fName += File::separatorString; - return fName; -} - -void BinaryRecording::openFiles(File rootFolder, int experimentNumber, int recordingNumber) -{ - String basepath = rootFolder.getFullPathName() + rootFolder.separatorString + "experiment" + String(experimentNumber) - + File::separatorString + "recording" + String(recordingNumber + 1) + File::separatorString; - String contPath = basepath + "continuous" + File::separatorString; - //Open channel files - int nProcessors = getNumRecordedProcessors(); - - m_channelIndexes.insertMultiple(0, 0, getNumRecordedChannels()); - m_fileIndexes.insertMultiple(0, 0, getNumRecordedChannels()); - - Array indexedDataChannels; - Array indexedChannelCount; - Array jsonContinuousfiles; - Array jsonChannels; - StringArray continuousFileNames; - int lastId = 0; - for (int proc = 0; proc < nProcessors; proc++) - { - const RecordProcessorInfo& pInfo = getProcessorInfo(proc); - int recChans = pInfo.recordedChannels.size(); - - for (int chan = 0; chan < recChans; chan++) - { - int recordedChan = pInfo.recordedChannels[chan]; - int realChan = getRealChannel(recordedChan); - const DataChannel* channelInfo = getDataChannel(realChan); - int sourceId = channelInfo->getSourceNodeID(); - int sourceSubIdx = channelInfo->getSubProcessorIdx(); - int nInfoArrays = indexedDataChannels.size(); - bool found = false; - DynamicObject::Ptr jsonChan = new DynamicObject(); - jsonChan->setProperty("channel_name", channelInfo->getName()); - jsonChan->setProperty("description", channelInfo->getDescription()); - jsonChan->setProperty("identifier", channelInfo->getIdentifier()); - jsonChan->setProperty("history", channelInfo->getHistoricString()); - jsonChan->setProperty("bit_volts", channelInfo->getBitVolts()); - jsonChan->setProperty("units", channelInfo->getDataUnits()); - jsonChan->setProperty("source_processor_index", channelInfo->getSourceIndex()); - jsonChan->setProperty("recorded_processor_index", channelInfo->getCurrentNodeChannelIdx()); - createChannelMetaData(channelInfo, jsonChan); - for (int i = lastId; i < nInfoArrays; i++) - { - if (sourceId == indexedDataChannels[i]->getSourceNodeID() && sourceSubIdx == indexedDataChannels[i]->getSubProcessorIdx()) - { - unsigned int count = indexedChannelCount[i]; - m_channelIndexes.set(recordedChan, count); - m_fileIndexes.set(recordedChan, i); - indexedChannelCount.set(i, count + 1); - jsonChannels.getReference(i).append(var(jsonChan)); - found = true; - break; - } - } - if (!found) - { - String datPath = getProcessorString(channelInfo); - continuousFileNames.add(contPath + datPath + "continuous.dat"); - - ScopedPointer tFile = new NpyFile(contPath + datPath + "timestamps.npy", NpyType(BaseType::INT64,1)); - m_dataTimestampFiles.add(tFile.release()); - - m_fileIndexes.set(recordedChan, nInfoArrays); - m_channelIndexes.set(recordedChan, 0); - indexedChannelCount.add(1); - indexedDataChannels.add(channelInfo); - - Array jsonChanArray; - jsonChanArray.add(var(jsonChan)); - jsonChannels.add(var(jsonChanArray)); - DynamicObject::Ptr jsonFile = new DynamicObject(); - jsonFile->setProperty("folder_name", datPath.replace(File::separatorString, "/")); //to make it more system agnostic, replace separator with only one slash - jsonFile->setProperty("sample_rate", channelInfo->getSampleRate()); - jsonFile->setProperty("source_processor_name", channelInfo->getSourceName()); - jsonFile->setProperty("source_processor_id", channelInfo->getSourceNodeID()); - jsonFile->setProperty("source_processor_sub_idx", channelInfo->getSubProcessorIdx()); - jsonFile->setProperty("recorded_processor", channelInfo->getCurrentNodeName()); - jsonFile->setProperty("recorded_processor_id", channelInfo->getCurrentNodeID()); - jsonContinuousfiles.add(var(jsonFile)); - } - } - lastId = indexedDataChannels.size(); - } - int nFiles = continuousFileNames.size(); - for (int i = 0; i < nFiles; i++) - { - int numChannels = jsonChannels.getReference(i).size(); - ScopedPointer bFile = new SequentialBlockFile(numChannels, samplesPerBlock); - if (bFile->openFile(continuousFileNames[i])) - m_DataFiles.add(bFile.release()); - else - m_DataFiles.add(nullptr); - DynamicObject::Ptr jsonFile = jsonContinuousfiles.getReference(i).getDynamicObject(); - jsonFile->setProperty("num_channels", numChannels); - jsonFile->setProperty("channels", jsonChannels.getReference(i)); - } - - int nChans = getNumRecordedChannels(); - //Timestamps - Array procIDs; - for (int i = 0; i < nChans; i++) - { - m_startTS.add(getTimestamp(i)); - } - - int nEvents = getNumRecordedEvents(); - String eventPath(basepath + "events" + File::separatorString); - Array jsonEventFiles; - - for (int ev = 0; ev < nEvents; ev++) - { - const EventChannel* chan = getEventChannel(ev); - String eventName = getProcessorString(chan); - NpyType type; - String dataFileName; - - switch (chan->getChannelType()) - { - case EventChannel::TEXT: - eventName += "TEXT_group"; - type = NpyType(BaseType::CHAR, chan->getLength()); - dataFileName = "text"; - break; - case EventChannel::TTL: - eventName += "TTL"; - type = NpyType(BaseType::INT16, 1); - dataFileName = "channel_states"; - break; - default: - eventName += "BINARY_group"; - type = NpyType(chan->getEquivalentMetaDataType(), chan->getLength()); - dataFileName = "data_array"; - break; - } - eventName += "_" + String(chan->getSourceIndex() + 1) + File::separatorString; - ScopedPointer rec = new EventRecording(); - - rec->mainFile = new NpyFile(eventPath + eventName + dataFileName + ".npy", type); - rec->timestampFile = new NpyFile(eventPath + eventName + "timestamps.npy", NpyType(BaseType::INT64, 1)); - rec->channelFile = new NpyFile(eventPath + eventName + "channels.npy", NpyType(BaseType::UINT16, 1)); - if (chan->getChannelType() == EventChannel::TTL && m_saveTTLWords) - { - rec->extraFile = new NpyFile(eventPath + eventName + "full_words.npy", NpyType(BaseType::UINT8, chan->getDataSize())); - } - - DynamicObject::Ptr jsonChannel = new DynamicObject(); - jsonChannel->setProperty("folder_name", eventName.replace(File::separatorString, "/")); - jsonChannel->setProperty("channel_name", chan->getName()); - jsonChannel->setProperty("description", chan->getDescription()); - jsonChannel->setProperty("identifier", chan->getIdentifier()); - jsonChannel->setProperty("sample_rate", chan->getSampleRate()); - jsonChannel->setProperty("type", jsonTypeValue(type.getType())); - jsonChannel->setProperty("num_channels", (int)chan->getNumChannels()); - jsonChannel->setProperty("source_processor", chan->getSourceName()); - createChannelMetaData(chan, jsonChannel); - - rec->metaDataFile = createEventMetadataFile(chan, eventPath + eventName + "metadata.npy", jsonChannel); - m_eventFiles.add(rec.release()); - jsonEventFiles.add(var(jsonChannel)); - } - - int nSpikes = getNumRecordedSpikes(); - Array indexedSpikes; - Array indexedChannels; - m_spikeFileIndexes.insertMultiple(0, 0, nSpikes); - m_spikeChannelIndexes.insertMultiple(0, 0, nSpikes); - String spikePath(basepath + "spikes" + File::separatorString); - Array jsonSpikeFiles; - Array jsonSpikeChannels; - std::map groupMap; - for (int sp = 0; sp < nSpikes; sp++) - { - const SpikeChannel* ch = getSpikeChannel(sp); - DynamicObject::Ptr jsonChannel = new DynamicObject(); - unsigned int numSpikeChannels = ch->getNumChannels(); - jsonChannel->setProperty("channel_name", ch->getName()); - jsonChannel->setProperty("description", ch->getDescription()); - jsonChannel->setProperty("identifier", ch->getIdentifier()); - Array jsonChannelInfo; - for (int i = 0; i < numSpikeChannels; i++) - { - SourceChannelInfo sourceInfo = ch->getSourceChannelInfo()[i]; - DynamicObject::Ptr jsonSpikeChInfo = new DynamicObject(); - jsonSpikeChInfo->setProperty("source_processor_id", sourceInfo.processorID); - jsonSpikeChInfo->setProperty("source_processor_sub_idx", sourceInfo.subProcessorID); - jsonSpikeChInfo->setProperty("source_processor_channel", sourceInfo.channelIDX); - jsonChannelInfo.add(var(jsonSpikeChInfo)); - } - jsonChannel->setProperty("source_channel_info", jsonChannelInfo); - createChannelMetaData(ch, jsonChannel); - - int nIndexed = indexedSpikes.size(); - bool found = false; - for (int i = 0; i < nIndexed; i++) - { - const SpikeChannel* ich = indexedSpikes[i]; - //identical channels (same data and metadata) from the same processor go to the same file - if (ch->getSourceNodeID() == ich->getSourceNodeID() && ch->getSubProcessorIdx() == ich->getSubProcessorIdx() && *ch == *ich) - { - found = true; - m_spikeFileIndexes.set(sp, i); - unsigned int numChans = indexedChannels[i]; - indexedChannels.set(i, numChans); - m_spikeChannelIndexes.set(sp, numChans + 1); - jsonSpikeChannels.getReference(i).append(var(jsonChannel)); - break; - } - } - - if (!found) - { - int fileIndex = m_spikeFiles.size(); - m_spikeFileIndexes.set(sp, fileIndex); - indexedSpikes.add(ch); - m_spikeChannelIndexes.set(sp, 0); - indexedChannels.add(1); - ScopedPointer rec = new EventRecording(); - - uint32 procID = GenericProcessor::getProcessorFullId(ch->getSourceNodeID(), ch->getSubProcessorIdx()); - int groupIndex = ++groupMap[procID]; - - String spikeName = getProcessorString(ch) + "spike_group_" + String(groupIndex) + File::separatorString; - - rec->mainFile = new NpyFile(spikePath + spikeName + "spike_waveforms.npy", NpyType(BaseType::INT16, ch->getTotalSamples()), ch->getNumChannels()); - rec->timestampFile = new NpyFile(spikePath + spikeName + "spike_times.npy", NpyType(BaseType::INT64, 1)); - rec->channelFile = new NpyFile(spikePath + spikeName + "spike_electrode_indices.npy", NpyType(BaseType::UINT16, 1)); - rec->extraFile = new NpyFile(spikePath + spikeName + "spike_clusters.npy", NpyType(BaseType::UINT16, 1)); - Array tsTypes; - - Array jsonChanArray; - jsonChanArray.add(var(jsonChannel)); - jsonSpikeChannels.add(var(jsonChanArray)); - DynamicObject::Ptr jsonFile = new DynamicObject(); - - jsonFile->setProperty("folder_name", spikeName.replace(File::separatorString,"/")); - jsonFile->setProperty("sample_rate", ch->getSampleRate()); - jsonFile->setProperty("source_processor", ch->getSourceName()); - jsonFile->setProperty("num_channels", (int)numSpikeChannels); - jsonFile->setProperty("pre_peak_samples", (int)ch->getPrePeakSamples()); - jsonFile->setProperty("post_peak_samples", (int)ch->getPostPeakSamples()); - - rec->metaDataFile = createEventMetadataFile(ch, spikePath + spikeName + "metadata.npy", jsonFile); - m_spikeFiles.add(rec.release()); - jsonSpikeFiles.add(var(jsonFile)); - } - } - int nSpikeFiles = jsonSpikeFiles.size(); - for (int i = 0; i < nSpikeFiles; i++) - { - int size = jsonSpikeChannels.getReference(i).size(); - DynamicObject::Ptr jsonFile = jsonSpikeFiles.getReference(i).getDynamicObject(); - jsonFile->setProperty("num_channels", size); - jsonFile->setProperty("channels", jsonSpikeChannels.getReference(i)); - } - - File syncFile = File(basepath + "sync_messages.txt"); - Result res = syncFile.create(); - if (res.failed()) - { - std::cerr << "Error creating sync text file:" << res.getErrorMessage() << std::endl; - } - else - { - m_syncTextFile = syncFile.createOutputStream(); - } - - m_recordingNum = recordingNumber; - - DynamicObject::Ptr jsonSettingsFile = new DynamicObject(); - jsonSettingsFile->setProperty("GUI version", CoreServices::getGUIVersion()); - jsonSettingsFile->setProperty("continuous", jsonContinuousfiles); - jsonSettingsFile->setProperty("events", jsonEventFiles); - jsonSettingsFile->setProperty("spikes", jsonSpikeFiles); - FileOutputStream settingsFileStream(File(basepath + "structure.oebin")); - - jsonSettingsFile->writeAsJSON(settingsFileStream, 2, false); -} - -NpyFile* BinaryRecording::createEventMetadataFile(const MetaDataEventObject* channel, String filename, DynamicObject* jsonFile) -{ - int nMetaData = channel->getEventMetaDataCount(); - if (nMetaData < 1) return nullptr; - - Array types; - Array jsonMetaData; - for (int i = 0; i < nMetaData; i++) - { - const MetaDataDescriptor* md = channel->getEventMetaDataDescriptor(i); - types.add(NpyType(md->getName(), md->getType(), md->getLength())); - DynamicObject::Ptr jsonValues = new DynamicObject(); - jsonValues->setProperty("name", md->getName()); - jsonValues->setProperty("description", md->getDescription()); - jsonValues->setProperty("identifier", md->getIdentifier()); - jsonValues->setProperty("type", jsonTypeValue(md->getType())); - jsonValues->setProperty("length", (int)md->getLength()); - jsonMetaData.add(var(jsonValues)); - } - if (jsonFile) - jsonFile->setProperty("event_metadata", jsonMetaData); - return new NpyFile(filename, types); -} - -template -void dataToVar(var& dataTo, const void* dataFrom, int length) -{ - const FROM* buffer = reinterpret_cast(dataFrom); - for (int i = 0; i < length; i++) - { - dataTo.append(static_cast(*(buffer + i))); - } -} - -void BinaryRecording::createChannelMetaData(const MetaDataInfoObject* channel, DynamicObject* jsonFile) -{ - int nMetaData = channel->getMetaDataCount(); - if (nMetaData < 1) return; - - Array jsonMetaData; - for (int i = 0; i < nMetaData; i++) - { - const MetaDataDescriptor* md = channel->getMetaDataDescriptor(i); - const MetaDataValue* mv = channel->getMetaDataValue(i); - DynamicObject::Ptr jsonValues = new DynamicObject(); - MetaDataDescriptor::MetaDataTypes type = md->getType(); - unsigned int length = md->getLength(); - jsonValues->setProperty("name", md->getName()); - jsonValues->setProperty("description", md->getDescription()); - jsonValues->setProperty("identifier", md->getIdentifier()); - jsonValues->setProperty("type", jsonTypeValue(type)); - jsonValues->setProperty("length", (int)length); - var val; - if (type == MetaDataDescriptor::CHAR) - { - String tmp; - mv->getValue(tmp); - val = tmp; - } - else - { - const void* buf = mv->getRawValuePointer(); - switch (type) - { - case MetaDataDescriptor::INT8: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::UINT8: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::INT16: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::UINT16: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::INT32: - dataToVar(val, buf, length); - break; - //A full uint32 doesn't fit in a regular int, so we increase size - case MetaDataDescriptor::UINT32: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::INT64: - dataToVar(val, buf, length); - break; - //This might overrun and end negative if the uint64 is really big, but there is no way to store a full uint64 in a var - case MetaDataDescriptor::UINT64: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::FLOAT: - dataToVar(val, buf, length); - break; - case MetaDataDescriptor::DOUBLE: - dataToVar(val, buf, length); - break; - default: - val = "invalid"; - } - } - jsonValues->setProperty("value", val); - jsonMetaData.add(var(jsonValues)); - } - jsonFile->setProperty("channel_metadata", jsonMetaData); -} - -void BinaryRecording::closeFiles() -{ - resetChannels(); -} - -void BinaryRecording::resetChannels() -{ - m_DataFiles.clear(); - m_channelIndexes.clear(); - m_fileIndexes.clear(); - m_dataTimestampFiles.clear(); - m_eventFiles.clear(); - m_spikeChannelIndexes.clear(); - m_spikeFileIndexes.clear(); - m_spikeFiles.clear(); - m_syncTextFile = nullptr; - - m_scaledBuffer.malloc(MAX_BUFFER_SIZE); - m_intBuffer.malloc(MAX_BUFFER_SIZE); - m_tsBuffer.malloc(MAX_BUFFER_SIZE); - m_bufferSize = MAX_BUFFER_SIZE; - m_startTS.clear(); -} - -void BinaryRecording::writeData(int writeChannel, int realChannel, const float* buffer, int size) -{ - if (size > m_bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. - { - std::cerr << "Write buffer overrun, resizing to" << size << std::endl; - m_bufferSize = size; - m_scaledBuffer.malloc(size); - m_intBuffer.malloc(size); - m_tsBuffer.malloc(size); - } - double multFactor = 1 / (float(0x7fff) * getDataChannel(realChannel)->getBitVolts()); - FloatVectorOperations::copyWithMultiply(m_scaledBuffer.getData(), buffer, multFactor, size); - AudioDataConverters::convertFloatToInt16LE(m_scaledBuffer.getData(), m_intBuffer.getData(), size); - int fileIndex = m_fileIndexes[writeChannel]; - m_DataFiles[fileIndex]->writeChannel(getTimestamp(writeChannel) - m_startTS[writeChannel], m_channelIndexes[writeChannel], m_intBuffer.getData(), size); - - if (m_channelIndexes[writeChannel] == 0) - { - int64 baseTS = getTimestamp(writeChannel); - //Let's hope that the compiler is smart enough to vectorize this. - for (int i = 0; i < size; i++) - { - m_tsBuffer[i] = (baseTS + i); - } - m_dataTimestampFiles[fileIndex]->writeData(m_tsBuffer, size*sizeof(int64)); - m_dataTimestampFiles[fileIndex]->increaseRecordCount(size); - } -} - - -void BinaryRecording::addSpikeElectrode(int index, const SpikeChannel* elec) -{ -} - -void BinaryRecording::writeEventMetaData(const MetaDataEvent* event, NpyFile* file) -{ - if (!file || !event) return; - int nMetaData = event->getMetadataValueCount(); - for (int i = 0; i < nMetaData; i++) - { - const MetaDataValue* val = event->getMetaDataValue(i); - file->writeData(val->getRawValuePointer(), val->getDataSize()); - } -} - -void BinaryRecording::writeEvent(int eventIndex, const MidiMessage& event) -{ - EventPtr ev = Event::deserializeFromMessage(event, getEventChannel(eventIndex)); - EventRecording* rec = m_eventFiles[eventIndex]; - if (!rec) return; - const EventChannel* info = getEventChannel(eventIndex); - int64 ts = ev->getTimestamp(); - rec->timestampFile->writeData(&ts, sizeof(int64)); - - uint16 chan = ev->getChannel() +1; - rec->channelFile->writeData(&chan, sizeof(uint16)); - - if (ev->getEventType() == EventChannel::TTL) - { - TTLEvent* ttl = static_cast(ev.get()); - int16 data = (ttl->getChannel()+1) * (ttl->getState() ? 1 : -1); - rec->mainFile->writeData(&data, sizeof(int16)); - if (rec->extraFile) - rec->extraFile->writeData(ttl->getTTLWordPointer(), info->getDataSize()); - } - else - { - rec->mainFile->writeData(ev->getRawDataPointer(), info->getDataSize()); - } - - writeEventMetaData(ev.get(), rec->metaDataFile); - increaseEventCounts(rec); -} - -void BinaryRecording::writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float, String text) -{ - if (!m_syncTextFile) - return; - m_syncTextFile->writeText(text + "\n", false, false); -} - - - -void BinaryRecording::writeSpike(int electrodeIndex, const SpikeEvent* spike) -{ - const SpikeChannel* channel = getSpikeChannel(electrodeIndex); - EventRecording* rec = m_spikeFiles[m_spikeFileIndexes[electrodeIndex]]; - uint16 spikeChannel = m_spikeChannelIndexes[electrodeIndex]; - - int totalSamples = channel->getTotalSamples() * channel->getNumChannels(); - - - if (totalSamples > m_bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. - { - std::cerr << "(spike) Write buffer overrun, resizing to" << totalSamples << std::endl; - m_bufferSize = totalSamples; - m_scaledBuffer.malloc(totalSamples); - m_intBuffer.malloc(totalSamples); - } - double multFactor = 1 / (float(0x7fff) * channel->getChannelBitVolts(0)); - FloatVectorOperations::copyWithMultiply(m_scaledBuffer.getData(), spike->getDataPointer(), multFactor, totalSamples); - AudioDataConverters::convertFloatToInt16LE(m_scaledBuffer.getData(), m_intBuffer.getData(), totalSamples); - rec->mainFile->writeData(m_intBuffer.getData(), totalSamples*sizeof(int16)); - - int64 ts = spike->getTimestamp(); - rec->timestampFile->writeData(&ts, sizeof(int64)); - - rec->channelFile->writeData(&spikeChannel, sizeof(uint16)); - - uint16 sortedID = spike->getSortedID(); - rec->extraFile->writeData(&sortedID, sizeof(uint16)); - writeEventMetaData(spike, rec->metaDataFile); - - increaseEventCounts(rec); -} - -void BinaryRecording::increaseEventCounts(EventRecording* rec) -{ - rec->mainFile->increaseRecordCount(); - rec->timestampFile->increaseRecordCount(); - if (rec->extraFile) rec->extraFile->increaseRecordCount(); - if (rec->channelFile) rec->channelFile->increaseRecordCount(); - if (rec->metaDataFile) rec->metaDataFile->increaseRecordCount(); -} - -RecordEngineManager* BinaryRecording::getEngineManager() -{ - RecordEngineManager* man = new RecordEngineManager("RAWBINARY", "Binary", &(engineFactory)); - EngineParameter* param; - param = new EngineParameter(EngineParameter::BOOL, 0, "Record TTL full words", true); - man->addParameter(param); - - return man; -} - -void BinaryRecording::setParameter(EngineParameter& parameter) -{ - boolParameter(0, m_saveTTLWords); -} - -String BinaryRecording::jsonTypeValue(BaseType type) -{ - switch (type) - { - case BaseType::CHAR: - return "string"; - case BaseType::INT8: - return "int8"; - case BaseType::UINT8: - return "uint8"; - case BaseType::INT16: - return "int16"; - case BaseType::UINT16: - return "uint16"; - case BaseType::INT32: - return "int32"; - case BaseType::UINT32: - return "uint32"; - case BaseType::INT64: - return "int64"; - case BaseType::UINT64: - return "uint64"; - case BaseType::FLOAT: - return "float"; - case BaseType::DOUBLE: - return "double"; - default: - return String::empty; - } -} diff --git a/Source/Plugins/BinaryWriter/BinaryRecording.h b/Source/Plugins/BinaryWriter/BinaryRecording.h deleted file mode 100644 index 98d76badc2..0000000000 --- a/Source/Plugins/BinaryWriter/BinaryRecording.h +++ /dev/null @@ -1,101 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ -#ifndef BINARYRECORDING_H -#define BINARYRECORDING_H - -#include -#include "SequentialBlockFile.h" -#include "NpyFile.h" - -namespace BinaryRecordingEngine -{ - - class BinaryRecording : public RecordEngine - { - public: - BinaryRecording(); - ~BinaryRecording(); - - String getEngineID() const override; - void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; - void closeFiles() override; - void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; - void writeEvent(int eventIndex, const MidiMessage& event) override; - void resetChannels() override; - void addSpikeElectrode(int index, const SpikeChannel* elec) override; - void writeSpike(int electrodeIndex, const SpikeEvent* spike) override; - void writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float, String text) override; - void setParameter(EngineParameter& parameter) override; - - static RecordEngineManager* getEngineManager(); - - private: - - class EventRecording - { - public: - ScopedPointer mainFile; - ScopedPointer timestampFile; - ScopedPointer metaDataFile; - ScopedPointer channelFile; - ScopedPointer extraFile; - }; - - - NpyFile* createEventMetadataFile(const MetaDataEventObject* channel, String fileName, DynamicObject* jsonObject); - void createChannelMetaData(const MetaDataInfoObject* channel, DynamicObject* jsonObject); - void writeEventMetaData(const MetaDataEvent* event, NpyFile* file); - void increaseEventCounts(EventRecording* rec); - static String jsonTypeValue(BaseType type); - static String getProcessorString(const InfoObjectCommon* channelInfo); - - bool m_saveTTLWords{ true }; - - HeapBlock m_scaledBuffer; - HeapBlock m_intBuffer; - HeapBlock m_tsBuffer; - int m_bufferSize; - - OwnedArray m_DataFiles; - Array m_channelIndexes; - Array m_fileIndexes; - OwnedArray m_eventFiles; - OwnedArray m_spikeFiles; - OwnedArray m_dataTimestampFiles; - ScopedPointer m_syncTextFile; - - Array m_spikeFileIndexes; - Array m_spikeChannelIndexes; - - int m_recordingNum; - Array m_startTS; - - - //Compile-time constants - const int samplesPerBlock{ 4096 }; - - }; - -} - -#endif \ No newline at end of file diff --git a/Source/Plugins/BinaryWriter/FileMemoryBlock.h b/Source/Plugins/BinaryWriter/FileMemoryBlock.h deleted file mode 100644 index eb61a2fb7d..0000000000 --- a/Source/Plugins/BinaryWriter/FileMemoryBlock.h +++ /dev/null @@ -1,68 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#ifndef FILEMEMORYBLOCK_H -#define FILEMEMORYBLOCK_H - -#include - -namespace BinaryRecordingEngine -{ - - template - class FileMemoryBlock - { - public: - FileMemoryBlock(FileOutputStream* file, int blockSize, uint64 offset) : - m_data(blockSize, true), - m_file(file), - m_blockSize(blockSize), - m_offset(offset) - {}; - ~FileMemoryBlock() { - if (!m_flushed) - { - m_file->write(m_data, m_blockSize*sizeof(StorageType)); - } - }; - - inline uint64 getOffset() { return m_offset; } - inline StorageType* getData() { return m_data.getData(); } - void partialFlush(size_t size, bool markFlushed = true) - { - std::cout << "flushing last block " << size << std::endl; - m_file->write(m_data, size*sizeof(StorageType)); - if (markFlushed) - m_flushed = true; - } - - private: - HeapBlock m_data; - FileOutputStream* const m_file; - const int m_blockSize; - const uint64 m_offset; - bool m_flushed{ false }; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileMemoryBlock); - }; -} -#endif \ No newline at end of file diff --git a/Source/Plugins/BinaryWriter/Makefile b/Source/Plugins/BinaryWriter/Makefile deleted file mode 100644 index 0c0c2f510c..0000000000 --- a/Source/Plugins/BinaryWriter/Makefile +++ /dev/null @@ -1,38 +0,0 @@ - -LIBNAME := $(notdir $(CURDIR)) -OBJDIR := $(OBJDIR)/$(LIBNAME) -TARGET := $(LIBNAME).so - -SRC_DIR := ${shell find ./ -type d -print} -VPATH := $(SOURCE_DIRS) - -SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) -OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) - -BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) - -VPATH = $(SRC_DIR) - -.PHONY: objdir - -$(OUTDIR)/$(TARGET): objdir $(OBJ) - -@mkdir -p $(BINDIR) - -@mkdir -p $(LIBDIR) - -@mkdir -p $(OUTDIR) - @echo "Building $(TARGET)" - @$(BLDCMD) - -$(OBJDIR)/%.o : %.cpp - @echo "Compiling $<" - @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" - - -objdir: - -@mkdir -p $(OBJDIR) - -clean: - @echo "Cleaning $(LIBNAME)" - -@rm -rf $(OBJDIR) - -@rm -f $(OUTDIR)/$(TARGET) - --include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/BinaryWriter/NpyFile.cpp b/Source/Plugins/BinaryWriter/NpyFile.cpp deleted file mode 100644 index 68b3f66d15..0000000000 --- a/Source/Plugins/BinaryWriter/NpyFile.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2017 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 . - -*/ - -#include "NpyFile.h" - -using namespace BinaryRecordingEngine; - -NpyFile::NpyFile(String path, const Array& typeList) -{ - - m_dim1 = 1; - m_dim2 = 1; - - /*If there is only one element on the list but is - an array, make this a multidimensional file. - */ - if (typeList.size() == 1) - { - NpyType type = typeList[0]; - if (type.getType() != BaseType::CHAR) //strings work different - m_dim1 = type.getTypeLength(); - } - - if (!openFile(path)) - return; - writeHeader(typeList); - -} - -NpyFile::NpyFile(String path, NpyType type, unsigned int dim) -{ - - if (!openFile(path)) - return; - - Array typeList; - typeList.add(type); - m_dim1 = dim; - m_dim2 = type.getTypeLength(); - writeHeader(typeList); - -} - -bool NpyFile::openFile(String path) -{ - File file(path); - Result res = file.create(); - if (res.failed()) - { - std::cerr << "Error creating file " << path << ":" << res.getErrorMessage() << std::endl; - return false; - } - m_file = file.createOutputStream(); - if (!m_file) - return false; - - m_okOpen = true; - return true; -} - -void NpyFile::writeHeader(const Array& typeList) -{ - bool multiValue = typeList.size() > 1; - String header = "{'descr': "; - header.preallocateBytes(100); - - if (multiValue) - header += "["; - - int nTypes = typeList.size(); - - for (int i = 0; i < nTypes; i++) - { - NpyType& type = typeList.getReference(i); - if (i > 0) header += ", "; - if (multiValue) - header += "('" + type.getName() + "', '" + type.getTypeString() + "', (" + String(type.getTypeLength()) + ",))"; - else - header += "'" + type.getTypeString() + "'"; - } - if (multiValue) - header += "]"; - header += ", 'fortran_order': False, 'shape': "; - - m_countPos = header.length() + 10; - header += "(1,), }"; - int padding = (int((header.length() + 30) / 16) + 1) * 16; - header = header.paddedRight(' ', padding); - header += '\n'; - - uint8 magicNum = 0x093; - m_file->write(&magicNum, sizeof(uint8)); - String magic = "NUMPY"; - uint16 len = header.length(); - m_file->write(magic.toUTF8(), magic.getNumBytesAsUTF8()); - uint16 ver = 0x0001; - m_file->write(&ver, sizeof(uint16)); - m_file->write(&len, sizeof(uint16)); - m_file->write(header.toUTF8(), len); -} - -NpyFile::~NpyFile() -{ - if (m_file->setPosition(m_countPos)) - { - String newShape = "("; - newShape.preallocateBytes(20); - newShape += String(m_recordCount) + ","; - if (m_dim1 > 1) - { - newShape += String(m_dim1) + ","; - } - if (m_dim2 > 1) - newShape += String(m_dim2); - newShape += "), }"; - m_file->write(newShape.toUTF8(), newShape.getNumBytesAsUTF8()); - } - else - { - std::cerr << "Error. Unable to seek to update header on file " << m_file->getFile().getFullPathName() << std::endl; - } -} - -void NpyFile::writeData(const void* data, size_t size) -{ - m_file->write(data, size); -} - -void NpyFile::increaseRecordCount(int count) -{ - m_recordCount += count; -} - - -NpyType::NpyType(String n, BaseType t, size_t l) - : name(n), type(t), length(l) -{ -} - -NpyType::NpyType(BaseType t, size_t l) - : name(String::empty), type(t), length(l) -{ -} - -NpyType::NpyType() - : name(String::empty), type(BaseType::INT8), length(1) -{ - -} - -String NpyType::getTypeString() const -{ - switch (type) - { - case BaseType::CHAR: - return "S" + String(length + 1); //account for the null separator - case BaseType::INT8: - return ". - -*/ - -#ifndef NPYFILE_H -#define NPYFILE_H - -#include - -namespace BinaryRecordingEngine -{ - - class NpyType - { - public: - NpyType(String, BaseType, size_t); - NpyType(BaseType, size_t); - NpyType(); - String getName() const; - String getTypeString() const; - int getTypeLength() const; - BaseType getType() const; - private: - String name; - BaseType type; - size_t length; - }; - - class NpyFile - { - public: - NpyFile(String path, const Array& typeList); - NpyFile(String path, NpyType type, unsigned int dim = 1); - ~NpyFile(); - void writeData(const void* data, size_t size); - void increaseRecordCount(int count = 1); - private: - bool openFile(String path); - void writeHeader(const Array& typeList); - ScopedPointer m_file; - bool m_okOpen{ false }; - int64 m_recordCount{ 0 }; - size_t m_countPos; - unsigned int m_dim1; - unsigned int m_dim2; - }; - -}; -#endif \ No newline at end of file diff --git a/Source/Plugins/BinaryWriter/OpenEphysLib.cpp b/Source/Plugins/BinaryWriter/OpenEphysLib.cpp deleted file mode 100644 index 4f5679530f..0000000000 --- a/Source/Plugins/BinaryWriter/OpenEphysLib.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#include -#include "BinaryRecording.h" -#include -#ifdef WIN32 -#include -#define EXPORT __declspec(dllexport) -#else -#define EXPORT __attribute__((visibility("default"))) -#endif - - -using namespace Plugin; -#define NUM_PLUGINS 1 - -extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) -{ - info->apiVersion = PLUGIN_API_VER; - info->name = "Binary recording"; - info->libVersion = 1; - info->numPlugins = NUM_PLUGINS; -} - -extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) -{ - switch (index) - { - case 0: - info->type = Plugin::PLUGIN_TYPE_RECORD_ENGINE; - info->recordEngine.name = "Binary"; - info->recordEngine.creator = &(Plugin::createRecordEngine); - break; - default: - return -1; - } - - return 0; -} - -#ifdef WIN32 -BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, - IN DWORD nReason, - IN LPVOID Reserved) -{ - return TRUE; -} - -#endif \ No newline at end of file diff --git a/Source/Plugins/BinaryWriter/SequentialBlockFile.cpp b/Source/Plugins/BinaryWriter/SequentialBlockFile.cpp deleted file mode 100644 index 334de359d2..0000000000 --- a/Source/Plugins/BinaryWriter/SequentialBlockFile.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#include "SequentialBlockFile.h" - -using namespace BinaryRecordingEngine; - -SequentialBlockFile::SequentialBlockFile(int nChannels, int samplesPerBlock) : -m_file(nullptr), -m_nChannels(nChannels), -m_samplesPerBlock(samplesPerBlock), -m_blockSize(nChannels*samplesPerBlock), -m_lastBlockFill(0) -{ - m_memBlocks.ensureStorageAllocated(blockArrayInitSize); - for (int i = 0; i < nChannels; i++) - m_currentBlock.add(-1); -} - -SequentialBlockFile::~SequentialBlockFile() -{ - //Ensure that all remaining blocks are flushed in order. Keep the last one - int n = m_memBlocks.size(); - for (int i = 0; i < n - 1; i++) - { - m_memBlocks.remove(0); - } - - //manually flush the last one to avoid trailing zeroes - m_memBlocks[0]->partialFlush(m_lastBlockFill * m_nChannels); -} - -bool SequentialBlockFile::openFile(String filename) -{ - File file(filename); - Result res = file.create(); - if (res.failed()) - { - std::cerr << "Error creating file " << filename << ":" << res.getErrorMessage() << std::endl; - return false; - } - m_file = file.createOutputStream(streamBufferSize); - if (!m_file) - return false; - - m_memBlocks.add(new FileBlock(m_file, m_blockSize, 0)); - return true; -} - -bool SequentialBlockFile::writeChannel(uint64 startPos, int channel, int16* data, int nSamples) -{ - if (!m_file) - return false; - - int bIndex = m_memBlocks.size() - 1; - if ((bIndex < 0) || (m_memBlocks[bIndex]->getOffset() + m_samplesPerBlock) < (startPos + nSamples)) - allocateBlocks(startPos, nSamples); - - for (bIndex = m_memBlocks.size() - 1; bIndex >= 0; bIndex--) - { - if (m_memBlocks[bIndex]->getOffset() <= startPos) - break; - } - if (bIndex < 0) - { - std::cerr << "BINARY WRITER: Memory block unloaded ahead of time for chan " << channel << " start " << startPos << " ns " << nSamples << " first " << m_memBlocks[0]->getOffset() <getOffset(); - int startMemPos = startIdx*m_nChannels; - int dataIdx = 0; - int lastBlockIdx = m_memBlocks.size() - 1; - while (writtenSamples < nSamples) - { - int16* blockPtr = m_memBlocks[bIndex]->getData(); - int samplesToWrite = jmin((nSamples - writtenSamples), (m_samplesPerBlock - startIdx)); - for (int i = 0; i < samplesToWrite; i++) - { - *(blockPtr + startMemPos + channel + i*m_nChannels) = *(data + dataIdx); - dataIdx++; - } - writtenSamples += samplesToWrite; - - //Update the last block fill index - size_t samplePos = startIdx + samplesToWrite; - if (bIndex == lastBlockIdx && samplePos > m_lastBlockFill) - { - m_lastBlockFill = samplePos; - } - - startIdx = 0; - startMemPos = 0; - bIndex++; - } - m_currentBlock.set(channel, bIndex - 1); //store the last block a channel was written in - return true; -} - -void SequentialBlockFile::allocateBlocks(uint64 startIndex, int numSamples) -{ - //First deallocate full blocks - //Search for the earliest unused block; - unsigned int minBlock = 0xFFFFFFFF; //large number; - for (int i = 0; i < m_nChannels; i++) - { - if (m_currentBlock[i] < minBlock) - minBlock = m_currentBlock[i]; - } - - //Update block indexes - for (int i = 0; i < m_nChannels; i++) - { - m_currentBlock.set(i, m_currentBlock[i] - minBlock); - } - - m_memBlocks.removeRange(0, minBlock); - - //for (int i = 0; i < minBlock; i++) - //{ - //Not the most efficient way, as it has to move back all the elements, but it's a simple array of pointers, so it's quick enough - // m_memBlocks.remove(0); - //} - - //Look for the last available position and calculate needed space - uint64 lastOffset = m_memBlocks.getLast()->getOffset(); - uint64 maxAddr = lastOffset + m_samplesPerBlock - 1; - uint64 newSpaceNeeded = numSamples - (maxAddr - startIndex); - int newBlocks = (newSpaceNeeded + m_samplesPerBlock - 1) / m_samplesPerBlock; //Fast ceiling division - - for (int i = 0; i < newBlocks; i++) - { - lastOffset += m_samplesPerBlock; - m_memBlocks.add(new FileBlock(m_file, m_blockSize, lastOffset)); - } - if (newBlocks > 0) - m_lastBlockFill = 0; //we've added some new blocks, so the last one will be empty -} - diff --git a/Source/Plugins/BinaryWriter/SequentialBlockFile.h b/Source/Plugins/BinaryWriter/SequentialBlockFile.h deleted file mode 100644 index a5b6e7e095..0000000000 --- a/Source/Plugins/BinaryWriter/SequentialBlockFile.h +++ /dev/null @@ -1,63 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#ifndef SEQUENTIALBLOCKFILE_H -#define SEQUENTIALBLOCKFILE_H - -#include "FileMemoryBlock.h" - -namespace BinaryRecordingEngine -{ - - typedef FileMemoryBlock FileBlock; - - class SequentialBlockFile - { - public: - SequentialBlockFile(int nChannels, int samplesPerBlock); - ~SequentialBlockFile(); - - bool openFile(String filename); - bool writeChannel(uint64 startPos, int channel, int16* data, int nSamples); - - private: - ScopedPointer m_file; - const int m_nChannels; - const int m_samplesPerBlock; - const int m_blockSize; - OwnedArray m_memBlocks; - Array m_currentBlock; - size_t m_lastBlockFill; - - void allocateBlocks(uint64 startIndex, int numSamples); - - - //Compile-time parameters - const int streamBufferSize{ 0 }; - const int blockArrayInitSize{ 128 }; - - }; - -} - -#endif \ No newline at end of file diff --git a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp deleted file mode 100644 index 4ca543b52d..0000000000 --- a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* - ------------------------------------------------------------------ - - This file is part of the Open Ephys GUI - Copyright (C) 2013 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 . - -*/ -#include -#include "KwikFileSource.h" -#include - - -using namespace H5; - -#define PROCESS_ERROR std::cerr << "KwikFilesource exception: " << error.getCDetailMsg() << std::endl - -KWIKFileSource::KWIKFileSource() : samplePos(0), skipRecordEngineCheck(false) -{ -} - -KWIKFileSource::~KWIKFileSource() -{ -} - -bool KWIKFileSource::Open(File file) -{ - ScopedPointer tmpFile; - Attribute ver; - uint16 vernum; - try - { - tmpFile = new H5File(file.getFullPathName().toUTF8(),H5F_ACC_RDONLY); - if (!tmpFile->attrExists("kwik_version")) - { - return false; - } - - ver = tmpFile->openAttribute("kwik_version"); - ver.read(PredType::NATIVE_UINT16,&vernum); - if ((vernum < MIN_KWIK_VERSION) || (vernum > MAX_KWIK_VERSION)) - { - return false; - } - - sourceFile = tmpFile; - return true; - - } - catch (FileIException error) - { - PROCESS_ERROR; - return false; - } - catch (AttributeIException error) - { - PROCESS_ERROR; - return false; - } - - //Code should never reach here - return false; -} - -void KWIKFileSource::fillRecordInfo() -{ - Group recordings; - - try - { - recordings = sourceFile->openGroup("/recordings"); - int numObjs = (int) recordings.getNumObjs(); - - for (int i=0; i < numObjs; i++) - { - try - { - Group recordN; - DataSet data; - Attribute attr; - DataSpace dSpace; - float sampleRate; - float bitVolts; - hsize_t dims[3]; - RecordInfo info; - - recordN = recordings.openGroup(String(i).toUTF8()); - data = recordN.openDataSet("data"); - attr = recordN.openAttribute("sample_rate"); - attr.read(PredType::NATIVE_FLOAT,&sampleRate); - attr = recordN.openAttribute("bit_depth"); - attr.read(PredType::NATIVE_FLOAT,&bitVolts); - dSpace = data.getSpace(); - dSpace.getSimpleExtentDims(dims); - - info.name="Record "+String(i); - info.numSamples = dims[0]; - info.sampleRate = sampleRate; - - bool foundBitVoltArray = false; - HeapBlock bitVoltArray(dims[1]); - - try - { - recordN = recordings.openGroup((String(i) + "/application_data").toUTF8()); - try - { - DataSet bV = recordN.openDataSet("channel_bit_volts"); - bV.read(bitVoltArray.getData(), PredType::NATIVE_FLOAT); - foundBitVoltArray = true; - } - catch (GroupIException) - { } - catch (DataSetIException) - { } - if (!foundBitVoltArray) - { - attr = recordN.openAttribute("channel_bit_volts"); - attr.read(ArrayType(PredType::NATIVE_FLOAT, 1, &dims[1]), bitVoltArray); - foundBitVoltArray = true; - } - } catch (GroupIException) - { - } catch (AttributeIException) - { - } - - for (int j = 0; j < dims[1]; j++) - { - RecordedChannelInfo c; - c.name = "CH" + String(j); - - if (foundBitVoltArray) - c.bitVolts = bitVoltArray[j]; - else - c.bitVolts = bitVolts; - - info.channels.add(c); - } - infoArray.add(info); - availableDataSets.add(i); - numRecords++; - } - catch (GroupIException) - { - } - catch (DataSetIException) - { - } - catch (AttributeIException) - { - } - catch (DataSpaceIException error) - { - PROCESS_ERROR; - } - } - } - catch (FileIException error) - { - PROCESS_ERROR; - } - catch (GroupIException error) - { - PROCESS_ERROR; - } -} - -void KWIKFileSource::updateActiveRecord() -{ - samplePos=0; - try - { - String path = "/recordings/" + String(availableDataSets[activeRecord.get()]) + "/data"; - dataSet = new DataSet(sourceFile->openDataSet(path.toUTF8())); - } - catch (FileIException error) - { - PROCESS_ERROR; - } - catch (DataSetIException error) - { - PROCESS_ERROR; - } -} - -void KWIKFileSource::seekTo(int64 sample) -{ - samplePos = sample % getActiveNumSamples(); -} - -int KWIKFileSource::readData(int16* buffer, int nSamples) -{ - DataSpace fSpace,mSpace; - int samplesToRead; - int nChannels = getActiveNumChannels(); - hsize_t dim[3],offset[3]; - - if (samplePos + nSamples > getActiveNumSamples()) - { - samplesToRead = (int) getActiveNumSamples() - (int) samplePos; - } - else - { - samplesToRead = nSamples; - } - - try - { - fSpace = dataSet->getSpace(); - dim[0] = samplesToRead; - dim[1] = nChannels; - dim[2] = 1; - offset[0] = samplePos; - offset[1] = 0; - offset[2] = 0; - - fSpace.selectHyperslab(H5S_SELECT_SET,dim,offset); - mSpace = DataSpace(2,dim); - - dataSet->read(buffer,PredType::NATIVE_INT16,mSpace,fSpace); - samplePos += samplesToRead; - return samplesToRead; - - } - catch (DataSetIException error) - { - PROCESS_ERROR; - return 0; - } - catch (DataSpaceIException error) - { - PROCESS_ERROR; - return 0; - } - return 0; -} - -void KWIKFileSource::processChannelData(int16* inBuffer, float* outBuffer, int channel, int64 numSamples) -{ - int n = getActiveNumChannels(); - float bitVolts = getChannelInfo(channel).bitVolts; - - for (int i=0; i < numSamples; i++) - { - *(outBuffer+i) = *(inBuffer+(n*i)+channel) * bitVolts; - } - -} - -bool KWIKFileSource::isReady() -{ - //HDF5 is by default not thread-safe, so we must warn the user. - if ((!skipRecordEngineCheck) && (CoreServices::getSelectedRecordEngineId() == "KWIK")) - { - int res = AlertWindow::showYesNoCancelBox(AlertWindow::WarningIcon, "Record format conflict", - "Both the selected input file for the File Reader and the output file format for recording use the HDF5 library.\n" - "This library is, by default, not thread safe, so running both at the same time might cause unexpected crashes (chances increase with signal complexity and number of recorded channels).\n\n" - "If you have a custom-built hdf5 library with the thread safe features turned on, you can safely continue, but performance will be reduced.\n" - "More information on:\n" - "https://www.hdfgroup.org/HDF5/doc/TechNotes/ThreadSafeLibrary.html/n" - "https://www.hdfgroup.org/hdf5-quest.html/n/n" - "Do you want to continue acquisition?", "Yes", "Yes and don't ask again", "No"); - switch (res) - { - case 2: - skipRecordEngineCheck = true; - case 1: - return true; - break; - default: - return false; - } - } - else - return true; -} diff --git a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h deleted file mode 100644 index 581462967d..0000000000 --- a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - ------------------------------------------------------------------ - - This file is part of the Open Ephys GUI - Copyright (C) 2013 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 . - -*/ - -#ifndef KWIKFILESOURCE_H_INCLUDED -#define KWIKFILESOURCE_H_INCLUDED - -#include - -#define MIN_KWIK_VERSION 2 -#define MAX_KWIK_VERSION 2 - -class HDF5RecordingData; -namespace H5 -{ -class DataSet; -class H5File; -class DataType; -} - -class KWIKFileSource : public FileSource -{ -public: - KWIKFileSource(); - ~KWIKFileSource(); - - int readData (int16* buffer, int nSamples) override; - - void seekTo (int64 sample) override; - - void processChannelData (int16* inBuffer, float* outBuffer, int channel, int64 numSamples) override; - - bool isReady() override; - - -private: - bool Open (File file) override; - void fillRecordInfo() override; - void updateActiveRecord() override; - - ScopedPointer sourceFile; - ScopedPointer dataSet; - - int64 samplePos; - Array availableDataSets; - bool skipRecordEngineCheck; -}; - - - -#endif // KWIKFILESOURCE_H_INCLUDED diff --git a/Source/Plugins/KWIKFormat/Makefile b/Source/Plugins/KWIKFormat/Makefile deleted file mode 100644 index 3b6cc54f68..0000000000 --- a/Source/Plugins/KWIKFormat/Makefile +++ /dev/null @@ -1,41 +0,0 @@ - -LIBNAME := $(notdir $(CURDIR)) -OBJDIR := $(OBJDIR)/$(LIBNAME) -TARGET := $(LIBNAME).so - -CXXFLAGS := $(CXXFLAGS) -I/usr/include/hdf5/serial -I/usr/local/hdf5/include -LDFLAGS := $(LDFLAGS) -L/usr/lib/x86_64-linux-gnu/hdf5/serial -L/usr/local/hdf5/lib -lhdf5 -lhdf5_cpp -l:OpenEphysHDF5Lib.so - -SRC_DIR := ${shell find ./ -type d -print} -VPATH := $(SOURCE_DIRS) - -SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) -OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) - -BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) - -VPATH = $(SRC_DIR) - -.PHONY: objdir - -$(OUTDIR)/$(TARGET): objdir $(OBJ) - -@mkdir -p $(BINDIR) - -@mkdir -p $(LIBDIR) - -@mkdir -p $(OUTDIR) - @echo "Building $(TARGET)" - @$(BLDCMD) - -$(OBJDIR)/%.o : %.cpp - @echo "Compiling $<" - @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" - - -objdir: - -@mkdir -p $(OBJDIR) - -clean: - @echo "Cleaning $(LIBNAME)" - -@rm -rf $(OBJDIR) - -@rm -f $(OUTDIR)/$(TARGET) - --include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/KWIKFormat/OpenEphysLib.cpp b/Source/Plugins/KWIKFormat/OpenEphysLib.cpp deleted file mode 100644 index ed107bdc4c..0000000000 --- a/Source/Plugins/KWIKFormat/OpenEphysLib.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#include -#include "RecordEngine/HDF5Recording.h" -#include "FileSource/KwikFileSource.h" -#include -#ifdef WIN32 -#include -#define EXPORT __declspec(dllexport) -#else -#define EXPORT __attribute__((visibility("default"))) -#endif - - -using namespace Plugin; -#define NUM_PLUGINS 2 - -extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) -{ - info->apiVersion = PLUGIN_API_VER; - info->name = "Kwik Format"; - info->libVersion = 1; - info->numPlugins = NUM_PLUGINS; -} - -extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) -{ - switch (index) - { - case 0: - info->type = Plugin::PLUGIN_TYPE_RECORD_ENGINE; - info->recordEngine.name = "Kwik"; - info->recordEngine.creator = &(Plugin::createRecordEngine); - break; - case 1: - info->type = Plugin::PLUGIN_TYPE_FILE_SOURCE; - info->fileSource.name = "Kwd file"; - info->fileSource.extensions = "kwd"; - info->fileSource.creator = &(Plugin::createFileSource); - break; - default: - return -1; - } - - return 0; -} - -#ifdef WIN32 -BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, - IN DWORD nReason, - IN LPVOID Reserved) -{ - return TRUE; -} - -#endif diff --git a/Source/Plugins/KWIKFormat/RecordEngine/.HDF5Recording.cpp.swo b/Source/Plugins/KWIKFormat/RecordEngine/.HDF5Recording.cpp.swo deleted file mode 100644 index d76ea40bbe72b51ccae817bf907953c0fcf1b01c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeHPYm6jS6)r#?jXads4>a-8%P!S3J>5OK>$1-5PSQKuv)k^x?CxRMUDu(e>-J2s zHC1g@_0A5BqiVtfYC7-B&5JNMSJr+ao( zj6bLf-*k7~*SY7Md+t5w+-0$JLv@N>U)ZVOd55BW?#ROYJy+zFlUo(VkYM=8&^|qmlcaTKfz7Ay}~W8Xt%jF#M{ejZn154mJDm?z+AOhw>wUg7iYyAw|Hp2 zdT7FSRy40z<4xPqD;8?<;=%nB@0BkMYpKv|x6eR8TAXs=t;&Ip*BaV=v9e?PaEVy3 zC3_9K{Ia>XDmT)3ryNK*ka8g9K+1uX11Sem4x}7-OLD+#pQGG?nV;>?e%1dxtLyV^ z{%_s?JlR!#j{keN|M_B9x&Jf$NI8&lAmu>Hfs_L&2T~5C97s8kavymX18d>*&~7zRqf%NHxk*MT;$ z4=4j~T%;(E0^bC_0Xz)c2b=^Zfvtc7{PXRK@&Ir@@KN9}upKA?Mc`84BH%(G1H5#h zqWl7Q26!6yA#gYF37`dNz)irrfvbUEXB6cT-~r$ipaC}ldx6V=-&~+5PXP}BUjx1h z+y&eTdc@}sW_$;snECTz0Yk;$WKb;49l$&=0OWvkfzy!XQ@|s@ zy}&9k23!b`oX-Qgp3EsF)3P0=8kWZlwwIMg@v(<3ac{b_BJWsmeSBH7EN;4L4yA*G zIVIC}>?U_z+d-9C+btND%N=j3V|qs0*@oAj=1C@$7|S!=J(UUg{H|h2SY0iPZ5_w_KTifMZCbB63d&wNJC?;r#apyu&IG>7jeed>|&0KT|tyM>EYeQ7C7`EKDiKY)ibOi$IL@)Usiy zh+l5oIL_To%H5|W-iGcU1v(XfZ&-%VCUsH6ttHJ{4(D-b{zjp9>(nmZRZJT<>Zu}o zqKQcEv0ykTm1Eo9gl+2FDL~j09n+iz9n+A9O6-mm6NQfE*^b}IAj|TTZSFu_EH4|0 zCVDB?0p@f{Ibj<*OWdRy8KJ2VPhFM%#)is6qdnH$k9^xLctyEQp_zs2drE5;G+kH2 z7gbB=C;JkCs)2?Jt3;LaP1}OX`0oc;6NUk5D+i*%;xcr&8!i&ESqfDkosTNPgi#kT zYT^2omMSzhLHQ(ac{3-tQx`VDjjYjTtW~(AqGm)($Oz(0I3kru%Tv=l?sm~brHh7l zgKc`NI6OC;Gh(%FU924p+PJ($Lm-s zN-e(1mjcP_54QpX%qGcmCI)&sFiC@&ApMf^w>(JN=?Uz!q!0DET z4Z&AdQ4C@hblk;UM&ah&VGe23sM!BJByZKC!;5=oQ8vf6<5-u&$Jv z^%KgH;d(LsQss&~z%AIDW~g=pf*LH%Vv*fOf9Ua+4R>fP+{G|@_oT5Ly>`|w! zV=}Zv>tc(A>h0PYH4ZI+!@Z7Uu`KNqS(<`x5EBy|83Bvr4btjD}seGmq)2C|JX3li6+0u&o@^ ztTkq0c{&W+ql;IVC7tdj91vZ%TOJ6&FS}!55u-TIIK&oJXFCEm`#jBqDGCja7N|yj zq4yOxsI~#aHE06K$6dyJ7T4C#w?#y`K2aky5!Tc!$UR~NjjUOE5t3Q4b)&V0Ne}(k z4>}o#MmfgPvd7f%9NS(R-pM9R+c7Zy1SE6|?%GTF(4IoV!Kuy*COs1!ym3SR%^|5c zppgI1;je1&Ysvo)^7lW1kN+%iFF?Nk5}*SY0OtYc0?)(ee-8K(@G;;czzlFU@CJPU zp8@v+?*aY_AOB_GkHGJN-vRdkp9U6y9l#6l>%Rz00`CW^z-#c`{|=l6o&?CRUjQ<| z`M`O=OBuuhfbRk)fnC5=z!d=b_Ag>?F90sk1Zb`|0qK)+Amu>Hfs_L&2T~6Fk2oMh zGrnWCqFK^?m9azVgiUFdw#1zQhEm@l?NO{sr!Jp8v}zpB=C`R_{2Ratt%}As~(kx!F7s zMNRf0hBz`(Z`AVH20!U#edoV#4~@oTcjLL;`K+kF5JWoG^%9a<3CQg2!i(^@i21q( zoyU7kd1!3aF+5(U=&H(1-t-&_EsGGGU`)Z1K?XN3f8uDb`Cmg%wr>z2);C4=y84$6 zITK^gEZ&T#=(fpj15JsT zNbwE$6u&mY-5^?watc!a`2Mc95AomQIYiSb zD(p`XV{X71nF<7TGat zzXqtE>7$@=LZF8*R%$coF)7VOk_Td?h#w3xBL{A(GFB|>F8h-tkfiD0L z0xqxu|fJcG*fZKp+;2>}Y)81LlA_ zPy_x#`FY?Na0GZK@H%qyF9D~4CjbxF0sIts`BmU5;7Z`<$iY7bdO6*c3a1J2HZulD;D-sCf;$YI(!ECM#+p zix3;HFA>zg=;H>Qoook@LnMfB&=VuH*X9v@^>1jb_3d0H67q~vQ}T>0Cm8U3vl|nM zYTe$X+lbNfSl_Vg!>q_uk%Lo9C$Dd{S~)gAdzNo^afL(fbh5)BKS$({b&Yu8=OTr9 zLt)PhWLOxbOF)5GFx0}+Aqws|Y|kE+ou_lKpI2cv#N%`j!A5Sk*`B({Iat?uNISF<+09eLnT~geJ%^cHJ6n!8N@^Ak4j2kJ%ElzEe#rdb&?Q7Q;7?1|5^NQlX@aj^DIRcUowT~DkW>>|-#@|JXWI_5+r4+vC@H>~ z>^&)%KTa~gOosylSDZjEowVtpx*2J7qU`-qBRE*nnNrmA{{(vMA29n>i52TLlL-d3}tAi2H3qTZ4(k3)W4CTE-lkM8nwob?thqzi2*zb3Sf~kp5Ed zv1BQ1gXeURk{1QhU6be=H3;V@q%EB64ey|>7d@>X_y?4j5A>l+UE)P>RpgOWI^sgx z=z5K4st)wf!GE{2a1qh;f+tBrFp1c}!K+P8A*dWJ$Y31$OpYa3nz)k}uj^kikgs|* z^x*7~Bmxe${r-h@kUN{l$r7E?;d=+LT8>ZZ2D%`SYCx4M!7LMy7oT3w3{nHbh=-K|)$PskV&Ir6wDJ2)&V_KX08cjQn{)S^AvZwCbqLjkbwsBu)B#DvE$ z_MVs``{9b@?ILN(GITl-5{(_P@HHMGFmhm8WJcIEd*chz?_8FP8%X5+N7d!!G@m-@ zt8RKIG3&rI_;$n}k9dBDeu9Rgm;U>~OTXiJQohide*r!ER`mb? diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp deleted file mode 100644 index 185f885ed1..0000000000 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/* - ------------------------------------------------------------------ - - This file is part of the Open Ephys GUI - Copyright (C) 2014 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 . - - */ - -#include "HDF5Recording.h" -#define MAX_BUFFER_SIZE 40960 -#define CHANNEL_TIMESTAMP_PREALLOC_SIZE 128 -#define CHANNEL_TIMESTAMP_MIN_WRITE 32 -#define TIMESTAMP_EACH_NSAMPLES 1024 - -HDF5Recording::HDF5Recording() : processorIndex(-1), bufferSize(MAX_BUFFER_SIZE), hasAcquired(false) -{ - //timestamp = 0; - scaledBuffer.malloc(MAX_BUFFER_SIZE); - intBuffer.malloc(MAX_BUFFER_SIZE); -} - -HDF5Recording::~HDF5Recording() -{ -} - -String HDF5Recording::getEngineID() const -{ - return "KWIK"; -} - -// void HDF5Recording::updateTimeStamp(int64 timestamp) -// { -// this->timestamp = timestamp; -// } - -void HDF5Recording::registerProcessor(const GenericProcessor* proc) -{ - KWIKRecordingInfo* info = new KWIKRecordingInfo(); - info->sample_rate = proc->getSampleRate(); - info->bit_depth = 16; - info->multiSample = false; - infoArray.add (info); - - fileArray.add (new KWDFile()); - bitVoltsArray.add (new Array); - sampleRatesArray.add (new Array); - channelsPerProcessor.add (0); - processorIndex++; -} - -void HDF5Recording::resetChannels() -{ - scaledBuffer.malloc(MAX_BUFFER_SIZE); - intBuffer.malloc(MAX_BUFFER_SIZE); - bufferSize = MAX_BUFFER_SIZE; - processorIndex = -1; - fileArray.clear(); - channelsPerProcessor.clear(); - bitVoltsArray.clear(); - sampleRatesArray.clear(); - processorMap.clear(); - infoArray.clear(); - recordedChanToKWDChan.clear(); - channelLeftOverSamples.clear(); - channelTimestampArray.clear(); - if (spikesFile) - spikesFile->resetChannels(); -} - -void HDF5Recording::addDataChannel(int index,const DataChannel* chan) -{ - processorMap.add(processorIndex); -} - -void HDF5Recording::openFiles(File rootFolder, int experimentNumber, int recordingNumber) -{ - String basepath = rootFolder.getFullPathName() + rootFolder.separatorString + "experiment" + String(experimentNumber); - //KWE file - eventFile->initFile(basepath); - eventFile->open(); - - //KWX file - spikesFile->initFile(basepath); - spikesFile->open(); - spikesFile->startNewRecording(recordingNumber); - - //Let's just put the first processor (usually the source node) on the KWIK for now - infoArray[0]->name = String("Open Ephys Recording #") + String(recordingNumber); - - /* if (hasAcquired) - infoArray[0]->start_time = (*timestamps)[getChannel(0)->sourceNodeId]; //(*timestamps).begin()->first; - else - infoArray[0]->start_time = 0;*/ - infoArray[0]->start_time = getTimestamp(0); - - infoArray[0]->start_sample = 0; - eventFile->startNewRecording(recordingNumber,infoArray[0]); - - //KWD files - recordedChanToKWDChan.clear(); - Array processorRecPos; - processorRecPos.insertMultiple(0, 0, fileArray.size()); - for (int i = 0; i < getNumRecordedChannels(); i++) - { - int index = processorMap[getRealChannel(i)]; - if (!fileArray[index]->isOpen()) - { - fileArray[index]->initFile(getDataChannel(getRealChannel(i))->getCurrentNodeID(), basepath); - infoArray[index]->start_time = getTimestamp(i); - } - - channelsPerProcessor.set(index, channelsPerProcessor[index] + 1); - bitVoltsArray[index]->add(getDataChannel(getRealChannel(i))->getBitVolts()); - sampleRatesArray[index]->add(getDataChannel(getRealChannel(i))->getSampleRate()); - if (getDataChannel(getRealChannel(i))->getSampleRate() != infoArray[index]->sample_rate) - { - infoArray[index]->multiSample = true; - } - int procPos = processorRecPos[index]; - recordedChanToKWDChan.add(procPos); - processorRecPos.set(index, procPos+1); - channelTimestampArray.add(new Array); - channelTimestampArray.getLast()->ensureStorageAllocated(CHANNEL_TIMESTAMP_PREALLOC_SIZE); - channelLeftOverSamples.add(0); - } - - for (int i = 0; i < fileArray.size(); i++) - { - if ((!fileArray[i]->isOpen()) && (fileArray[i]->isReadyToOpen())) - { - fileArray[i]->open(channelsPerProcessor[i]); - } - if (fileArray[i]->isOpen()) - { - // File f(fileArray[i]->getFileName()); - // eventFile->addKwdFile(f.getFileName()); - - infoArray[i]->name = String("Open Ephys Recording #") + String(recordingNumber); - // infoArray[i]->start_time = timestamp; - infoArray[i]->start_sample = 0; - infoArray[i]->bitVolts.clear(); - infoArray[i]->bitVolts.addArray(*bitVoltsArray[i]); - infoArray[i]->channelSampleRates.clear(); - infoArray[i]->channelSampleRates.addArray(*sampleRatesArray[i]); - fileArray[i]->startNewRecording(recordingNumber,bitVoltsArray[i]->size(),infoArray[i]); - } - } - - hasAcquired = true; -} - -void HDF5Recording::closeFiles() -{ - eventFile->stopRecording(); - eventFile->close(); - spikesFile->stopRecording(); - spikesFile->close(); - for (int i = 0; i < fileArray.size(); i++) - { - if (fileArray[i]->isOpen()) - { - std::cout << "Closed file " << i << std::endl; - fileArray[i]->stopRecording(); - fileArray[i]->close(); - bitVoltsArray[i]->clear(); - sampleRatesArray[i]->clear(); - } - channelsPerProcessor.set(i, 0); - } - recordedChanToKWDChan.clear(); - channelTimestampArray.clear(); - channelLeftOverSamples.clear(); - scaledBuffer.malloc(MAX_BUFFER_SIZE); - intBuffer.malloc(MAX_BUFFER_SIZE); - bufferSize = MAX_BUFFER_SIZE; -} - -void HDF5Recording::writeData(int writeChannel, int realChannel, const float* buffer, int size) -{ - if (size > bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on flie close and reset. - { - std::cerr << "Write buffer overrun, resizing to" << size << std::endl; - bufferSize = size; - scaledBuffer.malloc(size); - intBuffer.malloc(size); - } - double multFactor = 1 / (float(0x7fff) * getDataChannel(realChannel)->getBitVolts()); - int index = processorMap[realChannel]; //CHECK - FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), buffer, multFactor, size); - AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), size); - fileArray[index]->writeRowData(intBuffer.getData(), size, recordedChanToKWDChan[writeChannel]); - - int sampleOffset = channelLeftOverSamples[writeChannel]; - int blockStart = sampleOffset; - int64 currentTS = getTimestamp(writeChannel); - - if (sampleOffset > 0) - { - currentTS += TIMESTAMP_EACH_NSAMPLES - sampleOffset; - blockStart += TIMESTAMP_EACH_NSAMPLES - sampleOffset; - } - - for (int i = 0; i < size; i += TIMESTAMP_EACH_NSAMPLES) - { - if ((blockStart + i) < (sampleOffset + size)) - { - channelTimestampArray[writeChannel]->add(currentTS); - currentTS += TIMESTAMP_EACH_NSAMPLES; - } - } - channelLeftOverSamples.set(writeChannel, (size + sampleOffset) % TIMESTAMP_EACH_NSAMPLES); -} - -void HDF5Recording::endChannelBlock(bool lastBlock) -{ - int nCh = channelTimestampArray.size(); - for (int ch = 0; ch < nCh; ++ch) - { - int tsSize = channelTimestampArray[ch]->size(); - if ((tsSize > 0) && ((tsSize > CHANNEL_TIMESTAMP_MIN_WRITE) || lastBlock)) - { - int realChan = getRealChannel(ch); - int index = processorMap[realChan]; //CHECK - fileArray[index]->writeTimestamps(channelTimestampArray[ch]->getRawDataPointer(), tsSize, recordedChanToKWDChan[ch]); - channelTimestampArray[ch]->clearQuick(); - } - } -} - -void HDF5Recording::writeEvent(int eventChannel, const MidiMessage& event) -{ - if (Event::getEventType(event) == EventChannel::TTL) - { - TTLEventPtr ttl = TTLEvent::deserializeFromMessage(event, getEventChannel(eventChannel)); - if (ttl == nullptr) return; - uint8 channel = ttl->getChannel(); - eventFile->writeEvent(0, (ttl->getState() ? 1 : 0), ttl->getSourceID(), &channel, ttl->getTimestamp()); - } - else if (Event::getEventType(event) == EventChannel::TEXT) - { - TextEventPtr text = TextEvent::deserializeFromMessage(event, getEventChannel(eventChannel)); - if (text == nullptr) return; - String textMsg = text->getText(); - eventFile->writeEvent(1, 0, text->getSourceID(), textMsg.toUTF8().getAddress(), text->getTimestamp()); - } -} - -void HDF5Recording::writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float, String text) -{ - eventFile->writeEvent(1, 0xFF, sourceID, text.toUTF8().getAddress(), timestamp); -} - -void HDF5Recording::addSpikeElectrode(int index, const SpikeChannel* elec) -{ - spikesFile->addChannelGroup(elec->getNumChannels()); -} -void HDF5Recording::writeSpike(int electrodeIndex, const SpikeEvent* spike) -{ - const SpikeChannel* spikeInfo = getSpikeChannel(electrodeIndex); - Array bitVolts; - for (int i = 0; i < spikeInfo->getNumChannels(); i++) - bitVolts.add(spikeInfo->getChannelBitVolts(i)); - spikesFile->writeSpike(electrodeIndex,spikeInfo->getTotalSamples(),spike->getDataPointer(), bitVolts, spike->getTimestamp()); -} - -void HDF5Recording::startAcquisition() -{ - eventFile = new KWEFile(); - eventFile->addEventType("TTL",HDF5FileBase::BaseDataType::U8,"event_channels"); - eventFile->addEventType("Messages", HDF5FileBase::BaseDataType::DSTR, "Text"); - spikesFile = new KWXFile(); -} - -RecordEngineManager* HDF5Recording::getEngineManager() -{ - RecordEngineManager* man = new RecordEngineManager("KWIK","Kwik",&(engineFactory)); - return man; -} diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h deleted file mode 100644 index 7b9705032f..0000000000 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - ------------------------------------------------------------------ - - This file is part of the Open Ephys GUI - Copyright (C) 2014 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 . - - */ - -#ifndef HDF5RECORDING_H_INCLUDED -#define HDF5RECORDING_H_INCLUDED - -#include -#include "KWIKFormat.h" - -class HDF5Recording : public RecordEngine -{ -public: - HDF5Recording(); - ~HDF5Recording(); - String getEngineID() const override; - void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; - void closeFiles() override; - void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; - void writeEvent(int eventType, const MidiMessage& event) override; - void writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float, String text) override; - void addDataChannel(int index, const DataChannel* chan) override; - void addSpikeElectrode(int index,const SpikeChannel* elec) override; - void writeSpike(int electrodeIndex, const SpikeEvent* spike) override; - void registerProcessor(const GenericProcessor* processor) override; - void resetChannels() override; - void startAcquisition() override; - void endChannelBlock(bool lastBlock) override; - - static RecordEngineManager* getEngineManager(); -private: - - int processorIndex; - - Array processorMap; - Array channelsPerProcessor; - Array recordedChanToKWDChan; - OwnedArray> bitVoltsArray; - OwnedArray> sampleRatesArray; - OwnedArray> channelTimestampArray; - Array channelLeftOverSamples; - OwnedArray fileArray; - OwnedArray infoArray; - ScopedPointer eventFile; - ScopedPointer spikesFile; - HeapBlock scaledBuffer; - HeapBlock intBuffer; - int bufferSize; - //float* scaledBuffer; - //int16* intBuffer; - - bool hasAcquired; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HDF5Recording); -}; - - -#endif // HDF5RECORDING_H_INCLUDED diff --git a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp b/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp deleted file mode 100644 index 110d86cf3c..0000000000 --- a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/* - ------------------------------------------------------------------ - - This file is part of the Open Ephys GUI - Copyright (C) 2014 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 . - - */ - - #include "KWIKFormat.h" - -#define CHUNK_XSIZE 2048 -#define EVENT_CHUNK_SIZE 8 -#define SPIKE_CHUNK_XSIZE 8 -#define SPIKE_CHUNK_YSIZE 40 -#define TIMESTAMP_CHUNK_SIZE 16 -#define MAX_TRANSFORM_SIZE 512 -#define MAX_STR_SIZE 256 - - //KWD File - -KWDFile::KWDFile(int processorNumber, String basename) : HDF5FileBase() -{ - initFile(processorNumber, basename); -} - -KWDFile::KWDFile() : HDF5FileBase() -{ -} - -KWDFile::~KWDFile() {} - -String KWDFile::getFileName() -{ - return filename; -} - -void KWDFile::initFile(int processorNumber, String basename) -{ - if (isOpen()) return; - filename = basename + "_" + String(processorNumber) + ".raw.kwd"; - readyToOpen=true; -} - -void KWDFile::startNewRecording(int recordingNumber, int nChannels, KWIKRecordingInfo* info) -{ - this->recordingNumber = recordingNumber; - this->nChannels = nChannels; - this->multiSample = info->multiSample; - uint8 mSample = info->multiSample ? 1 : 0; - - ScopedPointer bitVoltsSet; - ScopedPointer sampleRateSet; - - String recordPath = String("/recordings/")+String(recordingNumber); - CHECK_ERROR(createGroup(recordPath)); - CHECK_ERROR(setAttributeStr(info->name,recordPath,String("name"))); - CHECK_ERROR(setAttribute(BaseDataType::U64, &(info->start_time), recordPath, String("start_time"))); - CHECK_ERROR(setAttribute(BaseDataType::U32, &(info->start_sample), recordPath, String("start_sample"))); - CHECK_ERROR(setAttribute(BaseDataType::F32, &(info->sample_rate), recordPath, String("sample_rate"))); - CHECK_ERROR(setAttribute(BaseDataType::U32, &(info->bit_depth), recordPath, String("bit_depth"))); - CHECK_ERROR(createGroup(recordPath+"/application_data")); - // CHECK_ERROR(setAttributeArray(F32,info->bitVolts.getRawDataPointer(),info->bitVolts.size(),recordPath+"/application_data",String("channel_bit_volts"))); - bitVoltsSet = createDataSet(BaseDataType::F32, info->bitVolts.size(), 0, recordPath + "/application_data/channel_bit_volts"); - if (bitVoltsSet.get()) - bitVoltsSet->writeDataBlock(info->bitVolts.size(), BaseDataType::F32, info->bitVolts.getRawDataPointer()); - else - std::cerr << "Error creating bitvolts data set" << std::endl; - - CHECK_ERROR(setAttribute(BaseDataType::U8, &mSample, recordPath + "/application_data", String("is_multiSampleRate_data"))); - //CHECK_ERROR(setAttributeArray(F32,info->channelSampleRates.getRawDataPointer(),info->channelSampleRates.size(),recordPath+"/application_data",String("channel_sample_rates"))); - sampleRateSet = createDataSet(BaseDataType::F32, info->channelSampleRates.size(), 0, recordPath + "/application_data/channel_sample_rates"); - if (sampleRateSet.get()) - sampleRateSet->writeDataBlock(info->channelSampleRates.size(), BaseDataType::F32, info->channelSampleRates.getRawDataPointer()); - else - std::cerr << "Error creating sample rates data set" << std::endl; - - recdata = createDataSet(BaseDataType::I16, 0, nChannels, CHUNK_XSIZE, recordPath + "/data"); - if (!recdata.get()) - std::cerr << "Error creating data set" << std::endl; - - tsData = createDataSet(BaseDataType::I64, 0, nChannels, TIMESTAMP_CHUNK_SIZE, recordPath + "/application_data/timestamps"); - if (!tsData.get()) - std::cerr << "Error creating timestamps data set" << std::endl; - - curChan = nChannels; -} - -void KWDFile::stopRecording() -{ - Array samples; - String path = String("/recordings/")+String(recordingNumber)+String("/data"); - recdata->getRowXPositions(samples); - - CHECK_ERROR(setAttributeArray(BaseDataType::U32, samples.getRawDataPointer(), samples.size(), path, "valid_samples")); - //ScopedPointer does the deletion and destructors the closings - recdata = nullptr; - tsData = nullptr; -} - -int KWDFile::createFileStructure() -{ - const uint16 ver = 2; - if (createGroup("/recordings")) return -1; - if (setAttribute(BaseDataType::U16, (void*)&ver, "/", "kwik_version")) return -1; - return 0; -} - -void KWDFile::writeBlockData(int16* data, int nSamples) -{ - CHECK_ERROR(recdata->writeDataBlock(nSamples, BaseDataType::I16, data)); -} - -void KWDFile::writeRowData(int16* data, int nSamples) -{ - if (curChan >= nChannels) - { - curChan=0; - } - CHECK_ERROR(recdata->writeDataRow(curChan, nSamples, BaseDataType::I16, data)); - curChan++; -} - -void KWDFile::writeRowData(int16* data, int nSamples, int channel) -{ - if (channel >= 0 && channel < nChannels) - { - CHECK_ERROR(recdata->writeDataRow(channel, nSamples, BaseDataType::I16, data)); - curChan = channel; - } -} - -void KWDFile::writeTimestamps(int64* ts, int nTs, int channel) -{ - if (channel >= 0 && channel < nChannels) - { - CHECK_ERROR(tsData->writeDataRow(channel, nTs, BaseDataType::I64, ts)); - } -} - -//KWE File - -KWEFile::KWEFile(String basename) : HDF5FileBase() -{ - initFile(basename); -} - -KWEFile::KWEFile() : HDF5FileBase() -{ - -} - -KWEFile::~KWEFile() {} - -String KWEFile::getFileName() -{ - return filename; -} - -void KWEFile::initFile(String basename) -{ - if (isOpen()) return; - filename = basename + ".kwe"; - readyToOpen=true; -} - -int KWEFile::createFileStructure() -{ - const uint16 ver = 2; - if (createGroup("/recordings")) return -1; - if (createGroup("/event_types")) return -1; - for (int i=0; i < eventNames.size(); i++) - { - ScopedPointer dSet; - String path = "/event_types/" + eventNames[i]; - if (createGroup(path)) return -1; - path += "/events"; - if (createGroup(path)) return -1; - dSet = createDataSet(BaseDataType::U64, 0, EVENT_CHUNK_SIZE, path + "/time_samples"); - if (!dSet) return -1; - dSet = createDataSet(BaseDataType::U16, 0, EVENT_CHUNK_SIZE, path + "/recording"); - if (!dSet) return -1; - path += "/user_data"; - if (createGroup(path)) return -1; - dSet = createDataSet(BaseDataType::U8, 0, EVENT_CHUNK_SIZE, path + "/eventID"); - if (!dSet) return -1; - dSet = createDataSet(BaseDataType::U8, 0, EVENT_CHUNK_SIZE, path + "/nodeID"); - if (!dSet) return -1; - dSet = createDataSet(eventTypes[i],0,EVENT_CHUNK_SIZE,path + "/" + eventDataNames[i]); - if (!dSet) return -1; - } - if (setAttribute(BaseDataType::U16, (void*)&ver, "/", "kwik_version")) return -1; - - return 0; -} - -void KWEFile::startNewRecording(int recordingNumber, KWIKRecordingInfo* info) -{ - this->recordingNumber = recordingNumber; - kwdIndex=0; - String recordPath = String("/recordings/")+String(recordingNumber); - CHECK_ERROR(createGroup(recordPath)); - CHECK_ERROR(setAttributeStr(info->name,recordPath,String("name"))); - CHECK_ERROR(setAttribute(BaseDataType::U64, &(info->start_time), recordPath, String("start_time"))); - // CHECK_ERROR(setAttribute(U32,&(info->start_sample),recordPath,String("start_sample"))); - CHECK_ERROR(setAttribute(BaseDataType::F32, &(info->sample_rate), recordPath, String("sample_rate"))); - CHECK_ERROR(setAttribute(BaseDataType::U32, &(info->bit_depth), recordPath, String("bit_depth"))); - // CHECK_ERROR(createGroup(recordPath + "/raw")); - // CHECK_ERROR(createGroup(recordPath + "/raw/hdf5_paths")); - - for (int i = 0; i < eventNames.size(); i++) - { - HDF5RecordingData* dSet; - String path = "/event_types/" + eventNames[i] + "/events"; - dSet = getDataSet(path + "/time_samples"); - if (!dSet) - std::cerr << "Error loading event timestamps dataset for type " << i << std::endl; - timeStamps.add(dSet); - dSet = getDataSet(path + "/recording"); - if (!dSet) - std::cerr << "Error loading event recordings dataset for type " << i << std::endl; - recordings.add(dSet); - dSet = getDataSet(path + "/user_data/eventID"); - if (!dSet) - std::cerr << "Error loading event ID dataset for type " << i << std::endl; - eventID.add(dSet); - dSet = getDataSet(path + "/user_data/nodeID"); - if (!dSet) - std::cerr << "Error loading event node ID dataset for type " << i << std::endl; - nodeID.add(dSet); - dSet = getDataSet(path + "/user_data/" + eventDataNames[i]); - if (!dSet) - std::cerr << "Error loading event channel dataset for type " << i << std::endl; - eventData.add(dSet); - } -} - -void KWEFile::stopRecording() -{ - timeStamps.clear(); - recordings.clear(); - eventID.clear(); - nodeID.clear(); - eventData.clear(); -} - -void KWEFile::writeEvent(int type, uint8 id, uint8 processor, void* data, int64 timestamp) -{ - if (type > eventNames.size() || type < 0) - { - std::cerr << "HDF5::writeEvent Invalid event type " << type << std::endl; - return; - } - CHECK_ERROR(timeStamps[type]->writeDataBlock(1, BaseDataType::U64, ×tamp)); - CHECK_ERROR(recordings[type]->writeDataBlock(1, BaseDataType::I32, &recordingNumber)); - CHECK_ERROR(eventID[type]->writeDataBlock(1, BaseDataType::U8, &id)); - CHECK_ERROR(nodeID[type]->writeDataBlock(1, BaseDataType::U8, &processor)); - CHECK_ERROR(eventData[type]->writeDataBlock(1,eventTypes[type],data)); -} - -/*void KWEFile::addKwdFile(String filename) -{ - if (kwdIndex == 0) - { - CHECK_ERROR(setAttributeStr(filename + "/recordings/" + String(recordingNumber), "/recordings/" + String(recordingNumber) + - "/raw", "hdf5_path")); - } - CHECK_ERROR(setAttributeStr(filename + "/recordings/" + String(recordingNumber),"/recordings/" + String(recordingNumber) + - "/raw/hdf5_paths",String(kwdIndex))); - kwdIndex++; -}*/ - -void KWEFile::addEventType(String name, BaseDataType type, String dataName) -{ - eventNames.add(name); - eventTypes.add(type); - eventDataNames.add(dataName); -} - -//KWX File - -KWXFile::KWXFile(String basename) : HDF5FileBase() -{ - initFile(basename); - numElectrodes=0; - transformVector.malloc(MAX_TRANSFORM_SIZE); -} - -KWXFile::KWXFile() : HDF5FileBase() -{ - numElectrodes=0; - transformVector.malloc(MAX_TRANSFORM_SIZE); -} - -KWXFile::~KWXFile() -{ -} - -String KWXFile::getFileName() -{ - return filename; -} - -void KWXFile::initFile(String basename) -{ - if (isOpen()) return; - filename = basename + ".kwx"; - readyToOpen=true; -} - -int KWXFile::createFileStructure() -{ - const uint16 ver = 2; - if (createGroup("/channel_groups")) return -1; - if (setAttribute(BaseDataType::U16, (void*)&ver, "/", "kwik_version")) return -1; - for (int i=0; i < channelArray.size(); i++) - { - int res = createChannelGroup(i); - if (res) return -1; - } - return 0; -} - -void KWXFile::addChannelGroup(int nChannels) -{ - channelArray.add(nChannels); - numElectrodes++; -} - -int KWXFile::createChannelGroup(int index) -{ - ScopedPointer dSet; - int nChannels = channelArray[index]; - String path("/channel_groups/"+String(index)); - CHECK_ERROR(createGroup(path)); - dSet = createDataSet(BaseDataType::I16, 0, 0, nChannels, SPIKE_CHUNK_XSIZE, SPIKE_CHUNK_YSIZE, path + "/waveforms_filtered"); - if (!dSet) return -1; - dSet = createDataSet(BaseDataType::U64, 0, SPIKE_CHUNK_XSIZE, path + "/time_samples"); - if (!dSet) return -1; - dSet = createDataSet(BaseDataType::U16, 0, SPIKE_CHUNK_XSIZE, path + "/recordings"); - if (!dSet) return -1; - return 0; -} - -void KWXFile::startNewRecording(int recordingNumber) -{ - HDF5RecordingData* dSet; - String path; - this->recordingNumber = recordingNumber; - - for (int i=0; i < channelArray.size(); i++) - { - path = "/channel_groups/"+String(i); - dSet=getDataSet(path+"/waveforms_filtered"); - if (!dSet) - std::cerr << "Error loading spikes dataset for group " << i << std::endl; - spikeArray.add(dSet); - dSet=getDataSet(path+"/time_samples"); - if (!dSet) - std::cerr << "Error loading spike timestamp dataset for group " << i << std::endl; - timeStamps.add(dSet); - dSet=getDataSet(path+"/recordings"); - if (!dSet) - std::cerr << "Error loading spike recordings dataset for group " << i << std::endl; - recordingArray.add(dSet); - } -} - -void KWXFile::stopRecording() -{ - spikeArray.clear(); - timeStamps.clear(); - recordingArray.clear(); -} - -void KWXFile::resetChannels() -{ - stopRecording(); //Just in case - channelArray.clear(); -} - -void KWXFile::writeSpike(int groupIndex, int nSamples, const float* data, Array& bitVolts, int64 timestamp) -{ - if ((groupIndex < 0) || (groupIndex >= numElectrodes)) - { - std::cerr << "HDF5::writeSpike Electrode index out of bounds " << groupIndex << std::endl; - return; - } - int nChans= channelArray[groupIndex]; - int16* dst=transformVector; - - //Given the way we store spike data, we need to transpose it to store in - //N x NSAMPLES x NCHANNELS as well as convert from float to i16 - for (int i = 0; i < nSamples; i++) - { - for (int j = 0; j < nChans; j++) - { - *(dst++) = static_cast((*(data+j*nSamples+i))/bitVolts[j]); - } - } - - CHECK_ERROR(spikeArray[groupIndex]->writeDataBlock(1, nSamples, BaseDataType::I16, transformVector)); - CHECK_ERROR(recordingArray[groupIndex]->writeDataBlock(1, BaseDataType::I32, &recordingNumber)); - CHECK_ERROR(timeStamps[groupIndex]->writeDataBlock(1, BaseDataType::U64, ×tamp)); -} diff --git a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h b/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h deleted file mode 100644 index 0ffefea2d0..0000000000 --- a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - ------------------------------------------------------------------ - - This file is part of the Open Ephys GUI - Copyright (C) 2014 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 . - - */ - -#ifndef KWIKFORMAT_H_INCLUDED -#define KWIKFORMAT_H_INCLUDED - -#include -using namespace OpenEphysHDF5; - -struct KWIKRecordingInfo -{ - String name; - int64 start_time; - uint32 start_sample; - float sample_rate; - uint32 bit_depth; - Array bitVolts; - Array channelSampleRates; - bool multiSample; -}; - -class KWDFile : public HDF5FileBase -{ -public: - KWDFile(int processorNumber, String basename); - KWDFile(); - virtual ~KWDFile(); - void initFile(int processorNumber, String basename); - void startNewRecording(int recordingNumber, int nChannels, KWIKRecordingInfo* info); - void stopRecording(); - void writeBlockData(int16* data, int nSamples); - void writeRowData(int16* data, int nSamples); - void writeRowData(int16* data, int nSamples, int channel); - void writeTimestamps(int64* ts, int nTs, int channel); - String getFileName(); - -protected: - int createFileStructure(); - -private: - int recordingNumber; - int nChannels; - int curChan; - String filename; - bool multiSample; - ScopedPointer recdata; - ScopedPointer tsData; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(KWDFile); -}; - -class KWEFile : public HDF5FileBase -{ -public: - KWEFile(String basename); - KWEFile(); - virtual ~KWEFile(); - void initFile(String basename); - void startNewRecording(int recordingNumber, KWIKRecordingInfo* info); - void stopRecording(); - void writeEvent(int type, uint8 id, uint8 processor, void* data, int64 timestamp); - // void addKwdFile(String filename); - void addEventType(String name, BaseDataType type, String dataName); - String getFileName(); - -protected: - int createFileStructure(); - -private: - int recordingNumber; - String filename; - OwnedArray timeStamps; - OwnedArray recordings; - OwnedArray eventID; - OwnedArray nodeID; - OwnedArray eventData; - Array eventNames; - Array eventTypes; - Array eventDataNames; - int kwdIndex; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(KWEFile); -}; - -class KWXFile : public HDF5FileBase -{ -public: - KWXFile(String basename); - KWXFile(); - virtual ~KWXFile(); - void initFile(String basename); - void startNewRecording(int recordingNumber); - void stopRecording(); - void addChannelGroup(int nChannels); - void resetChannels(); - void writeSpike(int groupIndex, int nSamples, const float* data, Array& bitVolts, int64 timestamp); - String getFileName(); - -protected: - int createFileStructure(); - -private: - int createChannelGroup(int index); - int recordingNumber; - String filename; - OwnedArray spikeArray; - OwnedArray recordingArray; - OwnedArray timeStamps; - Array channelArray; - int numElectrodes; - HeapBlock transformVector; - //int16* transformVector; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(KWXFile); -}; - -#endif diff --git a/Source/Plugins/NWBFormat/Makefile b/Source/Plugins/NWBFormat/Makefile deleted file mode 100644 index fbe5b93837..0000000000 --- a/Source/Plugins/NWBFormat/Makefile +++ /dev/null @@ -1,40 +0,0 @@ - -LIBNAME := $(notdir $(CURDIR)) -OBJDIR := $(OBJDIR)/$(LIBNAME) -TARGET := $(LIBNAME).so - -LDFLAGS += -l:OpenEphysHDF5Lib.so - -SRC_DIR := ${shell find ./ -type d -print} -VPATH := $(SOURCE_DIRS) - -SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) -OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) - -BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) - -VPATH = $(SRC_DIR) - -.PHONY: objdir - -$(OUTDIR)/$(TARGET): objdir $(OBJ) - -@mkdir -p $(BINDIR) - -@mkdir -p $(LIBDIR) - -@mkdir -p $(OUTDIR) - @echo "Building $(TARGET)" - @$(BLDCMD) - -$(OBJDIR)/%.o : %.cpp - @echo "Compiling $<" - @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" - - -objdir: - -@mkdir -p $(OBJDIR) - -clean: - @echo "Cleaning $(LIBNAME)" - -@rm -rf $(OBJDIR) - -@rm -f $(OUTDIR)/$(TARGET) - --include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/NWBFormat/NWBFormat.cpp b/Source/Plugins/NWBFormat/NWBFormat.cpp deleted file mode 100644 index 30e3d3a08b..0000000000 --- a/Source/Plugins/NWBFormat/NWBFormat.cpp +++ /dev/null @@ -1,674 +0,0 @@ -/* - ------------------------------------------------------------------ - - 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 . - - */ - - #include "NWBFormat.h" - using namespace NWBRecording; - -#ifndef EVENT_CHUNK_SIZE -#define EVENT_CHUNK_SIZE 8 -#endif - -#ifndef SPIKE_CHUNK_XSIZE -#define SPIKE_CHUNK_XSIZE 8 -#endif - -#ifndef SPIKE_CHUNK_YSIZE -#define SPIKE_CHUNK_YSIZE 40 -#endif - - #define MAX_BUFFER_SIZE 40960 - - NWBFile::NWBFile(String fName, String ver, String idText) : HDF5FileBase(), filename(fName), identifierText(idText), GUIVersion(ver) - { - //Init stuff - readyToOpen=true; //In KWIK this is in initFile, but the new recordEngine methods make it safe for it to be here - - scaledBuffer.malloc(MAX_BUFFER_SIZE); - intBuffer.malloc(MAX_BUFFER_SIZE); - bufferSize = MAX_BUFFER_SIZE; - } - - NWBFile::~NWBFile() - { - } - - - void NWBFile::setXmlText(const String& xmlText) - { - this->xmlText = &xmlText; - } - -//All int return values are 0 if succesful, other number otherwise -int NWBFile::createFileStructure() -{ - //This is called when the file is first created (not if it's opened but it already exists) - //Creates all the common structures, like file version and such - if (createGroup("/acquisition")) return -1; - if (createGroup("/analysis")) return -1; - if (createGroup("/epochs")) return -1; - if (createGroup("/general")) return -1; - if (createGroup("/processing")) return -1; - if (createGroup("/stimulus")) return -1; - - if (createGroup("/acquisition/timeseries")) return -1; - - - if (createGroup("/general/data_collection")) return -1; - - CHECK_ERROR(setAttributeStr(String("OpenEphys GUI v") + GUIVersion, "/general/data_collection", "software")); - CHECK_ERROR(setAttributeStr(*xmlText, "/general/data_collection", "configuration")); - - //TODO: Add default datasets - //Modify this one once we have JUCE4 to allow UTC time - String time = Time::getCurrentTime().formatted("%Y-%m-%dT%H:%M:%S"); - createTextDataSet("", "file_create_date", time); - createTextDataSet("", "identifier", identifierText); - createTextDataSet("", "nwb_version", "NWB-1.0.6"); - createTextDataSet("", "session_description", " "); - createTextDataSet("", "session_start_time", time); - - return 0; -} - -bool NWBFile::startNewRecording(int recordingNumber, const Array& continuousArray, - const Array& eventArray, const Array& electrodeArray) - { - //Created each time a new recording is started. Creates the specific file structures and attributes - //for that specific recording - String basePath; - StringArray ancestry; - String rootPath = "/acquisition/timeseries/recording" + String(recordingNumber + 1); - if (createGroup(rootPath)) return false; - if (createGroupIfDoesNotExist(rootPath + "/continuous")) return false; - if (createGroupIfDoesNotExist(rootPath + "/spikes")) return false; - if (createGroupIfDoesNotExist(rootPath + "/events")) return false; - - //just in case - continuousDataSets.clearQuick(true); - spikeDataSets.clearQuick(true); - eventDataSets.clearQuick(true); - - ScopedPointer tsStruct; - ScopedPointer dSet; - - int nCont; - nCont = continuousArray.size(); - for (int i = 0; i < nCont; i++) - { - //All channels in a group will share the same source information (any caller to this method MUST assure this happen - //so we just pick the first channel. - const DataChannel* info = continuousArray.getReference(i)[0]; - basePath = rootPath + "/continuous/processor" + String(info->getCurrentNodeID()) + "_" + String(info->getSourceNodeID()); - if (info->getSourceSubprocessorCount() > 1) basePath += "." + String(info->getSubProcessorIdx()); - String name = info->getCurrentNodeName() + " (" + String(info->getCurrentNodeID()) + ") From " + info->getSourceName() + " (" + String(info->getSourceNodeID()); - if (info->getSourceSubprocessorCount() > 1) name += "." + String(info->getSubProcessorIdx()); - name += ")"; - ancestry.clearQuick(); - ancestry.add("Timeseries"); - ancestry.add("ElectricalSeries"); - if (!createTimeSeriesBase(basePath, name, "Stores acquired voltage data from extracellular recordings", "", ancestry)) return false; - tsStruct = new TimeSeries(); - tsStruct->basePath = basePath; - dSet = createDataSet(BaseDataType::I16, 0, continuousArray.getReference(i).size(), CHUNK_XSIZE, basePath + "/data"); - if (dSet == nullptr) - { - std::cerr << "Error creating dataset for " << name << std::endl; - return false; - } - else - { - createDataAttributes(basePath, info->getBitVolts(), info->getBitVolts() / 65536, info->getDataUnits()); - } - tsStruct->baseDataSet = dSet; - - dSet = createTimestampDataSet(basePath, CHUNK_XSIZE); - if (dSet == nullptr) return false; - tsStruct->timestampDataSet = dSet; - - basePath = basePath + "/oe_extra_info"; - if (createGroup(basePath)) return false; - int nChans = continuousArray.getReference(i).size(); - for (int j = 0; j < nChans; j++) - { - String channelPath = basePath + "/channel" + String(j + 1); - const DataChannel* chan = continuousArray.getReference(i)[j]; - createExtraInfo(channelPath, chan->getName(), chan->getDescription(), chan->getIdentifier(), chan->getSourceIndex(), chan->getSourceTypeIndex()); - createChannelMetaDataSets(channelPath + "/channel_metadata", chan); - } - continuousDataSets.add(tsStruct.release()); - } - - nCont = electrodeArray.size(); - for (int i = 0; i < nCont; i++) - { - basePath = rootPath + "/spikes/electrode" + String(i + 1); - const SpikeChannel* info = electrodeArray[i]; - String sourceName = info->getSourceName() + "_" + String(info->getSourceNodeID()); - if (info->getSourceSubprocessorCount() > 1) sourceName = sourceName + "." + String(info->getSubProcessorIdx()); - ancestry.clearQuick(); - ancestry.add("Timeseries"); - ancestry.add("SpikeEventSeries"); - if (!createTimeSeriesBase(basePath, sourceName, "Snapshorts of spike events from data", info->getName(), ancestry)) return false; - - tsStruct = new TimeSeries(); - tsStruct->basePath = basePath; - - dSet = createDataSet(BaseDataType::I16, 0, info->getNumChannels(), info->getTotalSamples(), SPIKE_CHUNK_XSIZE, basePath + "/data"); - if (dSet == nullptr) - { - std::cerr << "Error creating dataset for electrode " << i << std::endl; - return false; - } - else - { - createDataAttributes(basePath, info->getChannelBitVolts(0), info->getChannelBitVolts(0) / 65536, "volt"); - } - tsStruct->baseDataSet = dSet; - dSet = createTimestampDataSet(basePath, SPIKE_CHUNK_XSIZE); - if (dSet == nullptr) return false; - tsStruct->timestampDataSet = dSet; - - basePath = basePath + "/oe_extra_info"; - createExtraInfo(basePath, info->getName(), info->getDescription(), info->getIdentifier(), info->getSourceIndex(), info->getSourceTypeIndex()); - createChannelMetaDataSets(basePath + "/channel_metadata", info); - createEventMetaDataSets(basePath + "/spike_metadata", tsStruct, info); - - spikeDataSets.add(tsStruct.release()); - - - } - - nCont = eventArray.size(); - int nTTL = 0; - int nTXT = 0; - int nBIN = 0; - for (int i = 0; i < nCont; i++) - { - basePath = rootPath + "/events"; - const EventChannel* info = eventArray[i]; - String sourceName = info->getSourceName() + "_" + String(info->getSourceNodeID()); - if (info->getSourceSubprocessorCount() > 1) sourceName = sourceName + "." + String(info->getSubProcessorIdx()); - ancestry.clearQuick(); - ancestry.add("Timeseries"); - - String helpText; - - switch (info->getChannelType()) - { - case EventChannel::TTL: - nTTL += 1; - basePath = basePath + "/ttl" + String(nTTL); - ancestry.add("IntervalSeries"); - ancestry.add("TTLSeries"); - helpText = "Stores the start and stop times for TTL events"; - break; - case EventChannel::TEXT: - nTXT += 1; - basePath = basePath + "/text" + String(nTXT); - ancestry.add("AnnotationSeries"); - helpText = "Time-stamped annotations about an experiment"; - break; - default: - nBIN += 1; - basePath = basePath + "/binary" + String(nBIN); - ancestry.add("BinarySeries"); - helpText = "Stores arbitrary binary data"; - break; - } - - if (!createTimeSeriesBase(basePath, sourceName, helpText, info->getDescription(), ancestry)) return false; - - tsStruct = new TimeSeries(); - tsStruct->basePath = basePath; - - if (info->getChannelType() >= EventChannel::BINARY_BASE_VALUE) //only binary events have length greater than 1 - { - dSet = createDataSet(getEventH5Type(info->getChannelType(), info->getLength()), 0, info->getLength(), EVENT_CHUNK_SIZE, basePath + "/data");; - } - else - { - dSet = createDataSet(getEventH5Type(info->getChannelType(), info->getLength()), 0, EVENT_CHUNK_SIZE, basePath + "/data"); - } - - if (dSet == nullptr) - { - std::cerr << "Error creating dataset for event " << info->getName() << std::endl; - return false; - } - else - { - createDataAttributes(basePath, NAN, NAN, "n/a"); - } - - - tsStruct->baseDataSet = dSet; - dSet = createTimestampDataSet(basePath, EVENT_CHUNK_SIZE); - if (dSet == nullptr) return false; - tsStruct->timestampDataSet = dSet; - - dSet = createDataSet(BaseDataType::U8, 0, EVENT_CHUNK_SIZE, basePath + "/control"); - if (dSet == nullptr) return false; - tsStruct->controlDataSet = dSet; - - if (info->getChannelType() == EventChannel::TTL) - { - dSet = createDataSet(BaseDataType::U8, 0, info->getDataSize(), EVENT_CHUNK_SIZE, basePath + "/full_word"); - if (dSet == nullptr) return false; - tsStruct->ttlWordDataSet = dSet; - } - - basePath = basePath + "/oe_extra_info"; - createExtraInfo(basePath, info->getName(), info->getDescription(), info->getIdentifier(), info->getSourceIndex(), info->getSourceTypeIndex()); - createChannelMetaDataSets(basePath + "/channel_metadata", info); - createEventMetaDataSets(basePath + "/event_metadata", tsStruct, info); - eventDataSets.add(tsStruct.release()); - - } - basePath = rootPath + "/events/sync_messages"; - ancestry.clearQuick(); - ancestry.add("Timeseries"); - ancestry.add("AnnotationSeries"); - String desc = "Stores recording start timestamps for each processor in text format"; - if (!createTimeSeriesBase(basePath, "Autogenerated messages", desc, desc, ancestry)) return false; - tsStruct = new TimeSeries(); - tsStruct->basePath = basePath; - dSet = createDataSet(BaseDataType::STR(100), 0, 1, basePath + "/data"); - if (dSet == nullptr) - { - std::cerr << "Error creating dataset for sync messages" << std::endl; - return false; - } - else - { - createDataAttributes(basePath, NAN, NAN, "n/a"); - } - tsStruct->baseDataSet = dSet; - dSet = createTimestampDataSet(basePath, 1); - if (dSet == nullptr) return false; - tsStruct->timestampDataSet = dSet; - - dSet = createDataSet(BaseDataType::U8, 0, 1, basePath + "/control"); - if (dSet == nullptr) return false; - tsStruct->controlDataSet = dSet; - syncMsgDataSet = tsStruct; - - return true; - } - - void NWBFile::stopRecording() - { - int nObjs = continuousDataSets.size(); - const TimeSeries* tsStruct; - for (int i = 0; i < nObjs; i++) - { - tsStruct = continuousDataSets[i]; - CHECK_ERROR(setAttribute(BaseDataType::U64, &(tsStruct->numSamples), tsStruct->basePath, "num_samples")); - } - nObjs = spikeDataSets.size(); - for (int i = 0; i < nObjs; i++) - { - tsStruct = spikeDataSets[i]; - CHECK_ERROR(setAttribute(BaseDataType::U64, &(tsStruct->numSamples), tsStruct->basePath, "num_samples")); - } - nObjs = eventDataSets.size(); - for (int i = 0; i < nObjs; i++) - { - tsStruct = eventDataSets[i]; - CHECK_ERROR(setAttribute(BaseDataType::U64, &(tsStruct->numSamples), tsStruct->basePath, "num_samples")); - } - - CHECK_ERROR(setAttribute(BaseDataType::U64, &(syncMsgDataSet->numSamples), syncMsgDataSet->basePath, "num_samples")); - - continuousDataSets.clear(); - spikeDataSets.clear(); - eventDataSets.clear(); - syncMsgDataSet = nullptr; - } - - void NWBFile::writeData(int datasetID, int channel, int nSamples, const float* data, float bitVolts) - { - if (!continuousDataSets[datasetID]) - return; - - if (nSamples > bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. - { - std::cerr << "Write buffer overrun, resizing to" << nSamples << std::endl; - bufferSize = nSamples; - scaledBuffer.malloc(nSamples); - intBuffer.malloc(nSamples); - } - - double multFactor = 1 / (float(0x7fff) * bitVolts); - FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), data, multFactor, nSamples); - AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), nSamples); - - CHECK_ERROR(continuousDataSets[datasetID]->baseDataSet->writeDataRow(channel, nSamples, BaseDataType::I16, intBuffer)); - - /* Since channels are filled asynchronouysly by the Record Thread, there is no guarantee - that at a any point in time all channels in a dataset have the same number of filled samples. - However, since each dataset is filled from a single source, all channels must have the - same number of samples at acquisition stop. To keep track of the written samples we must chose - an arbitrary channel, and at the end all channels will be the same. */ - - if (channel == 0) //there will always be a first channel or there wouldn't be dataset - continuousDataSets[datasetID]->numSamples += nSamples; - } - - void NWBFile::writeTimestamps(int datasetID, int nSamples, const double* data) - { - if (!continuousDataSets[datasetID]) - return; - - CHECK_ERROR(continuousDataSets[datasetID]->timestampDataSet->writeDataBlock(nSamples, BaseDataType::F64, data)); - } - - void NWBFile::writeSpike(int electrodeId, const SpikeChannel* channel, const SpikeEvent* event) - { - if (!spikeDataSets[electrodeId]) - return; - int nSamples = channel->getTotalSamples() * channel->getNumChannels(); - - if (nSamples > bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. - { - std::cerr << "Write buffer overrun, resizing to" << nSamples << std::endl; - bufferSize = nSamples; - scaledBuffer.malloc(nSamples); - intBuffer.malloc(nSamples); - } - - double multFactor = 1 / (float(0x7fff) * channel->getChannelBitVolts(0)); - FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), event->getDataPointer(), multFactor, nSamples); - AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), nSamples); - - double timestampSec = event->getTimestamp() / channel->getSampleRate(); - - CHECK_ERROR(spikeDataSets[electrodeId]->baseDataSet->writeDataBlock(1, BaseDataType::I16, intBuffer)); - CHECK_ERROR(spikeDataSets[electrodeId]->timestampDataSet->writeDataBlock(1, BaseDataType::F64, ×tampSec)); - writeEventMetaData(spikeDataSets[electrodeId], channel, event); - - spikeDataSets[electrodeId]->numSamples += 1; - - } - - void NWBFile::writeEvent(int eventID, const EventChannel* channel, const Event* event) - { - if (!eventDataSets[eventID]) - return; - - const void* dataSrc; - BaseDataType type; - int8 ttlVal; - String text; - - switch (event->getEventType()) - { - case EventChannel::TTL: - ttlVal = (static_cast(event)->getState() ? 1 : -1) * (event->getChannel() + 1); - dataSrc = &ttlVal; - type = BaseDataType::I8; - break; - case EventChannel::TEXT: - text = static_cast(event)->getText(); - dataSrc = text.toUTF8().getAddress(); - type = BaseDataType::STR(text.length()); - break; - default: - dataSrc = static_cast(event)->getBinaryDataPointer(); - type = getEventH5Type(event->getEventType()); - break; - } - CHECK_ERROR(eventDataSets[eventID]->baseDataSet->writeDataBlock(1, type, dataSrc)); - - double timeSec = event->getTimestamp() / channel->getSampleRate(); - - CHECK_ERROR(eventDataSets[eventID]->timestampDataSet->writeDataBlock(1, BaseDataType::F64, &timeSec)); - - uint8 controlValue = event->getChannel() + 1; - - CHECK_ERROR(eventDataSets[eventID]->controlDataSet->writeDataBlock(1, BaseDataType::U8, &controlValue)); - - if (event->getEventType() == EventChannel::TTL) - { - CHECK_ERROR(eventDataSets[eventID]->ttlWordDataSet->writeDataBlock(1, BaseDataType::U8, static_cast(event)->getTTLWordPointer())); - } - - eventDataSets[eventID]->numSamples += 1; - } - - void NWBFile::writeTimestampSyncText(uint16 sourceID, int64 timestamp, float sourceSampleRate, String text) - { - CHECK_ERROR(syncMsgDataSet->baseDataSet->writeDataBlock(1, BaseDataType::STR(text.length()), text.toUTF8())); - double timeSec = timestamp / sourceSampleRate; - CHECK_ERROR(syncMsgDataSet->timestampDataSet->writeDataBlock(1, BaseDataType::F64, &timeSec)); - CHECK_ERROR(syncMsgDataSet->controlDataSet->writeDataBlock(1, BaseDataType::U8, &sourceID)); - syncMsgDataSet->numSamples += 1; - } - - - String NWBFile::getFileName() - { - return filename; - } - - bool NWBFile::createTimeSeriesBase(String basePath, String source, String helpText, String description, StringArray ancestry) - { - if (createGroup(basePath)) return false; - CHECK_ERROR(setAttributeStrArray(ancestry, basePath, "ancestry")); - CHECK_ERROR(setAttributeStr(" ", basePath, "comments")); - CHECK_ERROR(setAttributeStr(description, basePath, "description")); - CHECK_ERROR(setAttributeStr("TimeSeries", basePath, "neurodata_type")); - CHECK_ERROR(setAttributeStr(source, basePath, "source")); - CHECK_ERROR(setAttributeStr(helpText, basePath, "help")); - return true; - } - - void NWBFile::createDataAttributes(String basePath, float conversion, float resolution, String unit) - { - CHECK_ERROR(setAttribute(BaseDataType::F32, &conversion, basePath + "/data", "conversion")); - CHECK_ERROR(setAttribute(BaseDataType::F32, &resolution, basePath + "/data", "resolution")); - CHECK_ERROR(setAttributeStr(unit, basePath + "/data", "unit")); - } - - HDF5RecordingData* NWBFile::createTimestampDataSet(String basePath, int chunk_size) - { - HDF5RecordingData* tsSet = createDataSet(BaseDataType::F64, 0, chunk_size, basePath + "/timestamps"); - if (!tsSet) - std::cerr << "Error creating timestamp dataset in " << basePath << std::endl; - else - { - const int32 one = 1; - CHECK_ERROR(setAttribute(BaseDataType::I32, &one, basePath + "/timestamps", "interval")); - CHECK_ERROR(setAttributeStr("seconds", basePath + "/timestamps", "unit")); - } - return tsSet; - } - - bool NWBFile::createExtraInfo(String basePath, String name, String desc, String id, uint16 index, uint16 typeIndex) - { - if (createGroup(basePath)) return false; - CHECK_ERROR(setAttributeStr("openephys:/", basePath, "schema_id")); - CHECK_ERROR(setAttributeStr(name, basePath, "name")); - CHECK_ERROR(setAttributeStr(desc, basePath, "description")); - CHECK_ERROR(setAttributeStr(id, basePath, "identifier")); - CHECK_ERROR(setAttribute(BaseDataType::U16, &index, basePath, "source_index")); - CHECK_ERROR(setAttribute(BaseDataType::U16, &typeIndex, basePath, "source_type_index")); - return true; - } - - bool NWBFile::createChannelMetaDataSets(String basePath, const MetaDataInfoObject* info) - { - if (!info) return false; - if (createGroup(basePath)) return false; - CHECK_ERROR(setAttributeStr("openephys:/", basePath, "schema_id")); - int nMetaData = info->getMetaDataCount(); - - for (int i = 0; i < nMetaData; i++) - { - const MetaDataDescriptor* desc = info->getMetaDataDescriptor(i); - String fieldName = "Field_" + String(i+1); - String name = desc->getName(); - String description = desc->getDescription(); - String identifier = desc->getIdentifier(); - BaseDataType type = getMetaDataH5Type(desc->getType(), desc->getLength()); //only string types use length, for others is always set to 1. If array types are implemented, change this - int length = desc->getType() == MetaDataDescriptor::CHAR ? 1 : desc->getLength(); //strings are a single element of length set in the type (see above) while other elements are saved a - HeapBlock data(desc->getDataSize()); - info->getMetaDataValue(i)->getValue(static_cast(data.getData())); - createBinaryDataSet(basePath, fieldName, type, length, data.getData()); - String fullPath = basePath + "/" + fieldName; - CHECK_ERROR(setAttributeStr("openephys:/", fullPath, "schema_id")); - CHECK_ERROR(setAttributeStr(name, fullPath, "name")); - CHECK_ERROR(setAttributeStr(description, fullPath, "description")); - CHECK_ERROR(setAttributeStr(identifier, fullPath, "identifier")); - } - return true; - } - - - bool NWBFile::createEventMetaDataSets(String basePath, TimeSeries* timeSeries, const MetaDataEventObject* info) - { - if (!info) return false; - if (createGroup(basePath)) return false; - CHECK_ERROR(setAttributeStr("openephys:/", basePath, "schema_id")); - int nMetaData = info->getEventMetaDataCount(); - - timeSeries->metaDataSet.clear(); //just in case - for (int i = 0; i < nMetaData; i++) - { - const MetaDataDescriptor* desc = info->getEventMetaDataDescriptor(i); - String fieldName = "Field_" + String(i+1); - String name = desc->getName(); - String description = desc->getDescription(); - String identifier = desc->getIdentifier(); - BaseDataType type = getMetaDataH5Type(desc->getType(), desc->getLength()); //only string types use length, for others is always set to 1. If array types are implemented, change this - int length = desc->getType() == MetaDataDescriptor::CHAR ? 1 : desc->getLength(); //strings are a single element of length set in the type (see above) while other elements are saved as arrays - String fullPath = basePath + "/" + fieldName; - HDF5RecordingData* dSet = createDataSet(type, 0, length, EVENT_CHUNK_SIZE, fullPath); - if (!dSet) return false; - timeSeries->metaDataSet.add(dSet); - - CHECK_ERROR(setAttributeStr("openephys:/", fullPath, "schema_id")); - CHECK_ERROR(setAttributeStr(name, fullPath, "name")); - CHECK_ERROR(setAttributeStr(description, fullPath, "description")); - CHECK_ERROR(setAttributeStr(identifier, fullPath, "identifier")); - } - return true; - } - - void NWBFile::writeEventMetaData(TimeSeries* timeSeries, const MetaDataEventObject* info, const MetaDataEvent* event) - { - jassert(timeSeries->metaDataSet.size() == event->getMetadataValueCount()); - jassert(info->getEventMetaDataCount() == event->getMetadataValueCount()); - int nMetaData = event->getMetadataValueCount(); - for (int i = 0; i < nMetaData; i++) - { - BaseDataType type = getMetaDataH5Type(info->getEventMetaDataDescriptor(i)->getType(), info->getEventMetaDataDescriptor(i)->getLength()); - timeSeries->metaDataSet[i]->writeDataBlock(1, type, event->getMetaDataValue(i)->getRawValuePointer()); - } - - } - - void NWBFile::createTextDataSet(String path, String name, String text) - { - ScopedPointer dSet; - - if (text.isEmpty()) text = " "; //to avoid 0-length strings, which cause errors - BaseDataType type = BaseDataType::STR(text.length()); - - dSet = createDataSet(type, 1, 0, path + "/" + name); - if (!dSet) return; - dSet->writeDataBlock(1, type, text.toUTF8()); - } - - void NWBFile::createBinaryDataSet(String path, String name, BaseDataType type, int length, void* data) - { - ScopedPointer dSet; - if ((length < 1) || !data) return; - - dSet = createDataSet(type, 1, length, 1, path + "/" + name); - if (!dSet) return; - dSet->writeDataBlock(1, type, data); - } - - - //These two methods whould be easy to adapt to support array types for all base types, for now - //length is only used for string types. - NWBFile::BaseDataType NWBFile::getEventH5Type(EventChannel::EventChannelTypes type, int length) - { - switch (type) - { - case EventChannel::INT8_ARRAY: - return BaseDataType::I8; - case EventChannel::UINT8_ARRAY: - return BaseDataType::U8; - case EventChannel::INT16_ARRAY: - return BaseDataType::I16; - case EventChannel::UINT16_ARRAY: - return BaseDataType::U16; - case EventChannel::INT32_ARRAY: - return BaseDataType::I32; - case EventChannel::UINT32_ARRAY: - return BaseDataType::U32; - case EventChannel::INT64_ARRAY: - return BaseDataType::I64; - case EventChannel::UINT64_ARRAY: - return BaseDataType::U64; - case EventChannel::FLOAT_ARRAY: - return BaseDataType::F32; - case EventChannel::DOUBLE_ARRAY: - return BaseDataType::F64; - case EventChannel::TEXT: - return BaseDataType::STR(length); - default: - return BaseDataType::I8; - } - } - NWBFile::BaseDataType NWBFile::getMetaDataH5Type(MetaDataDescriptor::MetaDataTypes type, int length) - { - switch (type) - { - case MetaDataDescriptor::INT8: - return BaseDataType::I8; - case MetaDataDescriptor::UINT8: - return BaseDataType::U8; - case MetaDataDescriptor::INT16: - return BaseDataType::I16; - case MetaDataDescriptor::UINT16: - return BaseDataType::U16; - case MetaDataDescriptor::INT32: - return BaseDataType::I32; - case MetaDataDescriptor::UINT32: - return BaseDataType::U32; - case MetaDataDescriptor::INT64: - return BaseDataType::I64; - case MetaDataDescriptor::UINT64: - return BaseDataType::U64; - case MetaDataDescriptor::FLOAT: - return BaseDataType::F32; - case MetaDataDescriptor::DOUBLE: - return BaseDataType::F64; - case MetaDataDescriptor::CHAR: - return BaseDataType::STR(length); - default: - return BaseDataType::I8; - } - } \ No newline at end of file diff --git a/Source/Plugins/NWBFormat/NWBFormat.h b/Source/Plugins/NWBFormat/NWBFormat.h deleted file mode 100644 index bf5fb79b26..0000000000 --- a/Source/Plugins/NWBFormat/NWBFormat.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - ------------------------------------------------------------------ - - 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 . - - */ - -#ifndef NWBFORMAT_H -#define NWBFORMAT_H - -#include -#include -using namespace OpenEphysHDF5; - -namespace NWBRecording -{ - typedef Array ContinuousGroup; - class TimeSeries - { - public: - ScopedPointer baseDataSet; - ScopedPointer timestampDataSet; - ScopedPointer controlDataSet; //for all but spikes - ScopedPointer ttlWordDataSet; //just for ttl events - OwnedArray metaDataSet; - String basePath; - uint64 numSamples{ 0 }; - }; - - class NWBFile : public HDF5FileBase - { - public: - NWBFile(String fName, String ver, String idText); //with whatever arguments it's necessary - ~NWBFile(); - bool startNewRecording(int recordingNumber, const Array& continuousArray, - const Array& eventArray, const Array& electrodeArray); - void stopRecording(); - void writeData(int datasetID, int channel, int nSamples, const float* data, float bitVolts); - void writeTimestamps(int datasetID, int nSamples, const double* data); - void writeSpike(int electrodeId, const SpikeChannel* channel, const SpikeEvent* event); - void writeEvent(int eventID, const EventChannel* channel, const Event* event); - void writeTimestampSyncText(uint16 sourceID, int64 timestamp, float sourceSampleRate, String text); - String getFileName() override; - void setXmlText(const String& xmlText); - - protected: - int createFileStructure() override; - - private: - - void createTextDataSet(String path, String name, String text); - void createBinaryDataSet(String path, String name, HDF5FileBase::BaseDataType type, int length, void* data); - static HDF5FileBase::BaseDataType getEventH5Type(EventChannel::EventChannelTypes type, int length = 1); - static HDF5FileBase::BaseDataType getMetaDataH5Type(MetaDataDescriptor::MetaDataTypes type, int length = 1); - - bool createTimeSeriesBase(String basePath, String source, String helpText, String description, StringArray ancestry); - bool createExtraInfo(String basePath, String name, String desc, String id, uint16 index, uint16 typeIndex); - HDF5RecordingData* createTimestampDataSet(String basePath, int chunk_size); - void createDataAttributes(String basePath, float conversion, float resolution, String unit); - bool createChannelMetaDataSets(String basePath, const MetaDataInfoObject* info); - bool createEventMetaDataSets(String basePath, TimeSeries* timeSeries, const MetaDataEventObject* info); - - void writeEventMetaData(TimeSeries* timeSeries, const MetaDataEventObject* info, const MetaDataEvent* event); - - - const String filename; - const String GUIVersion; - - OwnedArray continuousDataSets; - OwnedArray spikeDataSets; - OwnedArray eventDataSets; - ScopedPointer syncMsgDataSet; - - const String* xmlText; - const String identifierText; - - HeapBlock scaledBuffer; - HeapBlock intBuffer; - size_t bufferSize; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NWBFile); - - }; - -} - -#endif diff --git a/Source/Plugins/NWBFormat/NWBRecording.cpp b/Source/Plugins/NWBFormat/NWBRecording.cpp deleted file mode 100644 index e729934bbf..0000000000 --- a/Source/Plugins/NWBFormat/NWBRecording.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/* - ------------------------------------------------------------------ - - 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 . - - */ - - #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)); - 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); -} \ No newline at end of file diff --git a/Source/Plugins/NWBFormat/NWBRecording.h b/Source/Plugins/NWBFormat/NWBRecording.h deleted file mode 100644 index e74219d6c4..0000000000 --- a/Source/Plugins/NWBFormat/NWBRecording.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - ------------------------------------------------------------------ - - 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 . - - */ - - #ifndef NWBRECORDING_H - #define NWBRECORDING_H - - #include - #include "NWBFormat.h" - - namespace NWBRecording { - class NWBRecordEngine : public RecordEngine - { - public: - NWBRecordEngine(); - ~NWBRecordEngine(); - //Those are only the basic set of calls. Look at RecordEngine.cpp and RecordEngine.h for description on all possible hooks and the order they're called. - String getEngineID() const override; - void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; - void closeFiles() override; - void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; - void writeEvent(int eventIndex, const MidiMessage& event) override; - void addSpikeElectrode(int index,const SpikeChannel* elec) override; - void writeSpike(int electrodeIndex, const SpikeEvent* spike) override; - void writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float sourceSampleRate, String text) override; - void resetChannels() override; - void setParameter(EngineParameter& parameter) override; - - static RecordEngineManager* getEngineManager(); - - private: - ScopedPointer recordFile; - Array datasetIndexes; - Array writeChannelIndexes; - - Array continuousChannels; - Array eventChannels; - Array spikeChannels; - - HeapBlock tsBuffer; - size_t bufferSize; - - String identifierText; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NWBRecordEngine); - - - }; - } - - #endif \ No newline at end of file diff --git a/Source/Plugins/NWBFormat/OpenEphysLib.cpp b/Source/Plugins/NWBFormat/OpenEphysLib.cpp deleted file mode 100644 index f079ebf3f9..0000000000 --- a/Source/Plugins/NWBFormat/OpenEphysLib.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* ------------------------------------------------------------------- - -This file is part of the Open Ephys GUI -Copyright (C) 2013 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 . - -*/ - -#include -#include "NWBRecording.h" -#include -#ifdef WIN32 -#include -#define EXPORT __declspec(dllexport) -#else -#define EXPORT __attribute__((visibility("default"))) -#endif - - -using namespace Plugin; -#define NUM_PLUGINS 1 - -extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) -{ - info->apiVersion = PLUGIN_API_VER; - info->name = "NWB format"; - info->libVersion = 1; - info->numPlugins = NUM_PLUGINS; -} - -extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) -{ - switch (index) - { - case 0: - info->type = Plugin::PLUGIN_TYPE_RECORD_ENGINE; - info->recordEngine.name = "NWB"; - info->recordEngine.creator = &(Plugin::createRecordEngine); - break; - default: - return -1; - } - - return 0; -} - -#ifdef WIN32 -BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, - IN DWORD nReason, - IN LPVOID Reserved) -{ - return TRUE; -} - -#endif From 5ed79a266f8dde0089b34729fabe9e1e48538713 Mon Sep 17 00:00:00 2001 From: Martin Spacek Date: Fri, 18 May 2018 14:35:22 +0200 Subject: [PATCH 03/97] Replace prepended and appended names with base file name * Also write a new settings .xml file for each recording --- .../JuceLibraryCode/BinaryData.cpp | 4 +- Source/CoreServices.cpp | 20 +-- Source/CoreServices.h | 4 +- .../BusseLabBinaryWriter/BinaryRecording.cpp | 11 +- .../BusseLabBinaryWriter/BinaryRecording.h | 4 +- .../Plugins/NetworkEvents/NetworkEvents.cpp | 8 +- .../openEphys_RecordEnginePluginTemplate.cpp | 2 +- .../openEphys_RecordEnginePluginTemplate.h | 2 +- .../RecordNode/OriginalRecording.cpp | 2 +- .../Processors/RecordNode/OriginalRecording.h | 2 +- Source/Processors/RecordNode/RecordEngine.h | 2 +- Source/Processors/RecordNode/RecordNode.cpp | 73 +++++++---- Source/Processors/RecordNode/RecordNode.h | 18 ++- Source/Processors/RecordNode/RecordThread.cpp | 6 +- Source/Processors/RecordNode/RecordThread.h | 4 +- Source/UI/ControlPanel.cpp | 119 +++--------------- Source/UI/ControlPanel.h | 22 +--- 17 files changed, 114 insertions(+), 189 deletions(-) diff --git a/PluginGenerator/JuceLibraryCode/BinaryData.cpp b/PluginGenerator/JuceLibraryCode/BinaryData.cpp index ea15500858..ee2ac20b88 100644 --- a/PluginGenerator/JuceLibraryCode/BinaryData.cpp +++ b/PluginGenerator/JuceLibraryCode/BinaryData.cpp @@ -1902,7 +1902,7 @@ static const unsigned char temp_binary_data_18[] = "}\r\n" "\r\n" "\r\n" -"void PROCESSORCLASSNAME::openFiles (File rootFolder, int experimentNumber, int recordingNumber)\r\n" +"void PROCESSORCLASSNAME::openFiles (File rootFolder, String baseName, int recordingNumber)\r\n" "{\r\n" "}\r\n" "\r\n" @@ -2003,7 +2003,7 @@ static const unsigned char temp_binary_data_19[] = "\r\n" " String getEngineID() const override;\r\n" "\r\n" -" void openFiles (File rootFolder, int experimentNumber, int recordingNumber) override;\r\n" +" void openFiles (File rootFolder, String baseName, int recordingNumber) override;\r\n" " void closeFiles() override;\r\n" "\r\n" " void writeData (int writeChannel, int realChannel, const float* buffer, int size) override;\r\n" diff --git a/Source/CoreServices.cpp b/Source/CoreServices.cpp index edecf4e1fa..8b5a253c4d 100644 --- a/Source/CoreServices.cpp +++ b/Source/CoreServices.cpp @@ -108,16 +108,6 @@ void createNewRecordingDir() getControlPanel()->labelTextChanged(NULL); } -void setPrependTextToRecordingDir(String text) -{ - getControlPanel()->setPrependText(text); -} - -void setAppendTextToRecordingDir(String text) -{ - getControlPanel()->setAppendText(text); -} - String getSelectedRecordEngineId() { return getControlPanel()->getSelectedRecordEngineId(); @@ -140,14 +130,14 @@ File getRecordingPath() return getProcessorGraph()->getRecordNode()->getDataDirectory(); } -int getRecordingNumber() +String getBaseName() { - return getProcessorGraph()->getRecordNode()->getRecordingNumber(); + return getProcessorGraph()->getRecordNode()->getBaseName(); } -int getExperimentNumber() +int getRecordingNumber() { - return getProcessorGraph()->getRecordNode()->getExperimentNumber(); + return getProcessorGraph()->getRecordNode()->getRecordingNumber(); } void writeSpike(const SpikeEvent* spike, const SpikeChannel* chan) @@ -191,4 +181,4 @@ String getGUIVersion() return STR_DEF(JUCE_APP_VERSION); } -}; \ No newline at end of file +}; diff --git a/Source/CoreServices.h b/Source/CoreServices.h index 24f593e1d2..11bb7db5fa 100644 --- a/Source/CoreServices.h +++ b/Source/CoreServices.h @@ -99,10 +99,10 @@ namespace RecordNode /** Forces creation of new directory on recording */ PLUGIN_API void createNewrecordingDir(); -/** Gets the current recording directories and status information */ +/** Gets the current recording path, base name, and number */ PLUGIN_API File getRecordingPath(); +PLUGIN_API String getBaseName(); PLUGIN_API int getRecordingNumber(); -PLUGIN_API int getExperimentNumber(); /* Spike related methods. See record engine documentation */ diff --git a/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp index 502cd770a7..ff62169714 100644 --- a/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp +++ b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.cpp @@ -59,11 +59,9 @@ String BinaryRecording::getProcessorString(const InfoObjectCommon* channelInfo) return fName; } -void BinaryRecording::openFiles(File rootFolder, int experimentNumber, int recordingNumber) +void BinaryRecording::openFiles(File rootFolder, String baseName, int recordingNumber) { - String basepath = rootFolder.getFullPathName() + rootFolder.separatorString + "experiment" + String(experimentNumber) - + File::separatorString + "recording" + String(recordingNumber + 1) + File::separatorString; - String contPath = basepath + "continuous" + File::separatorString; + String basepath = rootFolder.getFullPathName() + rootFolder.separatorString + baseName; //Open channel files int nProcessors = getNumRecordedProcessors(); @@ -115,8 +113,7 @@ void BinaryRecording::openFiles(File rootFolder, int experimentNumber, int recor } if (!found) { - String datPath = getProcessorString(channelInfo); - continuousFileNames.add(contPath + datPath + "continuous.dat"); + continuousFileNames.add(basepath + ".dat"); ScopedPointer tFile = new NpyFile(contPath + datPath + "timestamps.npy", NpyType(BaseType::INT64,1)); m_dataTimestampFiles.add(tFile.release()); @@ -130,7 +127,7 @@ void BinaryRecording::openFiles(File rootFolder, int experimentNumber, int recor jsonChanArray.add(var(jsonChan)); jsonChannels.add(var(jsonChanArray)); DynamicObject::Ptr jsonFile = new DynamicObject(); - jsonFile->setProperty("folder_name", datPath.replace(File::separatorString, "/")); //to make it more system agnostic, replace separator with only one slash + jsonFile->setProperty("folder_name", basepath.replace(File::separatorString, "/")); //to make it more system agnostic, replace separator with only one slash jsonFile->setProperty("sample_rate", channelInfo->getSampleRate()); jsonFile->setProperty("source_processor_name", channelInfo->getSourceName()); jsonFile->setProperty("source_processor_id", channelInfo->getSourceNodeID()); diff --git a/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h index 98d76badc2..e8e37a98ad 100644 --- a/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h +++ b/Source/Plugins/BusseLabBinaryWriter/BinaryRecording.h @@ -37,7 +37,7 @@ namespace BinaryRecordingEngine ~BinaryRecording(); String getEngineID() const override; - void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; + void openFiles(File rootFolder, String baseName, int recordingNumber) override; void closeFiles() override; void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; void writeEvent(int eventIndex, const MidiMessage& event) override; @@ -98,4 +98,4 @@ namespace BinaryRecordingEngine } -#endif \ No newline at end of file +#endif diff --git a/Source/Plugins/NetworkEvents/NetworkEvents.cpp b/Source/Plugins/NetworkEvents/NetworkEvents.cpp index d61ddc987b..0c61fddb1b 100644 --- a/Source/Plugins/NetworkEvents/NetworkEvents.cpp +++ b/Source/Plugins/NetworkEvents/NetworkEvents.cpp @@ -514,16 +514,16 @@ String NetworkEvents::handleSpecialMessages (StringTS msg) String msg (file.getFullPathName()); return msg; } - else if (cmd.compareIgnoreCase ("GetRecordingNumber") == 0) + else if (cmd.compareIgnoreCase ("GetBaseName") == 0) { String status; - status += (CoreServices::RecordNode::getRecordingNumber() + 1); + status += CoreServices::RecordNode::getBaseName(); return status; } - else if (cmd.compareIgnoreCase ("GetExperimentNumber") == 0) + else if (cmd.compareIgnoreCase ("GetRecordingNumber") == 0) { String status; - status += CoreServices::RecordNode::getExperimentNumber(); + status += (CoreServices::RecordNode::getRecordingNumber() + 1); return status; } diff --git a/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.cpp b/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.cpp index 7bb3950b7f..87afd73470 100644 --- a/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.cpp +++ b/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.cpp @@ -39,7 +39,7 @@ String PROCESSORCLASSNAME::getEngineID() const } -void PROCESSORCLASSNAME::openFiles (File rootFolder, int experimentNumber, int recordingNumber) +void PROCESSORCLASSNAME::openFiles (File rootFolder, String baseName, int recordingNumber) { } diff --git a/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.h b/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.h index 71912ca978..95a577721b 100644 --- a/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.h +++ b/Source/Processors/PluginManager/Templates/openEphys_RecordEnginePluginTemplate.h @@ -34,7 +34,7 @@ class PROCESSORCLASSNAME : public RecordEngine String getEngineID() const override; - void openFiles (File rootFolder, int experimentNumber, int recordingNumber) override; + void openFiles (File rootFolder, String baseName, int recordingNumber) override; void closeFiles() override; void writeData (int writeChannel, int realChannel, const float* buffer, int size) override; diff --git a/Source/Processors/RecordNode/OriginalRecording.cpp b/Source/Processors/RecordNode/OriginalRecording.cpp index f3163358b0..4afa17e1ad 100644 --- a/Source/Processors/RecordNode/OriginalRecording.cpp +++ b/Source/Processors/RecordNode/OriginalRecording.cpp @@ -83,7 +83,7 @@ void OriginalRecording::resetChannels() procIndex = 0; } -void OriginalRecording::openFiles(File rootFolder, int experimentNumber, int recordingNumber) +void OriginalRecording::openFiles(File rootFolder, String baseName, int recordingNumber) { this->recordingNumber = recordingNumber; this->experimentNumber = experimentNumber; diff --git a/Source/Processors/RecordNode/OriginalRecording.h b/Source/Processors/RecordNode/OriginalRecording.h index 849e479f1d..d64b0cc1d2 100644 --- a/Source/Processors/RecordNode/OriginalRecording.h +++ b/Source/Processors/RecordNode/OriginalRecording.h @@ -46,7 +46,7 @@ class OriginalRecording : public RecordEngine void setParameter(EngineParameter& parameter) override; String getEngineID() const override; - void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; + void openFiles(File rootFolder, String baseName, int recordingNumber) override; void closeFiles() override; void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; void writeEvent(int eventIndex, const MidiMessage& event) override; diff --git a/Source/Processors/RecordNode/RecordEngine.h b/Source/Processors/RecordNode/RecordEngine.h index d6234c07dc..6903ddb5f3 100644 --- a/Source/Processors/RecordNode/RecordEngine.h +++ b/Source/Processors/RecordNode/RecordEngine.h @@ -88,7 +88,7 @@ class PLUGIN_API RecordEngine virtual void setParameter (EngineParameter& parameter); /** Called when recording starts to open all needed files */ - virtual void openFiles (File rootFolder, int experimentNumber, int recordingNumber) = 0; + virtual void openFiles (File rootFolder, String baseName, int recordingNumber) = 0; /** Called when recording stops to close all files and do all the necessary cleanups */ diff --git a/Source/Processors/RecordNode/RecordNode.cpp b/Source/Processors/RecordNode/RecordNode.cpp index 74c5d23d78..0f615642f3 100755 --- a/Source/Processors/RecordNode/RecordNode.cpp +++ b/Source/Processors/RecordNode/RecordNode.cpp @@ -49,9 +49,7 @@ RecordNode::RecordNode() spikeElectrodeIndex = 0; - experimentNumber = 0; hasRecorded = false; - settingsNeeded = false; // 128 inputs, 0 outputs setPlayConfigDetails(getNumInputs(),getNumOutputs(),44100.0,128); @@ -173,13 +171,23 @@ void RecordNode::addInputChannel(const GenericProcessor* sourceNode, int chan) void RecordNode::createNewDirectory() { - std::cout << "Creating new directory." << std::endl; - - rootFolder = File(dataDirectory.getFullPathName() + File::separator + generateDirectoryName()); + baseName = AccessClass::getControlPanel()->getBaseName(); + rootFolder = File(dataDirectory.getFullPathName() + File::separator + baseName); + ensureRootFolderExists(); newDirectoryNeeded = false; +} +void RecordNode::ensureRootFolderExists() +{ + if (!rootFolder.exists()) + { + std::cout << "CREATING NEW FOLDER: " << rootFolder.getFullPathName() << std::endl; + rootFolder.createDirectory(); + } } + +/* String RecordNode::generateDirectoryName() { Time calendar = Time::getCurrentTime(); @@ -217,6 +225,7 @@ String RecordNode::generateDirectoryName() return filename; } +*/ String RecordNode::generateDateString() const { @@ -252,14 +261,20 @@ String RecordNode::generateDateString() const } -int RecordNode::getExperimentNumber() const +int RecordNode::getRecordingNumber() const { - return experimentNumber; + return recordingNumber; } -int RecordNode::getRecordingNumber() const +String RecordNode::getBaseNameGlob() { - return recordingNumber; + String baseNameGlob = baseName; + if (recordingNumber > 0) + { + baseNameGlob += "_" + String(recordingNumber); + } + baseNameGlob += ".*"; + return baseNameGlob; } void RecordNode::setParameter(int parameterIndex, float newValue) @@ -273,15 +288,11 @@ void RecordNode::setParameter(int parameterIndex, float newValue) if (parameterIndex == 1) { - - // std::cout << "START RECORDING." << std::endl; - if (newDirectoryNeeded) { createNewDirectory(); + baseName = AccessClass::getControlPanel()->getBaseName(); recordingNumber = 0; - experimentNumber = 1; - settingsNeeded = true; EVERY_ENGINE->directoryChanged(); } else @@ -289,18 +300,30 @@ void RecordNode::setParameter(int parameterIndex, float newValue) recordingNumber++; // increment recording number within this directory } - if (!rootFolder.exists()) + // it's possible the user deleted the folder after the first recording, so it needs + // to be recreated before starting a subsequent recording + ensureRootFolderExists(); + + // if files with baseName + recordingNumber already exist, inc recordingNumber + while (rootFolder.getNumberOfChildFiles(File::findFiles, getBaseNameGlob()) > 0) { - rootFolder.createDirectory(); + std::cout << "FOUND EXISTING RECORDING " << String(recordingNumber) << std::endl; + recordingNumber++; } - if (settingsNeeded) + + std::cout << "STARTING RECORDING " << String(recordingNumber) << std::endl; + + // write a new settings file for every recording + String settingsFileName = rootFolder.getFullPathName() + File::separator + baseName; + if (recordingNumber > 0) { - String settingsFileName = rootFolder.getFullPathName() + File::separator + "settings" + ((experimentNumber > 1) ? "_" + String(experimentNumber) : String::empty) + ".xml"; - AccessClass::getEditorViewport()->saveState(File(settingsFileName), m_lastSettingsText); - settingsNeeded = false; + settingsFileName += "_" + String(recordingNumber); } + settingsFileName += ".xml"; + std::cout << "WRITING FILE: " << settingsFileName << std::endl; + AccessClass::getEditorViewport()->saveState(File(settingsFileName), m_lastSettingsText); - m_recordThread->setFileComponents(rootFolder, experimentNumber, recordingNumber); + m_recordThread->setFileComponents(rootFolder, baseName, recordingNumber); channelMap.clear(); int totChans = dataChannelArray.size(); @@ -353,9 +376,7 @@ void RecordNode::setParameter(int parameterIndex, float newValue) } else if (parameterIndex == 0) { - - - std::cout << "STOP RECORDING." << std::endl; + std::cout << "STOP RECORDING." << std::endl << std::endl; if (isRecording) { @@ -418,10 +439,10 @@ bool RecordNode::enable() { hasRecorded = false; experimentNumber++; - settingsNeeded = true; } - //When starting a recording, if a new directory is needed it gets rewritten. Else is incremented by one. + // When starting a recording, if a new directory is needed, recordingNumber is rewritten, + // otherwise it's incremented by one recordingNumber = -1; EVERY_ENGINE->configureEngine(); EVERY_ENGINE->startAcquisition(); diff --git a/Source/Processors/RecordNode/RecordNode.h b/Source/Processors/RecordNode/RecordNode.h index 1eff5a4565..f3f1dd5879 100755 --- a/Source/Processors/RecordNode/RecordNode.h +++ b/Source/Processors/RecordNode/RecordNode.h @@ -76,11 +76,12 @@ class RecordNode : public GenericProcessor, */ void setParameter(int parameterIndex, float newValue) override; - /** returns current experiment number */ - int getExperimentNumber() const; /** returns current recording number */ int getRecordingNumber() const; + /** returns current base name file glob pattern, based on current recording number */ + String getBaseNameGlob(); + /** Called by the processor graph for each processor that could record data */ void registerProcessor(const GenericProcessor* sourceNode); @@ -124,9 +125,17 @@ class RecordNode : public GenericProcessor, */ void createNewDirectory(); + /** Make sure rootFolder exists on disk, if not, create it + */ + void ensureRootFolderExists(); File getDataDirectory() const; + String getBaseName() const + { + return baseName; + } + /** Adds a Record Engine to use */ void registerRecordEngine(RecordEngine* engine); @@ -179,6 +188,9 @@ class RecordNode : public GenericProcessor, */ File rootFolder; + /** Base folder and file name. + */ + String baseName; /** Integer timestamp saved for each buffer. */ @@ -195,9 +207,7 @@ class RecordNode : public GenericProcessor, int spikeElectrodeIndex; - int experimentNumber; bool hasRecorded; - bool settingsNeeded; std::atomic setFirstBlock; /** Generates a default directory name, based on the current date and time */ String generateDirectoryName(); diff --git a/Source/Processors/RecordNode/RecordThread.cpp b/Source/Processors/RecordNode/RecordThread.cpp index 6c0e81bd0e..fb98215a60 100644 --- a/Source/Processors/RecordNode/RecordThread.cpp +++ b/Source/Processors/RecordNode/RecordThread.cpp @@ -42,13 +42,13 @@ RecordThread::~RecordThread() { } -void RecordThread::setFileComponents(File rootFolder, int experimentNumber, int recordingNumber) +void RecordThread::setFileComponents(File rootFolder, String baseName, int recordingNumber) { if (isThreadRunning()) return; m_rootFolder = rootFolder; - m_experimentNumber = experimentNumber; + m_baseName = baseName; m_recordingNumber = recordingNumber; } @@ -91,7 +91,7 @@ void RecordThread::run() Array timestamps; m_dataQueue->getTimestampsForBlock(0, timestamps); EVERY_ENGINE->updateTimestamps(timestamps); - EVERY_ENGINE->openFiles(m_rootFolder, m_experimentNumber, m_recordingNumber); + EVERY_ENGINE->openFiles(m_rootFolder, m_baseName, m_recordingNumber); } //3-Normal loop while (!threadShouldExit()) diff --git a/Source/Processors/RecordNode/RecordThread.h b/Source/Processors/RecordNode/RecordThread.h index 1406c3731e..1c95f71252 100644 --- a/Source/Processors/RecordNode/RecordThread.h +++ b/Source/Processors/RecordNode/RecordThread.h @@ -41,7 +41,7 @@ class RecordThread : public Thread public: RecordThread(const OwnedArray& engines); ~RecordThread(); - void setFileComponents(File rootFolder, int experimentNumber, int recordingNumber); + void setFileComponents(File rootFolder, String baseName, int recordingNumber); void setChannelMap(const Array& channels); void setQueuePointers(DataQueue* data, EventMsgQueue* events, SpikeMsgQueue* spikes); @@ -64,7 +64,7 @@ class RecordThread : public Thread std::atomic m_cleanExit; File m_rootFolder; - int m_experimentNumber; + String m_baseName; int m_recordingNumber; int m_numChannels; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RecordThread); diff --git a/Source/UI/ControlPanel.cpp b/Source/UI/ControlPanel.cpp index 7d449a04e3..639de87570 100755 --- a/Source/UI/ControlPanel.cpp +++ b/Source/UI/ControlPanel.cpp @@ -419,12 +419,6 @@ ControlPanel::ControlPanel(ProcessorGraph* graph_, AudioComponent* audio_) recordOptionsButton->setTooltip("Configure options for selected record engine"); addChildComponent(recordOptionsButton); - newDirectoryButton = new UtilityButton("+", Font("Small Text", 15, Font::plain)); - newDirectoryButton->setEnabledState(false); - newDirectoryButton->addListener(this); - newDirectoryButton->setTooltip("Start a new data directory"); - addChildComponent(newDirectoryButton); - #if defined(__APPLE__) const File dataDirectory = CoreServices::getDefaultUserSaveDirectory(); @@ -442,25 +436,14 @@ ControlPanel::ControlPanel(ProcessorGraph* graph_, AudioComponent* audio_) ""); addChildComponent(filenameComponent); - prependText = new Label("Prepend",""); - prependText->setEditable(true); - prependText->addListener(this); - prependText->setColour(Label::backgroundColourId, Colours::lightgrey); - prependText->setTooltip("Prepend to name of data directory"); - - addChildComponent(prependText); - - dateText = new Label("Date","YYYY-MM-DD_HH-MM-SS"); - dateText->setColour(Label::backgroundColourId, Colours::lightgrey); - dateText->setColour(Label::textColourId, Colours::grey); - addChildComponent(dateText); + baseNameText = new Label("Base name",""); + baseNameText->setEditable(true); + baseNameText->addListener(this); + baseNameText->setColour(Label::backgroundColourId, Colours::lightgrey); + //baseNameText->setColour(Label::textColourId, Colours::black); + baseNameText->setTooltip("Base file and folder name"); - appendText = new Label("Append",""); - appendText->setEditable(true); - appendText->addListener(this); - appendText->setColour(Label::backgroundColourId, Colours::lightgrey); - addChildComponent(appendText); - appendText->setTooltip("Append to name of data directory"); + addChildComponent(baseNameText); //diskMeter->updateDiskSpace(graph->getRecordNode()->getFreeSpace()); //diskMeter->repaint(); @@ -740,26 +723,14 @@ void ControlPanel::resized() filenameComponent->setBounds (165, topBound, w - 500, h - 10); filenameComponent->setVisible (true); - newDirectoryButton->setBounds (w - h + 4, topBound, h - 10, h - 10); - newDirectoryButton->setVisible (true); - - prependText->setBounds (165 + w - 490, topBound, 50, h - 10); - prependText->setVisible (true); - - dateText->setBounds (165 + w - 435, topBound, 175, h - 10); - dateText->setVisible (true); - - appendText->setBounds (165 + w - 255, topBound, 50, h - 10); - appendText->setVisible (true); + baseNameText->setBounds (165 + w - 490, topBound, 315, h - 10); + baseNameText->setVisible (true); } else { filenameComponent->setVisible (false); - newDirectoryButton->setVisible (false); - prependText->setVisible (false); - dateText->setVisible (false); - appendText->setVisible (false); + baseNameText->setVisible (false); recordSelector->setVisible (false); recordOptionsButton->setVisible (false); } @@ -779,10 +750,7 @@ void ControlPanel::openState(bool os) void ControlPanel::labelTextChanged(Label* label) { graph->getRecordNode()->newDirectoryNeeded = true; - newDirectoryButton->setEnabledState(false); masterClock->resetRecordTime(); - - dateText->setColour(Label::textColourId, Colours::grey); } void ControlPanel::startRecording() @@ -790,9 +758,8 @@ void ControlPanel::startRecording() masterClock->startRecording(); // turn on recording backgroundColour = Colour(255,0,0); - prependText->setEditable(false); - appendText->setEditable(false); - dateText->setColour(Label::textColourId, Colours::black); + filenameComponent->setEnabled(false); + baseNameText->setEnabled(false); graph->setRecordState(true); @@ -804,11 +771,9 @@ void ControlPanel::stopRecording() graph->setRecordState(false); // turn off recording in processor graph masterClock->stopRecording(); - newDirectoryButton->setEnabledState(true); backgroundColour = Colour (51, 51, 51); - prependText->setEditable(true); - appendText->setEditable(true); + baseNameText->setEnabled(true); recordButton->setToggleState(false, dontSendNotification); @@ -818,17 +783,6 @@ void ControlPanel::stopRecording() void ControlPanel::buttonClicked(Button* button) { - if (button == newDirectoryButton && newDirectoryButton->getEnabledState()) - { - graph->getRecordNode()->newDirectoryNeeded = true; - newDirectoryButton->setEnabledState(false); - masterClock->resetRecordTime(); - - dateText->setColour(Label::textColourId, Colours::grey); - - return; - } - if (button == playButton) { if (playButton->getToggleState()) @@ -944,10 +898,8 @@ void ControlPanel::comboBoxChanged(ComboBox* combo) AccessClass::getProcessorGraph()->getRecordNode()->registerRecordEngine(re); graph->getRecordNode()->newDirectoryNeeded = true; - newDirectoryButton->setEnabledState(false); masterClock->resetRecordTime(); - dateText->setColour(Label::textColourId, Colours::grey); lastEngineIndex=combo->getSelectedId()-1; } @@ -1041,47 +993,16 @@ void ControlPanel::toggleState() AccessClass::getUIComponent()->childComponentChanged(); } -String ControlPanel::getTextToAppend() -{ - String t = appendText->getText(); - - if (t.length() > 0) - { - return "_" + t; - } - else - { - return t; - } -} -String ControlPanel::getTextToPrepend() +String ControlPanel::getBaseName() { - String t = prependText->getText(); - - if (t.length() > 0) - { - return t + "_"; - } - else - { + String t = baseNameText->getText(); return t; - } -} - -void ControlPanel::setPrependText(String t) -{ - prependText->setText(t, sendNotificationSync); -} - -void ControlPanel::setAppendText(String t) -{ - appendText->setText(t, sendNotificationSync); } -void ControlPanel::setDateText(String t) +void ControlPanel::setBaseName(String t) { - dateText->setText(t, dontSendNotification); + baseNameText->setText(t, sendNotificationSync); } @@ -1091,8 +1012,7 @@ void ControlPanel::saveStateToXml(XmlElement* xml) XmlElement* controlPanelState = xml->createNewChildElement("CONTROLPANEL"); controlPanelState->setAttribute("isOpen",open); controlPanelState->setAttribute("recordPath", filenameComponent->getCurrentFile().getFullPathName()); - controlPanelState->setAttribute("prependText",prependText->getText()); - controlPanelState->setAttribute("appendText",appendText->getText()); + controlPanelState->setAttribute("baseNameText",baseNameText->getText()); controlPanelState->setAttribute("recordEngine",recordEngines[recordSelector->getSelectedId()-1]->getID()); audioEditor->saveStateToXml(xml); @@ -1120,8 +1040,7 @@ void ControlPanel::loadStateFromXml(XmlElement* xml) { filenameComponent->setCurrentFile(File(recordPath), true, sendNotificationAsync); } - appendText->setText(xmlNode->getStringAttribute("appendText", ""), dontSendNotification); - prependText->setText(xmlNode->getStringAttribute("prependText", ""), dontSendNotification); + baseNameText->setText(xmlNode->getStringAttribute("baseNameText", ""), dontSendNotification); String selectedEngine = xmlNode->getStringAttribute("recordEngine"); for (int i = 0; i < recordEngines.size(); i++) { diff --git a/Source/UI/ControlPanel.h b/Source/UI/ControlPanel.h index f63296ecf6..fc0fa33c15 100755 --- a/Source/UI/ControlPanel.h +++ b/Source/UI/ControlPanel.h @@ -324,20 +324,11 @@ class ControlPanel : public Component, /** Notifies the control panel when the filename is updated */ void labelTextChanged(Label*); - /** Used by RecordNode to set the filename. */ - String getTextToPrepend(); + /** Used by RecordNode to retrieve the base file and folder name. */ + String getBaseName(); - /** Used by RecordNode to set the filename. */ - String getTextToAppend(); - - /** Manually set the text to be prepended to the recording directory */ - void setPrependText(String text); - - /** Manually set the text to be appended to the recording directory */ - void setAppendText(String text); - - /** Set date text. */ - void setDateText(String); + /** Set base file and folder name. */ + void setBaseName(String); /** Save settings. */ void saveStateToXml(XmlElement*); @@ -377,14 +368,11 @@ class ControlPanel : public Component, ScopedPointer cpuMeter; ScopedPointer diskMeter; ScopedPointer filenameComponent; - ScopedPointer newDirectoryButton; ScopedPointer cpb; ScopedPointer recordSelector; - ScopedPointer