#include "RackExtension.h"
#include "Constants.h"
#include <cmath>
#include <stdlib.h>


#include "OmRack.h"

#include "arecibo.h"

#define GUITAR_E 40
#define GUITAR_A 45
#define GUITAR_D 50
#define GUITAR_G 55
#define GUITAR_B 59
#define GUITAR_E2 64


static void *mallocClear(u32 n)
{
    void *m = malloc(n);
    char *mc = (char *)m;
    for(u32 i = 0; i < n; i++)
        mc[i] = 0;
    return m;
}

CRackExtension::CRackExtension()
{
    this->init = 0;
    
    this->audIn = (f32 *)mallocClear(sizeof(f32) * AUDIO_HISTORY_SAMPLES);
    this->fftMem = (f32 *)mallocClear(sizeof(f32) * FFT_MAX_SIZE);

    this->om = new Om();
    this->lamps.init(this->om, FIRST_LAMP, FIRST_REAR_LAMP);
    this->foldedLamps.init(this->om, FIRST_FOLDED_LAMP, FIRST_FOLDED_REAR_LAMP);
    
    this->guitarRows[5] = GUITAR_E;
    this->guitarRows[4] = GUITAR_A;
    this->guitarRows[3] = GUITAR_D;
    this->guitarRows[2] = GUITAR_G;
    this->guitarRows[1] = GUITAR_B;
    this->guitarRows[0] = GUITAR_E2;
}

#define HUE_FROM_PITCH(_p) (_p % 11 % HUESTEPS)

void CRackExtension::mode0Hues()
{
    for(TJBox_UInt32 p = 0; p < 128; p++)
    {
        u32 hue = HUE_FROM_PITCH(p);
        u32 tag = p - FIRST_LAMP_PITCH + FIRST_LAMP;
        this->lamps.set(tag, hue, 0);
    }
    this->init = 1;
}

void CRackExtension::RenderBatch(const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount)
{
    
    this->batchTicker++;
    
    this->audioRenderTicks = this->om->cpuMiser ? 128 : 32;

    om->doDiffs(iPropertyDiffs, iDiffCount);
    
    {
        // handle cv changes
        int isAnyCvConnected = this->om->isGateConnected() || this->om->isPitchConnected();
        if(isAnyCvConnected != this->wasAnyCvConnected)
        {
            if(!isAnyCvConnected) // then off the most recent
                this->om->addNoteChange(this->lastPitchCv, 0);
            this->wasAnyCvConnected = isAnyCvConnected;
        }
        
        if(isAnyCvConnected)
        {
            f32 gate = this->om->getGate();
            u32 pitch = 127.0 * this->om->getPitch();
            if(pitch != this->lastPitchCv)
                this->om->addNoteChange(this->lastPitchCv, 0);
            if(gate != this->lastGateCv)
                this->om->addNoteChange(pitch, gate);
            this->lastGateCv = gate;
            this->lastPitchCv = pitch;
        }
    }
    
    {
        // maybe get audio, maybe do pass through
        this->audInConnected = this->om->isAudioInConnected();
        this->audOutConnected = this->om->isAudioOutConnected();
        
        this->gotAudIn = false;
        if(this->audInConnected && this->audOutConnected)
        {
            this->maybeGetAud();
            // pass through only if audio exceeds silence threshold. + only, sorry!
            bool anyAudio = false;
            f32 *w = this->audIn + this->audInPointer;
            for(u32 i = 0; i < kBatchSize; i++)
            {
                if(*w > kJBox_SilentThreshold || *w < -kJBox_SilentThreshold)
                {
                    anyAudio = true;
                    break;
                }
                w++;
            }
            if(anyAudio)
                this->om->setAudioOut(this->audIn + this->audInPointer);
        }
    }

#define lownote 36
#define highnote 107

#define firstLamp M_036_PROPERTY_TAG
#define lastLamp M_107_PROPERTY_TAG
    
    if(this->init == 0)
        this->mode0Hues();

    int mode = om->mode_0 + om->mode_1 * 2 + om->mode_2 * 4;
    if(mode != this->lastMode)
        this->modeChanged(mode);
    
    this->mainHue = floor(this->om->getColor());
    
    switch(mode)
    {
        case 0:
            this->doPattern0();
            break;
        case 1:
            this->doPattern1();
            break;
        case 2:
            this->doPattern010BigLamp();
            break;
        case 3:
            this->doPattern3();
            break;
            
        case 4:
            this->doAud0();
            break;
        case 5:
            this->doAud1();
            break;
        case 6:
            this->doAud2();
            break;
        case 7:
            this->doAud3();
            break;
    }
    if((this->batchTicker & 0x7) == 0)
    {
        this->lamps.glide();
        this->foldedLamps.glide();
    }
    
        // crash if om got set to zero...
    this->audioRenderTicks = this->om->cpuMiser ? 128 : 32;

}

