Published

Dependencies:   BLE_API TLC5955 mbed nRF51822

Fork of BLE_LoopbackUART by Bluetooth Low Energy

sequencer.cpp

Committer:
roysandberg
Date:
2018-06-09
Revision:
14:73923b07ae4a

File content as of revision 14:73923b07ae4a:

/*

Control lighting sequences


*/

#include "TLC5955.h"
#include <ctype.h>
#include "sequencer.h"
#include "color.h"

DigitalOut led1(LED1);

// zero-based time, starts at 7:30PM
volatile uint32_t TheElapsedTime = (1000*60*60*6) + (1000*60*30);    


// THE FOLLOWING CHANNELS (ZERO-BASED) ARE BROKEN, AND ARE SKIPPED:
// 4,6,14, 35-46

 
// Each row address indivual panels starting at the front right panel and moving clockwise as viewed from below
// These are the two middle (internal-use) channels: 12, 34

int PANELS[ROWS][COLUMNS] = {  
   {0, 1, 2, 3, 5, 7} ,   // Lowest row of panels
   {8, 9, 10, 11, 13, 15} ,  
   {16, 17, 18, 19, 20, 21} ,  
   {22, 23, 24, 25, 26, 27} ,  
   {28, 29, 31, 32, 33, 34}   // Highest row of panels
  };   
  
//int PANELS[5][6] = {  
//   {0, 1, 2, 3, 4, 5} ,   // Lowest row of panels
//   {6, 7, 8, 9, 10, 11} ,  
//   {12, 13, 14, 15, 16, 17} ,  
//   {18, 19, 20, 21, 22, 23} ,  
//   {24, 25, 26, 27, 28, 29}   // Highest row of panels
//};

int CHANNEL_MAPPING[] = { 8, 12, 9, 13, 14, 10, 15, 11, 7, 3, 6, 2, 1, 5, 0, 4 };

uint16_t debugRedOut[NUMBER_OF_PANELS];
uint16_t debugGreenOut[NUMBER_OF_PANELS];
uint16_t debugBlueOut[NUMBER_OF_PANELS];

volatile int channelColorSettings[NUMBER_OF_PANELS];
volatile int Settings[NUMBER_OF_PANELS];
volatile int channelColorPointer[2][NUMBER_OF_PANELS];
volatile int randomBaseTimeOffset[NUMBER_OF_PANELS];
volatile int randomMovementTimeOffset[NUMBER_OF_PANELS];

volatile uint16_t UserAdjustableMovementInterval = 2000;

extern TLC5955* theChip;

volatile int TheBaseDitherMode;
volatile int TheBaseEffectType;
volatile int TheBaseTimeConstant;
volatile uint32_t TheBaseEffectStartTime;
const int* TheBaseColorList;

volatile int TheMovementPosition;
volatile int TheMoveEndPosition;
volatile int TheMovementNumberOfLevels;
volatile int NumberOfMovesSoFar;

void (*TheMovementAddressModePtr)(int,const int*,int);

volatile int TheMovementMode;
volatile int TheDitherMode;
volatile int TheMovementFill;
volatile int TheEffectType;
const int* TheColorList;
volatile int TheTimeConstant;
volatile int TheMoveTimeConstant;
volatile uint32_t TheEffectStartTime;
volatile int TheMode=1; // lighting effect mode. zero is off.
volatile bool inMinuteEffect = FALSE;
volatile bool inHourEffect = FALSE;
        
// https://developer.mbed.org/questions/2886/Why-is-the-rand-function-not-the-least-b/
unsigned int m_z=12434,m_w=33254;
unsigned int rnd() {
    m_z = 36969 * (m_z & 65535) + (m_z >>16);
    m_w = 18000 * (m_w & 65535) + (m_w >>16);
    return ((m_z <<16) + m_w);
}

void setMode(int mode) {
    TheMode = mode;
}

int getMode() {
    return TheMode;
}


int movementToEnum(char* input) {  
    if (!strcmp(input,"v-")) {
        return VERTICAL_UP;
    } else if (!strcmp(input,"v+")) {
        return VERTICAL_DOWN;
    } else if (!strcmp(input,"h+")) {
        return HORIZONTAL_FORWARD;
    } else if (!strcmp(input,"h-")) {
        return HORIZONTAL_BACKWARDS;
    } else if (!strcmp(input,"r+")) {
        return RADIAL_CLOCKWISE;
    } else if (!strcmp(input,"r-")) {
        return RADIAL_COUNTERCLOCKWISE;
    } else if (!strcmp(input,"b+")) {
        return BARREL_CLOCKWISE;
    } else if (!strcmp(input,"b-")) {
        return BARREL_COUNTERCLOCKWISE;
    } else if (!strcmp(input,"al")) {
        return ALL;
    } else if (!strcmp(input,"of")) {
        return OFF;
    } else {
        return -1;
    }
}

