Array of structs with constants too big size in RAM

Hi,
I am playing some music with 2 piezo elements and a standalone Atmega328P.
I store the frequencies of the notes in constants, then I created a struct, which contains the notes for both piezos and the length of the note, and I create an array of these structs, which is later passed to a function which plays the music.
Everything works fine, but as I add more and more music my RAM gets full too quickly.

Here is the code:

#include <Tone.h>

//Define the notes frq
#define G2 98
#define Gs2 104
#define Ab2 104
#define A2 110
#define As2 116

... and so on with many other music notes ...

#define Fs7 2960
#define Gb7 2960
#define G7 3136

//Rest
#define R 0

typedef struct {
  int n1;
  int n2;
  byte units;
} NOTES;

Tone buzzer1;  //The buzzer name is because of a mistake//
Tone buzzer2;

int marioTempo = 50;

NOTES superMario[] = {

                             {E5, Fs4, 3},
                             {E5, Fs4, 3},
                             {R, R, 3},
                             {E5, Fs4, 3},
                             {R, R, 3},
                             {C5, Fs4, 3},
                             {E5, Fs4, 3},
                             {R, R, 3},
                             {G5, G4, 6},
                             {R, R,  6},
                             {G4, G4, 6},
                             {R, R, 6},

                             {C5, E4, 6},
                             {R, R, 3},
                             {G4, C4, 6},
                             {R, R, 3},
                             {E4, G3, 6},
                             {R, R, 3},
                             {A4, C5, 3},
                             {R, R, 3},
                             {B4, D4, 3},
                             {R, R, 3},
                             {Bb4, Db4, 3},
                             {A4, C4, 6},

                             {G4, C4, 3},
                             {R, R, 1},
                             {E5,G4, 3},
                             {R, R, 1},
                             {G5, B4, 3},
                             {R, R, 1},
                             {A5,C5,  3},
                             {R, R, 3},
                             {F5, A4, 3},
                             {G5, B4, 3},

                             {R, R,  3},
                             {E5, A4, 3},
                             {R, R, 3},
                             {C5, E4, 3},
                             {D5, F4, 3},
                             {B4, D4, 6},
                             {R, R, 3},
                             
                             {C5, E4, 6},
                             {R, R, 3},
                             {G4, C4, 6},
                             {R, R, 3},
                             {E4, G3, 6},
                             {R, R, 3},
                             {A4, C4, 3},
                             {R, R, 3},
                             {B4, D4, 3},
                             {R, R, 3},
                             {Bb4, Db4, 3},
                             {A4, C4, 6},

                             {G4, C4, 3},
                             {R, R, 1},
                             {E5, G4, 3},
                             {R, R, 1},
                             {G5, B4, 3},
                             {R, R, 1},
                             {A5, C5, 3},
                             {R, R, 3},
                             {F5, A4, 3},
                             {G5, B4, 3},

                             {R, R, 3},
                             {E5, A4, 3},
                             {R, R, 3},
                             {C5, E4, 3},
                             {D5, F4, 3},
                             {B4, D4, 6},
                             {R, R, 3}        
};

void playSong(NOTES song[], size_t numNotes, int tempo)
{
  //We store the frw of the second pizo in this variable
  int secondFrq = 0;
  //Walk through the array of music
  for(int i = 0; i < numNotes; i++) 
  {
      // Only play if it is not a rest
      if(song[i].n1 > 0) 
        {
     
          //Play the note of the first piezo
          buzzer1.play(song[i].n1, (song[i].units*tempo));
          //If the frq of the second piezo is 0, we play the same note as the first, else the note set for the second one
          if(song[i].n2 == 0)
          {
              secondFrq = song[i].n1;
          }else{
              secondFrq = song[i].n2;
          }
          
          buzzer2.play(secondFrq, (song[i].units*tempo));  
      
        }
    
    //Then we wait for the note to end plus a little, between two notes
    delay((song[i].units*tempo) + 10);
    
  }
}


void setup() {
  buzzer1.begin(buzzer1Pin);
  buzzer2.begin(buzzer2Pin);
}


void loop() {
   playSong3(superMario, sizeof(superMario)/sizeof(superMario[0]), marioTempo);
}

I thought that I put references in the structs like:

typedef struct {
const int n1;
const int n2;
byte units;
} NOTES;

but surprisingly it took even more room in the RAM.

I read some about the PROGMEM but I am not sure if it is possible to put my array of struct and the constants to the PROGMEM.

If it is possible, how can I access it later?
I heard that it is quite diddicult to read from there.
Or what is the most efficient way to do this?