void CRackExtension::modeChanged(int newMode)
{
    this->lamps.darkenAll(100);
    if(newMode == 0)
        this->mode0Hues();
    this->lastMode = newMode;
}

#define ROWS 6
#define COLUMNS 12
#define LAMPS (ROWS * COLUMNS)
#define FOLDED_LAMPS (LAST_FOLDED_LAMP - FIRST_FOLDED_LAMP + 1)


void CRackExtension::doPattern0()
{
    if(this->om->noteVels[2])
    {
        this->doArecibo();
        return;
    }
    if(this->wasArecibo)
    {
        this->mode0Hues(); // reset, thx
        this->wasArecibo = 0;
    }
    for(TJBox_UInt32 i = 0; i < om->noteChangeCount; i++)
    {
        OmNote *note = om->noteChanges + i;
        TJBox_Float32 v = note->vel;
        TJBox_UInt32 p = note->pitch;
        // which frame?
        u32 tag = p - FIRST_LAMP_PITCH + FIRST_LAMP;
        
        if(v)
        {
            u32 hue = HUE_FROM_PITCH(p);
            this->lamps.set(tag, hue, BRIGHTSTEPS - 1);
            this->foldedLamps.setByRef(p, hue, BRIGHTSTEPS - 1);
        }
        else
        {
            this->lamps.glideTo(tag, 0, 150);
            this->foldedLamps.glideToByRef(p, 0, 150);
        }
    }
}

void CRackExtension::doArecibo()
{
    s32 ix = FIRST_LAMP;

    u32 batchesPerBeat = om->getSystemSampleRate()/ om->getFilteredTempo() * 60  / kBatchSize / 4;
    this->pattern1BatchCounter++;
    if(this->pattern1BatchCounter >= batchesPerBeat)
    {
        this->pattern1BatchCounter = 0;
        this->areciboY++;
        if(this->areciboY > ARECIBO_HEIGHT - ROWS)
        {
            this->areciboY = 0;
            this->areciboX++;
            if(this->areciboX > ARECIBO_WIDTH - COLUMNS)
                this->areciboX = 0;
        }
    }

    for(s32 y = 0; y < ROWS; y++)
        for(s32 x = 0; x < COLUMNS; x++)
        {
            s32 a = areciboData[(y + this->areciboY) * ARECIBO_WIDTH + (x + this->areciboX)];
            s32 hue = a & 0x0f;
            s32 brightness = (a & 0xf0) >> 4;
//            this->lamps.set(ix, hue, brightness);
            if(brightness)
                this->lamps.set(ix, hue, brightness);
            else
                this->lamps.glideTo(ix, brightness,120);
            ix++;
        }
    
    this->wasArecibo = 1;
}

/*
 off
   PLAY --> played1

 played1
   STOP --> hold1
   TICK --> played2
 
 hold1 -->
   PLAY --> played1
   TICK --> off
 
 played2
   PLAY --> played1
   STOP --> off
 */

