diff --git a/Source/Devices/AnalogIO.cpp b/Source/Devices/AnalogIO.cpp new file mode 100644 index 0000000..86c4bf6 --- /dev/null +++ b/Source/Devices/AnalogIO.cpp @@ -0,0 +1,190 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "AnalogIO.h" + +AnalogIO::AnalogIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, OnixDeviceType::ANALOGIO, deviceIdx_, oni_ctx) +{ + StreamInfo analogInputStream = StreamInfo( + name + "-AnalogInput", + "Analog Input data", + "onix-analogio.data.input", + 12, + std::floor(AnalogIOFrequencyHz / framesToAverage), + "AnalogInput", + ContinuousChannel::Type::ADC, + 20.0f / numberOfDivisions, // NB: +/- 10 Volts + "V", + {}); + streamInfos.add(analogInputStream); + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; + + for (int i = 0; i < numChannels; i += 1) + { + channelDirection[i] = AnalogIODirection::Input; + channelVoltageRange[i] = AnalogIOVoltageRange::TenVolts; + } + + dataType = AnalogIODataType::Volts; +} + +int AnalogIO::configureDevice() +{ + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + + deviceContext->writeRegister(deviceIdx, (uint32_t)AnalogIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); + + return deviceContext->getLastResult(); +} + +bool AnalogIO::updateSettings() +{ + for (int i = 0; i < numChannels; i += 1) + { + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CH00_IN_RANGE + i, (oni_reg_val_t)channelVoltageRange[i]); + if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + } + + uint32_t ioReg = 0; + + for (int i = 0; i < numChannels; i += 1) + { + ioReg = (ioReg & ~((uint32_t)1 << i)) | ((uint32_t)(channelDirection[i]) << i); + } + + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CHDIR, ioReg); + if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + + for (int i = 0; i < numChannels; i += 1) + { + voltsPerDivision[i] = getVoltsPerDivision(channelVoltageRange[i]); + } + + return true; +} + +float AnalogIO::getVoltsPerDivision(AnalogIOVoltageRange voltageRange) +{ + switch (voltageRange) + { + case AnalogIOVoltageRange::TenVolts: + return 20.0f / numberOfDivisions; + case AnalogIOVoltageRange::TwoPointFiveVolts: + return 5.0f / numberOfDivisions; + case AnalogIOVoltageRange::FiveVolts: + return 10.0f / numberOfDivisions; + default: + return 0.0f; + } +} + +void AnalogIO::startAcquisition() +{ + currentFrame = 0; + currentAverageFrame = 0; + sampleNumber = 0; + + analogInputSamples.fill(0); +} + +void AnalogIO::stopAcquisition() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_destroy_frame(frameArray.removeAndReturn(0)); + } +} + +void AnalogIO::addFrame(oni_frame_t* frame) +{ + const GenericScopedLock frameLock(frameArray.getLock()); + frameArray.add(frame); +} + +void AnalogIO::addSourceBuffers(OwnedArray& sourceBuffers) +{ + for (StreamInfo streamInfo : streamInfos) + { + sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); + + if (streamInfo.getChannelPrefix().equalsIgnoreCase("AnalogInput")) + analogInputBuffer = sourceBuffers.getLast(); + } +} + +void AnalogIO::processFrames() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_frame_t* frame = frameArray.removeAndReturn(0); + + int16_t* dataPtr = (int16_t*)frame->data; + + int dataOffset = 4; + + for (int i = 0; i < numChannels; i += 1) + { + if (dataType == AnalogIODataType::S16) + analogInputSamples[currentFrame + i * numFrames] += *(dataPtr + dataOffset + i); + else + analogInputSamples[currentFrame + i * numFrames] += *(dataPtr + dataOffset + i) * voltsPerDivision[i]; + } + + currentAverageFrame += 1; + + if (currentAverageFrame >= framesToAverage) + { + for (int i = 0; i < numChannels; i += 1) + { + analogInputSamples[currentFrame + i * numFrames] /= framesToAverage; + } + + currentAverageFrame = 0; + + timestamps[currentFrame] = frame->time; + sampleNumbers[currentFrame] = sampleNumber++; + + currentFrame++; + } + + oni_destroy_frame(frame); + + if (currentFrame >= numFrames) + { + shouldAddToBuffer = true; + currentFrame = 0; + } + + if (shouldAddToBuffer) + { + shouldAddToBuffer = false; + analogInputBuffer->addToBuffer(analogInputSamples.data(), sampleNumbers, timestamps, eventCodes, numFrames); + + analogInputSamples.fill(0); + } + } +} diff --git a/Source/Devices/AnalogIO.h b/Source/Devices/AnalogIO.h new file mode 100644 index 0000000..d74466a --- /dev/null +++ b/Source/Devices/AnalogIO.h @@ -0,0 +1,178 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + +#pragma once + +#include "../OnixDevice.h" + +enum class AnalogIORegisters : uint32_t +{ + ENABLE = 0, + CHDIR = 1, + CH00_IN_RANGE = 2, + CH01_IN_RANGE = 3, + CH02_IN_RANGE = 4, + CH03_IN_RANGE = 5, + CH04_IN_RANGE = 6, + CH05_IN_RANGE = 7, + CH06_IN_RANGE = 8, + CH07_IN_RANGE = 9, + CH08_IN_RANGE = 10, + CH09_IN_RANGE = 11, + CH10_IN_RANGE = 12, + CH11_IN_RANGE = 13, +}; + +enum class AnalogIOVoltageRange : uint32_t +{ + TenVolts = 0, + TwoPointFiveVolts = 1, + FiveVolts = 2 +}; + +enum class AnalogIODirection : uint32_t +{ + Input = 0, + Output = 1 +}; + +enum class AnalogIODataType : uint32_t +{ + S16 = 0, + Volts = 1 +}; + +/* + Configures and streams data from an AnalogIO device on a Breakout Board +*/ +class AnalogIO : public OnixDevice +{ +public: + AnalogIO(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + /** Configures the device so that it is ready to stream with default settings */ + int configureDevice() override; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override; + + void addFrame(oni_frame_t*) override; + + void processFrames() override; + + AnalogIODirection getChannelDirection(int channelNumber) + { + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return AnalogIODirection::Input; + } + + return channelDirection[channelNumber]; + } + + void setChannelDirection(int channelNumber, AnalogIODirection direction) + { + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return; + } + + channelDirection[channelNumber] = direction; + } + + AnalogIOVoltageRange getChannelVoltageRange(int channelNumber) + { + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return AnalogIOVoltageRange::FiveVolts; + } + + return channelVoltageRange[channelNumber]; + } + + void setChannelVoltageRange(int channelNumber, AnalogIOVoltageRange direction) + { + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return; + } + + channelVoltageRange[channelNumber] = direction; + } + + AnalogIODataType getDataType() const { return dataType; } + + void setDataType(AnalogIODataType type) { dataType = type; } + + int getNumChannels() { return numChannels; } + +private: + + DataBuffer* analogInputBuffer = nullptr; + + static const int AnalogIOFrequencyHz = 100000; + + static const int numFrames = 25; + static const int framesToAverage = 4; // NB: Downsampling from 100 kHz to 25 kHz + static const int numChannels = 12; + + static const int numberOfDivisions = 1 << 16; + const int dacMidScale = 1 << 15; + + std::array channelDirection; + std::array channelVoltageRange; + + AnalogIODataType dataType = AnalogIODataType::Volts; + + Array frameArray; + + unsigned short currentFrame = 0; + unsigned short currentAverageFrame = 0; + int sampleNumber = 0; + + bool shouldAddToBuffer = false; + + std::array analogInputSamples; + + double timestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; + + std::array voltsPerDivision; + + static float getVoltsPerDivision(AnalogIOVoltageRange voltageRange); + + JUCE_LEAK_DETECTOR(AnalogIO); +}; diff --git a/Source/Devices/Bno055.cpp b/Source/Devices/Bno055.cpp index b73920b..49cc3e3 100644 --- a/Source/Devices/Bno055.cpp +++ b/Source/Devices/Bno055.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -39,7 +38,7 @@ Bno055::Bno055(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptrisInitialized()) return -1; @@ -155,7 +150,7 @@ void Bno055::processFrames() int16_t* dataPtr = (int16_t*)frame->data; - bnoTimestamps[currentFrame] = *(uint64_t*)frame->data; + bnoTimestamps[currentFrame] = frame->time; int dataOffset = 4; diff --git a/Source/Devices/Bno055.h b/Source/Devices/Bno055.h index aaa0044..9f4c6cf 100644 --- a/Source/Devices/Bno055.h +++ b/Source/Devices/Bno055.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef BNO055_H_DEFINED -#define BNO055_H_DEFINED +#pragma once #include "../OnixDevice.h" @@ -31,6 +29,9 @@ enum class Bno055Registers ENABLE = 0x00 }; +/* + Configures and streams data from a BNO055 device +*/ class Bno055 : public OnixDevice { public: @@ -38,9 +39,6 @@ class Bno055 : public OnixDevice /** Constructor */ Bno055(String name, const oni_dev_idx_t, std::shared_ptr ctx); - /** Destructor */ - ~Bno055(); - int configureDevice() override; /** Update the settings of the device */ @@ -82,5 +80,3 @@ class Bno055 : public OnixDevice JUCE_LEAK_DETECTOR(Bno055); }; - -#endif diff --git a/Source/Devices/DS90UB9x.h b/Source/Devices/DS90UB9x.h index 57ca7b7..93463b9 100644 --- a/Source/Devices/DS90UB9x.h +++ b/Source/Devices/DS90UB9x.h @@ -1,4 +1,27 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + #pragma once + #include static class DS90UB9x diff --git a/Source/Devices/DeviceList.h b/Source/Devices/DeviceList.h index d4ed212..30ebb78 100644 --- a/Source/Devices/DeviceList.h +++ b/Source/Devices/DeviceList.h @@ -1,6 +1,34 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "Bno055.h" #include "DS90UB9x.h" #include "HeadStageEEPROM.h" #include "Neuropixels_1.h" #include "Neuropixels2e.h" +#include "MemoryMonitor.h" +#include "OutputClock.h" +#include "Heartbeat.h" +#include "HarpSyncInput.h" +#include "AnalogIO.h" +#include "DigitalIO.h" #include "PortController.h" diff --git a/Source/Devices/DigitalIO.cpp b/Source/Devices/DigitalIO.cpp new file mode 100644 index 0000000..32dff0f --- /dev/null +++ b/Source/Devices/DigitalIO.cpp @@ -0,0 +1,119 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "DigitalIO.h" + +DigitalIO::DigitalIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, OnixDeviceType::DIGITALIO, deviceIdx_, oni_ctx) +{ +} + +int DigitalIO::configureDevice() +{ + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + + deviceContext->writeRegister(deviceIdx, (uint32_t)DigitalIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); + + return deviceContext->getLastResult(); +} + +bool DigitalIO::updateSettings() +{ + return true; +} + +void DigitalIO::startAcquisition() +{ +} + +void DigitalIO::stopAcquisition() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_destroy_frame(frameArray.removeAndReturn(0)); + } +} + +EventChannel::Settings DigitalIO::getEventChannelSettings() +{ + // NB: The stream must be assigned before adding the channel + EventChannel::Settings settings{ + EventChannel::Type::TTL, + "DigitalIO Event Channel", + "Digital inputs and breakout button states coming from a DigitalIO device", + "onix-digitalio.events", + nullptr, + numButtons + numDigitalInputs + }; + + return settings; +} + +void DigitalIO::addFrame(oni_frame_t* frame) +{ + const GenericScopedLock frameLock(frameArray.getLock()); + frameArray.add(frame); +} + +void DigitalIO::processFrames() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + const GenericScopedLock digitalInputsLock(eventWords.getLock()); + oni_frame_t* frame = frameArray.removeAndReturn(0); + + uint16_t* dataPtr = (uint16_t*)frame->data; + uint64_t timestamp = frame->time; + + int dataOffset = 4; + + uint64_t portState = *(dataPtr + dataOffset); + uint64_t buttonState = *(dataPtr + dataOffset + 1); + + if (portState != 0 || buttonState != 0) + { + uint64_t ttlEventWord = (portState & 255) << 6 | (buttonState & 63); + eventWords.add(ttlEventWord); + } + + oni_destroy_frame(frame); + } +} + +uint64_t DigitalIO::getEventWord() +{ + const GenericScopedLock digitalInputsLock(eventWords.getLock()); + + if (eventWords.size() != 0) + return eventWords.removeAndReturn(0); + + return 0; +} + +bool DigitalIO::hasEventWord() +{ + const GenericScopedLock digitalInputsLock(eventWords.getLock()); + + return eventWords.size() > 0; +} diff --git a/Source/Devices/DigitalIO.h b/Source/Devices/DigitalIO.h new file mode 100644 index 0000000..3e9d02c --- /dev/null +++ b/Source/Devices/DigitalIO.h @@ -0,0 +1,102 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + +#pragma once + +#include "../OnixDevice.h" + +enum class DigitalIORegisters : uint32_t +{ + ENABLE = 0 +}; + +enum class DigitalPortState : uint16_t +{ + Pin0 = 0x1, + Pin1 = 0x2, + Pin2 = 0x4, + Pin3 = 0x8, + Pin4 = 0x10, + Pin5 = 0x20, + Pin6 = 0x40, + Pin7 = 0x80 +}; + +enum class BreakoutButtonState : uint16_t +{ + Moon = 0x1, + Triangle = 0x2, + X = 0x4, + Check = 0x8, + Circle = 0x10, + Square = 0x20, + Reserved0 = 0x40, + Reserved1 = 0x80, + PortDOn = 0x100, + PortCOn = 0x200, + PortBOn = 0x400, + PortAOn = 0x800 +}; + +/* + Configures and streams data from an AnalogIO device on a Breakout Board +*/ +class DigitalIO : public OnixDevice +{ +public: + DigitalIO(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + /** Configures the device so that it is ready to stream with default settings */ + int configureDevice() override; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override {}; + + EventChannel::Settings getEventChannelSettings(); + + void addFrame(oni_frame_t*) override; + + void processFrames() override; + + uint64_t getEventWord(); + + bool hasEventWord(); + +private: + + static const int numDigitalInputs = 8; + static const int numButtons = 6; + + Array frameArray; + Array eventWords; + + JUCE_LEAK_DETECTOR(DigitalIO); +}; diff --git a/Source/Devices/HarpSyncInput.cpp b/Source/Devices/HarpSyncInput.cpp new file mode 100644 index 0000000..1af5d66 --- /dev/null +++ b/Source/Devices/HarpSyncInput.cpp @@ -0,0 +1,126 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "HarpSyncInput.h" + +HarpSyncInput::HarpSyncInput(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, OnixDeviceType::HARPSYNCINPUT, deviceIdx_, oni_ctx) +{ + setEnabled(false); + + StreamInfo harpTimeStream = StreamInfo( + name + "-HarpTime", + "Harp clock time corresponding to the local acquisition ONIX clock count", + "onix-harpsyncinput.data.harptime", + 1, + 1, + "HarpTime", + ContinuousChannel::Type::AUX, + 1.0f, + "s", + {""}); + streamInfos.add(harpTimeStream); + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; +} + +int HarpSyncInput::configureDevice() +{ + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + + deviceContext->writeRegister(deviceIdx, (uint32_t)HarpSyncInputRegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); + + return deviceContext->getLastResult(); +} + +bool HarpSyncInput::updateSettings() +{ + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HarpSyncInputRegisters::SOURCE, (oni_reg_val_t)HarpSyncSource::Breakout); + + return deviceContext->getLastResult() == ONI_ESUCCESS; +} + +void HarpSyncInput::startAcquisition() +{ + currentFrame = 0; + sampleNumber = 0; +} + +void HarpSyncInput::stopAcquisition() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_destroy_frame(frameArray.removeAndReturn(0)); + } +} + +void HarpSyncInput::addFrame(oni_frame_t* frame) +{ + const GenericScopedLock frameLock(frameArray.getLock()); + frameArray.add(frame); +} + +void HarpSyncInput::addSourceBuffers(OwnedArray& sourceBuffers) +{ + for (StreamInfo streamInfo : streamInfos) + { + sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); + + if (streamInfo.getChannelPrefix().equalsIgnoreCase("HarpTime")) + harpTimeBuffer = sourceBuffers.getLast(); + } +} + +void HarpSyncInput::processFrames() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_frame_t* frame = frameArray.removeAndReturn(0); + + uint32_t* dataPtr = (uint32_t*)frame->data; + + timestamps[currentFrame] = frame->time; + + harpTimeSamples[currentFrame] = *(dataPtr + 2) + 1; + + oni_destroy_frame(frame); + + sampleNumbers[currentFrame] = sampleNumber++; + + currentFrame++; + + if (currentFrame >= numFrames) + { + shouldAddToBuffer = true; + currentFrame = 0; + } + + if (shouldAddToBuffer) + { + shouldAddToBuffer = false; + harpTimeBuffer->addToBuffer(harpTimeSamples, sampleNumbers, timestamps, eventCodes, numFrames); + } + } +} diff --git a/Source/Devices/HarpSyncInput.h b/Source/Devices/HarpSyncInput.h new file mode 100644 index 0000000..cabdc2d --- /dev/null +++ b/Source/Devices/HarpSyncInput.h @@ -0,0 +1,86 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + +#pragma once + +#include "../OnixDevice.h" + +enum class HarpSyncInputRegisters : uint32_t +{ + ENABLE = 0, + SOURCE = 1 +}; + +enum class HarpSyncSource : uint32_t +{ + Breakout = 0, + ClockAdapter = 1 +}; + +/* + Configures and streams data from a HarpSyncInput device on a Breakout Board +*/ +class HarpSyncInput : public OnixDevice +{ +public: + HarpSyncInput(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + /** Configures the device so that it is ready to stream with default settings */ + int configureDevice() override; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override; + + void addFrame(oni_frame_t* frame) override; + + void processFrames() override; + +private: + + DataBuffer* harpTimeBuffer; + + static const int numFrames = 2; + + Array frameArray; + + unsigned short currentFrame = 0; + int sampleNumber = 0; + + bool shouldAddToBuffer = false; + + float harpTimeSamples[numFrames]; + + double timestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; + + JUCE_LEAK_DETECTOR(HarpSyncInput); +}; diff --git a/Source/Devices/HeadStageEEPROM.cpp b/Source/Devices/HeadStageEEPROM.cpp index 1ae4cc4..7f2577c 100644 --- a/Source/Devices/HeadStageEEPROM.cpp +++ b/Source/Devices/HeadStageEEPROM.cpp @@ -1,3 +1,25 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "HeadStageEEPROM.h" #include "DS90UB9x.h" #include diff --git a/Source/Devices/HeadStageEEPROM.h b/Source/Devices/HeadStageEEPROM.h index c5936db..c211a0c 100644 --- a/Source/Devices/HeadStageEEPROM.h +++ b/Source/Devices/HeadStageEEPROM.h @@ -1,3 +1,26 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + + #pragma once #include "../I2CRegisterContext.h" diff --git a/Source/Devices/Heartbeat.cpp b/Source/Devices/Heartbeat.cpp new file mode 100644 index 0000000..6ddd99f --- /dev/null +++ b/Source/Devices/Heartbeat.cpp @@ -0,0 +1,49 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "Heartbeat.h" + +Heartbeat::Heartbeat(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, OnixDeviceType::HEARTBEAT, deviceIdx_, oni_ctx) +{ +} + +int Heartbeat::configureDevice() +{ + setEnabled(true); + + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + + deviceContext->writeRegister(deviceIdx, (uint32_t)HeartbeatRegisters::ENABLE, 1); + + return deviceContext->getLastResult(); +} + +bool Heartbeat::updateSettings() +{ + oni_reg_val_t clkHz = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)HeartbeatRegisters::CLK_HZ); + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HeartbeatRegisters::CLK_DIV, clkHz / beatsPerSecond); + + if (deviceContext->getLastResult() == ONI_EREADONLY) return true; // NB: Ignore read-only errors + + return deviceContext->getLastResult() == ONI_ESUCCESS; +} diff --git a/Source/Devices/Heartbeat.h b/Source/Devices/Heartbeat.h new file mode 100644 index 0000000..e23bcfc --- /dev/null +++ b/Source/Devices/Heartbeat.h @@ -0,0 +1,66 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + +#pragma once + +#include "../OnixDevice.h" + +enum class HeartbeatRegisters : uint32_t +{ + ENABLE = 0, + CLK_DIV = 1, + CLK_HZ = 2 +}; + +/* + Configures and streams data from a MemoryMonitor device on a Breakout Board +*/ +class Heartbeat : public OnixDevice +{ +public: + Heartbeat(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + /** Configures the device so that it is ready to stream with default settings */ + int configureDevice() override; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override {}; + + /** Stops probe data streaming*/ + void stopAcquisition() override {}; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override {}; + + void addFrame(oni_frame_t* frame) override { oni_destroy_frame(frame); } + + void processFrames() override {}; + +private: + + const uint32_t beatsPerSecond = 100; + + JUCE_LEAK_DETECTOR(Heartbeat); +}; diff --git a/Source/Devices/MemoryMonitor.cpp b/Source/Devices/MemoryMonitor.cpp new file mode 100644 index 0000000..b631305 --- /dev/null +++ b/Source/Devices/MemoryMonitor.cpp @@ -0,0 +1,178 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "MemoryMonitor.h" +#include "DigitalIO.h" + +MemoryMonitorUsage::MemoryMonitorUsage(GenericProcessor* p) + : LevelMonitor(p) +{ + device = nullptr; +} + +void MemoryMonitorUsage::timerCallback() +{ + if (device != nullptr) + { + setFillPercentage(std::log(device->getLastPercentUsedValue() + 1) / maxLogarithmicValue); + repaint(); + } +} + +void MemoryMonitorUsage::setMemoryMonitor(std::shared_ptr memoryMonitor) +{ + device = memoryMonitor; +} + +void MemoryMonitorUsage::startAcquisition() +{ + startTimerHz(TimerFrequencyHz); +} + +void MemoryMonitorUsage::stopAcquisition() +{ + stopTimer(); + setFillPercentage(0.0f); + repaint(); +} + +MemoryMonitor::MemoryMonitor(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, OnixDeviceType::MEMORYMONITOR, deviceIdx_, oni_ctx) +{ + StreamInfo percentUsedStream = StreamInfo( + name + "-PercentUsed", + "Percent of available memory that is currently used", + "onix - memorymonitor.data.percentused", + 1, + samplesPerSecond, + "Percent", + ContinuousChannel::Type::AUX, + 1.0f, + "%", + {""}); + streamInfos.add(percentUsedStream); + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; +} + +int MemoryMonitor::configureDevice() +{ + setEnabled(true); + + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + + deviceContext->writeRegister(deviceIdx, (uint32_t)MemoryMonitorRegisters::ENABLE, 1); + if (deviceContext->getLastResult() != ONI_ESUCCESS) return deviceContext->getLastResult(); + + totalMemory = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::TOTAL_MEM); + + return deviceContext->getLastResult(); +} + +bool MemoryMonitor::updateSettings() +{ + oni_reg_val_t clkHz = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_HZ); + + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_DIV, clkHz / samplesPerSecond); + + return deviceContext->getLastResult() == ONI_ESUCCESS; +} + +void MemoryMonitor::startAcquisition() +{ + currentFrame = 0; + sampleNumber = 0; + + lastPercentUsedValue = 0.0f; +} + +void MemoryMonitor::stopAcquisition() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_destroy_frame(frameArray.removeAndReturn(0)); + } +} + +void MemoryMonitor::addFrame(oni_frame_t* frame) +{ + const GenericScopedLock frameLock(frameArray.getLock()); + frameArray.add(frame); +} + +void MemoryMonitor::addSourceBuffers(OwnedArray& sourceBuffers) +{ + for (StreamInfo streamInfo : streamInfos) + { + sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); + + if (streamInfo.getChannelPrefix().equalsIgnoreCase("Percent")) + percentUsedBuffer = sourceBuffers.getLast(); + } +} + +float MemoryMonitor::getLastPercentUsedValue() +{ + return lastPercentUsedValue; +} + +void MemoryMonitor::processFrames() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_frame_t* frame = frameArray.removeAndReturn(0); + + uint32_t* dataPtr = (uint32_t*)frame->data; + uint64_t timestamp = frame->time; + + timestamps[currentFrame] = frame->time; + + percentUsedSamples[currentFrame] = 100.0f * float(*(dataPtr + 2)) / totalMemory; + + lastPercentUsedValue = percentUsedSamples[currentFrame]; + + oni_destroy_frame(frame); + + sampleNumbers[currentFrame] = sampleNumber++; + + prevWord = m_digitalIO != nullptr && m_digitalIO->hasEventWord() ? m_digitalIO->getEventWord() : prevWord; + + eventCodes[currentFrame] = prevWord; + + currentFrame++; + + if (currentFrame >= numFrames) + { + shouldAddToBuffer = true; + currentFrame = 0; + } + + if (shouldAddToBuffer) + { + shouldAddToBuffer = false; + percentUsedBuffer->addToBuffer(percentUsedSamples, sampleNumbers, timestamps, eventCodes, numFrames); + } + } +} diff --git a/Source/Devices/MemoryMonitor.h b/Source/Devices/MemoryMonitor.h new file mode 100644 index 0000000..add5cab --- /dev/null +++ b/Source/Devices/MemoryMonitor.h @@ -0,0 +1,130 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + +#pragma once + +#include "../OnixDevice.h" + +class DigitalIO; + +enum class MemoryMonitorRegisters : uint32_t +{ + ENABLE = 0, + CLK_DIV = 1, + CLK_HZ = 2, + TOTAL_MEM = 3 +}; + +/* + Configures and streams data from a MemoryMonitor device on a Breakout Board +*/ +class MemoryMonitor : public OnixDevice +{ +public: + MemoryMonitor(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + /** Configures the device so that it is ready to stream with default settings */ + int configureDevice() override; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override; + + void addFrame(oni_frame_t*) override; + + void processFrames() override; + + float getLastPercentUsedValue(); + + void setDigitalIO(std::shared_ptr digitalIO) { m_digitalIO = digitalIO; } + +private: + + DataBuffer* percentUsedBuffer; + + std::shared_ptr m_digitalIO; + + uint64_t prevWord = 0; + + static const int numFrames = 10; + + Array frameArray; + + unsigned short currentFrame = 0; + int sampleNumber = 0; + + /** The frequency at which memory use is recorded in Hz. */ + const uint32_t samplesPerSecond = 100; + + bool shouldAddToBuffer = false; + + float percentUsedSamples[numFrames]; + float bytesUsedSamples[numFrames]; + + double timestamps[numFrames]; + int64_t sampleNumbers[numFrames]; + uint64_t eventCodes[numFrames]; + + /** The total amount of memory, in 32-bit words, on the hardware that is available for data buffering*/ + uint32_t totalMemory; + + std::atomic lastPercentUsedValue = 0.0f; + + JUCE_LEAK_DETECTOR(MemoryMonitor); +}; + +/* + Tracks the MemoryMonitor usage while data acquisition is running +*/ +class MemoryMonitorUsage : public LevelMonitor +{ +public: + MemoryMonitorUsage(GenericProcessor*); + + void timerCallback() override; + + void setMemoryMonitor(std::shared_ptr memoryMonitor); + + void startAcquisition(); + + void stopAcquisition(); + +private: + + std::shared_ptr device; + + // NB: Calculate the maximum logarithmic value to convert from linear scale (x: 0-100) to logarithmic scale (y: 0-1) + // using the following equation: y = log_e(x + 1) / log_e(x_max + 1); + const float maxLogarithmicValue = std::log(101); + + const int TimerFrequencyHz = 10; + + JUCE_LEAK_DETECTOR(MemoryMonitorUsage); +}; diff --git a/Source/Devices/Neuropixels2e.cpp b/Source/Devices/Neuropixels2e.cpp index db1f17f..8ccb09d 100644 --- a/Source/Devices/Neuropixels2e.cpp +++ b/Source/Devices/Neuropixels2e.cpp @@ -1,3 +1,25 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "Neuropixels2e.h" #include "DS90UB9x.h" @@ -18,11 +40,7 @@ void Neuropixels2e::createDataStream(int n) 0.195f, CharPointer_UTF8("\xc2\xb5V"), {}); - streams.add(apStream); -} - -Neuropixels2e::~Neuropixels2e() -{ + streamInfos.add(apStream); } int Neuropixels2e::getNumProbes() const @@ -32,6 +50,8 @@ int Neuropixels2e::getNumProbes() const int Neuropixels2e::configureDevice() { + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + configureSerDes(); setProbeSupply(true); probeSNA = getProbeSN(ProbeASelected); @@ -43,7 +63,7 @@ int Neuropixels2e::configureDevice() if (probeSNA == -1 && probeSNB == -1) { m_numProbes = 0; - return -1; + return -2; } else if (probeSNA != -1 && probeSNB != -1) { @@ -215,7 +235,7 @@ void Neuropixels2e::addFrame(oni_frame_t* frame) void Neuropixels2e::addSourceBuffers(OwnedArray& sourceBuffers) { int bufferIdx = 0; - for (const auto& streamInfo : streams) + for (const auto& streamInfo : streamInfos) { sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); apBuffer[bufferIdx++] = sourceBuffers.getLast(); @@ -233,6 +253,7 @@ void Neuropixels2e::processFrames() dataPtr = (uint16_t*)frame->data; uint16_t probeIndex = *(dataPtr + 4); uint16_t* amplifierData = dataPtr + 6; + uint64_t timestamp = frame->time; if (m_numProbes == 1) { diff --git a/Source/Devices/Neuropixels2e.h b/Source/Devices/Neuropixels2e.h index 4f8b41a..2c7f692 100644 --- a/Source/Devices/Neuropixels2e.h +++ b/Source/Devices/Neuropixels2e.h @@ -1,15 +1,38 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + #pragma once + #include "../OnixDevice.h" #include "../I2CRegisterContext.h" +/* + Configures and streams data from a Neuropixels 2.0e device +*/ class Neuropixels2e : public OnixDevice { public: Neuropixels2e(String name, const oni_dev_idx_t, std::shared_ptr); - /** Destructor */ - ~Neuropixels2e(); - int configureDevice() override; /** Update the settings of the device */ diff --git a/Source/Devices/Neuropixels_1.cpp b/Source/Devices/Neuropixels_1.cpp index a6ada23..3854746 100644 --- a/Source/Devices/Neuropixels_1.cpp +++ b/Source/Devices/Neuropixels_1.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -22,7 +21,6 @@ */ #include "Neuropixels_1.h" -#include "../OnixSource.h" BackgroundUpdaterWithProgressWindow::BackgroundUpdaterWithProgressWindow(Neuropixels_1* d) : ThreadWithProgressWindow("Writing calibration files to Neuropixels Probe: " + d->getName(), true, false) @@ -30,10 +28,6 @@ BackgroundUpdaterWithProgressWindow::BackgroundUpdaterWithProgressWindow(Neuropi device = d; } -BackgroundUpdaterWithProgressWindow::~BackgroundUpdaterWithProgressWindow() -{ -} - bool BackgroundUpdaterWithProgressWindow::updateSettings() { if (device->isEnabled()) @@ -189,7 +183,7 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s 0.195f, CharPointer_UTF8("\xc2\xb5V"), {}); - streams.add(apStream); + streamInfos.add(apStream); StreamInfo lfpStream = StreamInfo( name + "-LFP", @@ -202,7 +196,7 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s 0.195f, CharPointer_UTF8("\xc2\xb5V"), {}); - streams.add(lfpStream); + streamInfos.add(lfpStream); settings = std::make_unique(); defineMetadata(settings.get()); @@ -211,10 +205,6 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s gainCalibrationFilePath = "None"; } -Neuropixels_1::~Neuropixels_1() -{ -} - NeuropixelsGain Neuropixels_1::getGainEnum(int index) { switch (index) @@ -325,7 +315,7 @@ int Neuropixels_1::configureDevice() WriteByte((uint32_t)NeuropixelsRegisters::OP_MODE, (uint32_t)OpMode::RECORD); if (i2cContext->getLastResult() != ONI_ESUCCESS) return -3; - return 0; + return deviceContext->getLastResult(); } bool Neuropixels_1::updateSettings() @@ -429,7 +419,7 @@ void Neuropixels_1::stopAcquisition() void Neuropixels_1::addSourceBuffers(OwnedArray& sourceBuffers) { - for (StreamInfo streamInfo : streams) + for (StreamInfo streamInfo : streamInfos) { sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); @@ -456,16 +446,9 @@ void Neuropixels_1::processFrames() const GenericScopedLock frameLock(frameArray.getLock()); oni_frame_t* frame = frameArray.removeAndReturn(0); - uint16_t* dataPtr; - dataPtr = (uint16_t*)frame->data; - - auto dataclock = (unsigned char*)frame->data + 936; - uint64 hubClock = ((uint64_t)(*(uint16_t*)dataclock) << 48) | - ((uint64_t)(*(uint16_t*)(dataclock + 2)) << 32) | - ((uint64_t)(*(uint16_t*)(dataclock + 4)) << 16) | - ((uint64_t)(*(uint16_t*)(dataclock + 6)) << 0); + uint16_t* dataPtr = (uint16_t*)frame->data; - apTimestamps[superFrameCount] = hubClock; + apTimestamps[superFrameCount] = frame->time; apSampleNumbers[superFrameCount] = apSampleNumber++; for (int i = 0; i < framesPerSuperFrame; i++) diff --git a/Source/Devices/Neuropixels_1.h b/Source/Devices/Neuropixels_1.h index f2a51aa..2c4ac49 100644 --- a/Source/Devices/Neuropixels_1.h +++ b/Source/Devices/Neuropixels_1.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,14 +20,11 @@ */ -#ifndef NEUROPIXELS1_H_DEFINED -#define NEUROPIXELS1_H_DEFINED +#pragma once #include "../OnixDevice.h" #include "../NeuropixComponents.h" -class OnixSource; - #include #include #include @@ -149,7 +145,7 @@ struct NeuropixelsV1Adc /** - Streams data from an ONIX device + Configures and streams data from a Neuropixels 1.0f device */ class Neuropixels_1 : public OnixDevice, @@ -159,10 +155,7 @@ class Neuropixels_1 : public OnixDevice, /** Constructor */ Neuropixels_1(String name, const oni_dev_idx_t, std::shared_ptr); - /** Destructor */ - ~Neuropixels_1(); - - /** Enables the device so that it is ready to stream with default settings */ + /** Configures the device so that it is ready to stream with default settings */ int configureDevice() override; /** Update the settings of the device by writing to hardware */ @@ -292,8 +285,6 @@ class BackgroundUpdaterWithProgressWindow : public ThreadWithProgressWindow public: BackgroundUpdaterWithProgressWindow(Neuropixels_1* d); - ~BackgroundUpdaterWithProgressWindow(); - void run(); bool updateSettings(); @@ -306,5 +297,3 @@ class BackgroundUpdaterWithProgressWindow : public ThreadWithProgressWindow JUCE_LEAK_DETECTOR(BackgroundUpdaterWithProgressWindow); }; - -#endif diff --git a/Source/Devices/OutputClock.cpp b/Source/Devices/OutputClock.cpp new file mode 100644 index 0000000..c3f1ca4 --- /dev/null +++ b/Source/Devices/OutputClock.cpp @@ -0,0 +1,63 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 "OutputClock.h" + +OutputClock::OutputClock(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, OnixDeviceType::OUTPUTCLOCK, deviceIdx_, oni_ctx) +{ +} + +OutputClock::~OutputClock() +{ + if (deviceContext != nullptr && deviceContext->isInitialized()) setGateRun(false, true); +} + +bool OutputClock::updateSettings() +{ + oni_reg_val_t baseFreqHz = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::BASE_FREQ_HZ); + + auto periodCycles = (uint32_t)(baseFreqHz / frequencyHz); + auto h = (uint32_t)(periodCycles * ((double)dutyCycle / 100.0)); + auto l = periodCycles - h; + auto delayCycles = (uint32_t)(delay * baseFreqHz); + + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::CLOCK_GATE, 1); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::HIGH_CYCLES, h); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::LOW_CYCLES, l); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::DELAY_CYCLES, delayCycles); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + + deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::GATE_RUN, gateRun ? 1 : 0); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + + return true; +} + +void OutputClock::startAcquisition() +{ + writeGateRunRegister(); +} + +void OutputClock::stopAcquisition() +{ + setGateRun(false, true); +} \ No newline at end of file diff --git a/Source/Devices/OutputClock.h b/Source/Devices/OutputClock.h new file mode 100644 index 0000000..d93f859 --- /dev/null +++ b/Source/Devices/OutputClock.h @@ -0,0 +1,98 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) 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 . + +*/ + +#pragma once + +#include "../OnixDevice.h" + +enum class OutputClockRegisters : uint32_t +{ + NULL_REGISTER = 0, + CLOCK_GATE = 1, + HIGH_CYCLES = 2, + LOW_CYCLES = 3, + DELAY_CYCLES = 4, + GATE_RUN = 5, + BASE_FREQ_HZ = 6 +}; + +/* + Configures an OutputClock device on a Breakout Board +*/ +class OutputClock : public OnixDevice +{ +public: + OutputClock(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + ~OutputClock(); + + /** Device is always enabled */ + int configureDevice() override { setEnabled(true); return 0; }; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override {}; + + void addFrame(oni_frame_t* frame) override { oni_destroy_frame(frame); } + + void processFrames() override {}; + + float getFrequencyHz() const { return frequencyHz; } + + void setFrequencyHz(float frequency) { frequencyHz = frequency; } + + uint32_t getDutyCycle() const { return dutyCycle; } + + void setDutyCycle(uint32_t dutyCycle_) { dutyCycle = dutyCycle_; } + + uint32_t getDelay() const { return delay; } + + void setDelay(uint32_t delay_) { delay = delay_; } + + bool getGateRun() const { return gateRun; } + + void setGateRun(bool gate, bool writeToRegister = false) + { + gateRun = gate; + if (writeToRegister) writeGateRunRegister(); + } + +private: + + double frequencyHz = 1e6; + uint32_t dutyCycle = 50; + uint32_t delay = 0; + + bool gateRun = true; + + void writeGateRunRegister() { deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::GATE_RUN, gateRun ? 1 : 0); } + + JUCE_LEAK_DETECTOR(OutputClock); +}; diff --git a/Source/Devices/PortController.cpp b/Source/Devices/PortController.cpp index 6dd130c..c244773 100644 --- a/Source/Devices/PortController.cpp +++ b/Source/Devices/PortController.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -29,12 +28,10 @@ PortController::PortController(PortName port_, std::shared_ptr ctx_) : { } -PortController::~PortController() -{ -} - int PortController::configureDevice() { + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -5; + deviceContext->writeRegister(deviceIdx, (uint32_t)PortControllerRegister::ENABLE, 1); return deviceContext->getLastResult(); @@ -97,6 +94,21 @@ DiscoveryParameters PortController::getHeadstageDiscoveryParameters(String heads return DiscoveryParameters(); } +String PortController::getPortName(int offset) +{ + switch (offset) + { + case 0: + return ""; + case HubAddressPortA: + return "Port A"; + case HubAddressPortB: + return "Port B"; + default: + return ""; + } +} + bool PortController::configureVoltage(float voltage) { if (voltage == defaultVoltage) @@ -142,7 +154,7 @@ void PortController::setVoltage(float voltage) deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, 0); if (deviceContext->getLastResult() != ONI_ESUCCESS) return; sleep_for(std::chrono::milliseconds(300)); - + deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, voltage * 10); if (deviceContext->getLastResult() != ONI_ESUCCESS) return; sleep_for(std::chrono::milliseconds(500)); @@ -162,6 +174,23 @@ PortName PortController::getPortFromIndex(oni_dev_idx_t index) return index & (1 << 8) ? PortName::PortA : PortName::PortB; } +int PortController::getOffsetFromIndex(oni_dev_idx_t index) +{ + return index & 0b1100000000; +} + +Array PortController::getUniqueOffsetsFromIndices(std::vector indices) +{ + Array offsets; + + for (auto index : indices) + { + offsets.addIfNotAlreadyThere(getOffsetFromIndex(index)); + } + + return offsets; +} + Array PortController::getUniquePortsFromIndices(std::vector indices) { Array ports; diff --git a/Source/Devices/PortController.h b/Source/Devices/PortController.h index 823322c..5e1a39a 100644 --- a/Source/Devices/PortController.h +++ b/Source/Devices/PortController.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __PORTCONTROLLER_H__ -#define __PORTCONTROLLER_H__ +#pragma once #include #include @@ -71,8 +69,6 @@ class DiscoveryParameters voltageIncrement = voltageIncrement_; } - ~DiscoveryParameters() {}; - bool operator==(const DiscoveryParameters& rhs) const { return rhs.minVoltage == minVoltage && rhs.maxVoltage == maxVoltage && rhs.voltageOffset == voltageOffset && rhs.voltageIncrement == voltageIncrement; @@ -84,8 +80,6 @@ class PortController : public OnixDevice public: PortController(PortName port_, std::shared_ptr ctx_); - ~PortController(); - int configureDevice() override; bool updateSettings() override { return true; } @@ -116,15 +110,24 @@ class PortController : public OnixDevice static int getPortOffset(PortName port) { return (uint32_t)port << 8; } + static String getPortName(int offset); + static String getPortName(PortName port) { return port == PortName::PortA ? "Port A" : "Port B"; } static PortName getPortFromIndex(oni_dev_idx_t index); + static int getOffsetFromIndex(oni_dev_idx_t index); + + static Array getUniqueOffsetsFromIndices(std::vector indices); + static Array getUniquePortsFromIndices(std::vector); /** Check if the port status changed and there is an error reported */ bool getErrorFlag() { return errorFlag; } + static const int HubAddressPortA = 256; + static const int HubAddressPortB = 512; + private: Array frameArray; @@ -141,5 +144,3 @@ class PortController : public OnixDevice JUCE_LEAK_DETECTOR(PortController); }; - -#endif // !__PORTCONTROLLER_H__ diff --git a/Source/Formats/ProbeInterface.h b/Source/Formats/ProbeInterface.h index 6865c16..eca22e1 100644 --- a/Source/Formats/ProbeInterface.h +++ b/Source/Formats/ProbeInterface.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2024 Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __PROBEINTERFACE_H__ -#define __PROBEINTERFACE_H__ +#pragma once #include "../NeuropixComponents.h" @@ -171,5 +169,3 @@ class ProbeInterfaceJson return true; } }; - -#endif \ No newline at end of file diff --git a/Source/FrameReader.cpp b/Source/FrameReader.cpp index 1dc3aef..ae15ed5 100644 --- a/Source/FrameReader.cpp +++ b/Source/FrameReader.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -30,10 +29,6 @@ FrameReader::FrameReader(OnixDeviceVector sources_, std::shared_ptr ctx_) context = ctx_; } -FrameReader::~FrameReader() -{ -} - void FrameReader::run() { while (!threadShouldExit()) @@ -42,6 +37,8 @@ void FrameReader::run() if (context->getLastResult() < ONI_ESUCCESS) { + if (threadShouldExit()) return; + CoreServices::sendStatusMessage("Unable to read data frames. Stopping acquisition..."); CoreServices::setAcquisitionStatus(false); return; diff --git a/Source/FrameReader.h b/Source/FrameReader.h index 1bfb6c6..210786a 100644 --- a/Source/FrameReader.h +++ b/Source/FrameReader.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __FRAMEREADER_H__ -#define __FRAMEREADER_H__ +#pragma once #include @@ -34,8 +32,6 @@ class FrameReader : public Thread public: FrameReader(OnixDeviceVector sources_, std::shared_ptr); - ~FrameReader(); - void run() override; private: @@ -45,5 +41,3 @@ class FrameReader : public Thread JUCE_LEAK_DETECTOR(FrameReader); }; - -#endif // __FRAMEREADER_H__ diff --git a/Source/I2CRegisterContext.cpp b/Source/I2CRegisterContext.cpp index 06acc1e..46405e6 100644 --- a/Source/I2CRegisterContext.cpp +++ b/Source/I2CRegisterContext.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -22,7 +21,6 @@ */ #include "I2CRegisterContext.h" -#include I2CRegisterContext::I2CRegisterContext(uint32_t address_, const oni_dev_idx_t devIdx_, std::shared_ptr ctx_) : deviceIndex(devIdx_), i2caddress(address_) diff --git a/Source/I2CRegisterContext.h b/Source/I2CRegisterContext.h index d08f793..1820d1b 100644 --- a/Source/I2CRegisterContext.h +++ b/Source/I2CRegisterContext.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __I2CRegisterContext_H__ -#define __I2CRegisterContext_H__ +#pragma once #include "ProcessorHeaders.h" @@ -53,5 +51,3 @@ class I2CRegisterContext JUCE_LEAK_DETECTOR(I2CRegisterContext); }; - -#endif // !__I2CRegisterContext_H__ diff --git a/Source/NeuropixComponents.h b/Source/NeuropixComponents.h index fe4e5f3..e29363c 100644 --- a/Source/NeuropixComponents.h +++ b/Source/NeuropixComponents.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __NEUROPIXELCOMPONENTS_H__ -#define __NEUROPIXELCOMPONENTS_H__ +#pragma once #include #include @@ -162,5 +160,3 @@ struct ProbeSettings ProbeMetadata probeMetadata; }; - -#endif // __NEUROPIXELCOMPONENTS_H__ diff --git a/Source/Onix1.cpp b/Source/Onix1.cpp index cae2c61..7dfdcb0 100644 --- a/Source/Onix1.cpp +++ b/Source/Onix1.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ diff --git a/Source/Onix1.h b/Source/Onix1.h index 1ac2a54..279a8be 100644 --- a/Source/Onix1.h +++ b/Source/Onix1.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __ONIX1_H__ -#define __ONIX1_H__ +#pragma once #include #include @@ -32,6 +30,7 @@ #include "../../plugin-GUI/Source/Utils/Utils.h" constexpr const char* NEUROPIXELSV1F_HEADSTAGE_NAME = "Neuropixels 1.0f"; +constexpr const char* BREAKOUT_BOARD_NAME = "Breakout Board"; class error_t : public std::exception { @@ -115,5 +114,3 @@ class Onix1 void get_opt_(int option, void* value, size_t* size); }; - -#endif // !__ONIX1_H__ diff --git a/Source/OnixDevice.cpp b/Source/OnixDevice.cpp index e49e940..1ae5b3b 100644 --- a/Source/OnixDevice.cpp +++ b/Source/OnixDevice.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ diff --git a/Source/OnixDevice.h b/Source/OnixDevice.h index f529d79..b3a7a6d 100644 --- a/Source/OnixDevice.h +++ b/Source/OnixDevice.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __OnixDevice_H__ -#define __OnixDevice_H__ +#pragma once #include @@ -49,7 +47,13 @@ enum class OnixDeviceType { NEUROPIXELS_1, NEUROPIXELS_2, ADC, - PORT_CONTROL + PORT_CONTROL, + MEMORYMONITOR, + OUTPUTCLOCK, + HEARTBEAT, + HARPSYNCINPUT, + ANALOGIO, + DIGITALIO, }; struct StreamInfo { @@ -84,7 +88,9 @@ struct StreamInfo { if (numChannels_ != suffixes_.size()) { - LOGE("Difference between number of channels and suffixes. Generating default suffixes instead."); + if (suffixes_.size() != 0) + LOGE("Difference between number of channels and suffixes. Generating default suffixes instead."); + suffixes_.clear(); suffixes_.ensureStorageAllocated(numChannels); @@ -97,7 +103,7 @@ struct StreamInfo { String getName() const { return name_; } String getDescription() const { return description_; } - String getIdentifer() const { return identifier_; } + String getIdentifier() const { return identifier_; } int getNumChannels() const { return numChannels_; } float getSampleRate() const { return sampleRate_; } String getChannelPrefix() const { return channelPrefix_; } @@ -159,7 +165,7 @@ class OnixDevice OnixDeviceType type; - Array streams; + Array streamInfos; const int bufferSizeInSeconds = 10; @@ -178,5 +184,3 @@ class OnixDevice }; using OnixDeviceVector = std::vector>; - -#endif diff --git a/Source/OnixSource.cpp b/Source/OnixSource.cpp index c421e1e..a1a864b 100644 --- a/Source/OnixSource.cpp +++ b/Source/OnixSource.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -56,6 +55,7 @@ OnixSource::OnixSource(SourceNode* sn) : return; } + // TODO: Add these parameters in the registerParameters() override? addBooleanParameter(Parameter::PROCESSOR_SCOPE, "passthroughA", "Passthrough", "Enables passthrough mode for e-variant headstages on Port A", false, true); addBooleanParameter(Parameter::PROCESSOR_SCOPE, "passthroughB", "Passthrough", "Enables passthrough mode for e-variant headstages on Port B", false, true); @@ -169,7 +169,7 @@ void OnixSource::initializeDevices(bool updateStreamInfo) } sources.emplace_back(np1); - headstages.insert({ PortController::getPortFromIndex(index), NEUROPIXELSV1F_HEADSTAGE_NAME }); + headstages.insert({ PortController::getOffsetFromIndex(index), NEUROPIXELSV1F_HEADSTAGE_NAME }); npxProbeIdx++; } @@ -216,6 +216,96 @@ void OnixSource::initializeDevices(bool updateStreamInfo) sources.emplace_back(np2); } } + else if (device.id == ONIX_MEMUSAGE) + { + auto memoryMonitor = std::make_shared("Memory Monitor", index, context); + + int result = memoryMonitor->configureDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", index, " Error enabling device stream."); + continue; + } + + sources.emplace_back(memoryMonitor); + headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); + } + else if (device.id == ONIX_FMCCLKOUT1R3) + { + auto outputClock = std::make_shared("Output Clock", index, context); + + int result = outputClock->configureDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", index, " Error enabling device stream."); + continue; + } + + sources.emplace_back(outputClock); + headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); + } + else if (device.id == ONIX_HEARTBEAT) + { + auto heartbeat = std::make_shared("Heartbeat", index, context); + + int result = heartbeat->configureDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", index, " Error enabling device stream."); + continue; + } + + sources.emplace_back(heartbeat); + headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); + } + else if (device.id == ONIX_HARPSYNCINPUT) + { + auto harpSyncInput = std::make_shared("Harp Sync Input", index, context); + + int result = harpSyncInput->configureDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", index, " Error enabling device stream."); + continue; + } + + sources.emplace_back(harpSyncInput); + headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); + } + else if (device.id == ONIX_FMCANALOG1R3) + { + auto analogIO = std::make_shared("Analog IO", index, context); + + int result = analogIO->configureDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", index, " Error enabling device stream."); + continue; + } + + sources.emplace_back(analogIO); + headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); + } + else if (device.id == ONIX_BREAKDIG1R3) + { + auto digitalIO = std::make_shared("Digital IO", index, context); + + int result = digitalIO->configureDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", index, " Error enabling device stream."); + continue; + } + + sources.emplace_back(digitalIO); + headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); + } } if (portA->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port A."); @@ -236,7 +326,7 @@ void OnixSource::initializeDevices(bool updateStreamInfo) LOGD("All devices initialized."); } -OnixDeviceVector OnixSource::getDataSources() const +OnixDeviceVector OnixSource::getDataSources() { OnixDeviceVector devices{}; @@ -248,7 +338,7 @@ OnixDeviceVector OnixSource::getDataSources() const return devices; } -OnixDeviceVector OnixSource::getDataSourcesFromPort(PortName port) const +OnixDeviceVector OnixSource::getDataSourcesFromPort(PortName port) { OnixDeviceVector devices{}; @@ -261,24 +351,50 @@ OnixDeviceVector OnixSource::getDataSourcesFromPort(PortName port) const return devices; } -std::map OnixSource::createDeviceMap(OnixDeviceVector devices) +OnixDeviceVector OnixSource::getDataSourcesFromOffset(int offset) +{ + OnixDeviceVector devices{}; + offset = PortController::getOffsetFromIndex(offset); + + for (const auto& source : sources) + { + if (PortController::getOffsetFromIndex(source->getDeviceIdx()) == offset) + devices.emplace_back(source); + } + + return devices; +} + +std::shared_ptr OnixSource::getDevice(OnixDeviceType type) +{ + for (const auto& device : sources) + { + if (device->type == type) return device; + } + + return nullptr; +} + +std::map OnixSource::createDeviceMap(OnixDeviceVector devices, bool filterDevices) { std::map deviceMap; for (const auto& device : devices) { + if (filterDevices && (device->type == OnixDeviceType::HEARTBEAT || device->type == OnixDeviceType::MEMORYMONITOR)) continue; + deviceMap.insert({ device->getDeviceIdx(), device->type }); } return deviceMap; } -std::map OnixSource::createDeviceMap() +std::map OnixSource::createDeviceMap(bool filterDevices) { - return createDeviceMap(getDataSources()); + return createDeviceMap(getDataSources(), filterDevices); } -std::map OnixSource::getHeadstageMap() +std::map OnixSource::getHeadstageMap() { return headstages; } @@ -367,13 +483,14 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel updateSourceBuffers(); + std::shared_ptr digitalIO = std::static_pointer_cast(getDevice(OnixDeviceType::DIGITALIO)); + if (devicesFound) { for (const auto& source : sources) { if (!source->isEnabled()) continue; - // create device info object if (source->type == OnixDeviceType::NEUROPIXELS_1) { DeviceInfo::Settings deviceSettings{ @@ -386,7 +503,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel deviceInfos->add(new DeviceInfo(deviceSettings)); - addIndividualStreams(source->streams, dataStreams, deviceInfos, continuousChannels); + addIndividualStreams(source->streamInfos, dataStreams, deviceInfos, continuousChannels); } else if (source->type == OnixDeviceType::BNO) { @@ -404,10 +521,10 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel "Bno055", "Continuous data from a Bno055 9-axis IMU", "onix-bno055.data", - source->streams[0].getSampleRate() + source->streamInfos[0].getSampleRate() }; - addCombinedStreams(dataStreamSettings, source->streams, dataStreams, deviceInfos, continuousChannels); + addCombinedStreams(dataStreamSettings, source->streamInfos, dataStreams, deviceInfos, continuousChannels); } else if (source->type == OnixDeviceType::NEUROPIXELS_2) { @@ -421,7 +538,58 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel deviceInfos->add(new DeviceInfo(deviceSettings)); - addIndividualStreams(source->streams, dataStreams, deviceInfos, continuousChannels); + addIndividualStreams(source->streamInfos, dataStreams, deviceInfos, continuousChannels); + } + else if (source->type == OnixDeviceType::MEMORYMONITOR) + { + DeviceInfo::Settings deviceSettings{ + source->getName(), + "Memory Monitor", + "memorymonitor", + "0000000", + "" + }; + + deviceInfos->add(new DeviceInfo(deviceSettings)); + + addIndividualStreams(source->streamInfos, dataStreams, deviceInfos, continuousChannels); + + if (digitalIO != nullptr && digitalIO->isEnabled()) + { + auto ttlChannelSettings = digitalIO->getEventChannelSettings(); + ttlChannelSettings.stream = dataStreams->getLast(); + eventChannels->add(new EventChannel(ttlChannelSettings)); + + std::static_pointer_cast(source)->setDigitalIO(digitalIO); + } + } + else if (source->type == OnixDeviceType::ANALOGIO) + { + DeviceInfo::Settings deviceSettings{ + source->getName(), + "Analog IO", + "analogio", + "0000000", + "" + }; + + deviceInfos->add(new DeviceInfo(deviceSettings)); + + addIndividualStreams(source->streamInfos, dataStreams, deviceInfos, continuousChannels); + } + else if (source->type == OnixDeviceType::HARPSYNCINPUT) + { + DeviceInfo::Settings deviceSettings{ + source->getName(), + "Harp Sync Input", + "harpsyncinput", + "0000000", + "" + }; + + deviceInfos->add(new DeviceInfo(deviceSettings)); + + addIndividualStreams(source->streamInfos, dataStreams, deviceInfos, continuousChannels); } } } @@ -445,7 +613,7 @@ void OnixSource::addCombinedStreams(DataStream::Settings dataStreamSettings, streamInfo.getChannelType(), streamInfo.getChannelPrefix() + streamInfo.getSuffixes()[chan], streamInfo.getDescription(), - streamInfo.getIdentifer(), + streamInfo.getIdentifier(), streamInfo.getBitVolts(), stream }; @@ -455,9 +623,9 @@ void OnixSource::addCombinedStreams(DataStream::Settings dataStreamSettings, } } -void OnixSource::addIndividualStreams(Array streamInfos, - OwnedArray* dataStreams, - OwnedArray* deviceInfos, +void OnixSource::addIndividualStreams(Array streamInfos, + OwnedArray* dataStreams, + OwnedArray* deviceInfos, OwnedArray* continuousChannels) { for (StreamInfo streamInfo : streamInfos) @@ -466,7 +634,7 @@ void OnixSource::addIndividualStreams(Array streamInfos, { streamInfo.getName(), streamInfo.getDescription(), - streamInfo.getIdentifer(), + streamInfo.getIdentifier(), streamInfo.getSampleRate() }; @@ -481,7 +649,7 @@ void OnixSource::addIndividualStreams(Array streamInfos, streamInfo.getChannelType(), streamInfo.getChannelPrefix() + streamInfo.getSuffixes()[chan], streamInfo.getDescription(), - streamInfo.getIdentifer(), + streamInfo.getIdentifier(), streamInfo.getBitVolts(), stream }; @@ -494,7 +662,7 @@ void OnixSource::addIndividualStreams(Array streamInfos, bool OnixSource::isDevicesReady() { auto tabMap = editor->createTabMapFromCanvas(); - auto sourceMap = createDeviceMap(sources); + auto sourceMap = createDeviceMap(true); return tabMap == sourceMap; } @@ -541,6 +709,8 @@ bool OnixSource::startAcquisition() for (const auto& source : sources) { + if (!source->isEnabled()) continue; + devices.emplace_back(source); } @@ -549,8 +719,6 @@ bool OnixSource::startAcquisition() for (const auto& source : devices) { - if (!source->isEnabled()) continue; - source->startAcquisition(); } @@ -577,10 +745,6 @@ bool OnixSource::stopAcquisition() { oni_size_t reg = 0; context->setOption(ONI_OPT_RUNNING, reg); - if (context->getLastResult() != ONI_ESUCCESS) return false; - - uint32_t val = 1; - context->setOption(ONI_OPT_RESET, val); } for (const auto& source : sources) diff --git a/Source/OnixSource.h b/Source/OnixSource.h index 5d4d13c..c5dccf3 100644 --- a/Source/OnixSource.h +++ b/Source/OnixSource.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -#ifndef __OnixSource_H__ -#define __OnixSource_H__ +#pragma once #include @@ -94,15 +92,19 @@ class OnixSource : public DataThread void disconnectDevices(bool updateStreamInfo = false); - OnixDeviceVector getDataSources() const; + OnixDeviceVector getDataSources(); - OnixDeviceVector getDataSourcesFromPort(PortName port) const; + OnixDeviceVector getDataSourcesFromPort(PortName port); - std::map createDeviceMap(OnixDeviceVector); + OnixDeviceVector getDataSourcesFromOffset(int offset); - std::map createDeviceMap(); + std::shared_ptr getDevice(OnixDeviceType type); - std::map getHeadstageMap(); + static std::map createDeviceMap(OnixDeviceVector, bool filterDevices = false); + + std::map createDeviceMap(bool filterDevices = false); + + std::map getHeadstageMap(); void updateSourceBuffers(); @@ -119,8 +121,8 @@ class OnixSource : public DataThread /** Available data sources */ OnixDeviceVector sources; - /** Available headstages */ - std::map headstages; + /** Available headstages, indexed by their offset value */ + std::map headstages; /** Pointer to the editor */ OnixSourceEditor* editor; @@ -143,5 +145,3 @@ class OnixSource : public DataThread JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OnixSource); }; - -#endif // __OnixSource_H__ diff --git a/Source/OnixSourceCanvas.cpp b/Source/OnixSourceCanvas.cpp index abce423..2db1685 100644 --- a/Source/OnixSourceCanvas.cpp +++ b/Source/OnixSourceCanvas.cpp @@ -1,23 +1,22 @@ /* ------------------------------------------------------------------- + ------------------------------------------------------------------ -This file is part of the Open Ephys GUI -Copyright(C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright(C) 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 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. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with this program.If not, see < http://www.gnu.org/licenses/>. + You should have received a copy of the GNU General Public License + along with this program.If not, see < http://www.gnu.org/licenses/>. */ @@ -55,6 +54,8 @@ OnixSourceCanvas::OnixSourceCanvas(GenericProcessor* processor_, OnixSourceEdito { topLevelTabComponent = std::make_unique(editor, true); addAndMakeVisible(topLevelTabComponent.get()); + + addHub(BREAKOUT_BOARD_NAME, 0); } CustomTabComponent* OnixSourceCanvas::addTopLevelTab(String tabName, int index) @@ -74,20 +75,29 @@ Parameter* OnixSourceCanvas::getSourceParameter(String name) return source->getParameter(name); } -void OnixSourceCanvas::addHeadstage(String headstage, PortName port) +void OnixSourceCanvas::addHub(String hubName, int offset) { - int offset = PortController::getPortOffset(port); CustomTabComponent* tab = nullptr; OnixDeviceVector devices; + PortName port = PortController::getPortFromIndex(offset); - if (headstage == NEUROPIXELSV1F_HEADSTAGE_NAME) + if (hubName == NEUROPIXELSV1F_HEADSTAGE_NAME) { - tab = addTopLevelTab(getTopLevelTabName(port, headstage), (int)port - 1); + tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port); devices.emplace_back(std::make_shared("Probe-A", offset, nullptr)); devices.emplace_back(std::make_shared("Probe-B", offset + 1, nullptr)); devices.emplace_back(std::make_shared("BNO055", offset + 2, nullptr)); } + else if (hubName == BREAKOUT_BOARD_NAME) + { + tab = addTopLevelTab(hubName, 0); + + devices.emplace_back(std::make_shared("Output Clock", 5, nullptr)); + devices.emplace_back(std::make_shared("Analog IO", 6, nullptr)); + devices.emplace_back(std::make_shared("Digital IO", 7, nullptr)); + devices.emplace_back(std::make_shared("Harp Sync Input", 12, nullptr)); + } if (tab != nullptr && devices.size() > 0) { @@ -111,6 +121,26 @@ void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* tab, OnixDeviceVec auto bno055Interface = std::make_shared(std::static_pointer_cast(device), editor, this); addInterfaceToTab(getDeviceTabName(device), tab, bno055Interface); } + else if (device->type == OnixDeviceType::OUTPUTCLOCK) + { + auto outputClockInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(getDeviceTabName(device), tab, outputClockInterface); + } + else if (device->type == OnixDeviceType::HARPSYNCINPUT) + { + auto harpSyncInputInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(getDeviceTabName(device), tab, harpSyncInputInterface); + } + else if (device->type == OnixDeviceType::ANALOGIO) + { + auto analogIOInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(getDeviceTabName(device), tab, analogIOInterface); + } + else if (device->type == OnixDeviceType::DIGITALIO) + { + auto digitalIOInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(getDeviceTabName(device), tab, digitalIOInterface); + } } } @@ -134,18 +164,40 @@ void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptrgetName() + " to an open tab."); return; } + if (ind == -1) + { + if (device->type != OnixDeviceType::MEMORYMONITOR && device->type != OnixDeviceType::HEARTBEAT) + LOGD("Unable to match " + device->getName() + " to an open tab."); + + return; + } if (device->type == OnixDeviceType::NEUROPIXELS_1) { - // NB: Neuropixels-specific settings need to be updated auto npx1Found = std::static_pointer_cast(device); auto npx1Selected = std::static_pointer_cast(settingsInterfaces[ind]->device); npx1Found->setSettings(npx1Selected->settings.get()); npx1Found->adcCalibrationFilePath = npx1Selected->adcCalibrationFilePath; npx1Found->gainCalibrationFilePath = npx1Selected->gainCalibrationFilePath; } - // TODO: Add more devices, since they will have device-specific settings to be updated + else if (device->type == OnixDeviceType::OUTPUTCLOCK) + { + auto outputClockFound = std::static_pointer_cast(device); + auto outputClockSelected = std::static_pointer_cast(settingsInterfaces[ind]->device); + outputClockFound->setDelay(outputClockSelected->getDelay()); + outputClockFound->setDutyCycle(outputClockSelected->getDutyCycle()); + outputClockFound->setFrequencyHz(outputClockSelected->getFrequencyHz()); + outputClockFound->setGateRun(outputClockSelected->getGateRun()); + } + else if (device->type == OnixDeviceType::ANALOGIO) + { + auto analogIOFound = std::static_pointer_cast(device); + auto analogIOSelected = std::static_pointer_cast(settingsInterfaces[ind]->device); + for (int i = 0; i < analogIOFound->getNumChannels(); i++) + { + analogIOFound->setChannelDirection(i, analogIOSelected->getChannelDirection(i)); + } + } device->setEnabled(settingsInterfaces[ind]->device->isEnabled()); settingsInterfaces[ind]->device.reset(); @@ -181,14 +233,6 @@ CustomViewport* OnixSourceCanvas::createCustomViewport(SettingsInterface* settin return new CustomViewport(settingsInterface, bounds.getWidth(), bounds.getHeight()); } -OnixSourceCanvas::~OnixSourceCanvas() -{ -} - -void OnixSourceCanvas::paint(Graphics& g) -{ -} - void OnixSourceCanvas::refresh() { repaint(); @@ -225,7 +269,12 @@ void OnixSourceCanvas::removeTabs(PortName port) } if (tabExists) - topLevelTabComponent->removeTab((int)port - 1); + { + if (port == PortName::PortB && headstageTabs.size() == 1 && headstageTabs[0]->getName().contains(BREAKOUT_BOARD_NAME)) + topLevelTabComponent->removeTab((int)port - 1); // NB: If only one headstage is selected in the editor, the index needs to be corrected here. + else + topLevelTabComponent->removeTab((int)port); + } } void OnixSourceCanvas::removeAllTabs() @@ -248,11 +297,11 @@ std::map OnixSourceCanvas::createSelectedMap(std::vectorgetHeadstageSelected(port); + String selectedHeadstage = editor->getHeadstageSelected(offset); - String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(port) + ", but was not discovered there.\n\n"; + String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(offset) + ", but was not discovered there.\n\n"; msg += "Select one of the options below to continue:\n"; msg += " [Keep Current] to keep " + selectedHeadstage + " selected.\n"; msg += " [Remove] to remove " + selectedHeadstage + ".\n - Note: this will delete any settings that were modified."; @@ -269,7 +318,7 @@ void OnixSourceCanvas::askKeepRemove(PortName port) switch (result) { case 0: // Remove - removeTabs(port); + removeTabs(PortController::getPortFromIndex(offset)); break; case 1: // Keep Current break; @@ -278,12 +327,12 @@ void OnixSourceCanvas::askKeepRemove(PortName port) } } -void OnixSourceCanvas::askKeepUpdate(PortName port, String foundHeadstage, OnixDeviceVector devices) +void OnixSourceCanvas::askKeepUpdate(int offset, String foundHeadstage, OnixDeviceVector devices) { - String selectedHeadstage = editor->getHeadstageSelected(port); + String selectedHeadstage = editor->getHeadstageSelected(offset); - String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(port) + ". "; - msg += "However, headstage " + foundHeadstage + " was found on " + PortController::getPortName(port) + ". \n\n"; + String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(offset) + ". "; + msg += "However, headstage " + foundHeadstage + " was found on " + PortController::getPortName(offset) + ". \n\n"; msg += "Select one of the options below to continue:\n"; msg += " [Keep Current] to keep " + selectedHeadstage + " selected.\n"; msg += " [Update] to change the selected headstage to " + foundHeadstage + ".\n - Note: this will delete any settings that were modified."; @@ -300,13 +349,14 @@ void OnixSourceCanvas::askKeepUpdate(PortName port, String foundHeadstage, OnixD switch (result) { case 0: // Update + { + PortName port = PortController::getPortFromIndex(offset); removeTabs(port); - { - CustomTabComponent* tab = addTopLevelTab(getTopLevelTabName(port, foundHeadstage), (int)port); - populateSourceTabs(tab, devices); - } - break; + CustomTabComponent* tab = addTopLevelTab(getTopLevelTabName(port, foundHeadstage), (int)port); + populateSourceTabs(tab, devices); + } + break; case 1: // Keep Current break; default: @@ -317,7 +367,7 @@ void OnixSourceCanvas::askKeepUpdate(PortName port, String foundHeadstage, OnixD void OnixSourceCanvas::refreshTabs() { auto selectedMap = createSelectedMap(settingsInterfaces); - auto foundMap = source->createDeviceMap(); + auto foundMap = source->createDeviceMap(true); if (selectedMap != foundMap) { @@ -326,8 +376,8 @@ void OnixSourceCanvas::refreshTabs() for (const auto& [key, _] : selectedMap) { selectedIndices.emplace_back(key); } for (const auto& [key, _] : foundMap) { foundIndices.emplace_back(key); } - auto selectedPorts = PortController::getUniquePortsFromIndices(selectedIndices); - auto foundPorts = PortController::getUniquePortsFromIndices(foundIndices); + auto selectedOffsets = PortController::getUniqueOffsetsFromIndices(selectedIndices); + auto foundOffsets = PortController::getUniqueOffsetsFromIndices(foundIndices); if (foundIndices.size() == 0) // NB: No devices found, inform the user if they were expecting to find something { @@ -336,24 +386,24 @@ void OnixSourceCanvas::refreshTabs() AlertWindow::showMessageBox( MessageBoxIconType::WarningIcon, "No Headstages Found", - "No headstages were found when connecting. Double check that the correct headstage is selected. " + - String("If the correct headstage is selected, try pressing disconnect / connect again.\n\n") + - String("If the port voltage is manually set, try clearing the value and letting the automated voltage discovery algorithm run.") + "No headstages were found when connecting. Double check that the correct headstage is selected. " + + String("If the correct headstage is selected, try pressing disconnect / connect again.\n\n") + + String("If the port voltage is manually set, try clearing the value and letting the automated voltage discovery algorithm run.") ); } } else if (selectedIndices.size() == 0) // NB: No headstages selected, add all found headstages { - for (auto& [port, headstageName] : source->getHeadstageMap()) + for (auto& [offset, headstageName] : source->getHeadstageMap()) { - addHeadstage(headstageName, port); + addHub(headstageName, offset); } } - else if (selectedPorts.size() == foundPorts.size()) // NB: Same number of ports selected and found + else if (selectedOffsets.size() == foundOffsets.size()) // NB: Same number of ports selected and found { auto headstages = source->getHeadstageMap(); - if (selectedPorts.size() == 1) + if (selectedOffsets.size() == 1) { if (headstages.size() != 1) { @@ -361,24 +411,24 @@ void OnixSourceCanvas::refreshTabs() return; } - if (selectedPorts[0] == foundPorts[0]) // NB: Selected headstage is different from the found headstage on the same port + if (selectedOffsets[0] == foundOffsets[0]) // NB: Selected headstage is different from the found headstage on the same port { - askKeepUpdate(selectedPorts[0], headstages[foundPorts[0]], source->getDataSources()); + askKeepUpdate(selectedOffsets[0], headstages[foundOffsets[0]], source->getDataSources()); } else // NB: Selected headstage on one port is not found, and the found headstage is not selected on the other port { - askKeepRemove(selectedPorts[0]); + askKeepRemove(selectedOffsets[0]); - addHeadstage(headstages[foundPorts[0]], foundPorts[0]); + addHub(headstages[foundOffsets[0]], foundOffsets[0]); } } else // NB: Two headstages are selected on different ports, and at least one of those headstages does not match the found headstages { - for (auto port : foundPorts) + for (auto offset : foundOffsets) { - if (headstages[port] != editor->getHeadstageSelected(port)) + if (headstages[offset] != editor->getHeadstageSelected(offset)) { - askKeepUpdate(port, headstages[port], source->getDataSourcesFromPort(port)); + askKeepUpdate(offset, headstages[offset], source->getDataSourcesFromOffset(offset)); } } } @@ -387,37 +437,37 @@ void OnixSourceCanvas::refreshTabs() { auto headstages = source->getHeadstageMap(); - if (selectedPorts.size() > foundPorts.size()) // NB: More headstages selected than found + if (selectedOffsets.size() > foundOffsets.size()) // NB: More headstages selected than found { - for (auto port : selectedPorts) + for (auto offset : selectedOffsets) { - if (port == foundPorts[0]) + if (offset == foundOffsets[0]) { - if (headstages[port] != editor->getHeadstageSelected(port)) + if (headstages[offset] != editor->getHeadstageSelected(offset)) { - askKeepUpdate(port, headstages[port], source->getDataSourcesFromPort(port)); + askKeepUpdate(offset, headstages[offset], source->getDataSourcesFromOffset(offset)); } } else { - askKeepRemove(port); + askKeepRemove(offset); } } } else // NB: More headstages found than selected { - for (auto port : foundPorts) + for (auto offset : foundOffsets) { - if (port == selectedPorts[0]) + if (offset == selectedOffsets[0]) { - if (headstages[port] != editor->getHeadstageSelected(port)) + if (headstages[offset] != editor->getHeadstageSelected(offset)) { - askKeepUpdate(port, headstages[port], source->getDataSourcesFromPort(port)); + askKeepUpdate(offset, headstages[offset], source->getDataSourcesFromOffset(offset)); } } else { - addHeadstage(headstages[port], port); + addHub(headstages[offset], offset); } } } diff --git a/Source/OnixSourceCanvas.h b/Source/OnixSourceCanvas.h index ba8b059..c7bbced 100644 --- a/Source/OnixSourceCanvas.h +++ b/Source/OnixSourceCanvas.h @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2020 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -21,8 +20,7 @@ */ -# ifndef __ONIXSOURCECANVAS_H__ -# define __ONIXSOURCECANVAS_H__ +#pragma once #include @@ -31,6 +29,8 @@ #include "OnixSourceEditor.h" #include "OnixSource.h" +class OnixSource; + /** TabBarButton with custom appearance @@ -83,12 +83,6 @@ class OnixSourceCanvas : public Visualizer /** Constructor */ OnixSourceCanvas(GenericProcessor*, OnixSourceEditor*, OnixSource*); - /** Destructor */ - ~OnixSourceCanvas(); - - /** Fills background */ - void paint(Graphics& g); - /** Renders the Visualizer on each animation callback cycle */ void refresh() override; @@ -120,8 +114,8 @@ class OnixSourceCanvas : public Visualizer /** Stops animation of sub-interfaces */ void stopAcquisition(); - /** Add the headstage and all of its devices to the canvas */ - void addHeadstage(String headstage, PortName port); + /** Add the hub and all of its devices to the canvas */ + void addHub(String, int); /** Called when the basestation is created or refreshed */ void populateSourceTabs(CustomTabComponent*, OnixDeviceVector); @@ -174,15 +168,13 @@ class OnixSourceCanvas : public Visualizer Create an alert window that asks whether to keep the selected headstage on the given port, or to remove it since the hardware was not found */ - void askKeepRemove(PortName port); + void askKeepRemove(int offset); /** Create an alert window that asks whether to keep the selected headstage on the given port, or to update to the headstage that was found */ - void askKeepUpdate(PortName port, String foundHeadstage, OnixDeviceVector devices); + void askKeepUpdate(int offset, String foundHeadstage, OnixDeviceVector devices); JUCE_LEAK_DETECTOR(OnixSourceCanvas); }; - -# endif // __ONIXSOURCECANVAS_H__ diff --git a/Source/OnixSourceEditor.cpp b/Source/OnixSourceEditor.cpp index 523ab57..af9b6c2 100644 --- a/Source/OnixSourceEditor.cpp +++ b/Source/OnixSourceEditor.cpp @@ -1,8 +1,7 @@ /* ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2023 Allen Institute for Brain Science and Open Ephys + Copyright (C) Open Ephys ------------------------------------------------------------------ @@ -25,17 +24,22 @@ #include "OnixSource.h" OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* source_) - : VisualizerEditor(parentNode, "Onix Source", 200), source(source_) + : VisualizerEditor(parentNode, "Onix Source", 220), source(source_) { canvas = nullptr; FontOptions fontOptionSmall = FontOptions("Fira Code", "Regular", 12.0f); FontOptions fontOptionTitle = FontOptions("Fira Code", "Bold", 15.0f); + memoryUsage = std::make_unique(parentNode); + memoryUsage->setBounds(10, 30, 15, 95); + memoryUsage->setTooltip("Monitors the percent of the hardware memory buffer used."); + addAndMakeVisible(memoryUsage.get()); + if (source->isContextInitialized()) { portLabelA = std::make_unique