diff --git a/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp b/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp index fa33dde3adbb069ce918bb94df47e05d0293e10f..f644bba5122000a7c26713952945853bbc408501 100644 --- a/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp +++ b/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp @@ -942,7 +942,8 @@ void LfpDisplayCanvas::updateScreenBuffer() if (nSamples < 0) // buffer has reset to 0 { - nSamples = (displayBufferSize - dbi) + index; + nSamples = (displayBufferSize - dbi) + index +1; + std::cout << "nsamples 0 " ; } //if (channel == 15 || channel == 16) @@ -1007,12 +1008,12 @@ void LfpDisplayCanvas::updateScreenBuffer() float sample_min = 10000000; float sample_max = -10000000; - int nextpix = (dbi +(int)ratio +1) % displayBufferSize; // position to next pixels index + int nextpix = (dbi +(int)ratio +1) % (displayBufferSize+1); // position to next pixels index - if (nextpix <= dbi) { // at the end of the displaybuffer, this can occur and it causes the display to miss one pixel woth of sample - this circumvents that - //std::cout << "np " ; - nextpix=dbi; - } + //if (nextpix <= dbi) { // at the end of the displaybuffer, this can occur and it causes the display to miss one pixel woth of sample - this circumvents that + // std::cout << "np " ; + //nextpix=dbi; + //} for (int j = dbi; j <= nextpix; j++) { diff --git a/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayCanvas.cpp b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayCanvas.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f644bba5122000a7c26713952945853bbc408501 --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayCanvas.cpp @@ -0,0 +1,2670 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "LfpDisplayCanvas.h" + +#include <math.h> + +LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : + timebase(1.0f), displayGain(1.0f), timeOffset(0.0f), + processor(processor_), selectedChannelType(HEADSTAGE_CHANNEL) +{ + + nChans = processor->getNumInputs(); + std::cout << "Setting num inputs on LfpDisplayCanvas to " << nChans << std::endl; + + displayBuffer = processor->getDisplayBufferAddress(); + displayBufferSize = displayBuffer->getNumSamples(); + std::cout << "Setting displayBufferSize on LfpDisplayCanvas to " << displayBufferSize << std::endl; + + screenBuffer = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBuffer->clear(); + + screenBufferMin = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBufferMin->clear(); + screenBufferMean = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBufferMean->clear(); + screenBufferMax = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBufferMax->clear(); + + viewport = new LfpViewport(this); + lfpDisplay = new LfpDisplay(this, viewport); + timescale = new LfpTimescale(this); + + timescale->setTimebase(timebase); + + viewport->setViewedComponent(lfpDisplay, false); + viewport->setScrollBarsShown(true, false); + + scrollBarThickness = viewport->getScrollBarThickness(); + + isChannelEnabled.insertMultiple(0,true,10000); // max 10k channels + + //viewport->getVerticalScrollBar()->addListener(this->scrollBarMoved(viewport->getVerticalScrollBar(), 1.0)); + + UtilityButton* tbut; + + addAndMakeVisible(viewport); + addAndMakeVisible(timescale); + + //Ranges for neural data + voltageRanges[HEADSTAGE_CHANNEL].add("25"); + voltageRanges[HEADSTAGE_CHANNEL].add("50"); + voltageRanges[HEADSTAGE_CHANNEL].add("100"); + voltageRanges[HEADSTAGE_CHANNEL].add("250"); + voltageRanges[HEADSTAGE_CHANNEL].add("400"); + voltageRanges[HEADSTAGE_CHANNEL].add("500"); + voltageRanges[HEADSTAGE_CHANNEL].add("750"); + voltageRanges[HEADSTAGE_CHANNEL].add("1000"); + voltageRanges[HEADSTAGE_CHANNEL].add("2000"); + voltageRanges[HEADSTAGE_CHANNEL].add("5000"); + voltageRanges[HEADSTAGE_CHANNEL].add("10000"); + voltageRanges[HEADSTAGE_CHANNEL].add("15000"); + selectedVoltageRange[HEADSTAGE_CHANNEL] = 8; + rangeGain[HEADSTAGE_CHANNEL] = 1; //uV + rangeSteps[HEADSTAGE_CHANNEL] = 10; + rangeUnits.add("uV"); + typeNames.add("DATA"); + + tbut = new UtilityButton("DATA",Font("Small Text", 9, Font::plain)); + tbut->setEnabledState(true); + tbut->setCorners(false,false,false,false); + tbut->addListener(this); + tbut->setClickingTogglesState(true); + tbut->setRadioGroupId(100,dontSendNotification); + tbut->setToggleState(true,dontSendNotification); + addAndMakeVisible(tbut); + typeButtons.add(tbut); + + //Ranges for AUX/accelerometer data + voltageRanges[AUX_CHANNEL].add("25"); + voltageRanges[AUX_CHANNEL].add("50"); + voltageRanges[AUX_CHANNEL].add("100"); + voltageRanges[AUX_CHANNEL].add("250"); + voltageRanges[AUX_CHANNEL].add("400"); + voltageRanges[AUX_CHANNEL].add("500"); + voltageRanges[AUX_CHANNEL].add("750"); + voltageRanges[AUX_CHANNEL].add("1000"); + voltageRanges[AUX_CHANNEL].add("2000"); + //voltageRanges[AUX_CHANNEL].add("5000"); + selectedVoltageRange[AUX_CHANNEL] = 9; + rangeGain[AUX_CHANNEL] = 0.001; //mV + rangeSteps[AUX_CHANNEL] = 10; + rangeUnits.add("mV"); + typeNames.add("AUX"); + + + + tbut = new UtilityButton("AUX",Font("Small Text", 9, Font::plain)); + tbut->setEnabledState(true); + tbut->setCorners(false,false,false,false); + tbut->addListener(this); + tbut->setClickingTogglesState(true); + tbut->setRadioGroupId(100,dontSendNotification); + tbut->setToggleState(false,dontSendNotification); + addAndMakeVisible(tbut); + typeButtons.add(tbut); + + //Ranges for ADC data + voltageRanges[ADC_CHANNEL].add("0.01"); + voltageRanges[ADC_CHANNEL].add("0.05"); + voltageRanges[ADC_CHANNEL].add("0.1"); + voltageRanges[ADC_CHANNEL].add("0.5"); + voltageRanges[ADC_CHANNEL].add("1.0"); + voltageRanges[ADC_CHANNEL].add("2.0"); + voltageRanges[ADC_CHANNEL].add("5.0"); + voltageRanges[ADC_CHANNEL].add("10.0"); + selectedVoltageRange[ADC_CHANNEL] = 8; + rangeGain[ADC_CHANNEL] = 1; //V + rangeSteps[ADC_CHANNEL] = 0.1; //in V + rangeUnits.add("V"); + typeNames.add("ADC"); + + tbut = new UtilityButton("ADC",Font("Small Text", 9, Font::plain)); + tbut->setEnabledState(true); + tbut->setCorners(false,false,false,false); + tbut->addListener(this); + tbut->setClickingTogglesState(true); + tbut->setRadioGroupId(100,dontSendNotification); + tbut->setToggleState(false,dontSendNotification); + addAndMakeVisible(tbut); + typeButtons.add(tbut); + + selectedVoltageRangeValues[HEADSTAGE_CHANNEL] = voltageRanges[HEADSTAGE_CHANNEL][selectedVoltageRange[HEADSTAGE_CHANNEL]-1]; + selectedVoltageRangeValues[AUX_CHANNEL] = voltageRanges[AUX_CHANNEL][selectedVoltageRange[AUX_CHANNEL]-1]; + selectedVoltageRangeValues[ADC_CHANNEL] = voltageRanges[ADC_CHANNEL][selectedVoltageRange[ADC_CHANNEL]-1]; + + timebases.add("0.25"); + timebases.add("0.5"); + timebases.add("1.0"); + timebases.add("2.0"); + timebases.add("3.0"); + timebases.add("4.0"); + timebases.add("5.0"); + timebases.add("10.0"); + timebases.add("20.0"); + selectedTimebase = 4; + selectedTimebaseValue = timebases[selectedTimebase-1]; + + spreads.add("10"); + spreads.add("20"); + spreads.add("30"); + spreads.add("40"); + spreads.add("50"); + spreads.add("60"); + spreads.add("70"); + spreads.add("80"); + spreads.add("90"); + spreads.add("100"); + selectedSpread = 5; + selectedSpreadValue = spreads[selectedSpread-1]; + + + overlaps.add("0.5"); + overlaps.add("0.75"); + overlaps.add("1"); + overlaps.add("2"); + overlaps.add("3"); + overlaps.add("4"); + overlaps.add("5"); + selectedOverlap = 4; + selectedOverlapValue = overlaps[selectedOverlap-1]; + + saturationThresholds.add("0.5"); + saturationThresholds.add("100"); + saturationThresholds.add("1000"); + saturationThresholds.add("5000"); + saturationThresholds.add("6389"); + + selectedSaturation = 5; + selectedSaturationValue = saturationThresholds[selectedSaturation-1]; + + + colorGroupings.add("1"); + colorGroupings.add("2"); + colorGroupings.add("4"); + colorGroupings.add("8"); + colorGroupings.add("16"); + + + rangeSelection = new ComboBox("Voltage range"); + rangeSelection->addItemList(voltageRanges[HEADSTAGE_CHANNEL], 1); + rangeSelection->setSelectedId(selectedVoltageRange[HEADSTAGE_CHANNEL], sendNotification); + rangeSelection->setEditableText(true); + rangeSelection->addListener(this); + addAndMakeVisible(rangeSelection); + + + timebaseSelection = new ComboBox("Timebase"); + timebaseSelection->addItemList(timebases, 1); + timebaseSelection->setSelectedId(selectedTimebase, sendNotification); + timebaseSelection->setEditableText(true); + timebaseSelection->addListener(this); + addAndMakeVisible(timebaseSelection); + + + spreadSelection = new ComboBox("Spread"); + spreadSelection->addItemList(spreads, 1); + spreadSelection->setSelectedId(selectedSpread,sendNotification); + spreadSelection->addListener(this); + spreadSelection->setEditableText(true); + addAndMakeVisible(spreadSelection); + + overlapSelection = new ComboBox("Overlap"); + overlapSelection->addItemList(overlaps, 1); + overlapSelection->setSelectedId(selectedOverlap,sendNotification); + overlapSelection->addListener(this); + overlapSelection->setEditableText(true); + addAndMakeVisible(overlapSelection); + + saturationWarningSelection = new ComboBox("Sat.Warn"); + saturationWarningSelection->addItemList(saturationThresholds, 1); + saturationWarningSelection->setSelectedId(selectedSaturation,sendNotification); + saturationWarningSelection->addListener(this); + saturationWarningSelection->setEditableText(true); + addAndMakeVisible(saturationWarningSelection); + + + colorGroupingSelection = new ComboBox("Color Grouping"); + colorGroupingSelection->addItemList(colorGroupings, 1); + colorGroupingSelection->setSelectedId(1,sendNotification); + colorGroupingSelection->addListener(this); + addAndMakeVisible(colorGroupingSelection); + + invertInputButton = new UtilityButton("Invert", Font("Small Text", 13, Font::plain)); + invertInputButton->setRadius(5.0f); + invertInputButton->setEnabledState(true); + invertInputButton->setCorners(true, true, true, true); + invertInputButton->addListener(this); + invertInputButton->setClickingTogglesState(true); + invertInputButton->setToggleState(false, sendNotification); + addAndMakeVisible(invertInputButton); + + //button for controlling drawing algorithm - old line-style or new per-pixel style + drawMethodButton = new UtilityButton("DrawMethod", Font("Small Text", 13, Font::plain)); + drawMethodButton->setRadius(5.0f); + drawMethodButton->setEnabledState(true); + drawMethodButton->setCorners(true, true, true, true); + drawMethodButton->addListener(this); + drawMethodButton->setClickingTogglesState(true); + drawMethodButton->setToggleState(false, sendNotification); + addAndMakeVisible(drawMethodButton); + + // two sliders for the two histogram components of the supersampled plotting mode + // todo: rename these + brightnessSliderA = new Slider; + brightnessSliderA->setRange (0, 1); + brightnessSliderA->setTextBoxStyle(Slider::NoTextBox, false, 50,30); + brightnessSliderA->addListener(this); + addAndMakeVisible (brightnessSliderA); + + brightnessSliderB = new Slider; + brightnessSliderB->setRange (0, 1); + brightnessSliderB->setTextBoxStyle(Slider::NoTextBox, false, 50,30); + brightnessSliderB->addListener(this); + addAndMakeVisible (brightnessSliderB); + + sliderALabel = new Label("Brightness","Brightness"); + sliderALabel->setFont(Font("Small Text", 13, Font::plain)); + sliderALabel->setColour(Label::textColourId,Colour(150,150,150)); + addAndMakeVisible(sliderALabel); + + sliderBLabel = new Label("Min. brightness","Min. brightness"); + sliderBLabel->setFont(Font("Small Text", 13, Font::plain)); + sliderBLabel->setColour(Label::textColourId,Colour(150,150,150)); + addAndMakeVisible(sliderBLabel); + + + //ScopedPointer<UtilityButton> drawClipWarningButton; // optinally draw (subtle) warning if data is clipped in display + drawClipWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain)); + drawClipWarningButton->setRadius(5.0f); + drawClipWarningButton->setEnabledState(true); + drawClipWarningButton->setCorners(true, true, true, true); + drawClipWarningButton->addListener(this); + drawClipWarningButton->setClickingTogglesState(true); + drawClipWarningButton->setToggleState(false, sendNotification); + addAndMakeVisible(drawClipWarningButton); + + + //ScopedPointer<UtilityButton> drawSaturateWarningButton; // optionally raise hell if the actual data is saturating + drawSaturateWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain)); + drawSaturateWarningButton->setRadius(5.0f); + drawSaturateWarningButton->setEnabledState(true); + drawSaturateWarningButton->setCorners(true, true, true, true); + drawSaturateWarningButton->addListener(this); + drawSaturateWarningButton->setClickingTogglesState(true); + drawSaturateWarningButton->setToggleState(false, sendNotification); + addAndMakeVisible(drawSaturateWarningButton); + + + //button for pausing the display - works by skipping buffer updates. This way scrolling etc still works + pauseButton = new UtilityButton("Pause", Font("Small Text", 13, Font::plain)); + pauseButton->setRadius(5.0f); + pauseButton->setEnabledState(true); + pauseButton->setCorners(true, true, true, true); + pauseButton->addListener(this); + pauseButton->setClickingTogglesState(true); + pauseButton->setToggleState(false, sendNotification); + addAndMakeVisible(pauseButton); + + + lfpDisplay->setNumChannels(nChans); + lfpDisplay->setRange(voltageRanges[HEADSTAGE_CHANNEL][selectedVoltageRange[HEADSTAGE_CHANNEL]-1].getFloatValue()*rangeGain[HEADSTAGE_CHANNEL] + ,HEADSTAGE_CHANNEL); + lfpDisplay->setRange(voltageRanges[ADC_CHANNEL][selectedVoltageRange[ADC_CHANNEL] - 1].getFloatValue()*rangeGain[ADC_CHANNEL] + , ADC_CHANNEL); + lfpDisplay->setRange(voltageRanges[AUX_CHANNEL][selectedVoltageRange[AUX_CHANNEL] - 1].getFloatValue()*rangeGain[AUX_CHANNEL] + , AUX_CHANNEL); + + // 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); + + } + + // allocate samplesPerPixel, behaves like float samplesPerPixel[nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL] + samplesPerPixel = (float***)malloc(nChans * sizeof(float **)); + for(int i=0;i<nChans;i++) + { + samplesPerPixel[i] = (float**)malloc(MAX_N_SAMP * sizeof(float*)); + for(int j=0;j<MAX_N_SAMP;j++) + { + samplesPerPixel[i][j] = (float*)malloc(MAX_N_SAMP_PER_PIXEL*sizeof(float)); + } + } + + TopLevelWindow::getTopLevelWindow(0)->addKeyListener(this); +} + +LfpDisplayCanvas::~LfpDisplayCanvas() +{ + + deleteAndZero(screenBuffer); + deleteAndZero(screenBufferMin); + deleteAndZero(screenBufferMean); + deleteAndZero(screenBufferMax); + + // de-allocate 3d-array samplesPerPixel [nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]; + + for(int i=0;i<nChans;i++) + { + for(int j=0;j<MAX_N_SAMP;j++) + { + free(samplesPerPixel[i][j]); + } + free(samplesPerPixel[i]); + } + free(samplesPerPixel); + + + TopLevelWindow::getTopLevelWindow(0)->removeKeyListener(this); +} + +void LfpDisplayCanvas::resized() +{ + + timescale->setBounds(leftmargin,0,getWidth()-scrollBarThickness-leftmargin,30); + viewport->setBounds(0,30,getWidth(),getHeight()-90); + + if (lfpDisplay->getSingleChannelState()) + lfpDisplay->setChannelHeight(viewport->getHeight(),false); + + lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*nChans); + + rangeSelection->setBounds(5,getHeight()-30,80,25); + timebaseSelection->setBounds(175,getHeight()-30,60,25); + + spreadSelection->setBounds(245,getHeight()-30,60,25); + + overlapSelection->setBounds(315,getHeight()-30,60,25); + drawClipWarningButton->setBounds(410-30,getHeight()-29,20,20); + + colorGroupingSelection->setBounds(620,getHeight()-30,60,25); + + invertInputButton->setBounds(750,getHeight()-50,100,22); + drawMethodButton->setBounds(750,getHeight()-25,100,22); + pauseButton->setBounds(860,getHeight()-50,50,44); + + saturationWarningSelection->setBounds(315+90,getHeight()-30,60,25); + drawSaturateWarningButton->setBounds(410-30+90,getHeight()-29,20,20); + + 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(); + } + + brightnessSliderA->setBounds(920,getHeight()-50,100,22); + sliderALabel->setBounds(1020, getHeight()-50, 180, 22); + brightnessSliderA->setValue(0.9); //set default value + + brightnessSliderB->setBounds(920,getHeight()-25,100,22); + sliderBLabel->setBounds(1020, getHeight()-25, 180, 22); + brightnessSliderB->setValue(0.1); //set default value + + int bh = 25/typeButtons.size(); + for (int i = 0; i < typeButtons.size(); i++) + { + typeButtons[i]->setBounds(110,getHeight()-30+i*bh,50,bh); + } + // std::cout << "Canvas thinks LfpDisplay should be this high: " + // << lfpDisplay->getTotalHeight() << std::endl; + +} + +void LfpDisplayCanvas::beginAnimation() +{ + std::cout << "Beginning animation." << std::endl; + + displayBufferSize = displayBuffer->getNumSamples(); + + for (int i = 0; i < screenBufferIndex.size(); i++) + { + screenBufferIndex.set(i,0); + } + + startCallbacks(); +} + +void LfpDisplayCanvas::endAnimation() +{ + std::cout << "Ending animation." << std::endl; + + stopCallbacks(); +} + +void LfpDisplayCanvas::update() +{ + nChans = jmax(processor->getNumInputs(),1); + + sampleRate.clear(); + screenBufferIndex.clear(); + lastScreenBufferIndex.clear(); + displayBufferIndex.clear(); + + for (int i = 0; i <= nChans; i++) // extra channel for events + { + if (processor->getNumInputs() > 0) + { + if (i < nChans) + sampleRate.add(processor->channels[i]->sampleRate); + else + sampleRate.add(processor->channels[i - 1]->sampleRate); // for event channel (IT'S A HACK -- BE CAREFUL!) + } + else + sampleRate.add(30000); + + // std::cout << "Sample rate for ch " << i << " = " << sampleRate[i] << std::endl; + displayBufferIndex.add(0); + screenBufferIndex.add(0); + lastScreenBufferIndex.add(0); + } + + + + if (nChans != lfpDisplay->getNumChannels()) + { + //std::cout << "Setting num inputs on LfpDisplayCanvas to " << nChans << std::endl; + + refreshScreenBuffer(); + + lfpDisplay->setNumChannels(nChans); // add an extra channel for events + + // update channel names + for (int i = 0; i < processor->getNumInputs(); i++) + { + + String chName = processor->channels[i]->getName(); + + //std::cout << chName << std::endl; + + lfpDisplay->channelInfo[i]->setName(chName); + lfpDisplay->enableChannel(isChannelEnabled[i], i); + + } + + lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight()); + + resized(); + } + else + { + for (int i = 0; i < processor->getNumInputs(); i++) + { + lfpDisplay->channels[i]->updateType(); + lfpDisplay->channelInfo[i]->updateType(); + } + + } + +} + +void LfpDisplayCanvas::buttonClicked(Button* b) +{ + if (b == invertInputButton) + { + lfpDisplay->setInputInverted(b->getToggleState()); + return; + } + if (b == drawMethodButton) + { + lfpDisplay->setDrawMethod(b->getToggleState()); // this should be done the same way as drawClipWarning - or the other way around. + return; + } + if (b == drawClipWarningButton) + { + drawClipWarning = b->getToggleState(); + + fullredraw=true; + repaint(); + refresh(); + return; + } + if (b == drawSaturateWarningButton) + { + drawSaturationWarning = b->getToggleState(); + + fullredraw=true; + repaint(); + refresh(); + return; + } + + if (b == pauseButton) + { + lfpDisplay->isPaused = b->getToggleState(); + return; + } + + int idx = typeButtons.indexOf((UtilityButton*)b); + if ((idx >= 0) && (b->getToggleState())) + { + for (int i = 0; i < processor->getNumInputs(); i++) + { + if (lfpDisplay->channels[i]->getSelected()) + { + lfpDisplay->channels[i]->deselect(); + lfpDisplay->channels[i]->repaint(); + } + } + setSelectedType((ChannelType) idx, false); + } + +} + + +void LfpDisplayCanvas::comboBoxChanged(ComboBox* cb) +{ + + if (cb == timebaseSelection) + { + if (cb->getSelectedId()) + { + timebase = timebases[cb->getSelectedId()-1].getFloatValue(); + } + else + { + timebase = cb->getText().getFloatValue(); + if (timebase) + { + if (timebase < timebases[0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + timebase = timebases[0].getFloatValue(); + } + else if (timebase > timebases[timebases.size()-1].getFloatValue()) + { + cb->setSelectedId(timebases.size(),dontSendNotification); + timebase = timebases[timebases.size()-1].getFloatValue(); + } + else + cb->setText(String(timebase,1),dontSendNotification); + } + else + { + if (selectedSpread == 0) + { + cb->setText(selectedTimebaseValue,dontSendNotification); + timebase = selectedTimebaseValue.getFloatValue(); + } + else + { + cb->setSelectedId(selectedTimebase,dontSendNotification); + timebase = timebases[selectedTimebase-1].getFloatValue(); + } + + } + } + } + else if (cb == rangeSelection) + { + if (cb->getSelectedId()) + { + lfpDisplay->setRange(voltageRanges[selectedChannelType][cb->getSelectedId()-1].getFloatValue()*rangeGain[selectedChannelType] + ,selectedChannelType); + } + else + { + float vRange = cb->getText().getFloatValue(); + if (vRange) + { + if (vRange < voltageRanges[selectedChannelType][0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + vRange = voltageRanges[selectedChannelType][0].getFloatValue(); + } + else if (vRange > voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue()) + { + // cb->setSelectedId(voltageRanges[selectedChannelType].size(),dontSendNotification); + // vRange = voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue(); + } + else + { + if (rangeGain[selectedChannelType] > 1) + cb->setText(String(vRange,1),dontSendNotification); + else + cb->setText(String(vRange),dontSendNotification); + } + lfpDisplay->setRange(vRange*rangeGain[selectedChannelType],selectedChannelType); + } + else + { + if (selectedVoltageRange[selectedChannelType]) + cb->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification); + else + cb->setSelectedId(selectedVoltageRange[selectedChannelType],dontSendNotification); + } + } + selectedVoltageRange[selectedChannelType] = cb->getSelectedId(); + selectedVoltageRangeValues[selectedChannelType] = cb->getText(); + //std::cout << "Setting range to " << voltageRanges[cb->getSelectedId()-1].getFloatValue() << std::endl; + fullredraw = true; //issue full redraw + repaint(); + refresh(); + } + else if (cb == spreadSelection) + { + if (cb->getSelectedId()) + { + lfpDisplay->setChannelHeight(spreads[cb->getSelectedId()-1].getIntValue()); + resized(); + } + else + { + int spread = cb->getText().getIntValue(); + if (spread) + { + if (spread < spreads[0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + spread = spreads[0].getFloatValue(); + } + else if (spread > spreads[spreads.size()-1].getFloatValue()) + { + cb->setSelectedId(spreads.size(),dontSendNotification); + spread = spreads[spreads.size()-1].getFloatValue(); + } + else + { + cb->setText(String(spread),dontSendNotification); + } + lfpDisplay->setChannelHeight(spread); + resized(); + } + else + { + if (selectedSpread == 0) + cb->setText(selectedSpreadValue,dontSendNotification); + else + cb->setSelectedId(selectedSpread,dontSendNotification); + } + } + selectedSpread = cb->getSelectedId(); + selectedSpreadValue = cb->getText(); + fullredraw = true; //issue full redraw + repaint(); + refresh(); + //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl; + } + else if (cb == saturationWarningSelection) + { + if (cb->getSelectedId()) + { + selectedSaturationValueFloat=(saturationThresholds[cb->getSelectedId()-1].getFloatValue()); + } + else + { + selectedSaturationValueFloat = cb->getText().getFloatValue(); + if (selectedSaturationValueFloat) + { + std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl; + if (selectedSaturationValueFloat < 0) + { + cb->setSelectedId(1,dontSendNotification); + selectedSaturationValueFloat = saturationThresholds[0].getFloatValue(); + } + else + { + // cb->setText(String(selectedSaturationValueFloat),dontSendNotification); + } + } + else + { + // cb->setSelectedId(1,dontSendNotification); + //selectedSaturationValueFloat = saturationThresholds[0].getFloatValue(); + + } + } + // selectedSpread = cb->getSelectedId(); + // selectedSpreadValue = cb->getText(); + // lfpDisplay->setChannelHeight( lfpDisplay->getChannelHeight()); + fullredraw = true; //issue full redraw + repaint(); + refresh(); + std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl; + } + else if (cb == overlapSelection) + { + if (cb->getSelectedId()) + { + channelOverlapFactor=(overlaps[cb->getSelectedId()-1].getFloatValue()); + resized(); + } + else + { + float overlap = cb->getText().getFloatValue(); + if (overlap) + { + if (overlap < overlaps[0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + overlap = overlaps[0].getFloatValue(); + } + else if (overlap > overlaps[overlaps.size()-1].getFloatValue()) + { + cb->setSelectedId(overlaps.size(),dontSendNotification); + overlap = overlaps[overlaps.size()-1].getFloatValue(); + } + else + { + cb->setText(String(overlap),dontSendNotification); + } + channelOverlapFactor= overlap; + resized(); + } + else + { + if (selectedSpread == 0) + cb->setText(selectedSpreadValue,dontSendNotification); + else + cb->setSelectedId(selectedSpread,dontSendNotification); + } + } + selectedSpread = cb->getSelectedId(); + selectedSpreadValue = cb->getText(); + lfpDisplay->setChannelHeight( lfpDisplay->getChannelHeight()); + fullredraw = true; //issue full redraw + repaint(); + refresh(); + //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl; + } + + else if (cb == colorGroupingSelection) + { + // set color grouping hre + + lfpDisplay->setColorGrouping(colorGroupings[cb->getSelectedId()-1].getIntValue());// so that channel colors get re-assigned + fullredraw = true; //issue full redraw + repaint(); + refresh(); + } + + timescale->setTimebase(timebase); +} + + +void LfpDisplayCanvas::sliderValueChanged(Slider* sl) +{ + if (sl == brightnessSliderA) + histogramParameterA = sl->getValue(); + + if (sl == brightnessSliderB) + histogramParameterB = sl->getValue(); + + fullredraw=true; + //repaint(); + refresh(); + +} + + +void LfpDisplayCanvas::sliderEvent(Slider* sl) {} + +int LfpDisplayCanvas::getChannelHeight() +{ + //return spreads[spreadSelection->getSelectedId()-1].getIntValue(); + return (int)spreadSelection->getText().getIntValue(); + +} + + +void LfpDisplayCanvas::setParameter(int param, float val) +{ + // not used for anything, since LfpDisplayCanvas is not a processor +} + +void LfpDisplayCanvas:: setRangeSelection(float range, bool canvasMustUpdate) +{ + if (canvasMustUpdate) + { + rangeSelection->setText(String(range/rangeGain[selectedChannelType]), sendNotification); + } + else + { + rangeSelection->setText(String(range/rangeGain[selectedChannelType]),dontSendNotification); + selectedVoltageRange[selectedChannelType]=rangeSelection->getSelectedId(); + selectedVoltageRangeValues[selectedChannelType]=rangeSelection->getText(); + + repaint(); + refresh(); + } + +} + +void LfpDisplayCanvas:: setSpreadSelection(int spread, bool canvasMustUpdate) +{ + if (canvasMustUpdate) + { + spreadSelection->setText(String(spread),sendNotification); + } + else + { + spreadSelection->setText(String(spread),dontSendNotification); + selectedSpread=spreadSelection->getSelectedId(); + selectedSpreadValue=spreadSelection->getText(); + + repaint(); + refresh(); + } +} + +void LfpDisplayCanvas::refreshState() +{ + // called when the component's tab becomes visible again + + for (int i = 0; i <= displayBufferIndex.size(); i++) // include event channel + { + + displayBufferIndex.set(i, processor->getDisplayBufferIndex(i)); + screenBufferIndex.set(i,0); + } + +} + +void LfpDisplayCanvas::refreshScreenBuffer() +{ + + for (int i = 0; i < screenBufferIndex.size(); i++) + screenBufferIndex.set(i,0); + + screenBuffer->clear(); + screenBufferMin->clear(); + screenBufferMean->clear(); + screenBufferMax->clear(); + + + // int w = lfpDisplay->getWidth(); + // //std::cout << "Refreshing buffer size to " << w << "pixels." << std::endl; + + // for (int i = 0; i < w; 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 + // } + // } + +} + +void LfpDisplayCanvas::updateScreenBuffer() +{ + + // copy new samples from the displayBuffer into the screenBuffer + int maxSamples = lfpDisplay->getWidth() - leftmargin; + + ScopedLock displayLock(*processor->getMutex()); + + for (int channel = 0; channel <= nChans; channel++) // pull one extra channel for event display + { + + if (screenBufferIndex[channel] >= maxSamples) // wrap around if we reached right edge before + screenBufferIndex.set(channel, 0); + + // hold these values locally for each channel - is this a good idea? + int sbi = screenBufferIndex[channel]; + int dbi = displayBufferIndex[channel]; + + lastScreenBufferIndex.set(channel,sbi); + + int index = processor->getDisplayBufferIndex(channel); + + int nSamples = index - dbi; // N new samples (not pixels) to be added to displayBufferIndex + + if (nSamples < 0) // buffer has reset to 0 + { + nSamples = (displayBufferSize - dbi) + index +1; + std::cout << "nsamples 0 " ; + } + + //if (channel == 15 || channel == 16) + // std::cout << channel << " " << sbi << " " << dbi << " " << nSamples << std::endl; + + + float ratio = sampleRate[channel] * timebase / float(getWidth() - leftmargin - scrollBarThickness); // samples / pixel + // this number is crucial: converting from samples to values (in px) for the screen buffer + int valuesNeeded = (int) float(nSamples) / ratio; // N pixels needed for this update + + if (sbi + valuesNeeded > maxSamples) // crop number of samples to fit canvas width + { + valuesNeeded = maxSamples - sbi; + } + float subSampleOffset = 0.0; + + dbi %= displayBufferSize; // make sure we're not overshooting + int nextPos = (dbi + 1) % displayBufferSize; // position next to displayBufferIndex in display buffer to copy from + + // if (channel == 0) + // std::cout << "Channel " + // << channel << " : " + // << sbi << " : " + // << index << " : " + // << dbi << " : " + // << valuesNeeded << " : " + // << ratio + // << std::endl; + + if (valuesNeeded > 0 && valuesNeeded < 1000000) + { + for (int i = 0; i < valuesNeeded; i++) // also fill one extra sample for line drawing interpolation to match across draws + { + //If paused don't update screen buffers, but update all indexes as needed + if (!lfpDisplay->isPaused) + { + float gain = 1.0; + float alpha = (float) subSampleOffset; + float invAlpha = 1.0f - alpha; + + screenBuffer->clear(channel, sbi, 1); + screenBufferMin->clear(channel, sbi, 1); + screenBufferMax->clear(channel, sbi, 1); + + dbi %= displayBufferSize; // just to be sure + + // interpolate between two samples with invAlpha and alpha + screenBuffer->addFrom(channel, // destChannel + sbi, // destStartSample + displayBuffer->getReadPointer(channel, dbi), // source + 1, // numSamples + invAlpha*gain); // gain + + + screenBuffer->addFrom(channel, // destChannel + sbi, // destStartSample + displayBuffer->getReadPointer(channel, nextPos), // source + 1, // numSamples + alpha*gain); // gain + + // same thing again, but this time add the min,mean, and max of all samples in current pixel + float sample_min = 10000000; + float sample_max = -10000000; + + int nextpix = (dbi +(int)ratio +1) % (displayBufferSize+1); // position to next pixels index + + //if (nextpix <= dbi) { // at the end of the displaybuffer, this can occur and it causes the display to miss one pixel woth of sample - this circumvents that + // std::cout << "np " ; + //nextpix=dbi; + //} + + for (int j = dbi; j <= nextpix; j++) + { + float sample_current = displayBuffer->getSample(channel, j); + //sample_mean = sample_mean + sample_current; + + if (sample_min>sample_current) + { + sample_min=sample_current; + } + + if (sample_max<sample_current) + { + sample_max=sample_current; + } + + } + + // similarly, for each pixel on the screen, we want a list of all values so we can draw a histogram later + // for simplicity, we'll just do this as 2d array, samplesPerPixel[px][samples] + // with an additional array sampleCountPerPixel[px] that holds the N samples per pixel + if (channel < nChans) // we're looping over one 'extra' channel for events above, so make sure not to loop over that one here + { + int c = 0; + for (int j = dbi; j <= nextpix & c < MAX_N_SAMP_PER_PIXEL; j++) + { + float sample_current = displayBuffer->getSample(channel, j); + samplesPerPixel[channel][sbi][c]=sample_current; + c++; + } + if (c>0){ + sampleCountPerPixel[sbi]=c-1; // save count of samples for this pixel + }else{ + sampleCountPerPixel[sbi]=0; + } + //sample_mean = sample_mean/c; + //screenBufferMean->addSample(channel, sbi, sample_mean*gain); + + screenBufferMin->addSample(channel, sbi, sample_min*gain); + screenBufferMax->addSample(channel, sbi, sample_max*gain); + } + sbi++; + } + + subSampleOffset += ratio; + + while (subSampleOffset >= 1.0) + { + if (++dbi > displayBufferSize) + dbi = 0; + + nextPos = (dbi + 1) % displayBufferSize; + subSampleOffset -= 1.0; + } + + } + + // update values after we're done + screenBufferIndex.set(channel, sbi); + displayBufferIndex.set(channel, dbi); + } + + } + +} + +const float LfpDisplayCanvas::getXCoord(int chan, int samp) +{ + return samp; +} + +int LfpDisplayCanvas::getNumChannels() +{ + return nChans; +} + +const float LfpDisplayCanvas::getYCoord(int chan, int samp) +{ + return *screenBuffer->getReadPointer(chan, samp); +} + +const float LfpDisplayCanvas::getYCoordMean(int chan, int samp) +{ + return *screenBufferMean->getReadPointer(chan, samp); +} +const float LfpDisplayCanvas::getYCoordMin(int chan, int samp) +{ + return *screenBufferMin->getReadPointer(chan, samp); +} +const float LfpDisplayCanvas::getYCoordMax(int chan, int samp) +{ + return *screenBufferMax->getReadPointer(chan, samp); +} + +const float* LfpDisplayCanvas::getSamplesPerPixel(int chan, int px) +{ + return samplesPerPixel[chan][px]; +} +const int LfpDisplayCanvas::getSampleCountPerPixel(int px) +{ + return sampleCountPerPixel[px]; +} + + + +bool LfpDisplayCanvas::getInputInvertedState() +{ + return invertInputButton->getToggleState(); +} + +bool LfpDisplayCanvas::getDrawMethodState() +{ + return drawMethodButton->getToggleState(); +} + +void LfpDisplayCanvas::paint(Graphics& g) +{ + + //std::cout << "Painting" << std::endl; + + //g.setColour(Colour(0,0,0)); // for high-precision per-pixel density display, make background black for better visibility + g.setColour(lfpDisplay->backgroundColour); //background color + g.fillRect(0, 0, getWidth(), getHeight()); + + g.setGradientFill(ColourGradient(Colour(50,50,50),0,0, + Colour(25,25,25),0,30, + false)); + + g.fillRect(0, 0, getWidth()-scrollBarThickness, 30); + + 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 || i == 0) + g.drawLine(w/10*i+leftmargin,0,w/10*i+leftmargin,getHeight()-60,3.0f); + else + 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); + + g.setFont(Font("Default", 16, Font::plain)); + + g.setColour(Colour(100,100,100)); + + g.drawText("Range("+ rangeUnits[selectedChannelType] +")",5,getHeight()-55,300,20,Justification::left, false); + g.drawText("Timebase(s)",140,getHeight()-55,300,20,Justification::left, false); + g.drawText("Size(px)",240,getHeight()-55,300,20,Justification::left, false); + g.drawText("Clip",315,getHeight()-55,300,20,Justification::left, false); + g.drawText("Warn",373,getHeight()-55,300,20,Justification::left, false); + + g.drawText("Sat.Warn.",315+105,getHeight()-55,300,20,Justification::left, false); + //g.drawText("Warn",375+90,getHeight()-55,300,20,Justification::left, false); + + g.drawText("Color grouping",620,getHeight()-55,300,20,Justification::left, false); + + + //g.drawText(typeNames[selectedChannelType],110,getHeight()-30,50,20,Justification::centredLeft,false); + + g.drawText("Event disp.",500,getHeight()-55,300,20,Justification::left, false); + + if(drawClipWarning) + { + g.setColour(Colours::white); + g.fillRoundedRectangle(408-30,getHeight()-30-1,24,24,6.0f); + } + + if(drawSaturationWarning) + { + g.setColour(Colours::red); + g.fillRoundedRectangle(408-30+90,getHeight()-30-1,24,24,6.0f); + } + + +} + +void LfpDisplayCanvas::refresh() +{ + + updateScreenBuffer(); + + lfpDisplay->refresh(); // redraws only the new part of the screen buffer + + //getPeer()->performAnyPendingRepaintsNow(); + +} + +bool LfpDisplayCanvas::keyPressed(const KeyPress& key) +{ + if (key.getKeyCode() == key.spaceKey) + { + pauseButton->setToggleState(!pauseButton->getToggleState(), sendNotification); + return true; + } + + return false; +} + +bool LfpDisplayCanvas::keyPressed(const KeyPress& key, Component* orig) +{ + if (getTopLevelComponent() == orig && isVisible()) + { + return keyPressed(key); + } + return false; +} + +void LfpDisplayCanvas::saveVisualizerParameters(XmlElement* xml) +{ + + XmlElement* xmlNode = xml->createNewChildElement("LFPDISPLAY"); + + + xmlNode->setAttribute("Range",selectedVoltageRangeValues[0]+","+selectedVoltageRangeValues[1]+ + ","+selectedVoltageRangeValues[2]); + xmlNode->setAttribute("Timebase",timebaseSelection->getText()); + xmlNode->setAttribute("Spread",spreadSelection->getText()); + xmlNode->setAttribute("colorGrouping",colorGroupingSelection->getSelectedId()); + xmlNode->setAttribute("isInverted",invertInputButton->getToggleState()); + xmlNode->setAttribute("drawMethod",drawMethodButton->getToggleState()); + + int eventButtonState = 0; + + for (int i = 0; i < 8; i++) + { + if (lfpDisplay->eventDisplayEnabled[i]) + { + eventButtonState += (1 << i); + } + } + + xmlNode->setAttribute("EventButtonState", eventButtonState); + + String channelDisplayState = ""; + + for (int i = 0; i < nChans; i++) + { + if (lfpDisplay->getEnabledState(i)) + { + channelDisplayState += "1"; + } + else + { + channelDisplayState += "0"; + } + } + + xmlNode->setAttribute("ChannelDisplayState", channelDisplayState); + + xmlNode->setAttribute("ScrollX",viewport->getViewPositionX()); + xmlNode->setAttribute("ScrollY",viewport->getViewPositionY()); +} + + +void LfpDisplayCanvas::loadVisualizerParameters(XmlElement* xml) +{ + forEachXmlChildElement(*xml, xmlNode) + { + if (xmlNode->hasTagName("LFPDISPLAY")) + { + StringArray ranges; + ranges.addTokens(xmlNode->getStringAttribute("Range"),",",String::empty); + selectedVoltageRangeValues[0] = ranges[0]; + selectedVoltageRangeValues[1] = ranges[1]; + selectedVoltageRangeValues[2] = ranges[2]; + selectedVoltageRange[0] = voltageRanges[0].indexOf(ranges[0])+1; + selectedVoltageRange[1] = voltageRanges[1].indexOf(ranges[1])+1; + selectedVoltageRange[2] = voltageRanges[2].indexOf(ranges[2])+1; + rangeSelection->setText(ranges[0]); + + timebaseSelection->setText(xmlNode->getStringAttribute("Timebase")); + spreadSelection->setText(xmlNode->getStringAttribute("Spread")); + if (xmlNode->hasAttribute("colorGrouping")) + { + colorGroupingSelection->setSelectedId(xmlNode->getIntAttribute("colorGrouping")); + } + else + { + colorGroupingSelection->setSelectedId(1); + } + + invertInputButton->setToggleState(xmlNode->getBoolAttribute("isInverted", true), sendNotification); + + drawMethodButton->setToggleState(xmlNode->getBoolAttribute("drawMethod", true), sendNotification); + + viewport->setViewPosition(xmlNode->getIntAttribute("ScrollX"), + xmlNode->getIntAttribute("ScrollY")); + + int eventButtonState = xmlNode->getIntAttribute("EventButtonState"); + + for (int i = 0; i < 8; i++) + { + lfpDisplay->eventDisplayEnabled[i] = (eventButtonState >> i) & 1; + + eventDisplayInterfaces[i]->checkEnabledState(); + } + + String channelDisplayState = xmlNode->getStringAttribute("ChannelDisplayState"); + + for (int i = 0; i < channelDisplayState.length(); i++) + { + + if (channelDisplayState.substring(i,i+1).equalsIgnoreCase("1")) + { + //std::cout << "LfpDisplayCanvas enabling channel " << i << std::endl; + lfpDisplay->enableChannel(true, i); + isChannelEnabled.set(i,true); //lfpDisplay->enableChannel(true, i); + } + else + { + //std::cout << "LfpDisplayCanvas disabling channel " << i << std::endl; + lfpDisplay->enableChannel(false, i); + isChannelEnabled.set(i,false); + } + + + } + } + } + +} + +ChannelType LfpDisplayCanvas::getChannelType(int n) +{ + if (n < processor->getNumInputs()) + return processor->channels[n]->getType(); + else + return HEADSTAGE_CHANNEL; +} + +ChannelType LfpDisplayCanvas::getSelectedType() +{ + return selectedChannelType; +} + +void LfpDisplayCanvas::setSelectedType(ChannelType type, bool toggleButton) +{ + if (selectedChannelType == type) + return; //Nothing to do here + selectedChannelType = type; + rangeSelection->clear(dontSendNotification); + rangeSelection->addItemList(voltageRanges[type],1); + int id = selectedVoltageRange[type]; + if (id) + rangeSelection->setSelectedId(id,sendNotification); + else + rangeSelection->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification); + repaint(5,getHeight()-55,300,100); + + if (toggleButton) + typeButtons[type]->setToggleState(true,dontSendNotification); +} + +String LfpDisplayCanvas::getTypeName(ChannelType type) +{ + return typeNames[type]; +} + +int LfpDisplayCanvas::getRangeStep(ChannelType type) +{ + return rangeSteps[type]; +} + +//void LfpDisplayCanvas::sliderValueChanged(Slider* s) +//{ +// sliderEvent(slider); +//} + + +// ------------------------------------------------------------- + +LfpTimescale::LfpTimescale(LfpDisplayCanvas* c) : canvas(c) +{ + + font = Font("Default", 16, Font::plain); +} + +LfpTimescale::~LfpTimescale() +{ + +} + +void LfpTimescale::paint(Graphics& g) +{ + + g.setFont(font); + + g.setColour(Colour(100,100,100)); + + g.drawText("ms:",5,0,100,getHeight(),Justification::left, false); + + for (int i = 1; i < 10; i++) + { + if (i == 5) + g.drawLine(getWidth()/10*i,0,getWidth()/10*i,getHeight(),3.0f); + else + g.drawLine(getWidth()/10*i,0,getWidth()/10*i,getHeight(),1.0f); + + g.drawText(labels[i-1],getWidth()/10*i+3,0,100,getHeight(),Justification::left, false); + } + +} + +void LfpTimescale::setTimebase(float t) +{ + timebase = t; + + labels.clear(); + + for (float i = 1.0f; i < 10.0; i++) + { + String labelString = String(timebase/10.0f*1000.0f*i); + + labels.add(labelString.substring(0,6)); + } + + repaint(); + +} + + +// --------------------------------------------------------------- + +LfpDisplay::LfpDisplay(LfpDisplayCanvas* c, Viewport* v) : + singleChan(-1), canvas(c), viewport(v) +{ + totalHeight = 0; + colorGrouping=1; + + range[0] = 1000; + range[1] = 500; + range[2] = 500000; + + addMouseListener(this, true); + + // 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))); + //} + + backgroundColour = Colour(0,18,43); + + //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)); + + isPaused=false; + +} + +LfpDisplay::~LfpDisplay() +{ + deleteAllChildren(); +} + + + +int LfpDisplay::getNumChannels() +{ + return numChans; +} + + + +int LfpDisplay::getColorGrouping() +{ + return colorGrouping; +} + +void LfpDisplay::setColorGrouping(int i) +{ + colorGrouping=i; + setColors(); // so that channel colors get re-assigned + +} + + +void LfpDisplay::setNumChannels(int numChannels) +{ + numChans = numChannels; + + deleteAllChildren(); + + channels.clear(); + channelInfo.clear(); + + totalHeight = 0; + + for (int i = 0; i < numChans; i++) + { + + //std::cout << "Adding new display for channel " << i << std::endl; + + LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, this, i); + + //lfpChan->setColour(channelColours[i % channelColours.size()]); + lfpChan->setRange(range[canvas->getChannelType(i)]); + lfpChan->setChannelHeight(canvas->getChannelHeight()); + + addAndMakeVisible(lfpChan); + + channels.add(lfpChan); + + LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, this, i); + + //lfpInfo->setColour(channelColours[i % channelColours.size()]); + lfpInfo->setRange(range[canvas->getChannelType(i)]); + lfpInfo->setChannelHeight(canvas->getChannelHeight()); + + addAndMakeVisible(lfpInfo); + + channelInfo.add(lfpInfo); + + savedChannelState.add(true); + + totalHeight += lfpChan->getChannelHeight(); + + } + + setColors(); + + + //std::cout << "TOTAL HEIGHT = " << totalHeight << std::endl; + +} + +void LfpDisplay::setColors() +{ + for (int i = 0; i < numChans; i++) + { + + channels[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); + channelInfo[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); + } + +} + + +int LfpDisplay::getTotalHeight() +{ + return totalHeight; +} + +void LfpDisplay::resized() +{ + + //canvas->channelOverlapFactor + + int totalHeight = 0; + + for (int i = 0; i < channels.size(); i++) + { + + LfpChannelDisplay* disp = channels[i]; + + disp->setBounds(canvas->leftmargin, + totalHeight-(disp->getChannelOverlap()*canvas->channelOverlapFactor)/2, + getWidth(), + disp->getChannelHeight()+(disp->getChannelOverlap()*canvas->channelOverlapFactor)); + + disp-> resized(); + + LfpChannelDisplayInfo* info = channelInfo[i]; + + info->setBounds(0, + totalHeight-disp->getChannelHeight()/4, + canvas->leftmargin, + disp->getChannelHeight()); + + totalHeight += disp->getChannelHeight(); + + } + + canvas->fullredraw = true; //issue full redraw + if (singleChan != -1) + viewport->setViewPosition(Point<int>(0,singleChan*getChannelHeight())); + + + + lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); + + //inititalize black background + Graphics gLfpChannelBitmap(lfpChannelBitmap); + gLfpChannelBitmap.setColour(Colour(0,0,0)); //background color + gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); + + + canvas->fullredraw = true; + + refresh(); + // std::cout << "Total height: " << totalHeight << std::endl; + +} + +void LfpDisplay::paint(Graphics& g) +{ + + g.drawImageAt(lfpChannelBitmap, canvas->leftmargin,0); + +} + + +void LfpDisplay::refresh() +{ + // X-bounds of this update + int fillfrom = canvas->lastScreenBufferIndex[0]; + int fillto = (canvas->screenBufferIndex[0]); + + if (fillfrom<0){fillfrom=0;}; + if (fillto>lfpChannelBitmap.getWidth()){fillto=lfpChannelBitmap.getWidth();}; + + int topBorder = viewport->getViewPositionY(); + int bottomBorder = viewport->getViewHeight() + topBorder; + + // clear appropriate section of the bitmap -- + // we need to do this before each channel draws its new section of data into lfpChannelBitmap + Graphics gLfpChannelBitmap(lfpChannelBitmap); + gLfpChannelBitmap.setColour(backgroundColour); //background color + + if (canvas->fullredraw) + { + gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); + } else { + gLfpChannelBitmap.setColour(backgroundColour); //background color + + gLfpChannelBitmap.fillRect(fillfrom,0, (fillto-fillfrom)+1, getHeight()); + }; + + + for (int i = 0; i < numChans; i++) + { + + int componentTop = channels[i]->getY(); + int componentBottom = channels[i]->getHeight() + componentTop; + + if ((topBorder <= componentBottom && bottomBorder >= componentTop)) // only draw things that are visible + { + if (canvas->fullredraw) + { + channels[i]->fullredraw = true; + + channels[i]->pxPaint(); + channelInfo[i]->repaint(); + + } + else + { + channels[i]->pxPaint(); // draws to lfpChannelBitmap + + // it's not clear why, but apparently because the pxPaint() in a child component of LfpDisplay, we also need to issue repaint() calls for each channel, even though there's nothin to repaint there. Otherwise, the repaint call in LfpDisplay::refresh(), a few lines down, lags behind the update line by ~60 px. This could ahev something to do with teh reopaint message passing in juce. In any case, this seemingly redundant repaint here seems to fix the issue. + + // we redraw from 0 to +2 (px) relative to the real redraw window, the +1 draws the vertical update line + channels[i]->repaint(fillfrom, 0, (fillto-fillfrom)+2, channels[i]->getHeight()); + + + } + //std::cout << i << std::endl; + } + + } + + + if (canvas->fullredraw) + { + repaint(0,topBorder,getWidth(),bottomBorder-topBorder); + }else{ + //repaint(fillfrom, topBorder, (fillto-fillfrom)+1, bottomBorder-topBorder); // doesntb seem to be needed and results in duplicate repaint calls + } + + canvas->fullredraw = false; + +} + + + +void LfpDisplay::setRange(float r, ChannelType type) +{ + range[type] = r; + + for (int i = 0; i < numChans; i++) + { + if (channels[i]->getType() == type) + channels[i]->setRange(range[type]); + } + canvas->fullredraw = true; //issue full redraw +} + +int LfpDisplay::getRange() +{ + return getRange(canvas->getSelectedType()); +} + +int LfpDisplay::getRange(ChannelType type) +{ + for (int i=0; i < numChans; i++) + { + if (channels[i]->getType() == type) + return channels[i]->getRange(); + } + return 0; +} + + +void LfpDisplay::setChannelHeight(int r, bool resetSingle) +{ + + for (int i = 0; i < numChans; i++) + { + channels[i]->setChannelHeight(r); + channelInfo[i]->setChannelHeight(r); + } + if (resetSingle && singleChan != -1) + { + std::cout << "width " << getWidth() << " numchans " << numChans << " height " << getChannelHeight() << std::endl; + setSize(getWidth(),numChans*getChannelHeight()); + viewport->setScrollBarsShown(true,false); + viewport->setViewPosition(Point<int>(0,singleChan*r)); + singleChan = -1; + for (int n = 0; n < numChans; n++) + { + channelInfo[n]->setEnabledState(savedChannelState[n]); + } + } + + resized(); + +} + +void LfpDisplay::setInputInverted(bool isInverted) +{ + + for (int i = 0; i < numChans; i++) + { + channels[i]->setInputInverted(isInverted); + } + + resized(); + +} + +void LfpDisplay::setDrawMethod(bool isDrawMethod) +{ + for (int i = 0; i < numChans; i++) + { + channels[i]->setDrawMethod(isDrawMethod); + } + resized(); + +} + + +int LfpDisplay::getChannelHeight() +{ + return channels[0]->getChannelHeight(); +} + + + +void LfpDisplay::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) +{ + + //std::cout << "Mouse wheel " << e.mods.isCommandDown() << " " << wheel.deltaY << std::endl; + //TODO Changing ranges with the wheel is currently broken. With multiple ranges, most + //of the wheel range code needs updating + + + if (e.mods.isCommandDown()) // CTRL + scroll wheel -> change channel spacing + { + int h = getChannelHeight(); + int hdiff=0; + + // std::cout << wheel.deltaY << std::endl; + + if (wheel.deltaY > 0) + { + hdiff = 2; + } + else + { + if (h > 5) + hdiff = -2; + } + + if (abs(h) > 100) // accelerate scrolling for large ranges + hdiff *= 3; + + setChannelHeight(h+hdiff); + int oldX=viewport->getViewPositionX(); + int oldY=viewport->getViewPositionY(); + + setBounds(0,0,getWidth()-0, getChannelHeight()*canvas->nChans); // update height so that the scrollbar is correct + + int mouseY=e.getMouseDownY(); // should be y pos relative to inner viewport (0,0) + int scrollBy = (mouseY/h)*hdiff*2;// compensate for motion of point under current mouse position + viewport->setViewPosition(oldX,oldY+scrollBy); // set back to previous position plus offset + + canvas->setSpreadSelection(h+hdiff); // update combobox + + canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw + } + else + { + if (e.mods.isAltDown()) // ALT + scroll wheel -> change channel range (was SHIFT but that clamps wheel.deltaY to 0 on OSX for some reason..) + { + int h = getRange(); + + + int step = canvas->getRangeStep(canvas->getSelectedType()); + + // std::cout << wheel.deltaY << std::endl; + + if (wheel.deltaY > 0) + { + setRange(h+step,canvas->getSelectedType()); + } + else + { + if (h > step+1) + setRange(h-step,canvas->getSelectedType()); + } + + canvas->setRangeSelection(h); // update combobox + canvas->fullredraw = true; //issue full redraw - scrolling without modifier doesnt require a full redraw + + } + 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); + + } + + + } + //refresh(); // doesn't seem to be needed now that channels daraw to bitmap + +} + +void LfpDisplay::toggleSingleChannel(int chan) +{ + //std::cout << "Toggle channel " << chan << std::endl; + + if (chan != singleChan) + { + singleChan = chan; + int newHeight = viewport->getHeight(); + channelInfo[chan]->setEnabledState(true); + setChannelHeight(newHeight, false); + setSize(getWidth(), numChans*getChannelHeight()); + viewport->setScrollBarsShown(false,false); + viewport->setViewPosition(Point<int>(0,chan*newHeight)); + for (int n = 0; n < numChans; n++) + { + savedChannelState.set(n, channels[n]->getEnabledState()); + if (n != chan) channelInfo[n]->setEnabledState(false); + } + + } + else + { + setChannelHeight(canvas->getChannelHeight()); + } +} + +bool LfpDisplay::getSingleChannelState() +{ + if (singleChan < 0) return false; + else return true; +} + + +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 on 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; + } + } + + channels[closest]->select(); + canvas->setSelectedType(channels[closest]->getType()); + + if (event.getNumberOfClicks() == 2) + toggleSingleChannel(closest); + + if (event.mods.isRightButtonDown()) + { + PopupMenu channelMenu = channels[closest]->getOptions(); + const int result = channelMenu.show(); + channels[closest]->changeParameter(result); + } + + canvas->fullredraw = true;//issue full redraw + + refresh(); + +} + + +bool LfpDisplay::setEventDisplayState(int ch, bool state) +{ + eventDisplayEnabled[ch] = state; + return eventDisplayEnabled[ch]; +} + + +bool LfpDisplay::getEventDisplayState(int ch) +{ + return eventDisplayEnabled[ch]; +} + +void LfpDisplay::enableChannel(bool state, int chan) +{ + + if (chan < numChans) + { + channelInfo[chan]->setEnabledState(state); + canvas->isChannelEnabled.set(chan, state); + } +} + +void LfpDisplay::setEnabledState(bool state, int chan) +{ + + if (chan < numChans) + { + channels[chan]->setEnabledState(state); + canvas->isChannelEnabled.set(chan, state); + } +} + +bool LfpDisplay::getEnabledState(int chan) +{ + if (chan < numChans) + { + return channels[chan]->getEnabledState(); + } + + return false; +} + + +// ------------------------------------------------------------------ + +LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, int channelNumber) : + canvas(c), display(d), isSelected(false), chan(channelNumber), + channelOverlap(300), channelHeight(40), range(1000.0f), + isEnabled(true), inputInverted(false), canBeInverted(true), drawMethod(false) +{ + + + name = String(channelNumber+1); // default is to make the channelNumber the name + + + channelHeightFloat = (float) channelHeight; + + channelFont = Font("Default", channelHeight*0.6, Font::plain); + + lineColour = Colour(255,255,255); + + type = c->getChannelType(channelNumber); + typeStr = c->getTypeName(type); + + + +} + +LfpChannelDisplay::~LfpChannelDisplay() +{ + +} + +void LfpChannelDisplay::resized() +{ + // all of this will likely need to be moved into the sharedLfpDisplay image in the lfpDisplay, not here + // now that the complete height is know, the sharedLfpDisplay image that we'll draw the pixel-wise lfp plot to needs to be resized + //lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); +} + + +void LfpChannelDisplay::updateType() +{ + type = canvas->getChannelType(chan); + typeStr = canvas->getTypeName(type); +} + +void LfpChannelDisplay::setEnabledState(bool state) +{ + + //if (state) + //std::cout << "Setting channel " << name << " to true." << std::endl; + //else + //std::cout << "Setting channel " << name << " to false." << std::endl; + + isEnabled = state; + +} + +void LfpChannelDisplay::pxPaint() +{ + if (isEnabled) + { + Image::BitmapData bdLfpChannelBitmap(display->lfpChannelBitmap, 0,0, display->lfpChannelBitmap.getWidth(), display->lfpChannelBitmap.getHeight()); + + int center = getHeight()/2; + + // max and min of channel in absolute px coords for event displays etc - actual data might be drawn outside of this range + int jfrom_wholechannel= (int) (getY()+center-channelHeight/2)+1 +0 ; + int jto_wholechannel= (int) (getY()+center+channelHeight/2) -0; + + int jfrom_wholechannel_almost= (int) (getY()+center-channelHeight/3)+1 +0 ; // a bit less tall, for saturation warnings + int jto_wholechannel_almost= (int) (getY()+center+channelHeight/3) -0; + + + // max and min of channel, this is the range where actual data is drawn + int jfrom_wholechannel_clip= (int) (getY()+center-(channelHeight)*canvas->channelOverlapFactor)+1 ; + int jto_wholechannel_clip = (int) (getY()+center+(channelHeight)*canvas->channelOverlapFactor) -0; + + + + if (jfrom_wholechannel<0) {jfrom_wholechannel=0;}; + if (jto_wholechannel >= display->lfpChannelBitmap.getHeight()) {jto_wholechannel=display->lfpChannelBitmap.getHeight()-1;}; + + // draw most recent drawn sample position + if (canvas->screenBufferIndex[chan]+1 <= display->lfpChannelBitmap.getWidth()) + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k+=2) // draw line + bdLfpChannelBitmap.setPixelColour(canvas->screenBufferIndex[chan]+1,k, Colours::yellow); + + + bool clipWarningHi =false; // keep track if something clipped in the display, so we can draw warnings after the data pixels are done + bool clipWarningLo =false; + + bool saturateWarningHi =false; // similar, but for saturating the amplifier, not just the display - make this warning very visible + bool saturateWarningLo =false; + + // pre compute some colors for later so we dont do it once per pixel. + Colour lineColourBright = lineColour.withMultipliedBrightness(2.0f); + //Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f).withMultipliedBrightness(0.3f); + Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f*canvas->histogramParameterB).withMultipliedBrightness(canvas->histogramParameterB); + + + int stepSize = 1; + int from = 0; // for vertical line drawing in the LFP data + int to = 0; + + int ifrom = canvas->lastScreenBufferIndex[chan] - 1; // need to start drawing a bit before the actual redraw window for the interpolated line to join correctly + + if (ifrom < 0) + ifrom = 0; + + int ito = canvas->screenBufferIndex[chan] +0; + + if (fullredraw) + { + ifrom = 0; //canvas->leftmargin; + ito = getWidth()-stepSize; + fullredraw = false; + } + + + for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion + { + + //draw zero line + int m = getY()+center; + if(m>0 & m<display->lfpChannelBitmap.getHeight()){ + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i,m,Colour(50,50,50)); + } + } + + //draw range markers + if (isSelected) + { + m = getY()+center+channelHeight/2; + if(m>0 & m<display->lfpChannelBitmap.getHeight()){ + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i,m,Colour(80,80,80)); + } + } + m = getY()+center-channelHeight/2; + if(m>0 & m<display->lfpChannelBitmap.getHeight()){ + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i,m,Colour(80,80,80)); + } + } + } + + // draw event markers + int rawEventState = canvas->getYCoord(canvas->getNumChannels(), i);// get last channel+1 in buffer (represents events) + + //if (i == ifrom) + // std::cout << rawEventState << std::endl; + + 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 + { + //std::cout << "Drawing event." << std::endl; + Colour currentcolor=display->channelColours[ev_ch*2]; + + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++) // draw line + bdLfpChannelBitmap.setPixelColour(i,k,bdLfpChannelBitmap.getPixelColour(i,k).interpolatedWith(currentcolor,0.3f)); + + } + } + } + + //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl; + + + // set max-min range for plotting, used in all methods + double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat); + double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat); + + double a_raw = canvas->getYCoordMax(chan, i); + double b_raw = canvas->getYCoordMin(chan, i); + double from_raw=0; double to_raw=0; + + //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2; + if (a<b) + { + from = (a); to = (b); + from_raw = (a_raw); to_raw = (b_raw); + + } + else + { + from = (b); to = (a); + from_raw = (b_raw); to_raw = (a_raw); + } + + // start by clipping so that we're not populating pixels that we dont want to plot + int lm= channelHeightFloat*canvas->channelOverlapFactor; + if (lm>0) + lm=-lm; + + if (from > -lm) {from = -lm; clipWarningHi=true;}; + if (to > -lm) {to = -lm; clipWarningHi=true;}; + if (from < lm) {from = lm; clipWarningLo=true;}; + if (to < lm) {to = lm; clipWarningLo=true;}; + + + // test if raw data is clipped for displaying saturation warning + if (from_raw > canvas->selectedSaturationValueFloat) { saturateWarningHi=true;}; + if (to_raw > canvas->selectedSaturationValueFloat) { saturateWarningHi=true;}; + if (from_raw < -canvas->selectedSaturationValueFloat) { saturateWarningLo=true;}; + if (to_raw < -canvas->selectedSaturationValueFloat) { saturateWarningLo=true;}; + + + from=from+getHeight()/2; // so the plot is centered in the channeldisplay + to=to+getHeight()/2; + + int samplerange=to-from; + + + + if (drawMethod) // switched between 'supersampled' drawing and simple pixel wise drawing + { // histogram based supersampling method + + const float *samplesThisPixel = canvas->getSamplesPerPixel(chan, i); + int sampleCountThisPixel = canvas->getSampleCountPerPixel(i); + + if (samplerange>0 & sampleCountThisPixel>0) + { + + //float localHist[samplerange]; // simple histogram + float rangeHist[samplerange]; // paired range histogram, same as plotting at higher res. and subsampling + + for (int k=0; k<=samplerange; k++) + rangeHist[k]=0; + + + + + + for (int k=0; k<=sampleCountThisPixel; k++) // add up paired-range histogram per pixel - for each pair fill intermediate with uniform distr. + { + int cs_this = (((samplesThisPixel[k]/range*channelHeightFloat)+getHeight()/2)-from); // sample values -> pixel coordinates relative to from + int cs_next = (((samplesThisPixel[k+1]/range*channelHeightFloat)+getHeight()/2)-from); + + + if (cs_this<0) {cs_this=0;}; //here we could clip the diaplay to the max/min, or ignore out of bound values, not sure which one is better + if (cs_this>samplerange) {cs_this=samplerange;}; + if (cs_next<0) {cs_next=0;}; + if (cs_next>samplerange) {cs_next=samplerange;}; + + int hfrom=0; + int hto=0; + + if (cs_this<cs_next) + { + hfrom = (cs_this); hto = (cs_next); + } + else + { + hfrom = (cs_next); hto = (cs_this); + } + float hrange=hto-hfrom; + float ha=1; + for (int l=hfrom; l<hto; l++) + { + rangeHist[l]+=ha; //this emphasizes fast Y components + + //rangeHist[l]+=1/hrange; // this is like an oscilloscope, same energy depositetd per dx, not dy + } + } + + + for (int s = 0; s <= samplerange; s ++) // plot histogram one pixel per bin + { + float a=15*((rangeHist[s])/(sampleCountThisPixel)) *(2*(0.2+canvas->histogramParameterA)); + if (a>1.0f) {a=1.0f;}; + if (a<0.0f) {a=0.0f;}; + + + //Colour gradedColor = lineColour.withMultipliedBrightness(2.0f).interpolatedWith(lineColour.withMultipliedSaturation(0.6f).withMultipliedBrightness(0.3f),1-a) ; + Colour gradedColor = lineColourBright.interpolatedWith(lineColourDark,1-a); + //Colour gradedColor = Colour(0,255,0); + + int ploty = from+s+getY(); + if(ploty>0 & ploty < display->lfpChannelBitmap.getHeight()) { + bdLfpChannelBitmap.setPixelColour(i,from+s+getY(),gradedColor); + } + } + + } else { + + int ploty = from+getY(); + if(ploty>0 & ploty < display->lfpChannelBitmap.getHeight()) { + bdLfpChannelBitmap.setPixelColour(i,ploty,lineColour); + } + } + + + + } + else //drawmethod + { // simple per-pixel min-max drawing, has no anti-aliasing, but runs faster + + int jfrom=from+getY(); + int jto=to+getY(); + + //if (yofs<0) {yofs=0;}; + + if (i<0) {i=0;}; + if (i >= display->lfpChannelBitmap.getWidth()) {i = display->lfpChannelBitmap.getWidth()-1;}; // this shouldnt happen, there must be some bug above - to replicate, run at max refresh rate where draws overlap the right margin by a lot + + if (jfrom<0) {jfrom=0;}; + if (jto >= display->lfpChannelBitmap.getHeight()) {jto=display->lfpChannelBitmap.getHeight()-1;}; + + + for (int j = jfrom; j <= jto; j += 1) + { + + //uint8* const pu8Pixel = bdSharedLfpDisplay.getPixelPointer( (int)(i),(int)(j)); + //*(pu8Pixel) = 200; + //*(pu8Pixel+1) = 200; + //*(pu8Pixel+2) = 200; + + bdLfpChannelBitmap.setPixelColour(i,j,lineColour); + + } + + } + + // now draw warnings, if needed + if (canvas->drawClipWarning) // draw simple warning if display cuts off data + { + + if(clipWarningHi) { + for (int j=0; j<=3; j++) + { + int clipmarker = jto_wholechannel_clip; + + if(clipmarker>0 & clipmarker<display->lfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,clipmarker-j,Colour(255,255,255)); + } + } + } + + if(clipWarningLo) { + for (int j=0; j<=3; j++) + { + int clipmarker = jfrom_wholechannel_clip; + + if(clipmarker>0 & clipmarker<display->lfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,clipmarker+j,Colour(255,255,255)); + } + } + } + + clipWarningHi=false; + clipWarningLo=false; + } + + + if (canvas->drawSaturationWarning) // draw bigger warning if actual data gets cuts off + { + + if(saturateWarningHi || saturateWarningLo) { + + + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line + Colour thiscolour=Colour(255,0,0); + if (fmod((i+k),50)>25){ + thiscolour=Colour(255,255,255); + } + if(k>0 & k<display->lfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,k,thiscolour); + } + }; + } + + saturateWarningHi=false; // we likely just need one of this because for this warning we dont care if its saturating on the positive or negative side + saturateWarningLo=false; + } + + + } // for i (x pixels) + + } // isenabled + + +} + +void LfpChannelDisplay::paint(Graphics& g) {} + + + +PopupMenu LfpChannelDisplay::getOptions() +{ + + PopupMenu menu; + menu.addItem(1, "Invert signal", true, inputInverted); + + return menu; +} + +void LfpChannelDisplay::changeParameter(int id) +{ + switch (id) + { + case 1: + setInputInverted(!inputInverted); + default: + break; + } +} + +void LfpChannelDisplay::setRange(float r) +{ + + range = r; + + //std::cout << "Range: " << r << std::endl; +} + +int LfpChannelDisplay::getRange() +{ + return range; +} + + +void LfpChannelDisplay::select() +{ + isSelected = true; +} + +void LfpChannelDisplay::deselect() +{ + isSelected = false; +} + +bool LfpChannelDisplay::getSelected() +{ + return isSelected; +} + +void LfpChannelDisplay::setColour(Colour c) +{ + lineColour = c; +} + + +void LfpChannelDisplay::setChannelHeight(int c) +{ + channelHeight = c; + + channelHeightFloat = (float) channelHeight; + + if (!inputInverted) + channelHeightFloat = -channelHeightFloat; + + channelOverlap = channelHeight*2; +} + +int LfpChannelDisplay::getChannelHeight() +{ + + return channelHeight; +} + +void LfpChannelDisplay::setChannelOverlap(int overlap) +{ + channelOverlap = overlap; +} + + +int LfpChannelDisplay::getChannelOverlap() +{ + return channelOverlap; +} + +void LfpChannelDisplay::setCanBeInverted(bool _canBeInverted) +{ + canBeInverted = _canBeInverted; +} + +void LfpChannelDisplay::setInputInverted(bool isInverted) +{ + if (canBeInverted) + { + inputInverted = isInverted; + setChannelHeight(channelHeight); + } +} + +void LfpChannelDisplay::setDrawMethod(bool isDrawMethod) +{ + + drawMethod = isDrawMethod; + +} + + +void LfpChannelDisplay::setName(String name_) +{ + name = name_; +} + +ChannelType LfpChannelDisplay::getType() +{ + return type; +} + +// ------------------------------- + +LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, int ch) + : LfpChannelDisplay(canvas_, display_, ch) +{ + + chan = ch; + + enableButton = new UtilityButton(String(ch+1), Font("Small Text", 13, Font::plain)); + enableButton->setRadius(5.0f); + + enableButton->setEnabledState(true); + enableButton->setCorners(true, true, true, true); + enableButton->addListener(this); + enableButton->setClickingTogglesState(true); + enableButton->setToggleState(true, dontSendNotification); + + addAndMakeVisible(enableButton); + +} + +void LfpChannelDisplayInfo::updateType() +{ + type = canvas->getChannelType(chan); + typeStr = canvas->getTypeName(type); + repaint(); +} + +void LfpChannelDisplayInfo::buttonClicked(Button* button) +{ + + bool state = button->getToggleState(); + + display->setEnabledState(state, chan); + + //UtilityButton* b = (UtilityButton*) button; + + // if (state) + // { + // b->setLabel("ON"); + // } else { + // b->setLabel("OFF"); + // } + + //std::cout << "Turn channel " << chan << " to " << button->getToggleState() << std::endl; + +} + +void LfpChannelDisplayInfo::setEnabledState(bool state) +{ + enableButton->setToggleState(state, sendNotification); +} + +void LfpChannelDisplayInfo::paint(Graphics& g) +{ + + int center = getHeight()/2; + + g.setColour(lineColour); + + //if (chan > 98) + // g.fillRoundedRectangle(5,center-8,51,22,8.0f); + //else + g.fillRoundedRectangle(5,center-8,41,22,8.0f); + + g.setFont(Font("Small Text", 13, Font::plain)); + g.drawText(typeStr,5,center+16,41,10,Justification::centred,false); + // g.setFont(channelHeightFloat*0.3); + + // g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); + +} + +void LfpChannelDisplayInfo::resized() +{ + + int center = getHeight()/2; + + //if (chan > 98) + // enableButton->setBounds(8,center-5,45,16); + //else + enableButton->setBounds(8,center-5,35,16); +} + + + +// 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,6.0f); + } + + + //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false); + +} + +// Lfp Viewport ------------------------------------------- + +LfpViewport::LfpViewport(LfpDisplayCanvas *canvas) + : Viewport() +{ + this->canvas = canvas; +} + +void LfpViewport::visibleAreaChanged(const Rectangle<int>& newVisibleArea) +{ + canvas->fullredraw = true; + canvas->refresh(); +} diff --git a/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayCanvas.h b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayCanvas.h new file mode 100644 index 0000000000000000000000000000000000000000..2a27c8e6f57149c62f80390b441a9b7337da902d --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayCanvas.h @@ -0,0 +1,485 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ +#ifndef __LFPDISPLAYCANVAS_H_B711873A__ +#define __LFPDISPLAYCANVAS_H_B711873A__ + +#include "../../../JuceLibraryCode/JuceHeader.h" +#include "LfpDisplayNode.h" +#include "../../Processors/Visualization/Visualizer.h" +#define CHANNEL_TYPES 3 + +class LfpDisplayNode; + +class LfpTimescale; +class LfpDisplay; +class LfpChannelDisplay; +class LfpChannelDisplayInfo; +class EventDisplayInterface; +class LfpViewport; + +/** + + Displays multiple channels of continuous data. + + @see LfpDisplayNode, LfpDisplayEditor + +*/ + +class LfpDisplayCanvas : public Visualizer, + public Slider::Listener, + public ComboBox::Listener, + public Button::Listener, + public KeyListener +{ +public: + LfpDisplayCanvas(LfpDisplayNode* n); + ~LfpDisplayCanvas(); + + void beginAnimation(); + void endAnimation(); + + void refreshState(); + void update(); + + void setParameter(int, float); + void setParameter(int, int, int, float) {} + + void setRangeSelection(float range, bool canvasMustUpdate = false); // set range selection combo box to correct value if it has been changed by scolling etc. + void setSpreadSelection(int spread, bool canvasMustUpdate = false); // set spread selection combo box to correct value if it has been changed by scolling etc. + + void paint(Graphics& g); + + void refresh(); + + void resized(); + + int getChannelHeight(); + + float channelOverlapFactor; + + float histogramParameterA; + float histogramParameterB; + + int getNumChannels(); + bool getInputInvertedState(); + bool getDrawMethodState(); + + const float getXCoord(int chan, int samp); + const float getYCoord(int chan, int samp); + + const float *getSamplesPerPixel(int chan, int px); + const int getSampleCountPerPixel(int px); + + const float getYCoordMin(int chan, int samp); + const float getYCoordMean(int chan, int samp); + const float getYCoordMax(int chan, int samp); + + Array<int> screenBufferIndex; + Array<int> lastScreenBufferIndex; + + void comboBoxChanged(ComboBox* cb); + void buttonClicked(Button* button); + + /** Handles slider events for all editors. */ + void sliderValueChanged(Slider* sl); + + /** Called by sliderValueChanged(). Deals with clicks on custom sliders. Subclasses + of GenericEditor should modify this method only.*/ + void sliderEvent(Slider* sl); + + void saveVisualizerParameters(XmlElement* xml); + void loadVisualizerParameters(XmlElement* xml); + + bool keyPressed(const KeyPress& key); + bool keyPressed(const KeyPress& key, Component* orig); + + ChannelType getChannelType(int n); + ChannelType getSelectedType(); + String getTypeName(ChannelType type); + int getRangeStep(ChannelType type); + + void setSelectedType(ChannelType type, bool toggleButton = true); + + //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 each display; + static const int leftmargin=50; // left margin for lfp plots (so the ch number text doesnt overlap) + + Array<bool> isChannelEnabled; + + bool drawClipWarning; // optinally draw (subtle) warning if data is clipped in display + bool drawSaturationWarning; // optionally raise hell if the actual data is saturating + + float selectedSaturationValueFloat; // TODO: this is way ugly - we should refactor all these parameters soon and get them into a nicer format- probably when we do the genreal plugin parameter overhaul. + + + int nChans; + +private: + + Array<float> sampleRate; + float timebase; + float displayGain; + float timeOffset; + //int spread ; // vertical spacing between channels + + + static const int MAX_N_CHAN = 2048; // maximum number of channels + static const int MAX_N_SAMP = 5000; // maximum display size in pixels + static const int MAX_N_SAMP_PER_PIXEL = 1000; // maximum samples considered for drawing each pixel + //float waves[MAX_N_CHAN][MAX_N_SAMP*2]; // we need an x and y point for each sample + + LfpDisplayNode* processor; + AudioSampleBuffer* displayBuffer; // sample wise data buffer for display + AudioSampleBuffer* screenBuffer; // subsampled buffer- one int per pixel + + //'define 3 buffers for min mean and max for better plotting of spikes + // not pretty, but 'AudioSampleBuffer works only for channels X samples + AudioSampleBuffer* screenBufferMin; // like screenBuffer but holds min/mean/max values per pixel + AudioSampleBuffer* screenBufferMean; // like screenBuffer but holds min/mean/max values per pixel + AudioSampleBuffer* screenBufferMax; // like screenBuffer but holds min/mean/max values per pixel + + MidiBuffer* eventBuffer; + + ScopedPointer<LfpTimescale> timescale; + ScopedPointer<LfpDisplay> lfpDisplay; + ScopedPointer<LfpViewport> viewport; + + ScopedPointer<ComboBox> timebaseSelection; + ScopedPointer<ComboBox> rangeSelection; + ScopedPointer<ComboBox> spreadSelection; + + ScopedPointer<ComboBox> overlapSelection; + ScopedPointer<UtilityButton> drawClipWarningButton; // optinally draw (subtle) warning if data is clipped in display + + ScopedPointer<ComboBox> saturationWarningSelection; + ScopedPointer<UtilityButton> drawSaturateWarningButton; // optionally raise hell if the actual data is saturating + + ScopedPointer<ComboBox> colorGroupingSelection; + ScopedPointer<UtilityButton> invertInputButton; + ScopedPointer<UtilityButton> drawMethodButton; + ScopedPointer<UtilityButton> pauseButton; + OwnedArray<UtilityButton> typeButtons; + + + ScopedPointer<Slider> brightnessSliderA; + ScopedPointer<Slider> brightnessSliderB; + + ScopedPointer<Label> sliderALabel; + ScopedPointer<Label> sliderBLabel; + + StringArray voltageRanges[CHANNEL_TYPES]; + StringArray timebases; + StringArray spreads; // option for vertical spacing between channels + StringArray colorGroupings; // option for coloring every N channels the same + StringArray overlaps; // + StringArray saturationThresholds; //default values for when different amplifiers saturate + + + ChannelType selectedChannelType; + int selectedVoltageRange[CHANNEL_TYPES]; + String selectedVoltageRangeValues[CHANNEL_TYPES]; + float rangeGain[CHANNEL_TYPES]; + StringArray rangeUnits; + StringArray typeNames; + int rangeSteps[CHANNEL_TYPES]; + + int selectedSpread; + String selectedSpreadValue; + + int selectedTimebase; + String selectedTimebaseValue; + + int selectedOverlap; + String selectedOverlapValue; + + int selectedSaturation; // for saturation warning + String selectedSaturationValue; + + + OwnedArray<EventDisplayInterface> eventDisplayInterfaces; + + void refreshScreenBuffer(); + void updateScreenBuffer(); + + Array<int> displayBufferIndex; + int displayBufferSize; + + int scrollBarThickness; + + //float samplesPerPixel[MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]; + float*** samplesPerPixel; + int sampleCountPerPixel[MAX_N_SAMP]; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfpDisplayCanvas); + +}; + +class LfpTimescale : public Component +{ +public: + LfpTimescale(LfpDisplayCanvas*); + ~LfpTimescale(); + + void paint(Graphics& g); + + void setTimebase(float t); + +private: + + LfpDisplayCanvas* canvas; + + float timebase; + + Font font; + + StringArray labels; + +}; + +class LfpDisplay : public Component +{ +public: + LfpDisplay(LfpDisplayCanvas*, Viewport*); + ~LfpDisplay(); + + Image lfpChannelBitmap; // plot as bitmap instead of separately setting pixels + // this is done purely for the preformance improvement + + void setNumChannels(int numChannels); + int getNumChannels(); + + int getTotalHeight(); + + void paint(Graphics& g); + + void refresh(); + + void resized(); + + void mouseDown(const MouseEvent& event); + void mouseWheelMove(const MouseEvent& event, const MouseWheelDetails& wheel) ; + + + void setRange(float range, ChannelType type); + + //Withouth parameters returns selected type + int getRange(); + int getRange(ChannelType type); + + void setChannelHeight(int r, bool resetSingle = true); + int getChannelHeight(); + void setInputInverted(bool); + void setDrawMethod(bool); + + void setColors(); + + bool setEventDisplayState(int ch, bool state); + bool getEventDisplayState(int ch); + + int getColorGrouping(); + void setColorGrouping(int i); + + void setEnabledState(bool, int); + bool getEnabledState(int); + void enableChannel(bool, int); + + bool getSingleChannelState(); + + Colour backgroundColour; + + Array<Colour> channelColours; + + Array<LfpChannelDisplay*> channels; + Array<LfpChannelDisplayInfo*> channelInfo; + + bool eventDisplayEnabled[8]; + bool isPaused; // simple pause function, skips screen bufer updates + + +private: + + + void toggleSingleChannel(int chan); + int singleChan; + Array<bool> savedChannelState; + + int numChans; + + int totalHeight; + + int colorGrouping; + + LfpDisplayCanvas* canvas; + Viewport* viewport; + + float range[3]; + + +}; + +class LfpChannelDisplay : public Component +{ +public: + LfpChannelDisplay(LfpDisplayCanvas*, LfpDisplay*, int channelNumber); + ~LfpChannelDisplay(); + + void resized(); + + void paint(Graphics& g); + + void pxPaint(); // like paint, but just populate lfpChannelBitmap + // needs to avoid a paint(Graphics& g) mechanism here becauswe we need to clear the screen in the lfpDisplay repaint(), + // because otherwise we cant deal with the channel overlap (need to clear a vertical section first, _then_ all channels are dawn, so cant do it per channel) + + + void select(); + void deselect(); + + bool getSelected(); + + void setName(String); + + void setColour(Colour c); + + void setChannelHeight(int); + int getChannelHeight(); + + void setChannelOverlap(int); + int getChannelOverlap(); + + void setRange(float range); + int getRange(); + + void setInputInverted(bool); + void setCanBeInverted(bool); + + void setDrawMethod(bool); + + PopupMenu getOptions(); + void changeParameter(const int id); + + void setEnabledState(bool); + bool getEnabledState() + { + return isEnabled; + } + + ChannelType getType(); + void updateType(); + + bool fullredraw; // used to indicate that a full redraw is required. is set false after each full redraw + +protected: + + + LfpDisplayCanvas* canvas; + LfpDisplay* display; + + bool isSelected; + + int chan; + + String name; + + Font channelFont; + + Colour lineColour; + + int channelOverlap; + int channelHeight; + float channelHeightFloat; + + float range; + + bool isEnabled; + bool inputInverted; + bool canBeInverted; + bool drawMethod; + + ChannelType type; + String typeStr; + + + +}; + +class LfpChannelDisplayInfo : public LfpChannelDisplay, + public Button::Listener +{ +public: + LfpChannelDisplayInfo(LfpDisplayCanvas*, LfpDisplay*, int channelNumber); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + void resized(); + + void setEnabledState(bool); + void updateType(); + +private: + + ScopedPointer<UtilityButton> enableButton; + +}; + +class EventDisplayInterface : public Component, + public Button::Listener +{ +public: + EventDisplayInterface(LfpDisplay*, LfpDisplayCanvas*, int chNum); + ~EventDisplayInterface(); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + void checkEnabledState(); + + bool isEnabled; + +private: + + int channelNumber; + + LfpDisplay* display; + LfpDisplayCanvas* canvas; + + ScopedPointer<UtilityButton> chButton; + +}; + +class LfpViewport : public Viewport +{ +public: + LfpViewport(LfpDisplayCanvas* canvas); + void visibleAreaChanged(const Rectangle<int>& newVisibleArea); + +private: + LfpDisplayCanvas* canvas; +}; + + +#endif // __LFPDISPLAYCANVAS_H_B711873A__ diff --git a/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayEditor.cpp b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayEditor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cf75ba71b2915b523a32de968c977572cb8c947a --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayEditor.cpp @@ -0,0 +1,66 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "LfpDisplayEditor.h" + + +LfpDisplayEditor::LfpDisplayEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors=true) + : VisualizerEditor(parentNode, useDefaultParameterEditors) + +{ + + tabText = "LFP"; + + desiredWidth = 180; + +} + +LfpDisplayEditor::~LfpDisplayEditor() +{ +} + + +Visualizer* LfpDisplayEditor::createNewCanvas() +{ + + LfpDisplayNode* processor = (LfpDisplayNode*) getProcessor(); + return new LfpDisplayCanvas(processor); + +} + +void LfpDisplayEditor::buttonCallback(Button* button) +{ + + int gId = button->getRadioGroupId(); + + if (gId > 0) + { + if (canvas != 0) + { + canvas->setParameter(gId-1, button->getName().getFloatValue()); + } + + } + +} + diff --git a/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayEditor.h b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayEditor.h new file mode 100644 index 0000000000000000000000000000000000000000..41af1b9d6d150ab99853ec1c9c8c791a603ccf05 --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayEditor.h @@ -0,0 +1,63 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef __LFPDISPLAYEDITOR_H_3438800D__ +#define __LFPDISPLAYEDITOR_H_3438800D__ + +#include "../../../JuceLibraryCode/JuceHeader.h" +#include "../../Processors/Editors/GenericEditor.h" +#include "../../UI/UIComponent.h" +#include "../../UI/DataViewport.h" +#include "../../Processors/Visualization/DataWindow.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "../../Processors/Editors/VisualizerEditor.h" + +class Visualizer; + +/** + + User interface for the LfpDisplayNode sink. + + @see LfpDisplayNode, LfpDisplayCanvas + +*/ + +class LfpDisplayEditor : public VisualizerEditor +{ +public: + LfpDisplayEditor(GenericProcessor*, bool useDefaultParameterEditors); + ~LfpDisplayEditor(); + + void buttonCallback(Button* button); + + Visualizer* createNewCanvas(); + +private: + + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfpDisplayEditor); + +}; + +#endif // __LFPDISPLAYEDITOR_H_3438800D__ diff --git a/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayNode.cpp b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayNode.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2823f000d7616c786ac3dd1a07f9b65a675f4e2e --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayNode.cpp @@ -0,0 +1,347 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include <stdio.h> + +LfpDisplayNode::LfpDisplayNode() + : GenericProcessor("LFP Viewer Beta"), + displayGain(1), bufferLength(5.0f), + abstractFifo(100) +{ + //std::cout << " LFPDisplayNodeConstructor" << std::endl; + displayBuffer = new AudioSampleBuffer(8, 100); + + arrayOfOnes = new float[5000]; + + for (int n = 0; n < 5000; n++) + { + arrayOfOnes[n] = 1; + } + +} + +LfpDisplayNode::~LfpDisplayNode() +{ + +} + +AudioProcessorEditor* LfpDisplayNode::createEditor() +{ + + editor = new LfpDisplayEditor(this, true); + return editor; + +} + +void LfpDisplayNode::updateSettings() +{ + std::cout << "Setting num inputs on LfpDisplayNode to " << getNumInputs() << std::endl; + + channelForEventSource.clear(); + eventSourceNodes.clear(); + ttlState.clear(); + + for (int i = 0; i < eventChannels.size(); i++) + { + if (!eventSourceNodes.contains(eventChannels[i]->sourceNodeId) && eventChannels[i]->type == EVENT_CHANNEL) + { + eventSourceNodes.add(eventChannels[i]->sourceNodeId); + + } + }\ + + numEventChannels = eventSourceNodes.size(); + + std::cout << "Found " << numEventChannels << " event channels." << std::endl; + + for (int i = 0; i < eventSourceNodes.size(); i++) + { + std::cout << "Adding channel " << getNumInputs() + i << " for event source node " << eventSourceNodes[i] << std::endl; + channelForEventSource[eventSourceNodes[i]] = getNumInputs() + i; + ttlState[eventSourceNodes[i]] = 0; + Channel* eventChan = new Channel(this, getNumInputs() + i, EVENT_CHANNEL); + eventChan->sourceNodeId = eventSourceNodes[i]; + channels.add(eventChan); // add a channel for event data for each source node + } + + displayBufferIndex.clear(); + displayBufferIndex.insertMultiple(0, 0, getNumInputs() + numEventChannels); + +} + +bool LfpDisplayNode::resizeBuffer() +{ + int nSamples = (int) getSampleRate()*bufferLength; + int nInputs = getNumInputs(); + + std::cout << "Resizing buffer. Samples: " << nSamples << ", Inputs: " << nInputs << std::endl; + + if (nSamples > 0 && nInputs > 0) + { + abstractFifo.setTotalSize(nSamples); + displayBuffer->setSize(nInputs + numEventChannels, nSamples); // add extra channels for TTLs + return true; + } + else + { + return false; + } + +} + +bool LfpDisplayNode::enable() +{ + + if (resizeBuffer()) + { + LfpDisplayEditor* editor = (LfpDisplayEditor*) getEditor(); + editor->enable(); + return true; + } + else + { + return false; + } + +} + +bool LfpDisplayNode::disable() +{ + LfpDisplayEditor* editor = (LfpDisplayEditor*) getEditor(); + editor->disable(); + return true; +} + +void LfpDisplayNode::setParameter(int parameterIndex, float newValue) +{ + editor->updateParameterButtons(parameterIndex); + //Sets Parameter in parameters array for processor + Parameter* parameterPointer = parameters.getRawDataPointer(); + parameterPointer = parameterPointer+parameterIndex; + parameterPointer->setValue(newValue, currentChannel); + + //std::cout << "Saving Parameter from " << currentChannel << ", channel "; + + LfpDisplayEditor* ed = (LfpDisplayEditor*) getEditor(); + if (ed->canvas != 0) + ed->canvas->setParameter(parameterIndex, newValue); +} + +void LfpDisplayNode::handleEvent(int eventType, MidiMessage& event, int sampleNum) +{ + if (eventType == TTL) + { + const uint8* dataptr = event.getRawData(); + + //int eventNodeId = *(dataptr+1); + int eventId = *(dataptr+2); + int eventChannel = *(dataptr+3); + int eventTime = event.getTimeStamp(); + + int eventSourceNodeId = *(dataptr+5); + + int nSamples = numSamples.at(eventSourceNodeId); + + int samplesToFill = nSamples - eventTime; + + // std::cout << "Received event from " << eventSourceNode << ", channel " + // << eventChannel << ", with ID " << eventId << ", copying to " + // << channelForEventSource[eventSourceNode] << std::endl; + //// + int bufferIndex = (displayBufferIndex[channelForEventSource[eventSourceNodeId]] + eventTime - nSamples) % displayBuffer->getNumSamples(); + + bufferIndex = bufferIndex >= 0 ? bufferIndex : + displayBuffer->getNumSamples() + bufferIndex; + + + if (eventId == 1) + { + ttlState[eventSourceNodeId] |= (1L << eventChannel); + } + else + { + ttlState[eventSourceNodeId] &= ~(1L << eventChannel); + } + + if (samplesToFill + bufferIndex < displayBuffer->getNumSamples()) + { + + //std::cout << bufferIndex << " " << samplesToFill << " " << ttlState[eventSourceNode] << std::endl; + + displayBuffer->copyFrom(channelForEventSource[eventSourceNodeId], // destChannel + bufferIndex, // destStartSample + arrayOfOnes, // source + samplesToFill, // numSamples + float(ttlState[eventSourceNodeId])); // gain + } + else + { + + int block2Size = (samplesToFill + bufferIndex) % displayBuffer->getNumSamples(); + int block1Size = samplesToFill - block2Size; + + //std::cout << "OVERFLOW." << std::endl; + + //std::cout << bufferIndex << " " << block1Size << " " << ttlState << std::endl; + + displayBuffer->copyFrom(channelForEventSource[eventSourceNodeId], // destChannel + bufferIndex, // destStartSample + arrayOfOnes, // source + block1Size, // numSamples + float(ttlState[eventSourceNodeId])); // gain + + //std::cout << 0 << " " << block2Size << " " << ttlState << std::endl; + + displayBuffer->copyFrom(channelForEventSource[eventSourceNodeId], // destChannel + 0, // destStartSample + arrayOfOnes, // source + block2Size, // numSamples + float(ttlState[eventSourceNodeId])); // gain + + + } + + + // std::cout << "ttlState: " << ttlState << std::endl; + + // std::cout << "Received event from " << eventNodeId << + // " on channel " << eventChannel << + // " with value " << eventId << + // " at timestamp " << event.getTimeStamp() << std::endl; + + + } + +} + +void LfpDisplayNode::initializeEventChannels() +{ + + for (int i = 0; i < eventSourceNodes.size(); i++) + { + + int chan = channelForEventSource[eventSourceNodes[i]]; + int index = displayBufferIndex[chan]; + + //std::cout << "Event source node " << i << ", channel " << chan << std::endl; + + int samplesLeft = displayBuffer->getNumSamples() - index; + + int nSamples = numSamples.at(eventSourceNodes[i]); + + + + if (nSamples < samplesLeft) + { + + // std::cout << getNumInputs()+1 << " " << displayBufferIndex << " " << totalSamples << " " << ttlState << std::endl; + // + displayBuffer->copyFrom(chan, // destChannel + index, // destStartSample + arrayOfOnes, // source + nSamples, // numSamples + float(ttlState[eventSourceNodes[i]])); // gain + + displayBufferIndex.set(chan, index + nSamples); + } + else + { + + int extraSamples = nSamples - samplesLeft; + + // std::cout << "OVERFLOW." << std::endl; + // std::cout << bufferIndex << " " << block1Size << " " << ttlState << std::endl; + + displayBuffer->copyFrom(chan, // destChannel + index, // destStartSample + arrayOfOnes, // source + samplesLeft, // numSamples + float(ttlState[eventSourceNodes[i]])); // gain + // std::cout << 0 << " " << block2Size << " " << ttlState << std::endl; + + displayBuffer->copyFrom(chan, // destChannel + 0, // destStartSample + arrayOfOnes, // source + extraSamples, // numSamples + float(ttlState[eventSourceNodes[i]])); // gain + + displayBufferIndex.set(chan, extraSamples); + } + } +} + +void LfpDisplayNode::process(AudioSampleBuffer& buffer, MidiBuffer& events) +{ + // 1. place any new samples into the displayBuffer + //std::cout << "Display node sample count: " << nSamples << std::endl; ///buffer.getNumSamples() << std::endl; + + initializeEventChannels(); + + checkForEvents(events); // see if we got any TTL events + + ScopedLock displayLock(displayMutex); + + for (int chan = 0; chan < buffer.getNumChannels(); chan++) + { + int samplesLeft = displayBuffer->getNumSamples() - displayBufferIndex[chan]; + int nSamples = getNumSamples(chan); + + if (nSamples < samplesLeft) + { + + displayBuffer->copyFrom(chan, // destChannel + displayBufferIndex[chan], // destStartSample + buffer, // source + chan, // source channel + 0, // source start sample + nSamples); // numSamples + + displayBufferIndex.set(chan, displayBufferIndex[chan] + nSamples); + } + else + { + + int extraSamples = nSamples - samplesLeft; + + displayBuffer->copyFrom(chan, // destChannel + displayBufferIndex[chan], // destStartSample + buffer, // source + chan, // source channel + 0, // source start sample + samplesLeft); // numSamples + + displayBuffer->copyFrom(chan, + 0, + buffer, + chan, + samplesLeft, + extraSamples); + + displayBufferIndex.set(chan, extraSamples); + } + } + +} + diff --git a/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayNode.h b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayNode.h new file mode 100644 index 0000000000000000000000000000000000000000..16e7148064d75a229d290d6caf07b7103794ff16 --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/LfpDisplayNode.h @@ -0,0 +1,116 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef __LFPDISPLAYNODE_H_D969A379__ +#define __LFPDISPLAYNODE_H_D969A379__ + +#include "../../../JuceLibraryCode/JuceHeader.h" +#include "LfpDisplayEditor.h" +#include "../../Processors/Editors/VisualizerEditor.h" +#include "../../Processors/GenericProcessor/GenericProcessor.h" + +class DataViewport; + +/** + + Holds data in a displayBuffer to be used by the LfpDisplayCanvas + for rendering continuous data streams. + + @see GenericProcessor, LfpDisplayEditor, LfpDisplayCanvas + +*/ + +class LfpDisplayNode : public GenericProcessor + +{ +public: + + LfpDisplayNode(); + ~LfpDisplayNode(); + + AudioProcessorEditor* createEditor(); + + bool isSink() + { + return true; + } + + void process(AudioSampleBuffer& buffer, MidiBuffer& midiMessages); + + void setParameter(int, float); + + void updateSettings(); + + bool enable(); + bool disable(); + + void handleEvent(int, MidiMessage&, int); + + AudioSampleBuffer* getDisplayBufferAddress() + { + return displayBuffer; + } + int getDisplayBufferIndex(int chan) + { + return displayBufferIndex[chan]; + } + + CriticalSection* getMutex() + { + return &displayMutex; + } + +private: + + void initializeEventChannels(); + + ScopedPointer<AudioSampleBuffer> displayBuffer; + + Array<int> displayBufferIndex; + Array<int> eventSourceNodes; + std::map<int, int> channelForEventSource; + + int numEventChannels; + + float displayGain; // + float bufferLength; // s + + AbstractFifo abstractFifo; + + int64 bufferTimestamp; + std::map<int, int> ttlState; + float* arrayOfOnes; + int totalSamples; + + bool resizeBuffer(); + + CriticalSection displayMutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfpDisplayNode); + +}; + + + + +#endif // __LFPDISPLAYNODE_H_D969A379__ diff --git a/Source/Plugins/LfpDisplayNodeBeta/Makefile b/Source/Plugins/LfpDisplayNodeBeta/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7ea147feb7214c68e4ac0b8d490b5a6d7705ef7e --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/Makefile @@ -0,0 +1,39 @@ + +LIBNAME := $(notdir $(CURDIR)) +OBJDIR := $(OBJDIR)/$(LIBNAME) +TARGET := $(LIBNAME).so + + +SRC_DIR := ${shell find ./ -type d -print} +VPATH := $(SOURCE_DIRS) + +SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) +OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) + +BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) + +VPATH = $(SRC_DIR) + +.PHONY: objdir + +$(OUTDIR)/$(TARGET): objdir $(OBJ) + -@mkdir -p $(BINDIR) + -@mkdir -p $(LIBDIR) + -@mkdir -p $(OUTDIR) + @echo "Building $(TARGET)" + @$(BLDCMD) + +$(OBJDIR)/%.o : %.cpp + @echo "Compiling $<" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + + +objdir: + -@mkdir -p $(OBJDIR) + +clean: + @echo "Cleaning $(LIBNAME)" + -@rm -rf $(OBJDIR) + -@rm -f $(OUTDIR)/$(TARGET) + +-include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/LfpDisplayNodeBeta/OpenEphysLib.cpp b/Source/Plugins/LfpDisplayNodeBeta/OpenEphysLib.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9cc0b2f975fe7b215165d30ff7202987bde0a8f --- /dev/null +++ b/Source/Plugins/LfpDisplayNodeBeta/OpenEphysLib.cpp @@ -0,0 +1,70 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "../../Processors/PluginManager/OpenEphysPlugin.h" +#include "LfpDisplayNode.h" +#include <string> +#ifdef WIN32 +#include <Windows.h> +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +using namespace Plugin; +#define NUM_PLUGINS 1 + +extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) +{ + info->apiVersion = PLUGIN_API_VER; + info->name = "LFP viewer Beta"; + info->libVersion = 1; + info->numPlugins = NUM_PLUGINS; +} + +extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) +{ + switch (index) + { + case 0: + info->type = Plugin::ProcessorPlugin; + info->processor.name = "LFP Viewer Beta"; + info->processor.type = Plugin::SinkProcessor; + info->processor.creator = &(Plugin::createProcessor<LfpDisplayNode>); + break; + default: + return -1; + break; + } + return 0; +} + +#ifdef WIN32 +BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, + IN DWORD nReason, + IN LPVOID Reserved) +{ + return TRUE; +} + +#endif \ No newline at end of file