typedef enum { OFF = 0, PLAYED1, HOLD1, PLAYED2 };


void CRackExtension::doPattern1()
{

    for(TJBox_UInt32 i = 0; i < om->noteChangeCount; i++)
    {
        OmNote *note = om->noteChanges + i;
        TJBox_Float32 v = note->vel;
        TJBox_UInt32 p = note->pitch;
        // which frame?
        u32 row = (p % 6);
        u32 tag = row * 12 + 11 + FIRST_LAMP;
        
        if(v)
        {
            rowCaught[row] = PLAYED1;
            u32 hue = HUE_FROM_PITCH(p);
            this->lamps.set(tag, hue, BRIGHTSTEPS - 1);
            this->foldedLamps.setByRef(p, hue, BRIGHTSTEPS - 1);
        }
        else
        {
            this->foldedLamps.glideToByRef(p, 0, 150);

            if(rowCaught[row] == PLAYED1)
                rowCaught[row] = HOLD1;
            else if(rowCaught[row] == PLAYED2)
            {
                rowCaught[row] = OFF;
            
                this->lamps.glideTo(tag, 0, 150);
            }
        }
    }
    
    // samples/second * seconds/minute * minutes/beat * 1batch/64 samples = batches/beat
    
    u32 batchesPerBeat = om->getSystemSampleRate()/ om->getFilteredTempo() * 60  / kBatchSize / 4;
    this->pattern1BatchCounter++;
    if(this->pattern1BatchCounter >= batchesPerBeat)
    {
        this->pattern1BatchCounter = 0;
        for(u32 row = 0; row < 6; row++)
        {
            u32 rowTag = FIRST_LAMP + row * 12;
            for(u32 ix = rowTag; ix < rowTag + 11; ix++)
                this->lamps.copy(ix,ix+1);
            if(rowCaught[row] == PLAYED1)
                rowCaught[row] = PLAYED2;
            else if (rowCaught[row] == HOLD1)
            {
                rowCaught[row] = OFF;
                this->lamps.glideTo(rowTag + 11,0,250);
            }
        }
        
    }

    
}


void CRackExtension::doFoldedLampNotes()
{
    for(TJBox_UInt32 i = 0; i < om->noteChangeCount; i++)
    {
        OmNote *note = om->noteChanges + i;
        TJBox_Float32 v = note->vel;
        TJBox_UInt32 p = note->pitch;

        if(v)
        {
            u32 hue = HUE_FROM_PITCH(p);
            this->foldedLamps.setByRef(p, hue, BRIGHTSTEPS - 1);
        }
        else
        {
            this->foldedLamps.glideToByRef(p, 0, 150);
        }
    }
}


void CRackExtension::doPattern010BigLamp()
{
    this->doFoldedLampNotes();

    // big lamp.
    for(u32 i = 0; i < om->noteChangeCount; i++)
    {
        OmNote *note = om->noteChanges + i;
        TJBox_Float32 v = note->vel;
        u32 bigLampPitch = note->pitch;
        // which frame?
        
        bool bigLampNoteOn = v >= (1.0 / 127.0);
        
        if(bigLampNoteOn)
        {
            if ((!this->pattern010WasNoteOn) || (bigLampPitch != this->pattern010WasPitch))
            {
                u32 hue = HUE_FROM_PITCH(bigLampPitch);
                for(u32 tag = FIRST_LAMP; tag <= LAST_LAMP; tag++)
                {
                    this->lamps.set(tag, hue, BRIGHTSTEPS - 1);
                }
                this->pattern010WasPitch = bigLampPitch; // it only "was" this pitch if we blink it up now.
                this->pattern010WasNoteOn = bigLampNoteOn;
            }
        }
        else
        {
            if(this->pattern010WasNoteOn && (bigLampPitch == this->pattern010WasPitch))
            {
                for(u32 tag = FIRST_LAMP; tag <= LAST_LAMP; tag++)
                {
                    this->lamps.glideTo(tag, 0,150);
                }
            }
            this->pattern010WasNoteOn = bigLampNoteOn;
        }
    }
}

