diff --git a/Source/Devices/AnalogIO.cpp b/Source/Devices/AnalogIO.cpp index 8343bdc..03c3215 100644 --- a/Source/Devices/AnalogIO.cpp +++ b/Source/Devices/AnalogIO.cpp @@ -22,11 +22,13 @@ #include "AnalogIO.h" -AnalogIO::AnalogIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::ANALOGIO, deviceIdx_, oni_ctx) +using namespace OnixSourcePlugin; + +AnalogIO::AnalogIO(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, hubName, AnalogIO::getDeviceType(), deviceIdx_, oni_ctx) { StreamInfo analogInputStream = StreamInfo( - OnixDevice::createStreamName({ getHeadstageName(), name, "AnalogInput" }), + OnixDevice::createStreamName({ getHubName(), name, "AnalogInput" }), "Analog Input data", getStreamIdentifier(), getNumChannels(), @@ -51,10 +53,16 @@ AnalogIO::AnalogIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr< dataType = AnalogIODataType::Volts; } -int AnalogIO::configureDevice() +OnixDeviceType AnalogIO::getDeviceType() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + return OnixDeviceType::ANALOGIO; +} +int AnalogIO::configureDevice() +{ + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); + return deviceContext->writeRegister(deviceIdx, (uint32_t)AnalogIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); } diff --git a/Source/Devices/AnalogIO.h b/Source/Devices/AnalogIO.h index e21eacb..819e898 100644 --- a/Source/Devices/AnalogIO.h +++ b/Source/Devices/AnalogIO.h @@ -24,168 +24,173 @@ #include "../OnixDevice.h" -enum class AnalogIORegisters : uint32_t +namespace OnixSourcePlugin { - 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 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 AnalogIODataType : uint32_t -{ - S16 = 0, - Volts = 1 -}; + enum class AnalogIODirection : uint32_t + { + Input = 0, + Output = 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); + 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(std::string name, std::string hubName, 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; + /** 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; + /** Update the settings of the device */ + bool updateSettings() override; - /** Starts probe data streaming */ - void startAcquisition() override; + /** Starts probe data streaming */ + void startAcquisition() override; - /** Stops probe data streaming*/ - void stopAcquisition() 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; + /** 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 addFrame(oni_frame_t*) override; - void processFrames() override; + void processFrames() override; - AnalogIODirection getChannelDirection(int channelNumber) - { - if (channelNumber > numChannels || channelNumber < 0) + AnalogIODirection getChannelDirection(int channelNumber) { - LOGE("Channel number must be between 0 and " + String(channelNumber)); - return AnalogIODirection::Input; - } + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return AnalogIODirection::Input; + } - return channelDirection[channelNumber]; - } + return channelDirection[channelNumber]; + } - static String getChannelDirection(AnalogIODirection direction) - { - switch (direction) + static String getChannelDirection(AnalogIODirection direction) { - case AnalogIODirection::Input: - return "Input"; - case AnalogIODirection::Output: - return "Output"; - default: - return ""; + switch (direction) + { + case AnalogIODirection::Input: + return "Input"; + case AnalogIODirection::Output: + return "Output"; + default: + return ""; + } } - } - void setChannelDirection(int channelNumber, AnalogIODirection direction) - { - if (channelNumber > numChannels || channelNumber < 0) + void setChannelDirection(int channelNumber, AnalogIODirection direction) { - LOGE("Channel number must be between 0 and " + String(channelNumber)); - return; - } + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return; + } - channelDirection[channelNumber] = direction; - } + channelDirection[channelNumber] = direction; + } - AnalogIOVoltageRange getChannelVoltageRange(int channelNumber) - { - if (channelNumber > numChannels || channelNumber < 0) + AnalogIOVoltageRange getChannelVoltageRange(int channelNumber) { - LOGE("Channel number must be between 0 and " + String(channelNumber)); - return AnalogIOVoltageRange::FiveVolts; + if (channelNumber > numChannels || channelNumber < 0) + { + LOGE("Channel number must be between 0 and " + String(channelNumber)); + return AnalogIOVoltageRange::FiveVolts; + } + + return channelVoltageRange[channelNumber]; } - 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; + } - AnalogIODataType getDataType() const { return dataType; } + channelVoltageRange[channelNumber] = direction; + } - int getNumChannels() { return numChannels; } + AnalogIODataType getDataType() const { return dataType; } -private: + void setDataType(AnalogIODataType type) { dataType = type; } - DataBuffer* analogInputBuffer = nullptr; + int getNumChannels() { return numChannels; } - static const int AnalogIOFrequencyHz = 100000; + static OnixDeviceType getDeviceType(); - static const int numFrames = 25; - static const int framesToAverage = 4; // NB: Downsampling from 100 kHz to 25 kHz - static const int numChannels = 12; + private: - static const int numberOfDivisions = 1 << 16; - const int dacMidScale = 1 << 15; + DataBuffer* analogInputBuffer = nullptr; - std::array channelDirection; - std::array channelVoltageRange; + static const int AnalogIOFrequencyHz = 100000; - AnalogIODataType dataType = AnalogIODataType::Volts; + static const int numFrames = 25; + static const int framesToAverage = 4; // NB: Downsampling from 100 kHz to 25 kHz + static const int numChannels = 12; - Array frameArray; + static const int numberOfDivisions = 1 << 16; + const int dacMidScale = 1 << 15; - unsigned short currentFrame = 0; - unsigned short currentAverageFrame = 0; - int sampleNumber = 0; + std::array channelDirection; + std::array channelVoltageRange; - bool shouldAddToBuffer = false; + AnalogIODataType dataType = AnalogIODataType::Volts; - std::array analogInputSamples; + Array frameArray; - double timestamps[numFrames]; - int64 sampleNumbers[numFrames]; - uint64 eventCodes[numFrames]; + unsigned short currentFrame = 0; + unsigned short currentAverageFrame = 0; + int sampleNumber = 0; - std::array voltsPerDivision; + bool shouldAddToBuffer = false; - static float getVoltsPerDivision(AnalogIOVoltageRange voltageRange); + std::array analogInputSamples; - void setChannelVoltageRange(int channelNumber, AnalogIOVoltageRange direction) - { - if (channelNumber > numChannels || channelNumber < 0) - { - LOGE("Channel number must be between 0 and " + String(channelNumber)); - return; - } + double timestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; - channelVoltageRange[channelNumber] = direction; - } + std::array voltsPerDivision; - void setDataType(AnalogIODataType type) { dataType = type; } + static float getVoltsPerDivision(AnalogIOVoltageRange voltageRange); - JUCE_LEAK_DETECTOR(AnalogIO); -}; + JUCE_LEAK_DETECTOR(AnalogIO); + }; +} diff --git a/Source/Devices/Bno055.cpp b/Source/Devices/Bno055.cpp index b387004..6bae9b7 100644 --- a/Source/Devices/Bno055.cpp +++ b/Source/Devices/Bno055.cpp @@ -22,14 +22,16 @@ #include "Bno055.h" -Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) - : OnixDevice(name, headstageName, OnixDeviceType::BNO, deviceIdx_, ctx) +using namespace OnixSourcePlugin; + +Bno055::Bno055(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) + : OnixDevice(name, hubName, Bno055::getDeviceType(), deviceIdx_, ctx) { auto streamIdentifier = getStreamIdentifier(); - String port = PortController::getPortName(PortController::getPortFromIndex(deviceIdx)); + std::string port = getPortNameFromIndex(deviceIdx); StreamInfo eulerAngleStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Euler" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Euler" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Euler angle", streamIdentifier, 3, @@ -45,7 +47,7 @@ Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_ streamInfos.add(eulerAngleStream); StreamInfo quaternionStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Quaternion" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Quaternion" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Quaternion", streamIdentifier, 4, @@ -61,7 +63,7 @@ Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_ streamInfos.add(quaternionStream); StreamInfo accelerationStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Acceleration" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Acceleration" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Acceleration", streamIdentifier, 3, @@ -77,9 +79,9 @@ Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_ streamInfos.add(accelerationStream); StreamInfo gravityStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Gravity" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Gravity" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Gravity", - streamIdentifier + ".gravity", + streamIdentifier, 3, sampleRate, "Grav", @@ -93,7 +95,7 @@ Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_ streamInfos.add(gravityStream); StreamInfo temperatureStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Temperature" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Temperature" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Temperature", streamIdentifier, 1, @@ -107,15 +109,35 @@ Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_ ); streamInfos.add(temperatureStream); - // TODO: Add calibration stream here? + StreamInfo calibrationStatusStream = StreamInfo( + OnixDevice::createStreamName({ port, getHubName(), getName(), "Calibration" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Calibration status", + streamIdentifier, + 4, + sampleRate, + "Cal", + ContinuousChannel::Type::AUX, + 1.0f, + "", + { "Mag", "Acc", "Gyr", "Sys" }, + "calibration", + { "magnetometer", "acceleration", "gyroscope", "system" } + ); + streamInfos.add(calibrationStatusStream); for (int i = 0; i < numFrames; i++) eventCodes[i] = 0; } +OnixDeviceType Bno055::getDeviceType() +{ + return OnixDeviceType::BNO; +} + int Bno055::configureDevice() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); return deviceContext->writeRegister(deviceIdx, (uint32_t)Bno055Registers::ENABLE, isEnabled() ? 1 : 0); } @@ -163,48 +185,52 @@ void Bno055::processFrames() const GenericScopedLock frameLock(frameArray.getLock()); oni_frame_t* frame = frameArray.removeAndReturn(0); - int16_t* dataPtr = (int16_t*)frame->data; + int16_t* dataPtr = ((int16_t*)frame->data) + 4; bnoTimestamps[currentFrame] = deviceContext->convertTimestampToSeconds(frame->time); - int dataOffset = 4; - - int channelOffset = 0; + size_t offset = 0; // Euler for (int i = 0; i < 3; i++) { - bnoSamples[currentFrame + channelOffset * numFrames] = float(*(dataPtr + dataOffset)) * eulerAngleScale; - dataOffset++; - channelOffset++; + bnoSamples[currentFrame + offset * numFrames] = float(*(dataPtr + offset)) * eulerAngleScale; + offset++; } // Quaternion for (int i = 0; i < 4; i++) { - bnoSamples[currentFrame + channelOffset * numFrames] = float(*(dataPtr + dataOffset)) * quaternionScale; - dataOffset++; - channelOffset++; + bnoSamples[currentFrame + offset * numFrames] = float(*(dataPtr + offset)) * quaternionScale; + offset++; } // Acceleration for (int i = 0; i < 3; i++) { - bnoSamples[currentFrame + channelOffset * numFrames] = float(*(dataPtr + dataOffset)) * accelerationScale; - dataOffset++; - channelOffset++; + bnoSamples[currentFrame + offset * numFrames] = float(*(dataPtr + offset)) * accelerationScale; + offset++; } // Gravity for (int i = 0; i < 3; i++) { - bnoSamples[currentFrame + channelOffset * numFrames] = float(*(dataPtr + dataOffset)) * accelerationScale; - dataOffset++; - channelOffset++; + bnoSamples[currentFrame + offset * numFrames] = float(*(dataPtr + offset)) * accelerationScale; + offset++; } // Temperature - bnoSamples[currentFrame + channelOffset * numFrames] = float(*((uint8_t*)(dataPtr + dataOffset))); + bnoSamples[currentFrame + offset * numFrames] = *((uint8_t*)(dataPtr + offset)); + + // Calibration + auto calibrationStatus = *((uint8_t*)(dataPtr + offset) + 1); + + constexpr uint8_t statusMask = 0b11; + + for (int i = 0; i < 4; i++) + { + bnoSamples[currentFrame + (offset + i + 1) * numFrames] = (calibrationStatus & (statusMask << (2 * i))) >> (2 * i); + } oni_destroy_frame(frame); @@ -221,7 +247,7 @@ void Bno055::processFrames() if (shouldAddToBuffer) { shouldAddToBuffer = false; - bnoBuffer->addToBuffer(bnoSamples, sampleNumbers, bnoTimestamps, eventCodes, numFrames); + bnoBuffer->addToBuffer(bnoSamples.data(), sampleNumbers, bnoTimestamps, eventCodes, numFrames); } } } diff --git a/Source/Devices/Bno055.h b/Source/Devices/Bno055.h index 7948475..e0d2632 100644 --- a/Source/Devices/Bno055.h +++ b/Source/Devices/Bno055.h @@ -23,65 +23,69 @@ #pragma once #include "../OnixDevice.h" -#include "PortController.h" -enum class Bno055Registers +namespace OnixSourcePlugin { - ENABLE = 0x00 -}; + enum class Bno055Registers + { + ENABLE = 0x00 + }; -/* - Configures and streams data from a BNO055 device -*/ -class Bno055 : public OnixDevice -{ -public: + /* + Configures and streams data from a BNO055 device + */ + class Bno055 : public OnixDevice + { + public: + + /** Constructor */ + Bno055(std::string name, std::string hubName, const oni_dev_idx_t, std::shared_ptr ctx); - /** Constructor */ - Bno055(String name, String headstageName, const oni_dev_idx_t, std::shared_ptr ctx); + int configureDevice() override; - int configureDevice() override; + /** Update the settings of the device */ + bool updateSettings() override; - /** Update the settings of the device */ - bool updateSettings() override; + /** Starts probe data streaming */ + void startAcquisition() override; - /** Starts probe data streaming */ - void startAcquisition() override; + /** Stops probe data streaming*/ + void stopAcquisition() override; - /** Stops probe data streaming*/ - void stopAcquisition() override; + void addFrame(oni_frame_t*) override; - void addFrame(oni_frame_t*) override; + void processFrames() override; - void processFrames() override; + void addSourceBuffers(OwnedArray& sourceBuffers) override; - void addSourceBuffers(OwnedArray& sourceBuffers) override; + static OnixDeviceType getDeviceType(); -private: + private: - DataBuffer* bnoBuffer; + DataBuffer* bnoBuffer; - const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB - const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB - const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB + const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB + const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB + const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB - static const int numFrames = 2; + static const int numFrames = 2; - Array frameArray; + Array frameArray; - bool shouldAddToBuffer = false; + bool shouldAddToBuffer = false; - static const int numberOfChannels = 3 + 3 + 4 + 3 + 1; - static constexpr float sampleRate = 100.0f; + static const int numberOfChannels = 3 + 3 + 4 + 3 + 1 + 4; + static constexpr float sampleRate = 100.0f; - float bnoSamples[numberOfChannels * numFrames]; + std::array bnoSamples; - double bnoTimestamps[numFrames]; - int64 sampleNumbers[numFrames]; - uint64 eventCodes[numFrames]; + double bnoTimestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; - unsigned short currentFrame = 0; - int sampleNumber = 0; + unsigned short currentFrame = 0; + int sampleNumber = 0; - JUCE_LEAK_DETECTOR(Bno055); -}; + JUCE_LEAK_DETECTOR(Bno055); + }; +} diff --git a/Source/Devices/DS90UB9x.h b/Source/Devices/DS90UB9x.h index 7e44719..040c7d6 100644 --- a/Source/Devices/DS90UB9x.h +++ b/Source/Devices/DS90UB9x.h @@ -1,22 +1,22 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - Copyright (C) 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ @@ -24,111 +24,114 @@ #include -static class DS90UB9x +namespace OnixSourcePlugin { -public: - // managed registers - static const uint32_t ENABLE = 0x8000; - static const uint32_t READSZ = 0x8001; - static const uint32_t TRIGGER = 0x8002; - static const uint32_t TRIGGEROFF = 0x8003; - static const uint32_t DATAGATE = 0x8004; - static const uint32_t SYNCBITS = 0x8005; - static const uint32_t MARK = 0x8006; - static const uint32_t MAGIC_MASK = 0x8007; - static const uint32_t MAGIC = 0x8008; - static const uint32_t MAGIC_WAIT = 0x8009; - static const uint32_t DATAMODE = 0x800A; - static const uint32_t DATALINES0 = 0x800B; - static const uint32_t DATALINES1 = 0x800C; - - // reserved registers - static const uint32_t GPIO_DIR = 0x8010; - static const uint32_t GPIO_VAL = 0x8011; - static const uint32_t LINKSTATUS = 0x8012; - static const uint32_t LASTI2CL = 0x8013; - static const uint32_t LASTI2CH = 0x8014; - - // unmanaged default serializer / deserializer I2C addresses - static const uint32_t DES_ADDR = 0x30; - static const uint32_t SER_ADDR = 0x58; - - enum class DS90UB9xTriggerMode : uint32_t - { - Continuous = 0, - HsyncEdgePositive = 0b0001, - HsyncEdgeNegative = 0b1001, - HsyncLevelPositive = 0b0101, - HsyncLevelNegative = 0b1101, - VsyncEdgePositive = 0b0011, - VsyncEdgeNegative = 0b1011, - VsyncLevelPositive = 0b0111, - VsyncLevelNegative = 0b1111, - }; - - enum class DS90UB9xDataGate : uint32_t - { - Disabled = 0, - HsyncPositive = 0b001, - HsyncNegative = 0b101, - VsyncPositive = 0b011, - VsyncNegative = 0b111, - }; - - enum class DS90UB9xMarkMode : uint32_t - { - Disabled = 0, - HsyncRising = 0b001, - HsyncFalling = 0b101, - VsyncRising = 0b011, - VsyncFalling = 0b111, - }; - - enum class DS90UB9xDeserializerI2CRegister : uint32_t - { - PortMode = 0x6D, - PortSel = 0x4C, - I2CConfig = 0x58, - GpioCtrl0 = 0x6E, - GpioCtrl1 = 0x6F, - - SerAlias = 0x5C, - - SlaveID1 = 0x5E, - SlaveID2 = 0x5F, - SlaveID3 = 0x60, - SlaveID4 = 0x61, - SlaveID5 = 0x62, - SlaveID6 = 0x63, - SlaveID7 = 0x64, - - SlaveAlias1 = 0x66, - SlaveAlias2 = 0x67, - SlaveAlias3 = 0x68, - SlaveAlias4 = 0x69, - SlaveAlias5 = 0x6A, - SlaveAlias6 = 0x6B, - SlaveAlias7 = 0x6C, - }; - - enum class DS90UB9xSerializerI2CRegister : uint32_t - { - GPIO10 = 0x0D, - GPIO32 = 0x0E, - SCLHIGH = 0x0A, - SCLLOW = 0x0B - }; - - enum class DS90UB9xMode : uint32_t - { - Raw12BitLowFrequency = 1, - Raw12BitHighFrequency = 2, - Raw10Bit = 3, - }; - - enum class DS90UB9xDirection : uint32_t - { - Input = 0, - Output = 1 - }; -}; + static class DS90UB9x + { + public: + // managed registers + static const uint32_t ENABLE = 0x8000; + static const uint32_t READSZ = 0x8001; + static const uint32_t TRIGGER = 0x8002; + static const uint32_t TRIGGEROFF = 0x8003; + static const uint32_t DATAGATE = 0x8004; + static const uint32_t SYNCBITS = 0x8005; + static const uint32_t MARK = 0x8006; + static const uint32_t MAGIC_MASK = 0x8007; + static const uint32_t MAGIC = 0x8008; + static const uint32_t MAGIC_WAIT = 0x8009; + static const uint32_t DATAMODE = 0x800A; + static const uint32_t DATALINES0 = 0x800B; + static const uint32_t DATALINES1 = 0x800C; + + // reserved registers + static const uint32_t GPIO_DIR = 0x8010; + static const uint32_t GPIO_VAL = 0x8011; + static const uint32_t LINKSTATUS = 0x8012; + static const uint32_t LASTI2CL = 0x8013; + static const uint32_t LASTI2CH = 0x8014; + + // unmanaged default serializer / deserializer I2C addresses + static const uint32_t DES_ADDR = 0x30; + static const uint32_t SER_ADDR = 0x58; + + enum class DS90UB9xTriggerMode : uint32_t + { + Continuous = 0, + HsyncEdgePositive = 0b0001, + HsyncEdgeNegative = 0b1001, + HsyncLevelPositive = 0b0101, + HsyncLevelNegative = 0b1101, + VsyncEdgePositive = 0b0011, + VsyncEdgeNegative = 0b1011, + VsyncLevelPositive = 0b0111, + VsyncLevelNegative = 0b1111, + }; + + enum class DS90UB9xDataGate : uint32_t + { + Disabled = 0, + HsyncPositive = 0b001, + HsyncNegative = 0b101, + VsyncPositive = 0b011, + VsyncNegative = 0b111, + }; + + enum class DS90UB9xMarkMode : uint32_t + { + Disabled = 0, + HsyncRising = 0b001, + HsyncFalling = 0b101, + VsyncRising = 0b011, + VsyncFalling = 0b111, + }; + + enum class DS90UB9xDeserializerI2CRegister : uint32_t + { + PortMode = 0x6D, + PortSel = 0x4C, + I2CConfig = 0x58, + GpioCtrl0 = 0x6E, + GpioCtrl1 = 0x6F, + + SerAlias = 0x5C, + + SlaveID1 = 0x5E, + SlaveID2 = 0x5F, + SlaveID3 = 0x60, + SlaveID4 = 0x61, + SlaveID5 = 0x62, + SlaveID6 = 0x63, + SlaveID7 = 0x64, + + SlaveAlias1 = 0x66, + SlaveAlias2 = 0x67, + SlaveAlias3 = 0x68, + SlaveAlias4 = 0x69, + SlaveAlias5 = 0x6A, + SlaveAlias6 = 0x6B, + SlaveAlias7 = 0x6C, + }; + + enum class DS90UB9xSerializerI2CRegister : uint32_t + { + GPIO10 = 0x0D, + GPIO32 = 0x0E, + SCLHIGH = 0x0A, + SCLLOW = 0x0B + }; + + enum class DS90UB9xMode : uint32_t + { + Raw12BitLowFrequency = 1, + Raw12BitHighFrequency = 2, + Raw10Bit = 3, + }; + + enum class DS90UB9xDirection : uint32_t + { + Input = 0, + Output = 1 + }; + }; +} diff --git a/Source/Devices/DeviceList.h b/Source/Devices/DeviceList.h index 7c7dfcb..6499a85 100644 --- a/Source/Devices/DeviceList.h +++ b/Source/Devices/DeviceList.h @@ -23,7 +23,7 @@ #include "Bno055.h" #include "DS90UB9x.h" #include "HeadStageEEPROM.h" -#include "Neuropixels_1.h" +#include "Neuropixels1f.h" #include "Neuropixels2e.h" #include "MemoryMonitor.h" #include "OutputClock.h" diff --git a/Source/Devices/DigitalIO.cpp b/Source/Devices/DigitalIO.cpp index df9f87e..34457fe 100644 --- a/Source/Devices/DigitalIO.cpp +++ b/Source/Devices/DigitalIO.cpp @@ -22,14 +22,22 @@ #include "DigitalIO.h" -DigitalIO::DigitalIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::DIGITALIO, deviceIdx_, oni_ctx) +using namespace OnixSourcePlugin; + +DigitalIO::DigitalIO(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, hubName, DigitalIO::getDeviceType(), deviceIdx_, oni_ctx) +{ +} + +OnixDeviceType DigitalIO::getDeviceType() { + return OnixDeviceType::DIGITALIO; } int DigitalIO::configureDevice() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); return deviceContext->writeRegister(deviceIdx, (uint32_t)DigitalIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); } @@ -57,7 +65,7 @@ EventChannel::Settings DigitalIO::getEventChannelSettings() // NB: The stream must be assigned before adding the channel EventChannel::Settings settings{ EventChannel::Type::TTL, - OnixDevice::createStreamName({getHeadstageName(), getName(), "Events"}), + OnixDevice::createStreamName({getHubName(), getName(), "Events"}), "Digital inputs and breakout button states coming from a DigitalIO device", getStreamIdentifier() + ".event.digital", nullptr, diff --git a/Source/Devices/DigitalIO.h b/Source/Devices/DigitalIO.h index 3e9d02c..57849f8 100644 --- a/Source/Devices/DigitalIO.h +++ b/Source/Devices/DigitalIO.h @@ -24,79 +24,84 @@ #include "../OnixDevice.h" -enum class DigitalIORegisters : uint32_t +namespace OnixSourcePlugin { - 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); -}; + 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(std::string name, std::string hubName, 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(); + + static OnixDeviceType getDeviceType(); + + 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 index d0a522f..804a25c 100644 --- a/Source/Devices/HarpSyncInput.cpp +++ b/Source/Devices/HarpSyncInput.cpp @@ -22,13 +22,15 @@ #include "HarpSyncInput.h" -HarpSyncInput::HarpSyncInput(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::HARPSYNCINPUT, deviceIdx_, oni_ctx) +using namespace OnixSourcePlugin; + +HarpSyncInput::HarpSyncInput(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, hubName, HarpSyncInput::getDeviceType(), deviceIdx_, oni_ctx) { setEnabled(false); StreamInfo harpTimeStream = StreamInfo( - OnixDevice::createStreamName({ getHeadstageName(), getName(), "HarpTime" }), + OnixDevice::createStreamName({ getHubName(), getName(), "HarpTime" }), "Harp clock time corresponding to the local acquisition ONIX clock count", getStreamIdentifier(), 1, @@ -46,16 +48,22 @@ HarpSyncInput::HarpSyncInput(String name, const oni_dev_idx_t deviceIdx_, std::s eventCodes[i] = 0; } +OnixDeviceType HarpSyncInput::getDeviceType() +{ + return OnixDeviceType::HARPSYNCINPUT; +} + int HarpSyncInput::configureDevice() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); return deviceContext->writeRegister(deviceIdx, (uint32_t)HarpSyncInputRegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); } bool HarpSyncInput::updateSettings() { - return deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HarpSyncInputRegisters::SOURCE, (oni_reg_val_t)HarpSyncSource::Breakout); + return deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HarpSyncInputRegisters::SOURCE, (oni_reg_val_t)HarpSyncSource::Breakout) == ONI_ESUCCESS; } void HarpSyncInput::startAcquisition() diff --git a/Source/Devices/HarpSyncInput.h b/Source/Devices/HarpSyncInput.h index cabdc2d..975ab4c 100644 --- a/Source/Devices/HarpSyncInput.h +++ b/Source/Devices/HarpSyncInput.h @@ -24,63 +24,68 @@ #include "../OnixDevice.h" -enum class HarpSyncInputRegisters : uint32_t +namespace OnixSourcePlugin { - ENABLE = 0, - SOURCE = 1 -}; + enum class HarpSyncInputRegisters : uint32_t + { + ENABLE = 0, + SOURCE = 1 + }; -enum class HarpSyncSource : uint32_t -{ - Breakout = 0, - ClockAdapter = 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 and streams data from a HarpSyncInput device on a Breakout Board + */ + class HarpSyncInput : public OnixDevice + { + public: + HarpSyncInput(std::string name, std::string hubName, 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; - /** 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; - /** Update the settings of the device */ - bool updateSettings() override; + /** Starts probe data streaming */ + void startAcquisition() override; - /** Starts probe data streaming */ - void startAcquisition() override; + /** Stops probe data streaming*/ + void stopAcquisition() 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; - /** 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 addFrame(oni_frame_t* frame) override; + void processFrames() override; - void processFrames() override; + static OnixDeviceType getDeviceType(); -private: + private: - DataBuffer* harpTimeBuffer; + DataBuffer* harpTimeBuffer; - static const int numFrames = 2; + static const int numFrames = 2; - Array frameArray; + Array frameArray; - unsigned short currentFrame = 0; - int sampleNumber = 0; + unsigned short currentFrame = 0; + int sampleNumber = 0; - bool shouldAddToBuffer = false; + bool shouldAddToBuffer = false; - float harpTimeSamples[numFrames]; + float harpTimeSamples[numFrames]; - double timestamps[numFrames]; - int64 sampleNumbers[numFrames]; - uint64 eventCodes[numFrames]; + double timestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; - JUCE_LEAK_DETECTOR(HarpSyncInput); -}; + JUCE_LEAK_DETECTOR(HarpSyncInput); + }; +} diff --git a/Source/Devices/HeadStageEEPROM.cpp b/Source/Devices/HeadStageEEPROM.cpp index 7d28807..f6ec487 100644 --- a/Source/Devices/HeadStageEEPROM.cpp +++ b/Source/Devices/HeadStageEEPROM.cpp @@ -24,6 +24,8 @@ #include "DS90UB9x.h" #include +using namespace OnixSourcePlugin; + HeadStageEEPROM::HeadStageEEPROM(const oni_dev_idx_t dev_id, std::shared_ptr ctx) : I2CRegisterContext(HeadStageEEPROM::EEPROM_ADDRESS, dev_id, ctx) { diff --git a/Source/Devices/HeadStageEEPROM.h b/Source/Devices/HeadStageEEPROM.h index c211a0c..0c93714 100644 --- a/Source/Devices/HeadStageEEPROM.h +++ b/Source/Devices/HeadStageEEPROM.h @@ -28,18 +28,20 @@ #include #include -class HeadStageEEPROM : - public I2CRegisterContext +namespace OnixSourcePlugin { -public: - HeadStageEEPROM(const oni_dev_idx_t, std::shared_ptr); + class HeadStageEEPROM : + public I2CRegisterContext + { + public: + HeadStageEEPROM(const oni_dev_idx_t, std::shared_ptr); - uint32_t GetHeadStageID(); + uint32_t GetHeadStageID(); -private: - static const uint32_t EEPROM_ADDRESS = 0x51; - static const uint32_t DEVID_START_ADDR = 18; - - JUCE_LEAK_DETECTOR(HeadStageEEPROM); -}; + private: + static const uint32_t EEPROM_ADDRESS = 0x51; + static const uint32_t DEVID_START_ADDR = 18; + JUCE_LEAK_DETECTOR(HeadStageEEPROM); + }; +} diff --git a/Source/Devices/Heartbeat.cpp b/Source/Devices/Heartbeat.cpp index 664ab26..6fe8234 100644 --- a/Source/Devices/Heartbeat.cpp +++ b/Source/Devices/Heartbeat.cpp @@ -22,16 +22,24 @@ #include "Heartbeat.h" -Heartbeat::Heartbeat(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::HEARTBEAT, deviceIdx_, oni_ctx) +using namespace OnixSourcePlugin; + +Heartbeat::Heartbeat(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, hubName, Heartbeat::getDeviceType(), deviceIdx_, oni_ctx) +{ +} + +OnixDeviceType Heartbeat::getDeviceType() { + return OnixDeviceType::HEARTBEAT; } int Heartbeat::configureDevice() { - setEnabled(true); + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + setEnabled(true); return deviceContext->writeRegister(deviceIdx, (uint32_t)HeartbeatRegisters::ENABLE, 1); } diff --git a/Source/Devices/Heartbeat.h b/Source/Devices/Heartbeat.h index 7819de3..ba45839 100644 --- a/Source/Devices/Heartbeat.h +++ b/Source/Devices/Heartbeat.h @@ -24,43 +24,48 @@ #include "../OnixDevice.h" -enum class HeartbeatRegisters : uint32_t +namespace OnixSourcePlugin { - ENABLE = 0, - CLK_DIV = 1, - CLK_HZ = 2 -}; + enum class HeartbeatRegisters : uint32_t + { + ENABLE = 0, + CLK_DIV = 1, + CLK_HZ = 2 + }; -/* - Configures and streams data from a Heartbeat device on a Breakout Board -*/ -class Heartbeat : public OnixDevice -{ -public: - Heartbeat(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); + /* + Configures a Heartbeat device on a Breakout Board + */ + class Heartbeat : public OnixDevice + { + public: + Heartbeat(std::string name, std::string hubName, 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; - /** 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; - /** Update the settings of the device */ - bool updateSettings() override; + /** Starts probe data streaming */ + void startAcquisition() override {}; - /** Starts probe data streaming */ - void startAcquisition() override {}; + /** Stops probe data streaming*/ + void stopAcquisition() 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 {}; - /** 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 addFrame(oni_frame_t* frame) override { oni_destroy_frame(frame); } + void processFrames() override {}; - void processFrames() override {}; + static OnixDeviceType getDeviceType(); -private: + private: - const uint32_t beatsPerSecond = 100; + const uint32_t beatsPerSecond = 100; - JUCE_LEAK_DETECTOR(Heartbeat); -}; + JUCE_LEAK_DETECTOR(Heartbeat); + }; +} diff --git a/Source/Devices/MemoryMonitor.cpp b/Source/Devices/MemoryMonitor.cpp index 6c40c17..0a8a7e6 100644 --- a/Source/Devices/MemoryMonitor.cpp +++ b/Source/Devices/MemoryMonitor.cpp @@ -23,6 +23,8 @@ #include "MemoryMonitor.h" #include "DigitalIO.h" +using namespace OnixSourcePlugin; + MemoryMonitorUsage::MemoryMonitorUsage(GenericProcessor* p) : LevelMonitor(p) { @@ -55,11 +57,11 @@ void MemoryMonitorUsage::stopAcquisition() repaint(); } -MemoryMonitor::MemoryMonitor(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::MEMORYMONITOR, deviceIdx_, oni_ctx) +MemoryMonitor::MemoryMonitor(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, hubName, MemoryMonitor::getDeviceType(), deviceIdx_, oni_ctx) { StreamInfo percentUsedStream = StreamInfo( - OnixDevice::createStreamName({ getHeadstageName(), getName(), "PercentUsed" }), + OnixDevice::createStreamName({ getHubName(), getName(), "PercentUsed" }), "Percent of available memory that is currently used", getStreamIdentifier(), 1, @@ -77,17 +79,27 @@ MemoryMonitor::MemoryMonitor(String name, const oni_dev_idx_t deviceIdx_, std::s eventCodes[i] = 0; } +OnixDeviceType MemoryMonitor::getDeviceType() +{ + return OnixDeviceType::MEMORYMONITOR; +} + int MemoryMonitor::configureDevice() { - setEnabled(true); + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + setEnabled(true); int rc = deviceContext->writeRegister(deviceIdx, (uint32_t)MemoryMonitorRegisters::ENABLE, 1); - if (rc != ONI_ESUCCESS) return rc; + if (rc != ONI_ESUCCESS) + throw error_str("Unable to enable " + getName()); rc = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::TOTAL_MEM, &totalMemory); + if (rc != ONI_ESUCCESS) + throw error_str("Unable to find the total memory used for " + getName()); + return rc; } diff --git a/Source/Devices/MemoryMonitor.h b/Source/Devices/MemoryMonitor.h index add5cab..04281f4 100644 --- a/Source/Devices/MemoryMonitor.h +++ b/Source/Devices/MemoryMonitor.h @@ -22,109 +22,115 @@ #pragma once +#include #include "../OnixDevice.h" -class DigitalIO; - -enum class MemoryMonitorRegisters : uint32_t +namespace OnixSourcePlugin { - ENABLE = 0, - CLK_DIV = 1, - CLK_HZ = 2, - TOTAL_MEM = 3 -}; + class DigitalIO; -/* - 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); + enum class MemoryMonitorRegisters : uint32_t + { + ENABLE = 0, + CLK_DIV = 1, + CLK_HZ = 2, + TOTAL_MEM = 3 + }; - /** Configures the device so that it is ready to stream with default settings */ - int configureDevice() override; + /* + Configures and streams data from a MemoryMonitor device on a Breakout Board + */ + class MemoryMonitor : public OnixDevice + { + public: + MemoryMonitor(std::string name, std::string hubName, const oni_dev_idx_t, std::shared_ptr oni_ctx); - /** Update the settings of the device */ - bool updateSettings() override; + /** Configures the device so that it is ready to stream with default settings */ + int configureDevice() override; - /** Starts probe data streaming */ - void startAcquisition() override; + /** Update the settings of the device */ + bool updateSettings() override; - /** Stops probe data streaming*/ - void stopAcquisition() override; + /** Starts probe data streaming */ + void startAcquisition() override; - /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ - void addSourceBuffers(OwnedArray& sourceBuffers) override; + /** Stops probe data streaming*/ + void stopAcquisition() override; - void addFrame(oni_frame_t*) override; + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override; - void processFrames() override; + void addFrame(oni_frame_t*) override; - float getLastPercentUsedValue(); + void processFrames() override; - void setDigitalIO(std::shared_ptr digitalIO) { m_digitalIO = digitalIO; } + float getLastPercentUsedValue(); -private: + void setDigitalIO(std::shared_ptr digitalIO) { m_digitalIO = digitalIO; } - DataBuffer* percentUsedBuffer; + static OnixDeviceType getDeviceType(); - std::shared_ptr m_digitalIO; + private: - uint64_t prevWord = 0; + DataBuffer* percentUsedBuffer; - static const int numFrames = 10; + std::shared_ptr m_digitalIO; - Array frameArray; + uint64_t prevWord = 0; - unsigned short currentFrame = 0; - int sampleNumber = 0; + static const int numFrames = 10; - /** The frequency at which memory use is recorded in Hz. */ - const uint32_t samplesPerSecond = 100; + Array frameArray; - bool shouldAddToBuffer = false; + unsigned short currentFrame = 0; + int sampleNumber = 0; - float percentUsedSamples[numFrames]; - float bytesUsedSamples[numFrames]; + /** The frequency at which memory use is recorded in Hz. */ + const uint32_t samplesPerSecond = 100; - double timestamps[numFrames]; - int64_t sampleNumbers[numFrames]; - uint64_t eventCodes[numFrames]; + bool shouldAddToBuffer = false; - /** The total amount of memory, in 32-bit words, on the hardware that is available for data buffering*/ - uint32_t totalMemory; + float percentUsedSamples[numFrames]; + float bytesUsedSamples[numFrames]; - std::atomic lastPercentUsedValue = 0.0f; + double timestamps[numFrames]; + int64_t sampleNumbers[numFrames]; + uint64_t eventCodes[numFrames]; - JUCE_LEAK_DETECTOR(MemoryMonitor); -}; + /** The total amount of memory, in 32-bit words, on the hardware that is available for data buffering*/ + uint32_t totalMemory; -/* - Tracks the MemoryMonitor usage while data acquisition is running -*/ -class MemoryMonitorUsage : public LevelMonitor -{ -public: - MemoryMonitorUsage(GenericProcessor*); + 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 timerCallback() override; - void setMemoryMonitor(std::shared_ptr memoryMonitor); + void setMemoryMonitor(std::shared_ptr memoryMonitor); - void startAcquisition(); + void startAcquisition(); - void stopAcquisition(); + void stopAcquisition(); -private: + private: - std::shared_ptr device; + 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); + // 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; + const int TimerFrequencyHz = 10; - JUCE_LEAK_DETECTOR(MemoryMonitorUsage); -}; + JUCE_LEAK_DETECTOR(MemoryMonitorUsage); + }; +} diff --git a/Source/Devices/Neuropixels_1.cpp b/Source/Devices/Neuropixels1f.cpp similarity index 82% rename from Source/Devices/Neuropixels_1.cpp rename to Source/Devices/Neuropixels1f.cpp index 6162746..e049239 100644 --- a/Source/Devices/Neuropixels_1.cpp +++ b/Source/Devices/Neuropixels1f.cpp @@ -20,9 +20,11 @@ */ -#include "Neuropixels_1.h" +#include "Neuropixels1f.h" -BackgroundUpdaterWithProgressWindow::BackgroundUpdaterWithProgressWindow(Neuropixels_1* d) +using namespace OnixSourcePlugin; + +BackgroundUpdaterWithProgressWindow::BackgroundUpdaterWithProgressWindow(Neuropixels1f* d) : ThreadWithProgressWindow("Writing calibration files to Neuropixels Probe: " + d->getName(), true, false) { device = d; @@ -50,15 +52,13 @@ void BackgroundUpdaterWithProgressWindow::run() { result = false; - LOGE("Missing ADC or Gain calibration files" + device->getName()); - if (adcPath == "None") { - CoreServices::sendStatusMessage("Missing ADC calibration file for " + device->getName()); + Onix1::showWarningMessageBoxAsync("Missing File", "Missing ADC calibration file for probe " + std::to_string(device->getProbeSerialNumber())); } else if (gainPath == "None") { - CoreServices::sendStatusMessage("Missing Gain calibration file for " + device->getName()); + Onix1::showWarningMessageBoxAsync("Missing File", "Missing Gain calibration file for probe " + std::to_string(device->getProbeSerialNumber())); } return; @@ -71,15 +71,13 @@ void BackgroundUpdaterWithProgressWindow::run() { result = false; - LOGE("Invalid ADC or Gain calibration files for " + device->getName()); - if (!adcFile.existsAsFile()) { - CoreServices::sendStatusMessage("Invalid ADC calibration file for " + device->getName()); + Onix1::showWarningMessageBoxAsync("Invalid File", "Invalid ADC calibration file for probe " + std::to_string(device->getProbeSerialNumber())); } else if (!gainFile.existsAsFile()) { - CoreServices::sendStatusMessage("Invalid Gain calibration file for " + device->getName()); + Onix1::showWarningMessageBoxAsync("Invalid File", "Invalid gain calibration file for probe " + std::to_string(device->getProbeSerialNumber())); } return; @@ -98,9 +96,7 @@ void BackgroundUpdaterWithProgressWindow::run() { result = false; - LOGE("Gain calibration serial number (", gainSN, ") does not match probe serial number (", device->getProbeSerialNumber(), ")."); - - CoreServices::sendStatusMessage("Serial Number Mismatch: Gain calibration (" + String(gainSN) + ") does not match " + String(device->getProbeSerialNumber())); + Onix1::showWarningMessageBoxAsync("Invalid Serial Number", "Gain calibration file serial number (" + std::to_string(gainSN) + ") does not match probe serial number (" + std::to_string(device->getProbeSerialNumber()) + ")."); return; } @@ -129,21 +125,19 @@ void BackgroundUpdaterWithProgressWindow::run() { result = false; - LOGE("ADC calibration serial number (", adcSN, ") does not match probe serial number (", device->getProbeSerialNumber(), ")."); - - CoreServices::sendStatusMessage("Serial Number Mismatch: ADC calibration (" + String(adcSN) + ") does not match " + String(device->getProbeSerialNumber())); + Onix1::showWarningMessageBoxAsync("Invalid Serial Number", "ADC calibration file serial number (" + std::to_string(adcSN) + ") does not match probe serial number (" + std::to_string(device->getProbeSerialNumber()) + ")."); return; } - Array adcs; + Array adcs; for (int i = 1; i < adcFileLines.size() - 1; i++) { auto adcLine = StringArray::fromTokens(adcFileLines[i], breakCharacters, noQuote); adcs.add( - NeuropixelsV1Adc( + NeuropixelsV1fAdc( std::stoi(adcLine[1].toStdString()), std::stoi(adcLine[2].toStdString()), std::stoi(adcLine[3].toStdString()), @@ -168,16 +162,16 @@ void BackgroundUpdaterWithProgressWindow::run() result = true; } -Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : - OnixDevice(name, NEUROPIXELSV1F_HEADSTAGE_NAME, OnixDeviceType::NEUROPIXELS_1, deviceIdx_, ctx_), +Neuropixels1f::Neuropixels1f(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : + OnixDevice(name, hubName, Neuropixels1f::getDeviceType(), deviceIdx_, ctx_), I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_), INeuropixel(NeuropixelsV1fValues::numberOfSettings, NeuropixelsV1fValues::numberOfShanks) { - String port = PortController::getPortName(PortController::getPortFromIndex(deviceIdx)); + std::string port = getPortNameFromIndex(deviceIdx); auto streamIdentifier = getStreamIdentifier(); StreamInfo apStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "AP" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "AP" }), "Neuropixels 1.0 AP band data stream", streamIdentifier, numberOfChannels, @@ -192,7 +186,7 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s streamInfos.add(apStream); StreamInfo lfpStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "LFP" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "LFP" }), "Neuropixels 1.0 LFP band data stream", streamIdentifier, numberOfChannels, @@ -218,7 +212,7 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s } } -NeuropixelsGain Neuropixels_1::getGainEnum(int index) +NeuropixelsGain Neuropixels1f::getGainEnum(int index) { switch (index) { @@ -246,7 +240,7 @@ NeuropixelsGain Neuropixels_1::getGainEnum(int index) return NeuropixelsGain::Gain50; } -int Neuropixels_1::getGainValue(NeuropixelsGain gain) +int Neuropixels1f::getGainValue(NeuropixelsGain gain) { switch (gain) { @@ -272,32 +266,44 @@ int Neuropixels_1::getGainValue(NeuropixelsGain gain) } } -NeuropixelsV1Reference Neuropixels_1::getReference(int index) +NeuropixelsV1fReference Neuropixels1f::getReference(int index) { switch (index) { case 0: - return NeuropixelsV1Reference::External; + return NeuropixelsV1fReference::External; case 1: - return NeuropixelsV1Reference::Tip; + return NeuropixelsV1fReference::Tip; default: break; } - return NeuropixelsV1Reference::External; + return NeuropixelsV1fReference::External; +} + +OnixDeviceType Neuropixels1f::getDeviceType() +{ + return OnixDeviceType::NEUROPIXELSV1F; } -int Neuropixels_1::configureDevice() +int Neuropixels1f::configureDevice() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -5; + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); + + int rc = deviceContext->writeRegister(deviceIdx, ENABLE, isEnabled() ? 1 : 0); + if (rc != ONI_ESUCCESS) + throw error_str("Unable to enable " + getName()); - deviceContext->writeRegister(deviceIdx, ENABLE, isEnabled() ? 1 : 0); + if (!isEnabled()) + { + return ONI_ESUCCESS; + } // Get Probe SN uint32_t eepromOffset = 0; uint32_t i2cAddr = 0x50; int errorCode = 0; - int rc; for (int i = 0; i < 8; i++) { @@ -306,7 +312,11 @@ int Neuropixels_1::configureDevice() oni_reg_val_t reg_val; rc = deviceContext->readRegister(deviceIdx, reg_addr, ®_val); - if (rc != ONI_ESUCCESS) return -1; + if (rc != ONI_ESUCCESS) + { + LOGE(oni_error_str(rc)); + throw error_str("Could not communicate with " + getName() + " on " + getHubName() + ". Ensure that the flex connection is properly seated, or disable the device if it is not connected."); + } if (reg_val <= 0xFF) { @@ -316,38 +326,38 @@ int Neuropixels_1::configureDevice() LOGD("Probe SN: ", probeNumber); - if (!isEnabled()) - { - return 0; - } - // Enable device streaming rc = deviceContext->writeRegister(deviceIdx, 0x8000, 1); - if (rc != ONI_ESUCCESS) return -2; + if (rc != ONI_ESUCCESS) + throw error_str("Unable to activate streaming for device at address " + std::to_string(deviceIdx)); rc = WriteByte((uint32_t)NeuropixelsRegisters::CAL_MOD, (uint32_t)CalMode::CAL_OFF); - if (rc != ONI_ESUCCESS) return -3; + if (rc != ONI_ESUCCESS) + throw error_str("Error configuring device at address " + std::to_string(deviceIdx)); rc = WriteByte((uint32_t)NeuropixelsRegisters::SYNC, (uint32_t)0); - if (rc != ONI_ESUCCESS) return -3; + if (rc != ONI_ESUCCESS) + throw error_str("Error configuring device at address " + std::to_string(deviceIdx)); rc = WriteByte((uint32_t)NeuropixelsRegisters::REC_MOD, (uint32_t)RecMod::DIG_AND_CH_RESET); - if (rc != ONI_ESUCCESS) return -3; + if (rc != ONI_ESUCCESS) + throw error_str("Error configuring device at address " + std::to_string(deviceIdx)); rc = WriteByte((uint32_t)NeuropixelsRegisters::OP_MODE, (uint32_t)OpMode::RECORD); - if (rc != ONI_ESUCCESS) return -3; + if (rc != ONI_ESUCCESS) + throw error_str("Error configuring device at address " + std::to_string(deviceIdx)); return rc; } -bool Neuropixels_1::updateSettings() +bool Neuropixels1f::updateSettings() { BackgroundUpdaterWithProgressWindow updater = BackgroundUpdaterWithProgressWindow(this); return updater.updateSettings(); } -void Neuropixels_1::setSettings(ProbeSettings* settings_, int index) +void Neuropixels1f::setSettings(ProbeSettings* settings_, int index) { if (index >= settings.size()) { @@ -358,7 +368,7 @@ void Neuropixels_1::setSettings(ProbeSettingsupdateProbeSettings(settings_); } -std::vector Neuropixels_1::selectElectrodeConfiguration(String config) +std::vector Neuropixels1f::selectElectrodeConfiguration(String config) { std::vector selection; @@ -405,7 +415,7 @@ std::vector Neuropixels_1::selectElectrodeConfiguration(String config) return selection; } -void Neuropixels_1::startAcquisition() +void Neuropixels1f::startAcquisition() { apGain = getGainValue(getGainEnum(settings[0]->apGainIndex)); lfpGain = getGainValue(getGainEnum(settings[0]->lfpGainIndex)); @@ -436,7 +446,7 @@ void Neuropixels_1::startAcquisition() lfpSampleNumber = 0; } -void Neuropixels_1::stopAcquisition() +void Neuropixels1f::stopAcquisition() { WriteByte((uint32_t)NeuropixelsRegisters::REC_MOD, (uint32_t)RecMod::RESET_ALL); @@ -447,7 +457,7 @@ void Neuropixels_1::stopAcquisition() } } -void Neuropixels_1::addSourceBuffers(OwnedArray& sourceBuffers) +void Neuropixels1f::addSourceBuffers(OwnedArray& sourceBuffers) { for (StreamInfo streamInfo : streamInfos) { @@ -460,13 +470,13 @@ void Neuropixels_1::addSourceBuffers(OwnedArray& sourceBuffers) } } -void Neuropixels_1::addFrame(oni_frame_t* frame) +void Neuropixels1f::addFrame(oni_frame_t* frame) { const GenericScopedLock frameLock(frameArray.getLock()); frameArray.add(frame); } -void Neuropixels_1::processFrames() +void Neuropixels1f::processFrames() { const float apConversion = (1171.875 / apGain) * -1.0f; const float lfpConversion = (1171.875 / lfpGain) * -1.0f; @@ -539,7 +549,7 @@ void Neuropixels_1::processFrames() } } -void Neuropixels_1::updateApOffsets(std::array& samples, int64 sampleNumber) +void Neuropixels1f::updateApOffsets(std::array& samples, int64 sampleNumber) { if (sampleNumber > apSampleRate * secondsToSettle) { @@ -570,7 +580,7 @@ void Neuropixels_1::updateApOffsets(std::array& samples, in } } -void Neuropixels_1::updateLfpOffsets(std::array& samples, int64 sampleNumber) +void Neuropixels1f::updateLfpOffsets(std::array& samples, int64 sampleNumber) { if (sampleNumber > lfpSampleRate * secondsToSettle) { @@ -601,7 +611,7 @@ void Neuropixels_1::updateLfpOffsets(std::array& samples, } } -Neuropixels_1::ShankBitset Neuropixels_1::makeShankBits(NeuropixelsV1Reference reference, std::array channelMap) +Neuropixels1f::ShankBitset Neuropixels1f::makeShankBits(NeuropixelsV1fReference reference, std::array channelMap) { const int shankBitExt1 = 965; const int shankBitExt2 = 2; @@ -624,11 +634,11 @@ Neuropixels_1::ShankBitset Neuropixels_1::makeShankBits(NeuropixelsV1Reference r switch (reference) { - case NeuropixelsV1Reference::External: + case NeuropixelsV1fReference::External: shankBits[shankBitExt1] = true; shankBits[shankBitExt2] = true; break; - case NeuropixelsV1Reference::Tip: + case NeuropixelsV1fReference::Tip: shankBits[shankBitTip1] = true; shankBits[shankBitTip2] = true; break; @@ -644,7 +654,7 @@ Neuropixels_1::ShankBitset Neuropixels_1::makeShankBits(NeuropixelsV1Reference r return shankBits; } -Neuropixels_1::CongigBitsArray Neuropixels_1::makeConfigBits(NeuropixelsV1Reference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs) +Neuropixels1f::CongigBitsArray Neuropixels1f::makeConfigBits(NeuropixelsV1fReference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs) { const int BaseConfigurationConfigOffset = 576; @@ -724,7 +734,7 @@ Neuropixels_1::CongigBitsArray Neuropixels_1::makeConfigBits(NeuropixelsV1Refere return baseConfigs; } -void Neuropixels_1::writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection) +void Neuropixels1f::writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection) { auto shankBytes = toBitReversedBytes(shankBits); @@ -811,24 +821,34 @@ void Neuropixels_1::writeShiftRegisters(ShankBitset shankBits, CongigBitsArray c } } -void Neuropixels_1::defineMetadata(ProbeSettings* settings) +void Neuropixels1f::defineMetadata(ProbeSettings* settings) { settings->probeType = ProbeType::NPX_V1E; settings->probeMetadata.name = "Neuropixels 1.0f"; - Path path; - path.startNewSubPath(27, 31); - path.lineTo(27, 514); - path.lineTo(27 + 5, 522); - path.lineTo(27 + 10, 514); - path.lineTo(27 + 10, 31); - path.closeSubPath(); + std::vector> shankOutline{ + {27, 31}, + {27, 514}, + {27 + 5, 522}, + {27 + 10, 514}, + {27 + 10, 31} + }; + + std::vector> probeContour{ + {0, 155}, + {35, 0}, + {70, 155}, + {70, 9770}, + {0, 9770}, + {0, 155} + }; settings->probeMetadata.shank_count = 1; settings->probeMetadata.electrodes_per_shank = numberOfElectrodes; settings->probeMetadata.rows_per_shank = numberOfElectrodes / 2; settings->probeMetadata.columns_per_shank = 2; - settings->probeMetadata.shankOutline = path; + settings->probeMetadata.shankOutline = shankOutline; + settings->probeMetadata.probeContour = probeContour; settings->probeMetadata.num_adcs = 32; // NB: Is this right for 1.0e? settings->probeMetadata.adc_bits = 10; // NB: Is this right for 1.0e? diff --git a/Source/Devices/Neuropixels1f.h b/Source/Devices/Neuropixels1f.h new file mode 100644 index 0000000..8842545 --- /dev/null +++ b/Source/Devices/Neuropixels1f.h @@ -0,0 +1,299 @@ +/* + ------------------------------------------------------------------ + + 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 "../NeuropixelsComponents.h" +#include "../I2CRegisterContext.h" + +namespace OnixSourcePlugin +{ + enum class NeuropixelsRegisters : uint32_t + { + OP_MODE = 0x00, + REC_MOD = 0x01, + CAL_MOD = 0x02, + STATUS = 0X08, + SYNC = 0X09 + }; + + enum class CalMode : uint32_t + { + CAL_OFF = 0, + OSC_ACTIVE = 1 << 4, // 0 = external osc inactive, 1 = activate the external calibration oscillator + ADC_CAL = 1 << 5, // Enable ADC calibration + CH_CAL = 1 << 6, // Enable channel gain calibration + PIX_CAL = 1 << 7, // Enable pixel + channel gain calibration + + // Useful combinations + OSC_ACTIVE_AND_ADC_CAL = OSC_ACTIVE | ADC_CAL, + OSC_ACTIVE_AND_CH_CAL = OSC_ACTIVE | CH_CAL, + OSC_ACTIVE_AND_PIX_CAL = OSC_ACTIVE | PIX_CAL, + }; + + enum class OpMode : uint32_t + { + TEST = 1 << 3, // Enable Test mode + DIG_TEST = 1 << 4, // Enable Digital Test mode + CALIBRATE = 1 << 5, // Enable calibration mode + RECORD = 1 << 6, // Enable recording mode + POWER_DOWN = 1 << 7, // Enable power down mode + }; + + enum class RecMod : uint32_t + { + DIG_AND_CH_RESET = 0, + RESET_ALL = 1 << 5, // 1 = Set analog SR chains to default values + DIG_NRESET = 1 << 6, // 0 = Reset the MUX, ADC, and PSB counter, 1 = Disable reset + CH_NRESET = 1 << 7, // 0 = Reset channel pseudo-registers, 1 = Disable reset + + // Useful combinations + SR_RESET = RESET_ALL | CH_NRESET | DIG_NRESET, + DIG_RESET = CH_NRESET, // Yes, this is actually correct + CH_RESET = DIG_NRESET, // Yes, this is actually correct + ACTIVE = DIG_NRESET | CH_NRESET + }; + + enum class NeuropixelsV1fReference : unsigned char + { + External = 0b001, + Tip = 0b010 + }; + + enum class NeuropixelsGain : unsigned char + { + Gain50 = 0b000, + Gain125 = 0b001, + Gain250 = 0b010, + Gain500 = 0b011, + Gain1000 = 0b100, + Gain1500 = 0b101, + Gain2000 = 0b110, + Gain3000 = 0b111 + }; + + enum class ShiftRegisters : uint32_t + { + SR_CHAIN1 = 0X0E, + SR_CHAIN3 = 0X0C, + SR_CHAIN2 = 0X0D, + SR_LENGTH2 = 0X0F, + SR_LENGTH1 = 0X10, + SOFT_RESET = 0X11 + }; + + // ADC number to frame index mapping + static const int adcToFrameIndex[] = { + 0, 7 , 14, 21, 28, + 1, 8 , 15, 22, 29, + 2, 9 , 16, 23, 30, + 3, 10, 17, 24, 31, + 4, 11, 18, 25, 32, + 5, 12, 19, 26, 33, + 6, 13 + }; + + // ADC to muxed channel mapping + static const int adcToChannel[] = { + 0, 1, 24, 25, 48, 49, 72, 73, 96, 97, + 120, 121, 144, 145, 168, 169, 192, 193, + 216, 217, 240, 241, 264, 265, 288, 289, + 312, 313, 336, 337, 360, 361 + }; + + struct NeuropixelsV1fAdc + { + public: + const int compP; + const int compN; + const int slope; + const int coarse; + const int fine; + const int cfix; + const int offset; + const int threshold; + + NeuropixelsV1fAdc(int compP_ = 16, int compN_ = 16, int slope_ = 0, int coarse_ = 0, int fine_ = 0, int cfix_ = 0, int offset_ = 0, int threshold_ = 512) + : compP(compP_), compN(compN_), slope(slope_), coarse(coarse_), fine(fine_), cfix(cfix_), offset(offset_), threshold(threshold_) + { + } + }; + + /** + + Configures and streams data from a Neuropixels 1.0f device + + */ + class Neuropixels1f : public INeuropixel, + public OnixDevice, + public I2CRegisterContext + { + public: + /** Constructor */ + Neuropixels1f(std::string name, std::string hubName, const oni_dev_idx_t, std::shared_ptr); + + /** 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 */ + 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; + + NeuropixelsGain getGainEnum(int index); + + int getGainValue(NeuropixelsGain); + + NeuropixelsV1fReference getReference(int index); + + static const int shankConfigurationBitCount = 968; + static const int BaseConfigurationBitCount = 2448; + + using ShankBitset = std::bitset; + using CongigBitsArray = std::array, 2>; + + String adcCalibrationFilePath; + String gainCalibrationFilePath; + + bool getCorrectOffset() const { return correctOffset; } + + void setCorrectOffset(bool value) { correctOffset = value; } + + ShankBitset static makeShankBits(NeuropixelsV1fReference reference, std::array channelMap); + + CongigBitsArray static makeConfigBits(NeuropixelsV1fReference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs); + + void writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection); + + // INeuropixels methods + void setSettings(ProbeSettings* settings_, int index = 0) override; + + void defineMetadata(ProbeSettings* settings) override; + + uint64_t getProbeSerialNumber(int index = 0) override { return probeNumber; } + + /** Select a preset electrode configuration */ + std::vector selectElectrodeConfiguration(String config) override; + + static OnixDeviceType getDeviceType(); + + private: + + DataBuffer* apBuffer; + DataBuffer* lfpBuffer; + + const uint32_t ENABLE = 0x8000; + + static const int superFramesPerUltraFrame = 12; + static const int framesPerSuperFrame = 13; + static const int framesPerUltraFrame = superFramesPerUltraFrame * framesPerSuperFrame; + static const int numUltraFrames = 12; + static const int dataOffset = 1; + + static const int secondsToSettle = 5; + static const int samplesToAverage = 100; + + static const uint32_t numLfpSamples = 384 * numUltraFrames; + static const uint32_t numApSamples = 384 * numUltraFrames * superFramesPerUltraFrame; + + static constexpr float lfpSampleRate = 2500.0f; + static constexpr float apSampleRate = 30000.0f; + + bool lfpOffsetCalculated = false; + bool apOffsetCalculated = false; + + bool correctOffset = true; + + std::array apOffsets; + std::array lfpOffsets; + + std::vector> apOffsetValues; + std::vector> lfpOffsetValues; + + void updateLfpOffsets(std::array&, int64); + void updateApOffsets(std::array&, int64); + + static const int ProbeI2CAddress = 0x70; + + Array frameArray; + + uint64_t probeNumber = 0; + + std::array lfpSamples; + std::array apSamples; + + int64 apSampleNumbers[numUltraFrames * superFramesPerUltraFrame]; + double apTimestamps[numUltraFrames * superFramesPerUltraFrame]; + uint64 apEventCodes[numUltraFrames * superFramesPerUltraFrame]; + + int64 lfpSampleNumbers[numUltraFrames]; + double lfpTimestamps[numUltraFrames]; + uint64 lfpEventCodes[numUltraFrames]; + + bool shouldAddToBuffer = false; + int superFrameCount = 0; + int ultraFrameCount = 0; + + int apSampleNumber = 0; + int lfpSampleNumber = 0; + + int apGain = 1000; + int lfpGain = 50; + + JUCE_LEAK_DETECTOR(Neuropixels1f); + }; + + /* + + A thread that updates probe settings in the background and shows a progress bar + + */ + class BackgroundUpdaterWithProgressWindow : public ThreadWithProgressWindow + { + public: + BackgroundUpdaterWithProgressWindow(Neuropixels1f* d); + + void run() override; + + bool updateSettings(); + + private: + + Neuropixels1f* device; + + std::atomic result = false; + + JUCE_LEAK_DETECTOR(BackgroundUpdaterWithProgressWindow); + }; +} diff --git a/Source/Devices/Neuropixels2e.cpp b/Source/Devices/Neuropixels2e.cpp index 7fded7e..643a144 100644 --- a/Source/Devices/Neuropixels2e.cpp +++ b/Source/Devices/Neuropixels2e.cpp @@ -22,13 +22,14 @@ #include "Neuropixels2e.h" -Neuropixels2e::Neuropixels2e(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : - OnixDevice(name, NEUROPIXELSV2E_HEADSTAGE_NAME, OnixDeviceType::NEUROPIXELSV2E, deviceIdx_, ctx_), +using namespace OnixSourcePlugin; + +Neuropixels2e::Neuropixels2e(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : + OnixDevice(name, hubName, Neuropixels2e::getDeviceType(), deviceIdx_, ctx_, true), I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_), INeuropixel(NeuropixelsV2eValues::numberOfSettings, NeuropixelsV2eValues::numberOfShanks) { - probeSN[0] = 0; - probeSN[1] = 0; + probeSN.fill(0); for (int i = 0; i < NeuropixelsV2eValues::numberOfSettings; i++) { @@ -42,7 +43,7 @@ Neuropixels2e::Neuropixels2e(String name, const oni_dev_idx_t deviceIdx_, std::s void Neuropixels2e::createDataStream(int n) { StreamInfo apStream = StreamInfo( - OnixDevice::createStreamName({ PortController::getPortName(PortController::getPortFromIndex(deviceIdx)), getHeadstageName(), "Probe" + String(n) }), + OnixDevice::createStreamName({ getPortNameFromIndex(getDeviceIdx()), getHubName(), "Probe" + std::to_string(n) }), "Neuropixels 2.0 data stream", getStreamIdentifier(), numberOfChannels, @@ -329,28 +330,36 @@ std::vector Neuropixels2e::selectElectrodeConfiguration(String config) uint64_t Neuropixels2e::getProbeSerialNumber(int index) { - switch (index) + try { + return probeSN.at(index); + } + catch (const std::out_of_range& ex) // filter for out of range { - case 0: - return probeSN[0]; - case 1: - return probeSN[1]; - default: - return 0; + LOGE("Invalid index given requesting probe serial number."); } + + return 0ull; +} + +OnixDeviceType Neuropixels2e::getDeviceType() +{ + return OnixDeviceType::NEUROPIXELSV2E; } int Neuropixels2e::configureDevice() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); int rc = deviceContext->writeRegister(deviceIdx, DS90UB9x::ENABLE, isEnabled() ? 1 : 0); - if (rc != ONI_ESUCCESS) return rc; + if (rc != ONI_ESUCCESS) + throw error_str("Unable to enable " + getName()); configureSerDes(); setProbeSupply(true); rc = serializer->set933I2cRate(400e3); - if (rc != ONI_ESUCCESS) return rc; + if (rc != ONI_ESUCCESS) + throw error_str("Unable to set I2C rate for " + getName()); probeSN[0] = getProbeSN(ProbeASelected); probeSN[1] = getProbeSN(ProbeBSelected); setProbeSupply(false); @@ -360,7 +369,7 @@ int Neuropixels2e::configureDevice() if (probeSN[0] == 0 && probeSN[1] == 0) { m_numProbes = 0; - return -2; + throw error_str("No probes were found connected at address " + std::to_string(getDeviceIdx())); } else if (probeSN[0] != 0 && probeSN[1] != 0) { @@ -378,7 +387,7 @@ int Neuropixels2e::configureDevice() createDataStream(i); } - return 0; + return ONI_ESUCCESS; } bool Neuropixels2e::updateSettings() @@ -389,7 +398,7 @@ bool Neuropixels2e::updateSettings() { if (gainCorrectionFilePath[i] == "None" || gainCorrectionFilePath[i] == "") { - LOGE("Missing gain correction file for probe " + String(probeSN[i])); + Onix1::showWarningMessageBoxAsync("Missing File", "Missing gain correction file for probe " + std::to_string(probeSN[i])); return false; } @@ -397,7 +406,7 @@ bool Neuropixels2e::updateSettings() if (!gainCorrectionFile.existsAsFile()) { - LOGE("The gain correction file \"", gainCorrectionFilePath[i], "\" for probe ", String(probeSN[i]), " does not exist."); + Onix1::showWarningMessageBoxAsync("Missing File", "The gain correction file \"" + gainCorrectionFilePath[i].toStdString() + "\" for probe " + std::to_string(probeSN[i]) + " does not exist."); return false; } @@ -410,13 +419,13 @@ bool Neuropixels2e::updateSettings() if (gainSN != probeSN[i]) { - LOGE("Invalid serial number found in the calibration file. Should match the probe serial number (", String(probeSN[i]), ")"); + Onix1::showWarningMessageBoxAsync("Invalid Serial Number", "Gain correction serial number (" + std::to_string(gainSN) + ") does not match probe serial number (" + std::to_string(probeSN[i]) + ")."); return false; } if (fileLines.size() != numberOfChannels + 1) { - LOGE("Found the wrong number of lines in the calibration file. Expected ", numberOfChannels + 1, ", found ", fileLines.size()); + Onix1::showWarningMessageBoxAsync("File Format Invalid", "Found the wrong number of lines in the calibration file. Expected " + std::to_string(numberOfChannels + 1) + ", found " + std::to_string(fileLines.size())); return false; } @@ -431,7 +440,7 @@ bool Neuropixels2e::updateSettings() if (std::stoi(calibrationValues[0].toStdString()) != j || std::stod(calibrationValues[1].toStdString()) != correctionValue) { - LOGE("Calibration file is incorrectly formatted for probe ", String(probeSN[i])); + Onix1::showWarningMessageBoxAsync("File Format Invalid", "Calibration file is incorrectly formatted for probe " + std::to_string(probeSN[i])); return false; } } @@ -445,7 +454,7 @@ bool Neuropixels2e::updateSettings() setProbeSupply(true); resetProbes(); - for (int i = 0; i < 2; i++) + for (int i = 0; i < NumberOfProbes; i++) { if (probeSN[i] != 0) { @@ -539,6 +548,12 @@ void Neuropixels2e::setProbeSupply(bool en) void Neuropixels2e::selectProbe(uint8_t probeSelect) { + if (serializer == nullptr) + { + LOGE("Serializer is not initialized for Neuropixels 2.0"); + return; + } + serializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO32), probeSelect); Thread::sleep(20); } @@ -554,8 +569,14 @@ void Neuropixels2e::resetProbes() uint64_t Neuropixels2e::getProbeSN(uint8_t probeSelect) { + if (flex == nullptr) + { + LOGE("Flex is not initialized for Neuropixels 2.0"); + return 0ull; + } + selectProbe(probeSelect); - uint64_t probeSN = 0; + uint64_t probeSN = 0ull; int errorCode = 0, rc; for (unsigned int i = 0; i < sizeof(probeSN); i++) { @@ -564,7 +585,7 @@ uint64_t Neuropixels2e::getProbeSN(uint8_t probeSelect) rc = flex->ReadByte(reg_addr, &val); - if (rc != ONI_ESUCCESS) return 0; + if (rc != ONI_ESUCCESS) return 0ull; if (val <= 0xFF) { @@ -842,19 +863,46 @@ void Neuropixels2e::defineMetadata(ProbeSettingsprobeType = ProbeType::NPX_V2E; settings->probeMetadata.name = "Neuropixels 2.0e" + String(shankCount == 1 ? " - Single Shank" : " - Quad Shank"); - Path path; - path.startNewSubPath(27, 31); - path.lineTo(27, 514); - path.lineTo(27 + 5, 522); - path.lineTo(27 + 10, 514); - path.lineTo(27 + 10, 31); - path.closeSubPath(); + constexpr float shankTipY = 0.0f; + constexpr float shankBaseY = 155.0f; + constexpr float shankLengthY = 10000.0f; + constexpr float probeLengthY = 10155.0f; + constexpr float shankOffsetX = 200.0f; + constexpr float shankWidthX = 70.0f; + constexpr float shankPitchX = 250.0f; + + std::vector> probeContour{ + {0, probeLengthY}, + {0, shankLengthY}, + }; + + for (int i = 0; i < shankCount; i++) + { + probeContour.emplace_back(std::array{ shankOffsetX + (shankWidthX + shankPitchX) * i, shankLengthY }); + probeContour.emplace_back(std::array{ shankOffsetX + (shankWidthX + shankPitchX) * i, shankBaseY }); + probeContour.emplace_back(std::array{ shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX / 2, shankTipY }); + probeContour.emplace_back(std::array{ shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankBaseY }); + probeContour.emplace_back(std::array{ shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankLengthY }); + } + + probeContour.emplace_back(std::array{shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, shankLengthY}); + probeContour.emplace_back(std::array{shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, probeLengthY}); + probeContour.emplace_back(std::array{0.0f, probeLengthY}); + + std::vector> shankOutline{ + {27, 31}, + {27, 514}, + {27 + 5, 522}, + {27 + 10, 514}, + {27 + 10, 31} + }; settings->probeMetadata.shank_count = shankCount; settings->probeMetadata.electrodes_per_shank = NeuropixelsV2eValues::electrodesPerShank; settings->probeMetadata.rows_per_shank = NeuropixelsV2eValues::electrodesPerShank / 2; settings->probeMetadata.columns_per_shank = 2; - settings->probeMetadata.shankOutline = path; + settings->probeMetadata.shankOutline = shankOutline; + settings->probeMetadata.probeContour = probeContour; settings->probeMetadata.num_adcs = 24; settings->probeMetadata.adc_bits = 12; @@ -875,8 +923,9 @@ void Neuropixels2e::defineMetadata(ProbeSettingsprobeMetadata.electrodes_per_shank; metadata.shank_local_index = i % settings->probeMetadata.electrodes_per_shank; - metadata.xpos = i % 2 * 32.0f + 8.0f; - metadata.ypos = (metadata.shank_local_index - (metadata.shank_local_index % 2)) * 7.5f; + auto offset = shankOffsetX + (shankWidthX + shankPitchX) * metadata.shank + 11.0f; + metadata.xpos = offset + (i % 2) * 32.0f + 8.0f; + metadata.ypos = std::floor((i % settings->probeMetadata.electrodes_per_shank) / 2.0f) * 15 + 170; metadata.site_width = 12; metadata.column_index = i % 2; diff --git a/Source/Devices/Neuropixels2e.h b/Source/Devices/Neuropixels2e.h index 563e1ca..519025a 100644 --- a/Source/Devices/Neuropixels2e.h +++ b/Source/Devices/Neuropixels2e.h @@ -24,243 +24,250 @@ #include "../OnixDevice.h" #include "../I2CRegisterContext.h" -#include "../NeuropixComponents.h" +#include "../NeuropixelsComponents.h" #include "DS90UB9x.h" -#include "PortController.h" -enum class NeuropixelsV2Reference : uint32_t +namespace OnixSourcePlugin { - External, - Tip1, - Tip2, - Tip3, - Tip4 -}; - -enum class NeuropixelsV2Status : uint32_t -{ - SR_OK = 1 << 7 -}; - -/* - Configures and streams data from a Neuropixels 2.0e device -*/ -class Neuropixels2e : public INeuropixel, - public OnixDevice, - public I2CRegisterContext -{ -public: - Neuropixels2e(String name, const oni_dev_idx_t, std::shared_ptr); - - ~Neuropixels2e() + enum class NeuropixelsV2Reference : uint32_t { - if (serializer != nullptr) - serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10, DefaultGPO10Config); - - if (i2cContext != nullptr) - selectProbe(NoProbeSelected); - - if (deviceContext != nullptr && deviceContext->isInitialized()) - deviceContext->setOption(ONIX_OPT_PASSTHROUGH, 0); - } - - int configureDevice() override; + External, + Tip1, + Tip2, + Tip3, + Tip4 + }; - /** Update the settings of the device */ - bool updateSettings() override; + enum class NeuropixelsV2Status : uint32_t + { + SR_OK = 1 << 7 + }; - /** Starts probe data streaming */ - void startAcquisition() override; + /* + Configures and streams data from a Neuropixels 2.0e device (aka a configured raw deserializer) + */ + class Neuropixels2e : public INeuropixel, + public OnixDevice, + public I2CRegisterContext + { + public: + Neuropixels2e(std::string name, std::string hubName, const oni_dev_idx_t, std::shared_ptr); - /** Stops probe data streaming*/ - void stopAcquisition() override; + ~Neuropixels2e() + { + if (serializer != nullptr) + { + selectProbe(NoProbeSelected); + serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10, DefaultGPO10Config); + } - void addFrame(oni_frame_t*) override; + if (deviceContext != nullptr && deviceContext->isInitialized()) + deviceContext->setOption(ONIX_OPT_PASSTHROUGH, 0); + } - void processFrames() override; + int configureDevice() override; - void addSourceBuffers(OwnedArray& sourceBuffers) override; + /** Update the settings of the device */ + bool updateSettings() override; - int getNumProbes() const; + /** Starts probe data streaming */ + void startAcquisition() override; - static const int baseBitsPerChannel = 4; - static const int configurationBitCount = NeuropixelsV2eValues::numberOfChannels * baseBitsPerChannel / 2; + /** Stops probe data streaming*/ + void stopAcquisition() override; - static const int referencePixelCount = 4; - static const int dummyPixelCount = 4; - static const int registersPerShank = NeuropixelsV2eValues::electrodesPerShank + referencePixelCount + dummyPixelCount; + void addFrame(oni_frame_t*) override; - using BaseBitsArray = std::array, 2>; - using ShankBitsArray = std::array, 4>; + void processFrames() override; - BaseBitsArray static makeBaseBits(NeuropixelsV2Reference reference); - ShankBitsArray static makeShankBits(NeuropixelsV2Reference reference, std::array channelMap); + void addSourceBuffers(OwnedArray& sourceBuffers) override; - template - void writeShiftRegister(uint32_t srAddress, std::bitset bits); + int getNumProbes() const; - void setGainCorrectionFile(int index, String filename); + static const int baseBitsPerChannel = 4; + static const int configurationBitCount = NeuropixelsV2eValues::numberOfChannels * baseBitsPerChannel / 2; - String getGainCorrectionFile(int index); + static const int referencePixelCount = 4; + static const int dummyPixelCount = 4; + static const int registersPerShank = NeuropixelsV2eValues::electrodesPerShank + referencePixelCount + dummyPixelCount; - // INeuropixel Methods + using BaseBitsArray = std::array, 2>; + using ShankBitsArray = std::array, 4>; - std::vector selectElectrodeConfiguration(String config) override; + BaseBitsArray static makeBaseBits(NeuropixelsV2Reference reference); + ShankBitsArray static makeShankBits(NeuropixelsV2Reference reference, std::array channelMap); - uint64_t getProbeSerialNumber(int index) override; + template + void writeShiftRegister(uint32_t srAddress, std::bitset bits); - void defineMetadata(ProbeSettings*, int); + void setGainCorrectionFile(int index, String filename); - void setSettings(ProbeSettings* settings_, int index) override; + String getGainCorrectionFile(int index); -private: - DataBuffer* amplifierBuffer[2]; + // INeuropixel Methods - std::array probeSN; - std::array gainCorrection; - std::array gainCorrectionFilePath; + std::vector selectElectrodeConfiguration(String config) override; - void createDataStream(int n); + uint64_t getProbeSerialNumber(int index) override; - uint64_t getProbeSN(uint8_t probeSelect); - void configureSerDes(); - void setProbeSupply(bool); - void resetProbes(); + void defineMetadata(ProbeSettings*, int); - void selectProbe(uint8_t probeSelect); - void configureProbeStreaming(); - void writeConfiguration(ProbeSettings*); + void setSettings(ProbeSettings* settings_, int index) override; - NeuropixelsV2Reference getReference(int); - static String getShankName(uint32_t shiftRegisterAddress); + static OnixDeviceType getDeviceType(); - int m_numProbes = 0; + private: - const float sampleRate = 30000.0f; - static const int numFrames = 10; - static const int numSamples = numberOfChannels * numFrames; + static constexpr int NumberOfProbes = 2; - std::array samples; + DataBuffer* amplifierBuffer[NumberOfProbes]; - int64 sampleNumbers[numFrames]; - double timestamps[numFrames]; - uint64 eventCodes[numFrames]; + std::array probeSN; + std::array gainCorrection; + std::array gainCorrectionFilePath; - bool shouldAddToBuffer = false; - int frameCount = 0; - int sampleNumber = 0; + void createDataStream(int n); - bool singleProbe = false; + uint64_t getProbeSN(uint8_t probeSelect); + void configureSerDes(); + void setProbeSupply(bool); + void resetProbes(); - std::unique_ptr serializer; - std::unique_ptr deserializer; - std::unique_ptr flex; - std::unique_ptr probeControl; + void selectProbe(uint8_t probeSelect); + void configureProbeStreaming(); + void writeConfiguration(ProbeSettings*); - static const int ProbeI2CAddress = 0x10; - static const int FlexAddress = 0x50; + NeuropixelsV2Reference getReference(int); + static String getShankName(uint32_t shiftRegisterAddress); - static const int ProbeAddress = 0x10; - static const int FlexEEPROMAddress = 0x50; + int m_numProbes = 0; - static const uint32_t GPO10SupplyMask = 1 << 3; // Used to turn on VDDA analog supply - static const uint32_t GPO10ResetMask = 1 << 7; // Used to issue full reset commands to probes - static const uint8_t DefaultGPO10Config = 0b00010001; // NPs in reset, VDDA not enabled - static const uint8_t NoProbeSelected = 0b00010001; // No probes selected - static const uint8_t ProbeASelected = 0b00011001; // TODO: Changes in Rev. B of headstage - static const uint8_t ProbeBSelected = 0b10011001; - - static const int FramesPerSuperFrame = 16; - static const int AdcsPerProbe = 24; - static const int ChannelCount = 384; - static const int FrameWords = 36; // TRASH TRASH TRASH 0 ADC0 ADC8 ADC16 0 ADC1 ADC9 ADC17 0 ... ADC7 ADC15 ADC23 0 - - // unmanaged register map - static const uint32_t OP_MODE = 0x00; - static const uint32_t REC_MODE = 0x01; - static const uint32_t CAL_MODE = 0x02; - static const uint32_t ADC_CONFIG = 0x03; - static const uint32_t TEST_CONFIG1 = 0x04; - static const uint32_t TEST_CONFIG2 = 0x05; - static const uint32_t TEST_CONFIG3 = 0x06; - static const uint32_t TEST_CONFIG4 = 0x07; - static const uint32_t TEST_CONFIG5 = 0x08; - static const uint32_t STATUS = 0x09; - static const uint32_t SUPERSYNC0 = 0x0A; - static const uint32_t SUPERSYNC1 = 0x0B; - static const uint32_t SUPERSYNC2 = 0x0C; - static const uint32_t SUPERSYNC3 = 0x0D; - static const uint32_t SUPERSYNC4 = 0x0E; - static const uint32_t SUPERSYNC5 = 0x0F; - static const uint32_t SUPERSYNC6 = 0x10; - static const uint32_t SUPERSYNC7 = 0x11; - static const uint32_t SUPERSYNC8 = 0x12; - static const uint32_t SUPERSYNC9 = 0x13; - static const uint32_t SUPERSYNC10 = 0x14; - static const uint32_t SUPERSYNC11 = 0x15; - static const uint32_t SR_CHAIN6 = 0x16; // Odd channel base config - static const uint32_t SR_CHAIN5 = 0x17; // Even channel base config - static const uint32_t SR_CHAIN4 = 0x18; // Shank 4 - static const uint32_t SR_CHAIN3 = 0x19; // Shank 3 - static const uint32_t SR_CHAIN2 = 0x1A; // Shank 2 - static const uint32_t SR_CHAIN1 = 0x1B; // Shank 1 - static const uint32_t SR_LENGTH2 = 0x1C; - static const uint32_t SR_LENGTH1 = 0x1D; - static const uint32_t PROBE_ID = 0x1E; - static const uint32_t SOFT_RESET = 0x1F; - - const uint32_t OFFSET_PROBE_SN = 0x00; - const uint32_t OFFSET_FLEX_VERSION = 0x10; - const uint32_t OFFSET_FLEX_REVISION = 0x11; - const uint32_t OFFSET_FLEX_PN = 0x20; - const uint32_t OFFSET_PROBE_PN = 0x40; - - Array frameArray; - - static inline const std::array adcIndices = { - 0, 1, 2, - 4, 5, 6, - 8, 9, 10, - 12, 13, 14, - 16, 17, 18, - 20, 21, 22, - 24, 25, 26, - 28, 29, 30 - }; + const float sampleRate = 30000.0f; + static const int numFrames = 10; + static const int numSamples = numberOfChannels * numFrames; - static inline const std::array, AdcsPerProbe> rawToChannel = { { - { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, // Data Index 9, ADC 0 - { 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158 }, // Data Index 10, ADC 8 - { 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, // Data Index 11, ADC 16 + std::array samples; - { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 }, // Data Index 13, ADC 1 - { 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159 }, // Data Index 14, ADC 9 - { 257, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, // Data Index 15, ADC 17 + int64 sampleNumbers[numFrames]; + double timestamps[numFrames]; + uint64 eventCodes[numFrames]; - { 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 }, // Data Index 17, ADC 2 - { 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, // Data Index 18, ADC 10 - { 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318 }, // Data Index 19, ADC 18 + bool shouldAddToBuffer = false; + int frameCount = 0; + int sampleNumber = 0; - { 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63 }, // Data Index 21, ADC 3 - { 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, // Data Index 22, ADC 11 - { 289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319 }, // Data Index 23, ADC 19 + bool singleProbe = false; - { 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, // Data Index 25, ADC 4 - { 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222 }, // Data Index 26, ADC 12 - { 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350 }, // Data Index 27, ADC 20 + std::unique_ptr serializer; + std::unique_ptr deserializer; + std::unique_ptr flex; + std::unique_ptr probeControl; - { 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, // Data Index 29, ADC 5 - { 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223 }, // Data Index 30, ADC 13 - { 321, 323, 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351 }, // Data Index 31, ADC 21 + static const int ProbeI2CAddress = 0x10; + static const int FlexAddress = 0x50; - { 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126 }, // Data Index 33, ADC 6 - { 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254 }, // Data Index 34, ADC 14 - { 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, // Data Index 35, ADC 22 + static const int ProbeAddress = 0x10; + static const int FlexEEPROMAddress = 0x50; - { 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 }, // Data Index 37, ADC 7 - { 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }, // Data Index 38, ADC 15 - { 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 }, // Data Index 39, ADC 23 - } }; + static const uint32_t GPO10SupplyMask = 1 << 3; // Used to turn on VDDA analog supply + static const uint32_t GPO10ResetMask = 1 << 7; // Used to issue full reset commands to probes + static const uint8_t DefaultGPO10Config = 0b00010001; // NPs in reset, VDDA not enabled + static const uint8_t NoProbeSelected = 0b00010001; // No probes selected + static const uint8_t ProbeASelected = 0b00011001; // TODO: Changes in Rev. B of headstage + static const uint8_t ProbeBSelected = 0b10011001; - JUCE_LEAK_DETECTOR(Neuropixels2e); -}; + static const int FramesPerSuperFrame = 16; + static const int AdcsPerProbe = 24; + static const int ChannelCount = 384; + static const int FrameWords = 36; // TRASH TRASH TRASH 0 ADC0 ADC8 ADC16 0 ADC1 ADC9 ADC17 0 ... ADC7 ADC15 ADC23 0 + + // unmanaged register map + static const uint32_t OP_MODE = 0x00; + static const uint32_t REC_MODE = 0x01; + static const uint32_t CAL_MODE = 0x02; + static const uint32_t ADC_CONFIG = 0x03; + static const uint32_t TEST_CONFIG1 = 0x04; + static const uint32_t TEST_CONFIG2 = 0x05; + static const uint32_t TEST_CONFIG3 = 0x06; + static const uint32_t TEST_CONFIG4 = 0x07; + static const uint32_t TEST_CONFIG5 = 0x08; + static const uint32_t STATUS = 0x09; + static const uint32_t SUPERSYNC0 = 0x0A; + static const uint32_t SUPERSYNC1 = 0x0B; + static const uint32_t SUPERSYNC2 = 0x0C; + static const uint32_t SUPERSYNC3 = 0x0D; + static const uint32_t SUPERSYNC4 = 0x0E; + static const uint32_t SUPERSYNC5 = 0x0F; + static const uint32_t SUPERSYNC6 = 0x10; + static const uint32_t SUPERSYNC7 = 0x11; + static const uint32_t SUPERSYNC8 = 0x12; + static const uint32_t SUPERSYNC9 = 0x13; + static const uint32_t SUPERSYNC10 = 0x14; + static const uint32_t SUPERSYNC11 = 0x15; + static const uint32_t SR_CHAIN6 = 0x16; // Odd channel base config + static const uint32_t SR_CHAIN5 = 0x17; // Even channel base config + static const uint32_t SR_CHAIN4 = 0x18; // Shank 4 + static const uint32_t SR_CHAIN3 = 0x19; // Shank 3 + static const uint32_t SR_CHAIN2 = 0x1A; // Shank 2 + static const uint32_t SR_CHAIN1 = 0x1B; // Shank 1 + static const uint32_t SR_LENGTH2 = 0x1C; + static const uint32_t SR_LENGTH1 = 0x1D; + static const uint32_t PROBE_ID = 0x1E; + static const uint32_t SOFT_RESET = 0x1F; + + const uint32_t OFFSET_PROBE_SN = 0x00; + const uint32_t OFFSET_FLEX_VERSION = 0x10; + const uint32_t OFFSET_FLEX_REVISION = 0x11; + const uint32_t OFFSET_FLEX_PN = 0x20; + const uint32_t OFFSET_PROBE_PN = 0x40; + + Array frameArray; + + static inline const std::array adcIndices = { + 0, 1, 2, + 4, 5, 6, + 8, 9, 10, + 12, 13, 14, + 16, 17, 18, + 20, 21, 22, + 24, 25, 26, + 28, 29, 30 + }; + + static inline const std::array, AdcsPerProbe> rawToChannel = { { + { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, // Data Index 9, ADC 0 + { 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158 }, // Data Index 10, ADC 8 + { 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, // Data Index 11, ADC 16 + + { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 }, // Data Index 13, ADC 1 + { 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159 }, // Data Index 14, ADC 9 + { 257, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, // Data Index 15, ADC 17 + + { 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 }, // Data Index 17, ADC 2 + { 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, // Data Index 18, ADC 10 + { 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318 }, // Data Index 19, ADC 18 + + { 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63 }, // Data Index 21, ADC 3 + { 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, // Data Index 22, ADC 11 + { 289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319 }, // Data Index 23, ADC 19 + + { 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, // Data Index 25, ADC 4 + { 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222 }, // Data Index 26, ADC 12 + { 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350 }, // Data Index 27, ADC 20 + + { 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, // Data Index 29, ADC 5 + { 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223 }, // Data Index 30, ADC 13 + { 321, 323, 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351 }, // Data Index 31, ADC 21 + + { 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126 }, // Data Index 33, ADC 6 + { 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254 }, // Data Index 34, ADC 14 + { 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, // Data Index 35, ADC 22 + + { 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 }, // Data Index 37, ADC 7 + { 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }, // Data Index 38, ADC 15 + { 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 }, // Data Index 39, ADC 23 + } }; + + JUCE_LEAK_DETECTOR(Neuropixels2e); + }; +} diff --git a/Source/Devices/Neuropixels_1.h b/Source/Devices/Neuropixels_1.h deleted file mode 100644 index e543926..0000000 --- a/Source/Devices/Neuropixels_1.h +++ /dev/null @@ -1,294 +0,0 @@ -/* - ------------------------------------------------------------------ - - 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 "../NeuropixComponents.h" -#include "PortController.h" - -enum class NeuropixelsRegisters : uint32_t -{ - OP_MODE = 0x00, - REC_MOD = 0x01, - CAL_MOD = 0x02, - STATUS = 0X08, - SYNC = 0X09 -}; - -enum class CalMode : uint32_t -{ - CAL_OFF = 0, - OSC_ACTIVE = 1 << 4, // 0 = external osc inactive, 1 = activate the external calibration oscillator - ADC_CAL = 1 << 5, // Enable ADC calibration - CH_CAL = 1 << 6, // Enable channel gain calibration - PIX_CAL = 1 << 7, // Enable pixel + channel gain calibration - - // Useful combinations - OSC_ACTIVE_AND_ADC_CAL = OSC_ACTIVE | ADC_CAL, - OSC_ACTIVE_AND_CH_CAL = OSC_ACTIVE | CH_CAL, - OSC_ACTIVE_AND_PIX_CAL = OSC_ACTIVE | PIX_CAL, -}; - -enum class OpMode : uint32_t -{ - TEST = 1 << 3, // Enable Test mode - DIG_TEST = 1 << 4, // Enable Digital Test mode - CALIBRATE = 1 << 5, // Enable calibration mode - RECORD = 1 << 6, // Enable recording mode - POWER_DOWN = 1 << 7, // Enable power down mode -}; - -enum class RecMod : uint32_t -{ - DIG_AND_CH_RESET = 0, - RESET_ALL = 1 << 5, // 1 = Set analog SR chains to default values - DIG_NRESET = 1 << 6, // 0 = Reset the MUX, ADC, and PSB counter, 1 = Disable reset - CH_NRESET = 1 << 7, // 0 = Reset channel pseudo-registers, 1 = Disable reset - - // Useful combinations - SR_RESET = RESET_ALL | CH_NRESET | DIG_NRESET, - DIG_RESET = CH_NRESET, // Yes, this is actually correct - CH_RESET = DIG_NRESET, // Yes, this is actually correct - ACTIVE = DIG_NRESET | CH_NRESET -}; - -enum class NeuropixelsV1Reference : unsigned char -{ - External = 0b001, - Tip = 0b010 -}; - -enum class NeuropixelsGain : unsigned char -{ - Gain50 = 0b000, - Gain125 = 0b001, - Gain250 = 0b010, - Gain500 = 0b011, - Gain1000 = 0b100, - Gain1500 = 0b101, - Gain2000 = 0b110, - Gain3000 = 0b111 -}; - -enum class ShiftRegisters : uint32_t -{ - SR_CHAIN1 = 0X0E, - SR_CHAIN3 = 0X0C, - SR_CHAIN2 = 0X0D, - SR_LENGTH2 = 0X0F, - SR_LENGTH1 = 0X10, - SOFT_RESET = 0X11 -}; - -// ADC number to frame index mapping -static const int adcToFrameIndex[] = { - 0, 7 , 14, 21, 28, - 1, 8 , 15, 22, 29, - 2, 9 , 16, 23, 30, - 3, 10, 17, 24, 31, - 4, 11, 18, 25, 32, - 5, 12, 19, 26, 33, - 6, 13 -}; - -// ADC to muxed channel mapping -static const int adcToChannel[] = { - 0, 1, 24, 25, 48, 49, 72, 73, 96, 97, - 120, 121, 144, 145, 168, 169, 192, 193, - 216, 217, 240, 241, 264, 265, 288, 289, - 312, 313, 336, 337, 360, 361 -}; - -struct NeuropixelsV1Adc -{ -public: - const int compP; - const int compN; - const int slope; - const int coarse; - const int fine; - const int cfix; - const int offset; - const int threshold; - - NeuropixelsV1Adc(int compP_ = 16, int compN_ = 16, int slope_ = 0, int coarse_ = 0, int fine_ = 0, int cfix_ = 0, int offset_ = 0, int threshold_ = 512) - : compP(compP_), compN(compN_), slope(slope_), coarse(coarse_), fine(fine_), cfix(cfix_), offset(offset_), threshold(threshold_) - { - } -}; - -/** - - Configures and streams data from a Neuropixels 1.0f device - -*/ -class Neuropixels_1 : public INeuropixel, - public OnixDevice, - public I2CRegisterContext -{ -public: - /** Constructor */ - Neuropixels_1(String name, const oni_dev_idx_t, std::shared_ptr); - - /** 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 */ - 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; - - NeuropixelsGain getGainEnum(int index); - - int getGainValue(NeuropixelsGain); - - NeuropixelsV1Reference getReference(int index); - - static const int shankConfigurationBitCount = 968; - static const int BaseConfigurationBitCount = 2448; - - using ShankBitset = std::bitset; - using CongigBitsArray = std::array, 2>; - - String adcCalibrationFilePath; - String gainCalibrationFilePath; - - bool getCorrectOffset() const { return correctOffset; } - - void setCorrectOffset(bool value) { correctOffset = value; } - - ShankBitset static makeShankBits(NeuropixelsV1Reference reference, std::array channelMap); - - CongigBitsArray static makeConfigBits(NeuropixelsV1Reference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs); - - void writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection); - - // INeuropixels methods - void setSettings(ProbeSettings* settings_, int index = 0) override; - - void defineMetadata(ProbeSettings* settings) override; - - uint64_t getProbeSerialNumber(int index = 0) override { return probeNumber; } - - /** Select a preset electrode configuration */ - std::vector selectElectrodeConfiguration(String config) override; - -private: - - DataBuffer* apBuffer; - DataBuffer* lfpBuffer; - - const uint32_t ENABLE = 0x8000; - - static const int superFramesPerUltraFrame = 12; - static const int framesPerSuperFrame = 13; - static const int framesPerUltraFrame = superFramesPerUltraFrame * framesPerSuperFrame; - static const int numUltraFrames = 12; - static const int dataOffset = 1; - - static const int secondsToSettle = 5; - static const int samplesToAverage = 100; - - static const uint32_t numLfpSamples = 384 * numUltraFrames; - static const uint32_t numApSamples = 384 * numUltraFrames * superFramesPerUltraFrame; - - static constexpr float lfpSampleRate = 2500.0f; - static constexpr float apSampleRate = 30000.0f; - - bool lfpOffsetCalculated = false; - bool apOffsetCalculated = false; - - bool correctOffset = true; - - std::array apOffsets; - std::array lfpOffsets; - - std::vector> apOffsetValues; - std::vector> lfpOffsetValues; - - void updateLfpOffsets(std::array&, int64); - void updateApOffsets(std::array&, int64); - - static const int ProbeI2CAddress = 0x70; - - Array frameArray; - - uint64_t probeNumber = 0; - - std::array lfpSamples; - std::array apSamples; - - int64 apSampleNumbers[numUltraFrames * superFramesPerUltraFrame]; - double apTimestamps[numUltraFrames * superFramesPerUltraFrame]; - uint64 apEventCodes[numUltraFrames * superFramesPerUltraFrame]; - - int64 lfpSampleNumbers[numUltraFrames]; - double lfpTimestamps[numUltraFrames]; - uint64 lfpEventCodes[numUltraFrames]; - - bool shouldAddToBuffer = false; - int superFrameCount = 0; - int ultraFrameCount = 0; - - int apSampleNumber = 0; - int lfpSampleNumber = 0; - - int apGain = 1000; - int lfpGain = 50; - - JUCE_LEAK_DETECTOR(Neuropixels_1); -}; - -/* - - A thread that updates probe settings in the background and shows a progress bar - -*/ -class BackgroundUpdaterWithProgressWindow : public ThreadWithProgressWindow -{ -public: - BackgroundUpdaterWithProgressWindow(Neuropixels_1* d); - - void run() override; - - bool updateSettings(); - -private: - - Neuropixels_1* device; - - std::atomic result = false; - - JUCE_LEAK_DETECTOR(BackgroundUpdaterWithProgressWindow); -}; diff --git a/Source/Devices/OutputClock.cpp b/Source/Devices/OutputClock.cpp index de56efa..203da84 100644 --- a/Source/Devices/OutputClock.cpp +++ b/Source/Devices/OutputClock.cpp @@ -22,11 +22,27 @@ #include "OutputClock.h" -OutputClock::OutputClock(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::OUTPUTCLOCK, deviceIdx_, oni_ctx) +using namespace OnixSourcePlugin; + +OutputClock::OutputClock(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) + : OnixDevice(name, hubName, OutputClock::getDeviceType(), deviceIdx_, oni_ctx) +{ +} + +OnixDeviceType OutputClock::getDeviceType() { + return OnixDeviceType::OUTPUTCLOCK; } +int OutputClock::configureDevice() +{ + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); + + setEnabled(true); + return ONI_ESUCCESS; +}; + bool OutputClock::updateSettings() { oni_reg_val_t baseFreqHz; diff --git a/Source/Devices/OutputClock.h b/Source/Devices/OutputClock.h index 08a45a3..c0d59e5 100644 --- a/Source/Devices/OutputClock.h +++ b/Source/Devices/OutputClock.h @@ -24,73 +24,78 @@ #include "../OnixDevice.h" -enum class OutputClockRegisters : uint32_t +namespace OnixSourcePlugin { - NULL_REGISTER = 0, - CLOCK_GATE = 1, - HIGH_CYCLES = 2, - LOW_CYCLES = 3, - DELAY_CYCLES = 4, - GATE_RUN = 5, - BASE_FREQ_HZ = 6 -}; + 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); + /* + Configures an OutputClock device on a Breakout Board + */ + class OutputClock : public OnixDevice + { + public: + OutputClock(std::string name, std::string hubName, const oni_dev_idx_t, std::shared_ptr oni_ctx); + + /** Device is always enabled */ + int configureDevice() override; - /** Device is always enabled */ - int configureDevice() override { setEnabled(true); return 0; }; + /** Update the settings of the device */ + bool updateSettings() override; - /** Update the settings of the device */ - bool updateSettings() override; + /** Starts probe data streaming */ + void startAcquisition() override {}; - /** Starts probe data streaming */ - void startAcquisition() override {}; + /** Stops probe data streaming*/ + void stopAcquisition() 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 {}; - /** 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 addFrame(oni_frame_t* frame) override { oni_destroy_frame(frame); } + void processFrames() override {}; - void processFrames() override {}; + double getFrequencyHz() const { return frequencyHz; } - double getFrequencyHz() const { return frequencyHz; } + void setFrequencyHz(double frequency) { frequencyHz = frequency; } - void setFrequencyHz(double frequency) { frequencyHz = frequency; } + int32_t getDutyCycle() const { return dutyCycle; } - int32_t getDutyCycle() const { return dutyCycle; } + void setDutyCycle(int32_t dutyCycle_) { dutyCycle = dutyCycle_; } - void setDutyCycle(int32_t dutyCycle_) { dutyCycle = dutyCycle_; } + int32_t getDelay() const { return delay; } - int32_t getDelay() const { return delay; } + void setDelay(int32_t delay_) { delay = delay_; } - void setDelay(int32_t delay_) { delay = delay_; } + bool getGateRun() const { return gateRun; } - bool getGateRun() const { return gateRun; } + void setGateRun(bool gate, bool writeToRegister = false) + { + gateRun = gate; + if (writeToRegister) writeGateRunRegister(); + } - void setGateRun(bool gate, bool writeToRegister = false) - { - gateRun = gate; - if (writeToRegister) writeGateRunRegister(); - } + static OnixDeviceType getDeviceType(); -private: + private: - double frequencyHz = 1e6; - int32_t dutyCycle = 50; - int32_t delay = 0; + double frequencyHz = 1e6; + int32_t dutyCycle = 50; + int32_t delay = 0; - bool gateRun = true; + bool gateRun = true; - void writeGateRunRegister() { deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::GATE_RUN, gateRun ? 1 : 0); } + void writeGateRunRegister() { deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::GATE_RUN, gateRun ? 1 : 0); } - JUCE_LEAK_DETECTOR(OutputClock); -}; + JUCE_LEAK_DETECTOR(OutputClock); + }; +} diff --git a/Source/Devices/PolledBno055.cpp b/Source/Devices/PolledBno055.cpp index ff192f7..4736970 100644 --- a/Source/Devices/PolledBno055.cpp +++ b/Source/Devices/PolledBno055.cpp @@ -22,15 +22,17 @@ #include "PolledBno055.h" -PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) - : OnixDevice(name, headstageName, OnixDeviceType::POLLEDBNO, deviceIdx_, ctx), +using namespace OnixSourcePlugin; + +PolledBno055::PolledBno055(std::string name, std::string hubName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) + : OnixDevice(name, hubName, PolledBno055::getDeviceType(), deviceIdx_, ctx, true), I2CRegisterContext(Bno055Address, deviceIdx_, ctx) { auto streamIdentifier = getStreamIdentifier(); - String port = PortController::getPortName(PortController::getPortFromIndex(deviceIdx)); + std::string port = getPortNameFromIndex(deviceIdx); StreamInfo eulerAngleStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Euler" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Euler" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Euler angle", streamIdentifier, 3, @@ -46,7 +48,7 @@ PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_ streamInfos.add(eulerAngleStream); StreamInfo quaternionStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Quaternion" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Quaternion" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Quaternion", streamIdentifier, 4, @@ -62,7 +64,7 @@ PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_ streamInfos.add(quaternionStream); StreamInfo accelerationStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Acceleration" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Acceleration" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Acceleration", streamIdentifier, 3, @@ -78,7 +80,7 @@ PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_ streamInfos.add(accelerationStream); StreamInfo gravityStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Gravity" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Gravity" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Gravity", streamIdentifier, 3, @@ -94,7 +96,7 @@ PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_ streamInfos.add(gravityStream); StreamInfo temperatureStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Temperature" }), + OnixDevice::createStreamName({ port, getHubName(), getName(), "Temperature" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Temperature", streamIdentifier, 1, @@ -109,17 +111,18 @@ PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_ streamInfos.add(temperatureStream); StreamInfo calibrationStatusStream = StreamInfo( - OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Calibration" }), - "Bosch Bno055 9-axis inertial measurement unit (IMU) Calibration", + OnixDevice::createStreamName({ port, getHubName(), getName(), "Calibration" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Calibration status", streamIdentifier, - 1, + 4, sampleRate, "Cal", ContinuousChannel::Type::AUX, 1.0f, "", - { "" }, - "calibration" + { "Mag", "Acc", "Gyr", "Sys" }, + "calibration", + { "magnetometer", "acceleration", "gyroscope", "system" } ); streamInfos.add(calibrationStatusStream); @@ -127,16 +130,24 @@ PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_ eventCodes[i] = 0; } +OnixDeviceType PolledBno055::getDeviceType() +{ + return OnixDeviceType::POLLEDBNO; +} + int PolledBno055::configureDevice() { - if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + if (deviceContext == nullptr || !deviceContext->isInitialized()) + throw error_str("Device context is not initialized properly for " + getName()); deserializer = std::make_unique(DS90UB9x::DES_ADDR, deviceIdx, deviceContext); uint32_t alias = Bno055Address << 1; int rc = deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveID4, alias); - if (rc != ONI_ESUCCESS) return -2; + if (rc != ONI_ESUCCESS) + throw error_str("Error while writing bytes for " + getName()); rc = deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveAlias4, alias); - if (rc != ONI_ESUCCESS) return -2; + if (rc != ONI_ESUCCESS) + throw error_str("Error while writing bytes for " + getName()); return ONI_ESUCCESS; } @@ -211,65 +222,47 @@ int16_t PolledBno055::readInt16(uint32_t startAddress) void PolledBno055::hiResTimerCallback() { - int offset = 0; + size_t offset = 0; // Euler - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress) * eulerAngleScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 2) * eulerAngleScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 4) * eulerAngleScale; - offset++; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress) * eulerAngleScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 2) * eulerAngleScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 4) * eulerAngleScale; // Quaternion - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 6) * quaternionScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 8) * quaternionScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 10) * quaternionScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 12) * quaternionScale; - offset++; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 6) * quaternionScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 8) * quaternionScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 10) * quaternionScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 12) * quaternionScale; // Acceleration - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 14) * accelerationScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 16) * accelerationScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 18) * accelerationScale; - offset++; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 14) * accelerationScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 16) * accelerationScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 18) * accelerationScale; // Gravity - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 20) * accelerationScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 22) * accelerationScale; - offset++; - - bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 24) * accelerationScale; - offset++; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 20) * accelerationScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 22) * accelerationScale; + bnoSamples[offset++ * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 24) * accelerationScale; // Temperature oni_reg_val_t byte; ReadByte(EulerHeadingLsbAddress + 26, &byte); - bnoSamples[offset * numFrames + currentFrame] = static_cast(byte); - offset++; + bnoSamples[offset++ * numFrames + currentFrame] = static_cast(byte); // Calibration Status ReadByte(EulerHeadingLsbAddress + 27, &byte); - bnoSamples[offset * numFrames + currentFrame] = byte; - offset++; + + constexpr uint8_t statusMask = 0b11; + + for (int i = 0; i < 4; i++) + { + bnoSamples[currentFrame + (offset + i) * numFrames] = (byte & (statusMask << (2 * i))) >> (2 * i); + } oni_reg_val_t timestampL = 0, timestampH = 0; int rc = deviceContext->readRegister(deviceIdx, DS90UB9x::LASTI2CL, ×tampL); diff --git a/Source/Devices/PolledBno055.h b/Source/Devices/PolledBno055.h index dd2a2fe..0574e22 100644 --- a/Source/Devices/PolledBno055.h +++ b/Source/Devices/PolledBno055.h @@ -29,111 +29,115 @@ #include -class PolledBno055 : public OnixDevice, - public I2CRegisterContext, - public HighResolutionTimer +namespace OnixSourcePlugin { -public: + class PolledBno055 : public OnixDevice, + public I2CRegisterContext, + public HighResolutionTimer + { + public: - /** Constructor */ - PolledBno055(String name, String headstageName, const oni_dev_idx_t, std::shared_ptr ctx); + /** Constructor */ + PolledBno055(std::string name, std::string hubName, const oni_dev_idx_t, std::shared_ptr ctx); - ~PolledBno055() - { - stopTimer(); - } + ~PolledBno055() + { + stopTimer(); + } - int configureDevice() override; + int configureDevice() override; - /** Update the settings of the device */ - bool updateSettings() override; + /** Update the settings of the device */ + bool updateSettings() override; - /** Starts probe data streaming */ - void startAcquisition() override; + /** Starts probe data streaming */ + void startAcquisition() override; - /** Stops probe data streaming*/ - void stopAcquisition() override; + /** Stops probe data streaming*/ + void stopAcquisition() override; - void addFrame(oni_frame_t*) override; + void addFrame(oni_frame_t*) override; - void processFrames() override {}; + void processFrames() override {}; - void addSourceBuffers(OwnedArray& sourceBuffers) override; + void addSourceBuffers(OwnedArray& sourceBuffers) override; - void hiResTimerCallback() override; + void hiResTimerCallback() override; - enum class Bno055AxisMap : uint32_t - { - XYZ = 0b00100100, // Specifies that X->X', Y->Y', Z->Z' (chip default). - XZY = 0b00011000, // Specifies that X->X', Z->Y', Y->Z' - YXZ = 0b00100001, // Specifies that Y->X', X->Y', Z->Z' - YZX = 0b00001001, // Specifies that Y->X', Z->Y', X->Z' - ZXY = 0b00010010, // Specifies that Z->X', X->Y', Y->Z' - ZYX = 0b00000110, // Specifies that Z->X', Y->Y', X->Z' - }; + enum class Bno055AxisMap : uint32_t + { + XYZ = 0b00100100, // Specifies that X->X', Y->Y', Z->Z' (chip default). + XZY = 0b00011000, // Specifies that X->X', Z->Y', Y->Z' + YXZ = 0b00100001, // Specifies that Y->X', X->Y', Z->Z' + YZX = 0b00001001, // Specifies that Y->X', Z->Y', X->Z' + ZXY = 0b00010010, // Specifies that Z->X', X->Y', Y->Z' + ZYX = 0b00000110, // Specifies that Z->X', Y->Y', X->Z' + }; - enum class Bno055AxisSign : uint32_t - { - Default = 0b00000000, // Specifies that all axes are positive (chip default). - MirrorZ = 0b00000001, // Specifies that Z' axis should be mirrored. - MirrorY = 0b00000010, // Specifies that Y' axis should be mirrored. - MirrorX = 0b00000100, // Specifies that X' axis should be mirrored. - MirrorXAndY = 0b00000110, // X' and Y' are mirrored - }; + enum class Bno055AxisSign : uint32_t + { + Default = 0b00000000, // Specifies that all axes are positive (chip default). + MirrorZ = 0b00000001, // Specifies that Z' axis should be mirrored. + MirrorY = 0b00000010, // Specifies that Y' axis should be mirrored. + MirrorX = 0b00000100, // Specifies that X' axis should be mirrored. + MirrorXAndY = 0b00000110, // X' and Y' are mirrored + }; - void setBnoAxisMap(Bno055AxisMap map); - void setBnoAxisSign(Bno055AxisSign sign); + void setBnoAxisMap(Bno055AxisMap map); + void setBnoAxisSign(Bno055AxisSign sign); -private: + static OnixDeviceType getDeviceType(); - DataBuffer* bnoBuffer; + private: - static const int Bno055Address = 0x28; - static const int EulerHeadingLsbAddress = 0x1A; + DataBuffer* bnoBuffer; - const double i2cRate = 400e3; + static const int Bno055Address = 0x28; + static const int EulerHeadingLsbAddress = 0x1A; - static const int numberOfBytes = 28; + const double i2cRate = 400e3; - const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB - const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB - const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB + static const int numberOfBytes = 28; - std::unique_ptr deserializer; + const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB + const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB + const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB - enum class PolledBno055Registers : int32_t - { - EulerAngle = 0x1, // Specifies that the Euler angles will be polled. - Quaternion = 0x2, // Specifies that the quaternion will be polled. - Acceleration = 0x4, // Specifies that the linear acceleration will be polled. - Gravity = 0x8, // Specifies that the gravity vector will be polled. - Temperature = 0x10, // Specifies that the temperature measurement will be polled. - Calibration = 0x20, // Specifies that the sensor calibration status will be polled. - All = EulerAngle | Quaternion | Acceleration | Gravity | Temperature | Calibration, // Specifies that all sensor measurements and calibration status will be polled. - }; + std::unique_ptr deserializer; - Bno055AxisMap axisMap = Bno055AxisMap::XYZ; - Bno055AxisSign axisSign = Bno055AxisSign::Default; + enum class PolledBno055Registers : int32_t + { + EulerAngle = 0x1, // Specifies that the Euler angles will be polled. + Quaternion = 0x2, // Specifies that the quaternion will be polled. + Acceleration = 0x4, // Specifies that the linear acceleration will be polled. + Gravity = 0x8, // Specifies that the gravity vector will be polled. + Temperature = 0x10, // Specifies that the temperature measurement will be polled. + Calibration = 0x20, // Specifies that the sensor calibration status will be polled. + All = EulerAngle | Quaternion | Acceleration | Gravity | Temperature | Calibration, // Specifies that all sensor measurements and calibration status will be polled. + }; - static const int numberOfChannels = 3 + 3 + 4 + 3 + 1 + 1; - static constexpr double sampleRate = 30.0; + Bno055AxisMap axisMap = Bno055AxisMap::XYZ; + Bno055AxisSign axisSign = Bno055AxisSign::Default; - static const int timerIntervalInMilliseconds = (int)(1e3 * (1 / sampleRate)); + static const int numberOfChannels = 3 + 3 + 4 + 3 + 1 + 4; + static constexpr double sampleRate = 30.0; - static const int numFrames = 2; + static const int timerIntervalInMilliseconds = (int)(1e3 * (1 / sampleRate)); - std::array bnoSamples; + static const int numFrames = 2; - double bnoTimestamps[numFrames]; - int64 sampleNumbers[numFrames]; - uint64 eventCodes[numFrames]; + std::array bnoSamples; - unsigned short currentFrame = 0; - int sampleNumber = 0; + double bnoTimestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; - // Given the starting address (i.e., the LSB), read two bytes and convert to an int16_t - int16_t readInt16(uint32_t); + unsigned short currentFrame = 0; + int sampleNumber = 0; - JUCE_LEAK_DETECTOR(PolledBno055); -}; + // Given the starting address (i.e., the LSB), read two bytes and convert to an int16_t + int16_t readInt16(uint32_t); + JUCE_LEAK_DETECTOR(PolledBno055); + }; +} diff --git a/Source/Devices/PortController.cpp b/Source/Devices/PortController.cpp index 07133ac..7c334a7 100644 --- a/Source/Devices/PortController.cpp +++ b/Source/Devices/PortController.cpp @@ -22,8 +22,10 @@ #include "PortController.h" +using namespace OnixSourcePlugin; + PortController::PortController(PortName port_, std::shared_ptr ctx_) : - OnixDevice(getPortName(port_), BREAKOUT_BOARD_NAME, OnixDeviceType::PORT_CONTROL, (oni_dev_idx_t)port_, ctx_), + OnixDevice(OnixDevice::getPortName(port_), BREAKOUT_BOARD_NAME, OnixDeviceType::PORT_CONTROL, (oni_dev_idx_t)port_, ctx_), port(port_) { } @@ -96,21 +98,6 @@ 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(double voltage) { if (voltage == defaultVoltage) @@ -180,37 +167,3 @@ bool PortController::checkLinkState() const else if ((linkState & LINKSTATE_SL) == 0) { LOGD("Unable to acquire communication lock."); return false; } else return true; } - -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; - - for (auto index : indices) - { - ports.addIfNotAlreadyThere(PortController::getPortFromIndex(index)); - } - - return ports; -} diff --git a/Source/Devices/PortController.h b/Source/Devices/PortController.h index 211a748..d03d5d5 100644 --- a/Source/Devices/PortController.h +++ b/Source/Devices/PortController.h @@ -30,173 +30,159 @@ using namespace std::this_thread; -enum class PortControllerRegister : uint32_t +namespace OnixSourcePlugin { - ENABLE = 0, - GPOSTATE = 1, - DESPWR = 2, - PORTVOLTAGE = 3, - SAVEVOLTAGE = 4, - LINKSTATE = 5 -}; - -enum class PortStatusCode : uint32_t -{ - SerdesLock = 0x0001, - SerdesParityPass = 0x0002, - CrcError = 0x0100, - TooManyDevices = 0x0200, - InitializationError = 0x0400, - BadPacketFormat = 0x0800, - InitializationCrcError = 0x1000, -}; - -class DiscoveryParameters -{ -public: - double minVoltage = 0.0; - double maxVoltage = 0.0; - double voltageOffset = 0.0; - double voltageIncrement = 0.0; - - DiscoveryParameters() {}; - - DiscoveryParameters(double minVoltage_, double maxVoltage_, double voltageOffset_, double voltageIncrement_) + enum class PortControllerRegister : uint32_t { - minVoltage = minVoltage_; - maxVoltage = maxVoltage_; - voltageOffset = voltageOffset_; - voltageIncrement = voltageIncrement_; - } - - bool operator==(const DiscoveryParameters& rhs) const + ENABLE = 0, + GPOSTATE = 1, + DESPWR = 2, + PORTVOLTAGE = 3, + SAVEVOLTAGE = 4, + LINKSTATE = 5 + }; + + enum class PortStatusCode : uint32_t { - return rhs.minVoltage == minVoltage && rhs.maxVoltage == maxVoltage && rhs.voltageOffset == voltageOffset && rhs.voltageIncrement == voltageIncrement; - } -}; - -class PortController : public OnixDevice -{ -public: - PortController(PortName port_, std::shared_ptr ctx_); - - int configureDevice() override; - - bool updateSettings() override { return true; } - - void startAcquisition() override; - - void stopAcquisition() override; - - void addFrame(oni_frame_t*) override; + SerdesLock = 0x0001, + SerdesParityPass = 0x0002, + CrcError = 0x0100, + TooManyDevices = 0x0200, + InitializationError = 0x0400, + BadPacketFormat = 0x0800, + InitializationCrcError = 0x1000, + }; + + class DiscoveryParameters + { + public: + double minVoltage = 0.0; + double maxVoltage = 0.0; + double voltageOffset = 0.0; + double voltageIncrement = 0.0; - void processFrames() override; + DiscoveryParameters() {}; - void addSourceBuffers(OwnedArray& sourceBuffers) override {}; + DiscoveryParameters(double minVoltage_, double maxVoltage_, double voltageOffset_, double voltageIncrement_) + { + minVoltage = minVoltage_; + maxVoltage = maxVoltage_; + voltageOffset = voltageOffset_; + voltageIncrement = voltageIncrement_; + } - void updateDiscoveryParameters(DiscoveryParameters parameters); + bool operator==(const DiscoveryParameters& rhs) const + { + return rhs.minVoltage == minVoltage && rhs.maxVoltage == maxVoltage && rhs.voltageOffset == voltageOffset && rhs.voltageIncrement == voltageIncrement; + } + }; - bool configureVoltage(double voltage = defaultVoltage); + class PortController : public OnixDevice + { + public: + PortController(PortName port_, std::shared_ptr ctx_); - /** Sets the voltage to the given value, after setting the voltage to zero */ - void setVoltage(double voltage); + int configureDevice() override; - /** Overrides the voltage setting and directly sets it to the given voltage */ - void setVoltageOverride(double voltage, bool waitToSettle = true); + bool updateSettings() override { return true; } - bool checkLinkState() const; + void startAcquisition() override; - static DiscoveryParameters getHeadstageDiscoveryParameters(String headstage); + void stopAcquisition() override; - static int getPortOffset(PortName port) { return (uint32_t)port << 8; } + void addFrame(oni_frame_t*) override; - static String getPortName(int offset); + void processFrames() override; - static String getPortName(PortName port) { return port == PortName::PortA ? "Port A" : "Port B"; } + void addSourceBuffers(OwnedArray& sourceBuffers) override {}; - String getPortName() const { return getPortName(port); } + void updateDiscoveryParameters(DiscoveryParameters parameters); - static PortName getPortFromIndex(oni_dev_idx_t index); + bool configureVoltage(double voltage = defaultVoltage); - static int getOffsetFromIndex(oni_dev_idx_t index); + /** Sets the voltage to the given value, after setting the voltage to zero */ + void setVoltage(double voltage); - static Array getUniqueOffsetsFromIndices(std::vector indices); + /** Overrides the voltage setting and directly sets it to the given voltage */ + void setVoltageOverride(double voltage, bool waitToSettle = true); - static Array getUniquePortsFromIndices(std::vector); + bool checkLinkState() const; - /** Check if the port status changed and there is an error reported */ - bool getErrorFlag() { return errorFlag; } + static DiscoveryParameters getHeadstageDiscoveryParameters(String headstage); - double getLastVoltageSet() const { return lastVoltageSet; } + String getPortName() const { return OnixDevice::getPortName(port); } - static constexpr int HubAddressPortA = 256; - static constexpr int HubAddressPortB = 512; + /** Check if the port status changed and there is an error reported */ + bool getErrorFlag() { return errorFlag; } -private: - Array frameArray; + double getLastVoltageSet() const { return lastVoltageSet; } - const PortName port; + private: + Array frameArray; - static constexpr double defaultVoltage = -1.0; + const PortName port; - double lastVoltageSet = 0.0; + static constexpr double defaultVoltage = -1.0; - static constexpr uint32_t LINKSTATE_PP = 0x2; // parity check pass bit - static constexpr uint32_t LINKSTATE_SL = 0x1; // SERDES lock bit + double lastVoltageSet = 0.0; - DiscoveryParameters discoveryParameters; + static constexpr uint32_t LINKSTATE_PP = 0x2; // parity check pass bit + static constexpr uint32_t LINKSTATE_SL = 0x1; // SERDES lock bit - std::atomic errorFlag = false; + DiscoveryParameters discoveryParameters; - JUCE_LEAK_DETECTOR(PortController); -}; + std::atomic errorFlag = false; -class ConfigureVoltageWithProgressBar : public ThreadWithProgressWindow -{ -public: - ConfigureVoltageWithProgressBar(DiscoveryParameters params, PortController* port) - : ThreadWithProgressWindow("Configuring voltage on " + port->getPortName(), true, false) - { - m_params = params; - m_port = port; - } + JUCE_LEAK_DETECTOR(PortController); + }; - void run() override + class ConfigureVoltageWithProgressBar : public ThreadWithProgressWindow { - double voltage, progress = 0.0; - - double increment = m_params.voltageIncrement / (m_params.maxVoltage - m_params.minVoltage); + public: + ConfigureVoltageWithProgressBar(DiscoveryParameters params, PortController* port) + : ThreadWithProgressWindow("Configuring voltage on " + port->getPortName(), true, false) + { + m_params = params; + m_port = port; + } - for (voltage = m_params.minVoltage; voltage <= m_params.maxVoltage; voltage += m_params.voltageIncrement) + void run() override { - progress += increment; - setProgress(progress); + double voltage, progress = 0.0; - m_port->setVoltage(voltage); + double increment = m_params.voltageIncrement / (m_params.maxVoltage - m_params.minVoltage); - if (m_port->checkLinkState()) + for (voltage = m_params.minVoltage; voltage <= m_params.maxVoltage; voltage += m_params.voltageIncrement) { - setProgress(0.95); - m_port->setVoltage(voltage + m_params.voltageOffset); - result = m_port->checkLinkState();; - setProgress(1.0); - return; + progress += increment; + setProgress(progress); + + m_port->setVoltage(voltage); + + if (m_port->checkLinkState()) + { + setProgress(0.95); + m_port->setVoltage(voltage + m_params.voltageOffset); + result = m_port->checkLinkState();; + setProgress(1.0); + return; + } } - } - result = false; - return; - } + result = false; + return; + } - bool getResult() const { return result; } + bool getResult() const { return result; } -private: + private: - DiscoveryParameters m_params; + DiscoveryParameters m_params; - PortController* m_port; + PortController* m_port; - bool result = false; + bool result = false; - JUCE_LEAK_DETECTOR(ConfigureVoltageWithProgressBar); -}; + JUCE_LEAK_DETECTOR(ConfigureVoltageWithProgressBar); + }; +} diff --git a/Source/Formats/ProbeInterface.h b/Source/Formats/ProbeInterface.h index 635b34b..36e00f3 100644 --- a/Source/Formats/ProbeInterface.h +++ b/Source/Formats/ProbeInterface.h @@ -22,151 +22,248 @@ #pragma once -#include "../NeuropixComponents.h" +#include "../NeuropixelsComponents.h" -class ProbeInterfaceJson +namespace OnixSourcePlugin { -public: - static bool writeProbeSettingsToJson(File& file, ProbeSettings<0, 0>* settings) + class ProbeInterfaceJson { - DynamicObject output; + public: + template + static bool writeProbeSettingsToJson(File& file, ProbeSettings* settings) + { + DynamicObject output; - output.setProperty(Identifier("specification"), - var("probeinterface")); - output.setProperty(Identifier("version"), - var("0.2.23")); + output.setProperty(Identifier("specification"), + var("probeinterface")); + output.setProperty(Identifier("version"), + var("0.2.23")); - Array contact_positions; - Array shank_ids; - Array device_channel_indices; - Array contact_plane_axes; - Array contact_shapes; - Array contact_shape_params; + Array contact_positions; + Array shank_ids; + Array device_channel_indices; + Array contact_plane_axes; + Array contact_shapes; + Array contact_shape_params; - Array ax1 = { 1.0f, 0.0f }; - Array ax2 = { 0.0f, 1.0f }; - Array contact_plane_axis = { ax1, ax2 }; + Array ax1 = { 1.0f, 0.0f }; + Array ax2 = { 0.0f, 1.0f }; + Array contact_plane_axis = { ax1, ax2 }; - for (int elec = 0; elec < settings->electrodeMetadata.size(); elec++) - { - ElectrodeMetadata& em = settings->electrodeMetadata[elec]; - int channelIndex = settings->selectedElectrode[elec]; - // TODO: Fix this section - //int channel = -1; - //if (channelIndex > -1) - // channel = settings->selectedChannel[channelIndex]; - - Array contact_position; - contact_position.add(em.xpos + 250 * em.shank); - contact_position.add(em.ypos); - - DynamicObject::Ptr contact_shape_param = new DynamicObject; - contact_shape_param->setProperty(Identifier("width"), settings->electrodeMetadata[elec].site_width); - - contact_positions.add(contact_position); - shank_ids.add(String(em.shank)); - device_channel_indices.add(0); - contact_plane_axes.add(contact_plane_axis); - contact_shapes.add("square"); - contact_shape_params.add(contact_shape_param.get()); - } + for (int elec = 0; elec < settings->electrodeMetadata.size(); elec++) + { + ElectrodeMetadata& em = settings->electrodeMetadata[elec]; - DynamicObject::Ptr probe = new DynamicObject(); - DynamicObject::Ptr annotations = new DynamicObject(); - //annotations->setProperty(Identifier("name"), settings->probe->name); - annotations->setProperty(Identifier("manufacturer"), "imec"); + Array contact_position; + contact_position.add(em.xpos); + contact_position.add(em.ypos); - probe->setProperty(Identifier("ndim"), 2); - probe->setProperty(Identifier("si_units"), "um"); - probe->setProperty(Identifier("annotations"), var(annotations)); - probe->setProperty(Identifier("contact_positions"), contact_positions); - probe->setProperty(Identifier("contact_plane_axes"), contact_plane_axes); - probe->setProperty(Identifier("contact_shapes"), contact_shapes); - probe->setProperty(Identifier("contact_shape_params"), contact_shape_params); - probe->setProperty(Identifier("device_channel_indices"), device_channel_indices); - probe->setProperty(Identifier("shank_ids"), shank_ids); + DynamicObject::Ptr contact_shape_param = new DynamicObject; + contact_shape_param->setProperty(Identifier("width"), em.site_width); - Array probes; - probes.add(probe.get()); + contact_positions.add(contact_position); + shank_ids.add(String(em.shank)); + device_channel_indices.add(em.status == ElectrodeStatus::CONNECTED ? em.channel : -1); + contact_plane_axes.add(contact_plane_axis); + contact_shapes.add("square"); + contact_shape_params.add(contact_shape_param.get()); + } - output.setProperty(Identifier("probes"), probes); + Array probe_planar_contours; + auto& probeContour = settings->probeMetadata.probeContour; - if (file.exists()) - file.deleteFile(); + for (int i = 0; i < probeContour.size(); i++) + { + probe_planar_contours.add(Array { probeContour[i][0], probeContour[i][1] }); + } - FileOutputStream f(file); + DynamicObject::Ptr probe = new DynamicObject(); + DynamicObject::Ptr annotations = new DynamicObject(); + annotations->setProperty(Identifier("manufacturer"), "imec"); + auto probeName = getProbeName(settings->probeType); + annotations->setProperty(Identifier("name"), String(probeName)); - JSON::FormatOptions options; + probe->setProperty(Identifier("ndim"), 2); + probe->setProperty(Identifier("si_units"), "um"); + probe->setProperty(Identifier("annotations"), var(annotations)); + probe->setProperty(Identifier("contact_positions"), contact_positions); + probe->setProperty(Identifier("contact_plane_axes"), contact_plane_axes); + probe->setProperty(Identifier("contact_shapes"), contact_shapes); + probe->setProperty(Identifier("contact_shape_params"), contact_shape_params); + probe->setProperty(Identifier("probe_planar_contour"), probe_planar_contours); + probe->setProperty(Identifier("device_channel_indices"), device_channel_indices); + probe->setProperty(Identifier("shank_ids"), shank_ids); - options = options.withMaxDecimalPlaces(4); - options = options.withSpacing(JSON::Spacing::multiLine); + Array probes; + probes.add(probe.get()); - output.writeAsJSON(f, options); + output.setProperty(Identifier("probes"), probes); - return true; - } + if (file.exists()) + file.deleteFile(); - static bool readProbeSettingsFromJson(File& file, ProbeSettings<0,0>* settings) - { - var result; + FileOutputStream f(file); - static Result r = JSON::parse(file.loadFileAsString(), result); + JSON::FormatOptions options; - DynamicObject::Ptr obj = result.getDynamicObject(); + options = options.withMaxDecimalPlaces(4); + options = options.withSpacing(JSON::Spacing::multiLine); - // check that specification == 'probeinterface' - if (obj->hasProperty(Identifier("specification"))) - { - String specification = obj->getProperty(Identifier("specification")).toString(); + output.writeAsJSON(f, options); - if (specification.compare("probeinterface") != 0) - return false; + return true; } - if (obj->hasProperty(Identifier("probes"))) + template + static bool readProbeSettingsFromJson(File& file, ProbeSettings* settings) { - Array* probes = obj->getProperty(Identifier("probes")).getArray(); + auto json = JSON::parse(file); - // check that this file contains only one probe - if (probes->size() != 1) + if (json == var()) return false; - DynamicObject::Ptr probe = probes->getReference(0).getDynamicObject(); + DynamicObject::Ptr obj = json.getDynamicObject(); - if (probe->hasProperty(Identifier("device_channel_indices"))) + if (obj->hasProperty(Identifier("specification"))) { - Array* device_channel_indices = probe->getProperty(Identifier("device_channel_indices")).getArray(); + String specification = obj->getProperty(Identifier("specification")).toString(); - for (int ch = 0; ch < device_channel_indices->size(); ch++) - { - int index = int(device_channel_indices->getReference(ch)); - } + if (specification.compare("probeinterface") != 0) + return false; } + else + return false; - if (probe->hasProperty(Identifier("shank_ids"))) + if (obj->hasProperty(Identifier("probes"))) { - Array* shank_ids = probe->getProperty(Identifier("shank_ids")).getArray(); + Array* probes = obj->getProperty(Identifier("probes")).getArray(); - for (int ch = 0; ch < shank_ids->size(); ch++) + if (probes->size() != 1) + return false; + + DynamicObject::Ptr probe = probes->getReference(0).getDynamicObject(); + + if (probe->hasProperty(Identifier("ndim"))) { - int shank_id = int(shank_ids->getReference(ch)); + if (!probe->getProperty(Identifier("ndim")).equalsWithSameType(2)) + { + return false; + } } - } + else + return false; - if (probe->hasProperty(Identifier("contact_positions"))) - { - Array* contact_positions = probe->getProperty(Identifier("contact_positions")).getArray(); + if (probe->hasProperty(Identifier("si_units"))) + { + if (!probe->getProperty(Identifier("si_units")).equalsWithSameType("um")) + { + return false; + } + } + else + return false; + + Array* contact_positions = nullptr; + + if (probe->hasProperty(Identifier("contact_positions"))) + { + contact_positions = probe->getProperty(Identifier("contact_positions")).getArray(); + + if (contact_positions->size() != settings->electrodeMetadata.size()) + return false; + } + else + return false; + + Array* probe_planar_contour = nullptr; + + if (probe->hasProperty(Identifier("probe_planar_contour"))) + { + probe_planar_contour = probe->getProperty(Identifier("probe_planar_contour")).getArray(); + } + else + return false; + + Array* device_channel_indices = nullptr; + + if (probe->hasProperty(Identifier("device_channel_indices"))) + { + device_channel_indices = probe->getProperty(Identifier("device_channel_indices")).getArray(); + + if (device_channel_indices->size() != settings->electrodeMetadata.size()) + return false; + } + else + return false; + + Array* shank_ids = nullptr; + + if (probe->hasProperty(Identifier("shank_ids"))) + { + shank_ids = probe->getProperty(Identifier("shank_ids")).getArray(); + + if (shank_ids->size() != settings->electrodeMetadata.size()) + return false; + } + else + return false; for (int ch = 0; ch < contact_positions->size(); ch++) { Array* contact_position = contact_positions->getReference(ch).getArray(); - int xpos = int(contact_position->getReference(0)); - int ypos = int(contact_position->getReference(1)); + settings->electrodeMetadata[ch].xpos = int(contact_position->getReference(0)); + settings->electrodeMetadata[ch].ypos = int(contact_position->getReference(1)); + } + + settings->probeMetadata.probeContour.clear(); + + for (int i = 0; i < probe_planar_contour->size(); i++) + { + Array* point = probe_planar_contour->getReference(i).getArray(); + + settings->probeMetadata.probeContour.emplace_back(std::array{float(point->getReference(0)), float(point->getReference(1))}); + } + + for (int ch = 0; ch < shank_ids->size(); ch++) + { + settings->electrodeMetadata[ch].shank = int(shank_ids->getReference(ch)); + } + + settings->clearElectrodeSelection(); + std::vector selectedChannels{}; + + for (int ch = 0; ch < device_channel_indices->size(); ch++) + { + if (int(device_channel_indices->getReference(ch)) >= 0) + { + selectedChannels.emplace_back(ch); + } } + + settings->selectElectrodes(selectedChannels); } + else + return false; + + return true; } - return true; - } -}; + static std::string getProbeName(ProbeType type) + { + switch (type) + { + case OnixSourcePlugin::ProbeType::NONE: + return ""; + case OnixSourcePlugin::ProbeType::NPX_V1E: + return "Neuropixels 1.0"; + case OnixSourcePlugin::ProbeType::NPX_V2E: + return "Neuropixels 2.0"; + case OnixSourcePlugin::ProbeType::NPX_V2E_BETA: + return "Neuropixels 2.0"; + default: + return ""; + } + } + }; +} diff --git a/Source/FrameReader.cpp b/Source/FrameReader.cpp index 52d324f..16500fd 100644 --- a/Source/FrameReader.cpp +++ b/Source/FrameReader.cpp @@ -22,6 +22,8 @@ #include "FrameReader.h" +using namespace OnixSourcePlugin; + FrameReader::FrameReader(OnixDeviceVector sources_, std::shared_ptr ctx_) : Thread("FrameReader") { diff --git a/Source/FrameReader.h b/Source/FrameReader.h index 210786a..23aacfe 100644 --- a/Source/FrameReader.h +++ b/Source/FrameReader.h @@ -27,17 +27,20 @@ #include "OnixDevice.h" #include "oni.h" -class FrameReader : public Thread +namespace OnixSourcePlugin { -public: - FrameReader(OnixDeviceVector sources_, std::shared_ptr); + class FrameReader : public Thread + { + public: + FrameReader(OnixDeviceVector sources_, std::shared_ptr); - void run() override; + void run() override; -private: + private: - OnixDeviceVector sources; - std::shared_ptr context; + OnixDeviceVector sources; + std::shared_ptr context; - JUCE_LEAK_DETECTOR(FrameReader); -}; + JUCE_LEAK_DETECTOR(FrameReader); + }; +} diff --git a/Source/I2CRegisterContext.cpp b/Source/I2CRegisterContext.cpp index 3036d9e..eb4280c 100644 --- a/Source/I2CRegisterContext.cpp +++ b/Source/I2CRegisterContext.cpp @@ -22,6 +22,8 @@ #include "I2CRegisterContext.h" +using namespace OnixSourcePlugin; + 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 ec4604b..d8b0508 100644 --- a/Source/I2CRegisterContext.h +++ b/Source/I2CRegisterContext.h @@ -29,26 +29,29 @@ #include -class I2CRegisterContext +namespace OnixSourcePlugin { -public: + class I2CRegisterContext + { + public: - I2CRegisterContext(uint32_t address, const oni_dev_idx_t, std::shared_ptr); + I2CRegisterContext(uint32_t address, const oni_dev_idx_t, std::shared_ptr); - int WriteByte(uint32_t address, uint32_t value, bool sixteenBitAddress = false); + int WriteByte(uint32_t address, uint32_t value, bool sixteenBitAddress = false); - int ReadByte(uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress = false); + int ReadByte(uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress = false); - int set933I2cRate(double); + int set933I2cRate(double); -protected: - std::shared_ptr i2cContext; + protected: + std::shared_ptr i2cContext; -private: + private: - const oni_dev_idx_t deviceIndex; + const oni_dev_idx_t deviceIndex; - const uint32_t i2cAddress; + const uint32_t i2cAddress; - JUCE_LEAK_DETECTOR(I2CRegisterContext); -}; + JUCE_LEAK_DETECTOR(I2CRegisterContext); + }; +} diff --git a/Source/NeuropixComponents.h b/Source/NeuropixComponents.h deleted file mode 100644 index 40cef71..0000000 --- a/Source/NeuropixComponents.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - ------------------------------------------------------------------ - - 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 -#include -#include -#include - -#include "UI/ActivityView.h" - -enum class VisualizationMode -{ - ENABLE_VIEW, - AP_GAIN_VIEW, - LFP_GAIN_VIEW, - REFERENCE_VIEW, - ACTIVITY_VIEW -}; - -enum class ProbeType -{ - NONE = 1, - NPX_V1E, - NPX_V2E, - NPX_V2E_BETA, -}; - -enum class Bank -{ - NONE = -1, - A = 0, - B = 1, - C = 2, - D = 3, - E = 4, - F = 5, - G = 6, - H = 7, - I = 8, - J = 9, - K = 10, - L = 11, - M = 12, -}; - -enum class ElectrodeStatus -{ - CONNECTED, - DISCONNECTED -}; - -enum class ElectrodeType -{ - ELECTRODE, - REFERENCE -}; - -struct ElectrodeMetadata -{ - int global_index; - int shank_local_index; - int shank; - int column_index; - int channel; - int row_index; - float xpos; // position on shank, in microns - float ypos; // position on shank, in microns - float site_width; // in microns - Bank bank; - ElectrodeStatus status; - ElectrodeType type; - bool isSelected; - Colour colour; -}; - -struct ProbeMetadata -{ - int shank_count; - int electrodes_per_shank; - int num_adcs; - int adc_bits; - Path shankOutline; - int columns_per_shank; - int rows_per_shank; - String name; - bool switchable; -}; - -struct NeuropixelsV1fValues -{ - static const int numberOfChannels = 384; - static const int numberOfElectrodes = 960; - static const int numberOfShanks = 1; - static const int numberOfSettings = 1; -}; - -struct NeuropixelsV2eValues -{ - static const int numberOfChannels = 384; - static const int electrodesPerShank = 1280; - static const int numberOfShanks = 4; - static const int numberOfElectrodes = numberOfShanks * electrodesPerShank; - static const int numberOfSettings = 2; -}; - -template -struct ProbeSettings -{ - void updateProbeSettings(ProbeSettings* newSettings) - { - availableElectrodeConfigurations = newSettings->availableElectrodeConfigurations; - availableApGains = newSettings->availableApGains; - availableLfpGains = newSettings->availableLfpGains; - availableReferences = newSettings->availableReferences; - availableBanks = newSettings->availableBanks; - - electrodeConfigurationIndex = newSettings->electrodeConfigurationIndex; - apGainIndex = newSettings->apGainIndex; - lfpGainIndex = newSettings->lfpGainIndex; - referenceIndex = newSettings->referenceIndex; - apFilterState = newSettings->apFilterState; - - selectedBank = newSettings->selectedBank; - selectedShank = newSettings->selectedShank; - selectedElectrode = newSettings->selectedElectrode; - electrodeMetadata = newSettings->electrodeMetadata; - - probeType = newSettings->probeType; - - probeMetadata = newSettings->probeMetadata; - }; - - void clearElectrodeSelection() - { - selectedBank.clear(); - selectedShank.clear(); - selectedElectrode.clear(); - } - - void selectElectrodes(std::vector electrodes) - { - for (int i = 0; i < electrodes.size(); i++) - { - selectElectrode(electrodes[i]); - } - } - - void selectElectrode(int electrode) - { - Bank bank = electrodeMetadata[electrode].bank; - int channel = electrodeMetadata[electrode].channel; - int shank = electrodeMetadata[electrode].shank; - int global_index = electrodeMetadata[electrode].global_index; - - for (int j = 0; j < electrodeMetadata.size(); j++) - { - if (electrodeMetadata[j].channel == channel) - { - if (electrodeMetadata[j].bank == bank && electrodeMetadata[j].shank == shank) - { - electrodeMetadata[j].status = ElectrodeStatus::CONNECTED; - } - - else - { - electrodeMetadata[j].status = ElectrodeStatus::DISCONNECTED; - } - } - } - - selectedBank[channel] = bank; - selectedShank[channel] = shank; - selectedElectrode[channel] = global_index; - } - - Array availableElectrodeConfigurations; - Array availableApGains; // Available AP gain values for each channel (if any) - Array availableLfpGains; // Available LFP gain values for each channel (if any) - Array availableReferences; // reference types - Array availableBanks; // bank inds - - int electrodeConfigurationIndex; - int apGainIndex; - int lfpGainIndex; - int referenceIndex; - bool apFilterState; - - std::array selectedBank; - std::array selectedShank; - std::array selectedElectrode; - std::array electrodeMetadata; - - ProbeType probeType; - - ProbeMetadata probeMetadata; - - bool isValid = false; -}; - -template -std::vector toBitReversedBytes(std::bitset bits) -{ - std::vector bytes((bits.size() - 1) / 8 + 1); - - for (int i = 0; i < bytes.size(); i++) - { - for (int j = 0; j < 8; j++) - { - bytes[i] |= bits[i * 8 + j] << (8 - j - 1); - } - - // NB: Reverse bytes (http://graphics.stanford.edu/~seander/bithacks.html) - bytes[i] = (unsigned char)((bytes[i] * 0x0202020202ul & 0x010884422010ul) % 1023); - } - - return bytes; -} - -template -class INeuropixel -{ -public: - INeuropixel(int numSettings, int numShanks) : numberOfShanks(numShanks) - { - if (numSettings > 2) return; - - for (int i = 0; i < numSettings; i++) - { - settings.emplace_back(std::make_unique>()); - } - } - - static const int numberOfChannels = ch; - static const int numberOfElectrodes = e; - const int numberOfShanks; - - std::vector>> settings; - - virtual void setSettings(ProbeSettings* settings_, int index) { return; } - - virtual void defineMetadata(ProbeSettings* settings) { return; } - - virtual uint64_t getProbeSerialNumber(int index) { return 0; } - - virtual std::vector selectElectrodeConfiguration(String config) { return {}; } -}; diff --git a/Source/NeuropixelsComponents.h b/Source/NeuropixelsComponents.h new file mode 100644 index 0000000..2d4048e --- /dev/null +++ b/Source/NeuropixelsComponents.h @@ -0,0 +1,271 @@ +/* + ------------------------------------------------------------------ + + 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 +#include +#include +#include + +#include "UI/ActivityView.h" + +namespace OnixSourcePlugin +{ + enum class VisualizationMode + { + ENABLE_VIEW, + AP_GAIN_VIEW, + LFP_GAIN_VIEW, + REFERENCE_VIEW, + ACTIVITY_VIEW + }; + + enum class ProbeType + { + NONE = 1, + NPX_V1E, + NPX_V2E, + NPX_V2E_BETA, + }; + + enum class Bank + { + NONE = -1, + A = 0, + B = 1, + C = 2, + D = 3, + E = 4, + F = 5, + G = 6, + H = 7, + I = 8, + J = 9, + K = 10, + L = 11, + M = 12, + }; + + enum class ElectrodeStatus + { + CONNECTED, + DISCONNECTED + }; + + enum class ElectrodeType + { + ELECTRODE, + REFERENCE + }; + + struct ElectrodeMetadata + { + int global_index; + int shank_local_index; + int shank; + int column_index; + int channel; + int row_index; + float xpos; // position on shank, in microns + float ypos; // position on shank, in microns + float site_width; // in microns + Bank bank; + ElectrodeStatus status; + ElectrodeType type; + bool isSelected; + Colour colour; + }; + + struct ProbeMetadata + { + int shank_count; + int electrodes_per_shank; + int num_adcs; + int adc_bits; + std::vector> shankOutline; + std::vector> probeContour; + int columns_per_shank; + int rows_per_shank; + String name; + bool switchable; + }; + + struct NeuropixelsV1fValues + { + static const int numberOfChannels = 384; + static const int numberOfElectrodes = 960; + static const int numberOfShanks = 1; + static const int numberOfSettings = 1; + }; + + struct NeuropixelsV2eValues + { + static const int numberOfChannels = 384; + static const int electrodesPerShank = 1280; + static const int numberOfShanks = 4; + static const int numberOfElectrodes = numberOfShanks * electrodesPerShank; + static const int numberOfSettings = 2; + }; + + template + struct ProbeSettings + { + void updateProbeSettings(ProbeSettings* newSettings) + { + availableElectrodeConfigurations = newSettings->availableElectrodeConfigurations; + availableApGains = newSettings->availableApGains; + availableLfpGains = newSettings->availableLfpGains; + availableReferences = newSettings->availableReferences; + availableBanks = newSettings->availableBanks; + + electrodeConfigurationIndex = newSettings->electrodeConfigurationIndex; + apGainIndex = newSettings->apGainIndex; + lfpGainIndex = newSettings->lfpGainIndex; + referenceIndex = newSettings->referenceIndex; + apFilterState = newSettings->apFilterState; + + selectedBank = newSettings->selectedBank; + selectedShank = newSettings->selectedShank; + selectedElectrode = newSettings->selectedElectrode; + electrodeMetadata = newSettings->electrodeMetadata; + + probeType = newSettings->probeType; + + probeMetadata = newSettings->probeMetadata; + }; + + void clearElectrodeSelection() + { + selectedBank.fill(Bank::A); + selectedShank.fill(0); + selectedElectrode.fill(-1); + } + + void selectElectrodes(std::vector electrodes) + { + for (int i = 0; i < electrodes.size(); i++) + { + selectElectrode(electrodes[i]); + } + } + + void selectElectrode(int electrode) + { + Bank bank = electrodeMetadata[electrode].bank; + int channel = electrodeMetadata[electrode].channel; + int shank = electrodeMetadata[electrode].shank; + int global_index = electrodeMetadata[electrode].global_index; + + for (int j = 0; j < electrodeMetadata.size(); j++) + { + if (electrodeMetadata[j].channel == channel) + { + if (electrodeMetadata[j].bank == bank && electrodeMetadata[j].shank == shank) + { + electrodeMetadata[j].status = ElectrodeStatus::CONNECTED; + } + + else + { + electrodeMetadata[j].status = ElectrodeStatus::DISCONNECTED; + } + } + } + + selectedBank[channel] = bank; + selectedShank[channel] = shank; + selectedElectrode[channel] = global_index; + } + + Array availableElectrodeConfigurations; + Array availableApGains; // Available AP gain values for each channel (if any) + Array availableLfpGains; // Available LFP gain values for each channel (if any) + Array availableReferences; // reference types + Array availableBanks; // bank inds + + int electrodeConfigurationIndex; + int apGainIndex; + int lfpGainIndex; + int referenceIndex; + bool apFilterState; + + std::array selectedBank; + std::array selectedShank; + std::array selectedElectrode; + std::array electrodeMetadata; + + ProbeType probeType; + + ProbeMetadata probeMetadata; + + bool isValid = false; + }; + + template + std::vector toBitReversedBytes(std::bitset bits) + { + std::vector bytes((bits.size() - 1) / 8 + 1); + + for (int i = 0; i < bytes.size(); i++) + { + for (int j = 0; j < 8; j++) + { + bytes[i] |= bits[i * 8 + j] << (8 - j - 1); + } + + // NB: Reverse bytes (http://graphics.stanford.edu/~seander/bithacks.html) + bytes[i] = (unsigned char)((bytes[i] * 0x0202020202ul & 0x010884422010ul) % 1023); + } + + return bytes; + } + + template + class INeuropixel + { + public: + INeuropixel(int numSettings, int numShanks) : numberOfShanks(numShanks) + { + if (numSettings > 2) return; + + for (int i = 0; i < numSettings; i++) + { + settings.emplace_back(std::make_unique>()); + } + } + + static const int numberOfChannels = ch; + static const int numberOfElectrodes = e; + const int numberOfShanks; + + std::vector>> settings; + + virtual void setSettings(ProbeSettings* settings_, int index) { return; } + + virtual void defineMetadata(ProbeSettings* settings) { return; } + + virtual uint64_t getProbeSerialNumber(int index) { return 0; } + + virtual std::vector selectElectrodeConfiguration(String config) { return {}; } + }; +} diff --git a/Source/Onix1.cpp b/Source/Onix1.cpp index cdd99ac..b31166f 100644 --- a/Source/Onix1.cpp +++ b/Source/Onix1.cpp @@ -21,6 +21,9 @@ */ #include "Onix1.h" +#include "OnixDevice.h" + +using namespace OnixSourcePlugin; Onix1::Onix1(int hostIndex) { @@ -57,7 +60,6 @@ int Onix1::updateDeviceTable() if (numDevices == 0 || rc != ONI_ESUCCESS) return rc; size_t devicesSize = sizeof(oni_device_t) * numDevices; - deviceTable.reserve(devicesSize); std::vector devices; devices.resize(numDevices); @@ -74,6 +76,43 @@ int Onix1::updateDeviceTable() return rc; } +std::map Onix1::getHubIds() +{ + std::map hubIds; + + if (deviceTable.size() == 0) + return hubIds; + + auto deviceIndices = getDeviceIndices(deviceTable); + + auto offsets = OnixDevice::getUniqueOffsetsFromIndices(deviceIndices, false); + + for (int i = 0; i < offsets.size(); i++) + { + oni_reg_val_t hubId = 0; + int rc = oni_read_reg(ctx_, offsets[i] + ONIX_HUB_DEV_IDX, (uint32_t)ONIX_HUB_HARDWAREID, &hubId); + if (rc != ONI_ESUCCESS) + LOGE("Unable to read the hub device index for the hub at index ", offsets[i]); + + hubIds.insert({ offsets[i], hubId }); + } + + return hubIds; +} + +std::vector Onix1::getDeviceIndices(device_map_t deviceMap, int hubIndex) +{ + std::vector deviceIndices; + + for (const auto& [idx, dev] : deviceMap) + { + if (dev.id != ONIX_NULL && (hubIndex == -1 || OnixDevice::getOffsetFromIndex(dev.idx) == hubIndex)) + deviceIndices.emplace_back(idx); + } + + return deviceIndices; +} + int Onix1::get_opt_(int option, void* value, size_t* size) const { int rc = oni_get_opt(ctx_, option, value, size); @@ -107,3 +146,16 @@ oni_frame_t* Onix1::readFrame() const return frame; } + +void Onix1::showWarningMessageBoxAsync(std::string title, std::string error_msg) +{ + LOGE(error_msg); + MessageManager::callAsync([title, error_msg] + { + AlertWindow::showMessageBoxAsync( + MessageBoxIconType::WarningIcon, + title, + error_msg + ); + }); +} diff --git a/Source/Onix1.h b/Source/Onix1.h index 9a0ec8f..5d4c5bd 100644 --- a/Source/Onix1.h +++ b/Source/Onix1.h @@ -29,95 +29,123 @@ #include "../../plugin-GUI/Source/Utils/Utils.h" -constexpr char* NEUROPIXELSV1F_HEADSTAGE_NAME = "Neuropixels 1.0 Headstage"; -constexpr char* NEUROPIXELSV2E_HEADSTAGE_NAME = "Neuropixels 2.0 Headstage"; -constexpr char* BREAKOUT_BOARD_NAME = "Breakout Board"; - -class error_t : public std::exception +namespace OnixSourcePlugin { -public: - explicit error_t(int errorNum) : errorNum_(errorNum) {} + constexpr char* NEUROPIXELSV1F_HEADSTAGE_NAME = "Neuropixels 1.0 Headstage"; + constexpr char* NEUROPIXELSV2E_HEADSTAGE_NAME = "Neuropixels 2.0 Headstage"; + constexpr char* BREAKOUT_BOARD_NAME = "Breakout Board"; - const char* what() const noexcept override + class error_t : public std::exception { - return oni_error_str(errorNum_); - } + public: + explicit error_t(int errorNum) : errorNum_(errorNum) {} - int num() const { return errorNum_; } + const char* what() const noexcept override + { + return oni_error_str(errorNum_); + } -private: - int errorNum_; -}; + int num() const { return errorNum_; } -using device_map_t = std::unordered_map; + private: + int errorNum_; + }; -class Onix1 -{ -public: - Onix1(int hostIndex = 0); + class error_str : public std::exception + { + public: + explicit error_str(std::string errorStr) : m_errorStr(errorStr) {} - ~Onix1(); + const char* what() const noexcept override + { + return m_errorStr.c_str(); + } - inline bool isInitialized() const { return ctx_ != nullptr; } + std::string str() const { return m_errorStr; } - template - int getOption(int option, opt_t* value) - { - size_t len = sizeof(opt_t); - int rc = get_opt_(option, value, &len); - return rc; - } + private: + std::string m_errorStr; + }; - template - int setOption(int option, const opt_t value) + using device_map_t = std::map; + + class Onix1 { - int rc = oni_set_opt(ctx_, option, &value, opt_size_(value)); - if (rc != ONI_ESUCCESS) LOGE(oni_error_str(rc)); - return rc; - } - - int readRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress, oni_reg_val_t* value) const; + public: + Onix1(int hostIndex = 0); - int writeRegister(oni_dev_idx_t, oni_reg_addr_t, oni_reg_val_t) const; + ~Onix1(); - device_map_t getDeviceTable() const noexcept { return deviceTable; } + inline bool isInitialized() const { return ctx_ != nullptr; } - int updateDeviceTable(); + template + int getOption(int option, opt_t* value) + { + size_t len = sizeof(opt_t); + int rc = get_opt_(option, value, &len); + return rc; + } - oni_frame_t* readFrame() const; + template + int setOption(int option, const opt_t value) + { + int rc = oni_set_opt(ctx_, option, &value, opt_size_(value)); + if (rc != ONI_ESUCCESS) LOGE(oni_error_str(rc)); + return rc; + } - int issueReset() { int val = 1; int rc = setOption(ONI_OPT_RESET, val); return rc; } + int readRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress, oni_reg_val_t* value) const; - std::string getVersion() const { return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); } + int writeRegister(oni_dev_idx_t, oni_reg_addr_t, oni_reg_val_t) const; - double convertTimestampToSeconds(uint32_t timestamp) const { return static_cast(timestamp) / ACQ_CLK_HZ; } + device_map_t getDeviceTable() const noexcept { return deviceTable; } -private: + int updateDeviceTable(); - /** The ONI ctx object */ - oni_ctx ctx_; + oni_frame_t* readFrame() const; - int major; - int minor; - int patch; + int issueReset() { int val = 1; int rc = setOption(ONI_OPT_RESET, val); return rc; } - uint32_t ACQ_CLK_HZ; + std::string getVersion() const { return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); } - device_map_t deviceTable; + double convertTimestampToSeconds(uint32_t timestamp) const { return static_cast(timestamp) / ACQ_CLK_HZ; } - template - size_t opt_size_(opt_t opt) - { - size_t len = 0; - if constexpr (std::is_same::value) - len = strlen(len) + 1; - else if constexpr (std::is_same::value) - len = strlen(len) + 1; - else - len = sizeof(opt); - - return len; - } - - int get_opt_(int option, void* value, size_t* size) const; -}; + /** Gets a map of all hubs connected, where the index of the map is the hub address, and the value is the hub ID */ + std::map getHubIds(); + + /** Gets a vector of device indices from a device_map_t object, optionally filtered by a specific hub */ + static std::vector getDeviceIndices(device_map_t deviceMap, int hubIndex = -1); + + /** Displays an AlertWindow::showMessageBoxAsync window using the message thread, with a warning icon */ + static void showWarningMessageBoxAsync(std::string, std::string); + + private: + + /** The ONI ctx object */ + oni_ctx ctx_; + + int major; + int minor; + int patch; + + uint32_t ACQ_CLK_HZ; + + device_map_t deviceTable; + + template + size_t opt_size_(opt_t opt) + { + size_t len = 0; + if constexpr (std::is_same::value) + len = strlen(len) + 1; + else if constexpr (std::is_same::value) + len = strlen(len) + 1; + else + len = sizeof(opt); + + return len; + } + + int get_opt_(int option, void* value, size_t* size) const; + }; +} diff --git a/Source/OnixDevice.cpp b/Source/OnixDevice.cpp index 8761ac3..a1d46d3 100644 --- a/Source/OnixDevice.cpp +++ b/Source/OnixDevice.cpp @@ -22,15 +22,15 @@ #include "OnixDevice.h" -OnixDevice::OnixDevice(String name_, String headstageName, OnixDeviceType type_, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) +using namespace OnixSourcePlugin; + +OnixDevice::OnixDevice(std::string name_, std::string hubName, OnixDeviceType type_, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx, bool passthrough) : type(type_), deviceIdx(deviceIdx_) { deviceContext = ctx; name = name_; - m_headstageName = headstageName; - - if (type == OnixDeviceType::NEUROPIXELSV2E || type == OnixDeviceType::POLLEDBNO) - isPassthrough = true; + m_hubName = hubName; + isPassthrough = passthrough; } oni_dev_idx_t OnixDevice::getDeviceIdx(bool getPassthroughIndex) @@ -41,11 +41,44 @@ oni_dev_idx_t OnixDevice::getDeviceIdx(bool getPassthroughIndex) return deviceIdx; } -oni_dev_idx_t OnixDevice::getDeviceIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex) +std::string OnixDevice::createStreamName(std::vector names) +{ + std::string streamName; + + for (int i = 0; i < names.size(); i++) + { + streamName += names[i]; + + if (i != names.size() - 1) streamName += "-"; + } + + streamName.erase(std::remove(streamName.begin(), streamName.end(), ' '), streamName.end()); + + return streamName; +} + +oni_dev_idx_t OnixDevice::getHubIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex) { + if (passthroughIndex != (uint32_t)PassthroughIndex::A && passthroughIndex != (uint32_t)PassthroughIndex::B) + { + LOGE("Invalid passthrough index given. Value was ", passthroughIndex); + return 0; + } + oni_dev_idx_t idx = (passthroughIndex - 7) << 8; +} - if (type == OnixDeviceType::POLLEDBNO) +oni_dev_idx_t OnixDevice::getDeviceIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex) const +{ + if (passthroughIndex != (uint32_t)PassthroughIndex::A && passthroughIndex != (uint32_t)PassthroughIndex::B) + { + LOGE("Invalid passthrough index given. Value was ", passthroughIndex); + return 0; + } + + auto idx = getHubIndexFromPassthroughIndex(passthroughIndex); + + if (type == OnixDeviceType::POLLEDBNO && m_hubName == NEUROPIXELSV2E_HEADSTAGE_NAME) idx++; return idx; @@ -58,51 +91,127 @@ OnixDeviceType OnixDevice::getDeviceType() const String OnixDevice::getStreamIdentifier() { - String streamIdentifier = "onix."; + String streamIdentifier = "onix"; // Insert the headstage or breakout board - if (getHeadstageName() == NEUROPIXELSV1F_HEADSTAGE_NAME) + if (getHubName() == NEUROPIXELSV1F_HEADSTAGE_NAME) { - streamIdentifier += "npx1f."; + streamIdentifier += ".npx1f"; } - else if (getHeadstageName() == NEUROPIXELSV2E_HEADSTAGE_NAME) + else if (getHubName() == NEUROPIXELSV2E_HEADSTAGE_NAME) { - streamIdentifier += "npx2e."; + streamIdentifier += ".npx2e"; } - else if (getHeadstageName() == BREAKOUT_BOARD_NAME) + else if (getHubName() == BREAKOUT_BOARD_NAME) { - streamIdentifier += "breakout."; + streamIdentifier += ".breakout"; + } + else + { + streamIdentifier += ".headstage"; } // Insert the device if (getDeviceType() == OnixDeviceType::ANALOGIO) { - streamIdentifier += "analogio"; + streamIdentifier += ".analogio"; } else if (getDeviceType() == OnixDeviceType::BNO || getDeviceType() == OnixDeviceType::POLLEDBNO) { - streamIdentifier += "9dof"; + streamIdentifier += ".9dof"; } else if (getDeviceType() == OnixDeviceType::DIGITALIO) { - streamIdentifier += "digitalio"; + streamIdentifier += ".digitalio"; } else if (getDeviceType() == OnixDeviceType::HARPSYNCINPUT) { - streamIdentifier += "harp"; + streamIdentifier += ".harp"; } else if (getDeviceType() == OnixDeviceType::MEMORYMONITOR) { - streamIdentifier += "memory"; + streamIdentifier += ".memory"; } - else if (getDeviceType() == OnixDeviceType::NEUROPIXELS_1) + else if (getDeviceType() == OnixDeviceType::NEUROPIXELSV1F) { - streamIdentifier += "npx1f"; + streamIdentifier += ".npx1f"; } else if (getDeviceType() == OnixDeviceType::NEUROPIXELSV2E) { - streamIdentifier += "npx2e"; + streamIdentifier += ".npx2e"; + } + else + { + streamIdentifier += ".device"; } return streamIdentifier; } + +int OnixDevice::getPortOffset(PortName port) +{ + return (uint32_t)port << 8; +} + +std::string OnixDevice::getPortName(int offset) +{ + switch (offset) + { + case 0: + return ""; + case HubAddressPortA: + return "Port A"; + case HubAddressPortB: + return "Port B"; + default: + return ""; + } +} + +std::string OnixDevice::getPortName(PortName port) +{ + return getPortName(getPortOffset(port)); +} + +std::string OnixDevice::getPortNameFromIndex(oni_dev_idx_t index) +{ + return getPortName(getOffsetFromIndex(index)); +} + +PortName OnixDevice::getPortFromIndex(oni_dev_idx_t index) +{ + return index & (1 << 8) ? PortName::PortA : PortName::PortB; +} + +int OnixDevice::getOffsetFromIndex(oni_dev_idx_t index) +{ + return index & 0x0000FF00; +} + +std::vector OnixDevice::getUniqueOffsetsFromIndices(std::vector indices, bool ignoreBreakoutBoard) +{ + std::set offsets; + + for (auto index : indices) + { + auto offset = getOffsetFromIndex(index); + + if (offset == HubAddressBreakoutBoard && ignoreBreakoutBoard) continue; + + offsets.emplace(offset); + } + + return std::vector(offsets.begin(), offsets.end()); +} + +Array OnixDevice::getUniquePortsFromIndices(std::vector indices) +{ + Array ports; + + for (auto index : indices) + { + ports.addIfNotAlreadyThere(getPortFromIndex(index)); + } + + return ports; +} diff --git a/Source/OnixDevice.h b/Source/OnixDevice.h index 2d45a3e..92b4df4 100644 --- a/Source/OnixDevice.h +++ b/Source/OnixDevice.h @@ -29,202 +29,212 @@ #include #include -#include "I2CRegisterContext.h" -#include "NeuropixComponents.h" #include "Onix1.h" using namespace std::chrono; -enum class PortName +namespace OnixSourcePlugin { - PortA = 1, - PortB = 2 -}; - -enum class OnixDeviceType { - BNO, - POLLEDBNO, - NEUROPIXELS_1, - NEUROPIXELSV2E, - PORT_CONTROL, - MEMORYMONITOR, - OUTPUTCLOCK, - HEARTBEAT, - HARPSYNCINPUT, - ANALOGIO, - DIGITALIO, -}; - -struct StreamInfo { -public: - StreamInfo() {} - - StreamInfo(String name, String description, String streamIdentifier, int numChannels, float sampleRate, String channelPrefix, ContinuousChannel::Type channelType, - float bitVolts, String units, StringArray channelNameSuffixes, String channelIdentifierDataType, StringArray channelIdentifierSubTypes = {}) + enum class PortName { - m_name = name; - m_description = description; - m_streamIdentifier = streamIdentifier; - m_numChannels = numChannels; - m_sampleRate = sampleRate; - m_channelPrefix = channelPrefix; - m_channelType = channelType; - m_bitVolts = bitVolts; - m_units = units; - m_channelNameSuffixes = channelNameSuffixes; - m_channelIdentifierDataType = channelIdentifierDataType; - m_channelIdentifierSubTypes = channelIdentifierSubTypes; - - if (m_numChannels != m_channelNameSuffixes.size()) - { - if (m_channelNameSuffixes.size() != 0) - LOGE("Difference between number of channels and channel name suffixes. Generating default suffixes instead."); + PortA = 1, + PortB = 2 + }; - m_channelNameSuffixes.clear(); - m_channelNameSuffixes.ensureStorageAllocated(numChannels); + enum class OnixDeviceType { + BNO, + POLLEDBNO, + NEUROPIXELSV1F, + NEUROPIXELSV2E, + PORT_CONTROL, + MEMORYMONITOR, + OUTPUTCLOCK, + HEARTBEAT, + HARPSYNCINPUT, + ANALOGIO, + DIGITALIO, + }; - for (int i = 0; i < m_numChannels; i++) - { - m_channelNameSuffixes.add(String(i + 1)); - } - } + struct StreamInfo { + public: + StreamInfo() {} - if (m_channelIdentifierSubTypes.size() > 0 && m_numChannels != m_channelIdentifierSubTypes.size()) + StreamInfo(String name, String description, String streamIdentifier, int numChannels, float sampleRate, String channelPrefix, ContinuousChannel::Type channelType, + float bitVolts, String units, StringArray channelNameSuffixes, String channelIdentifierDataType, StringArray channelIdentifierSubTypes = {}) { - if (m_channelIdentifierSubTypes.size() == 1) + m_name = name; + m_description = description; + m_streamIdentifier = streamIdentifier; + m_numChannels = numChannels; + m_sampleRate = sampleRate; + m_channelPrefix = channelPrefix; + m_channelType = channelType; + m_bitVolts = bitVolts; + m_units = units; + m_channelNameSuffixes = channelNameSuffixes; + m_channelIdentifierDataType = channelIdentifierDataType; + m_channelIdentifierSubTypes = channelIdentifierSubTypes; + + if (m_numChannels != m_channelNameSuffixes.size()) { - for (int i = 1; i < m_numChannels; i++) + if (m_channelNameSuffixes.size() != 0) + LOGE("Difference between number of channels and channel name suffixes. Generating default suffixes instead."); + + m_channelNameSuffixes.clear(); + m_channelNameSuffixes.ensureStorageAllocated(numChannels); + + for (int i = 0; i < m_numChannels; i++) { - m_channelIdentifierSubTypes.add(m_channelIdentifierSubTypes[0]); + m_channelNameSuffixes.add(String(i + 1)); } } - else + + if (m_channelIdentifierSubTypes.size() > 0 && m_numChannels != m_channelIdentifierSubTypes.size()) { - LOGE("Difference between number of channels and channel identifier subtypes. Generating default subtypes instead."); + if (m_channelIdentifierSubTypes.size() == 1) + { + for (int i = 1; i < m_numChannels; i++) + { + m_channelIdentifierSubTypes.add(m_channelIdentifierSubTypes[0]); + } + } + else + { + LOGE("Difference between number of channels and channel identifier subtypes. Generating default subtypes instead."); - m_channelIdentifierSubTypes.clear(); - m_channelIdentifierSubTypes.ensureStorageAllocated(numChannels); + m_channelIdentifierSubTypes.clear(); + m_channelIdentifierSubTypes.ensureStorageAllocated(numChannels); - for (int i = 0; i < m_numChannels; i++) - { - m_channelIdentifierSubTypes.add(String(i + 1)); + for (int i = 0; i < m_numChannels; i++) + { + m_channelIdentifierSubTypes.add(String(i + 1)); + } } } - } + }; + + String getName() const { return m_name; } + String getDescription() const { return m_description; } + String getStreamIdentifier() const { return m_streamIdentifier; } + int getNumChannels() const { return m_numChannels; } + float getSampleRate() const { return m_sampleRate; } + String getChannelPrefix() const { return m_channelPrefix; } + ContinuousChannel::Type getChannelType() const { return m_channelType; } + float getBitVolts() const { return m_bitVolts; } + String getUnits() const { return m_units; } + StringArray getChannelNameSuffixes() const { return m_channelNameSuffixes; } + String getChannelIdentifierDataType() const { return m_channelIdentifierDataType; } + StringArray getChannelIdentifierSubTypes() const { return m_channelIdentifierSubTypes; } + + private: + String m_name = "name"; + String m_description = "description"; + String m_streamIdentifier = "identifier"; + int m_numChannels = 0; + float m_sampleRate = 0; + String m_channelPrefix = "channelPrefix"; + ContinuousChannel::Type m_channelType = ContinuousChannel::Type::INVALID; + float m_bitVolts = 1.0f; + String m_units = "units"; + StringArray m_channelNameSuffixes = { "suffixes" }; + String m_channelIdentifierDataType = "datatype"; + StringArray m_channelIdentifierSubTypes = { "subtypes" }; }; - String getName() const { return m_name; } - String getDescription() const { return m_description; } - String getStreamIdentifier() const { return m_streamIdentifier; } - int getNumChannels() const { return m_numChannels; } - float getSampleRate() const { return m_sampleRate; } - String getChannelPrefix() const { return m_channelPrefix; } - ContinuousChannel::Type getChannelType() const { return m_channelType; } - float getBitVolts() const { return m_bitVolts; } - String getUnits() const { return m_units; } - StringArray getChannelNameSuffixes() const { return m_channelNameSuffixes; } - String getChannelIdentifierDataType() const { return m_channelIdentifierDataType; } - StringArray getChannelIdentifierSubTypes() const { return m_channelIdentifierSubTypes; } - -private: - String m_name = "name"; - String m_description = "description"; - String m_streamIdentifier = "identifier"; - int m_numChannels = 0; - float m_sampleRate = 0; - String m_channelPrefix = "channelPrefix"; - ContinuousChannel::Type m_channelType = ContinuousChannel::Type::INVALID; - float m_bitVolts = 1.0f; - String m_units = "units"; - StringArray m_channelNameSuffixes = { "suffixes" }; - String m_channelIdentifierDataType = "datatype"; - StringArray m_channelIdentifierSubTypes = { "subtypes" }; -}; - -/** - - Streams data from an ONIX device + /** -*/ -class OnixDevice -{ -public: + Streams data from an ONIX device - /** Constructor */ - OnixDevice(String name_, String headstageName, OnixDeviceType type_, const oni_dev_idx_t, std::shared_ptr oni_ctx); + */ + class OnixDevice + { + public: - /** Destructor */ - ~OnixDevice() { } + /** Constructor */ + OnixDevice(std::string name_, std::string hubName, OnixDeviceType type_, const oni_dev_idx_t, std::shared_ptr oni_ctx, bool passthrough = false); - virtual void addFrame(oni_frame_t*) {}; + /** Destructor */ + ~OnixDevice() { } - virtual void processFrames() {}; + virtual void addFrame(oni_frame_t*) {}; - const String getName() { return name; } + virtual void processFrames() {}; - bool isEnabled() const { return enabled; } + const std::string getName() { return name; } - void setEnabled(bool newState) { enabled = newState; } + bool isEnabled() const { return enabled; } - virtual int configureDevice() { return -1; }; + void setEnabled(bool newState) { enabled = newState; } - virtual bool updateSettings() { return false; }; + virtual int configureDevice() { return -1; }; - virtual void startAcquisition() {}; + virtual bool updateSettings() { return false; }; - virtual void stopAcquisition() {}; + virtual void startAcquisition() {}; - /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ - virtual void addSourceBuffers(OwnedArray& sourceBuffers) {}; + virtual void stopAcquisition() {}; - oni_dev_idx_t getDeviceIdx(bool getPassthroughIndex = false); + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + virtual void addSourceBuffers(OwnedArray& sourceBuffers) {}; - /** Creates a stream name using the provided inputs, returning a String following the pattern: name[0]-name[1]-name[2]-etc., with all spaces removed */ - static String createStreamName(std::vector names) - { - String streamName; + oni_dev_idx_t getDeviceIdx(bool getPassthroughIndex = false); - for (int i = 0; i < names.size(); i++) - { - streamName += names[i].removeCharacters(" "); + /** Creates a stream name using the provided inputs, returning a String following the pattern: name[0]-name[1]-name[2]-etc., with all spaces removed */ + static std::string createStreamName(std::vector names); + + Array streamInfos; - if (i != names.size() - 1) streamName += "-"; - } + const int bufferSizeInSeconds = 10; - return streamName; - } + static constexpr int HubAddressBreakoutBoard = 0; + static constexpr int HubAddressPortA = 256; + static constexpr int HubAddressPortB = 512; - Array streamInfos; + std::string getHubName() { return m_hubName; } + void setHubName(std::string hubName) { m_hubName = hubName; } - const int bufferSizeInSeconds = 10; + static int getPortOffset(PortName port); + static std::string getPortName(int offset); + static std::string getPortName(PortName port); + static std::string getPortNameFromIndex(oni_dev_idx_t index); + static PortName getPortFromIndex(oni_dev_idx_t index); + static int getOffsetFromIndex(oni_dev_idx_t index); + static std::vector getUniqueOffsetsFromIndices(std::vector indices, bool ignoreBreakoutBoard = true); + static Array getUniquePortsFromIndices(std::vector indices); - String getHeadstageName() { return m_headstageName; } - void setHeadstageName(String headstage) { m_headstageName = headstage; } + OnixDeviceType getDeviceType() const; - OnixDeviceType getDeviceType() const; + /** Returns a string for this device that follows the pattern: onix.[hub].[device] */ + String getStreamIdentifier(); - /** Returns a string for this device that follows the pattern: onix.[headstage|breakout].[device] */ - String getStreamIdentifier(); + static oni_dev_idx_t getHubIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex); -protected: + protected: - oni_dev_idx_t getDeviceIndexFromPassthroughIndex(oni_dev_idx_t hubIndex); + oni_dev_idx_t getDeviceIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex) const; - const oni_dev_idx_t deviceIdx; - std::shared_ptr deviceContext; + const oni_dev_idx_t deviceIdx; + std::shared_ptr deviceContext; -private: + private: - String name; + std::string name; - bool enabled = true; - bool isPassthrough = false; + bool enabled = true; + bool isPassthrough = false; - String m_headstageName; + std::string m_hubName; - const OnixDeviceType type; + const OnixDeviceType type; - JUCE_LEAK_DETECTOR(OnixDevice); -}; + enum class PassthroughIndex : uint32_t + { + A = 8, + B = 9 + }; + + JUCE_LEAK_DETECTOR(OnixDevice); + }; -using OnixDeviceVector = std::vector>; + using OnixDeviceVector = std::vector>; +} diff --git a/Source/OnixSource.cpp b/Source/OnixSource.cpp index 7edcdaa..bd39d8b 100644 --- a/Source/OnixSource.cpp +++ b/Source/OnixSource.cpp @@ -21,8 +21,11 @@ */ #include "OnixSource.h" +#include "OnixSourceCanvas.h" #include "Devices/DeviceList.h" +using namespace OnixSourcePlugin; + OnixSource::OnixSource(SourceNode* sn) : DataThread(sn), devicesFound(false), @@ -55,16 +58,18 @@ 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); - portA = std::make_shared(PortName::PortA, context); portB = std::make_shared(PortName::PortB, context); if (!context->isInitialized()) { LOGE("Failed to initialize context."); return; } } +void OnixSource::registerParameters() +{ + addBooleanParameter(Parameter::PROCESSOR_SCOPE, "passthroughA", "Passthrough A", "Enables passthrough mode for e-variant headstages on Port A", false, true); + addBooleanParameter(Parameter::PROCESSOR_SCOPE, "passthroughB", "Passthrough B", "Enables passthrough mode for e-variant headstages on Port B", false, true); +} + DataThread* OnixSource::createDataThread(SourceNode* sn) { return new OnixSource(sn); @@ -83,7 +88,7 @@ void OnixSource::disconnectDevices(bool updateStreamInfo) sourceBuffers.clear(true); sources.clear(); - headstages.clear(); + hubNames.clear(); devicesFound = false; @@ -93,6 +98,56 @@ void OnixSource::disconnectDevices(bool updateStreamInfo) if (updateStreamInfo) CoreServices::updateSignalChain(editor); } +template +bool OnixSource::configureDevice(OnixDeviceVector& sources, + OnixSourceCanvas* canvas, + std::string deviceName, + std::string hubName, + OnixDeviceType deviceType, + const oni_dev_idx_t deviceIdx, + std::shared_ptr ctx) +{ + std::shared_ptr device = std::static_pointer_cast(canvas->getDevicePtr(Device::getDeviceType(), deviceIdx)); + + if (device != nullptr) + { + if (device->getName() != deviceName || device->getHubName() != hubName) + { + LOGD("Difference in names found for device at address ", deviceIdx, ". Found ", deviceName, " on ", hubName, ", but was expecting ", device->getName(), " on ", device->getHubName()); + } + } + else + { + // NB: Create a new device if a tab does not exist, but the device was found while connecting + LOGD("Creating new device ", deviceName, " on ", hubName); + device = std::make_shared(deviceName, hubName, deviceIdx, ctx); + } + + if (device == nullptr) + { + LOGE("Could not create or find ", deviceName, " on ", hubName); + return false; + } + + int res = -1; + + try + { + res = device->configureDevice(); + } + catch (const error_str& e) + { + LOGE(e.what()); + AlertWindow::showMessageBox(MessageBoxIconType::WarningIcon, "Configuration Error", e.what()); + + return false; + } + + sources.emplace_back(device); + + return res == ONI_ESUCCESS; +} + void OnixSource::initializeDevices(bool updateStreamInfo) { if (context == nullptr || !context->isInitialized()) @@ -139,64 +194,70 @@ void OnixSource::initializeDevices(bool updateStreamInfo) return; } + auto hubIds = context->getHubIds(); + + if (hubIds.size() == 0) + { + LOGE("No hub IDs found."); + if (updateStreamInfo) CoreServices::updateSignalChain(editor); + return; + } + devicesFound = true; const int bufferSizeInSeconds = 10; - int npxProbeIdx = 0; - for (const auto& [index, device] : deviceMap) + // NB: Search through all hubs, and initialize devices + for (const auto& [hubIndex, hubId] : hubIds) { - if (device.id == ONIX_NEUROPIX1R0) + if (hubId == ONIX_HUB_FMCHOST) // NB: Breakout Board { - auto np1 = std::make_shared("Probe" + String(npxProbeIdx++), index, context); + hubNames.insert({ hubIndex, BREAKOUT_BOARD_NAME }); + bool result = false; + auto canvas = editor->getCanvas(); - int res = np1->configureDevice(); + result = configureDevice(sources, canvas, "Heartbeat", BREAKOUT_BOARD_NAME, Heartbeat::getDeviceType(), hubIndex, context); + if (!result) devicesFound = false; - if (res != 0) - { - if (res == -1) - { - LOGE("Device Idx: ", index, " Unable to read probe serial number. Device not found."); + result = configureDevice(sources, canvas, "Output Clock", BREAKOUT_BOARD_NAME, OutputClock::getDeviceType(), hubIndex + 5, context); + if (!result) devicesFound = false; - continue; - } - else if (res == -2) - { - LOGE("Device Idx: ", index, " Error enabling device stream."); + result = configureDevice(sources, canvas, "Analog IO", BREAKOUT_BOARD_NAME, AnalogIO::getDeviceType(), hubIndex + 6, context); + if (!result) devicesFound = false; - continue; - } - else if (res == -3) - { - LOGE("Error enabling device ", index); + result = configureDevice(sources, canvas, "Digital IO", BREAKOUT_BOARD_NAME, DigitalIO::getDeviceType(), hubIndex + 7, context); + if (!result) devicesFound = false; - continue; - } - } + result = configureDevice(sources, canvas, "Memory Monitor", BREAKOUT_BOARD_NAME, MemoryMonitor::getDeviceType(), hubIndex + 10, context); + if (!result) devicesFound = false; - sources.emplace_back(np1); - headstages.insert({ PortController::getOffsetFromIndex(index), NEUROPIXELSV1F_HEADSTAGE_NAME }); + result = configureDevice(sources, canvas, "Harp Sync Input", BREAKOUT_BOARD_NAME, HarpSyncInput::getDeviceType(), hubIndex + 12, context); + if (!result) devicesFound = false; } - else if (device.id == ONIX_BNO055) + else if (hubId == ONIX_HUB_HSNP) { - String headstage = headstages.find(PortController::getOffsetFromIndex(index))->second; - - auto bno = std::make_shared("BNO055", headstage, index, context); + hubNames.insert({ hubIndex, NEUROPIXELSV1F_HEADSTAGE_NAME }); + bool result = false; + auto canvas = editor->getCanvas(); - int result = bno->configureDevice(); - - if (result != 0) + for (int i = 0; i < 2; i++) { - LOGE("Device Idx: ", index, " Error enabling device stream."); - continue; + result = configureDevice(sources, canvas, "Probe" + std::to_string(i), NEUROPIXELSV1F_HEADSTAGE_NAME, Neuropixels1f::getDeviceType(), hubIndex + i, context); + if (!result) devicesFound = false; } - sources.emplace_back(bno); + result = configureDevice(sources, canvas, "BNO055", NEUROPIXELSV1F_HEADSTAGE_NAME, Bno055::getDeviceType(), hubIndex + 2, context); + if (!result) devicesFound = false; } - else if (device.id == ONIX_DS90UB9RAW) + } + + // NB: Search for passthrough devices, and initialize any headstages found in passthrough mode + for (const auto& [index, device] : deviceMap) + { + if (device.id == ONIX_DS90UB9RAW) { LOGD("Passthrough device detected"); - //initialize main i2c parameter + auto serializer = std::make_unique(DS90UB9x::SER_ADDR, index, context); serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::SCLHIGH, 20); serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::SCLLOW, 20); @@ -204,134 +265,30 @@ void OnixSource::initializeDevices(bool updateStreamInfo) auto EEPROM = std::make_unique(index, context); uint32_t hsid = EEPROM->GetHeadStageID(); LOGD("Detected headstage ", hsid); - if (hsid == ONIX_HUB_HSNP2E) - { - auto np2 = std::make_shared("", index, context); - int res = np2->configureDevice(); - if (res != ONI_ESUCCESS) - { - if (res == -1) - { - LOGE("Device Idx: ", index, " Unable to read probe serial number. Device not found."); - } - //TODO add other errors if needed - continue; - } - - sources.emplace_back(np2); - auto polledBno = std::make_shared("BNO055", NEUROPIXELSV2E_HEADSTAGE_NAME, index, context); + bool result = false; + auto canvas = editor->getCanvas(); - if (polledBno->configureDevice() != ONI_ESUCCESS) - { - if (res == -1) - { - LOGE("Context is not initialized properly for PolledBno055"); - } - else if (res == -2) - { - LOGE("Error writing bytes for PolledBno055"); - } - continue; - } - - polledBno->setBnoAxisMap(PolledBno055::Bno055AxisMap::YZX); - polledBno->setBnoAxisSign(PolledBno055::Bno055AxisSign::MirrorXAndY); - - sources.emplace_back(polledBno); - - headstages.insert({ PortController::getOffsetFromIndex(index), NEUROPIXELSV2E_HEADSTAGE_NAME }); - } - } - 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) + if (hsid == ONIX_HUB_HSNP2E) { - LOGE("Device Idx: ", index, " Error enabling device stream."); - continue; - } + auto hubIndex = OnixDevice::getHubIndexFromPassthroughIndex(index); - 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); + result = configureDevice(sources, canvas, "", NEUROPIXELSV2E_HEADSTAGE_NAME, Neuropixels2e::getDeviceType(), hubIndex, context); + if (!result) devicesFound = false; - int result = analogIO->configureDevice(); + result = configureDevice(sources, canvas, "BNO055", NEUROPIXELSV2E_HEADSTAGE_NAME, PolledBno055::getDeviceType(), hubIndex + 1, context); + if (!result) devicesFound = false; - if (result != 0) - { - LOGE("Device Idx: ", index, " Error enabling device stream."); - continue; - } + if (sources.back()->getDeviceType() != OnixDeviceType::POLLEDBNO) + LOGE("Unknown device encountered when setting headstage."); - 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); + const auto& polledBno = std::static_pointer_cast(sources.back()); - int result = digitalIO->configureDevice(); + polledBno->setBnoAxisMap(PolledBno055::Bno055AxisMap::YZX); + polledBno->setBnoAxisSign(PolledBno055::Bno055AxisSign::MirrorXAndY); - if (result != 0) - { - LOGE("Device Idx: ", index, " Error enabling device stream."); - continue; + hubNames.insert({ PortController::getOffsetFromIndex(polledBno->getDeviceIdx()), NEUROPIXELSV2E_HEADSTAGE_NAME }); } - - sources.emplace_back(digitalIO); - headstages.insert({ PortController::getOffsetFromIndex(index), BREAKOUT_BOARD_NAME }); } } @@ -429,12 +386,12 @@ std::map OnixSource::createDeviceMap(OnixDeviceVector devic std::map OnixSource::createDeviceMap(bool filterDevices) { - return createDeviceMap(getDataSources(), filterDevices); + return createDeviceMap(getEnabledDataSources(), filterDevices); } -std::map OnixSource::getHeadstageMap() +std::map OnixSource::getHubNames() { - return headstages; + return hubNames; } void OnixSource::updateSourceBuffers() @@ -520,6 +477,22 @@ double OnixSource::getLastVoltageSet(PortName port) } } +void OnixSource::resetContext() +{ + if (context != nullptr && context->isInitialized()) + context->issueReset(); +} + +bool OnixSource::isContextInitialized() +{ + return context != nullptr && context->isInitialized(); +} + +std::shared_ptr OnixSource::getContext() +{ + return context; +} + void OnixSource::updateSettings(OwnedArray* continuousChannels, OwnedArray* eventChannels, OwnedArray* spikeChannels, @@ -546,7 +519,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel { if (!source->isEnabled()) continue; - if (source->getDeviceType() == OnixDeviceType::NEUROPIXELS_1) + if (source->getDeviceType() == OnixDeviceType::NEUROPIXELSV1F) { DeviceInfo::Settings deviceSettings{ source->getName(), @@ -573,7 +546,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel deviceInfos->add(new DeviceInfo(deviceSettings)); DataStream::Settings dataStreamSettings{ - OnixDevice::createStreamName({PortController::getPortName(PortController::getPortFromIndex(source->getDeviceIdx())), source->getHeadstageName(), source->getName()}), + OnixDevice::createStreamName({OnixDevice::getPortNameFromIndex(source->getDeviceIdx()), source->getHubName(), source->getName()}), "Continuous data from a Bno055 9-axis IMU", source->getStreamIdentifier(), source->streamInfos[0].getSampleRate() @@ -758,7 +731,7 @@ bool OnixSource::isReady() if (!isDevicesReady()) { - CoreServices::sendStatusMessage("Selected headstages do not match hardware found."); + CoreServices::sendStatusMessage("Devices not initialized properly. Check error logs for details."); return false; } diff --git a/Source/OnixSource.h b/Source/OnixSource.h index df72b1f..ce37d41 100644 --- a/Source/OnixSource.h +++ b/Source/OnixSource.h @@ -30,127 +30,138 @@ #include "FrameReader.h" #include "Devices/PortController.h" -/** - - @see DataThread, SourceNode - -*/ -class OnixSource : public DataThread +namespace OnixSourcePlugin { -public: + /** - /** Constructor */ - OnixSource(SourceNode* sn); + @see DataThread, SourceNode - /** Destructor */ - ~OnixSource() + */ + class OnixSource : public DataThread { - if (context != nullptr && context->isInitialized()) + public: + + /** Constructor */ + OnixSource(SourceNode* sn); + + /** Destructor */ + ~OnixSource() { - portA->setVoltageOverride(0.0f, false); - portB->setVoltageOverride(0.0f, false); + if (context != nullptr && context->isInitialized()) + { + portA->setVoltageOverride(0.0f, false); + portB->setVoltageOverride(0.0f, false); + } } - } - /** Static method to create DataThread */ - static DataThread* createDataThread(SourceNode* sn); + void registerParameters() override; + + /** Static method to create DataThread */ + static DataThread* createDataThread(SourceNode* sn); + + /** Creates the custom editor */ + std::unique_ptr createEditor(SourceNode* sn) override; + + /** Reads frames and adds them to the appropriate device */ + bool updateBuffer() override; - /** Creates the custom editor */ - std::unique_ptr createEditor(SourceNode* sn) override; + /** Returns true if the processor is ready to stream data, including all hardware initialization. Returns false if not ready. */ + bool isReady() override; - /** Reads frames and adds them to the appropriate device */ - bool updateBuffer() override; + /** Returns true if the hardware is connected, false otherwise.*/ + bool foundInputSource() override; - /** Returns true if the processor is ready to stream data, including all hardware initialization. Returns false if not ready. */ - bool isReady() override; + /** Returns true if the deviceMap matches the settings tabs that are open */ + bool isDevicesReady(); - /** Returns true if the hardware is connected, false otherwise.*/ - bool foundInputSource() override; + /** Initializes data transfer.*/ + bool startAcquisition() override; - /** Returns true if the deviceMap matches the settings tabs that are open */ - bool isDevicesReady(); + /** Stops data transfer.*/ + bool stopAcquisition() override; - /** Initializes data transfer.*/ - bool startAcquisition() override; + void updateDiscoveryParameters(PortName port, DiscoveryParameters parameters); - /** Stops data transfer.*/ - bool stopAcquisition() override; + /** Takes a string from the editor. Can be an empty string to allow for automated discovery */ + bool configurePortVoltage(PortName port, String voltage) const; - void updateDiscoveryParameters(PortName port, DiscoveryParameters parameters); + /** Sets the port voltage */ + void setPortVoltage(PortName port, double voltage) const; - /** Takes a string from the editor. Can be an empty string to allow for automated discovery */ - bool configurePortVoltage(PortName port, String voltage) const; + double getLastVoltageSet(PortName port); - /** Sets the port voltage */ - void setPortVoltage(PortName port, double voltage) const; + void resetContext(); - double getLastVoltageSet(PortName port); + bool isContextInitialized(); - void resetContext() { if (context != nullptr && context->isInitialized()) context->issueReset(); } + std::shared_ptr getContext(); - bool isContextInitialized() { return context != nullptr && context->isInitialized(); } + void initializeDevices(bool updateStreamInfo = false); - void initializeDevices(bool updateStreamInfo = false); + void disconnectDevices(bool updateStreamInfo = false); - void disconnectDevices(bool updateStreamInfo = false); + OnixDeviceVector getDataSources(); - OnixDeviceVector getDataSources(); + OnixDeviceVector getEnabledDataSources(); - OnixDeviceVector getEnabledDataSources(); + OnixDeviceVector getDataSourcesFromPort(PortName port); - OnixDeviceVector getDataSourcesFromPort(PortName port); + OnixDeviceVector getDataSourcesFromOffset(int offset); - OnixDeviceVector getDataSourcesFromOffset(int offset); + std::shared_ptr getDevice(OnixDeviceType type); - std::shared_ptr getDevice(OnixDeviceType type); + static std::map createDeviceMap(OnixDeviceVector, bool filterDevices = false); - static std::map createDeviceMap(OnixDeviceVector, bool filterDevices = false); + std::map createDeviceMap(bool filterDevices = false); - std::map createDeviceMap(bool filterDevices = false); + std::map getHubNames(); - std::map getHeadstageMap(); + String getLiboniVersion() { if (context != nullptr && context->isInitialized()) return context->getVersion(); else return ""; } - String getLiboniVersion() { if (context != nullptr && context->isInitialized()) return context->getVersion(); else return ""; } + void updateSourceBuffers(); - void updateSourceBuffers(); + // DataThread Methods + void updateSettings(OwnedArray* continuousChannels, + OwnedArray* eventChannels, + OwnedArray* spikeChannels, + OwnedArray* sourceStreams, + OwnedArray* devices, + OwnedArray* configurationObjects); - // DataThread Methods - void updateSettings(OwnedArray* continuousChannels, - OwnedArray* eventChannels, - OwnedArray* spikeChannels, - OwnedArray* sourceStreams, - OwnedArray* devices, - OwnedArray* configurationObjects); + private: -private: + /** Available data sources */ + OnixDeviceVector sources; + OnixDeviceVector enabledSources; - /** Available data sources */ - OnixDeviceVector sources; - OnixDeviceVector enabledSources; + /** Available headstages, indexed by their offset value */ + std::map hubNames; - /** Available headstages, indexed by their offset value */ - std::map headstages; + /** Pointer to the editor */ + OnixSourceEditor* editor; - /** Pointer to the editor */ - OnixSourceEditor* editor; + /** Thread that reads frames */ + std::unique_ptr frameReader; - /** Thread that reads frames */ - std::unique_ptr frameReader; + std::shared_ptr context = nullptr; - std::shared_ptr context = nullptr; + std::shared_ptr portA; + std::shared_ptr portB; - std::shared_ptr portA; - std::shared_ptr portB; + const oni_size_t block_read_size = 2048; - const oni_size_t block_read_size = 2048; + bool devicesFound = false; - bool devicesFound = false; + void addIndividualStreams(Array, OwnedArray*, OwnedArray*, OwnedArray*); - void addIndividualStreams(Array, OwnedArray*, OwnedArray*, OwnedArray*); + void addCombinedStreams(DataStream::Settings, Array, OwnedArray*, OwnedArray*, OwnedArray*); - void addCombinedStreams(DataStream::Settings, Array, OwnedArray*, OwnedArray*, OwnedArray*); + String createContinuousChannelIdentifier(StreamInfo streamInfo, int channelNumber); - String createContinuousChannelIdentifier(StreamInfo streamInfo, int channelNumber); + /** Template method to initialize an OnixDevice and add it to the currently active OnixDeviceVector variable */ + template + static bool configureDevice(OnixDeviceVector&, OnixSourceCanvas*, std::string, std::string, OnixDeviceType, const oni_dev_idx_t, std::shared_ptr); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OnixSource); -}; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OnixSource); + }; +} diff --git a/Source/OnixSourceCanvas.cpp b/Source/OnixSourceCanvas.cpp index f845bae..8dd9bdb 100644 --- a/Source/OnixSourceCanvas.cpp +++ b/Source/OnixSourceCanvas.cpp @@ -22,6 +22,9 @@ #include "OnixSourceCanvas.h" #include "OnixSource.h" +#include "OnixSourceEditor.h" + +using namespace OnixSourcePlugin; OnixSourceCanvas::OnixSourceCanvas(GenericProcessor* processor_, OnixSourceEditor* editor_, OnixSource* onixSource_) : Visualizer(processor_), @@ -41,12 +44,12 @@ CustomTabComponent* OnixSourceCanvas::addTopLevelTab(String tabName, int index) topLevelTabComponent->addTab(tabName, Colours::grey, tab, true, index); tab->setName(tabName); - headstageTabs.add(tab); + hubTabs.add(tab); return tab; } -void OnixSourceCanvas::addHub(String hubName, int offset) +void OnixSourceCanvas::addHub(std::string hubName, int offset) { CustomTabComponent* tab = nullptr; OnixDeviceVector devices; @@ -56,18 +59,18 @@ void OnixSourceCanvas::addHub(String hubName, int offset) { tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port); - devices.emplace_back(std::make_shared("Probe0", offset, nullptr)); - devices.emplace_back(std::make_shared("Probe1", offset + 1, nullptr)); - devices.emplace_back(std::make_shared("BNO055", hubName, offset + 2, nullptr)); + devices.emplace_back(std::make_shared("Probe0", hubName, offset, source->getContext())); + devices.emplace_back(std::make_shared("Probe1", hubName, offset + 1, source->getContext())); + devices.emplace_back(std::make_shared("BNO055", hubName, offset + 2, source->getContext())); } 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)); + devices.emplace_back(std::make_shared("Output Clock", hubName, 5, source->getContext())); + devices.emplace_back(std::make_shared("Analog IO", hubName, 6, source->getContext())); + devices.emplace_back(std::make_shared("Digital IO", hubName, 7, source->getContext())); + devices.emplace_back(std::make_shared("Harp Sync Input", hubName, 12, source->getContext())); } else if (hubName == NEUROPIXELSV2E_HEADSTAGE_NAME) { @@ -75,8 +78,8 @@ void OnixSourceCanvas::addHub(String hubName, int offset) tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port); - devices.emplace_back(std::make_shared("", passthroughIndex, nullptr)); - devices.emplace_back(std::make_shared("BNO055", hubName, passthroughIndex, nullptr)); + devices.emplace_back(std::make_shared("", hubName, passthroughIndex, source->getContext())); + devices.emplace_back(std::make_shared("BNO055", hubName, passthroughIndex, source->getContext())); } if (tab != nullptr && devices.size() > 0) @@ -85,15 +88,30 @@ void OnixSourceCanvas::addHub(String hubName, int offset) } } +std::shared_ptr OnixSourceCanvas::getDevicePtr(OnixDeviceType deviceType, int deviceIndex) +{ + for (const auto& settingsInterface : settingsInterfaces) + { + auto device = settingsInterface->getDevice(); + + if (device->getDeviceType() == deviceType && device->getDeviceIdx() == deviceIndex) + { + return device; + } + } + + return nullptr; +} + void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* tab, OnixDeviceVector devices) { int portTabNumber = 0; for (const auto& device : devices) { - if (device->getDeviceType() == OnixDeviceType::NEUROPIXELS_1) + if (device->getDeviceType() == OnixDeviceType::NEUROPIXELSV1F) { - auto neuropixInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + auto neuropixInterface = std::make_shared(std::static_pointer_cast(device), editor, this); addInterfaceToTab(device->getName(), tab, neuropixInterface); } else if (device->getDeviceType() == OnixDeviceType::BNO) @@ -124,7 +142,9 @@ void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* tab, OnixDeviceVec else if (device->getDeviceType() == OnixDeviceType::NEUROPIXELSV2E) { auto npxv2eInterface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(device->getHeadstageName(), tab, npxv2eInterface); + std::string substring = " Headstage"; + std::string hubName = device->getHubName(); + addInterfaceToTab(hubName.erase(hubName.find(substring), substring.size()), tab, npxv2eInterface); } else if (device->getDeviceType() == OnixDeviceType::POLLEDBNO) { @@ -140,99 +160,18 @@ void OnixSourceCanvas::addInterfaceToTab(String tabName, CustomTabComponent* tab tab->addTab(tabName, Colours::darkgrey, CustomViewport::createCustomViewport(interface_.get()), true); } -bool OnixSourceCanvas::compareDeviceNames(String dev1, String dev2) +std::string OnixSourceCanvas::getTopLevelTabName(PortName port, std::string headstage) { - StringRef charsToTrim = "-ABCD"; - - if (dev1 == dev2) - return true; - else if (dev1.trimCharactersAtEnd(charsToTrim) == dev2.trimCharactersAtEnd(charsToTrim)) - return true; - else - return false; + return OnixDevice::getPortName(port) + ": " + headstage; } -void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptr device) -{ - int ind = -1; - - for (int j = 0; j < settingsInterfaces.size(); j++) - { - auto selectedDevice = settingsInterfaces[j]->getDevice(); - - if (device->getDeviceIdx() == selectedDevice->getDeviceIdx() && - compareDeviceNames(device->getName(), selectedDevice->getName())) - { - ind = j; - break; - } - } - - if (ind == -1) - { - if (device->getDeviceType() != OnixDeviceType::MEMORYMONITOR && device->getDeviceType() != OnixDeviceType::HEARTBEAT) - LOGD("Unable to match " + device->getName() + " to an open tab."); - - return; - } - - auto selectedDevice = settingsInterfaces[ind]->getDevice(); - - if (device->getDeviceType() == OnixDeviceType::NEUROPIXELS_1) - { - auto npx1Found = std::static_pointer_cast(device); - auto npx1Selected = std::static_pointer_cast(selectedDevice); - npx1Found->setSettings(npx1Selected->settings[0].get()); - npx1Found->adcCalibrationFilePath = npx1Selected->adcCalibrationFilePath; - npx1Found->gainCalibrationFilePath = npx1Selected->gainCalibrationFilePath; - npx1Found->setCorrectOffset(npx1Selected->getCorrectOffset()); - } - else if (device->getDeviceType() == OnixDeviceType::OUTPUTCLOCK) - { - auto outputClockFound = std::static_pointer_cast(device); - auto outputClockSelected = std::static_pointer_cast(selectedDevice); - outputClockFound->setDelay(outputClockSelected->getDelay()); - outputClockFound->setDutyCycle(outputClockSelected->getDutyCycle()); - outputClockFound->setFrequencyHz(outputClockSelected->getFrequencyHz()); - outputClockFound->setGateRun(outputClockSelected->getGateRun()); - } - else if (device->getDeviceType() == OnixDeviceType::ANALOGIO) - { - auto analogIOFound = std::static_pointer_cast(device); - auto analogIOSelected = std::static_pointer_cast(selectedDevice); - for (int i = 0; i < analogIOFound->getNumChannels(); i++) - { - analogIOFound->setChannelDirection(i, analogIOSelected->getChannelDirection(i)); - } - } - else if (device->getDeviceType() == OnixDeviceType::NEUROPIXELSV2E) - { - auto npx2Found = std::static_pointer_cast(device); - auto npx2Selected = std::static_pointer_cast(selectedDevice); - npx2Found->setSettings(npx2Selected->settings[0].get(), 0); - npx2Found->setSettings(npx2Selected->settings[1].get(), 1); - npx2Found->setGainCorrectionFile(0, npx2Selected->getGainCorrectionFile(0)); - npx2Found->setGainCorrectionFile(1, npx2Selected->getGainCorrectionFile(1)); - - std::static_pointer_cast(settingsInterfaces[ind])->updateDevice(npx2Found); - } - - device->setEnabled(selectedDevice->isEnabled()); - settingsInterfaces[ind]->setDevice(device); -} - -String OnixSourceCanvas::getTopLevelTabName(PortName port, String headstage) -{ - return PortController::getPortName(port) + ": " + headstage; -} - -Array OnixSourceCanvas::getHeadstageTabs() +Array OnixSourceCanvas::getHubTabs() { Array tabs; - for (const auto headstage : headstageTabs) + for (const auto hub : hubTabs) { - tabs.add(headstage); + tabs.add(hub); } return tabs; @@ -252,11 +191,11 @@ void OnixSourceCanvas::removeTabs(PortName port) { bool tabExists = false; - for (int i = headstageTabs.size() - 1; i >= 0; i -= 1) + for (int i = hubTabs.size() - 1; i >= 0; i -= 1) { - if (headstageTabs[i]->getName().contains(PortController::getPortName(port))) + if (hubTabs[i]->getName().contains(OnixDevice::getPortName(port))) { - headstageTabs.remove(i, true); + hubTabs.remove(i, true); tabExists = true; break; } @@ -277,7 +216,7 @@ void OnixSourceCanvas::removeTabs(PortName port) if (tabExists) { - if (port == PortName::PortB && headstageTabs.size() == 1 && headstageTabs[0]->getName().contains(BREAKOUT_BOARD_NAME)) + if (port == PortName::PortB && hubTabs.size() == 1 && hubTabs[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); @@ -286,7 +225,7 @@ void OnixSourceCanvas::removeTabs(PortName port) void OnixSourceCanvas::removeAllTabs() { - headstageTabs.clear(true); + hubTabs.clear(true); settingsInterfaces.clear(); topLevelTabComponent->clearTabs(); @@ -300,6 +239,9 @@ std::map OnixSourceCanvas::createSelectedMap(std::vectorgetDevice(); + if (!device->isEnabled()) + continue; + tabMap.insert({ device->getDeviceIdx(), device->getDeviceType() }); } @@ -308,9 +250,9 @@ std::map OnixSourceCanvas::createSelectedMap(std::vectorgetHeadstageSelected(offset); + std::string selectedHeadstage = editor->getHeadstageSelected(offset); - String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(offset) + ", but was not discovered there.\n\n"; + std::string msg = "Headstage " + selectedHeadstage + " is selected on " + OnixDevice::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."; @@ -336,14 +278,14 @@ void OnixSourceCanvas::askKeepRemove(int offset) } } -void OnixSourceCanvas::askKeepUpdate(int offset, String foundHeadstage, OnixDeviceVector devices) +void OnixSourceCanvas::askKeepUpdate(int offset, std::string foundHeadstage, OnixDeviceVector devices) { - String selectedHeadstage = editor->getHeadstageSelected(offset); + std::string selectedHeadstage = editor->getHeadstageSelected(offset); if (selectedHeadstage == foundHeadstage) return; - String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(offset) + ". "; - msg += "However, headstage " + foundHeadstage + " was found on " + PortController::getPortName(offset) + ". \n\n"; + std::string msg = "Headstage " + selectedHeadstage + " is selected on " + OnixDevice::getPortName(offset) + ". "; + msg += "However, headstage " + foundHeadstage + " was found on " + OnixDevice::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."; @@ -387,36 +329,33 @@ void OnixSourceCanvas::refreshTabs() for (const auto& [key, _] : selectedMap) { selectedIndices.emplace_back(key); } for (const auto& [key, _] : foundMap) { foundIndices.emplace_back(key); } - auto selectedOffsets = PortController::getUniqueOffsetsFromIndices(selectedIndices); - auto foundOffsets = PortController::getUniqueOffsetsFromIndices(foundIndices); + auto selectedOffsets = OnixDevice::getUniqueOffsetsFromIndices(selectedIndices); + auto foundOffsets = OnixDevice::getUniqueOffsetsFromIndices(foundIndices); - if (foundIndices.size() == 0) // NB: No devices found, inform the user if they were expecting to find something + if (foundIndices.size() == 0 || foundOffsets.size() == 0) // NB: No devices found { if (selectedMap.size() != 0) { - 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.") - ); + for (const auto& offset : selectedOffsets) + { + askKeepRemove(offset); + } } } else if (selectedIndices.size() == 0) // NB: No headstages selected, add all found headstages { - for (auto& [offset, headstageName] : source->getHeadstageMap()) + for (auto& [offset, hubName] : source->getHubNames()) { - addHub(headstageName, offset); + addHub(hubName, offset); } } else if (selectedOffsets.size() == foundOffsets.size()) // NB: Same number of ports selected and found { - auto headstages = source->getHeadstageMap(); + auto hubNames = source->getHubNames(); if (selectedOffsets.size() == 1) { - if (headstages.size() != 1) + if (hubNames.size() != 2) { LOGE("Wrong number of headstages found in the source node."); return; @@ -424,39 +363,39 @@ void OnixSourceCanvas::refreshTabs() if (selectedOffsets[0] == foundOffsets[0]) // NB: Selected headstage is different from the found headstage on the same port { - askKeepUpdate(selectedOffsets[0], headstages[foundOffsets[0]], source->getDataSources()); + askKeepUpdate(selectedOffsets[0], hubNames[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(selectedOffsets[0]); - addHub(headstages[foundOffsets[0]], foundOffsets[0]); + addHub(hubNames[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 offset : foundOffsets) + for (const auto& offset : foundOffsets) { - if (headstages[offset] != editor->getHeadstageSelected(offset)) + if (hubNames[offset] != editor->getHeadstageSelected(offset)) { - askKeepUpdate(offset, headstages[offset], source->getDataSourcesFromOffset(offset)); + askKeepUpdate(offset, hubNames[offset], source->getDataSourcesFromOffset(offset)); } } } } else // NB: Different number of ports selected versus found { - auto headstages = source->getHeadstageMap(); + auto hubNames = source->getHubNames(); if (selectedOffsets.size() > foundOffsets.size()) // NB: More headstages selected than found { - for (auto offset : selectedOffsets) + for (const auto& offset : selectedOffsets) { if (offset == foundOffsets[0]) { - if (headstages[offset] != editor->getHeadstageSelected(offset)) + if (hubNames[offset] != editor->getHeadstageSelected(offset)) { - askKeepUpdate(offset, headstages[offset], source->getDataSourcesFromOffset(offset)); + askKeepUpdate(offset, hubNames[offset], source->getDataSourcesFromOffset(offset)); } } else @@ -467,29 +406,24 @@ void OnixSourceCanvas::refreshTabs() } else // NB: More headstages found than selected { - for (auto offset : foundOffsets) + for (const auto& offset : foundOffsets) { if (offset == selectedOffsets[0]) { - if (headstages[offset] != editor->getHeadstageSelected(offset)) + if (hubNames[offset] != editor->getHeadstageSelected(offset)) { - askKeepUpdate(offset, headstages[offset], source->getDataSourcesFromOffset(offset)); + askKeepUpdate(offset, hubNames[offset], source->getDataSourcesFromOffset(offset)); } } else { - addHub(headstages[offset], offset); + addHub(hubNames[offset], offset); } } } } } - for (const auto& device : source->getDataSources()) - { - updateSettingsInterfaceDataSource(device); - } - CoreServices::updateSignalChain(editor); editor->refreshComboBoxSelection(); } diff --git a/Source/OnixSourceCanvas.h b/Source/OnixSourceCanvas.h index 54b22db..c2ed5af 100644 --- a/Source/OnixSourceCanvas.h +++ b/Source/OnixSourceCanvas.h @@ -27,106 +27,105 @@ #include "UI/InterfaceList.h" #include "UI/CustomTabComponent.h" -#include "OnixSourceEditor.h" - -class OnixSource; - -/** - Holds the visualizer for additional probe settings -*/ -class OnixSourceCanvas : public Visualizer +namespace OnixSourcePlugin { -public: - /** Constructor */ - OnixSourceCanvas(GenericProcessor*, OnixSourceEditor*, OnixSource*); + class OnixSource; + class OnixSourceEditor; - /** Renders the Visualizer on each animation callback cycle */ - void refresh() override; + /** + Holds the visualizer for additional probe settings + */ + class OnixSourceCanvas : public Visualizer + { + public: + /** Constructor */ + OnixSourceCanvas(GenericProcessor*, OnixSourceEditor*, OnixSource*); - /** Starts animation (not needed for this component) */ - void beginAnimation() override {} + /** Renders the Visualizer on each animation callback cycle */ + void refresh() override; - /** Stops animation (not needed for this component) */ - void endAnimation() override {} + /** Starts animation (not needed for this component) */ + void beginAnimation() override {} - /** Called when the Visualizer's tab becomes visible after being hidden */ - void refreshState() override; + /** Stops animation (not needed for this component) */ + void endAnimation() override {} - /** Removes tabs from the canvas at the specified port */ - void removeTabs(PortName port); + /** Called when the Visualizer's tab becomes visible after being hidden */ + void refreshState() override; - /** Removes all tabs from the canvas */ - void removeAllTabs(); + /** Removes tabs from the canvas at the specified port */ + void removeTabs(PortName port); - /** Called when the hardware is connected, to ensure the right tabs are present */ - void refreshTabs(); + /** Removes all tabs from the canvas */ + void removeAllTabs(); - /** Called when the Visualizer is first created, and optionally when - the parameters of the underlying processor are changed */ - void update() const; + /** Called when the hardware is connected, to ensure the right tabs are present */ + void refreshTabs(); - /** Starts animation of sub-interfaces */ - void startAcquisition(); + /** Called when the Visualizer is first created, and optionally when + the parameters of the underlying processor are changed */ + void update() const; - /** Stops animation of sub-interfaces */ - void stopAcquisition(); + /** Starts animation of sub-interfaces */ + void startAcquisition(); - /** Add the hub and all of its devices to the canvas */ - void addHub(String, int); + /** Stops animation of sub-interfaces */ + void stopAcquisition(); - /** Called when the basestation is created or refreshed */ - void populateSourceTabs(CustomTabComponent*, OnixDeviceVector); + /** Add the hub and all of its devices to the canvas */ + void addHub(std::string, int); - /** Saves custom UI settings */ - void saveCustomParametersToXml(XmlElement* xml) override; + /** Called when the basestation is created or refreshed */ + void populateSourceTabs(CustomTabComponent*, OnixDeviceVector); - /** Loads custom UI settings*/ - void loadCustomParametersFromXml(XmlElement* xml) override; + /** Saves custom UI settings */ + void saveCustomParametersToXml(XmlElement* xml) override; - /** Sets bounds of sub-components*/ - void resized(); + /** Loads custom UI settings*/ + void loadCustomParametersFromXml(XmlElement* xml) override; - Array getHeadstageTabs(); + /** Sets bounds of sub-components*/ + void resized(); - std::map createSelectedMap(std::vector>); + Array getHubTabs(); - std::vector> settingsInterfaces; + std::map createSelectedMap(std::vector>); - void resetContext(); + std::vector> settingsInterfaces; - bool foundInputSource(); + void resetContext(); -private: + bool foundInputSource(); - OnixSourceEditor* editor; + std::shared_ptr getDevicePtr(OnixDeviceType, int); - OnixSource* source; + private: - std::unique_ptr topLevelTabComponent; - OwnedArray headstageTabs; + OnixSourceEditor* editor; - CustomTabComponent* addTopLevelTab(String tabName, int index = -1); + OnixSource* source; - void addInterfaceToTab(String tabName, CustomTabComponent* tab, std::shared_ptr interface_); + std::unique_ptr topLevelTabComponent; + OwnedArray hubTabs; - void updateSettingsInterfaceDataSource(std::shared_ptr); + CustomTabComponent* addTopLevelTab(String tabName, int index = -1); - // Compare two device names, ignoring the "-X" if it exists - bool compareDeviceNames(String dev1, String dev2); + void addInterfaceToTab(String tabName, CustomTabComponent* tab, std::shared_ptr interface_); - String getTopLevelTabName(PortName port, String headstage); + std::string getTopLevelTabName(PortName port, std::string headstage); - /** - 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(int offset); + /** + 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(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(int offset, String foundHeadstage, OnixDeviceVector devices); + /** + 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(int offset, std::string foundHeadstage, OnixDeviceVector devices); - JUCE_LEAK_DETECTOR(OnixSourceCanvas); -}; + JUCE_LEAK_DETECTOR(OnixSourceCanvas); + }; +} diff --git a/Source/OnixSourceEditor.cpp b/Source/OnixSourceEditor.cpp index a67d5a7..64cd8c7 100644 --- a/Source/OnixSourceEditor.cpp +++ b/Source/OnixSourceEditor.cpp @@ -22,6 +22,9 @@ #include "OnixSourceEditor.h" #include "OnixSource.h" +#include "OnixSourceCanvas.h" + +using namespace OnixSourcePlugin; OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* source_) : VisualizerEditor(parentNode, "Onix Source", 250), source(source_) @@ -257,16 +260,35 @@ void OnixSourceEditor::setConnectedStatus(bool connected) lastVoltageSetB->setText(String(source->getLastVoltageSet(PortName::PortB)) + " V", dontSendNotification); source->initializeDevices(false); - canvas->refreshTabs(); + + if (source->foundInputSource()) + canvas->refreshTabs(); + + // NB: Check if headstages were not discovered, and then removed + if (!isHeadstageSelected(PortName::PortA) && source->getLastVoltageSet(PortName::PortA) > 0) + { + source->setPortVoltage(PortName::PortA, 0); + portStatusA->setFill(fillDisconnected); + lastVoltageSetA->setText(String(source->getLastVoltageSet(PortName::PortA)) + " V", dontSendNotification); + } + + if (!isHeadstageSelected(PortName::PortB) && source->getLastVoltageSet(PortName::PortB) > 0) + { + source->setPortVoltage(PortName::PortB, 0); + portStatusB->setFill(fillDisconnected); + lastVoltageSetB->setText(String(source->getLastVoltageSet(PortName::PortB)) + " V", dontSendNotification); + } connectButton->setLabel("DISCONNECT"); headstageComboBoxA->setEnabled(false); headstageComboBoxB->setEnabled(false); + portVoltageValueA->setEnabled(false); + portVoltageValueB->setEnabled(false); if (!source->foundInputSource()) { - CoreServices::sendStatusMessage("No Onix hardware found."); + CoreServices::sendStatusMessage("Error configuring hardware. Check logs for more details."); connectButton->setToggleState(false, sendNotification); } } @@ -286,6 +308,8 @@ void OnixSourceEditor::setConnectedStatus(bool connected) headstageComboBoxA->setEnabled(true); headstageComboBoxB->setEnabled(true); + portVoltageValueA->setEnabled(true); + portVoltageValueB->setEnabled(true); } } @@ -311,7 +335,7 @@ void OnixSourceEditor::updateComboBox(ComboBox* cb) for (auto& [key, _] : deviceMap) { deviceIndices.emplace_back(key); } for (auto& [key, _] : tabMap) { tabIndices.emplace_back(key); } - auto devicePorts = PortController::getUniquePortsFromIndices(deviceIndices); + auto devicePorts = OnixDevice::getUniquePortsFromIndices(deviceIndices); auto tabPorts = PortController::getUniquePortsFromIndices(tabIndices); bool isPortA = cb == headstageComboBoxA.get(); @@ -323,7 +347,7 @@ void OnixSourceEditor::updateComboBox(ComboBox* cb) AlertWindow::showMessageBox( MessageBoxIconType::WarningIcon, "Devices Connected", - "Cannot select a different headstage on " + PortController::getPortName(currentPort) + " when connected. \n\nPress disconnect before changing the selected headstage."); + "Cannot select a different headstage on " + OnixDevice::getPortName(currentPort) + " when connected. \n\nPress disconnect before changing the selected headstage."); refreshComboBoxSelection(); return; @@ -339,7 +363,7 @@ void OnixSourceEditor::updateComboBox(ComboBox* cb) if (currentHeadstageSelected) { - String headstage = isPortA ? headstageComboBoxA->getText() : headstageComboBoxB->getText(); + std::string headstage = isPortA ? headstageComboBoxA->getText().toStdString() : headstageComboBoxB->getText().toStdString(); source->updateDiscoveryParameters(currentPort, PortController::getHeadstageDiscoveryParameters(headstage)); canvas->addHub(headstage, PortController::getPortOffset(currentPort)); @@ -452,6 +476,11 @@ Visualizer* OnixSourceEditor::createNewCanvas(void) return canvas; } +OnixSourceCanvas* OnixSourceEditor::getCanvas() +{ + return canvas; +} + void OnixSourceEditor::resetCanvas() { if (canvas != nullptr) @@ -486,22 +515,22 @@ bool OnixSourceEditor::isHeadstageSelected(PortName port) } } -String OnixSourceEditor::getHeadstageSelected(int offset) +std::string OnixSourceEditor::getHeadstageSelected(int offset) { switch (offset) { case 0: return "Breakout Board"; - case PortController::HubAddressPortA: - return headstageComboBoxA->getText(); - case PortController::HubAddressPortB: - return headstageComboBoxB->getText(); + case OnixDevice::HubAddressPortA: + return headstageComboBoxA->getText().toStdString(); + case OnixDevice::HubAddressPortB: + return headstageComboBoxB->getText().toStdString(); default: return ""; } } -String OnixSourceEditor::getHeadstageSelected(PortName port) +std::string OnixSourceEditor::getHeadstageSelected(PortName port) { switch (port) { @@ -520,7 +549,7 @@ void OnixSourceEditor::setComboBoxSelection(ComboBox* comboBox, String headstage { if (headstage.contains(comboBox->getItemText(i))) { - comboBox->setSelectedItemIndex(i, dontSendNotification); // TODO: double check this indexing + comboBox->setSelectedItemIndex(i, dontSendNotification); return; } } @@ -530,19 +559,19 @@ void OnixSourceEditor::setComboBoxSelection(ComboBox* comboBox, String headstage void OnixSourceEditor::refreshComboBoxSelection() { - Array headstageTabs = canvas->getHeadstageTabs(); + Array hubTabs = canvas->getHubTabs(); bool resetPortA = true, resetPortB = true; - for (const auto tab : headstageTabs) + for (const auto tab : hubTabs) { - if (tab->getName().contains(PortController::getPortName(PortName::PortA))) + if (tab->getName().contains(OnixDevice::getPortName(PortName::PortA))) { setComboBoxSelection(headstageComboBoxA.get(), tab->getName()); source->updateDiscoveryParameters(PortName::PortA, PortController::getHeadstageDiscoveryParameters(headstageComboBoxA->getText())); resetPortA = false; } - else if (tab->getName().contains(PortController::getPortName(PortName::PortB))) + else if (tab->getName().contains(OnixDevice::getPortName(PortName::PortB))) { setComboBoxSelection(headstageComboBoxB.get(), tab->getName()); source->updateDiscoveryParameters(PortName::PortB, PortController::getHeadstageDiscoveryParameters(headstageComboBoxB->getText())); diff --git a/Source/OnixSourceEditor.h b/Source/OnixSourceEditor.h index 13e5fda..953b281 100644 --- a/Source/OnixSourceEditor.h +++ b/Source/OnixSourceEditor.h @@ -1,22 +1,22 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - Copyright (C) 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ @@ -24,105 +24,109 @@ #include -#include "OnixSourceCanvas.h" -#include "NeuropixComponents.h" #include "Devices/MemoryMonitor.h" -class OnixSource; +namespace OnixSourcePlugin +{ + class OnixSource; + class OnixSourceCanvas; -/** + /** - Used to change the number of data streams, and the - number of channels per stream. + Used to change the number of data streams, and the + number of channels per stream. -*/ -class OnixSourceEditor : public VisualizerEditor, - public Label::Listener, - public Button::Listener, - public TextEditor::Listener, - public ComboBox::Listener -{ -public: + */ + class OnixSourceEditor : public VisualizerEditor, + public Label::Listener, + public Button::Listener, + public TextEditor::Listener, + public ComboBox::Listener + { + public: + + OnixSourceEditor(GenericProcessor* parentNode, OnixSource* source_); - OnixSourceEditor(GenericProcessor* parentNode, OnixSource* source_); + void labelTextChanged(Label* l) override; - void labelTextChanged(Label* l) override; + void buttonClicked(Button* b) override; - void buttonClicked(Button* b) override; + void comboBoxChanged(ComboBox* cb) override; - void comboBoxChanged(ComboBox* cb) override; + void updateComboBox(ComboBox* cb); - void updateComboBox(ComboBox* cb); + void updateSettings() override; - void updateSettings() override; + void setInterfaceEnabledState(bool newState); - void setInterfaceEnabledState(bool newState); + void startAcquisition() override; - void startAcquisition() override; + void stopAcquisition() override; - void stopAcquisition() override; + Visualizer* createNewCanvas(void) override; - Visualizer* createNewCanvas(void) override; + void saveVisualizerEditorParameters(XmlElement* xml) override; - void saveVisualizerEditorParameters(XmlElement* xml) override; + void loadVisualizerEditorParameters(XmlElement* xml) override; - void loadVisualizerEditorParameters(XmlElement* xml) override; + void checkCanvas() { checkForCanvas(); }; - void checkCanvas() { checkForCanvas(); }; + void resetCanvas(); - void resetCanvas(); + OnixSourceCanvas* getCanvas(); - bool isHeadstageSelected(PortName port); + bool isHeadstageSelected(PortName port); - String getHeadstageSelected(int offset); + std::string getHeadstageSelected(int offset); - String getHeadstageSelected(PortName port); + std::string getHeadstageSelected(PortName port); - void refreshComboBoxSelection(); + void refreshComboBoxSelection(); - std::map createTabMapFromCanvas(); + std::map createTabMapFromCanvas(); - void setConnectedStatus(bool); + void setConnectedStatus(bool); -private: + private: - OnixSourceCanvas* canvas; - OnixSource* source; + OnixSourceCanvas* canvas; + OnixSource* source; - const FillType fillDisconnected = FillType(Colours::transparentBlack); - const FillType fillSearching = FillType(Colour::fromFloatRGBA(0.0f, 1.0f, 87.0f / 255, 1.0f)); - const FillType fillConnected = FillType(Colour::fromFloatRGBA(184.0f / 255, 0.0f, 252.0f / 255, 1.0f)); + const FillType fillDisconnected = FillType(Colours::transparentBlack); + const FillType fillSearching = FillType(Colour::fromFloatRGBA(0.0f, 1.0f, 87.0f / 255, 1.0f)); + const FillType fillConnected = FillType(Colour::fromFloatRGBA(184.0f / 255, 0.0f, 252.0f / 255, 1.0f)); - const Colour statusIndicatorStrokeColor = Colours::black; + const Colour statusIndicatorStrokeColor = Colours::black; - const float statusIndicatorStrokeThickness = 1.0f; + const float statusIndicatorStrokeThickness = 1.0f; - std::unique_ptr portStatusA; - std::unique_ptr portStatusB; + std::unique_ptr portStatusA; + std::unique_ptr portStatusB; - std::unique_ptr