Thanks in advance for any help.

I would recommend assigning the notes consecutive numbers, then use a look-up table in PROGMEM to convert from note number to frequency. That way you could save almost half the memory by storing note numbers in a byte instead of storing a frequency in an int.

 enum NoteNumbers {R, G2, Gs2, Ab2, A2, As2
... and so on with many other music notes ...
Fs7,Gb7, G7};

const int NoteFrequencies[] PROGMEM = {0, 98, 104, 104, 110, 116
... and so on with many other music notes ...
2960, 2960, 3136};

HI,

Thank you for the idea and sorry for not answering for long time, but i did not have much time in the past few days because of my work.

So if I do it with enums, my declaration of just one note would look like;

NOTES oneNote = {pgm_read_word(&(NoteFrequencies[Fs3])), pgm_read_word(&(NoteFrequencies[Fs4])), 2}

instead of

NOTES oneNote = {Fs3, Fs4, 2};

And it would make quite a big mess in my program, and would unreadable.
Or did I misunderstand something?

But, I read about how to read and write structures from PROGMEM.
I tried out a library called PROGMEM_readAnything, which contains of just a couple of lines, tried to use the memcpy_P() function and the pgm_read_word, and pgm_read_byte functions, and always the same error.
As I loop through the array of NOTES it skips some of the elements, while reads and plays the others correctly.
It always skips the same elements, and not just random ones.
I even tried to change the microcontroller, thinking that certain parts of the chip may have been damaged by something, but uploading the same sketch I got the same results, so the microcontroller is probably intact.

Here is the code:

#include <Tone.h>
#include <avr/pgmspace.h>

//Define the notes frq
#define G2 98
#define Gs2 104
#define Ab2 104
#define A2 110
#define As2 116

... and so on with many other music notes ...

#define Fs7 2960
#define Gb7 2960
#define G7 3136

//Rest
#define R 0

typedef struct {
  int n1;
  int n2;
  byte units;
} NOTES;

Tone buzzer1;  
Tone buzzer2;

int myTempo = 100;

//Walkyrie
const NOTES walkyrie[] PROGMEM = {
  
                          {Fs3, Fs4, 2}, 
                          {B3, B4,3}, 
                          {Fs3, Fs4, 1}, 
                          {B3, B4, 2},
                          {D4, D5, 6},
                          {B3, B4, 6}, 
                          {D4, D5, 3},
                          {B3, B4, 1},
                          {D4, D5, 2},
                          {Fs4, Fs5, 6},
                          {D4, D5, 6},
                          {Fs4, Fs5, 3},
                          {D4, D5, 1},
                          {Fs4, Fs5, 2},
                          {A4, A5, 6},
                          {A3, A4, 6},
                          {D4, D5, 3},
                          {A3, A4, 1},
                          {D4, D5, 2},
                          {Fs4, Fs5, 6},
                          {R, 0, 4},
                          {A3, A4, 2},
                          {D4, D5, 3},
                          {A3, A4, 1},
                          {D4, D5, 2},
                          {Fs4, Fs5, 6},
                          {D4, D5, 6},
                          {Fs4, Fs5, 3},
                          {D4, D5, 1},
                          {Fs4, Fs5, 2},
                          {A4, A5, 6},
                          {Fs4, Fs5, 6},
                          {A4, A5, 3},
                          {Fs4, Fs5, 1},
                          {A4, A5, 2},
                          {Cs5, Cs6, 6},
                          {Cs4, Cs5, 6},
                          {Fs4, Fs5, 3},
                          {Cs4, Cs5, 1},
                          {Fs4, Fs5, 2},
                          {As4, As5, 6}
                          
                          
                          
                        };

void playSong()
{
  //We store the frq of the second pizo in this variable
  int secondFrq = 0;
  Serial.println(sizeof(walkyrie)/sizeof(walkyrie[0]));
  //Walk through the array of music
  for(int i = 0; i < sizeof(walkyrie)/sizeof(walkyrie[0]); i++) 
  {
     
      int n1;
      int n2;
      byte units;
      // Only play if it is not a rest
      if(walkyrie[i].n1 > 0) 
        {
          

          n1 = pgm_read_word(&(walkyrie[i].n1));
          n2 = pgm_read_word(&(walkyrie[i].n2));
          units = pgm_read_byte(&(walkyrie[i].units));
          
          Serial.print("Row ");
          Serial.print(i);
          Serial.print(": Frq1: ");
          Serial.print(n1);
          Serial.print(" Frq2: ");
          Serial.print(n2);
          Serial.print(" Units: ");
          Serial.println(units);
          
          //Play the note of the first piezo
          buzzer1.play(n1, (units*myTempo));
          //If the frq of the second piezo is 0, we play the same note as the first, else the note set for the second one
          if(n2 == 0)
          {
              secondFrq = n1;
          }else{
              secondFrq = n2;
          }
          
          buzzer2.play(secondFrq, (units*myTempo));  
      
        }
    
    //Then we wait for the note to end plus a little, between two notes
    delay((units*myTempo) + 10);
    
  }
} 