int ditherToEnum(char* input) {
  if (!strcmp(input,"f")) {
        return FIXED;
    } else if (!strcmp(input,"c")) {
        return CROSSFADE;
    } else if (!strcmp(input,"i")) {
        return FADE_IN;
    } else if (!strcmp(input,"o")) {
        return FADE_OUT_AND_IN;
    } else if (!strcmp(input,"s")) {
        return FADE_IN_AND_OUT;
    } else if (!strcmp(input,"p")) {
        return PULSED_INTENSITY;
    } else {
        return -1;    
    }
}

int fillToEnum(char* input) {
  if (!strcmp(input,"l")) {
        return LINE;
    } else if (!strcmp(input,"f")) {
        return FILL;
    } else {    
        return -1;
    }
}

int effectToEnum(char* input) {
  if (!strcmp(input,"c")) {
        return CONSTANT;
    } else if (!strcmp(input,"r")) {
        return RANDOM;
    } else if (!strcmp(input,"s")) {
        return SEQUENCE;
    } else if (!strcmp(input,"f")) {
        return FIXED_RANDOM;
    } else {
        return -1;    
    }
}

// only compare first two characters. The rest are unused.
const int* colorListToPointer(char* input) {
    char shortInput[3] ;
    shortInput[0] = input[0];
    shortInput[1] = input[1];
    shortInput[2] = 0;
    for (int i=0; i< USER_COLOR_CHOICES_SIZE; i++) {
        char shortColor[3];
        shortColor[0] = listOfColorLists[i].colorListName[0];
        shortColor[1] = listOfColorLists[i].colorListName[1];
        shortColor[2] = 0;
        if (!strcmp (shortColor, shortInput)) {
            return listOfColorLists[i].colorList;
        }
    }
    return NULL;
}

// The channels on the board are numbered differently than the channels in software, so this corrects for it
int channelMapping(int channel) {
    int valueToMap = channel % 16;
    int channelOffset = channel / 16;
    return (channelOffset*16) + CHANNEL_MAPPING[valueToMap];
}

int getColorArraySize(const int* colorArray) {
    int i=0;
    while (colorArray[i] != -1) {
        i++;
    }
    return i;
}
    
int pickRandomColorFromArray(const int* colorArray) {
    int n = getColorArraySize(colorArray);
    int random = colorArray[rnd() % n];
    //printf("n=%d, s=%d, r=%d\n\r",n, rnd(), random);
    return random;
}

void setArrayColor(int panel, int color, bool isBaseEffect) {
    channelColorPointer[isBaseEffect][panel] = color;    
}

int getArrayColor(int panel, bool isBaseEffect) {
    return channelColorPointer[isBaseEffect][panel];
}

int pickNextColorFromArray(int panel, const int* colorArray, bool isBaseEffect) {
    channelColorPointer[isBaseEffect][panel]++;
    int n = getColorArraySize(colorArray);
    channelColorPointer[isBaseEffect][panel] = channelColorPointer[isBaseEffect][panel] % n;
    return colorArray[channelColorPointer[isBaseEffect][panel]];
}

void setChannelToColorName(int channel, int colorName) {
    debugRedOut[channel] = color_data[colorName].rgb.r;
    debugGreenOut[channel] = color_data[colorName].rgb.g;
    debugBlueOut[channel] = color_data[colorName].rgb.b;
        
    channel = channelMapping(channel);
    theChip->setChannel(channel, 
    color_data[colorName].rgb.r,
    color_data[colorName].rgb.g,
    color_data[colorName].rgb.b);
} 

// 8 bit r,g,b for 24 bit color
void setChannelToRGB(int channel, uint16_t r, uint16_t g, uint16_t b) {

    debugRedOut[channel] = r;
    debugGreenOut[channel] = g;
    debugBlueOut[channel] = b;

    channel = channelMapping(channel);
    theChip->setChannel(channel,r,g,b);
}

