Skip to content
Snippets Groups Projects
LfpDisplayCanvas.cpp 142 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
    ------------------------------------------------------------------
    
    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>
    
    using namespace LfpDisplayNodeAlpha;
    
    
    #pragma mark - LfpDisplayCanvas -
    
    LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) :
         timebase(1.0f), displayGain(1.0f),   timeOffset(0.0f),
        processor(processor_)
    {
    
        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, lfpDisplay);
    
        options = new LfpDisplayOptions(this, timescale, lfpDisplay, processor);
    
        lfpDisplay->options = options;
    
        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));
    
        addAndMakeVisible(viewport);
        addAndMakeVisible(timescale);
        addAndMakeVisible(options);
    
        lfpDisplay->setNumChannels(nChans);
    
        resizeSamplesPerPixelBuffer(nChans);
    
        TopLevelWindow::getTopLevelWindow(0)->addKeyListener(this);
    
        optionsDrawerIsOpen = false;
    
    }
    
    LfpDisplayCanvas::~LfpDisplayCanvas()
    {
    
        // 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);
    
        samplesPerPixel.clear();
        
        TopLevelWindow::getTopLevelWindow(0)->removeKeyListener(this);
    }
    
    void LfpDisplayCanvas::resizeSamplesPerPixelBuffer(int numCh)
    {
        // allocate samplesPerPixel, behaves like float samplesPerPixel[nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]
        //samplesPerPixel = (float***)malloc(nChans * sizeof(float **));
    
        // 3D array: dimensions channels x samples x samples per pixel
        samplesPerPixel.clear();
        samplesPerPixel.resize(numCh);
    
        //for(int i = 0; i < numCh; i++)
        //{
            //std::vector< std::vector<float>> v1;
        //    samplesPerPixel[i].resize(MAX_N_SAMP);
            //samplesPerPixel.push_back(v1);
            //samplesPerPixel[i] = (float**)malloc(MAX_N_SAMP * sizeof(float*));
    
        //    for(int j = 0; j < MAX_N_SAMP; j++)
        //    {
                //std::vector<float> v2;
                //v2.resize(MAX_N_SAMP_PER_PIXEL);
        //        samplesPerPixel[i][j].resize(MAX_N_SAMP_PER_PIXEL);
        //        //samplesPerPixel[i][j] = (float*)malloc(MAX_N_SAMP_PER_PIXEL*sizeof(float));
        //    }
       //}
    }
    
    void LfpDisplayCanvas::toggleOptionsDrawer(bool isOpen)
    {
        optionsDrawerIsOpen = isOpen;
    
        auto viewportPosition = viewport->getViewPositionY();   // remember viewport position
    
        viewport->setViewPosition(0, viewportPosition);         // return viewport position
    
    }
    
    void LfpDisplayCanvas::resized()
    {
    
        timescale->setBounds(leftmargin,0,getWidth()-scrollBarThickness-leftmargin,30);
        viewport->setBounds(0,30,getWidth(),getHeight()-90);
    
    
        if (nChans > 0)
        {
            if (lfpDisplay->getSingleChannelState())
                lfpDisplay->setChannelHeight(viewport->getHeight(),false);
            
            lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*lfpDisplay->drawableChannels.size());
        }
        else
        {
            lfpDisplay->setBounds(0, 0, getWidth(), getHeight());
        }
    
    
        if (optionsDrawerIsOpen)
            options->setBounds(0, getHeight()-200, getWidth(), 200);
        else
            options->setBounds(0, getHeight()-55, getWidth(), 55);
    
    }
    
    void LfpDisplayCanvas::resizeToChannels(bool respectViewportPosition)
    {
        lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*lfpDisplay->drawableChannels.size());
        
        // if param is flagged, move the viewport scroll back to same relative position before
        // resize took place
        if (!respectViewportPosition) return;
        
        // get viewport scroll position as ratio against lfpDisplay's dims
        // so that we can set scrollbar back to rough position before resize
        // (else viewport scrolls back to top after resize)
        const double yPositionRatio = viewport->getViewPositionY() / (double)lfpDisplay->getHeight();
        const double xPositionRatio = viewport->getViewPositionX() / (double)lfpDisplay->getWidth();
        
        viewport->setViewPosition(lfpDisplay->getWidth() * xPositionRatio,
                                  lfpDisplay->getHeight() * yPositionRatio);
    }
    
    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(), 0);
    
    
        resizeSamplesPerPixelBuffer(nChans);
    
        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->getDataChannel(i)->getSampleRate());
    			else
    			{
    				//Since for now the canvas only supports one event channel, find the first TTL one and use that as sampleRate.
    				//This is a bit hackish and should be fixed for proper multi-ttl-channel support
    				for (int c = 0; c < processor->getTotalEventChannels(); c++)
    				{
    					if (processor->getEventChannel(c)->getChannelType() == EventChannel::TTL)
    					{
    						sampleRate.add(processor->getEventChannel(c)->getSampleRate());
    					}
    				}
    			}
    		}
    		else
    
            
           // 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->getDataChannel(i)->getName();
    
                //std::cout << chName << std::endl;
    
                lfpDisplay->channelInfo[i]->setName(chName);
                lfpDisplay->setEnabledState(isChannelEnabled[i], i);
    
            }
    
            
            if (nChans > 0)
                lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight());
            else
                lfpDisplay->setBounds(0, 0, getWidth(), getHeight());
            
    
            resized();
        }
        else
        {
            for (int i = 0; i < processor->getNumInputs(); i++)
            {
                lfpDisplay->channels[i]->updateType();
                lfpDisplay->channelInfo[i]->updateType();
            }
            
        }
    
        if (nChans > 0)
            lfpDisplay->rebuildDrawableChannelsList();
    
    
    }
    
    
    
    int LfpDisplayCanvas::getChannelHeight()
    {
        //return spreads[spreadSelection->getSelectedId()-1].getIntValue();
        return options->getChannelHeight();
        
    }
    
    
    void LfpDisplayCanvas::setParameter(int param, float val)
    {
        // not used for anything, since LfpDisplayCanvas is not a processor
    }
    
    
    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();
    
    }
    
    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 -- xxx 2do bug: this shouldnt happen because it makes the range/histogram display not work properly/look off for one pixel
            {
                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);
    
    kmichaelfox's avatar
    kmichaelfox committed
                        screenBufferMean->clear(channel, sbi, 1);
    
                        screenBufferMin->clear(channel, sbi, 1);
                        screenBufferMax->clear(channel, sbi, 1);
    
    
                        dbi %= displayBufferSize; // just to be sure
                        
                        // update continuous data channels
                        if (channel != nChans)
                        {
                            // 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;
    
    kmichaelfox's avatar
    kmichaelfox committed
                        float sample_mean  =  0;
    
                        
                        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++)
                        {
                            
    
    kmichaelfox's avatar
    kmichaelfox committed
                            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;
                            }
                           
                        }
                        
    
                        // update event channel
                        if (channel == nChans)
                        {
                            screenBuffer->setSample(channel, sbi, sample_max);
                        }
                        
    
                        // 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;
                                }
    
    kmichaelfox's avatar
    kmichaelfox committed
                                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);
    
                    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;
    }
    
    int LfpDisplayCanvas::getNumChannelsVisible()
    {
    
        return lfpDisplay->drawableChannels.size();
    
    int LfpDisplayCanvas::getChannelSubprocessorIdx(int channel)
    {
        return processor->getDataChannel(channel)->getSubProcessorIdx();
    }
    
    
    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);
    }
    
    std::array<float, MAX_N_SAMP_PER_PIXEL> LfpDisplayCanvas::getSamplesPerPixel(int chan, int px)
    {
        return samplesPerPixel[chan][px];
    }
    const int LfpDisplayCanvas::getSampleCountPerPixel(int px)
    {
        return sampleCountPerPixel[px];
    }
    
    float LfpDisplayCanvas::getMean(int chan)
    {
        float total = 0.0f;
        float numPts = 0;
    
        float sample = 0.0f;
        for (int samp = 0; samp < (lfpDisplay->getWidth() - leftmargin); samp += 10)
        {
            sample = *screenBuffer->getReadPointer(chan, samp);
            total += sample;
            numPts++;
        }
    
        //std::cout << sample << std::endl;
    
        return total / numPts;
    }
    
    float LfpDisplayCanvas::getStd(int chan)
    {
        float std = 0.0f;
    
        float mean = getMean(chan);
        float numPts = 1;
    
        for (int samp = 0; samp < (lfpDisplay->getWidth() - leftmargin); samp += 10)
        {
            std += pow((*screenBuffer->getReadPointer(chan, samp) - mean),2);
            numPts++;
        }
    
        return sqrt(std / numPts);
    
    }
    
    bool LfpDisplayCanvas::getInputInvertedState()
    {
        return options->getInputInvertedState(); //invertInputButton->getToggleState();
    }
    
    
    kmichaelfox's avatar
    kmichaelfox committed
    bool LfpDisplayCanvas::getDisplaySpikeRasterizerState()
    {
        return options->getDisplaySpikeRasterizerState();
    }
    
    
    bool LfpDisplayCanvas::getDrawMethodState()
    {
        
        return options->getDrawMethodState(); //drawMethodButton->getToggleState();
    }
    
    
    int LfpDisplayCanvas::getChannelSampleRate(int channel)
    {
        return sampleRate[channel];
    }
    
    
    void LfpDisplayCanvas::setDrawableSampleRate(float samplerate)
    {
    
    //    std::cout << "setting the drawable sample rate in the canvas" << std::endl;
    
        lfpDisplay->setDisplayedSampleRate(samplerate);
    }
    
    
    void LfpDisplayCanvas::setDrawableSubprocessor(int idx)
    {
        lfpDisplay->setDisplayedSubprocessor(idx);
    }
    
    
    void LfpDisplayCanvas::redraw()
    {
        fullredraw=true;
        repaint();
        refresh();
    }
    
    
    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);
    
    
    }
    
    void LfpDisplayCanvas::refresh()
    {
    
        updateScreenBuffer();
    
        lfpDisplay->refresh(); // redraws only the new part of the screen buffer
    
    }
    
    bool LfpDisplayCanvas::keyPressed(const KeyPress& key)
    {
        if (key.getKeyCode() == key.spaceKey)
        {
            options->togglePauseButton();
            
            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)
    {
    
        options->saveParameters(xml);
    }
    
    
    void LfpDisplayCanvas::loadVisualizerParameters(XmlElement* xml)
    {
        options->loadParameters(xml);
    
    }
    
    
    #pragma mark - ShowHideOptionsButton -
    // =============================================================
    
    
    ShowHideOptionsButton::ShowHideOptionsButton(LfpDisplayOptions* options) : Button("Button")
    {
        setClickingTogglesState(true);
    }
    ShowHideOptionsButton::~ShowHideOptionsButton()
    {
    
    }
    
    void ShowHideOptionsButton::paintButton(Graphics& g, bool, bool) 
    {   
        g.setColour(Colours::white);
    
        Path p;
    
        float h = getHeight();
        float w = getWidth();
    
        if (getToggleState())
        {
            p.addTriangle(0.5f*w, 0.2f*h,
                          0.2f*w, 0.8f*h,
                          0.8f*w, 0.8f*h);
        }
        else
        {
            p.addTriangle(0.8f*w, 0.8f*h,
                          0.2f*w, 0.5f*h,
                          0.8f*w, 0.2f*h);
        }
    
        PathStrokeType pst = PathStrokeType(1.0f, PathStrokeType::curved, PathStrokeType::rounded);
    
        g.strokePath(p, pst);
    }
    
    
    
    #pragma mark - LfpDisplayOptions -
    // -------------------------------------------------------------
    
    LfpDisplayOptions::LfpDisplayOptions(LfpDisplayCanvas* canvas_, LfpTimescale* timescale_, 
                                         LfpDisplay* lfpDisplay_, LfpDisplayNode* processor_)
        : canvas(canvas_),
          lfpDisplay(lfpDisplay_),
          timescale(timescale_),
          processor(processor_),
          selectedChannelType(DataChannel::HEADSTAGE_CHANNEL),
          labelFont("Default", 13.0f, Font::plain),
          labelColour(100, 100, 100)
    {
    
        // draw the colour scheme options
        // TODO: (kelly) this might be better as a modal window
        colourSchemeOptionLabel = new Label("colorSchemeOptionLabel", "Color Scheme");
        colourSchemeOptionLabel->setFont(labelFont);
        colourSchemeOptionLabel->setColour(Label::textColourId, labelColour);
        addAndMakeVisible(colourSchemeOptionLabel);
        
        StringArray colourSchemeNames = lfpDisplay->getColourSchemeNameArray();
        colourSchemeOptionSelection = new ComboBox("colorSchemeOptionSelection");
        colourSchemeOptionSelection->addItemList(colourSchemeNames, 1);
        colourSchemeOptionSelection->setEditableText(false);
        colourSchemeOptionSelection->addListener(this);
        colourSchemeOptionSelection->setSelectedId(1, dontSendNotification);
        addAndMakeVisible(colourSchemeOptionSelection);
        
        if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements())
            addAndMakeVisible(lfpDisplay->getColourSchemePtr());
        
    
     //Ranges for neural data
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("25");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("50");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("100");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("250");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("400");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("500");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("750");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("1000");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("2000");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("5000");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("10000");
    	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("15000");
    	selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] = 8;
    	rangeGain[DataChannel::HEADSTAGE_CHANNEL] = 1; //uV
    	rangeSteps[DataChannel::HEADSTAGE_CHANNEL] = 10;
        rangeUnits.add("uV");
        typeNames.add("DATA");
    
        UtilityButton* tbut;
        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[DataChannel::AUX_CHANNEL].add("25");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("50");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("100");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("250");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("400");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("500");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("750");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("1000");
    	voltageRanges[DataChannel::AUX_CHANNEL].add("2000");
        //voltageRanges[DataChannel::AUX_CHANNEL].add("5000");
    	selectedVoltageRange[DataChannel::AUX_CHANNEL] = 9;
    	rangeGain[DataChannel::AUX_CHANNEL] = 0.001; //mV
    	rangeSteps[DataChannel::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[DataChannel::ADC_CHANNEL].add("0.01");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("0.05");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("0.1");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("0.5");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("1.0");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("2.0");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("5.0");
    	 voltageRanges[DataChannel::ADC_CHANNEL].add("10.0");
    	 selectedVoltageRange[DataChannel::ADC_CHANNEL] = 8;
    	 rangeGain[DataChannel::ADC_CHANNEL] = 1; //V
    	 rangeSteps[DataChannel::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[DataChannel::HEADSTAGE_CHANNEL] = voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1];
    	selectedVoltageRangeValues[DataChannel::AUX_CHANNEL] = voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1];
    	selectedVoltageRangeValues[DataChannel::ADC_CHANNEL] = voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1];
        
    
        channelDisplaySkipOptions.add("All");
    
        channelDisplaySkipOptions.add("2");
        channelDisplaySkipOptions.add("4");
        channelDisplaySkipOptions.add("8");
        channelDisplaySkipOptions.add("16");
        channelDisplaySkipOptions.add("32");
    
        channelDisplaySkipOptions.add("64");
    
        selectedChannelDisplaySkip = 1;
        selectedChannelDisplaySkipValue = channelDisplaySkipOptions[selectedChannelDisplaySkip - 1];
        
        channelDisplaySkipSelection = new ComboBox("Channel Skip");
        channelDisplaySkipSelection->addItemList(channelDisplaySkipOptions, 1);
        channelDisplaySkipSelection->setSelectedId(selectedChannelDisplaySkip, sendNotification);
        channelDisplaySkipSelection->setEditableText(false);
        channelDisplaySkipSelection->addListener(this);
        addAndMakeVisible(channelDisplaySkipSelection);
        
        channelDisplaySkipLabel = new Label("Channel Display Skip", "Ch. Skip");
        channelDisplaySkipLabel->setFont(labelFont);
        channelDisplaySkipLabel->setColour(Label::textColourId, labelColour);
        addAndMakeVisible(channelDisplaySkipLabel);
    
    kmichaelfox's avatar
    kmichaelfox committed
        // init spike raster options
        spikeRasterSelectionOptions = {"Off", "-50", "-100", "-150", "-200", "-300", "-400", "-500"};
        selectedSpikeRasterThreshold = 1;
        selectedSpikeRasterThresholdValue = spikeRasterSelectionOptions[selectedSpikeRasterThreshold - 1];
        
        spikeRasterSelection = new ComboBox("spikeRasterSelection");
        spikeRasterSelection->addItemList(spikeRasterSelectionOptions, 1);
        spikeRasterSelection->setSelectedId(selectedSpikeRasterThreshold, dontSendNotification);
        spikeRasterSelection->setEditableText(true);
        spikeRasterSelection->addListener(this);
        addAndMakeVisible(spikeRasterSelection);
        
        spikeRasterLabel = new Label("spikeRasterLabel", "Spike Raster Thresh.");
        spikeRasterLabel->setFont(labelFont);
        spikeRasterLabel->setColour(Label::textColourId, labelColour);
        addAndMakeVisible(spikeRasterLabel);
    
        
        
        
        // init median offset plotting
        medianOffsetPlottingLabel = new Label("Median Offset Correction", "Median Offset Correction");
        medianOffsetPlottingLabel->setFont(labelFont);
        medianOffsetPlottingLabel->setColour(Label::textColourId, labelColour);
        addAndMakeVisible(medianOffsetPlottingLabel);
        
        medianOffsetPlottingButton = new UtilityButton("0", labelFont);
        medianOffsetPlottingButton->setRadius(5.0f);
        medianOffsetPlottingButton->setEnabledState(true);
        medianOffsetPlottingButton->setCorners(true, true, true, true);
        medianOffsetPlottingButton->addListener(this);
        medianOffsetPlottingButton->setClickingTogglesState(true);
        medianOffsetPlottingButton->setToggleState(false, sendNotification);
        addAndMakeVisible(medianOffsetPlottingButton);
    
        // init show/hide options button
        showHideOptionsButton = new ShowHideOptionsButton(this);
        showHideOptionsButton->addListener(this);
        addAndMakeVisible(showHideOptionsButton);
    
        // init timebases options
        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[DataChannel::HEADSTAGE_CHANNEL], 1);
    	rangeSelection->setSelectedId(selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL], sendNotification);
        rangeSelection->setEditableText(true);
        rangeSelection->addListener(this);
        addAndMakeVisible(rangeSelection);