Can I Replace This MASSIVE Switch Statement?

I'm relatively new to Arduino and C++.... So please play nice, I'll try to be as thorough and helpful about my issue as I can.

To rough out my project I have a guitar amp connected via USB into a USB Sheild on the Arduino. The arduino is part of a controller board with footswitches and the board read/writes to the amp over USB to make changes.

=====

The amp has lots of parameters it can manipulate such as Volume. Each of these has it's own address inside the amp.

Inside my Arduino code I have this class:

class Parameter
{
public:
const unsigned long address;
int16_t index = 0;

Parameter(unsigned long a)
:
address(a)
{
}

Instantiated like:

Parameter paramVolume(0x6000043E);

This is so that the arduino board and the amp have the same list of parameters with the same addresses, so they can both talk back and forth in order to read/write the changes and keep in sync on both sides.

When something is changed on the amp it essentially spits out a memory address relating to a parameter on the amp, plus some data. So using the above example maybe '0x6000043E, 100' (the address for Volume on the amp, and telling us the volume is set to 100/100)

Inside my arduino code I have a function that deals with incoming data with the arguments 'unsigned long address' and 'byte data'. I'm hoping it can set a pointer:

Parameter *paramPTR;

to point to a Parameter if the address matches, and then sets the associated index to the incoming data.

Inside I have/want a switch statement that compares the incoming address to the 'Parameter::address' so that we can set the Parameter::index as the data.

My problem is two fold... I cannot use member variables in a switch statement (apparently even if they are marked as const)... Secondly the list of Parameters I need to compare the incoming address to is huge, as in 500+ addresses.

Here's a non-working example of what I was trying to achieve.

void parseData(unsigned long address, byte data)
{
      switch (address)
      {
          case paramVolume.address:
			paramPTR = &paramVolume;
             break;

          case paramExample1.address:
			paramPTR = &paramExample1;
              break;

          case paramExample2.address:
			paramPTR = &paramExample2;
              break;

          case paramExample3.address:
			paramPTR = &paramExample3;
              break;

// This goes on forever... 500+ addresses to check


          case paramExample499.address:
			paramPTR = &paramExample499;
              break;

          case paramExample500.address:
			paramPTR = &paramExample500;
              break;
      }

      paramPTR->index = data;
}

I've had this working previously before I implemented the Parameter class because all of the addresses I had been comparing to had been global variables marked as const very big, very messy.

Since moving everything inside the class, and using pointers to objects, the switch statement firstly doesn't work against member variables... and secondly is so massive I can't help but wonder if there's something better (maybe even faster) I can use to compare the incoming address to the massive list, in order to set data effectively.

Any thoughts?

Many thanks!

Steve.

Thanks for using code tags but because you have the WYSIWYG option turned on in your profile the forum software make a mess of things by adding tags to the code (not your fault)

Please turn off the WYSIWYG option and post the code again

Many thanks

Thanks, UKHeliBob. I did wonder what was going on with that! I've cut all the colour tags out on an edit. Hope it still makes sense.

To continue slightly...

What's annoying is I could achieve this with a HUGE 'if/else if' list like this:

if (parameter == paramVolume.address) paramPTR=&paramVolume;
else if (parameter == paramExample1.address) paramPTR=&paramExample1;
else if (parameter == paramExample2.address) paramPTR=&paramExample2;
else if (parameter == paramExample3.address) paramPTR=&paramExample3;
// (.....)
else if (parameter == paramExample499.address) paramPTR=&paramExample499;
else if (parameter == paramExample500.address) paramPTR=&paramExample500;

paramPTR->index = data;

Make your paramExampleX objects an array of objects then you can loop through them:

if (parameter == paramVolume.address) 
{
  paramPTR=&paramVolume;
}
else
{
   int i;
   for (i = 0; i < 500; i++)
   {
     if (parameter == paramExample[i].address) 
     {
        paramPTR=&paramExample[i];
        break;
     }
   }

  if (i >= 500)
  {
   // match was not found.  Perform error handling here.
  }
}

paramPTR->index = data;

I am completely curious about a guitar amp with 500 parameters. Point us at that somewhere so we can appreciate what you are dealing with. And marvel.

It looks like there might be opportunities to do something besides stepping through, however you do it, 500 items looking for a match.

a7

alto777:
I am completely curious about a guitar amp with 500 parameters. Point us at that somewhere so we can appreciate what you are dealing with. And marvel.

It looks like there might be opportunities to do something besides stepping through, however you do it, 500 items looking for a match.

a7

Can do! It's the Boss Katana MKII guitar amp. The amplifier itself is very simple with Bass, Middle, Treble, Volume, Gain, Reverb, Mod, Fx, Delay, Boost knobs.... but with the super awesome fantastic 'Boss Tone Studio' program (PC/Mac) connected via USB you can fine-tune all sorts of incredible parameters. So for reverb you can adjust the reverb time, pre-delay, low cut etc.

I'll attach a TXT that contains all of the sysex addresses so you can see how many params we can adjust, and I'll also throw in a picture of my board if you're curious!

None of this is possible from just the amp... Only with a PC/Mac connected. That was... until I made this foot controller board with rotary encoders anyway. Now it's all available via footswitches, and a fancy menu system.

It was working great until I started refactoring the code and added the new class then my parseData function broke.

KATANA_MKII_MIDI_MAP.txt (77 KB)

Very cool!

I added in an array of pointers to all off the parameter objects which is big (590 of them!) and tried adding this:

void parseData(unsigned long address, byte data)
{
      for (byte i = 0; i < numOfParams; i++)
      {
            if (parameter == allParams[i]->address)
            {
                  paramPTR = allParams[i];
            }
            break;
      }
      paramPTR->index = data;

}

Aside from endlessly scrolling past my array of parameter pointers it's a nice clean way to check every address but now size became a big issue.

Before this addition by code was:
RAM: [===== ] 50.8% (used 4162 bytes from 8192 bytes)
Flash: [== ] 15.6% (used 39672 bytes from 253952 bytes)

After adding it in it's now:

RAM: [==========] 168.9% (used 13835 bytes from 8192 bytes)
Flash: [=== ] 31.7% (used 80522 bytes from 253952 bytes)

Ouch. I could maybe park the array of pointers to the Parameter objects in PROGMEM (like I have with all the LCD strings), but I wonder if this might affect the speed of reading/parsing the incoming data.

why do you need a pointer at all, make already an array of objects, than you have no need for an additional pointer

Parameter paramVolume[500] {0x6000043E, ... endless list };

I can't see how an array would cause you a memory problem unless you still have the individual instances of your class too. Or in other words - post your code.

according to his post #7 he has added pointers.

I (still) suggest a simple array of objects.

class Parameter
{
  public:
    const unsigned long address;
    int16_t index = 0;