void setStartingMovePosition() {
    switch(TheMovementMode) {
        case VERTICAL_UP:
            TheMovementPosition=-1;
            TheMoveEndPosition=4;
        break;
        case VERTICAL_DOWN:
            TheMovementPosition=5;
            TheMoveEndPosition=0;
        break;
        case HORIZONTAL_FORWARD:
            TheMovementPosition=-1;
            TheMoveEndPosition=2;
        break;
        case HORIZONTAL_BACKWARDS:
            TheMovementPosition=3;
            TheMoveEndPosition=0;
       break;
        case RADIAL_CLOCKWISE:
            TheMovementPosition=-1;
            TheMoveEndPosition=5;

        break;
        case RADIAL_COUNTERCLOCKWISE:
            TheMovementPosition=6;
            TheMoveEndPosition=0;
        break;
        case BARREL_CLOCKWISE:
            TheMovementPosition=-1;
            TheMoveEndPosition=9;
        break;
        case BARREL_COUNTERCLOCKWISE:
            TheMovementPosition=10;
            TheMoveEndPosition=0;
        break;
        case ALL:
        case OFF:
            TheMovementPosition=-1;
            TheMoveEndPosition=2;
        default:
        break;
    }
}

void setPanelTargetColor(int channel, int aColor) {
    channelColorSettings[channel] = aColor;
}

int getPanelTargetColor(int channel) {
    return channelColorSettings[channel];
}

void setPanelStartingColor(int channel, int aColor) {
    Settings[channel] = aColor;
}

int getPanelStartingColor(int channel) {
    return Settings[channel];
}


const char* getNameFromColorNumber(int color) {
    return color_data[color].name;
}


void setBaseEffect( int ditherMode, int effectType, const int* colorList, int timeConstant ) {
    TheBaseDitherMode = ditherMode;
    TheBaseEffectType = effectType;
    TheBaseColorList = colorList;
    TheBaseTimeConstant = timeConstant; // controls internal fades, blinking, etc.
    TheBaseEffectStartTime = TheElapsedTime;
    
    //printf ("New Base Effect. t=%d:%d dither=%d, effect=%d, firstColor=%s, timeConstant=%d\n\r", 
    //(TheElapsedTime/(1000*60*60))+1, (TheElapsedTime%(1000*60*60))/(60*1000),
    //ditherMode, effectType, getNameFromColorNumber(colorList[0]), timeConstant);
    
    if (TheBaseEffectType == CONSTANT) {
        // set all panels to a randomly selected color
        int theColor = pickRandomColorFromArray(colorList);
        //printf ("Using constant base color=(%d), %s\n\r", theColor, getNameFromColorNumber(theColor));
        for (int i=0; i< NUMBER_OF_PANELS; i++) {
            setPanelStartingColor(i, getPanelTargetColor(i));
            setArrayColor(i, theColor,1);
        }  
    } else if (TheBaseEffectType == FIXED_RANDOM) {
        // set each panel to a randomly selected color that doesn't change for the duration of the effect
        for (int i=0; i< NUMBER_OF_PANELS; i++) {
            setPanelStartingColor(i, getPanelTargetColor(i));
            setArrayColor(i, pickRandomColorFromArray(colorList),1);
        }
    }
    
    for (int i=0; i< NUMBER_OF_PANELS; i++) {
        randomBaseTimeOffset[i] = rnd() % TheBaseTimeConstant/2;
    }
}


void setOverlayEffect( int movementMode, int ditherMode, int movementFill, int effectType, const int* colorList, int timeConstant, int moveTimeConstant ) {
    TheMovementMode = movementMode;
    TheDitherMode = ditherMode;
    TheMovementFill = movementFill;
    TheEffectType = effectType;
    TheColorList = colorList;
    TheTimeConstant = timeConstant;  // controls internal fades, blinking, etc.
    TheMoveTimeConstant = moveTimeConstant;
    TheEffectStartTime = TheElapsedTime;
    NumberOfMovesSoFar = 0;
    setStartingMovePosition();
    //printf ("New Movement Effect. move=%d, dither=%d, effect=%d, firstColor=%s, timeConstant=%d\n\r", 
    //movementMode, ditherMode, effectType, getNameFromColorNumber(colorList[0]), timeConstant);
    
    if (TheEffectType == CONSTANT) {
        // set all panels to a randomly selected color
        int theColor = pickRandomColorFromArray(colorList);
        //printf ("Using constant overlay color=(%d), %s\n\r", theColor, getNameFromColorNumber(theColor));
        for (int i=0; i< NUMBER_OF_PANELS; i++) {
            setPanelStartingColor(i, getPanelTargetColor(i));
            setArrayColor(i, theColor,0);
        }  
    } else if (TheEffectType == FIXED_RANDOM) {
        // set each panel to a randomly selected color that doesn't change for the duration of the effect
        for (int i=0; i< NUMBER_OF_PANELS; i++) {
            setPanelStartingColor(i, getPanelTargetColor(i));
            setArrayColor(i, pickRandomColorFromArray(colorList),0);
        }
    }    
    for (int i=0; i< NUMBER_OF_PANELS; i++) {
        randomMovementTimeOffset[i] = rnd() % TheTimeConstant/2;
    }
}