void CRackExtension::doPattern3()
{

    for(TJBox_UInt32 i = 0; i < om->noteChangeCount; i++)
    {
        OmNote *note = om->noteChanges + i;
        TJBox_Float32 v = note->vel;
        TJBox_UInt32 p = note->pitch;

        u32 hue;
        if(v)
        {
            hue = HUE_FROM_PITCH(p);
            this->foldedLamps.setByRef(p, hue, BRIGHTSTEPS - 1);
        }
        else
            this->foldedLamps.glideToByRef(p, 0, 150);

        
        for(int row = 0; row < 6; row++)
        {
            u32 rowStart = this->guitarRows[row];
            if(p >= rowStart && p < rowStart + 12)
            {
                u32 tag = FIRST_LAMP + row * 12 + p - rowStart;
                if(v)
                    this->lamps.set(tag, hue, BRIGHTSTEPS - 1);
                else
                    this->lamps.glideTo(tag, 0,150);
            }
        }
        
    }

}

void CRackExtension::maybeGetAud()
{
    if(!this->gotAudIn)
    {
        this->audInPointer = (this->audInPointer + kBatchSize) % AUDIO_HISTORY_SAMPLES;
        this->om->getAudioIn(this->audIn + this->audInPointer);
        this->gotAudIn = true;
    }
}

/**
 * Map integer range with inclusive bounds
 */
static s32 mapRangeI(s32 x,s32 inLow,s32 inHigh,s32 outLow,s32 outHigh);
static s32 mapRangeI(s32 x,s32 inLow,s32 inHigh,s32 outLow,s32 outHigh)
{
    f32 inRange = inHigh - inLow + 1;
    f32 outRange = outHigh - outLow + 1;
    s32 result = floor((x - inLow) * outRange / inRange) + outLow;
    return result;
}

static f32 mapRange(f32 x,f32 inLow,f32 inHigh,f32 outLow,f32 outHigh);
static f32 mapRange(f32 x,f32 inLow,f32 inHigh,f32 outLow,f32 outHigh)
{
    f32 inRange = inHigh - inLow;
    f32 outRange = outHigh - outLow;
    f32 result = (x - inLow) * outRange / inRange + outLow;
    return result;
}

f32 max(f32 a,f32 b);
f32 max(f32 a,f32 b)
{
    if(a > b)
        return a;
    else
        return b;
}



#define OFFSET_WITH_MOD(_value,_offset,_mod) ((_value + _offset + _mod + _mod) % _mod)
#define INCR_WITH_MOD(_value,_mod) ((_value + 1) % _mod)

static s32 scanForLowest(TJBox_AudioSample *samples,s32 firstSample,u32 samplesToScanBackForSync)
{
    if(samplesToScanBackForSync)
    {
        s32 w = OFFSET_WITH_MOD(firstSample, -samplesToScanBackForSync, AUDIO_HISTORY_SAMPLES);
        s32 lowest = w;
        f32 lowestValue = 1000;
        while(w != firstSample)
        {
            f32 v = samples[w];
            if(v < lowestValue)
            {
                lowestValue = v;
                lowest = w;
            }
            w = INCR_WITH_MOD(w, AUDIO_HISTORY_SAMPLES);
        }
        firstSample = lowest;
    }
    return firstSample;
}



void CRackExtension::doAud1()
{
    // usually do nothing.
    if(this->batchTicker % this->audioRenderTicks)
        return;

    if(!this->om->isAudioInConnected())
        return;
    
    for(u32 i = FIRST_LAMP; i <= LAST_LAMP; i++)
        this->lamps.set(i,0,0);
    for(u32 i = FIRST_FOLDED_LAMP; i <= LAST_FOLDED_LAMP; i++)
        this->foldedLamps.set(i,0,0);
    
    this->maybeGetAud();
    
    s32 foldedLampOffset = 30;
    s32 blipCount = 20;
    f32 skipper = this->om->scanRate * 200.0 + 1.0;
    s32 firstSample = this->audInPointer + kBatchSize - 1;
    
    if(this->om->waveSync)
        firstSample = scanForLowest(this->audIn, firstSample, AUDIO_HISTORY_SAMPLES / 10);

    for(s32 i = 0; i < blipCount; i++)
    {
        f32 skip = -i * skipper;
        s32 sampleIndex = OFFSET_WITH_MOD(firstSample,((s32)skip), AUDIO_HISTORY_SAMPLES);
        f32 sample = this->audIn[sampleIndex];
        if(sample < kJBox_SilentThreshold && sample > -kJBox_SilentThreshold)
            continue;

        s32 a = mapRange(sample,-.9,.9,0    ,LAMPS);
        if(a >= 0 && a < LAMPS)
        {
            u32 hue = (this->mainHue + i % 3) % HUESTEPS;
            this->lamps.set(FIRST_LAMP + a,hue,BRIGHTSTEPS - 1);
            this->foldedLamps.set(FIRST_FOLDED_LAMP + a - foldedLampOffset,hue,BRIGHTSTEPS - 1);
        }
        
    }
}

static void graphIntoBuckets(TJBox_AudioSample *samples,
                      s32 firstSample,
                      u32 samplesToGraph,
                      u32 samplesToScanBackForSync,
                      u32 colorOr,
                      f32 *buckets,
                      u32 *bucketColorsRepresented,
                      f32 *bucketColumnHits,
                      f32 *bucketColumnMax)
{
    // backrev the sample through history...
    firstSample = OFFSET_WITH_MOD(firstSample,-(samplesToGraph - kBatchSize),AUDIO_HISTORY_SAMPLES);
    
    // we dont want to look at more than 1000 samples, no matter our windows...
    u32 skipping = samplesToGraph / 1000;
    if(skipping < 1)
        skipping = 1;
    
    // maybe rescan, looking bacwards for lowest sample value...
    firstSample = scanForLowest(samples, firstSample, samplesToScanBackForSync);
    
    for(u32 i = 0; i < samplesToGraph; i += skipping)
    {
        u32 x = i * COLUMNS / samplesToGraph;
        f32 sample = samples[(firstSample + i) % AUDIO_HISTORY_SAMPLES];
        bucketColumnMax[x] = max(bucketColumnMax[x], sample * sample);
        f32 y = sample * ROWS + ROWS / 2 - .5;
        if(y < 0)
            y = 0;
        if(y > ROWS - 1)
            y = ROWS - 1;
        u32 yI = y;
        f32 portion = y - yI;
        
        buckets[yI * COLUMNS + x] += 1 - portion;
        bucketColorsRepresented[yI * COLUMNS + x] |= colorOr;
        if(portion)
        {
            buckets[(yI + 1) * COLUMNS + x] += portion;
            bucketColorsRepresented[(yI + 1) * COLUMNS + x] |= colorOr;
        }
        bucketColumnHits[x] ++;
    }
}

/**
 * oscilloscope
 */
