diff --git a/Source/Processors/ChannelMappingNode.cpp b/Source/Processors/ChannelMappingNode.cpp index 05e691e6522f18f673b43dd9b453aa680644bd6b..536d47bdede43795d95a3933dd9c401b06ca8fca 100644 --- a/Source/Processors/ChannelMappingNode.cpp +++ b/Source/Processors/ChannelMappingNode.cpp @@ -32,6 +32,13 @@ ChannelMappingNode::ChannelMappingNode() { referenceArray.resize(1024); // make room for 1024 channels channelArray.resize(1024); + + for (int i = 0; i < referenceArray.size(); i++) + { + referenceArray.set(i, -1); + channelArray.set(i, i); + } + } ChannelMappingNode::~ChannelMappingNode() @@ -52,7 +59,9 @@ AudioProcessorEditor* ChannelMappingNode::createEditor() void ChannelMappingNode::updateSettings() { - channelBuffer.setSize(getNumInputs(), 10000); + if (getNumInputs() > 0) + channelBuffer.setSize(getNumInputs(), 10000); + } @@ -74,10 +83,8 @@ void ChannelMappingNode::process(AudioSampleBuffer& buffer, int& nSamples) { - // copy everything into the channel buffer - channelBuffer.setDataToReferTo(buffer.getArrayOfChannels(), - buffer.getNumChannels(), - buffer.getNumSamples()); + // use copy constructor to set the data to refer to + channelBuffer = buffer; // copy it back into the buffer according to the channel mapping buffer.clear(); @@ -90,7 +97,7 @@ void ChannelMappingNode::process(AudioSampleBuffer& buffer, channelArray[i], // sourceChannel 0, // sourceStartSample nSamples, // numSamples - 1.0f // gain to apply to source (positive) + 1.0f // gain to apply to source (positive for original signal) ); } diff --git a/Source/Processors/DataThreads/RHD2000Thread.cpp b/Source/Processors/DataThreads/RHD2000Thread.cpp index 60231bd0b6796759c5fdeb5cd468c4830a20ec30..66cbc03efba3cb614591831ea6863a23f6e1110c 100644 --- a/Source/Processors/DataThreads/RHD2000Thread.cpp +++ b/Source/Processors/DataThreads/RHD2000Thread.cpp @@ -434,8 +434,9 @@ void RHD2000Thread::updateChannelNames() chNum++; String chName = "AUX"; - // chName += (j+1); + chName += (j+1); + // this is causing a seg fault for some reason: // sn->channels[chNum]->setName(chName); } } @@ -449,7 +450,7 @@ void RHD2000Thread::updateChannelNames() chNum++; String chName = "ADC"; - // chName += (j+1); + chName += (j+1); // sn->channels[chNum]->setName(chName); } diff --git a/Source/Processors/Editors/ChannelMappingEditor.cpp b/Source/Processors/Editors/ChannelMappingEditor.cpp index 6a2d946419be5598b784db0bebae227f11327720..4f7faf24396ee598c6d1427456c8e56e1c8f1e5d 100644 --- a/Source/Processors/Editors/ChannelMappingEditor.cpp +++ b/Source/Processors/Editors/ChannelMappingEditor.cpp @@ -34,18 +34,17 @@ ChannelMappingEditor::ChannelMappingEditor(GenericProcessor* parentNode, bool us { desiredWidth = 340; - ElectrodeEditorButton* e1 = new ElectrodeEditorButton("MAPPING",Font("Small Text",14,Font::plain)); - e1->addListener(this); - addAndMakeVisible(e1); - e1->setBounds(15,110,80,10); - e1->setToggleState(true, false); - electrodeEditorButtons.add(e1); - - ElectrodeEditorButton* e2 = new ElectrodeEditorButton("REF",Font("Small Text",14,Font::plain)); - e2->addListener(this); - addAndMakeVisible(e2); - e2->setBounds(100,110,50,10); - electrodeEditorButtons.add(e2); + mappingButton = new ElectrodeEditorButton("MAPPING",Font("Small Text",14,Font::plain)); + mappingButton->addListener(this); + addAndMakeVisible(mappingButton); + mappingButton->setBounds(15,110,80,10); + mappingButton->setToggleState(true, false); + + referenceButton = new ElectrodeEditorButton("REF",Font("Small Text",14,Font::plain)); + referenceButton->addListener(this); + addAndMakeVisible(referenceButton); + referenceButton->setBounds(100,110,50,10); + referenceButton->setToggleState(false, false); channelSelector->setRadioStatus(true); @@ -104,7 +103,7 @@ void ChannelMappingEditor::createElectrodeButtons(int numNeeded) channelArray.add(i+1); - if (column%16 == 0) + if (column % 16 == 0) { column = 0; row++; @@ -119,28 +118,34 @@ void ChannelMappingEditor::createElectrodeButtons(int numNeeded) void ChannelMappingEditor::buttonEvent(Button* button) { - if (button == electrodeEditorButtons[0]) // mapping + if (button == mappingButton) // mapping { + std::cout << "Mapping button clicked." << std::endl; - electrodeEditorButtons[1]->setToggleState(false, false); + referenceButton->setToggleState(false, false); + mappingButton->setToggleState(true, false); for (int i = 0; i < electrodeButtons.size(); i++) { electrodeButtons[i]->setRadioGroupId(999); electrodeButtons[i]->setChannelNum(channelArray[i]); - electrodeButtons[i]->repaint(); + electrodeButtons[i]->repaint(); } } - else if (button == electrodeEditorButtons[1]) // reference + else if (button == referenceButton) // reference { - electrodeEditorButtons[0]->setToggleState(false, false); + + std::cout << "Reference button clicked." << std::endl; + + mappingButton->setToggleState(false, false); + referenceButton->setToggleState(true, false); for (int i = 0; i < electrodeButtons.size(); i++) { electrodeButtons[i]->setRadioGroupId(0); electrodeButtons[i]->setChannelNum(referenceArray[i]); - electrodeButtons[i]->repaint(); + electrodeButtons[i]->repaint(); } @@ -161,6 +166,9 @@ void ChannelMappingEditor::buttonEvent(Button* button) } + std::cout << "Reference button state: " << referenceButton->getToggleState() << std::endl; + std::cout << "Mapping button state: " << mappingButton->getToggleState() << std::endl; + } void ChannelMappingEditor::channelChanged(int chan) @@ -174,7 +182,7 @@ void ChannelMappingEditor::channelChanged(int chan) getProcessor()->setCurrentChannel(i); - if (electrodeEditorButtons[1]->getToggleState()) // reference + if (referenceButton->getToggleState()) // reference { referenceArray.set(i,chan); diff --git a/Source/Processors/Editors/ChannelMappingEditor.h b/Source/Processors/Editors/ChannelMappingEditor.h index 0ddde7119fa9aea18d4446c3db8ae0f5964f6b0a..46b7a499d8f9e453cccf8f63cafbab403e3c538a 100644 --- a/Source/Processors/Editors/ChannelMappingEditor.h +++ b/Source/Processors/Editors/ChannelMappingEditor.h @@ -60,7 +60,8 @@ public: private: OwnedArray<ElectrodeButton> electrodeButtons; - OwnedArray<ElectrodeEditorButton> electrodeEditorButtons; + ScopedPointer<ElectrodeEditorButton> referenceButton; + ScopedPointer<ElectrodeEditorButton> mappingButton; Array<int> channelArray; Array<int> referenceArray; diff --git a/Source/Processors/Editors/SpikeDetectorEditor.cpp b/Source/Processors/Editors/SpikeDetectorEditor.cpp index 20bfdee9d222af90d6e318903483ca9535adf8a0..92ba610036d18777affc392b3acb701230568e03 100755 --- a/Source/Processors/Editors/SpikeDetectorEditor.cpp +++ b/Source/Processors/Editors/SpikeDetectorEditor.cpp @@ -443,6 +443,8 @@ void SpikeDetectorEditor::labelTextChanged(Label* label) electrodeTypes->setText(currentText += "s"); } + getEditorViewport()->makeEditorVisible(this, false, true); + } void SpikeDetectorEditor::comboBoxChanged(ComboBox* comboBox) diff --git a/Source/Processors/PhaseDetector.cpp b/Source/Processors/PhaseDetector.cpp index 0cf973e0be04ffc703c9584892c5a8c6327550c7..2a2e149b0a81119ac616e98691aafd4f36df622d 100644 --- a/Source/Processors/PhaseDetector.cpp +++ b/Source/Processors/PhaseDetector.cpp @@ -149,9 +149,13 @@ void PhaseDetector::process(AudioSampleBuffer& buffer, numPeakIntervals++; + + //std::cout << "GOT EVENT." << std::endl; + // entering falling phase (just reached peak or trough) - if (true) - addEvent(events, TTL, i, 1, 3); + //if (true) + addEvent(events, TTL, i, 1, 3); + peakIntervals[numPeakIntervals % NUM_INTERVALS] = nSamplesSinceLastPeak; @@ -168,10 +172,10 @@ void PhaseDetector::process(AudioSampleBuffer& buffer, // either rising or falling nSamplesSinceLastPeak++; - // if (nSamplesSinceLastPeak == 100) - // { - // addEvent(events, TTL, i, 0, 3); - // } + if (nSamplesSinceLastPeak == 500) + { + addEvent(events, TTL, i, 0, 3); + } } diff --git a/Source/Processors/ProcessorGraph.cpp b/Source/Processors/ProcessorGraph.cpp index 2c0a3707d99c7c6f97b1566a9214e20ea624bf3d..fa997e55df66f1858981c0896e679b295974b26a 100644 --- a/Source/Processors/ProcessorGraph.cpp +++ b/Source/Processors/ProcessorGraph.cpp @@ -34,6 +34,7 @@ #include "RecordNode.h" #include "ResamplingNode.h" #include "ReferenceNode.h" +#include "ChannelMappingNode.h" #include "AudioResamplingNode.h" #include "SignalGenerator.h" #include "SourceNode.h" @@ -503,6 +504,11 @@ GenericProcessor* ProcessorGraph::createProcessorFromDescription(String& descrip std::cout << "Creating a new digital reference." << std::endl; processor = new ReferenceNode(); } + else if (subProcessorType.equalsIgnoreCase("Channel Map")) + { + std::cout << "Creating a new channel mapping node." << std::endl; + processor = new ChannelMappingNode(); + } sendActionMessage("New filter node created."); diff --git a/Source/Processors/RecordNode.h b/Source/Processors/RecordNode.h index baadf10e0cb4acb6faae76b8355c7f86024c6409..ac44861133170b90cf7806f405a1319af4c039ce 100755 --- a/Source/Processors/RecordNode.h +++ b/Source/Processors/RecordNode.h @@ -102,14 +102,22 @@ public: */ void createNewDirectory(); + File getDataDirectory() {return rootFolder;} + /** Signals when to create a new data directory when recording starts.*/ bool newDirectoryNeeded; + bool isRecording; + + /** Generate a Matlab-compatible datestring */ + String generateDateString(); + + private: /** Keep the RecordNode informed of acquisition and record states. */ - bool isRecording, isProcessing, signalFilesShouldClose; + bool isProcessing, signalFilesShouldClose; /** User-selectable directory for saving data files. Currently defaults to the user's home directory. @@ -165,9 +173,7 @@ private: /** Generates a default directory name, based on the current date and time */ String generateDirectoryName(); - /** Generate a Matlab-compatible datestring */ - String generateDateString(); - + /** Generate filename for a given channel */ void updateFileName(Channel* ch); diff --git a/Source/Processors/SpikeDetector.cpp b/Source/Processors/SpikeDetector.cpp index 1a5025421829e486c36f58e726e6d71872a5ec39..ce796f79b8d2040b7333b72d0ab270cc33666faa 100755 --- a/Source/Processors/SpikeDetector.cpp +++ b/Source/Processors/SpikeDetector.cpp @@ -165,7 +165,7 @@ bool SpikeDetector::addElectrode(int nChans) float SpikeDetector::getDefaultThreshold() { - return 75.0f; + return 50.0f; } StringArray SpikeDetector::getElectrodeNames() @@ -333,7 +333,9 @@ void SpikeDetector::addSpikeEvent(SpikeObject* s, MidiBuffer& eventBuffer, int p // std::cout << "Adding spike event for index " << peakIndex << std::endl; - int numBytes = packSpike(s, spikeBuffer, 256); + s->eventType = SPIKE_EVENT_CODE; + + int numBytes = packSpike(s, spikeBuffer, MAX_SPIKE_BUFFER_LEN); eventBuffer.addEvent(spikeBuffer, numBytes, peakIndex); @@ -347,10 +349,7 @@ void SpikeDetector::addWaveformToSpikeObject(SpikeObject* s, int spikeLength = electrodes[electrodeNumber]->prePeakSamples + + electrodes[electrodeNumber]->postPeakSamples; - //uint8 dataSize = spikeLength*2; - - // uint8 data[dataSize]; - // uint8* dataptr = data; + s->timestamp = timestamp + peakIndex; s->nSamples = spikeLength; @@ -397,6 +396,19 @@ void SpikeDetector::addWaveformToSpikeObject(SpikeObject* s, sampleIndex -= spikeLength; // reset sample index +} + +void SpikeDetector::handleEvent(int eventType, MidiMessage& event, int sampleNum) +{ + + if (eventType == TIMESTAMP) + { + const uint8* dataptr = event.getRawData(); + + memcpy(×tamp, dataptr + 4, 8); // remember to skip first four bytes + } + + } void SpikeDetector::process(AudioSampleBuffer& buffer, @@ -408,6 +420,8 @@ void SpikeDetector::process(AudioSampleBuffer& buffer, Electrode* electrode; dataBuffer = buffer; + checkForEvents(events); + //std::cout << dataBuffer.getMagnitude(0,nSamples) << std::endl; for (int i = 0; i < electrodes.size(); i++) diff --git a/Source/Processors/SpikeDetector.h b/Source/Processors/SpikeDetector.h index 11e84960eb82f975733dd1b597f696f22b8b70f3..8c820b985ce0c8cdb0b7ef1092174cccea2adfa9 100755 --- a/Source/Processors/SpikeDetector.h +++ b/Source/Processors/SpikeDetector.h @@ -167,6 +167,7 @@ private: }; uint8_t* spikeBuffer;///[256]; + uint64_t timestamp; Array<Electrode*> electrodes; @@ -175,6 +176,8 @@ private: // int& currentChannel, // MidiBuffer& eventBuffer); + void handleEvent(int eventType, MidiMessage& event, int sampleNum); + void addSpikeEvent(SpikeObject* s, MidiBuffer& eventBuffer, int peakIndex); void addWaveformToSpikeObject(SpikeObject* s, int& peakIndex, diff --git a/Source/Processors/SpikeDisplayNode.cpp b/Source/Processors/SpikeDisplayNode.cpp index 161aed18f00c95c9393e388042bb8a21f9a4ac9e..ce983edeb85ae85672f1c696b2821bc06da9e97e 100755 --- a/Source/Processors/SpikeDisplayNode.cpp +++ b/Source/Processors/SpikeDisplayNode.cpp @@ -101,6 +101,28 @@ int SpikeDisplayNode::getNumberOfChannelsForElectrode(int elec) return 0; } +String SpikeDisplayNode::getNameForElectrode(int elec) +{ + + int electrodeIndex = -1; + + for (int i = 0; i < eventChannels.size(); i++) + { + if (eventChannels[i]->eventType < 999) + { + electrodeIndex++; + + if (electrodeIndex == elec) + { + std::cout << "Electrode " << elec << " has " << eventChannels[i]->eventType << " channels" << std::endl; + return eventChannels[i]->name; + } + } + } + + return " "; +} + int SpikeDisplayNode::getNumElectrodes() { int nElectrodes = 0; diff --git a/Source/Processors/SpikeDisplayNode.h b/Source/Processors/SpikeDisplayNode.h index 66dcb85b44be709aece4b795320145063c9c961e..9de5055d33e4d141d33bb3d9abc418f807da3c88 100755 --- a/Source/Processors/SpikeDisplayNode.h +++ b/Source/Processors/SpikeDisplayNode.h @@ -71,7 +71,7 @@ public: return eventBuffer; } - + String getNameForElectrode(int i); int getNumberOfChannelsForElectrode(int i); int getNumElectrodes(); diff --git a/Source/Processors/Visualization/LfpDisplayCanvas.cpp b/Source/Processors/Visualization/LfpDisplayCanvas.cpp index 1235c8e1316e0a7fef9710368ee9c38b1b6d8b2d..392240da2fd496ab04104d1aa823c0f8092bafb0 100755 --- a/Source/Processors/Visualization/LfpDisplayCanvas.cpp +++ b/Source/Processors/Visualization/LfpDisplayCanvas.cpp @@ -107,6 +107,21 @@ LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : lfpDisplay->setNumChannels(nChans); lfpDisplay->setRange(1000.0f); + // add event display-specific controls (currently just an enable/disable button) + for (int i = 0; i < 8; i++) + { + + + eventDisplayInterface* eventOptions = new eventDisplayInterface(lfpDisplay, this, i); + eventDisplayInterfaces.add(eventOptions); + addAndMakeVisible(eventOptions); + eventOptions->setBounds(500+(floor(i/2)*20), getHeight()-20-(i%2)*20, 40, 20); + + lfpDisplay->setEventDisplayState(i,true); + + } + + } LfpDisplayCanvas::~LfpDisplayCanvas() @@ -127,6 +142,13 @@ void LfpDisplayCanvas::resized() timebaseSelection->setBounds(175,getHeight()-30,100,25); spreadSelection->setBounds(345,getHeight()-30,100,25); + for (int i = 0; i < 8; i++) + { + eventDisplayInterfaces[i]->setBounds(500+(floor(i/2)*20), getHeight()-40+(i%2)*20, 40, 20); // arrange event channel buttons in two rows + eventDisplayInterfaces[i]->repaint(); + } + + // std::cout << "Canvas thinks LfpDisplay should be this high: " // << lfpDisplay->getTotalHeight() << std::endl; @@ -305,7 +327,7 @@ void LfpDisplayCanvas::updateScreenBuffer() screenBuffer->clear(screenBufferIndex, 1); - for (int channel = 0; channel < nChans; channel++) + for (int channel = 0; channel <= nChans; channel++) // pull one extra channel for event display { screenBuffer->addFrom(channel, // destChannel @@ -351,6 +373,11 @@ float LfpDisplayCanvas::getXCoord(int chan, int samp) return samp; } +int LfpDisplayCanvas::getNumChannels() +{ + return nChans; +} + float LfpDisplayCanvas::getYCoord(int chan, int samp) { return *screenBuffer->getSampleData(chan, samp); @@ -395,6 +422,10 @@ void LfpDisplayCanvas::paint(Graphics& g) g.drawText("Timebase (s)",175,getHeight()-55,300,20,Justification::left, false); g.drawText("Spread (px)",345,getHeight()-55,300,20,Justification::left, false); + g.drawText("Event display",500,getHeight()-55,300,20,Justification::left, false); + + + } void LfpDisplayCanvas::refresh() @@ -535,6 +566,12 @@ LfpDisplay::~LfpDisplay() deleteAllChildren(); } + +int LfpDisplay::getNumChannels() +{ + return numChans; +} + void LfpDisplay::setNumChannels(int numChannels) { numChans = numChannels; @@ -551,7 +588,7 @@ void LfpDisplay::setNumChannels(int numChannels) //std::cout << "Adding new channel display." << std::endl; - LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, i); + LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, this, i); lfpChan->setColour(channelColours[i % channelColours.size()]); lfpChan->setRange(range); @@ -561,7 +598,7 @@ void LfpDisplay::setNumChannels(int numChannels) channels.add(lfpChan); - LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, i); + LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, this, i); lfpInfo->setColour(channelColours[i % channelColours.size()]); lfpInfo->setRange(range); @@ -762,10 +799,24 @@ void LfpDisplay::mouseDown(const MouseEvent& event) } + +bool LfpDisplay::setEventDisplayState(int ch, bool state) +{ + eventDisplayEnabled[ch] = state; + return eventDisplayEnabled[ch]; +} + + +bool LfpDisplay::getEventDisplayState(int ch) +{ + return eventDisplayEnabled[ch]; +} + + // ------------------------------------------------------------------ -LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, int channelNumber) : - canvas(c), isSelected(false), chan(channelNumber), channelHeight(40), channelOverlap(300), range(1000.0f) +LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, int channelNumber) : + canvas(c), display(d), isSelected(false), chan(channelNumber), channelHeight(40), channelOverlap(300), range(1000.0f) { @@ -793,6 +844,9 @@ void LfpChannelDisplay::paint(Graphics& g) g.setColour(Colours::yellow); // draw most recent drawn sample position g.drawLine(canvas->screenBufferIndex+1, 0, canvas->screenBufferIndex+1, getHeight()); + + + //g.setColour(Colours::red); // draw oldest drawn sample position //g.drawLine(canvas->lastScreenBufferIndex, 0, canvas->lastScreenBufferIndex, getHeight()-channelOverlap); @@ -819,7 +873,6 @@ void LfpChannelDisplay::paint(Graphics& g) int stepSize = 1; 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) // 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 @@ -839,6 +892,24 @@ void LfpChannelDisplay::paint(Graphics& g) for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion { + // draw event markers + int rawEventState = canvas->getYCoord(canvas->getNumChannels(), i);// get last channel+1 in buffer (represents events) + for (int ev_ch = 0; ev_ch < 8 ; ev_ch++) // for all event channels + { + if (display->getEventDisplayState(ev_ch)){ // check if plotting for this channel is enabled + if ( rawEventState & (1 << ev_ch)){ // events are representet by a bit code, so we have to extract the individual bits with a mask + g.setColour(display->channelColours[ev_ch*2]); // get color from lfp color scheme + g.setOpacity(0.25f); + g.drawLine(i, center-channelHeight/2 , i, center+channelHeight/2); + } + } + } + + //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl; + + g.setColour(lineColour); + g.setOpacity(1); + // drawLine makes for ok anti-aliased plots, but is pretty slow g.drawLine(i, (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2, @@ -889,6 +960,10 @@ void LfpChannelDisplay::paint(Graphics& g) } + + + + void LfpChannelDisplay::setRange(float r) { range = r; @@ -950,8 +1025,8 @@ void LfpChannelDisplay::setName(String name_) // ------------------------------- -LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, int ch) - : LfpChannelDisplay(canvas_, ch) +LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, int ch) + : LfpChannelDisplay(canvas_, display_, ch) { } @@ -960,7 +1035,6 @@ void LfpChannelDisplayInfo::paint(Graphics& g) { - int center = getHeight()/2; g.setColour(lineColour); @@ -972,4 +1046,67 @@ void LfpChannelDisplayInfo::paint(Graphics& g) } - \ No newline at end of file + + +// Event display Options -------------------------------------------------------------------- + +eventDisplayInterface::eventDisplayInterface(LfpDisplay* display_, LfpDisplayCanvas* canvas_, int chNum): + isEnabled(true), display(display_), canvas(canvas_) +{ + + channelNumber=chNum; + + chButton = new UtilityButton(String(channelNumber+1), Font("Small Text", 13, Font::plain)); + chButton->setRadius(5.0f); + chButton->setBounds(4,4,14,14); + chButton->setEnabledState(true); + chButton->setCorners(true, false, true, false); + //chButton.color = display->channelColours[channelNumber*2]; + chButton->addListener(this); + addAndMakeVisible(chButton); + + + checkEnabledState(); + +} + +eventDisplayInterface::~eventDisplayInterface() +{ + +} + +void eventDisplayInterface::checkEnabledState() +{ + isEnabled = display->getEventDisplayState(channelNumber); + + //repaint(); +} + +void eventDisplayInterface::buttonClicked(Button* button) +{ + checkEnabledState(); + if (isEnabled){ + display->setEventDisplayState(channelNumber, false); + } else { + display->setEventDisplayState(channelNumber, true); + } + + repaint(); + +} + + +void eventDisplayInterface::paint(Graphics& g) +{ + + checkEnabledState(); + + if (isEnabled){ + g.setColour(display->channelColours[channelNumber*2]); + g.fillRoundedRectangle(2,2,18,18,5.0f); + } + + + //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false); + +} diff --git a/Source/Processors/Visualization/LfpDisplayCanvas.h b/Source/Processors/Visualization/LfpDisplayCanvas.h index 298473735b79c8210fe9536b1017e5c5e3eaa40c..59e538689a0563fefde393e095fbbb31f0c9e978 100755 --- a/Source/Processors/Visualization/LfpDisplayCanvas.h +++ b/Source/Processors/Visualization/LfpDisplayCanvas.h @@ -33,6 +33,7 @@ class LfpTimescale; class LfpDisplay; class LfpChannelDisplay; class LfpChannelDisplayInfo; +class eventDisplayInterface; /** @@ -68,6 +69,8 @@ public: int getChannelHeight(); + int getNumChannels(); + float getXCoord(int chan, int samp); float getYCoord(int chan, int samp); @@ -117,6 +120,9 @@ private: StringArray timebases; StringArray spreads; // option for vertical spacing between channels + OwnedArray<eventDisplayInterface> eventDisplayInterfaces; + + void refreshScreenBuffer(); void updateScreenBuffer(); @@ -161,6 +167,7 @@ public: ~LfpDisplay(); void setNumChannels(int numChannels); + int getNumChannels(); int getTotalHeight(); @@ -180,9 +187,17 @@ public: void setChannelHeight(int r); int getChannelHeight(); + + bool setEventDisplayState(int ch, bool state); + bool getEventDisplayState(int ch); + + + Array<Colour> channelColours; + Array<LfpChannelDisplay*> channels; Array<LfpChannelDisplayInfo*> channelInfo; + private: int numChans; @@ -191,7 +206,9 @@ private: LfpDisplayCanvas* canvas; Viewport* viewport; - Array<Colour> channelColours; + bool eventDisplayEnabled[8]; + + float range; @@ -200,7 +217,7 @@ private: class LfpChannelDisplay : public Component { public: - LfpChannelDisplay(LfpDisplayCanvas*, int channelNumber); + LfpChannelDisplay(LfpDisplayCanvas*, LfpDisplay*, int channelNumber); ~LfpChannelDisplay(); void paint(Graphics& g); @@ -226,6 +243,7 @@ public: protected: LfpDisplayCanvas* canvas; + LfpDisplay* display; bool isSelected; @@ -248,10 +266,37 @@ protected: class LfpChannelDisplayInfo : public LfpChannelDisplay { public: - LfpChannelDisplayInfo(LfpDisplayCanvas*, int channelNumber); + LfpChannelDisplayInfo(LfpDisplayCanvas*, LfpDisplay*, int channelNumber); void paint(Graphics& g); }; +class eventDisplayInterface : public Component, + public Button::Listener +{ +public: + eventDisplayInterface(LfpDisplay*, LfpDisplayCanvas*, int chNum); + ~eventDisplayInterface(); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + void checkEnabledState(); + +private: + + int channelNumber; + + bool isEnabled; + + LfpDisplay* display; + LfpDisplayCanvas* canvas; + + ScopedPointer<UtilityButton> chButton; + +}; + + #endif // __LFPDISPLAYCANVAS_H_B711873A__ diff --git a/Source/Processors/Visualization/SpikeDisplayCanvas.cpp b/Source/Processors/Visualization/SpikeDisplayCanvas.cpp index fe16ddafc0a1c36265b1b66d4345aad9fbdd701a..5d7ab94589b33c895dfcdd93bf3abf15ff381eb1 100755 --- a/Source/Processors/Visualization/SpikeDisplayCanvas.cpp +++ b/Source/Processors/Visualization/SpikeDisplayCanvas.cpp @@ -20,8 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ - + #include "SpikeDisplayCanvas.h" +#include "../RecordNode.h" SpikeDisplayCanvas::SpikeDisplayCanvas(SpikeDisplayNode* n) : processor(n), newSpike(false) @@ -79,7 +80,8 @@ void SpikeDisplayCanvas::update() for (int i = 0; i < nPlots; i++) { - spikeDisplay->addSpikePlot(processor->getNumberOfChannelsForElectrode(i), i); + spikeDisplay->addSpikePlot(processor->getNumberOfChannelsForElectrode(i), i, + processor->getNameForElectrode(i)); } spikeDisplay->resized(); @@ -117,6 +119,11 @@ void SpikeDisplayCanvas::refresh() repaint(); } +RecordNode* SpikeDisplayCanvas::getRecordNode() +{ + return processor->getProcessorGraph()->getRecordNode(); +} + void SpikeDisplayCanvas::processSpikeEvents() { @@ -214,12 +221,12 @@ void SpikeDisplay::removePlots() } -void SpikeDisplay::addSpikePlot(int numChannels, int electrodeNum) +void SpikeDisplay::addSpikePlot(int numChannels, int electrodeNum, String name_) { std::cout << "Adding new spike plot." << std::endl; - SpikePlot* spikePlot = new SpikePlot(canvas, electrodeNum, 1000 + numChannels); + SpikePlot* spikePlot = new SpikePlot(canvas, electrodeNum, 1000 + numChannels, name_); spikePlots.add(spikePlot); addAndMakeVisible(spikePlot); } @@ -348,12 +355,14 @@ void SpikeDisplay::plotSpike(const SpikeObject& spike, int electrodeNum) // ---------------------------------------------------------------- -SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p) : +SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p, String name_) : canvas(sdc), isSelected(false), electrodeNumber(elecNum), plotType(p), - limitsChanged(true) + limitsChanged(true), name(name_), isRecording(false) { + recordNode = sdc->getRecordNode(); + font = Font("Default", 15, Font::plain); switch (p) @@ -407,6 +416,9 @@ SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p) : rangeButtons.add(rangeButton); } + spikeBuffer = new uint8_t[MAX_SPIKE_BUFFER_LEN]; // MAX_SPIKE_BUFFER_LEN defined in SpikeObject.h + + } SpikePlot::~SpikePlot() @@ -422,7 +434,7 @@ void SpikePlot::paint(Graphics& g) g.setFont(font); - g.drawText(String(electrodeNumber+1),10,0,50,20,Justification::left,false); + g.drawText(name,10,0,200,20,Justification::left,false); } @@ -430,13 +442,145 @@ void SpikePlot::processSpikeObject(const SpikeObject& s) { //std::cout<<"ElectrodePlot::processSpikeObject()"<<std::endl; + // first, check if it's above threshold + bool aboveThreshold = false; + for (int i = 0; i < nWaveAx; i++) - wAxes[i]->updateSpikeData(s); + { + aboveThreshold = aboveThreshold | wAxes[i]->checkThreshold(s); + } + + if (aboveThreshold) + { + for (int i = 0; i < nWaveAx; i++) + wAxes[i]->updateSpikeData(s); + + for (int i = 0; i < nProjAx; i++) + pAxes[i]->updateSpikeData(s); + } + + + // then record it! + if (recordNode->isRecording) + { + if (!isRecording) + { + // open file + openFile(); + + isRecording = true; + //std::cout << "Start recording spikes." << std::endl; + + } + + if (aboveThreshold) + { + // write spike to disk + writeSpike(s); + } + + } else { + + if (isRecording) + { + // close file + closeFile(); + + isRecording = false; + //std::cout << "Stop recording spikes." << std::endl; + } + } + +} + +void SpikePlot::openFile() +{ + dataDirectory = recordNode->getDataDirectory(); + + filename = dataDirectory.getFullPathName(); + filename += File::separator; + filename += name.removeCharacters(" "); + filename += ".spikes"; + + std::cout << "OPENING FILE: " << filename << std::endl; + + File fileToUse = File(filename); + + if (!fileToUse.exists()) + { + // open it and write header + file = fopen(filename.toUTF8(), "ab"); + + String header = generateHeader(); + + fwrite(header.toUTF8(), 1, header.getNumBytesAsUTF8(), file); + + } else { + + // append it + file = fopen(filename.toUTF8(), "ab"); + } + + +} + +void SpikePlot::closeFile() +{ + + std::cout << "CLOSING FILE: " << filename << std::endl; + + if (file != NULL) + { + fclose(file); + } - for (int i = 0; i < nProjAx; i++) - pAxes[i]->updateSpikeData(s); } +void SpikePlot::writeSpike(const SpikeObject& s) +{ + + + packSpike(&s, spikeBuffer, MAX_SPIKE_BUFFER_LEN); + + fwrite(spikeBuffer, 1, MAX_SPIKE_BUFFER_LEN, file); + + +} + +String SpikePlot::generateHeader() +{ + String header = "header.format = 'OPEN EPHYS DATA FORMAT v0.0'; \n"; + + header += "header.header_bytes = "; + header += String(HEADER_SIZE); + header += ";\n"; + + header += "header.description = 'Spike data...live it up!'; \n"; + + header += "header.date_created = '"; + header += recordNode->generateDateString(); + header += "';\n"; + + header += "header.electrode = '"; + header += name; + header += "';\n"; + + header += "header.num_channels = "; + header += nChannels; + header += ";\n"; + + header += "header.sampleRate = "; + header += String(canvas->processor->settings.sampleRate); + header += ";\n"; + + header = header.paddedRight(' ', HEADER_SIZE); + + //std::cout << header << std::endl; + + return header; +} + + void SpikePlot::select() { isSelected = true; @@ -611,7 +755,7 @@ void SpikePlot::clear() WaveAxes::WaveAxes(int channel) : GenericAxes(channel), drawGrid(true), - bufferSize(10), spikeIndex(0), thresholdLevel(0.5f), range(250.0f), + bufferSize(10), spikeIndex(0), thresholdLevel(0.0f), range(250.0f), isOverThresholdSlider(false), isDraggingThresholdSlider(false) { @@ -723,10 +867,11 @@ void WaveAxes::plotSpike(const SpikeObject& s, Graphics& g) void WaveAxes::drawThresholdSlider(Graphics& g) { - float h = getHeight()*thresholdLevel; + float h = getHeight()*(0.5f - thresholdLevel/range); g.setColour(thresholdColour); g.drawLine(0, h, getWidth(), h); + g.drawText(String(roundFloatToInt(thresholdLevel)),2,h,25,10,Justification::left, false); } @@ -740,12 +885,15 @@ void WaveAxes::drawWaveformGrid(Graphics& g) 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); + if (y == 0) + g.drawLine(0,h/2 + y/range*h, w, h/2+ y/range*h,2.0f); + else + g.drawLine(0,h/2 + y/range*h, w, h/2+ y/range*h); } } -void WaveAxes::updateSpikeData(const SpikeObject& s) +bool WaveAxes::updateSpikeData(const SpikeObject& s) { if (!gotFirstSpike) { @@ -754,12 +902,38 @@ void WaveAxes::updateSpikeData(const SpikeObject& s) SpikeObject newSpike = s; - spikeIndex++; - spikeIndex %= bufferSize; + //if (checkThreshold(newSpike)) + //{ + spikeIndex++; + spikeIndex %= bufferSize; + + spikeBuffer.set(spikeIndex, newSpike); + // return true; + + // } else { + // return false; + // } - spikeBuffer.set(spikeIndex, newSpike); +} + +bool WaveAxes::checkThreshold(const SpikeObject& s) +{ + int sampIdx = 40*type; + + for (int i = 0; i < s.nSamples-1; i++) + { + + if (float(s.data[sampIdx]-32768)/float(*s.gain)*1000.0f > thresholdLevel) + { + return true; + } + + sampIdx++; + } + + return false; } @@ -776,6 +950,8 @@ void WaveAxes::clear() spikeBuffer.add(so); } + + repaint(); } void WaveAxes::mouseMove(const MouseEvent& event) @@ -785,7 +961,7 @@ void WaveAxes::mouseMove(const MouseEvent& event) float y = event.y; - float h = getHeight()*thresholdLevel; + float h = getHeight()*(0.5f - thresholdLevel/range); // std::cout << y << " " << h << std::endl; @@ -827,7 +1003,19 @@ void WaveAxes::mouseDrag(const MouseEvent& event) { if (isOverThresholdSlider) { - thresholdLevel = float(event.y) / float(getHeight()); + + float thresholdSliderPosition = float(event.y) / float(getHeight()); + + if (thresholdSliderPosition > 0.5f) + thresholdSliderPosition = 0.5f; + else if (thresholdSliderPosition < 0.0f) + thresholdSliderPosition = 0.0f; + + + thresholdLevel = (0.5f - thresholdSliderPosition) * range; + + //std::cout << "Threshold = " << thresholdLevel << std::endl; + repaint(); } } @@ -886,7 +1074,7 @@ void ProjectionAxes::paint(Graphics& g) 0, imageDim-rangeY, rangeX, rangeY); } -void ProjectionAxes::updateSpikeData(const SpikeObject& s) +bool ProjectionAxes::updateSpikeData(const SpikeObject& s) { if (!gotFirstSpike) { @@ -946,6 +1134,8 @@ void ProjectionAxes::clear() { projectionImage.clear(Rectangle<int>(0, 0, projectionImage.getWidth(), projectionImage.getHeight()), Colours::black); + + repaint(); } void ProjectionAxes::n2ProjIdx(int proj, int* p1, int* p2) @@ -1012,7 +1202,7 @@ GenericAxes::~GenericAxes() } -void GenericAxes::updateSpikeData(const SpikeObject& newSpike) +bool GenericAxes::updateSpikeData(const SpikeObject& newSpike) { if (!gotFirstSpike) { diff --git a/Source/Processors/Visualization/SpikeDisplayCanvas.h b/Source/Processors/Visualization/SpikeDisplayCanvas.h index 02ba77143ab868b2840866023d47934bbe1cdad6..1b3f835aaf99b04c84cc1f747e8617425bf762c2 100755 --- a/Source/Processors/Visualization/SpikeDisplayCanvas.h +++ b/Source/Processors/Visualization/SpikeDisplayCanvas.h @@ -57,6 +57,7 @@ class GenericAxes; class ProjectionAxes; class WaveAxes; class SpikePlot; +class RecordNode; /** @@ -95,9 +96,11 @@ public: void buttonClicked(Button* button); -private: - + RecordNode* getRecordNode(); SpikeDisplayNode* processor; + +private: + MidiBuffer* spikeBuffer; ScopedPointer<SpikeDisplay> spikeDisplay; @@ -122,7 +125,7 @@ public: void removePlots(); void clear(); - void addSpikePlot(int numChannels, int electrodeNum); + void addSpikePlot(int numChannels, int electrodeNum, String name); void paint(Graphics& g); @@ -161,12 +164,14 @@ private: Class for drawing the waveforms and projections of incoming spikes. + Also responsible for saving spikes. + */ class SpikePlot : public Component, Button::Listener { public: - SpikePlot(SpikeDisplayCanvas*, int elecNum, int plotType); + SpikePlot(SpikeDisplayCanvas*, int elecNum, int plotType, String name_); virtual ~SpikePlot(); void paint(Graphics& g); @@ -197,6 +202,8 @@ public: private: + bool isRecording; + int plotType; int nWaveAx; @@ -215,9 +222,22 @@ private: void setLimitsOnAxes(); void updateAxesPositions(); + String name; Font font; + // methods for recording: + void openFile(); + void closeFile(); + void writeSpike(const SpikeObject& s); + String generateHeader(); + + RecordNode* recordNode; + FILE* file; + String filename; + File dataDirectory; + uint8_t* spikeBuffer; + }; /** @@ -236,7 +256,7 @@ public: virtual ~GenericAxes(); - virtual void updateSpikeData(const SpikeObject& s); + virtual bool updateSpikeData(const SpikeObject& s); void setXLims(double xmin, double xmax); void getXLims(double* xmin, double* xmax); @@ -280,7 +300,8 @@ public: WaveAxes(int channel); ~WaveAxes() {} - void updateSpikeData(const SpikeObject& s); + bool updateSpikeData(const SpikeObject& s); + bool checkThreshold(const SpikeObject& spike); void paint(Graphics& g); @@ -312,6 +333,8 @@ private: void drawThresholdSlider(Graphics& g); + + Font font; Array<SpikeObject> spikeBuffer; @@ -344,7 +367,7 @@ public: ProjectionAxes(int projectionNum); ~ProjectionAxes() {} - void updateSpikeData(const SpikeObject& s); + bool updateSpikeData(const SpikeObject& s); void paint(Graphics& g); diff --git a/Source/Processors/Visualization/SpikeObject.cpp b/Source/Processors/Visualization/SpikeObject.cpp index c2665d123aca88f6505a8b9432228b22d30d8be2..bb7ce020b4f4ddc6ec1e19f0eb6db375f2c857d3 100755 --- a/Source/Processors/Visualization/SpikeObject.cpp +++ b/Source/Processors/Visualization/SpikeObject.cpp @@ -27,16 +27,13 @@ #include "time.h" // Simple method for serializing a SpikeObject into a string of bytes -int packSpike(SpikeObject* s, uint8_t* buffer, int bufferSize) +int packSpike(const SpikeObject* s, uint8_t* buffer, int bufferSize) { //int reqBytes = 1 + 4 + 2 + 2 + 2 + 2 * s->nChannels * s->nSamples + 2 * s->nChannels * 2; int idx = 0; - s->eventType = SPIKE_EVENT_CODE; - - memcpy(buffer+idx, &(s->eventType), 1); idx += 1; diff --git a/Source/Processors/Visualization/SpikeObject.h b/Source/Processors/Visualization/SpikeObject.h index 1bd73f2ec990f3f2293f7808e7163191f97ff46e..5a71b9e5b9b8a74e553b7a926a15003b63e44fbb 100755 --- a/Source/Processors/Visualization/SpikeObject.h +++ b/Source/Processors/Visualization/SpikeObject.h @@ -68,7 +68,7 @@ struct SpikeObject }; /** Simple method for serializing a SpikeObject into a string of bytes, returns true is the packaged spike buffer is valid */ -int packSpike(SpikeObject* s, uint8_t* buffer, int bufferLength); +int packSpike(const SpikeObject* s, uint8_t* buffer, int bufferLength); /** Simple method for deserializing a string of bytes into a Spike object, returns true is the provided spike buffer is valid */ bool unpackSpike(SpikeObject* s, const uint8_t* buffer, int bufferLength); diff --git a/Source/Processors/Visualization/Visualizer.h b/Source/Processors/Visualization/Visualizer.h index 3825800a64294b488ba5f5d289bc5bf99ad8130d..0e23f3d2cfcb3af037fc5a808728ac1f2b5018b7 100755 --- a/Source/Processors/Visualization/Visualizer.h +++ b/Source/Processors/Visualization/Visualizer.h @@ -94,8 +94,6 @@ public: /** Loads parameters from XML */ virtual void loadVisualizerParameters(XmlElement* xml) { } - - }; diff --git a/Source/UI/ProcessorList.cpp b/Source/UI/ProcessorList.cpp index d5998e9343ea85e7c93a92d3cd3b74f6b1e5e51f..4cf9a433c7d5988ab5c2f0088e535898ae24c752 100755 --- a/Source/UI/ProcessorList.cpp +++ b/Source/UI/ProcessorList.cpp @@ -78,7 +78,8 @@ ProcessorList::ProcessorList() filters->addSubItem(new ProcessorListItem("Spike Detector")); //filters->addSubItem(new ProcessorListItem("Resampler")); filters->addSubItem(new ProcessorListItem("Phase Detector")); - filters->addSubItem(new ProcessorListItem("Digital Ref")); + //filters->addSubItem(new ProcessorListItem("Digital Ref")); + filters->addSubItem(new ProcessorListItem("Channel Map")); ProcessorListItem* sinks = new ProcessorListItem("Sinks"); sinks->addSubItem(new ProcessorListItem("LFP Viewer")); diff --git a/Source/UI/UIComponent.cpp b/Source/UI/UIComponent.cpp index 7d94fb6b2ea432e14860752e4831db6a34401eb8..f9de7eb391bfbc72533158217fd0e90b94fccbdd 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/rhythm_config.xml")); + //getEditorViewport()->loadState(File("/home/jsiegle/Programming/GUI/Builds/Linux/build/spike_display_2.xml")); }