inline void crossFade(int startingR, int startingG, int startingB, int endingR, int endingG, int endingB, uint32_t timeConstant, uint32_t currentTime, uint16_t* r, uint16_t* g, uint16_t* b) {
    currentTime = currentTime % timeConstant;
    
    //printf("crossFrade current=%d, total=%d\n\r", currentTime, timeConstant);
    
    //if (currentTime > timeConstant) currentTime = timeConstant;
    
    *r =  (uint16_t) ((((uint32_t)startingR *  (uint32_t)(timeConstant - currentTime)) + ( (uint32_t)endingR * (uint32_t) currentTime)) / ((uint32_t) timeConstant));
    *g =  (uint16_t) ((((uint32_t)startingG *  (uint32_t)(timeConstant - currentTime)) + ( (uint32_t)endingG * (uint32_t) currentTime)) / ((uint32_t) timeConstant));
    *b =  (uint16_t) ((((uint32_t)startingB *  (uint32_t)(timeConstant - currentTime)) + ( (uint32_t)endingB * (uint32_t) currentTime)) / ((uint32_t) timeConstant));

    //printf ("t=%d, c=%d, start=(%x,%x,%x), ending=(%x,%x,%x), now=%x,%x,%x\n\r", currentTime, timeConstant, startingR, startingG, startingB, endingR, endingG, endingB, *r, *b, *g );
    //printf("s={%d,%d,%d}, e={%d,%d,%d}, c={%d,%d,%d}\n\r",   startingR, startingG, startingB, endingR, endingG, endingB, *r, *g, *b);
}

inline void crossFade(int startingColor, int endingColor, uint32_t timeConstant, uint32_t currentTime, uint16_t* r, uint16_t* g, uint16_t* b) {
    int startingR, startingG, startingB;
    int endingR, endingG, endingB;
        
    startingR = color_data[startingColor].rgb.r;
    startingG = color_data[startingColor].rgb.g;
    startingB = color_data[startingColor].rgb.b;
    
    endingR = color_data[endingColor].rgb.r;
    endingG = color_data[endingColor].rgb.g;
    endingB = color_data[endingColor].rgb.b;

    crossFade(startingR, startingG, startingB, endingR, endingG, endingB, timeConstant, currentTime, r, g, b); 
}