void CRackExtension::doAud0()
{
    // usually do nothing.
    if(this->batchTicker % this->audioRenderTicks)
        return;
    
    if(!this->om->isAudioInConnected())
        return;
    
    this->maybeGetAud();
    
    f32 buckets[LAMPS] = {0};
    u32 bucketColorsRepresented[LAMPS] = {0};
    f32 bucketColumnHits[COLUMNS] = {0};
    f32 bucketMax[COLUMNS] = {0};
    
    f32 samplesToGraph = this->om->getSystemSampleRate() / 261.625565; // samples/second * second/cycle ==> samples/cycle
    f32 f1 = this->om->getScanRate();
    samplesToGraph *= (0.2 + f1 * f1 * 10);
    
    s32 backScan = 0;
    if(this->om->getWaveSync())
        backScan = AUDIO_HISTORY_SAMPLES / 10;
    graphIntoBuckets(this->audIn,
                     this->audInPointer,
                     samplesToGraph,
                     backScan,
                     1,
                     buckets,
                     bucketColorsRepresented,
                     bucketColumnHits,
                     bucketMax );
    
    s32 foldedLampOffset = 36;
    for(u32 i = 0; i < LAMPS; i++)
    {
        u32 brightness = buckets[i] / bucketColumnHits[i % 12] * BRIGHTSTEPS;
        u32 hue = mainHue;
        if(brightness == 0)
        {
            if(bucketMax[i % 12] > 1)
            {
                hue = 0;
                brightness = bucketMax[i % 12] + 2;
            }
        }
        this->lamps.set(i + FIRST_LAMP,hue,brightness);
        this->foldedLamps.set(i + FIRST_FOLDED_LAMP - foldedLampOffset,hue,brightness);
    }
    
    
}

static f32 pin(f32 x,f32 low,f32 high)
{
    if(x < low)
        x = low;
    else if(x > high)
        x = high;
    return x;
}

static s32 pinI(f32 x,s32 low,s32 high)
{
    s32 xI = x;
    if(xI < low)
        xI = low;
    else if(xI > high)
        xI = high;
    return xI;
}

static void plotI(f32 *buckets,s32 x,s32 y,f32 amt)
{
    if(amt > 0)
    {
        buckets += y * COLUMNS + x;
        *buckets = max(*buckets,amt);
    }
}

static void plot(f32 *buckets,f32 x,f32 y,f32 amt)
{
    f32 extent = 0.8f;
    x = mapRange(x,-extent,extent,0,COLUMNS-1);
    y = mapRange(y,-extent,extent,0,ROWS-1);
    
    x = pin(x,0,COLUMNS - 1);
    y = pin(y,0,ROWS - 1);
    s32 xI = (s32)floor(x);
    s32 yI = (s32)floor(y);
    
    f32 xP = x - xI;
    f32 yP = y - yI;
    
    f32 x0y0 = (f32)((1.0 - xP) * (1.0 - yP));
    f32 x1y0 = (f32)(xP * (1.0 - yP));
    f32 x0y1 = (f32)(1.0 - xP) * yP;
    f32 x1y1 = xP * yP;
    
    plotI(buckets,x,y,x0y0 * amt);
    plotI(buckets,x+1,y,x1y0 * amt);
    plotI(buckets,x,y+1,x0y1 * amt);
    plotI(buckets,x+1,y+1,x1y1 * amt);
}

void CRackExtension::doAud3()
{
    // usually do nothing.
    if(this->batchTicker % this->audioRenderTicks)
        return;
    
    if(!this->om->isAudioInConnected())
        return;
    
    this->maybeGetAud();
    
    f32 buckets[LAMPS] = {0};
    
    // we choose a sampling area, and split it in half for x & y
    s32 sampleRange = kBatchSize + this->om->getScanRate() * 1024;
    s32 halfSampleRange = sampleRange / 2;
    
    s32 pipsToDraw = 40;
    s32 samplesPerPip = (halfSampleRange / pipsToDraw);
    s32 sampleW = OFFSET_WITH_MOD(this->audInPointer,-(sampleRange - kBatchSize),AUDIO_HISTORY_SAMPLES);
    
    for(s32 i = 0; i < pipsToDraw; i++)
    {
        f32 sampleX = this->audIn[sampleW];
        f32 sampleY = this->audIn[OFFSET_WITH_MOD(sampleW, halfSampleRange, AUDIO_HISTORY_SAMPLES)];
        f32 boost = 2.4;
        sampleX *= boost;
        sampleY *= boost;
        plot(buckets,sampleX,sampleY,1);
        
        sampleW = OFFSET_WITH_MOD(sampleW, samplesPerPip, AUDIO_HISTORY_SAMPLES);
    }
    
    s32 foldedLampOffset = 39;
    for(u32 i = 0; i < LAMPS; i++)
    {
        u32 hue = this->mainHue;
        u32 brightness = buckets[i] * BRIGHTSTEPS;
        this->lamps.set(i + FIRST_LAMP,hue,brightness);
        this->foldedLamps.set(i + FIRST_FOLDED_LAMP - foldedLampOffset,hue,brightness);
    }
}