void setup() {
  Serial.begin(9600);
  buzzer1.begin(11);
  buzzer2.begin(12);
}

I added some lines to see in serial monitor what happens.
It reads the correct length…
The output of the serial monitor is the following:

41 (correct length)
Row 1: Frq1: 247 Frq2: 499 Units: 3 (row 0 - the first note is already missing)
Row 2: Frq1: 185 Frq2: 370 Units: 1
Row 3: Frq1: 247 Frq2: 499 Units: 2 (row 4 missing)
Row 5: Frq1: 247 Frq2: 499 Units: 6 (row 6-7 missing)
Row 8: Frq1: 294 Frq2: 587 Units: 2
Row 9: Frq1: 370 Frq2: 740 Units: 6
Row 10: Frq1: 294 Frq2: 587 Units: 6
Row 11: Frq1: 370 Frq2: 740 Units: 3
Row 12: Frq1: 294 Frq2: 587 Units: 1
Row 13: Frq1: 370 Frq2: 740 Units: 2
Row 14: Frq1: 440 Frq2: 880 Units: 6
Row 15: Frq1: 220 Frq2: 440 Units: 6 (row 16-17 missing)
Row 18: Frq1: 294 Frq2: 587 Units: 2
Row 19: Frq1: 370 Frq2: 740 Units: 6
Row 20: Frq1: 0 Frq2: 0 Units: 4
Row 21: Frq1: 220 Frq2: 440 Units: 2
Row 22: Frq1: 294 Frq2: 587 Units: 3
Row 23: Frq1: 220 Frq2: 440 Units: 1
Row 24: Frq1: 294 Frq2: 587 Units: 2
Row 25: Frq1: 370 Frq2: 740 Units: 6
Row 26: Frq1: 294 Frq2: 587 Units: 6
Row 27: Frq1: 370 Frq2: 740 Units: 3
Row 28: Frq1: 294 Frq2: 587 Units: 1
Row 29: Frq1: 370 Frq2: 740 Units: 2
Row 30: Frq1: 440 Frq2: 880 Units: 6
Row 31: Frq1: 370 Frq2: 740 Units: 6
Row 32: Frq1: 440 Frq2: 880 Units: 3
Row 33: Frq1: 370 Frq2: 740 Units: 1
Row 34: Frq1: 440 Frq2: 880 Units: 2
Row 35: Frq1: 554 Frq2: 1109 Units: 6
Row 36: Frq1: 277 Frq2: 554 Units: 6
Row 37: Frq1: 370 Frq2: 740 Units: 3
Row 38: Frq1: 277 Frq2: 554 Units: 1
Row 39: Frq1: 370 Frq2: 740 Units: 2
Row 40: Frq1: 466 Frq2: 932 Units: 6

Have you got any ideas why it is, or any more efficient ways to do that?

    // Only play if it is not a rest
    if (walkyrie[i].n1 > 0) {

That’s your problem. You are reading SRAM and finding some 0 (rest) values so you don’t display or play that row.

Change this part:

  for (int i = 0; i < sizeof(walkyrie) / sizeof(walkyrie[0]); i++) {
    int n1;
    int n2;
    byte units;
    // Only play if it is not a rest
    if (walkyrie[i].n1 > 0) {
      n1 = pgm_read_word(&(walkyrie[i].n1));
      n2 = pgm_read_word(&(walkyrie[i].n2));
      units = pgm_read_byte(&(walkyrie[i].units));

With this:

  for (int i = 0; i < sizeof(walkyrie) / sizeof(walkyrie[0]); i++) {
    unsigned int n1 = pgm_read_word(&(walkyrie[i].n1));
    unsigned int n2 = pgm_read_word(&(walkyrie[i].n2));
    byte units = pgm_read_byte(&(walkyrie[i].units));

    // Only play if it is not a rest
    if (n1 > 0) {

Hi, I dont know how I could make so big mistake...thank you very much for your help :slight_smile: