diff --git a/Resources/DataFiles/data_stream_16ch_cortex b/Resources/DataFiles/data_stream_16ch_cortex index 5a84489e3e31356737cf2f9e11ab0c5f72ca2fc1..7e1dd6831a396dc09ffd3c609a9cc7b307bf32f7 100644 Binary files a/Resources/DataFiles/data_stream_16ch_cortex and b/Resources/DataFiles/data_stream_16ch_cortex differ diff --git a/Resources/DataFiles/data_stream_sine_wave b/Resources/DataFiles/data_stream_sine_wave new file mode 100644 index 0000000000000000000000000000000000000000..26815b921e0ad1388c40d42de93005fcb31313eb Binary files /dev/null and b/Resources/DataFiles/data_stream_sine_wave differ diff --git a/Source/Processors/Channel.cpp b/Source/Processors/Channel.cpp index 19bf8e2222b08e06384104f381e00e8442c1a768..210ca9ac3c84c3650019fc0aaaac53c14added1e 100644 --- a/Source/Processors/Channel.cpp +++ b/Source/Processors/Channel.cpp @@ -61,6 +61,11 @@ String Channel::getName() } +void Channel::setName(String name_) +{ + name = name_; +} + void Channel::reset() { createDefaultName(); diff --git a/Source/Processors/Channel.h b/Source/Processors/Channel.h index af30c38c8cd32e3fa0225e9218625422dd049b9a..a32c6cb93161372784abad3f99a69ef582c20fca 100644 --- a/Source/Processors/Channel.h +++ b/Source/Processors/Channel.h @@ -61,6 +61,9 @@ public: /** Returns the name of a given channel. */ String getName(); + /** Sets the name of a given channel. */ + void setName(String); + /** Restores the default settings for a given channel. */ void reset(); diff --git a/Source/Processors/DataThreads/DataThread.h b/Source/Processors/DataThreads/DataThread.h index 03a18aa30679b23f4b10f207d65d0926439a6f0e..72c4aa45cd463291169279c96526c0ca5b90a484 100755 --- a/Source/Processors/DataThreads/DataThread.h +++ b/Source/Processors/DataThreads/DataThread.h @@ -93,6 +93,9 @@ public: return 0; } + /** Changes the names of channels, if the thread needs custom names. */ + virtual void updateChannelNames() { } + SourceNode* sn; int16 eventCode; diff --git a/Source/Processors/DataThreads/RHD2000Thread.cpp b/Source/Processors/DataThreads/RHD2000Thread.cpp index e8fb5c1a9e8d41f2b81f02687ade3dc15f91968e..60231bd0b6796759c5fdeb5cd468c4830a20ec30 100644 --- a/Source/Processors/DataThreads/RHD2000Thread.cpp +++ b/Source/Processors/DataThreads/RHD2000Thread.cpp @@ -25,10 +25,15 @@ #include "../SourceNode.h" RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), isTransmitting(false), - fastSettleEnabled(false) + fastSettleEnabled(false), chipRegisters(30000.0f), dspEnabled(true), boardSampleRate(30000.0f), + desiredDspCutoffFreq(0.5f), desiredUpperBandwidth(7500.0f), desiredLowerBandwidth(1.0f), + savedSampleRateIndex(16), audioOutputL(-1), audioOutputR(-1), dacOutputShouldChange(false), + acquireAdcChannels(false), acquireAuxChannels(true), + cableLengthPortA(0.914f), cableLengthPortB(0.914f), cableLengthPortC(0.914f), cableLengthPortD(0.914f) // default is 3 feet (0.914 m) { evalBoard = new Rhd2000EvalBoard; dataBlock = new Rhd2000DataBlock(1); + dataBuffer = new DataBuffer(2, 10000); // start with 2 channels and automatically resize // Open Opal Kelly XEM6010 board. int return_code = evalBoard->open(); @@ -45,27 +50,11 @@ RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), isTransmitting(fa if (deviceFound) { - numChannelsPerDataStream.insertMultiple(0,0,4); - - // initialize data buffer for 32 channels + 3 aux. - dataBuffer = new DataBuffer(35, 10000); - + // upload bitfile and restore default settings initializeBoard(); - // manually set cable delay for now - //2 for one cable - //3 for 2 cables daisy-chained - evalBoard->setCableDelay(Rhd2000EvalBoard::PortA, 3); - evalBoard->setCableDelay(Rhd2000EvalBoard::PortB, 3); - - enableHeadstage(0,true); // start off with one headstage - enableHeadstage(1,true); // start off with one headstage - - - - // automatically find connected headstages -- needs debugging - // scanPorts(); - + // automatically find connected headstages + scanPorts(); // do this after the editor has been created? } } @@ -89,131 +78,108 @@ void RHD2000Thread::initializeBoard() { string bitfilename; bitfilename = "rhd2000.bit"; - evalBoard->uploadFpgaBitfile(bitfilename); + if (!evalBoard->uploadFpgaBitfile(bitfilename)) + { + // what to do if there's an error + } - // Initialize board. + // Initialize the board + std::cout << "Initializing acquisition board." << std::endl; evalBoard->initialize(); + // This applies the following settings: + // - sample rate to 30 kHz + // - aux command banks to zero + // - aux command lengths to zero + // - continuous run mode to 'true' + // - maxTimeStep to 2^32 - 1 + // - all cable lengths to 3 feet + // - dspSettle to 'false' + // - data source mapping as 0->PortA1, 1->PortB1, 2->PortC1, 3->PortD1, etc. + // - enables all data streams + // - clears the ttlOut + // - disables all DACs and sets gain to 0 + + // Select RAM Bank 0 for AuxCmd3 initially, so the ADC is calibrated. + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3, 0); + + // Since our longest command sequence is 60 commands, run the SPI interface for + // 60 samples + evalBoard->setMaxTimeStep(60); + evalBoard->setContinuousRunMode(false); - // Select per-channel amplifier sampling rate. - evalBoard->setSampleRate(Rhd2000EvalBoard::SampleRate30000Hz); - - // // Select RAM Bank 0 for AuxCmd3 initially, so the ADC is calibrated. - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, 0); - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3, 0); - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3, 0); - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3, 0); - // evalBoard->flush(); // flush in case it crashed with data remaining - - // // Since our longest command sequence is 60 commands, run the SPI interface for - // // 60 samples - // evalBoard->setMaxTimeStep(60); - // evalBoard->setContinuousRunMode(false); - - // // Start SPI interface - // evalBoard->run(); - - // // Wait for the 60-sample run to complete - // while (evalBoard->isRunning()) - // { - // ; - // } + // Start SPI interface + evalBoard->run(); - // // Read the resulting single data block from the USB interface. We don't - // // need to do anything with this, since it was only used for ADC calibration - // Rhd2000DataBlock* dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams()); - // evalBoard->readDataBlock(dataBlock); - - // // Now that ADC calibration has been performed, we switch to the command sequence - // // that does not execute ADC calibration. - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, - // fastSettleEnabled ? 2 : 1); - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3, - // fastSettleEnabled ? 2 : 1); - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3, - // fastSettleEnabled ? 2 : 1); - // evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3, - // fastSettleEnabled ? 2 : 1); - - - // // Set default configuration for all eight DACs on interface board. - // evalBoard->enableDac(0, false); - // evalBoard->enableDac(1, false); - // evalBoard->enableDac(2, false); - // evalBoard->enableDac(3, false); - // evalBoard->enableDac(4, false); - // evalBoard->enableDac(5, false); - // evalBoard->enableDac(6, false); - // evalBoard->enableDac(7, false); - // evalBoard->selectDacDataStream(0, 0); - // evalBoard->selectDacDataStream(1, 0); - // evalBoard->selectDacDataStream(2, 0); - // evalBoard->selectDacDataStream(3, 0); - // evalBoard->selectDacDataStream(4, 0); - // evalBoard->selectDacDataStream(5, 0); - // evalBoard->selectDacDataStream(6, 0); - // evalBoard->selectDacDataStream(7, 0); - // evalBoard->selectDacDataChannel(0, 0); - // evalBoard->selectDacDataChannel(1, 1); - // evalBoard->selectDacDataChannel(2, 0); - // evalBoard->selectDacDataChannel(3, 0); - // evalBoard->selectDacDataChannel(4, 0); - // evalBoard->selectDacDataChannel(5, 0); - // evalBoard->selectDacDataChannel(6, 0); - // evalBoard->selectDacDataChannel(7, 0); - // evalBoard->setDacManual(Rhd2000EvalBoard::DacManual1, 32768); - // evalBoard->setDacManual(Rhd2000EvalBoard::DacManual2, 32768); - // evalBoard->setDacGain(0); - // evalBoard->setAudioNoiseSuppress(0); - - - // Set up an RHD2000 register object using this sample rate to optimize MUX-related - // register settings. - - std::cout << "Rhd sample rate : " << evalBoard->getSampleRate() << std::endl; - chipRegisters = new Rhd2000Registers(evalBoard->getSampleRate()); + // Wait for the 60-sample run to complete + while (evalBoard->isRunning()) + { + ; + } + // Read the resulting single data block from the USB interface. We don't + // need to do anything with this, since it was only used for ADC calibration + Rhd2000DataBlock* dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams()); - // Before generating register configuration command sequences, set amplifier - // bandwidth paramters. - double dspCutoffFreq; - dspCutoffFreq = chipRegisters->setDspCutoffFreq(10.0); - cout << "Actual DSP cutoff frequency: " << dspCutoffFreq << " Hz" << endl; + evalBoard->readDataBlock(dataBlock); - chipRegisters->setLowerBandwidth(1.0); - chipRegisters->setUpperBandwidth(7500.0); + // Now that ADC calibration has been performed, we switch to the command sequence + // that does not execute ADC calibration. + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); - // turn on aux inputs - chipRegisters->enableAux1(true); - chipRegisters->enableAux2(true); - chipRegisters->enableAux3(true); + updateRegisters(); - // Let's turn one LED on to indicate that the program is running. + // Let's turn one LED on to indicate that the board is now connected int ledArray[8] = {1, 0, 0, 0, 0, 0, 0, 0}; evalBoard->setLedDisplay(ledArray); - } void RHD2000Thread::scanPorts() { // Scan SPI ports - int delay, stream, id; + int delay, stream, id, i, channel, port; + int stream1, stream2; + int numChannelsOnPort[4] = {0, 0, 0, 0}; + Array<int> chipId; + chipId.insertMultiple(0,-1,8); + + setSampleRate(16, true); // set to 30 kHz temporarily - // assume we only have 32-channel headstages, for the sake of - // simplicity; this will have to be changed once 64-channel - // headstages are an option + // Enable all data streams, and set sources to cover one or two chips + // on Ports A-D. evalBoard->setDataSource(0, Rhd2000EvalBoard::PortA1); - evalBoard->setDataSource(1, Rhd2000EvalBoard::PortB1); - evalBoard->setDataSource(2, Rhd2000EvalBoard::PortC1); - evalBoard->setDataSource(3, Rhd2000EvalBoard::PortD1); + evalBoard->setDataSource(1, Rhd2000EvalBoard::PortA2); + evalBoard->setDataSource(2, Rhd2000EvalBoard::PortB1); + evalBoard->setDataSource(3, Rhd2000EvalBoard::PortB2); + evalBoard->setDataSource(4, Rhd2000EvalBoard::PortC1); + evalBoard->setDataSource(5, Rhd2000EvalBoard::PortC2); + evalBoard->setDataSource(6, Rhd2000EvalBoard::PortD1); + evalBoard->setDataSource(7, Rhd2000EvalBoard::PortD2); evalBoard->enableDataStream(0, true); evalBoard->enableDataStream(1, true); evalBoard->enableDataStream(2, true); evalBoard->enableDataStream(3, true); + evalBoard->enableDataStream(4, true); + evalBoard->enableDataStream(5, true); + evalBoard->enableDataStream(6, true); + evalBoard->enableDataStream(7, true); + + std::cout << "Number of enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl; + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, 0); @@ -233,23 +199,21 @@ void RHD2000Thread::scanPorts() new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams()); Array<int> sumGoodDelays; - sumGoodDelays.insertMultiple(0,0,4); + sumGoodDelays.insertMultiple(0,0,8); Array<int> indexFirstGoodDelay; - indexFirstGoodDelay.insertMultiple(0,-1,4); + indexFirstGoodDelay.insertMultiple(0,-1,8); Array<int> indexSecondGoodDelay; - indexSecondGoodDelay.insertMultiple(0,-1,4); - - Array<int> chipId; - chipId.insertMultiple(0,0,4); + indexSecondGoodDelay.insertMultiple(0,-1,8); - Array<int> optimumDelay; - optimumDelay.insertMultiple(0,0,4); // Run SPI command sequence at all 16 possible FPGA MISO delay settings // to find optimum delay for each SPI interface cable. - for (delay = 0; delay < 16; ++delay) + + std::cout << "Checking for connected amplifier chips..." << std::endl; + + for (delay = 0; delay < 16; delay++)//(delay = 0; delay < 16; ++delay) { evalBoard->setCableDelay(Rhd2000EvalBoard::PortA, delay); evalBoard->setCableDelay(Rhd2000EvalBoard::PortB, delay); @@ -270,13 +234,18 @@ void RHD2000Thread::scanPorts() // Read the Intan chip ID number from each RHD2000 chip found. // Record delay settings that yield good communication with the chip. - for (stream = 0; stream < 4; ++stream) + for (stream = 0; stream < MAX_NUM_DATA_STREAMS; ++stream)//MAX_NUM_DATA_STREAMS; ++stream) { + // std::cout << "Stream number " << stream << ", delay = " << delay << std::endl; + id = deviceId(dataBlock, stream); - std::cout << "Device ID found: " << id << std::endl; - if (id > 0) + + if (id > 0) // 1 = RHD2132, 2 = RHD2216 { + // std::cout << "Device ID found: " << id << std::endl; + sumGoodDelays.set(stream,sumGoodDelays[stream] + 1); + if (indexFirstGoodDelay[stream] == -1) { indexFirstGoodDelay.set(stream, delay); @@ -291,11 +260,19 @@ void RHD2000Thread::scanPorts() } } + // std::cout << "Chip IDs found: "; + // for (int i = 0; i < MAX_NUM_DATA_STREAMS; ++i) + // { + // std::cout << chipId[i] << " "; + // } + //std::cout << std::endl; + // Now, disable data streams where we did not find chips present. - for (stream = 0; stream < 4; ++stream) + for (stream = 0; stream < MAX_NUM_DATA_STREAMS; ++stream) { if (chipId[stream] > 0) { + //std::cout << "Enabling headstage on stream " << stream << std::endl; enableHeadstage(stream, true); } else @@ -304,9 +281,15 @@ void RHD2000Thread::scanPorts() } } + std::cout << "Number of enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl; + + // Set cable delay settings that yield good communication with each // RHD2000 chip. - for (stream = 0; stream < 4; ++stream) + Array<int> optimumDelay; + optimumDelay.insertMultiple(0,0,8); + + for (stream = 0; stream < MAX_NUM_DATA_STREAMS; ++stream) { if (sumGoodDelays[stream] == 1 || sumGoodDelays[stream] == 2) { @@ -336,7 +319,9 @@ void RHD2000Thread::scanPorts() cableLengthPortD = evalBoard->estimateCableLengthMeters(optimumDelay[3]); + setSampleRate(savedSampleRateIndex); // restore saved sample rate + updateRegisters(); } int RHD2000Thread::deviceId(Rhd2000DataBlock* dataBlock, int stream) @@ -346,11 +331,17 @@ int RHD2000Thread::deviceId(Rhd2000DataBlock* dataBlock, int stream) // First, check ROM registers 32-36 to verify that they hold 'INTAN'. // This is just used to verify that we are getting good data over the SPI // communication channel. - intanChipPresent = ((char) dataBlock->auxiliaryData[stream][2][32] == 'I' && - (char) dataBlock->auxiliaryData[stream][2][33] == 'N' && - (char) dataBlock->auxiliaryData[stream][2][34] == 'T' && - (char) dataBlock->auxiliaryData[stream][2][35] == 'A' && - (char) dataBlock->auxiliaryData[stream][2][36] == 'N'); + // std::cout << dataBlock->auxiliaryData[stream][2][32] << " "; + // std::cout << dataBlock->auxiliaryData[stream][2][33] << " "; + // std::cout << dataBlock->auxiliaryData[stream][2][34] << " "; + // std::cout << dataBlock->auxiliaryData[stream][2][35] << " "; + // std::cout << dataBlock->auxiliaryData[stream][2][36] << std::endl; + + intanChipPresent = (dataBlock->auxiliaryData[stream][2][32] == 73 && // I = 73 + dataBlock->auxiliaryData[stream][2][33] == 78 && // N = 78 + dataBlock->auxiliaryData[stream][2][34] == 84 && // T = 84 + dataBlock->auxiliaryData[stream][2][35] == 65 && // A = 65 + dataBlock->auxiliaryData[stream][2][36] == 78); // N = 78 // If the SPI communication is bad, return -1. Otherwise, return the Intan // chip ID number stored in ROM regstier 63. @@ -365,20 +356,31 @@ int RHD2000Thread::deviceId(Rhd2000DataBlock* dataBlock, int stream) } + bool RHD2000Thread::isAcquisitionActive() { return isTransmitting; } +void RHD2000Thread::setNumChannels(int hsNum, int numChannels) +{ + numChannelsPerDataStream.set(hsNum, numChannels); +} + int RHD2000Thread::getNumChannels() { numChannels = 0; - for (int i = 0; i < numChannelsPerDataStream.size(); i++) + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) { - numChannels += numChannelsPerDataStream[i]; + if (numChannelsPerDataStream[i] > 0) + { + numChannels += numChannelsPerDataStream[i]; + numChannels += 3; // to account for aux inputs + } + /* if (chipRegisters->adcAux1En){ // no public function to read these? fix this in some way @@ -392,7 +394,12 @@ int RHD2000Thread::getNumChannels() } */ } - numChannels += 6; + + + if (acquireAdcChannels) + { + numChannels += 8; // add 8 channels for the ADCs + } if (numChannels > 0) return numChannels; @@ -400,6 +407,57 @@ int RHD2000Thread::getNumChannels() return 1; // to prevent crashing with 0 channels } +void RHD2000Thread::updateChannelNames() +{ + + int chNum = -1; + + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) + { + + for (int j = 0; j < numChannelsPerDataStream[i]; j++) + { + chNum++; + + sn->channels[chNum]->setName(String(chNum)); + } + } + + if (acquireAuxChannels) + { + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) + { + + for (int j = 0; j < 3; j++) + { + + chNum++; + + String chName = "AUX"; + // chName += (j+1); + + // sn->channels[chNum]->setName(chName); + } + } + } + + + if (acquireAdcChannels) + { + for (int j = 0; j < 8; j++) + { + chNum++; + + String chName = "ADC"; + // chName += (j+1); + + // sn->channels[chNum]->setName(chName); + } + } + +} + + int RHD2000Thread::getNumEventChannels() { return 16; // 8 inputs, 8 outputs @@ -415,14 +473,23 @@ float RHD2000Thread::getBitVolts() return 0.195f; } -double RHD2000Thread::setUpperBandwidth(double desiredUpperBandwidth) +double RHD2000Thread::setUpperBandwidth(double upper) { - return chipRegisters->setUpperBandwidth(desiredUpperBandwidth); + + desiredUpperBandwidth = upper; + + updateRegisters(); + + return actualUpperBandwidth; } -double RHD2000Thread::setLowerBandwidth(double desiredLowerBandwidth) +double RHD2000Thread::setLowerBandwidth(double lower) { - return chipRegisters->setLowerBandwidth(desiredLowerBandwidth); + desiredLowerBandwidth = lower; + + updateRegisters(); + + return actualLowerBandwidth; } bool RHD2000Thread::foundInputSource() @@ -446,12 +513,15 @@ bool RHD2000Thread::enableHeadstage(int hsNum, bool enabled) numChannelsPerDataStream.set(hsNum, 0); } - std::cout << "Enabled channels: " << numChannelsPerDataStream[0] << - " " << numChannelsPerDataStream[1] << - " " << numChannelsPerDataStream[2] << - " " << numChannelsPerDataStream[3] << std::endl; + std::cout << "Enabled channels: "; + + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) + { + std::cout << numChannelsPerDataStream[i] << " "; + } + + std:: cout << std::endl; - std::cout << "Enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl; dataBuffer->resize(getNumChannels(), 10000); @@ -470,89 +540,227 @@ bool RHD2000Thread::isHeadstageEnabled(int hsNum) } -void RHD2000Thread::setSampleRate(int sampleRateIndex) +void RHD2000Thread::assignAudioOut(int dacChannel, int dataChannel) +{ + + if (dacChannel == 0) + { + audioOutputR = dataChannel; + + + } else if (dacChannel == 1) + { + audioOutputL = dataChannel; + + } + + dacOutputShouldChange = true; // set a flag and take care of setting wires + // during the updateBuffer() method + // to avoid problems + +} + +void RHD2000Thread::enableAdcs(bool t) +{ + + acquireAdcChannels = t; + + dataBuffer->resize(getNumChannels(), 10000); + +} + +void RHD2000Thread::setSampleRate(int sampleRateIndex, bool isTemporary) { + + if (!isTemporary) + { + savedSampleRateIndex = sampleRateIndex; + } + int numUsbBlocksToRead = 0; // placeholder - make this change the number of blocks that are read in RHD2000Thread::updateBuffer() - int numUsbBlocksToRead=0; // placeholder - make this change the number of blocks that are read in RHD2000Thread::updateBuffer() Rhd2000EvalBoard::AmplifierSampleRate sampleRate; // just for local use - switch (sampleRateIndex) { case 0: sampleRate = Rhd2000EvalBoard::SampleRate1000Hz; numUsbBlocksToRead = 1; + boardSampleRate = 1000.0f; break; case 1: sampleRate = Rhd2000EvalBoard::SampleRate1250Hz; numUsbBlocksToRead = 1; + boardSampleRate = 1250.0f; break; case 2: sampleRate = Rhd2000EvalBoard::SampleRate1500Hz; numUsbBlocksToRead = 1; + boardSampleRate = 1500.0f; break; case 3: sampleRate = Rhd2000EvalBoard::SampleRate2000Hz; numUsbBlocksToRead = 1; + boardSampleRate = 2000.0f; break; case 4: sampleRate = Rhd2000EvalBoard::SampleRate2500Hz; numUsbBlocksToRead = 1; + boardSampleRate = 2500.0f; break; case 5: sampleRate = Rhd2000EvalBoard::SampleRate3000Hz; numUsbBlocksToRead = 2; + boardSampleRate = 3000.0f; break; case 6: sampleRate = Rhd2000EvalBoard::SampleRate3333Hz; numUsbBlocksToRead = 2; + boardSampleRate = 3333.0f; break; case 7: sampleRate = Rhd2000EvalBoard::SampleRate4000Hz; numUsbBlocksToRead = 2; + boardSampleRate = 4000.0f; break; case 8: sampleRate = Rhd2000EvalBoard::SampleRate5000Hz; numUsbBlocksToRead = 3; + boardSampleRate = 5000.0f; break; case 9: sampleRate = Rhd2000EvalBoard::SampleRate6250Hz; numUsbBlocksToRead = 3; + boardSampleRate = 6250.0f; break; case 10: sampleRate = Rhd2000EvalBoard::SampleRate8000Hz; numUsbBlocksToRead = 4; + boardSampleRate = 8000.0f; break; case 11: sampleRate = Rhd2000EvalBoard::SampleRate10000Hz; numUsbBlocksToRead = 6; + boardSampleRate = 10000.0f; break; case 12: sampleRate = Rhd2000EvalBoard::SampleRate12500Hz; numUsbBlocksToRead = 7; + boardSampleRate = 12500.0f; break; case 13: sampleRate = Rhd2000EvalBoard::SampleRate15000Hz; numUsbBlocksToRead = 8; + boardSampleRate = 15000.0f; break; case 14: sampleRate = Rhd2000EvalBoard::SampleRate20000Hz; numUsbBlocksToRead = 12; + boardSampleRate = 20000.0f; break; case 15: sampleRate = Rhd2000EvalBoard::SampleRate25000Hz; numUsbBlocksToRead = 14; + boardSampleRate = 25000.0f; break; case 16: sampleRate = Rhd2000EvalBoard::SampleRate30000Hz; numUsbBlocksToRead = 16; + boardSampleRate = 30000.0f; break; + default: + sampleRate = Rhd2000EvalBoard::SampleRate10000Hz; + numUsbBlocksToRead = 6; + boardSampleRate = 10000.0f; } // Select per-channel amplifier sampling rate. evalBoard->setSampleRate(sampleRate); + std::cout << "Sample rate set to " << evalBoard->getSampleRate() << std::endl; + + // Now that we have set our sampling rate, we can set the MISO sampling delay + // which is dependent on the sample rate. + evalBoard->setCableLengthMeters(Rhd2000EvalBoard::PortA, cableLengthPortA); + evalBoard->setCableLengthMeters(Rhd2000EvalBoard::PortB, cableLengthPortB); + evalBoard->setCableLengthMeters(Rhd2000EvalBoard::PortC, cableLengthPortC); + evalBoard->setCableLengthMeters(Rhd2000EvalBoard::PortD, cableLengthPortD); + +} + +void RHD2000Thread::updateRegisters() +{ + // Set up an RHD2000 register object using this sample rate to + // optimize MUX-related register settings. + chipRegisters.defineSampleRate(boardSampleRate); + + + int commandSequenceLength; + vector<int> commandList; + + // Create a command list for the AuxCmd1 slot. This command sequence will create a 250 Hz, + // zero-amplitude sine wave (i.e., a flatline). We will change this when we want to perform + // impedance testing. + commandSequenceLength = chipRegisters.createCommandListZcheckDac(commandList, 250.0, 0.0); + evalBoard->uploadCommandList(commandList, Rhd2000EvalBoard::AuxCmd1, 0); + evalBoard->selectAuxCommandLength(Rhd2000EvalBoard::AuxCmd1, 0, commandSequenceLength - 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd1, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd1, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd1, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd1, 0); + + // // Next, we'll create a command list for the AuxCmd2 slot. This command sequence + // // will sample the temperature sensor and other auxiliary ADC inputs. + commandSequenceLength = chipRegisters.createCommandListTempSensor(commandList); + evalBoard->uploadCommandList(commandList, Rhd2000EvalBoard::AuxCmd2, 0); + evalBoard->selectAuxCommandLength(Rhd2000EvalBoard::AuxCmd2, 0, commandSequenceLength - 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd2, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd2, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd2, 0); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd2, 0); + + // Before generating register configuration command sequences, set amplifier + // bandwidth paramters. + actualDspCutoffFreq = chipRegisters.setDspCutoffFreq(desiredDspCutoffFreq); + actualLowerBandwidth = chipRegisters.setLowerBandwidth(desiredLowerBandwidth); + actualUpperBandwidth = chipRegisters.setUpperBandwidth(desiredUpperBandwidth); + chipRegisters.enableDsp(dspEnabled); + + // turn on aux inputs + chipRegisters.enableAux1(true); + chipRegisters.enableAux2(true); + chipRegisters.enableAux3(true); + + chipRegisters.createCommandListRegisterConfig(commandList, true); + // Upload version with ADC calibration to AuxCmd3 RAM Bank 0. + evalBoard->uploadCommandList(commandList, Rhd2000EvalBoard::AuxCmd3, 0); + evalBoard->selectAuxCommandLength(Rhd2000EvalBoard::AuxCmd3, 0, + commandSequenceLength - 1); + + commandSequenceLength = chipRegisters.createCommandListRegisterConfig(commandList, false); + // Upload version with no ADC calibration to AuxCmd3 RAM Bank 1. + evalBoard->uploadCommandList(commandList, Rhd2000EvalBoard::AuxCmd3, 1); + evalBoard->selectAuxCommandLength(Rhd2000EvalBoard::AuxCmd3, 0, + commandSequenceLength - 1); + + chipRegisters.setFastSettle(true); + commandSequenceLength = chipRegisters.createCommandListRegisterConfig(commandList, false); + // Upload version with fast settle enabled to AuxCmd3 RAM Bank 2. + evalBoard->uploadCommandList(commandList, Rhd2000EvalBoard::AuxCmd3, 2); + evalBoard->selectAuxCommandLength(Rhd2000EvalBoard::AuxCmd3, 0, + commandSequenceLength - 1); + chipRegisters.setFastSettle(false); + + + + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3, + fastSettleEnabled ? 2 : 1); } void RHD2000Thread::setCableLength(int hsNum, float length) @@ -660,8 +868,6 @@ bool RHD2000Thread::updateBuffer() bool return_code; - for (int n = 0; n < 10; n++) - { if (evalBoard->numWordsInFifo() >= blockSize) { return_code = evalBoard->readDataBlock(dataBlock); @@ -671,7 +877,8 @@ bool RHD2000Thread::updateBuffer() int streamNumber = -1; int channel = -1; - for (int dataStream = 0; dataStream < numChannelsPerDataStream.size(); dataStream++) + // do the neural data channels first + for (int dataStream = 0; dataStream < MAX_NUM_DATA_STREAMS; dataStream++) { if (numChannelsPerDataStream[dataStream] > 0) { @@ -679,60 +886,105 @@ bool RHD2000Thread::updateBuffer() for (int chan = 0; chan < numChannelsPerDataStream[dataStream]; chan++) { - //std::cout << "reading sample stream" << streamNumber << " chan " << chan << " sample "<< samp << std::endl; + // std::cout << "reading sample stream" << streamNumber << " chan " << chan << " sample "<< samp << std::endl; channel++; int value = dataBlock->amplifierData[streamNumber][chan][samp]; thisSample[channel] = float(value-32768)*0.195f; } - - - if (samp % 4 == 1) { // every 4th sample should have auxiliary input data - - channel++; - thisSample[channel] = 0.0374 * - float(dataBlock->auxiliaryData[dataStream][1][samp+0]); - auxBuffer[channel]=thisSample[channel]; + } - channel++; - thisSample[channel] = 0.0374 * - float(dataBlock->auxiliaryData[dataStream][1][samp+1]); - auxBuffer[channel]=thisSample[channel]; + } - - channel++; - thisSample[channel] = 0.0374 * - float(dataBlock->auxiliaryData[dataStream][1][samp+2]); - auxBuffer[channel]=thisSample[channel]; + streamNumber = -1; - } else{ - channel++; - thisSample[channel] = auxBuffer[channel]; - channel++; - thisSample[channel] = auxBuffer[channel]; - channel++; - thisSample[channel] = auxBuffer[channel]; - } - - } + // then do the ADC channels + for (int dataStream = 0; dataStream < MAX_NUM_DATA_STREAMS; dataStream++) + { + if (numChannelsPerDataStream[dataStream] > 0) + { + streamNumber++; + + if (samp % 4 == 1) { // every 4th sample should have auxiliary input data + + channel++; + thisSample[channel] = 0.0374 * + float(dataBlock->auxiliaryData[dataStream][1][samp+0]); + + auxBuffer[channel]=thisSample[channel]; + + channel++; + thisSample[channel] = 0.0374 * + float(dataBlock->auxiliaryData[dataStream][1][samp+1]); + + auxBuffer[channel]=thisSample[channel]; + + + channel++; + thisSample[channel] = 0.0374 * + float(dataBlock->auxiliaryData[dataStream][1][samp+2]); + + auxBuffer[channel]=thisSample[channel]; + + } else{ // repeat last values from buffer + + channel++; + thisSample[channel] = auxBuffer[channel]; + channel++; + thisSample[channel] = auxBuffer[channel]; + channel++; + thisSample[channel] = auxBuffer[channel]; + } + } } - // std::cout << channel << std::endl; + // finally, loop through ADC channels if necessary + if (acquireAdcChannels) + { + for (int adcChan = 0; adcChan < 8; ++adcChan) { + + channel++; + // ADC waveform units = volts + thisSample[channel] = + 0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]); + } + } + // std::cout << channel << std::endl; - timestamp = dataBlock->timeStamp[samp]; - timestamp = timestamp; - eventCode = dataBlock->ttlIn[samp]; + timestamp = dataBlock->timeStamp[samp]; + timestamp = timestamp; + eventCode = dataBlock->ttlIn[samp]; - dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); + dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); + } } - } + + if (dacOutputShouldChange) + { + if (audioOutputR >= 0) + { + evalBoard->enableDac(0, true); + evalBoard->selectDacDataChannel(0, audioOutputR); + } else { + evalBoard->enableDac(0, false); + } + + if (audioOutputL >= 0) + { + evalBoard->enableDac(1, true); + evalBoard->selectDacDataChannel(1, audioOutputR); + } else { + evalBoard->enableDac(1, false); + } + dacOutputShouldChange = false; + } return true; diff --git a/Source/Processors/DataThreads/RHD2000Thread.h b/Source/Processors/DataThreads/RHD2000Thread.h index 611d22d64b6271e20958f4c843972b11c45c6d24..47faa406f3c77257451d7080818bf473bade459b 100644 --- a/Source/Processors/DataThreads/RHD2000Thread.h +++ b/Source/Processors/DataThreads/RHD2000Thread.h @@ -38,6 +38,7 @@ #include "DataThread.h" +#define MAX_NUM_DATA_STREAMS 8 class SourceNode; @@ -67,22 +68,30 @@ public: void setCableLength(int hsNum, float length); void setNumChannels(int hsNum, int nChannels); - void setSampleRate(int sampleRateIndex); + void setSampleRate(int index, bool temporary = false); - double setUpperBandwidth(double desiredUpperBandwidth); // set desired BW, returns actual BW - double setLowerBandwidth(double desiredLowerBandwidth); + double setUpperBandwidth(double upper); // set desired BW, returns actual BW + double setLowerBandwidth(double lower); + void scanPorts(); int getNumEventChannels(); + void assignAudioOut(int dacChannel, int dataChannel); + void enableAdcs(bool); + bool isAcquisitionActive(); + void updateChannelNames(); + private: ScopedPointer<Rhd2000EvalBoard> evalBoard; - ScopedPointer<Rhd2000Registers> chipRegisters; + Rhd2000Registers chipRegisters; Rhd2000DataBlock* dataBlock; + int audioOutputL, audioOutputR; + Array<int> numChannelsPerDataStream; int numChannels; @@ -95,13 +104,26 @@ private: bool isTransmitting; + bool dacOutputShouldChange; + bool acquireAdcChannels; + bool acquireAuxChannels; + bool fastSettleEnabled; + bool dspEnabled; + double actualDspCutoffFreq, desiredDspCutoffFreq; + double actualUpperBandwidth, desiredUpperBandwidth; + double actualLowerBandwidth, desiredLowerBandwidth; + double boardSampleRate; + int savedSampleRateIndex; + bool startAcquisition(); bool stopAcquisition(); void initializeBoard(); - void scanPorts(); + + void updateRegisters(); + int deviceId(Rhd2000DataBlock* dataBlock, int stream); bool updateBuffer(); diff --git a/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.cpp b/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.cpp index d80dc6ebc2750f75400b64eda43cb145b2c1fdb8..449ca4731a291eaf8658a79b4015156aabade610 100755 --- a/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.cpp +++ b/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.cpp @@ -148,7 +148,7 @@ void Rhd2000DataBlock::fillFromUsbBuffer(unsigned char usbBuffer[], int blockInd { if (!checkUsbHeader(usbBuffer, index)) { - cerr << "Error in Rhd2000EvalBoard::readDataBlock: Incorrect header." << endl; + //cerr << "Error in Rhd2000EvalBoard::readDataBlock: Incorrect header." << endl; } index += 8; timeStamp[t] = convertUsbTimeStamp(usbBuffer, index); diff --git a/Source/Processors/Editors/ChannelMappingEditor.h b/Source/Processors/Editors/ChannelMappingEditor.h index 3376fea761a5ea7ffcfd210f0d114a8b9b385254..0ddde7119fa9aea18d4446c3db8ae0f5964f6b0a 100644 --- a/Source/Processors/Editors/ChannelMappingEditor.h +++ b/Source/Processors/Editors/ChannelMappingEditor.h @@ -28,7 +28,7 @@ #include "../../../JuceLibraryCode/JuceHeader.h" #include "GenericEditor.h" -#include "SpikeDetectorEditor.h" +#include "SpikeDetectorEditor.h" // for ElectrodeButton and ElectrodeEditorButton /** diff --git a/Source/Processors/Editors/ChannelSelector.cpp b/Source/Processors/Editors/ChannelSelector.cpp index 2cc01b7c55827310e74a5b2565d075e1b0a9747d..6e3b4bb3e3514414cdba77b1a0410d9b53b5f6e2 100755 --- a/Source/Processors/Editors/ChannelSelector.cpp +++ b/Source/Processors/Editors/ChannelSelector.cpp @@ -352,22 +352,29 @@ void ChannelSelector::stopAcquisition() void ChannelSelector::setRadioStatus(bool radioOn) { - radioStatus = radioOn; - - for (int i = 0; i < parameterButtons.size(); i++) + if (radioStatus != radioOn) { - if (radioOn) - { - parameterButtons[i]->setToggleState(false, false); - parameterButtons[i]->setRadioGroupId(999); - } - else + + radioStatus = radioOn; + + for (int i = 0; i < parameterButtons.size(); i++) { - parameterButtons[i]->setToggleState(false, false); - parameterButtons[i]->setRadioGroupId(0); + if (radioOn) + { + parameterButtons[i]->setToggleState(false, false); + parameterButtons[i]->setRadioGroupId(999); + } + else + { + parameterButtons[i]->setToggleState(false, false); + parameterButtons[i]->setRadioGroupId(0); + } } + } + + } bool ChannelSelector::getParamStatus(int chan) @@ -525,6 +532,13 @@ void ChannelSelector::buttonClicked(Button* button) audioButtons[i]->setToggleState(false, true); } } + + if (radioStatus) // if radio buttons are active + { + // send a message to parent + GenericEditor* editor = (GenericEditor*) getParentComponent(); + editor->channelChanged(-1); + } } else { diff --git a/Source/Processors/Editors/GenericEditor.cpp b/Source/Processors/Editors/GenericEditor.cpp index 4738166c21f254c5f9556c07377a06735e200a76..6c41a3a48733dca79f49c9c72b521e4af3e6758f 100755 --- a/Source/Processors/Editors/GenericEditor.cpp +++ b/Source/Processors/Editors/GenericEditor.cpp @@ -555,7 +555,7 @@ void DrawerButton::paintButton(Graphics& g, bool isMouseOver, bool isButtonDown) } -UtilityButton::UtilityButton(const String& label_, Font font_) : +UtilityButton::UtilityButton(String label_, Font font_) : Button(label_), label(label_), font(font_) { @@ -659,7 +659,7 @@ void UtilityButton::paintButton(Graphics& g, bool isMouseOver, bool isButtonDown g.setFont(font); g.setColour(fontColor); - g.drawText(getName(),0,0,getWidth(),getHeight(),Justification::centred,true); + g.drawText(label,0,0,getWidth(),getHeight(),Justification::centred,true); //g.drawSingleLineText(getName(), getWidth()/2 - stringWidth/2, 12); @@ -753,6 +753,12 @@ void UtilityButton::resized() } +void UtilityButton::setLabel(String label_) +{ + label = label_; + repaint(); +} + void TriangleButton::paintButton(Graphics& g, bool isMouseOver, bool isButtonDown) { @@ -803,10 +809,10 @@ void TriangleButton::paintButton(Graphics& g, bool isMouseOver, bool isButtonDow void GenericEditor::updateParameterButtons(int parameterIndex) { - if (parameterEditors.size()==0) + if (parameterEditors.size() == 0) { //Checks if there is a parameter editor, and stops a bug if there isn't. - std::cout << "No parameterEditors" << std::endl; + //std::cout << "No parameterEditors" << std::endl; } else { @@ -821,6 +827,6 @@ void GenericEditor::updateParameterButtons(int parameterIndex) { parameterEditors[parameterIndex]->channelSelectionUI(); } - std::cout << "updateParameterButtons" << std::endl; + //std::cout << "updateParameterButtons" << std::endl; } } diff --git a/Source/Processors/Editors/GenericEditor.h b/Source/Processors/Editors/GenericEditor.h index 762b9bc3f6593b28128c43f18c16d3516c1c609f..420af535b72a47e47e98512c511675443a19d1c0 100755 --- a/Source/Processors/Editors/GenericEditor.h +++ b/Source/Processors/Editors/GenericEditor.h @@ -106,10 +106,10 @@ public: void setEnabledState(bool); /** Called just prior to the start of acquisition, to allow the editor to prepare.*/ - void startAcquisition(); + virtual void startAcquisition(); /** Called after the end of acquisition.*/ - void stopAcquisition(); + virtual void stopAcquisition(); /** Returns the name of the editor.*/ String getName() @@ -344,7 +344,7 @@ private: class UtilityButton : public Button { public: - UtilityButton(const String& label_, Font font_); + UtilityButton(String label_, Font font_); ~UtilityButton() {} void setCorners(bool UL, bool UR, bool LL, bool LR); @@ -356,10 +356,12 @@ public: return isEnabled; } + void setLabel(String label); + private: void paintButton(Graphics& g, bool isMouseOver, bool isButtonDown); - const String label; + String label; Font font; bool roundUL, roundUR, roundLL, roundLR; float radius; diff --git a/Source/Processors/Editors/RHD2000Editor.cpp b/Source/Processors/Editors/RHD2000Editor.cpp index bea8a7d372c6cb7cfa3d767da10398f7458f1bbb..65a84dc7a54c6db3c54d9687b761edc4502ecc57 100644 --- a/Source/Processors/Editors/RHD2000Editor.cpp +++ b/Source/Processors/Editors/RHD2000Editor.cpp @@ -24,16 +24,17 @@ #include "RHD2000Editor.h" #include "../../UI/EditorViewport.h" +#include "ChannelSelector.h" + #include "../DataThreads/RHD2000Thread.h" RHD2000Editor::RHD2000Editor(GenericProcessor* parentNode, RHD2000Thread* board_, - bool useDefaultParameterEditors=true + bool useDefaultParameterEditors ) : GenericEditor(parentNode, useDefaultParameterEditors), board(board_) { - desiredWidth = 400; - + desiredWidth = 260; // add headstage-specific controls (currently just an enable/disable button) for (int i = 0; i < 4; i++) @@ -41,23 +42,128 @@ RHD2000Editor::RHD2000Editor(GenericProcessor* parentNode, HeadstageOptionsInterface* hsOptions = new HeadstageOptionsInterface(board, this, i); headstageOptionsInterfaces.add(hsOptions); addAndMakeVisible(hsOptions); - hsOptions->setBounds(80,25+i*23, 60, 22); + hsOptions->setBounds(3, 28+i*20, 70, 18); } // add sample rate selection - SampleRateInterface* rateOptions = new SampleRateInterface(board, this); - addAndMakeVisible(rateOptions); - rateOptions->setBounds(150,25,160, 50); + sampleRateInterface = new SampleRateInterface(board, this); + addAndMakeVisible(sampleRateInterface); + sampleRateInterface->setBounds(80, 25, 100, 50); // add Bandwidth selection - BandwidthInterface* bandwidthOptions = new BandwidthInterface(board, this); - addAndMakeVisible(bandwidthOptions); - bandwidthOptions->setBounds(150,65,160, 50); + bandwidthInterface = new BandwidthInterface(board, this); + addAndMakeVisible(bandwidthInterface); + bandwidthInterface->setBounds(80, 65, 100, 50); + + // add rescan button + rescanButton = new UtilityButton("RESCAN", Font("Small Text", 13, Font::plain)); + rescanButton->setRadius(3.0f); + rescanButton->setBounds(6, 108,65,18); + rescanButton->addListener(this); + addAndMakeVisible(rescanButton); + + for (int i = 0; i < 2; i++) + { + ElectrodeButton* button = new ElectrodeButton(-1); + electrodeButtons.add(button); + + button->setBounds(190+i*25, 40, 25, 15); + button->setChannelNum(-1); + button->setToggleState(false,false); + button->setRadioGroupId(999); + + addAndMakeVisible(button); + button->addListener(this); + } + + audioLabel = new Label("audio label", "Audio out"); + audioLabel->setBounds(180,25,180,15); + audioLabel->setFont(Font("Small Text", 10, Font::plain)); + audioLabel->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(audioLabel); + + adcButton = new UtilityButton("ADC 1-8", Font("Small Text", 13, Font::plain)); + adcButton->setRadius(3.0f); + adcButton->setBounds(180, 70,65,18); + adcButton->addListener(this); + adcButton->setClickingTogglesState(true); + addAndMakeVisible(adcButton); + } RHD2000Editor::~RHD2000Editor() { + +} + +void RHD2000Editor::scanPorts() +{ + rescanButton->triggerClick(); +} + +void RHD2000Editor::buttonEvent(Button* button) +{ + + if (button == rescanButton) + { + board->scanPorts(); + + for (int i = 0; i < 4; i++) + { + headstageOptionsInterfaces[i]->checkEnabledState(); + } + + } else if (button == electrodeButtons[0]) + { + channelSelector->setRadioStatus(true); + } else if (button == electrodeButtons[1]) + { + channelSelector->setRadioStatus(true); + } else if (button == adcButton) + { + board->enableAdcs(button->getToggleState()); + getEditorViewport()->makeEditorVisible(this, false, true); + } + +} + +void RHD2000Editor::channelChanged(int chan) +{ + for (int i = 0; i < 2; i++) + { + if (electrodeButtons[i]->getToggleState()) + { + electrodeButtons[i]->setChannelNum(chan); + electrodeButtons[i]->repaint(); + + board->assignAudioOut(i, chan); + } + } +} + +void RHD2000Editor::startAcquisition() +{ + + channelSelector->startAcquisition(); + + rescanButton->setEnabledState(false); + adcButton->setEnabledState(false); + + acquisitionIsActive = true; + +} + +void RHD2000Editor::stopAcquisition() +{ + + channelSelector->stopAcquisition(); + + rescanButton->setEnabledState(true); + adcButton->setEnabledState(true); + + acquisitionIsActive = false; + } // Bandwidth Options -------------------------------------------------------------------- @@ -67,20 +173,25 @@ BandwidthInterface::BandwidthInterface(RHD2000Thread* board_, board(board_), editor(editor_) { - name="Bandwidth"; + name = "Bandwidth"; + lastHighCutString = "7500"; + lastLowCutString = "1"; - UpperBandwidthSelection = new Label("UpperBandwidth","7500 Hz"); // this is currently set in RHD2000Thread, the cleaner would be to set it here again + UpperBandwidthSelection = new Label("UpperBandwidth",lastHighCutString); // this is currently set in RHD2000Thread, the cleaner would be to set it here again UpperBandwidthSelection->setEditable(true,false,false); UpperBandwidthSelection->addListener(this); - UpperBandwidthSelection->setBounds(0,10,300,30); + UpperBandwidthSelection->setBounds(30,30,60,20); + UpperBandwidthSelection->setColour(Label::textColourId, Colours::darkgrey); addAndMakeVisible(UpperBandwidthSelection); - LowerBandwidthSelection = new Label("LowerBandwidth","1 Hz"); + LowerBandwidthSelection = new Label("LowerBandwidth",lastLowCutString); LowerBandwidthSelection->setEditable(true,false,false); LowerBandwidthSelection->addListener(this); - LowerBandwidthSelection->setBounds(0,30,300,30); + LowerBandwidthSelection->setBounds(25,10,60,20); + LowerBandwidthSelection->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(LowerBandwidthSelection); @@ -93,29 +204,51 @@ BandwidthInterface::~BandwidthInterface() } -void BandwidthInterface::labelTextChanged(Label* te) +void BandwidthInterface::labelTextChanged(Label* label) { if (!(editor->acquisitionIsActive) && board->foundInputSource()) { - if (te == UpperBandwidthSelection) + if (label == UpperBandwidthSelection) { - double actualUpperBandwidth = board->setUpperBandwidth(te->getText().getDoubleValue()); - // cb->setText(cb->getItemText(te->getSelectedId()),true); - std::cout << "Setting Upper Bandwidth to " << te->getText().getDoubleValue() << std::endl; - std::cout << "Actual Upper Bandwidth: " << actualUpperBandwidth << std::endl; - te->setText(String(actualUpperBandwidth)+" Hz",false); + Value val = label->getTextValue(); + double requestedValue = double(val.getValue()); + + if (requestedValue < 100.0 || requestedValue > 20000.0 || requestedValue < lastLowCutString.getFloatValue()) + { + editor->sendActionMessage("Value out of range."); + + label->setText(lastHighCutString, dontSendNotification); + + return; + } + + double actualUpperBandwidth = board->setUpperBandwidth(requestedValue); + + std::cout << "Setting Upper Bandwidth to " << requestedValue << std::endl; + std::cout << "Actual Upper Bandwidth: " << actualUpperBandwidth << std::endl; + label->setText(String((roundFloatToInt)(actualUpperBandwidth)), false); - repaint(); } else { - double actualLowerBandwidth = board->setLowerBandwidth(te->getText().getDoubleValue()); - // cb->setText(cb->getItemText(te->getSelectedId()),true); - std::cout << "Setting Lower Bandwidth to " << te->getText().getDoubleValue() << std::endl; - std::cout << "Actual Lower Bandwidth: " << actualLowerBandwidth << std::endl; - te->setText(String(actualLowerBandwidth)+" Hz",false); - repaint(); + Value val = label->getTextValue(); + double requestedValue = double(val.getValue()); + + if (requestedValue < 0.1 || requestedValue > 500.0 || requestedValue > lastHighCutString.getFloatValue()) + { + editor->sendActionMessage("Value out of range."); + + label->setText(lastLowCutString, dontSendNotification); + + return; + } + + double actualLowerBandwidth = board->setLowerBandwidth(requestedValue); + + std::cout << "Setting Upper Bandwidth to " << requestedValue << std::endl; + std::cout << "Actual Upper Bandwidth: " << actualLowerBandwidth << std::endl; + label->setText(String(roundFloatToInt(actualLowerBandwidth)), false); } } } @@ -125,16 +258,17 @@ void BandwidthInterface::labelTextChanged(Label* te) void BandwidthInterface::paint(Graphics& g) { - //g.setColour(Colours::lightgrey); - //g.fillRoundedRectangle(5,0,getWidth()-10,getHeight(),4.0f); - - // g.setColour(Colours::grey); + g.setColour(Colours::darkgrey); - g.setFont(Font("Small Text",15,Font::plain)); + g.setFont(Font("Small Text",10,Font::plain)); g.drawText(name, 0, 0, 200, 15, Justification::left, false); + g.drawText("Low: ", 0, 10, 200, 20, Justification::left, false); + + g.drawText("High: ", 0, 30, 200, 20, Justification::left, false); + } // Sample rate Options -------------------------------------------------------------------- @@ -144,7 +278,7 @@ SampleRateInterface::SampleRateInterface(RHD2000Thread* board_, board(board_), editor(editor_) { - name="SampleRate"; + name="Sample Rate"; sampleRateOptions.add("1.00 kS/s"); sampleRateOptions.add("1.25 kS/s"); @@ -187,12 +321,11 @@ void SampleRateInterface::comboBoxChanged(ComboBox* cb) { if (cb == rateSelection) { - board->setSampleRate(cb->getSelectedId()); - //cb->setText(cb->getItemText(cb->getSelectedId()),true); - std::cout << "Setting sample rate to index " << cb->getSelectedId() << std::endl; + board->setSampleRate(cb->getSelectedId()-1); + + std::cout << "Setting sample rate to index " << cb->getSelectedId()-1 << std::endl; editor->getEditorViewport()->makeEditorVisible(editor, false, true); - //repaint(); } } } @@ -202,13 +335,10 @@ void SampleRateInterface::comboBoxChanged(ComboBox* cb) void SampleRateInterface::paint(Graphics& g) { - //g.setColour(Colours::lightgrey); - //g.fillRoundedRectangle(5,0,getWidth()-10,getHeight(),4.0f); - - // g.setColour(Colours::grey); + g.setColour(Colours::darkgrey); - g.setFont(Font("Small Text",15,Font::plain)); + g.setFont(Font("Small Text",10,Font::plain)); g.drawText(name, 0, 0, 200, 15, Justification::left, false); @@ -220,10 +350,10 @@ void SampleRateInterface::paint(Graphics& g) HeadstageOptionsInterface::HeadstageOptionsInterface(RHD2000Thread* board_, RHD2000Editor* editor_, int hsNum) : - hsNumber(hsNum), isEnabled(false), board(board_), editor(editor_) + isEnabled(false), board(board_), editor(editor_) { - switch (hsNumber) + switch (hsNum) { case 0 : name = "A"; @@ -241,16 +371,31 @@ HeadstageOptionsInterface::HeadstageOptionsInterface(RHD2000Thread* board_, name = "X"; } - isEnabled = board->isHeadstageEnabled(hsNumber); - - enabledButton = new UtilityButton("on", Font("Small Text", 13, Font::plain)); - enabledButton->addListener(this); - enabledButton->setRadius(3.0f); - enabledButton->setBounds(25,2,20,19); - addAndMakeVisible(enabledButton); + hsNumber1 = hsNum*2; // data stream 1 + hsNumber2 = hsNumber1+1; // data stream 2 + channelsOnHs1 = 0; + channelsOnHs2 = 0; + + hsButton1 = new UtilityButton(" ", Font("Small Text", 13, Font::plain)); + hsButton1->setRadius(3.0f); + hsButton1->setBounds(23,1,20,17); + hsButton1->setEnabledState(false); + hsButton1->setCorners(true, false, true, false); + hsButton1->addListener(this); + addAndMakeVisible(hsButton1); + + hsButton2 = new UtilityButton(" ", Font("Small Text", 13, Font::plain)); + hsButton2->setRadius(3.0f); + hsButton2->setBounds(43,1,20,17); + hsButton2->setEnabledState(false); + hsButton2->setCorners(false, true, false, true); + hsButton2->addListener(this); + addAndMakeVisible(hsButton2); + + checkEnabledState(); } HeadstageOptionsInterface::~HeadstageOptionsInterface() @@ -258,6 +403,37 @@ HeadstageOptionsInterface::~HeadstageOptionsInterface() } +void HeadstageOptionsInterface::checkEnabledState() +{ + isEnabled = (board->isHeadstageEnabled(hsNumber1) || + board->isHeadstageEnabled(hsNumber2)); + + if (board->isHeadstageEnabled(hsNumber1)) + { + channelsOnHs1 = 32; + hsButton1->setLabel(String(channelsOnHs1)); + hsButton1->setEnabledState(true); + } else { + channelsOnHs1 = 0; + hsButton1->setLabel(" "); + hsButton1->setEnabledState(false); + } + + if (board->isHeadstageEnabled(hsNumber2)) + { + channelsOnHs2 = 32; + hsButton2->setLabel(String(channelsOnHs2)); + hsButton2->setEnabledState(true); + } else { + channelsOnHs2 = 0; + hsButton2->setLabel(" "); + hsButton2->setEnabledState(false); + } + + repaint(); + +} + void HeadstageOptionsInterface::buttonClicked(Button* button) { @@ -265,35 +441,35 @@ void HeadstageOptionsInterface::buttonClicked(Button* button) { //std::cout << "Acquisition is not active" << std::endl; - if (isEnabled) + if (button == hsButton1) { - isEnabled = false; - } - else + if (channelsOnHs1 == 32) + channelsOnHs1 = 16; + else + channelsOnHs1 = 32; + + //std::cout << "HS1 has " << channelsOnHs1 << " channels." << std::endl; + + hsButton1->setLabel(String(channelsOnHs1)); + board->setNumChannels(hsNumber1, channelsOnHs1); + + } else if (button == hsButton2) { - isEnabled = true; - } + if (channelsOnHs2 == 32) + channelsOnHs2 = 16; + else + channelsOnHs2 = 32; - board->enableHeadstage(hsNumber, isEnabled); + hsButton2->setLabel(String(channelsOnHs2)); + board->setNumChannels(hsNumber2, channelsOnHs2); + } - repaint(); editor->getEditorViewport()->makeEditorVisible(editor, false, true); } } -// void HeadstageOptionsInterface::mouseUp(const MouseEvent& event) -// { -// ///>>>> ???? WHY ISN"T THIS WORKING? - -// if (event.eventComponent == this) -// { - - -// } - -// } void HeadstageOptionsInterface::paint(Graphics& g) { @@ -306,8 +482,8 @@ void HeadstageOptionsInterface::paint(Graphics& g) else g.setColour(Colours::grey); - g.setFont(Font("Small Text",20,Font::plain)); + g.setFont(Font("Small Text",15,Font::plain)); - g.drawText(name, 8, 5, 200, 15, Justification::left, false); + g.drawText(name, 8, 2, 200, 15, Justification::left, false); } \ No newline at end of file diff --git a/Source/Processors/Editors/RHD2000Editor.h b/Source/Processors/Editors/RHD2000Editor.h index 49aa01c799530da8e8d226695bcd41b0262c0d82..5ae7c7f3891a369aa392d28751572a1e31faaa28 100644 --- a/Source/Processors/Editors/RHD2000Editor.h +++ b/Source/Processors/Editors/RHD2000Editor.h @@ -27,6 +27,8 @@ #include "../../../JuceLibraryCode/JuceHeader.h" #include "GenericEditor.h" +#include "SpikeDetectorEditor.h" // for ElectrodeButton + class HeadstageOptionsInterface; class SampleRateInterface; class BandwidthInterface; @@ -50,10 +52,27 @@ public: RHD2000Editor(GenericProcessor* parentNode, RHD2000Thread*, bool useDefaultParameterEditors); ~RHD2000Editor(); + void buttonEvent(Button* button); + + void scanPorts(); + + void startAcquisition(); + void stopAcquisition(); + + void channelChanged(int chan); + private: OwnedArray<HeadstageOptionsInterface> headstageOptionsInterfaces; - ScopedPointer<SampleRateInterface> RateInterface; + OwnedArray<ElectrodeButton> electrodeButtons; + + ScopedPointer<SampleRateInterface> sampleRateInterface; + ScopedPointer<BandwidthInterface> bandwidthInterface; + + ScopedPointer<UtilityButton> rescanButton; + ScopedPointer<UtilityButton> adcButton; + + ScopedPointer<Label> audioLabel; RHD2000Thread* board; @@ -73,9 +92,12 @@ public: void buttonClicked(Button* button); + void checkEnabledState(); + private: - int hsNumber; + int hsNumber1, hsNumber2; + int channelsOnHs1, channelsOnHs2; String name; bool isEnabled; @@ -83,7 +105,8 @@ private: RHD2000Thread* board; RHD2000Editor* editor; - ScopedPointer<UtilityButton> enabledButton; + ScopedPointer<UtilityButton> hsButton1; + ScopedPointer<UtilityButton> hsButton2; }; @@ -102,6 +125,8 @@ private: String name; + String lastLowCutString, lastHighCutString; + RHD2000Thread* board; RHD2000Editor* editor; diff --git a/Source/Processors/Editors/ResamplingNodeEditor.h b/Source/Processors/Editors/ResamplingNodeEditor.h index 58878b2ec85843c495524dd16e95c3cd2e192d22..ee3fce9c7f280b7456dcd9934ae14dce92e58a23 100644 --- a/Source/Processors/Editors/ResamplingNodeEditor.h +++ b/Source/Processors/Editors/ResamplingNodeEditor.h @@ -42,8 +42,8 @@ public: ResamplingNodeEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors); virtual ~ResamplingNodeEditor(); - void startAcquisition(); - void stopAcquisition(); + // void startAcquisition(); + //void stopAcquisition(); private: diff --git a/Source/Processors/Editors/SpikeDetectorEditor.cpp b/Source/Processors/Editors/SpikeDetectorEditor.cpp index 19b31041c723bebad9550da6d171d1cb43f0c377..20bfdee9d222af90d6e318903483ca9535adf8a0 100755 --- a/Source/Processors/Editors/SpikeDetectorEditor.cpp +++ b/Source/Processors/Editors/SpikeDetectorEditor.cpp @@ -175,12 +175,11 @@ void SpikeDetectorEditor::sliderEvent(Slider* slider) void SpikeDetectorEditor::buttonEvent(Button* button) { - if (electrodeEditorButtons[0]->getToggleState()) // EDIT is active - { - std::cout << "Editing active." << std::endl; + if (electrodeButtons.contains((ElectrodeButton*) button)) + { - if (electrodeButtons.contains((ElectrodeButton*) button)) + if (electrodeEditorButtons[0]->getToggleState()) // EDIT is active { ElectrodeButton* eb = (ElectrodeButton*) button; int electrodeNum = eb->getChannelNum()-1; @@ -195,9 +194,27 @@ void SpikeDetectorEditor::buttonEvent(Button* button) thresholdSlider->setActive(true); thresholdSlider->setValue(processor->getChannelThreshold(electrodeList->getSelectedItemIndex(), electrodeButtons.indexOf((ElectrodeButton*) button))); + } else { + + SpikeDetector* processor = (SpikeDetector*) getProcessor(); + + ElectrodeButton* eb = (ElectrodeButton*) button; + int electrodeNum = electrodeList->getSelectedItemIndex(); + int channelNum = electrodeButtons.indexOf(eb); + + processor->setChannelActive(electrodeNum, + channelNum, + button->getToggleState()); + + std::cout << "Disabling channel " << channelNum << + " of electrode " << electrodeNum << std::endl; + } + + } + int num = numElectrodes->getText().getIntValue(); if (button == upButton) @@ -530,7 +547,8 @@ void ElectrodeButton::paintButton(Graphics& g, bool isMouseOver, bool isButtonDo g.drawRect(0,0,getWidth(),getHeight(),1.0); - g.drawText(String(chan),0,0,getWidth(),getHeight(),Justification::centred,true); + if (chan >= 0) + g.drawText(String(chan),0,0,getWidth(),getHeight(),Justification::centred,true); } diff --git a/Source/Processors/Editors/SpikeDisplayEditor.cpp b/Source/Processors/Editors/SpikeDisplayEditor.cpp index 565967fa5b872b97228370d7918d68d384fa40eb..0f9fc3db2874aa5d0405f0a21b1ca8b5c913432c 100755 --- a/Source/Processors/Editors/SpikeDisplayEditor.cpp +++ b/Source/Processors/Editors/SpikeDisplayEditor.cpp @@ -143,7 +143,7 @@ void SpikeDisplayEditor::initializeButtons() allSubChansBtn->setToggleState(true, false); x += (w+xPad) * 2; - for (int i=0; i<nSubChannels; i++) + for (int i = 0; i < nSubChannels; i++) { String s = ""; s += i; diff --git a/Source/Processors/Editors/VisualizerEditor.cpp b/Source/Processors/Editors/VisualizerEditor.cpp index 4ae16d93a285fad2ba61b2627773a74e8035eb6d..ff432a7bfd4031996702babf17eb177bf18be598 100755 --- a/Source/Processors/Editors/VisualizerEditor.cpp +++ b/Source/Processors/Editors/VisualizerEditor.cpp @@ -170,6 +170,7 @@ void VisualizerEditor::buttonEvent(Button* button) { canvas = createNewCanvas(); + canvas->update(); if (isPlaying) canvas->beginAnimation(); diff --git a/Source/Processors/FileReader.cpp b/Source/Processors/FileReader.cpp index 26dd11906045e6c03e1986d64d562c6233a6b07f..f8c2929e82a22ea433ce17ccfd16350d1c8d34c4 100644 --- a/Source/Processors/FileReader.cpp +++ b/Source/Processors/FileReader.cpp @@ -50,6 +50,17 @@ AudioProcessorEditor* FileReader::createEditor() } +bool FileReader::isReady() +{ + if (input == 0) + { + sendActionMessage("No file selected in File Reader."); + return false; + } else { + return true; + } +} + float FileReader::getDefaultSampleRate() { diff --git a/Source/Processors/FileReader.h b/Source/Processors/FileReader.h index 38e5c697412298296cd82f05d88115f65c2f2d58..d688cba448837ae6d46f1504021a099d79e7f711 100644 --- a/Source/Processors/FileReader.h +++ b/Source/Processors/FileReader.h @@ -60,6 +60,8 @@ public: void updateSettings(); + bool isReady(); + bool isSource() { return true; diff --git a/Source/Processors/FilterNode.cpp b/Source/Processors/FilterNode.cpp index 7b7500f3597404978519241a036fb93ec3c890f2..ac453c109920bb17a2d5ac45afb565a44f1aa7a0 100755 --- a/Source/Processors/FilterNode.cpp +++ b/Source/Processors/FilterNode.cpp @@ -249,11 +249,11 @@ void FilterNode::process(AudioSampleBuffer& buffer, void FilterNode::saveCustomChannelParametersToXml(XmlElement* channelInfo, int channelNumber, bool isEventChannel) { - std::cout << "CHANNEL: " << channelNumber << std::endl; + //std::cout << "CHANNEL: " << channelNumber << std::endl; if (!isEventChannel && channelNumber > -1 && channelNumber < highCuts.size()) { - std::cout << "Saving custom parameters for filter node." << std::endl; + //std::cout << "Saving custom parameters for filter node." << std::endl; XmlElement* channelParams = channelInfo->createNewChildElement("PARAMETERS"); channelParams->setAttribute("highcut",highCuts[channelNumber]); @@ -285,4 +285,4 @@ void FilterNode::loadCustomChannelParametersFromXml(XmlElement* channelInfo, boo } -} +} diff --git a/Source/Processors/GenericProcessor.cpp b/Source/Processors/GenericProcessor.cpp index 1c9306941f922b2a7e0b9b191eef3fa0078f3b26..e935833ecf9a1767d6df54f4665ca0194558520f 100755 --- a/Source/Processors/GenericProcessor.cpp +++ b/Source/Processors/GenericProcessor.cpp @@ -337,6 +337,8 @@ void GenericProcessor::update() { Channel* sourceChan = sourceNode->eventChannels[i]; Channel* ch = new Channel(*sourceChan); + ch->sampleRate = getDefaultSampleRate(); + ch->bitVolts = getDefaultBitVolts(); eventChannels.add(ch); } diff --git a/Source/Processors/RecordNode.cpp b/Source/Processors/RecordNode.cpp index bfcc0e1eb80064d4409c0917cbb6c280eab37430..8115f6ac0d7d0c71f14cc7e7a9c012842a50edfa 100755 --- a/Source/Processors/RecordNode.cpp +++ b/Source/Processors/RecordNode.cpp @@ -472,15 +472,17 @@ String RecordNode::generateHeader(Channel* ch) { header += "header.channelType = 'Continuous';\n"; - - header += "header.sampleRate = "; - header += String(ch->sampleRate); - header += ";\n"; - header += "header.blockLength = '"; - header += BLOCK_LENGTH; - header += "';\n"; } + header += "header.sampleRate = "; + header += String(channelPointers[0]->sampleRate); // all channels need to have the + // same sample rate under the current + // scheme + header += ";\n"; + header += "header.blockLength = "; + header += BLOCK_LENGTH; + header += ";\n"; + header += "header.bitVolts = "; header += String(ch->bitVolts); header += ";\n"; diff --git a/Source/Processors/SourceNode.cpp b/Source/Processors/SourceNode.cpp index c04d71b034df54e1db588595056161abf709345f..5a8b53ccc8cd2f88e386c5b6ec1b6465b62bbc46 100755 --- a/Source/Processors/SourceNode.cpp +++ b/Source/Processors/SourceNode.cpp @@ -114,6 +114,7 @@ void SourceNode::updateSettings() { inputBuffer = dataThread->getBufferAddress(); + dataThread->updateChannelNames(); std::cout << "Input buffer address is " << inputBuffer << std::endl; } @@ -125,6 +126,7 @@ void SourceNode::updateSettings() eventChannels.add(ch); } + } void SourceNode::actionListenerCallback(const String& msg) @@ -209,6 +211,9 @@ AudioProcessorEditor* SourceNode::createEditor() if (getName().equalsIgnoreCase("Rhythm FPGA")) { editor = new RHD2000Editor(this, (RHD2000Thread*) dataThread.get(), true); + + // RHD2000Editor* r2e = (RHD2000Editor*) editor.get(); + // r2e->scanPorts(); } // else if (getName().equalsIgnoreCase("File Reader")) // { diff --git a/Source/Processors/SpikeDetector.cpp b/Source/Processors/SpikeDetector.cpp index 46fcd17d411428e275ef8b72598eb3248d22c9d5..1a5025421829e486c36f58e726e6d71872a5ec39 100755 --- a/Source/Processors/SpikeDetector.cpp +++ b/Source/Processors/SpikeDetector.cpp @@ -165,7 +165,7 @@ bool SpikeDetector::addElectrode(int nChans) float SpikeDetector::getDefaultThreshold() { - return 200.0f; + return 75.0f; } StringArray SpikeDetector::getElectrodeNames() @@ -222,9 +222,20 @@ int SpikeDetector::getChannel(int index, int i) } -void SpikeDetector::setChannelActive(int electrodeIndex, int i, bool active) +void SpikeDetector::setChannelActive(int electrodeIndex, int subChannel, bool active) { - *(electrodes[electrodeIndex]->isActive+i) = active; + + + currentElectrode = electrodeIndex; + currentChannelIndex = subChannel; + + std::cout << "Setting parameter 98 to " << active << std::endl; + + if (active) + setParameter(98, 1); + else + setParameter(98, 0); + } bool SpikeDetector::isChannelActive(int electrodeIndex, int i) @@ -252,6 +263,12 @@ void SpikeDetector::setParameter(int parameterIndex, float newValue) if (parameterIndex == 99 && currentElectrode > -1) { *(electrodes[currentElectrode]->thresholds+currentChannelIndex) = newValue; + } else if (parameterIndex == 98 && currentElectrode > -1) + { + if (newValue == 0.0f) + *(electrodes[currentElectrode]->isActive+currentChannelIndex) = false; + else + *(electrodes[currentElectrode]->isActive+currentChannelIndex) = true; } } @@ -343,17 +360,37 @@ void SpikeDetector::addWaveformToSpikeObject(SpikeObject* s, s->threshold[currentChannel] = (int) *(electrodes[electrodeNumber]->thresholds+currentChannel) / channels[chan]->bitVolts * 1000; // cycle through buffer - for (int sample = 0; sample < spikeLength; sample++) + + if (isChannelActive(electrodeNumber, currentChannel)) { - // warning -- be careful of bitvolts conversion - s->data[currentIndex] = uint16(getNextSample(*(electrodes[electrodeNumber]->channels+currentChannel)) / channels[chan]->bitVolts + 32768); + for (int sample = 0; sample < spikeLength; sample++) + { + + // warning -- be careful of bitvolts conversion + - currentIndex++; - sampleIndex++; - //std::cout << currentIndex << std::endl; + s->data[currentIndex] = uint16(getNextSample(*(electrodes[electrodeNumber]->channels+currentChannel)) / channels[chan]->bitVolts + 32768); + currentIndex++; + sampleIndex++; + + //std::cout << currentIndex << std::endl; + + } + } else { + for (int sample = 0; sample < spikeLength; sample++) + { + + // insert a blank spike if the + s->data[currentIndex] = 0; + currentIndex++; + sampleIndex++; + + //std::cout << currentIndex << std::endl; + + } } diff --git a/Source/Processors/SpikeDisplayNode.cpp b/Source/Processors/SpikeDisplayNode.cpp index 008bb1c21b9c01b45b1a95979af30ce06bcf7a73..161aed18f00c95c9393e388042bb8a21f9a4ac9e 100755 --- a/Source/Processors/SpikeDisplayNode.cpp +++ b/Source/Processors/SpikeDisplayNode.cpp @@ -125,10 +125,10 @@ void SpikeDisplayNode::setParameter(int param, float val) -void SpikeDisplayNode::process(AudioSampleBuffer& buffer, MidiBuffer& midiMessages, int& nSamples) +void SpikeDisplayNode::process(AudioSampleBuffer& buffer, MidiBuffer& events, int& nSamples) { - checkForEvents(midiMessages); // automatically calls 'handleEvent + checkForEvents(events); // automatically calls 'handleEvent } @@ -143,21 +143,3 @@ void SpikeDisplayNode::handleEvent(int eventType, MidiMessage& event, int sample } } - -bool SpikeDisplayNode::getNextSpike(SpikeObject* spike) -{ - std::cout<<"SpikeDisplayNode::getNextSpike()"<<std::endl; - /* - if (bufferSize<1 || spikebuffer.empty()) - return false; - else{ - SpikeObject s = spikebuffer.front(); - spikebuffer.pop(); - bufferSize--; - *spike = s; - return true; - } - */ - return false; - -} \ No newline at end of file diff --git a/Source/Processors/SpikeDisplayNode.h b/Source/Processors/SpikeDisplayNode.h index 54922440afe86becbe2164eed36ae1e62591c93c..66dcb85b44be709aece4b795320145063c9c961e 100755 --- a/Source/Processors/SpikeDisplayNode.h +++ b/Source/Processors/SpikeDisplayNode.h @@ -75,8 +75,6 @@ public: int getNumberOfChannelsForElectrode(int i); int getNumElectrodes(); - bool getNextSpike(SpikeObject* spike); - private: int numberOfSources; diff --git a/Source/Processors/Visualization/LfpDisplayCanvas.cpp b/Source/Processors/Visualization/LfpDisplayCanvas.cpp index 9cf1e5e94e27fd5efcfdd91e1e1c269c2e109de5..1235c8e1316e0a7fef9710368ee9c38b1b6d8b2d 100755 --- a/Source/Processors/Visualization/LfpDisplayCanvas.cpp +++ b/Source/Processors/Visualization/LfpDisplayCanvas.cpp @@ -40,6 +40,7 @@ LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : std::cout << "Setting displayBufferSize on LfpDisplayCanvas to " << displayBufferSize << std::endl; screenBuffer = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBuffer->clear(); viewport = new Viewport(); lfpDisplay = new LfpDisplay(this, viewport); @@ -52,10 +53,15 @@ LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : scrollBarThickness = viewport->getScrollBarThickness(); + + //viewport->getVerticalScrollBar()->addListener(this->scrollBarMoved(viewport->getVerticalScrollBar(), 1.0)); + + + addAndMakeVisible(viewport); addAndMakeVisible(timescale); - voltageRanges.add("50"); + voltageRanges.add("50"); voltageRanges.add("100"); voltageRanges.add("500"); voltageRanges.add("1000"); @@ -112,7 +118,7 @@ LfpDisplayCanvas::~LfpDisplayCanvas() void LfpDisplayCanvas::resized() { - timescale->setBounds(0,0,getWidth()-scrollBarThickness,30); + timescale->setBounds(leftmargin,0,getWidth()-scrollBarThickness-leftmargin,30); viewport->setBounds(0,30,getWidth(),getHeight()-90); lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, getChannelHeight()*nChans); @@ -122,7 +128,7 @@ void LfpDisplayCanvas::resized() spreadSelection->setBounds(345,getHeight()-30,100,25); // std::cout << "Canvas thinks LfpDisplay should be this high: " - // << lfpDisplay->getTotalHeight() << std::endl; + // << lfpDisplay->getTotalHeight() << std::endl; } @@ -146,7 +152,7 @@ void LfpDisplayCanvas::endAnimation() void LfpDisplayCanvas::update() { - nChans = processor->getNumInputs(); + nChans = jmax(processor->getNumInputs(),1); sampleRate = processor->getSampleRate(); std::cout << "Setting num inputs on LfpDisplayCanvas to " << nChans << std::endl; @@ -154,10 +160,20 @@ void LfpDisplayCanvas::update() refreshScreenBuffer(); lfpDisplay->setNumChannels(nChans); - lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight()); + // update channel names + for (int i = 0; i < processor->getNumInputs(); i++) + { - repaint(); + String chName = processor->channels[i]->getName(); + + //std::cout << chName << std::endl; + + lfpDisplay->channelInfo[i]->setName(chName); + + } + + lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight()); } @@ -185,9 +201,12 @@ void LfpDisplayCanvas::comboBoxChanged(ComboBox* cb) timescale->setTimebase(timebase); } + + + int LfpDisplayCanvas::getChannelHeight() { - return spreads[spreadSelection->getSelectedId()-1].getIntValue(); + return spreads[spreadSelection->getSelectedId()-1].getIntValue(); } @@ -227,46 +246,58 @@ void LfpDisplayCanvas::refreshScreenBuffer() // for (int i = 0; i < w; i++) // { - // float x = float(i); + // float x = float(i); - // for (int n = 0; n < nChans; n++) - // { - // waves[n][i*2] = x; - // waves[n][i*2+1] = 0.5f; // line in center of display - // } + // for (int n = 0; n < nChans; n++) + // { + // waves[n][i*2] = x; + // waves[n][i*2+1] = 0.5f; // line in center of display + // } // } } void LfpDisplayCanvas::updateScreenBuffer() { + + // copy new samples from the displayBuffer into the screenBuffer (waves) + int maxSamples = lfpDisplay->getWidth() - leftmargin; - lastScreenBufferIndex = screenBufferIndex; + if (screenBufferIndex >= maxSamples) // wrap around if we reached right edge before + screenBufferIndex = 0; - int maxSamples = lfpDisplay->getWidth(); + lastScreenBufferIndex = screenBufferIndex; int index = processor->getDisplayBufferIndex(); - int nSamples = index - displayBufferIndex; + int nSamples = index - displayBufferIndex; // N new samples to be addeddisplayBufferIndex if (nSamples < 0) // buffer has reset to 0 { nSamples = (displayBufferSize - displayBufferIndex) + index; } - float ratio = sampleRate * timebase / float(getWidth()); + float ratio = sampleRate * timebase / float(getWidth() - leftmargin - scrollBarThickness); - // this number is crucial: + // this number is crucial: converting from samples to values (in px) for the screen buffer int valuesNeeded = (int) float(nSamples) / ratio; + + if ( screenBufferIndex + valuesNeeded > maxSamples) // crop number of samples to fit cavas width + { + valuesNeeded = maxSamples - screenBufferIndex; + } + float subSampleOffset = 0.0; - int nextPos = (displayBufferIndex + 1) % displayBufferSize; + + displayBufferIndex = displayBufferIndex % displayBufferSize; // make sure we're not overshooting + int nextPos = (displayBufferIndex + 1) % displayBufferSize; // position next to displayBufferIndex in display buffer to copy from if (valuesNeeded > 0 && valuesNeeded < 1000) { - for (int i = 0; i < valuesNeeded; i++) + for (int i = 0; i < valuesNeeded; i++) // also fill one extra sample for line drawing interpolation to match across draws { float gain = 1.0; float alpha = (float) subSampleOffset; @@ -289,26 +320,14 @@ void LfpDisplayCanvas::updateScreenBuffer() 1, // numSamples alpha*gain); // gain - //waves[channel][screenBufferIndex*2+1] = - // *(displayBuffer->getSampleData(channel, displayBufferIndex))*invAlpha*gain*displayGain; - - //waves[channel][screenBufferIndex*2+1] += - // *(displayBuffer->getSampleData(channel, nextPos))*alpha*gain*displayGain; - - //waves[channel][screenBufferIndex*2+1] += 0.5f; // to center in viewport } - //// now do the event channel - //// waves[nChans][screenBufferIndex*2+1] = - // *(displayBuffer->getSampleData(nChans, displayBufferIndex)); - - subSampleOffset += ratio; while (subSampleOffset >= 1.0) { - if (++displayBufferIndex >= displayBufferSize) + if (++displayBufferIndex > displayBufferSize) displayBufferIndex = 0; nextPos = (displayBufferIndex + 1) % displayBufferSize; @@ -316,9 +335,9 @@ void LfpDisplayCanvas::updateScreenBuffer() } screenBufferIndex++; - screenBufferIndex %= maxSamples; } + } else @@ -341,20 +360,29 @@ void LfpDisplayCanvas::paint(Graphics& g) { //std::cout << "Painting" << std::endl; - g.setColour(Colour(25,25,25)); - + g.setColour(Colour(0,18,43)); //background color g.fillRect(0, 0, getWidth(), getHeight()); - g.setColour(Colour(40,40,40)); + g.setGradientFill(ColourGradient(Colour(50,50,50),0,0, + Colour(25,25,25),0,30, + false)); - int w = getWidth()-scrollBarThickness; + g.fillRect(0, 0, getWidth()-scrollBarThickness, 30); - for (int i = 1; i < 10; i++) + g.setColour(Colours::black); + + g.drawLine(0,30,getWidth()-scrollBarThickness,30); + + g.setColour(Colour(25,25,60)); // timing grid color + + int w = getWidth()-scrollBarThickness-leftmargin; + + for (int i = 0; i < 10; i++) { - if (i == 5) - g.drawLine(w/10*i,0,w/10*i,getHeight()-60,3.0f); + if (i == 5 || i == 0) + g.drawLine(w/10*i+leftmargin,0,w/10*i+leftmargin,getHeight()-60,3.0f); else - g.drawLine(w/10*i,0,w/10*i,getHeight()-60,1.0f); + g.drawLine(w/10*i+leftmargin,0,w/10*i+leftmargin,getHeight()-60,1.0f); } g.drawLine(0,getHeight()-60,getWidth(),getHeight()-60,3.0f); @@ -364,17 +392,16 @@ void LfpDisplayCanvas::paint(Graphics& g) g.setColour(Colour(100,100,100)); g.drawText("Voltage range (uV)",5,getHeight()-55,300,20,Justification::left, false); - g.drawText("Timebase (s)",175,getHeight()-55,300,20,Justification::left, false); g.drawText("Spread (px)",345,getHeight()-55,300,20,Justification::left, false); } -void LfpDisplayCanvas::refresh() +void LfpDisplayCanvas::refresh() { updateScreenBuffer(); - lfpDisplay->refresh(); + lfpDisplay->refresh(); // redraws only the new part of the screen buffer //getPeer()->performAnyPendingRepaintsNow(); @@ -429,15 +456,7 @@ LfpTimescale::~LfpTimescale() void LfpTimescale::paint(Graphics& g) { - g.setGradientFill(ColourGradient(Colour(50,50,50),0,0, - Colour(25,25,25),0,getHeight(), - false)); - - g.fillAll(); - - g.setColour(Colours::black); - - g.drawLine(0,getHeight(),getWidth(),getHeight()); + g.setFont(font); @@ -485,15 +504,29 @@ LfpDisplay::LfpDisplay(LfpDisplayCanvas* c, Viewport* v) : addMouseListener(this, true); - for (int i = 0; i < 10; i++) - { - channelColours.add(Colour(200,200,255-i*25)); - } - - for (int i = 10; i > -1; i--) - { - channelColours.add(Colour(200,200,255-i*25)); - } + // hue cycle + //for (int i = 0; i < 15; i++) + //{ + // channelColours.add(Colour(float(sin((3.14/2)*(float(i)/15))),float(1.0),float(1),float(1.0))); + //} + + //hand-built palette + channelColours.add(Colour(224,185,36)); + channelColours.add(Colour(214,210,182)); + channelColours.add(Colour(243,119,33)); + channelColours.add(Colour(186,157,168)); + channelColours.add(Colour(237,37,36)); + channelColours.add(Colour(179,122,79)); + channelColours.add(Colour(217,46,171)); + channelColours.add(Colour(217, 139,196)); + channelColours.add(Colour(101,31,255)); + channelColours.add(Colour(141,111,181)); + channelColours.add(Colour(48,117,255)); + channelColours.add(Colour(184,198,224)); + channelColours.add(Colour(116,227,156)); + channelColours.add(Colour(150,158,155)); + channelColours.add(Colour(82,173,0)); + channelColours.add(Colour(125,99,32)); } @@ -509,6 +542,7 @@ void LfpDisplay::setNumChannels(int numChannels) deleteAllChildren(); channels.clear(); + channelInfo.clear(); totalHeight = 0; @@ -527,6 +561,16 @@ void LfpDisplay::setNumChannels(int numChannels) channels.add(lfpChan); + LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, i); + + lfpInfo->setColour(channelColours[i % channelColours.size()]); + lfpInfo->setRange(range); + lfpInfo->setChannelHeight(canvas->getChannelHeight()); + + addAndMakeVisible(lfpInfo); + + channelInfo.add(lfpInfo); + totalHeight += lfpChan->getChannelHeight(); } @@ -535,7 +579,7 @@ void LfpDisplay::setNumChannels(int numChannels) int LfpDisplay::getTotalHeight() { - return totalHeight; + return totalHeight; } void LfpDisplay::resized() @@ -543,20 +587,29 @@ void LfpDisplay::resized() int totalHeight = 0; - for (int i = 0; i < numChans; i++) + for (int i = 0; i < channels.size(); i++) { LfpChannelDisplay* disp = channels[i]; - disp->setBounds(0, + disp->setBounds(canvas->leftmargin, totalHeight-disp->getChannelOverlap()/2, getWidth(), disp->getChannelHeight()+disp->getChannelOverlap()); + LfpChannelDisplayInfo* info = channelInfo[i]; + + info->setBounds(0, + totalHeight-disp->getChannelOverlap()/2, + canvas->leftmargin, + disp->getChannelHeight()+disp->getChannelOverlap()); + totalHeight += disp->getChannelHeight(); } + canvas->fullredraw = true; //issue full redraw + // std::cout << "Total height: " << totalHeight << std::endl; } @@ -577,73 +630,148 @@ void LfpDisplay::refresh() for (int i = 0; i < numChans; i++) { - int componentTop = getChildComponent(i)->getY(); - int componentBottom = getChildComponent(i)->getHeight() + componentTop; + int componentTop = channels[i]->getY(); + int componentBottom = channels[i]->getHeight() + componentTop; if ((topBorder <= componentBottom && bottomBorder >= componentTop)) { - getChildComponent(i)->repaint(); - + if (canvas->fullredraw) + { + channels[i]->fullredraw = true; + channels[i]->repaint(); + channelInfo[i]->repaint(); + + } else { + channels[i]->repaint(canvas->lastScreenBufferIndex-2, 0, (canvas->screenBufferIndex-canvas->lastScreenBufferIndex)+3, getChildComponent(i)->getHeight() ); //repaint only the updated portion + // we redraw from -2 to +1 relative to the real redraw window, the -2 makes sure that the lines join nicely, and the +1 draws the vertical update line + } //std::cout << i << std::endl; } } - + + canvas->fullredraw = false; } void LfpDisplay::setRange(float r) { - range = r; for (int i = 0; i < numChans; i++) { - channels[i]->setRange(range); - } +} +int LfpDisplay::getRange() +{ + return channels[0]->getRange(); } + void LfpDisplay::setChannelHeight(int r) { for (int i = 0; i < numChans; i++) { channels[i]->setChannelHeight(r); + channelInfo[i]->setChannelHeight(r); } resized(); } -void LfpDisplay::mouseDown(const MouseEvent& event) +int LfpDisplay::getChannelHeight() { - //int x = event.getMouseDownX(); - //int y = event.getMouseDownY(); + return channels[0]->getChannelHeight(); +} + + + + void LfpDisplay::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel ) { + + //std::cout << "Mouse wheel " << e.mods.isCommandDown() << " " << wheel.deltaY << std::endl; + + if (e.mods.isCommandDown()){ // CTRL + scroll wheel -> change channel spacing + // + // this should also scroll to keep the selected channel at a constant y! + // + int h = getChannelHeight(); + if (wheel.deltaY>0){ + setChannelHeight(h+1); + } else{ + if (h>5) + setChannelHeight(h-1); + } + } else { + if(e.mods.isShiftDown()) {// SHIFT + scroll wheel -> change channel range + int h= getRange(); + if (wheel.deltaY>0){ + setRange(h+10); + } else{ + if (h>11) + setRange(h-10); + } + + } else{ // just scroll + // passes the event up to the viewport so the screen scrolls + if (viewport != nullptr && e.eventComponent == this) // passes only if it's not a listening event + viewport->mouseWheelMove(e.getEventRelativeTo(canvas), wheel); + + } + } + + canvas->fullredraw = true;//issue full redraw + + refresh(); - //std::cout << "Mouse down at " << x << ", " << y << std::endl; + } - for (int n = 0; n < numChans; n++) +void LfpDisplay::mouseDown(const MouseEvent& event) +{ + //int y = event.getMouseDownY(); //relative to each channel pos + MouseEvent canvasevent = event.getEventRelativeTo(viewport); + int y = canvasevent.getMouseDownY() + viewport->getViewPositionY(); // need to account for scrolling + + int dist=0; int mindist=10000; int closest=5; + for (int n = 0; n < numChans; n++) // select closest instead of relying ot eventComponent { channels[n]->deselect(); + + int cpos=(channels[n]->getY() + (channels[n]->getHeight()/2)); + dist=int(abs( y - cpos )); + + //std::cout << "Mouse down at " << y << " pos is "<< cpos << "n:" << n << " dist " << dist << std::endl; + + if (dist<mindist) { + mindist=dist-1; + closest=n; + } } - LfpChannelDisplay* lcd = (LfpChannelDisplay*) event.eventComponent; + //LfpChannelDisplay* lcd = (LfpChannelDisplay*) event.eventComponent; + //lcd->select(); - lcd->select(); + channels[closest]->select(); - repaint(); + canvas->fullredraw = true;//issue full redraw + + refresh(); } // ------------------------------------------------------------------ LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, int channelNumber) : - canvas(c), isSelected(false), chan(channelNumber), channelHeight(40), channelOverlap(60), range(1000.0f) + canvas(c), isSelected(false), chan(channelNumber), channelHeight(40), channelOverlap(300), range(1000.0f) { + + name = String(channelNumber+1); // default is to make the channelNumber the name + + channelHeightFloat = (float) channelHeight; channelFont = Font("Default", channelHeight*0.6, Font::plain); @@ -662,14 +790,17 @@ void LfpChannelDisplay::paint(Graphics& g) //g.fillAll(Colours::grey); - g.setColour(Colours::yellow); + g.setColour(Colours::yellow); // draw most recent drawn sample position + g.drawLine(canvas->screenBufferIndex+1, 0, canvas->screenBufferIndex+1, getHeight()); - g.drawLine(canvas->screenBufferIndex, 0, canvas->screenBufferIndex, getHeight()-channelOverlap); + //g.setColour(Colours::red); // draw oldest drawn sample position + //g.drawLine(canvas->lastScreenBufferIndex, 0, canvas->lastScreenBufferIndex, getHeight()-channelOverlap); int center = getHeight()/2; if (isSelected) { + g.setColour(Colours::lightgrey); g.fillRect(0,center-channelHeight/2,10,channelHeight); g.drawLine(0,center+channelHeight/2,getWidth(),center+channelHeight/2); @@ -686,56 +817,73 @@ void LfpChannelDisplay::paint(Graphics& g) g.drawLine(0, getHeight()/2, getWidth(), getHeight()/2); int stepSize = 1; - int from = 0; - int to = 0; + int from = 0; // for vertical line drawing in the LFP data + int to = 0; g.setColour(lineColour); - for (int i = 0; i < getWidth()-stepSize; i += stepSize) + //for (int i = 0; i < getWidth()-stepSize; i += stepSize) // redraw entire display + int ifrom = canvas->lastScreenBufferIndex - 3; // need to start drawing a bit before the actual redraw windowfor the interpolated line to join correctly + + if (ifrom < 0) + ifrom = 0; + + int ito = canvas->screenBufferIndex - 1; + + if (fullredraw) + { + ifrom = 0; //canvas->leftmargin; + ito = getWidth()-stepSize; + fullredraw = false; + } + + for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion { - // drawLine makes for nice anti-aliased plots, but is pretty slow - // g.drawLine(i, - // (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2, - // i+stepSize, - // (canvas->getYCoord(chan, i+stepSize)/range*channelHeightFloat)+getHeight()/2); - - - // // pixel wise line plot has no anti-aliasing, but runs much faster - double a = (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2; - double b = (canvas->getYCoord(chan, i+stepSize)/range*channelHeightFloat)+getHeight()/2; - - if (a<b){ - from = (a); - to = (b); - } else { - from = (b); - to = (a); - } - - if ((to-from) < 40){ // if there is too much vertical range in one pixel, don't draw the full line for speed reasons - for (int j = from; j <= to; j += 1) - { - g.setPixel(i,j); - } - } else if ((to-from) < 100){ - for (int j = from; j <= to; j += 2) - { - g.setPixel(i,j); - } - } else { - g.setPixel(i,to); - g.setPixel(i,from); - } - - - + // drawLine makes for ok anti-aliased plots, but is pretty slow + g.drawLine(i, + (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2, + i+stepSize, + (canvas->getYCoord(chan, i+stepSize)/range*channelHeightFloat)+getHeight()/2); + + if (false) // switched back to line drawing now that we only draw partial updates + { + + // // pixel wise line plot has no anti-aliasing, but runs much faster + double a = (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2; + double b = (canvas->getYCoord(chan, i+stepSize)/range*channelHeightFloat)+getHeight()/2; + + if (a<b){ + from = (a); + to = (b); + } else { + from = (b); + to = (a); + } + + if ((to-from) < 40){ // if there is too much vertical range in one pixel, don't draw the full line for speed reasons + for (int j = from; j <= to; j += 1) + { + g.setPixel(i,j); + } + } else if ((to-from) < 100){ + for (int j = from; j <= to; j += 2) + { + g.setPixel(i,j); + } + } else { + g.setPixel(i,to); + g.setPixel(i,from); + } + + } + } // g.setColour(lineColour.withAlpha(0.7f)); // alpha on seems to decrease draw speed - g.setFont(channelFont); - g.setFont(channelHeightFloat); + // g.setFont(channelFont); + // g.setFont(channelHeightFloat*0.6); - g.drawText(String(chan+1), 15, center-channelHeight/2, 200, channelHeight, Justification::left, false); + // g.drawText(String(chan+1), 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); } @@ -748,6 +896,12 @@ void LfpChannelDisplay::setRange(float r) //std::cout << "Range: " << r << std::endl; } +int LfpChannelDisplay::getRange() +{ + return range; +} + + void LfpChannelDisplay::select() { isSelected = true; @@ -768,7 +922,8 @@ void LfpChannelDisplay::setChannelHeight(int c) { channelHeight = c; channelHeightFloat = (float) channelHeight; - channelOverlap = channelHeight / 2; + //channelOverlap = channelHeight / 2; //clips data too early, + channelOverlap = channelHeight *5; } int LfpChannelDisplay::getChannelHeight() @@ -786,4 +941,35 @@ void LfpChannelDisplay::setChannelOverlap(int overlap) int LfpChannelDisplay::getChannelOverlap() { return channelOverlap; -} \ No newline at end of file +} + +void LfpChannelDisplay::setName(String name_) +{ + name = name_; +} + +// ------------------------------- + +LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, int ch) + : LfpChannelDisplay(canvas_, ch) +{ + +} + +void LfpChannelDisplayInfo::paint(Graphics& g) +{ + + + + int center = getHeight()/2; + + g.setColour(lineColour); + + g.setFont(channelFont); + g.setFont(channelHeightFloat*0.3); + + g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); + +} + + \ No newline at end of file diff --git a/Source/Processors/Visualization/LfpDisplayCanvas.h b/Source/Processors/Visualization/LfpDisplayCanvas.h index ebe5250480859a471bdd013048050ded9b39afd9..298473735b79c8210fe9536b1017e5c5e3eaa40c 100755 --- a/Source/Processors/Visualization/LfpDisplayCanvas.h +++ b/Source/Processors/Visualization/LfpDisplayCanvas.h @@ -32,6 +32,7 @@ class LfpDisplayNode; class LfpTimescale; class LfpDisplay; class LfpChannelDisplay; +class LfpChannelDisplayInfo; /** @@ -43,7 +44,7 @@ class LfpChannelDisplay; class LfpDisplayCanvas : public Visualizer, public ComboBox::Listener - + { public: LfpDisplayCanvas(LfpDisplayNode* n); @@ -79,7 +80,10 @@ public: void loadVisualizerParameters(XmlElement* xml); + //void scrollBarMoved(ScrollBar *scrollBarThatHasMoved, double newRangeStart); + bool fullredraw; // used to indicate that a full redraw is required. is set false after each full redraw, there is a similar switch for ach ch display; + static const int leftmargin=50; // left margin for lfp plots (so the ch number text doesnt overlap) private: @@ -93,6 +97,9 @@ private: static const int MAX_N_SAMP = 5000; // maximum display size in pixels //float waves[MAX_N_CHAN][MAX_N_SAMP*2]; // we need an x and y point for each sample + + + LfpDisplayNode* processor; AudioSampleBuffer* displayBuffer; AudioSampleBuffer* screenBuffer; @@ -120,6 +127,7 @@ private: int nChans; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfpDisplayCanvas); }; @@ -163,9 +171,17 @@ public: void resized(); void mouseDown(const MouseEvent& event); + void mouseWheelMove(const MouseEvent& event, const MouseWheelDetails& wheel ) ; + void setRange(float range); + int getRange(); + void setChannelHeight(int r); + int getChannelHeight(); + + Array<LfpChannelDisplay*> channels; + Array<LfpChannelDisplayInfo*> channelInfo; private: int numChans; @@ -175,7 +191,6 @@ private: LfpDisplayCanvas* canvas; Viewport* viewport; - Array<LfpChannelDisplay*> channels; Array<Colour> channelColours; float range; @@ -193,6 +208,8 @@ public: void select(); void deselect(); + void setName(String); + void setColour(Colour c); void setChannelHeight(int); @@ -202,8 +219,11 @@ public: int getChannelOverlap(); void setRange(float range); + int getRange(); -private: + bool fullredraw; // used to indicate that a full redraw is required. is set false after each full redraw + +protected: LfpDisplayCanvas* canvas; @@ -211,6 +231,8 @@ private: int chan; + String name; + Font channelFont; Colour lineColour; @@ -223,5 +245,13 @@ private: }; +class LfpChannelDisplayInfo : public LfpChannelDisplay +{ +public: + LfpChannelDisplayInfo(LfpDisplayCanvas*, int channelNumber); + + void paint(Graphics& g); + +}; #endif // __LFPDISPLAYCANVAS_H_B711873A__ diff --git a/Source/Processors/Visualization/SpikeDisplayCanvas.cpp b/Source/Processors/Visualization/SpikeDisplayCanvas.cpp index b36307fc645d395003a7dfbe2fea9ec35c51cc61..fe16ddafc0a1c36265b1b66d4345aad9fbdd701a 100755 --- a/Source/Processors/Visualization/SpikeDisplayCanvas.cpp +++ b/Source/Processors/Visualization/SpikeDisplayCanvas.cpp @@ -37,6 +37,11 @@ SpikeDisplayCanvas::SpikeDisplayCanvas(SpikeDisplayNode* n) : scrollBarThickness = viewport->getScrollBarThickness(); + clearButton = new UtilityButton("Clear plots", Font("Small Text", 13, Font::plain)); + clearButton->setRadius(3.0f); + clearButton->addListener(this); + addAndMakeVisible(clearButton); + addAndMakeVisible(viewport); setWantsKeyboardFocus(true); @@ -52,14 +57,14 @@ SpikeDisplayCanvas::~SpikeDisplayCanvas() void SpikeDisplayCanvas::beginAnimation() { - std::cout << "Beginning animation." << std::endl; + std::cout << "SpikeDisplayCanvas beginning animation." << std::endl; startCallbacks(); } void SpikeDisplayCanvas::endAnimation() { - std::cout << "Ending animation." << std::endl; + std::cout << "SpikeDisplayCanvas ending animation." << std::endl; stopCallbacks(); } @@ -67,17 +72,16 @@ void SpikeDisplayCanvas::endAnimation() void SpikeDisplayCanvas::update() { - std::cout << "UPDATING SpikeDisplayCanvas" << std::endl; + std::cout << "Updating SpikeDisplayCanvas" << std::endl; int nPlots = processor->getNumElectrodes(); - spikeDisplay->clear(); + spikeDisplay->removePlots(); for (int i = 0; i < nPlots; i++) { spikeDisplay->addSpikePlot(processor->getNumberOfChannelsForElectrode(i), i); } - //initializeSpikePlots(); spikeDisplay->resized(); spikeDisplay->repaint(); } @@ -86,8 +90,6 @@ void SpikeDisplayCanvas::update() void SpikeDisplayCanvas::refreshState() { // called when the component's tab becomes visible again - // displayBufferIndex = processor->getDisplayBufferIndex(); - // screenBufferIndex = 0; resized(); } @@ -96,6 +98,9 @@ void SpikeDisplayCanvas::resized() viewport->setBounds(0,0,getWidth(),getHeight()-90); spikeDisplay->setBounds(0,0,getWidth()-scrollBarThickness, spikeDisplay->getTotalHeight()); + + clearButton->setBounds(10, getHeight()-40, 100,20); + } void SpikeDisplayCanvas::paint(Graphics& g) @@ -130,25 +135,15 @@ void SpikeDisplayCanvas::processSpikeEvents() const uint8_t* dataptr = message.getRawData(); int bufferSize = message.getRawDataSize(); - //int nSamples = (bufferSize-4)/2; SpikeObject newSpike; - //SpikeObject simSpike; - unpackSpike(&newSpike, dataptr, bufferSize); + bool isValid = unpackSpike(&newSpike, dataptr, bufferSize); int electrodeNum = newSpike.source; - // generateSimulatedSpike(&simSpike, 0, 0); - - // for (int i = 0; i < newSpike.nChannels * newSpike.nSamples; i++) - // { - // simSpike.data[i] = newSpike.data[i%80] + 5000;// * 3 - 10000; - // } - - // simSpike.nSamples = 40; - - spikeDisplay->plotSpike(newSpike, electrodeNum); + if (isValid) + spikeDisplay->plotSpike(newSpike, electrodeNum); } @@ -160,7 +155,10 @@ void SpikeDisplayCanvas::processSpikeEvents() bool SpikeDisplayCanvas::keyPressed(const KeyPress& key) { - if (key.getKeyCode() == 67) // C + + KeyPress c = KeyPress::createFromDescription("c"); + + if (key.isKeyCode(c.getKeyCode())) // C { spikeDisplay->clear(); @@ -172,6 +170,17 @@ bool SpikeDisplayCanvas::keyPressed(const KeyPress& key) } +void SpikeDisplayCanvas::buttonClicked(Button* button) +{ + + if (button == clearButton) + { + spikeDisplay->clear(); + } +} + + + // ---------------------------------------------------------------- SpikeDisplay::SpikeDisplay(SpikeDisplayCanvas* sdc, Viewport* v) : @@ -199,6 +208,12 @@ void SpikeDisplay::clear() } +void SpikeDisplay::removePlots() +{ + spikePlots.clear(); + +} + void SpikeDisplay::addSpikePlot(int numChannels, int electrodeNum) { @@ -238,6 +253,9 @@ void SpikeDisplay::resized() float width, height; + + float maxHeight = 0; + for (int i = 0; i < spikePlots.size(); i++) { @@ -271,6 +289,8 @@ void SpikeDisplay::resized() spikePlots[i]->setBounds(width*column, row*height, width, height); + maxHeight = jmax(maxHeight, row*height + height); + if (spikePlots[i]->nChannels == 1) { stereotrodeStart = (int)(height*(float(row)+1)); @@ -282,6 +302,7 @@ void SpikeDisplay::resized() } + for (int i = 0; i < spikePlots.size(); i++) { @@ -293,21 +314,23 @@ void SpikeDisplay::resized() if (spikePlots[i]->nChannels == 2) { spikePlots[i]->setBounds(x, y+stereotrodeStart, w2, h2); + maxHeight = jmax(maxHeight, (float) y+stereotrodeStart+h2); } else if (spikePlots[i]->nChannels == 4) { spikePlots[i]->setBounds(x, y+stereotrodeStart+tetrodeStart, w2, h2); + maxHeight = jmax(maxHeight, (float) y+stereotrodeStart+tetrodeStart+h2); } + } - totalHeight = 5000; // don't even deal with making the display the correct height + totalHeight = (int) maxHeight + 50; - if (totalHeight < getHeight()) - { - canvas->resized(); - } + // std::cout << "New height = " << totalHeight << std::endl; + + setBounds(0, 0, getWidth(), totalHeight); } } @@ -365,7 +388,7 @@ SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p) : // nHistAx = 1; // break; default: // unsupported number of axes provided - std::cout<<"SpikePlot as UNKNOWN, defaulting to SINGLE_PLOT"<<std::endl; + std::cout << "SpikePlot as UNKNOWN, defaulting to SINGLE_PLOT" << std::endl; nWaveAx = 1; nProjAx = 0; plotType = SINGLE_PLOT; @@ -374,6 +397,15 @@ SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p) : initAxes(); + for (int i = 0; i < nChannels; i++) + { + UtilityButton* rangeButton = new UtilityButton("250", Font("Small Text", 10, Font::plain)); + rangeButton->setRadius(3.0f); + rangeButton->addListener(this); + addAndMakeVisible(rangeButton); + + rangeButtons.add(rangeButton); + } } @@ -424,6 +456,7 @@ void SpikePlot::initAxes() WaveAxes* wAx = new WaveAxes(WAVE1 + i); wAxes.add(wAx); addAndMakeVisible(wAx); + ranges.add(250.0f); // default range is 250 microvolts } for (int i = 0; i < nProjAx; i++) @@ -433,14 +466,14 @@ void SpikePlot::initAxes() addAndMakeVisible(pAx); } - setLimitsOnAxes(); // initialize thel limits on the axes + setLimitsOnAxes(); // initialize the ranges } void SpikePlot::resized() { float width = getWidth()-10; - float height = getHeight()-20; + float height = getHeight()-25; float axesWidth, axesHeight; @@ -472,10 +505,43 @@ void SpikePlot::resized() } for (int i = 0; i < nWaveAx; i++) - wAxes[i]->setBounds(5 + (i % nWaveCols) * axesWidth/nWaveCols, 15 + (i/nWaveCols) * axesHeight, axesWidth/nWaveCols, axesHeight); + { + wAxes[i]->setBounds(5 + (i % nWaveCols) * axesWidth/nWaveCols, 20 + (i/nWaveCols) * axesHeight, axesWidth/nWaveCols, axesHeight); + rangeButtons[i]->setBounds(8 + (i % nWaveCols) * axesWidth/nWaveCols, + 20 + (i/nWaveCols) * axesHeight + axesHeight - 18, + 25, 15); + } for (int i = 0; i < nProjAx; i++) - pAxes[i]->setBounds(5 + (1 + i%nProjCols) * axesWidth, 15 + (i/nProjCols) * axesHeight, axesWidth, axesHeight); + pAxes[i]->setBounds(5 + (1 + i%nProjCols) * axesWidth, 20 + (i/nProjCols) * axesHeight, axesWidth, axesHeight); + + +} + +void SpikePlot::buttonClicked(Button* button) +{ + UtilityButton* buttonThatWasClicked = (UtilityButton*) button; + + int index = rangeButtons.indexOf(buttonThatWasClicked); + String label; + + if (ranges[index] == 250.0f) + { + ranges.set(index, 500.0f); + label = "500"; + } else if (ranges[index] == 500.0f) + { + ranges.set(index, 100.0f); + label = "100"; + } else if (ranges[index] == 100.0f) + { + ranges.set(index, 250.0f); + label = "250"; + } + + buttonThatWasClicked->setLabel(label); + + setLimitsOnAxes(); } @@ -483,18 +549,17 @@ void SpikePlot::setLimitsOnAxes() { //std::cout<<"SpikePlot::setLimitsOnAxes()"<<std::endl; - // for (int i = 0; i < nWaveAx; i++) - // wAxes[i]->setYLims(limits[i][0], limits[i][1]); + for (int i = 0; i < nWaveAx; i++) + wAxes[i]->setRange(ranges[i]); - // // Each Projection sets its limits using the limits of the two waveform dims it represents. - // // Convert projection number to indecies, and then set the limits using those indices - // int j1, j2; - // for (int i = 0; i < nProjAx; i++) - // { - // n2ProjIdx(pAxes[i]->getType(), &j1, &j2); - // pAxes[i]->setYLims(limits[j1][0], limits[j1][1]); - // pAxes[i]->setXLims(limits[j2][0], limits[j2][1]); - // } + // Each projection sets its limits using the limits of the two waveform dims it represents. + // Convert projection number to indices, and then set the limits using those indices + int j1, j2; + for (int i = 0; i < nProjAx; i++) + { + pAxes[i]->n2ProjIdx(pAxes[i]->getType(), &j1, &j2); + pAxes[i]->setRange(ranges[j1], ranges[j2]); + } } void SpikePlot::initLimits() @@ -540,51 +605,13 @@ void SpikePlot::clear() pAxes[i]->clear(); } -void SpikePlot::pan(int dim, bool up) -{ - - std::cout << "SpikePlot::pan() dim:" << dim << std::endl; - - int mean = (limits[dim][0] + limits[dim][1])/2; - int dLim = limits[dim][1] - mean; - - if (up) - mean = mean + dLim/20; - else - mean = mean - dLim/20; - - limits[dim][0] = mean-dLim; - limits[dim][1] = mean+dLim; - - setLimitsOnAxes(); -} - -void SpikePlot::zoom(int dim, bool in) -{ - std::cout << "SpikePlot::zoom()" << std::endl; - - int mean = (limits[dim][0] + limits[dim][1])/2; - int dLim = limits[dim][1] - mean; - - if (in) - dLim = dLim * .90; - else - dLim = dLim / .90; - - limits[dim][0] = mean-dLim; - limits[dim][1] = mean+dLim; - - setLimitsOnAxes(); -} - - // -------------------------------------------------- WaveAxes::WaveAxes(int channel) : GenericAxes(channel), drawGrid(true), - bufferSize(10), spikeIndex(0), thresholdLevel(0.5f), + bufferSize(10), spikeIndex(0), thresholdLevel(0.5f), range(250.0f), isOverThresholdSlider(false), isDraggingThresholdSlider(false) { @@ -603,17 +630,27 @@ WaveAxes::WaveAxes(int channel) : GenericAxes(channel), drawGrid(true), } } +void WaveAxes::setRange(float r) +{ + + //std::cout << "Setting range to " << r << std::endl; + + range = r; + + repaint(); +} + void WaveAxes::paint(Graphics& g) { g.setColour(Colours::black); - g.fillRect(5,5,getWidth()-10, getHeight()-10); + g.fillRect(0,0,getWidth(), getHeight()); int chan = 0; // draw the grid lines for the waveforms if (drawGrid) - drawWaveformGrid(s.threshold[chan], s.gain[chan], g); + drawWaveformGrid(g); // draw the threshold line and labels drawThresholdSlider(g); @@ -650,7 +687,7 @@ void WaveAxes::plotSpike(const SpikeObject& s, Graphics& g) float h = getHeight(); //compute the spatial width for each waveform sample - float dx = (getWidth()-10)/float(spikeBuffer[0].nSamples); + float dx = getWidth()/float(spikeBuffer[0].nSamples); // type corresponds to channel so we need to calculate the starting // sample based upon which channel is getting plotted @@ -658,16 +695,25 @@ void WaveAxes::plotSpike(const SpikeObject& s, Graphics& g) int dSamples = 1; - - float x = 5.0f; + float x = 0.0f; for (int i = 0; i < s.nSamples-1; i++) { //std::cout << s.data[sampIdx] << std::endl; - g.drawLine(x, - h/2 + (s.data[sampIdx]-32768)/100, - x+dx, - h/2 + (s.data[sampIdx+1]-32768)/100); + + if (*s.gain != 0) + { + float s1 = h/2 + float(s.data[sampIdx]-32768)/float(*s.gain)*1000.0f / range * h; + float s2 = h/2 + float(s.data[sampIdx+1]-32768)/float(*s.gain)*1000.0f / range * h; + + g.drawLine(x, + s1, + x+dx, + s2); + } + + + sampIdx += dSamples; x += dx; } @@ -680,89 +726,23 @@ void WaveAxes::drawThresholdSlider(Graphics& g) float h = getHeight()*thresholdLevel; g.setColour(thresholdColour); - g.drawLine(5.0f, h, getWidth()-5.0f, h); + g.drawLine(0, h, getWidth(), h); } -void WaveAxes::drawWaveformGrid(int threshold, int gain, Graphics& g) +void WaveAxes::drawWaveformGrid(Graphics& g) { float h = getHeight(); float w = getWidth(); - for (int i = 1; i < 10; i++) - { - g.setColour(Colours::darkgrey); - - g.drawLine(5.0,h/10*i,w-5.0f,h/10*i); + g.setColour(Colours::darkgrey); + for (float y = -range/2; y < range/2; y += 25.0f) + { + g.drawLine(0,h/2 + y/range*h, w, h/2+ y/range*h); } - - // double voltRange = ylims[1] - ylims[0]; - // double pixelRange = getHeight(); - // //This is a totally arbitrary value that seemed to lok the best for me - // int minPixelsPerTick = 25; - // int MAX_N_TICKS = 10; - - // int nTicks = pixelRange / minPixelsPerTick; - // while (nTicks > MAX_N_TICKS) - // { - // minPixelsPerTick += 5; - // nTicks = pixelRange / minPixelsPerTick; - // } - - // int voltPerTick = (voltRange / nTicks); - - // g.setColour(Colours::red); - // char cstr[200] = {0}; - // String str; - - // double tickVoltage = (double) threshold; - - // // If the limits are bad we don't want to hang the program trying to draw too many ticks - // // so count the number of ticks drawn and kill the routine after 100 draws - // int tickCount=0; - // while (tickVoltage < ylims[1] - voltPerTick*1.5) // Draw the ticks above the thold line - // { - // tickVoltage = (double) roundUp(tickVoltage + voltPerTick, 100); - - // g.drawLine(0, tickVoltage, s.nSamples, tickVoltage); - - // // glBegin(GL_LINE_STRIP); - // // glVertex2i(0, tickVoltage); - // // glVertex2i(s.nSamples, tickVoltage); - // // glEnd(); - - // makeLabel(tickVoltage, gain, true, cstr); - // str = String(cstr); - // g.setFont(font); - // g.drawText(str, 1, tickVoltage+voltPerTick/10, 100, 15, Justification::left, false); - - // if (tickCount++>100) - // return; - // } - - // tickVoltage = threshold; - // tickCount = 0; - - // while (tickVoltage > ylims[0] + voltPerTick) // draw the ticks below the thold line - // { - // tickVoltage = (double) roundUp(tickVoltage - voltPerTick, 100); - - // g.drawLine(0, tickVoltage, s.nSamples, tickVoltage); - - // // glBegin(GL_LINE_STRIP); - // // glVertex2i(0, tickVoltage); - // // glVertex2i(s.nSamples, tickVoltage); - // // glEnd(); - - // makeLabel(tickVoltage, gain, true, cstr); - // str = String(cstr); - // g.drawText(str, 1, tickVoltage+voltPerTick/10, 100, 15, Justification::left, false); - - // if (tickCount++>100) - // return; - // } + } void WaveAxes::updateSpikeData(const SpikeObject& s) @@ -774,15 +754,21 @@ void WaveAxes::updateSpikeData(const SpikeObject& s) SpikeObject newSpike = s; - spikeBuffer.set(spikeIndex, newSpike); - spikeIndex++; spikeIndex %= bufferSize; + spikeBuffer.set(spikeIndex, newSpike); + + + } void WaveAxes::clear() { + + spikeBuffer.clear(); + spikeIndex = 0; + for (int n = 0; n < bufferSize; n++) { SpikeObject so; @@ -865,7 +851,8 @@ void WaveAxes::mouseExit(const MouseEvent& event) // -------------------------------------------------- -ProjectionAxes::ProjectionAxes(int projectionNum) : GenericAxes(projectionNum), imageDim(500) +ProjectionAxes::ProjectionAxes(int projectionNum) : GenericAxes(projectionNum), imageDim(500), + rangeX(250), rangeY(250) { projectionImage = Image(Image::RGB, imageDim, imageDim, true); @@ -879,13 +866,24 @@ ProjectionAxes::ProjectionAxes(int projectionNum) : GenericAxes(projectionNum), } +void ProjectionAxes::setRange(float x, float y) +{ + rangeX = (int) x; + rangeY = (int) y; + + //std::cout << "Setting range to " << x << " " << y << std::endl; + + repaint(); +} + void ProjectionAxes::paint(Graphics& g) { //g.setColour(Colours::orange); //g.fillRect(5,5,getWidth()-5, getHeight()-5); + g.drawImage(projectionImage, - 5, 5, getWidth()-10, getHeight()-10, - 0, 250, 250, 250); + 0, 0, getWidth(), getHeight(), + 0, imageDim-rangeY, rangeX, rangeY); } void ProjectionAxes::updateSpikeData(const SpikeObject& s) @@ -899,19 +897,25 @@ void ProjectionAxes::updateSpikeData(const SpikeObject& s) calcWaveformPeakIdx(s, ampDim1, ampDim2, &idx1, &idx2); // add peaks to image - updateProjectionImage(s.data[idx1], s.data[idx2]); + + updateProjectionImage(s.data[idx1], s.data[idx2], *s.gain); } -void ProjectionAxes::updateProjectionImage(uint16_t x, uint16_t y) +void ProjectionAxes::updateProjectionImage(uint16_t x, uint16_t y, uint16_t gain) { Graphics g(projectionImage); - float xf = float(x-32768)*(float(imageDim)/32768.0); - float yf = float(imageDim) - float(y-32768)*(float(imageDim)/32768.0); + // h/2 + float(s.data[sampIdx]-32768)/float(*s.gain)*1000.0f / range * h; - g.setColour(Colours::white); - g.fillEllipse(xf,yf,2.0f,2.0f); + if (gain != 0) + { + float xf = float(x-32768)/float(gain)*1000.0f; // in microvolts + float yf = float(imageDim) - float(y-32768)/float(gain)*1000.0f; // in microvolts + + g.setColour(Colours::white); + g.fillEllipse(xf,yf,2.0f,2.0f); + } } diff --git a/Source/Processors/Visualization/SpikeDisplayCanvas.h b/Source/Processors/Visualization/SpikeDisplayCanvas.h index 8edb6ecf2dba2788406250814f54582eb58cb143..02ba77143ab868b2840866023d47934bbe1cdad6 100755 --- a/Source/Processors/Visualization/SpikeDisplayCanvas.h +++ b/Source/Processors/Visualization/SpikeDisplayCanvas.h @@ -66,7 +66,7 @@ class SpikePlot; */ -class SpikeDisplayCanvas : public Visualizer +class SpikeDisplayCanvas : public Visualizer, public Button::Listener { public: @@ -93,6 +93,8 @@ public: bool keyPressed(const KeyPress& key); + void buttonClicked(Button* button); + private: SpikeDisplayNode* processor; @@ -101,7 +103,7 @@ private: ScopedPointer<SpikeDisplay> spikeDisplay; ScopedPointer<Viewport> viewport; - + ScopedPointer<UtilityButton> clearButton; bool newSpike; SpikeObject spike; @@ -118,6 +120,7 @@ public: SpikeDisplay(SpikeDisplayCanvas*, Viewport*); ~SpikeDisplay(); + void removePlots(); void clear(); void addSpikePlot(int numChannels, int electrodeNum); @@ -160,7 +163,7 @@ private: */ -class SpikePlot : public Component +class SpikePlot : public Component, Button::Listener { public: SpikePlot(SpikeDisplayCanvas*, int elecNum, int plotType); @@ -186,12 +189,12 @@ public: void getBestDimensions(int*, int*); void clear(); - void zoom(int, bool); - void pan(int, bool); float minWidth; float aspectRatio; + void buttonClicked(Button* button); + private: @@ -205,6 +208,8 @@ private: OwnedArray<ProjectionAxes> pAxes; OwnedArray<WaveAxes> wAxes; + OwnedArray<UtilityButton> rangeButtons; + Array<float> ranges; void initLimits(); void setLimitsOnAxes(); @@ -288,6 +293,9 @@ public: void mouseDown(const MouseEvent& event); void mouseDrag(const MouseEvent& event); + void setRange(float); + float getRange() {return range;} + //MouseCursor getMouseCursor(); private: @@ -300,7 +308,7 @@ private: float thresholdLevel; - void drawWaveformGrid(int threshold, int gain, Graphics& g); + void drawWaveformGrid(Graphics& g); void drawThresholdSlider(Graphics& g); @@ -311,6 +319,8 @@ private: int spikeIndex; int bufferSize; + float range; + bool isOverThresholdSlider; bool isDraggingThresholdSlider; @@ -340,14 +350,15 @@ public: void clear(); -private: + void setRange(float, float); - void updateProjectionImage(uint16_t, uint16_t); + static void n2ProjIdx(int i, int* p1, int* p2); - void calcWaveformPeakIdx(const SpikeObject&, int, int, int*, int*); +private: + void updateProjectionImage(uint16_t, uint16_t, uint16_t); - void n2ProjIdx(int i, int* p1, int* p2); + void calcWaveformPeakIdx(const SpikeObject&, int, int, int*, int*); int ampDim1, ampDim2; @@ -358,6 +369,10 @@ private: int imageDim; + int rangeX; + int rangeY; + + }; diff --git a/Source/Processors/Visualization/SpikeObject.cpp b/Source/Processors/Visualization/SpikeObject.cpp index 7522d0b08182a45d7500010a0d4a5d7ab262c80f..c2665d123aca88f6505a8b9432228b22d30d8be2 100755 --- a/Source/Processors/Visualization/SpikeObject.cpp +++ b/Source/Processors/Visualization/SpikeObject.cpp @@ -61,14 +61,14 @@ int packSpike(SpikeObject* s, uint8_t* buffer, int bufferSize) memcpy(buffer+idx, &(s->threshold), s->nChannels * 2); idx += s->nChannels * 2; - - if (idx >= MAX_SPIKE_BUFFER_LEN) { - std::cout<<"Spike is larger than it should be. Size was:"<<idx<<" Max Size is:"<<MAX_SPIKE_BUFFER_LEN<<std::endl; + std::cout << "Spike is larger than it should be. Size was: " << idx + << " Max Size is: " << MAX_SPIKE_BUFFER_LEN << std::endl; } - // makeBufferValid(buffer, bufferSize); + + makeBufferValid(buffer, bufferSize); return idx; @@ -77,8 +77,8 @@ int packSpike(SpikeObject* s, uint8_t* buffer, int bufferSize) // Simple method for deserializing a string of bytes into a Spike object bool unpackSpike(SpikeObject* s, const uint8_t* buffer, int bufferSize) { - // if (!isBufferValid(buffer, bufferSize)) - // return false; + // if (!isBufferValid(buffer, bufferSize)) + // return false; int idx = 0; @@ -115,7 +115,7 @@ bool unpackSpike(SpikeObject* s, const uint8_t* buffer, int bufferSize) } // Checks the validity of the buffer, this should be run before unpacking and after packing the buffer -bool isBufferValid(uint8_t* buffer, int bufferSize) +bool isBufferValid(const uint8_t* buffer, int bufferSize) { if (! CHECK_BUFFER_VALIDITY) @@ -126,14 +126,14 @@ bool isBufferValid(uint8_t* buffer, int bufferSize) int idx; - for (idx = 0; idx < bufferSize-2; idx += 2) + for (idx = 0; idx < bufferSize - 2; idx += 2) { - memcpy(buffer + idx, &value, 2); + memcpy(&value, buffer + idx, 2); runningSum += value; } uint16_t integrityCheck = 0; - memcpy(buffer + idx, &integrityCheck, 2); + memcpy(&integrityCheck, buffer + idx + 2, 2); std::cout << integrityCheck<< " == " << runningSum <<std::endl; @@ -150,13 +150,13 @@ void makeBufferValid(uint8_t* buffer, int bufferSize) int idx; - for (idx = 0; idx < bufferSize-2; idx += 2) + for (idx = 0; idx < bufferSize - 2; idx += 2) { - memcpy(buffer + idx, &value, 2); + memcpy(&value, buffer + idx, 2); runningSum += value; } - memcpy(&runningSum, buffer + idx, 2); + memcpy(buffer + idx + 2, &runningSum, 2); } diff --git a/Source/Processors/Visualization/SpikeObject.h b/Source/Processors/Visualization/SpikeObject.h index b32f3aaa8f04b3abf1b9e1b65efeb4683cb9963a..1bd73f2ec990f3f2293f7808e7163191f97ff46e 100755 --- a/Source/Processors/Visualization/SpikeObject.h +++ b/Source/Processors/Visualization/SpikeObject.h @@ -74,7 +74,7 @@ int packSpike(SpikeObject* s, uint8_t* buffer, int bufferLength); bool unpackSpike(SpikeObject* s, const uint8_t* buffer, int bufferLength); /** Checks the validity of the buffer, this should be run before unpacking the buffer */ -bool isBufferValid(uint8_t* buffer, int bufferLength); +bool isBufferValid(const uint8_t* buffer, int bufferLength); /** Computes the validity value for the buffer, this should be called after packing the buffer */ void makeBufferValid(uint8_t* buffer, int bufferLength); diff --git a/Source/Processors/Visualization/Visualizer.h b/Source/Processors/Visualization/Visualizer.h index bcf3b1d7f9e15cb4ac1cda2babee02d27ceebbb1..3825800a64294b488ba5f5d289bc5bf99ad8130d 100755 --- a/Source/Processors/Visualization/Visualizer.h +++ b/Source/Processors/Visualization/Visualizer.h @@ -69,7 +69,7 @@ public: /** Starts the timer callbacks. */ void startCallbacks() { - startTimer(50); + startTimer(20); } /** Stops the timer callbacks. */ diff --git a/Source/UI/EditorViewport.cpp b/Source/UI/EditorViewport.cpp index 747f4df9d0cb1b092e8eae7341aae288a3469429..b36413d395ba3900aab94dab21eb61a647b105c0 100755 --- a/Source/UI/EditorViewport.cpp +++ b/Source/UI/EditorViewport.cpp @@ -1413,7 +1413,7 @@ void EditorViewport::setParametersByXML(GenericProcessor* targetProcessor, XmlEl forEachXmlChildElementWithTagName(*processorXML, channelXML, "CHANNEL"){ currentChannel=channelXML->getIntAttribute("name"); - std::cout <<"currentChannel:"<< currentChannel << std::endl; + // std::cout <<"currentChannel:"<< currentChannel << std::endl; // Sets channel to change parameter on targetProcessor->setCurrentChannel(currentChannel-1); diff --git a/Source/UI/UIComponent.cpp b/Source/UI/UIComponent.cpp index 34747cb8248b924195079093dcd8ad199231c08f..7d94fb6b2ea432e14860752e4831db6a34401eb8 100755 --- a/Source/UI/UIComponent.cpp +++ b/Source/UI/UIComponent.cpp @@ -92,7 +92,7 @@ UIComponent::UIComponent(MainWindow* mainWindow_, ProcessorGraph* pgraph, AudioC mainWindow->setMenuBar(this); #endif - // getEditorViewport()->loadState(File("/home/jsiegle/Programming/GUI/Builds/Linux/build/spike_display.xml")); + // getEditorViewport()->loadState(File("/home/jsiegle/Programming/GUI/Builds/Linux/build/rhythm_config.xml")); }