void CRackExtension::doAud2()
{
    if(!this->om->isAudioInConnected())
        return;
    
    this->maybeGetAud(); // always get aud, for accumulating nice large frequency block.

    // usually do nothing.
    if(this->batchTicker % this->audioRenderTicks)
        return;
    
    
    // pull some audio to do fft on
    u32 fftBits = 13;
    u32 fftSize = 1 << fftBits;
    s32 w = OFFSET_WITH_MOD(this->audInPointer,kBatchSize - fftSize,AUDIO_HISTORY_SAMPLES);
    
    for(u32 i = 0; i < fftSize; i++)
    {
        this->fftMem[i] = this->audIn[w];
        w = OFFSET_WITH_MOD(w, 1, AUDIO_HISTORY_SAMPLES);
    }
    
    // taper the front and back, linearly.
    u32 trapezoidSamples = fftSize / 10;
    f32 trapezoidSamplesF = trapezoidSamples;
    f32 *w1 = this->fftMem;
    f32 *w2 = this->fftMem + fftSize - 1;
    for(u32 i = 0; i < trapezoidSamples; i++)
    {
        f32 m = (i + 1) / trapezoidSamplesF;
        *w1++ *= m;
        *w2-- *= m;
    }
    
    JBox_FFTRealForward(fftBits,this->fftMem);
    
    // Now. Render all these values, somehow, into our 72 lamps.
#define BUCKETS_PER_LAMP 7
#define SPECT_HUES 3
    u32 hues[SPECT_HUES];
    for(s32 i = 0; i < SPECT_HUES; i++)
        hues[i] = OFFSET_WITH_MOD(this->mainHue, i - SPECT_HUES/2, HUESTEPS);
    
    f32 *fftW = this->fftMem + (int)(this->om->scanRate * 512); // first fft sample we look at, marching forward.
    
    for(u32 i = FIRST_LAMP; i <= LAST_LAMP; i++)
    {
        f32 ampTotal = 0; // accumulate buckets-per-lamp into here
        f32 highestAmp = 0;
        u32 highestAmpIndex = 2;
        for(u32 j = 0; j < BUCKETS_PER_LAMP; j++)
        {
            f32 re = *fftW++;
            f32 im = *fftW++;
            f32 anAmp = sqrtf(re * re + im * im);
            ampTotal += anAmp;
            if(anAmp > highestAmp)
            {
                highestAmp = anAmp;
                highestAmpIndex = j;
            }
        }
        u32 hue = 0;
        ampTotal /= 30 * BUCKETS_PER_LAMP;
        u32 b = ampTotal * BRIGHTSTEPS;
        if(b < 3)
        {
            hue = this->mainHue;
            b = 0;
        }
        else
        {
            if(b < 6)
                b = 6;
            hue = hues[highestAmpIndex * SPECT_HUES / BUCKETS_PER_LAMP];
        }
        
        // Show the brightness of the lamp, in the color of the fullest bucket.
//        this->lamps.set(i,hue, ampTotal * BRIGHTSTEPS);

        this->lamps.glideTo(i, hue,b, 80);
        this->foldedLamps.set(i + FIRST_FOLDED_LAMP - FIRST_LAMP,hue,b);
    }
}