void manageDither(int totalTimeForEffect, int timeIntoEffect, int startingColor, int endingColor, int panel) {
    uint16_t r,g,b;
    
    switch (TheBaseDitherMode) {
        case FIXED:
            setChannelToColorName(panel, endingColor);
            break;
        case CROSSFADE:
            crossFade(startingColor, endingColor, totalTimeForEffect, timeIntoEffect, &r, &g, &b);
            setChannelToRGB(panel,r,g,b);
            break;
        case FADE_IN:
            crossFade(COLOR_BLACK, endingColor, totalTimeForEffect, timeIntoEffect, &r, &g, &b);
            setChannelToRGB(panel,r,g,b);    
            break;
        case FADE_OUT_AND_IN:
            if (timeIntoEffect < totalTimeForEffect/2) {
                crossFade(startingColor, COLOR_BLACK, totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
            } else {
                crossFade(COLOR_BLACK, endingColor, totalTimeForEffect/2, timeIntoEffect - (totalTimeForEffect/2), &r, &g, &b);
            }
            setChannelToRGB(panel,r,g,b);    
            break;
        case FADE_IN_AND_OUT:
            if (timeIntoEffect < totalTimeForEffect/2) {
                crossFade(COLOR_BLACK, endingColor, totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
            } else {
                crossFade(endingColor, COLOR_BLACK, totalTimeForEffect/2, timeIntoEffect - (totalTimeForEffect/2), &r, &g, &b);
            }
            setChannelToRGB(panel,r,g,b);    
            break;
        case PULSED_INTENSITY:
            if (timeIntoEffect < totalTimeForEffect/2) {
                crossFade(  color_data[endingColor].rgb.r/4, color_data[endingColor].rgb.g/4, color_data[endingColor].rgb.b/4, 
                            color_data[endingColor].rgb.r, color_data[endingColor].rgb.g, color_data[endingColor].rgb.b, 
                            totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
            } else {
                crossFade(  color_data[endingColor].rgb.r, color_data[endingColor].rgb.g, color_data[endingColor].rgb.b, 
                            color_data[endingColor].rgb.r/4, color_data[endingColor].rgb.g/4, color_data[endingColor].rgb.b/4, 
                            totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
            }
            setChannelToRGB(panel,r,g,b);    
            break;
        default:
            break;
    }
}


int isTimeForNewColor(int timeConstant, int timeSoFar) {
    if (timeSoFar % timeConstant < CLOCK_GRANULARITY) {
       //printf("isTime! soFar=%d, c=%d\n\r", timeSoFar, timeConstant);
        return true;
    } else {
        return false;
    }
}


void manageEffectType(int panel, const int* colorList, int isBaseEffect) {
    uint32_t startTime, timeConstant;
    int effectType;
    
    if (isBaseEffect) {
        effectType = TheBaseEffectType;        
        timeConstant = TheBaseTimeConstant - (effectType == RANDOM) ? randomBaseTimeOffset[panel] : 0;
        startTime = TheBaseEffectStartTime;
    } else {
        effectType = TheEffectType;
        timeConstant = TheTimeConstant - (effectType == RANDOM) ? randomMovementTimeOffset[panel] : 0;
        startTime = TheEffectStartTime;
    }


    
    switch (effectType) {
    case CONSTANT:
    case FIXED_RANDOM:
        // the colors are selected when the effect is first created
        setPanelStartingColor(panel, getArrayColor(panel, isBaseEffect));
        setPanelTargetColor(panel, getArrayColor(panel, isBaseEffect));
        break;
    case RANDOM:
        // TODO: Change to a random number that is picked so that the average length will be elapsed time. (probability at elapsedTime is 50% that it will be time)
        if (isTimeForNewColor(timeConstant, TheElapsedTime-startTime)) {
            setPanelStartingColor(panel, getPanelTargetColor(panel));
            setPanelTargetColor(panel, pickRandomColorFromArray(colorList));
            if (isBaseEffect) {
                randomBaseTimeOffset[panel] = rnd() % TheBaseTimeConstant / 2;
            } else {
                randomMovementTimeOffset[panel] = rnd() % TheTimeConstant / 2;
            }
        }
        break;
    case SEQUENCE:
        if (isTimeForNewColor(timeConstant, TheElapsedTime-startTime)) {
            setPanelStartingColor(panel, getPanelTargetColor(panel));
            int nextColor = pickNextColorFromArray(panel,colorList,isBaseEffect);
            setPanelTargetColor(panel, nextColor);    
        }
        // Dither from old sequence color to new sequence color
        break;
    case RANDOM_STROBE:
        // fluctuate intensity randomly
        break;
    default:
        break;
    }
    manageDither(timeConstant, TheElapsedTime-startTime, getPanelStartingColor(panel), getPanelTargetColor(panel), panel);
}

// top-to-bottom  - 5 levels
void addressVertically (int location, const int* colorList, int isBaseEffect) {
    if (location >= 0 && location < 5) {
        for (int i = 0; i < 6; i++) {
            manageEffectType(PANELS[location][i], colorList, isBaseEffect);
        }
    }
}

// front-to-back - 3 levels
void addressHorizontally (int location, const int* colorList, int isBaseEffect) {
    if (location >= 0 && location < 3) {
        for (int i = 0; i < 5; i++) {
            manageEffectType(PANELS[i][location], colorList, isBaseEffect);
            manageEffectType(PANELS[i][5-location], colorList, isBaseEffect);
        }
    }
}

void addressHorizontalRadially (int location, const int* colorList, int isBaseEffect) {
    if (location >= 0 && location < 6) {
        for (int i = 0; i < 5; i++) {
            manageEffectType(PANELS[i][location], colorList, isBaseEffect);
        }
    }
}

void addressVerticallyRadially(int location, const int* colorList, int isBaseEffect) {
    if (location >= 0 && location < 5) {
        for (int i = 0; i < 3; i++) {
            manageEffectType(PANELS[location][i], colorList, isBaseEffect);
        }
    }
    if (location >= 5 && location < 10) {
        for (int i = 3; i < 6; i++) {
            manageEffectType(PANELS[9-location][i], colorList, isBaseEffect);
        }
    }
}

int getMovementLevels(int aMovementMode) {
    switch (aMovementMode) {
            case VERTICAL_UP:
                return ROWS;
            case VERTICAL_DOWN:
                return ROWS;
            case HORIZONTAL_FORWARD:
                return 3;
            case HORIZONTAL_BACKWARDS:
                return 3;
            case RADIAL_CLOCKWISE:
                return 6;
            case RADIAL_COUNTERCLOCKWISE:
                return 6;
            case BARREL_CLOCKWISE:
                return 10;
            case BARREL_COUNTERCLOCKWISE:
                return 10;
            case ALL:
                return 1;
            case OFF:
                return 1;
            default:
                return 1;
    }
}

void manageMovement() {
    // Track which panels get painted with the Base information, and which get the effect information
    
    // cycle through moves at TheMoveTimeConstant rate
    //printf (">> t=%d, c=%d, %d <? %d\n\r",  (TheElapsedTime-TheEffectStartTime), TheMoveTimeConstant, (TheElapsedTime-TheEffectStartTime) % TheMoveTimeConstant, CLOCK_GRANULARITY);
    
    if ((TheElapsedTime-TheEffectStartTime) % TheMoveTimeConstant < CLOCK_GRANULARITY) {
        //printf ("Moving. Mode=%d\n\r", TheMovementMode);
        switch(TheMovementMode) {
            case VERTICAL_UP:
                TheMovementPosition++;
                TheMovementNumberOfLevels = ROWS;
                TheMovementAddressModePtr = &addressVertically;
            break;
            case VERTICAL_DOWN:
                TheMovementPosition--;
                TheMovementNumberOfLevels = ROWS;
                TheMovementAddressModePtr = &addressVertically;
            break;
            case HORIZONTAL_FORWARD:
                TheMovementPosition++;
                TheMovementNumberOfLevels = 3;
                TheMovementAddressModePtr = &addressHorizontally;
            break;
            case HORIZONTAL_BACKWARDS:
                TheMovementPosition--;
                TheMovementNumberOfLevels = 3;
                TheMovementAddressModePtr = &addressHorizontally;
            break;
            case RADIAL_CLOCKWISE:
                TheMovementNumberOfLevels = 6;
                TheMovementPosition++;
                TheMovementAddressModePtr = &addressHorizontalRadially;
            break;
            case RADIAL_COUNTERCLOCKWISE:
                TheMovementNumberOfLevels = 6;
                TheMovementPosition--;
                TheMovementAddressModePtr = &addressHorizontalRadially;
            break;
            case BARREL_CLOCKWISE:
                TheMovementPosition++;
                TheMovementNumberOfLevels = 10;
                TheMovementAddressModePtr = &addressVerticallyRadially;
            break;
            case BARREL_COUNTERCLOCKWISE:
                TheMovementPosition--;
                TheMovementNumberOfLevels = 10;
                TheMovementAddressModePtr = &addressVerticallyRadially;
            break;
            case ALL:
                TheMovementPosition = 0;
                TheMovementNumberOfLevels = 3;
                TheMovementAddressModePtr = &addressHorizontally;
            break;
            case OFF:
                TheMovementPosition = 0;
                TheMovementNumberOfLevels = 3;
                TheMovementAddressModePtr = &addressHorizontally;
            break;
            default:
            break;
        }
        if (TheMovementMode != OFF) {
            NumberOfMovesSoFar++;
            //printf ("Incrementing moves to: %d\n\r", NumberOfMovesSoFar);
        }
    }
    // end the move after it has reached its end
    if (NumberOfMovesSoFar > TheMovementNumberOfLevels) {
        //printf ("TURNING MOVE OFF.\n\r");
        TheMovementMode = OFF;
        NumberOfMovesSoFar = 0;
    }
    
    if (TheMovementPosition < 0) TheMovementPosition = TheMovementNumberOfLevels-1;
    TheMovementPosition = TheMovementPosition % TheMovementNumberOfLevels;


    //printf("mode=%d, pos=%d, levels=%d, numMoves=%d\n\r",TheMovementMode, TheMovementPosition, TheMovementNumberOfLevels, NumberOfMovesSoFar);
    
    // paint all the subeffects at 40Hz (or whatever the refresh rate is)
    for (int i=0; i < TheMovementNumberOfLevels; i++) {
        if (TheMovementMode != OFF && (i < TheMovementPosition && TheMovementFill == FILL || (TheMovementMode == ALL))) {
            TheMovementAddressModePtr (i, TheColorList,  false); 
        } else if (i == TheMovementPosition && (TheMovementMode != OFF)){
            TheMovementAddressModePtr (i, TheColorList,  false); 
        } else {
            TheMovementAddressModePtr (i, TheBaseColorList,  true); 
        }
    }
    
    // Latch the current color settings into the hardware
    theChip->latchData();
}

const int* getRandomMovementColorList() {
    return movementColors[rnd() % MOVEMENT_COLOR_LIST_SIZE];
}

const int* getRandomBaseColorList() {
    return baseColors[rnd() % BASE_COLOR_LIST_SIZE];
}

const int* getRandomColorList() {
    return listOfColorLists[rnd() % USER_COLOR_CHOICES_SIZE].colorList;
}

void generateBaseEffect(bool isBackgroundYellow) {
    if (TheMode == USER_BASE || TheMode == USER_MOVEMENT_AND_BASE) {
        return; // already set by user
    } else {        
        int randomDitherMode = rnd() % 6;
        int randomEffectType = rnd() % 4;
        const int* randomColorList;
        if (isBackgroundYellow) {
            randomColorList = getRandomBaseColorList();    
        } else {
            randomColorList = getRandomMovementColorList();    
        }            
        int timeConstant = 300 + (rnd() % 8000);
        setBaseEffect( randomDitherMode, randomEffectType, randomColorList, timeConstant ); 
    }
}

void generateMovementEffect(int durationMS) {

    // select a random movement effect to overlay onto the base effect
    if (TheMode == USER_MOVEMENT || TheMode == USER_MOVEMENT_AND_BASE) {
        return; // already set by user
    } else {
        int randomMovementMode = rnd() % 9;
        int randomDitherMode = rnd() % 6;
        int randomMovementFill = rnd() % 3;
        int randomEffectType = rnd() % 4;
        const int* randomColorList = getRandomMovementColorList();
        int moveTimeConstant =  (durationMS*(3+(rnd()%5))/8) / getMovementLevels(randomMovementMode);
        //int timeConstant =  ((durationMS*7)/8) / getMovementLevels(randomMovementMode);
        int timeConstant = 200 + (rnd() % (durationMS-200));
        setOverlayEffect( randomMovementMode, randomDitherMode, randomMovementFill, randomEffectType, randomColorList, timeConstant, moveTimeConstant);
        //printf ("New Movement Effect. move=%d, dither=%d, effect=%d, firstColor=%s, timeConstant=%d\n\r", 
    }
}

// hour is zero based, starting at midnight
void generateHourlyMovement(int hour, int msIntoEffect) {
    inHourEffect = TRUE;
    
    if (msIntoEffect < 2000) {
    // turn off for two seconds
        setChannelToColorName (BEAK, COLOR_BLACK);
        setChannelToColorName (EYES, COLOR_BLACK);
        setBaseEffect( CROSSFADE, CONSTANT, Black, 500 ); 
        setOverlayEffect( ALL, FIXED, LINE, CONSTANT, Black, 2000, 2000);
    } else if (msIntoEffect < 3000 + (hour*1000)) {
        if (msIntoEffect%1000 < 500) {
            //normal
            setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
            setChannelToColorName (EYES, COLOR_RED);
            setBaseEffect( FIXED, CONSTANT, OneYellow, 500 ); 
            setOverlayEffect( OFF, FIXED, LINE, CONSTANT, Black, 500, 500);
        } else {
            //weird
            setChannelToColorName (BEAK, COLOR_HUNTER_GREEN);
            setChannelToColorName (EYES, COLOR_BLUE);
            setBaseEffect( FIXED, RANDOM, Electrics, 100 ); 
            setOverlayEffect( OFF, FIXED, LINE, CONSTANT, Black, 500, 500);
        }
    
    } else if (msIntoEffect < 4000 + (hour*1000)) {
        // turn off for one seconds
        setChannelToColorName (BEAK, COLOR_BLACK);
        setChannelToColorName (EYES, COLOR_BLACK);
        setChannelToColorName(INTERNAL1, COLOR_RED);
        setChannelToColorName(INTERNAL2, COLOR_RED);
        setBaseEffect( CROSSFADE, CONSTANT, Black, 500 ); 
        setOverlayEffect( ALL, FIXED, LINE, CONSTANT, Black, 2000, 2000);
    } else {
        inHourEffect = FALSE;
        // select a new yellow (normal duck) base effect
        generateBaseEffect(true);
        setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
        setChannelToColorName (EYES, COLOR_BLUE);
        setChannelToColorName(INTERNAL1, COLOR_WHITE);
        setChannelToColorName(INTERNAL2, COLOR_WHITE);
    }
}

void generatePerMinuteMovement(int msIntoEffect) {
    inMinuteEffect = TRUE;
    
    // end after 4 seconds
    if (msIntoEffect >= 4000) {
        inMinuteEffect = FALSE;
        // select a new yellow (normal duck) base effect
        generateBaseEffect(true);
        setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
        setChannelToColorName (EYES, COLOR_BLUE);
        setChannelToColorName(INTERNAL1, COLOR_WHITE);
        setChannelToColorName(INTERNAL2, COLOR_WHITE);
    } else {
        if (msIntoEffect % 500 < CLOCK_GRANULARITY) {
            // do some number of movement effects in a row, twice a second
            generateBaseEffect(false);
            generateMovementEffect(500);
            setChannelToColorName (BEAK, COLOR_HUNTER_GREEN);
            setChannelToColorName (EYES, COLOR_RED);
            setChannelToColorName(INTERNAL1, COLOR_RED);
            setChannelToColorName(INTERNAL2, COLOR_RED);
        }    
    }
}


// This could also be used to beat match, hence the duration
void generatePerSecondMovement(int durationMS) {
    // single movement effect
    // if the movement effect is a fill, it will be restored in the next cycle
    generateMovementEffect(durationMS);    
}


void manageTimedInteractions() {
    TheElapsedTime += CLOCK_GRANULARITY; // called every 25mS
    if (TheElapsedTime % 43200000 < CLOCK_GRANULARITY) {
        TheElapsedTime = 0; // reset timer at midnight
    } else if (TheElapsedTime % 3600000 < CLOCK_GRANULARITY || inHourEffect) {
        // hourly tasks (reset timer)
        generateHourlyMovement(TheElapsedTime / 3600000, TheElapsedTime % 3600000);
    } else if (TheElapsedTime % 60000 < CLOCK_GRANULARITY || inMinuteEffect) {
        // one minute tasks
        generatePerMinuteMovement(TheElapsedTime % 60000);
    } else if (TheElapsedTime % UserAdjustableMovementInterval < CLOCK_GRANULARITY) {
        // one second tasks
        generatePerSecondMovement(UserAdjustableMovementInterval);
    }
    
    // This manages all the effects
    manageMovement();
}

// called prior to effects to initialize everything
void SequencerConfig() {
    for (int i=0;i<NUMBER_OF_PANELS;i++) {
        channelColorPointer[0][i]=0; // default to first color in list
        channelColorPointer[1][i]=0; // default to first color in list
        channelColorSettings[i] = COLOR_BLACK;
        Settings[i] = COLOR_BLACK;
    }
    
    TheMovementPosition = 0;
    TheMovementNumberOfLevels = 3;
    TheMovementAddressModePtr = &addressHorizontally;

    // Set a default base effect
    //setBaseEffect( CROSSFADE, RANDOM, Electrics, 300 ); 
    //setBaseEffect( CROSSFADE, RANDOM, KulorSunsetCamping, 500 ); 
    //setBaseEffect( CROSSFADE, RANDOM, TestColors, 2000 ); 
    setBaseEffect( FIXED, CONSTANT, GoodYellows, 5000 ); 
    
    // Set overlay effect to a reasonable neutral setting (OFF)
    setOverlayEffect(OFF , FIXED , LINE, CONSTANT, All, 1000, 1000 );
    setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
    setChannelToColorName (EYES, COLOR_BLUE);
    setChannelToColorName(INTERNAL1, COLOR_WHITE);
    setChannelToColorName(INTERNAL2, COLOR_WHITE);

}
    
void convertStringToLowercase(const char* input, char*output) {
    for (int i=0; i<strlen(input);i++) {
        output[i] = tolower(input[i]);
    }
    output[strlen(input)] = 0;
}

int getColorByName(char* colorName) {
    char lowerCaseInput[64];
    char lowerCaseSearch[64];

    convertStringToLowercase(colorName, lowerCaseInput);
    for (int i=0; i< COLOR_NAMES_MAX; i++) {
        convertStringToLowercase(color_data[i].name, lowerCaseSearch);
        if (!strcmp(lowerCaseSearch, lowerCaseInput)) {
            return i;
        }
    }
    return -1;
}

void debugInteractions() {
    // Display panel settings using a text output
    printf ("t=%d\n\r", TheElapsedTime);
    for (int i=0;i<ROWS;i++) {
        for (int j=0;j<COLUMNS;j++) {
            //printf ("-%d", (channelColorSettings[PANELS[i][j]]));
            //printf ("-%u:%u:%u", debugRedOut[PANELS[i][j]], debugGreenOut[PANELS[i][j]], debugBlueOut[PANELS[i][j]]);
            printf ("-%s", getNameFromColorNumber(channelColorSettings[PANELS[i][j]]));
        }
        printf ("\n\r");
    }
    printf("\n\r\n\r");
}

// called at 40Hz from main's Ticker
uint16_t timeCount=0;
uint16_t count=0;
void Sequencer() {
    led1 = !led1;
    
    if (TheMode == TEST) {
        return; // mode zero turns the sequencer off. Useful for testing or setting up a static display
    } else if (TheMode == SUN) {
        // sun mode
        for (int i=0; i< NUMBER_OF_PANELS;i++) {
            setChannelToColorName(i, COLOR_WHITE);
        }
        theChip->latchData();
        return;        
    }    
    
    // mode 1 is the standard mode
    manageTimedInteractions();
    if (SEQUENCER_RATE >= 0.25) {
        // only show debug output if the sequencer is running slow enough
        debugInteractions();
    }
}