    Parameter(unsigned long a) :
      address(a)
    {}
};

Parameter paramVolume[] ={
  0x42,
  0x4242,
  0x424242,
  0x42424242,
  0x6000043E,
};


void setup() {
  Serial.begin(115200);

  // simulate some member values
  paramVolume[0].index = 42;
  paramVolume[1].index = 41;
  paramVolume[2].index = 40;
  paramVolume[3].index = 39;
  paramVolume[4].index = 38;

  unsigned long lookup = 0x4242;
  
  // switch case replacement
  for (auto & i : paramVolume)
  {
    if (i.address == lookup)
    {
      Serial.print("found "); Serial.println(i.index);
      break;
    }
  }
}

void loop() {
}

The code itself is too big to post, plus it's a royal mess as I'm in the middle of redoing it... but I'll try and post what I have that's relevant. I tried to post representations of my approach before but maybe it wasn't enough.

Here is my real Parameter class: (as an aside, 'sysex' is the address I was talking about before (called parameter in the parseData function) and as before the data will need to go into index).

#ifndef PARAMETER_H
#include "Arduino.h"

enum ParamType
{
CYCLE = 0,
CAPPED = 1,
};

class Parameter
{

public:
    const char* name;
    const char *const *namearray;
    const unsigned long sysex;
    int16_t iMin;
    int16_t iMax;
    int16_t index = 0;
    ParamType type;
    char c;


    Parameter(const char* nm, unsigned long sys, int16_t mn, int16_t mx, ParamType t)
        : 
        name(nm),
        sysex(sys),
        iMin(mn),
          iMax(mx),
          type(t)
    {
    }

    Parameter(const char* nm, const char *const *na, unsigned long sys, int16_t mn, int16_t mx, ParamType t)
        : 
        name(nm),
        namearray(na),
        sysex(sys),
        iMin(mn),
          iMax(mx),
          type(t)
    {
    }

    void adjustParam(byte direction)
    {

        if (direction == ::Direction::INCREASE)
        {
            if (type == CAPPED && index != iMax)
            {
                index++;
            }
            else if (type == CYCLE)
            {
                index++;
                if (index > iMax)
                {
                    index = iMin;
                }
            }
        }
        else if (direction == ::Direction::DECREASE)
        {
            if (type == CAPPED && index != iMin)
            {
                index--;
            }
            else if (type == CYCLE)
            {
                index--;
                if (index < iMin)
                {
                    index = iMax;
                }
            }
        }
    }
};
#endif

There's 590 of these but here are 20 of the Parameter that are globals above void Setup():

Parameter PRM_SYS_USB_IN_OUT_MODE(string_Blank, 0x00000000, 0, 0, ParamType::CAPPED);
Parameter PRM_SYS_USB_INPUT_LEVEL(string_Blank, 0x00000001, -20, 20, ParamType::CAPPED);
Parameter PRM_SYS_USB_OUT_LEV(string_Blank, 0x00000002, 0, 100, ParamType::CAPPED);
Parameter PRM_SYS_USB_MIX_LEV(string_Blank, 0x00000003, 0, 100, ParamType::CAPPED);
Parameter PRM_SYS_USB_DIR_MONITOR(string_Blank, 0x00000004, 1, 1, ParamType::CAPPED);
Parameter PRM_SYS_USB_DIR_MONITOR_CMD(string_Blank, 0x00000005, 0, 0, ParamType::CAPPED);
Parameter PRM_SYS_USB_LOOPBACK(string_Blank, 0x00000006, 0, 1, ParamType::CAPPED);
Parameter PRM_SYS_USB_2_OUT_LEV(string_Blank, 0x00000007, 0, 100, ParamType::CAPPED);
Parameter PRM_SYS_EQ_SW(string_Blank, 0x00000010, 0, 1, ParamType::CAPPED);
Parameter PRM_SYS_EQ_TYPE(string_Blank, 0x00000011, 0, 1, ParamType::CAPPED);
Parameter PRM_SYS_EQ_POSITION(string_Blank, 0x00000012, 0, 1, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_LOW_CUT(string_Blank, 0x00000013, 0, 17, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_LOW_GAIN(string_Blank, 0x00000014, -20, 20, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_LOWMID_FREQ(string_Blank, 0x00000015, 0, 27, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_LOWMID_Q(string_Blank, 0x00000016, 0, 5, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_LOWMID_GAIN(string_Blank, 0x00000017, -20, 20, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_HIGHMID_FREQ(string_Blank, 0x00000018, 0, 27, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_HIGHMID_Q(string_Blank, 0x00000019, 0, 5, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_HIGHMID_GAIN(string_Blank, 0x0000001A, -20, 20, ParamType::CAPPED);
Parameter PRM_SYS_EQ_PEQ_HIGH_GAIN(string_Blank, 0x0000001B, -20, 20, ParamType::CAPPED);

and then parseData has something along the lines of:

void parseData(unsigned long parameter, byte data)
{
      for (byte i = 0; i < numOfParams; i++)
      {

/*  [Some sort of array solution]
 if (parameter == allParams[i]->sysex) // 
            {
                  paramPTR = allParams[i];
            }
            break;
*/
      }
      paramPTR->index = data;

}

I think an array of Parameters makes sense but it's going to get hard to do anything without variable names.

Managing something like:
allParams[376].adjustParam(1); is tricky and very error prone. What if I meant 365 or typed 367 by mistake. Especially when there are 500+ of these splattered about.

Where as PRM_SYS_EQ_SW.adjustParam(1); is at least quite descriptive.

As before I tried creating an array that simply points to each object but that seemed to sky rocket the ram.

You could set up a humungous enum so you don't have to use the literal array indexes, but I think you need something more clever to map what you're getting from the foot switches to the appropriate parameter. Need to see the code - which you can attach.

This approach can and will work:

#define numOfParams 20

class Parameter
{

public:
    const unsigned long sysex;
    int16_t index = 0;



    Parameter(unsigned long sys)
        :
        sysex(sys)
    {
    }


void displayIndex()
    {
        Serial.print(index);
    }
};


Parameter allParams[] = 
{
    Parameter(0x60000001), 
    Parameter(0x60000002), 
    Parameter(0x60000003),
    Parameter(0x60000004),
    Parameter(0x60000005),
    Parameter(0x60000006),
    Parameter(0x60000007),
    Parameter(0x60000008),
    Parameter(0x60000009),
    Parameter(0x60000010),
    Parameter(0x60000011),
    Parameter(0x60000012),
    Parameter(0x60000013),
    Parameter(0x60000014),
    Parameter(0x60000015),
    Parameter(0x60000016),
    Parameter(0x60000017),
    Parameter(0x60000018),
    Parameter(0x60000019),
    Parameter(0x60000020)
};




void setup() {
  Serial.begin(9600);

int incomingData = 69;
unsigned long lookup = 0x60000017; // What we want to find

  for(int i = 0; i < numOfParams; i++)
{
   if (allParams[i].sysex == lookup)
   {
       Serial.print("Found: ");
       Serial.print(allParams[i].sysex);
       allParams[i].index = incomingData;
       Serial.print(" with a new index of: ");
       allParams[i].displayIndex();
       break;
    }
}
    
}


void loop() {
  // put your main code here, to run repeatedly:

}

If only I could access each Parameter by a variable name. Which leads my thinking back to an array of pointers as I mentioned before.

You can have the constructors inside Parameter put thier addresses and references into some sort of global key/value map. I haven't used it, but there is probably one in c++ stdlib. You would then look up the map.

I would be inclined to put these things into the Parameter class. I don't have a C++ compiler with me at present, so I'm just guessing at the syntac

#include <map>

class Parameter
{
  const unsigned long address;
  static map<unsigned long, Parameter *p> param;

public :
  Parameter(unsigned long a) : address(a) {
    params[a] = this;
  }

  virtual void frob() = 0;

  static void frob(unsigned long addr) {
    param[addr].frob();
  }
}

If map is not available to Arduino programming, then another way is to dynamically allocate a linked list.

class Parameter
{
  const unsigned long address;
  static Parameter *head;
  Parameter *next;

public :
  Parameter(unsigned long a) : address(a) {
    next = head;
    head = this;
  }

  virtual void frob() = 0;

  static void frob(unsigned long addr) {
    for(Parameter *p = head; p; p=p->next) {
      if(p->address == addr) {
        param[p].frob();
        return;
      }
    }
  }
}

Parameter::head = NULL;

If iterating through that list bugs you, then code up a splaytree. This will work well in this situation, because fooling with control knobs means a series of consecutive accesses to that parameter.

Appreciate all the help!

I think the issue however it's spun is just going to be simply that this is too much to store easily on the Mega. Especially when I've already parked a big list of const char[] strings in PROGMEM already and even then I was bumping up against 50% RAM usage.

I think I got excited when I added in all the Parameters to the sketch and it compiled fine and RAM wasn't in issue. I assume because I hadn't DONE anything those 'Parameters' they all got optimised out. And when I tried to use them in parseData or anywhere else they had to be included and smashed the RAM.

With my full set of the data in question (and the class) pulled out into a test sketch I get:

Sketch uses 42688 bytes (16%) of program storage space. Maximum is 253952 bytes.
data section exceeds available space in board
Global variables use 9704 bytes (118%) of dynamic memory, leaving -1512 bytes for local variables. Maximum is 8192 bytes.

If I remove the names from the class, (this version is attached) I get a successful find with the full set:

Found: 131082 with a new index of: 69

but the sizes are still:

Sketch uses 37912 bytes (14%) of program storage space. Maximum is 253952 bytes.
Global variables use 7332 bytes (89%) of dynamic memory, leaving 860 bytes for local variables. Maximum is 8192 bytes.

I will need the names there so that's not good. But mostly, it just doesn't leave any room as this dataset is an addition to what was already a working model and I was at around 50% before RAM before adding these in.

Seeing as everything is constant in the dataset other than the index... Maybe I can keep all of the data in PROGMEM and have a matching array of indexes globally? Not sure how that would go down though :smiley:

Do Arduino make a model with more RAM? Arduino Giga? :cold_sweat:

ParamClassTest.ino (43.1 KB)

st3v3n92:
Do Arduino make a model with more RAM? Arduino Giga? :cold_sweat:

Yes, several. There's the new(ish) Nano 33 range, also the Due if you need more pins. Outside of true Arduinos, there is the Teensy range. Then there are ESPxxx and Adafruit's range of Feathers. Note though that most (if not all) of these are 3V3 although some of the Teensys are 5V tolerant.

So after a bit of digging, I found some helpful posts/info from a Mr Nick Gammon: Gammon Forum : Electronics : Microprocessors : Putting constant data into program memory (PROGMEM) the 'PROGMEM_readAnything' library idea was interesting and after a bit of messing about and a lot of trial and error I've created this possible solution (Attached).

Any thoughts on this?

Although my work is messy, running this I now get this output:

 - Simulating bad address - 
Searching For: 60000503
Nothing Found


 - Simulating good address - 
Searching For: 60000501

Found: 
===============
Selected Parameter: Address = 60000501 | Min = 0 | Max = 10 | Type = CAPPED
===============




### Full Parameter List ###
===============
Selected Parameter: Address = 60000500 | Min = 0 | Max = 1 | Type = CAPPED
===============

===============
Selected Parameter: Address = 60000501 | Min = 0 | Max = 10 | Type = CAPPED
===============

===============
Selected Parameter: Address = 60000502 | Min = 1 | Max = 2000 | Type = CAPPED
===============

I'm yet to add in the full 590 other Parameters to test but so far so good!

I guess the next step is to have a global array of int16_t's matched up as the indexes (not in progmem as they are variable), and possible a const 'ID' number added to the Parameters so I could reference the index as something along the lines of: allIndexes[selectedParam.ID]

And then maybe add back in the names, and we're away! (I hope!)

PROGMEM_readAnything.h (382 Bytes)

PROGMEM_Store_Test.ino (2.46 KB)