ATtiny13 Melody player, store Array in Progmem.

I received a few ATtiny13's in the mail the other day, and got going on my melody player (which is what i wanted them for) and of course memory is tiny, and i want to store as big a melody as possible. So far i got to this

#include "notes.h"

#define PIEZOPIN 4

#define BPM 100
#define QUART 60000/BPM
#define EIGHTH QUART/2
#define THREIGHTH EIGHTH*3
#define SIXTEENTH QUART/4
#define THRSIX SIXTEENTH*3
#define HALF QUART*2

void setup() {
  pinMode(PIEZOPIN,OUTPUT);
}

void loop() {
  buzz(0,EIGHTH);
  buzz(0,THRSIX);
  buzz(0,SIXTEENTH);

  buzz(3,QUART);
  buzz(3,QUART);
  buzz(4,QUART);
  buzz(4,QUART);

  buzz(7,THREIGHTH);
  buzz(5,EIGHTH);
  buzz(3,THRSIX);
  buzz(3,SIXTEENTH);
  buzz(5,THRSIX);
  buzz(3,SIXTEENTH);

  buzz(1,QUART);
  buzz(6,QUART);
  buzz(6,QUART);
  buzz(4,THRSIX);
  buzz(2,SIXTEENTH);

  buzz(3,HALF);
  wait(QUART);
  buzz(3,THRSIX);
  buzz(4,SIXTEENTH);

  buzz(5,QUART);
  buzz(5,QUART);
  buzz(5,QUART);
  buzz(6,THRSIX);
  buzz(5,SIXTEENTH);
    
}


void buzz(uint8_t note, uint16_t notelength) {
  //const uint16_t notelength[]={HALF,THREIGHTH,QUART,THRSIX,EIGHTH,SIXTEENTH};
  const uint16_t period[]={M64,M66,M68,M69,M71,M73,M74,M76};

  uint16_t ms=millis();
  notelength=notelength/8;
  while (((uint16_t) millis()-ms)<notelength*7) {
    uint16_t halfperiod=period[note]/2;
    digitalWrite(PIEZOPIN,HIGH);
    wait(halfperiod);
    digitalWrite(PIEZOPIN,LOW);
    wait(period[note]-halfperiod);
  }
  delay(notelength);
}

void wait(uint32_t us) {
  for(uint32_t mics=0; mics<us; mics++)  delayMicroseconds(1);
}

and for notes.h just using the intervals in us

#define M21 36360
#define M22 34320
#define M23 32400
#define M24 30580
#define M25 28860
#define M26 27240
#define M27 25710
#define M28 24270
#define M29 22910
#define M30 21620
#define M31 20410
#define M32 19260
#define M33 18180
#define M34 17160
#define M35 16200
#define M36 15290
#define M37 14290
#define M38 13620
#define M39 12860
#define M40 12130
#define M41 11450
#define M42 10810
#define M43 10200
#define M44 9631
#define M45 9091
#define M46 8581
#define M47 8099
#define M48 7645
#define M49 7216
#define M50 6811
#define M51 6428
#define M52 6068
#define M53 5727
#define M54 5405
#define M55 5102
#define M56 4816
#define M57 4545
#define M58 4290
#define M59 4050
#define M60 3822
#define M61 3608
#define M62 3405
#define M63 3214
#define M64 3034
#define M65 2863
#define M66 2703
#define M67 2551
#define M68 2408
#define M69 2273
#define M70 2145
#define M71 2025
#define M72 1910
#define M73 1804
#define M74 1703
#define M75 1607
#define M76 1517
#define M77 1432
#define M78 1351
#define M79 1276
#define M80 1204
#define M81 1136
#define M82 1073
#define M83 1012
#define M84 956
#define M85 902
#define M86 851
#define M87 803
#define M88 758
#define M89 716
#define M90 676
#define M91 638
#define M92 602
#define M93 568
#define M94 536
#define M95 506
#define M96 478

now of course i am trying to reduce the size of memory being used, and the main inefficiency i see now is that i am doing calls to 'buzz' which costs me 3 bytes every note (just for the call, op-code+address) which i could also do from a loop just retrieving the 3 note related bytes on each pass (which could actually be 2 bytes, if i compact the 5-bit note and a 11-bit length into a 16-bit value) but how to do this, i am sure i've seen it pass by somewhere.

There is an Arduino referance page on using Progmem.

srnet:
There is an Arduino referance page on using Progmem.

Ta ! thank you, these are the things that elude my mind while crunching on different issues. btw actually i think i can get away with 8-bit per note (16 different pitches, for 16 different times)

Yeah so all very cool and all but somehow i didn't get it to work ! when i upload this (to an UNO cause debugging an ATtiny is rather tricky)

const uint16_t lengths[]   = {0,2400,1800,1200,900,600,450,300,150};

// rest =0, D=1, D#=2, E=3, F=4, F#=5, G=6, G#=7, A=8, A#=9, B=A, C=B, C#=C, D=D, D#=E, E=F.
// ls nibble = pitch , ms nibble = length

//http://en.tapartoche.com/form/page-partition.php?miniature=7197.gif
const uint8_t melody[] PROGMEM = 
     {0x03, 0x06, 0x17, 0x16, 0x18,          0x65, 0x65, 0x85, 0x85,                0xD2, 0x97, 0x66, 0x68, 0xA6, 0x68,
      0x35, 0xB5, 0xB5, 0x86, 0x58,          0x63, 0x05, 0x66, 0x88,                0xA5, 0xA5, 0xA5, 0xB6, 0xA8,
      0xA5, 0x85, 0x05, 0x86, 0xA8,          0xB5, 0xB5, 0xB5, 0xC6, 0xB8,          0xA3, 0x05, 0xC6, 0xC8,
      0xC5, 0xA6, 0x68, 0xC5, 0xA6, 0x68,    0x13, 0x06, 0x17, 0x16, 0x58,          0x83, 0xB5, 0x86, 0x58,
      0x63, 0x43,                            0x35, 0x66, 0x68, 0x65, 0x56, 0x68,    0x63, 0x65, 0x07, 0x67,
      0x94, 0xA7, 0xA7, 0xA7, 0xB7, 0xD7,    0x83, 0x85, 0x97, 0x87,                0x64, 0x67, 0x67, 0x97, 0x87, 0x67,
      0x65, 0x55, 0x05, 0x06, 0xD8,          0xD3, 0x07, 0xD7, 0xA6, 0x68,          0x83, 0x05, 0x06, 0xD8, 
      0xD3, 0x07, 0xD7, 0xA6, 0x68,        0x83, 0x05, 0x15,         0x63, 0x05, 0x85,                  0x1A,   
      0xB3, 0xD5, 0xF5,                    0x83, 0x05, 0xF5,         0xD3, 0xD6, 0xA8, 0xB6, 0xA8,      0x63, 0x03,        
                                  0x0};  // and the terminating '0'




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

}

void loop() {
  static uint8_t count=0;
  if (count>20) while(1);
  else {
    //digitalWrite(PIEZOPIN,pinstate);

    uint8_t len=melody[count] ;
    Serial.println(len,HEX);
  }
  count++;
}

i get an output of

0
F8
FE
FF
0
0
B8
B8
2
B8
6
B8
CF
0
0
B8
B8
B8
B8
B8
B8

and when i remove the word 'PROGMEM'

3
6
17
16
18
65
65
85
85
D2
97
66
68
A6
68
35
B5
B5
86
58
63

which is what i should be getting ! i am using the syntax as by reference aren't I ?

Did you read this?

I did read it, but not very accurately, found the answer here (which is actually what is saw coming through that made me order these chippies in the first place)

Deva_Rishi:
I did read it, but not very accurately, found the answer here (which is actually what is saw coming through that made me order these chippies in the first place)

The answer is in the reference page, complete with examples.

@Deva_Rishi, pgm_read_byte

EDIT : read to the end of this post for important clarification

An example using an array of strings from my current project

Putting the array in PROGMEM

const byte GRAB = 8;
const byte DROP = 9;

const byte MAX_COMMANDS = 100;
char * const sequences[][MAX_COMMANDS] PROGMEM =
{
  //sequence 0
  {
    "1 block at 90 main commands only",
    "g120", //gripper open

    "b90",  //base 90
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b120", //base 120
    "s160", "g120", "s90", //forward, release, retract
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b90", //base 90
    "s160", "g120", "s90", //forward, release, retract

    "end"
  },
  //sequence 1
  {
    "1 block at 90 main commands only",
    "g120", //gripper open

    "b90", "e40", "s160", "g170", "s90", //forward, grip, retract

    "b150", "s160", "g120", "s90", //forward, release, retract
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b120", "s160", "g120", "s90", //forward, release, retract
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b90", "s160", "g120", "s90", //forward, release, retract
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b60", "s160", "g120", "s90", //forward, release, retract
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b30", "s160", "g120", "s90", //forward, release, retract
    "e40", "s160", "g170", "s90", //forward, grip, retract

    "b90", "s160", "g120", "s90", //forward, release, retract

    "end"
  },
etc, etc

Reading from the array

      strcpy(inputBuffer, (char *) pgm_read_word (&sequences[sequenceNumber][commandIndex]));

You can use similar methods for an array with a different data type

EDIT : As pointed out in later posts in this thread the method used here puts the pointers to the strings in PROGMEM, not the strings themselves.

UKHeliBob:
An example using an array of strings from my current project

the strings are not in PROGMEM. only the pointers to them.
if they were in PROGMEM then you would need to use strcpy_P

Juraj:
the strings are not in PROGMEM. only the pointers to them.
if they were in PROGMEM then you would need to use strcpy_P

This was discussed extensively in another thread that I started and in which you particpated problem using strcmp_P

As PaulS said in that thread

You want to consider whether saving 4 bytes of SRAM by putting sequences in PROGMEM is worth the extra level of complexity in accessing the data.

I was happy with the memory reduction that I had achieved so I stopped trying to improve it.

UKHeliBob:
This was discussed extensively in another thread that I started and in which you particpated problem using strcmp_P

As PaulS said in that thread
I was happy with the memory reduction that I had achieved so I stopped trying to improve it.

It is OK if it is OK for you, but I must point it out for the other readers who may be mislead

btw: Paul_S's comment was about this 4 bytes in your snippet there
char * const  PROGMEM sequences[] = {s1, s2};
his point was to simplify the code by not storing the pointers in PROGMEM, only the strings

UKHeliBob:
This was discussed extensively in another thread that I started and in which you particpated problem using strcmp_P

As PaulS said in that thread
I was happy with the memory reduction that I had achieved so I stopped trying to improve it.

In that case, could you maybe edit your earlier reply #9 to make this clear?
The initial impression is that the strings are stored in PROGMEM.

AWOL:
In that case, could you maybe edit your earlier reply #9 to make this clear?
The initial impression is that the strings are stored in PROGMEM.

Point taken

I will edit the post to clarify what is put into PROGMEM

Oh dear, i go to put the radiator back in my car and a whole discussion is has happened.

AWOL:
The answer is in the reference page, complete with examples.

So true, if only i were to read properly all the time anyway problem resolved, now after dinner i can look at doing some tuning

#define PIEZOPIN 4
#define CORR 2 // negative us correction on delay (twice per cycle)
#define POSCORR 1 // positive correction on delay once per cycle 
#define NOTECORR 1 // note specific correction once per cycle

#define BPM 115

#define QUART 4
#define EIGHTH 2
#define THREIGHTH 6
#define SIXTEENTH 1
#define THRSIX 3
#define HALF 8
#define WHOLE 16
#define THRQUART 12


#include "notes.h"


const uint8_t lengths[] PROGMEM = {0,WHOLE,THRQUART,HALF,THREIGHTH,QUART,THRSIX,EIGHTH,SIXTEENTH};


// rest =0, D=1, D#=2, E=3, F=4, F#=5, G=6, G#=7, A=8, A#=9, B=A, C=B, C#=C, D=D, D#=E, E=F.
// ls nibble = pitch , ms nibble = length

//const uint8_t melody[] PROGMEM = {0x15, 0x05, 0x0};

//http://en.tapartoche.com/form/page-partition.php?miniature=7197.gif
const uint8_t melody[] PROGMEM = 
     {0x03, 0x06, 0x17, 0x16, 0x18,          0x65, 0x65, 0x85, 0x85,                0xD2, 0x97, 0x66, 0x68, 0xA6, 0x68,
      0x35, 0xB5, 0xB5, 0x86, 0x58,          0x63, 0x05, 0x66, 0x88,                0xA5, 0xA5, 0xA5, 0xB6, 0xA8,
      0xA5, 0x85, 0x05, 0x86, 0xA8,          0xB5, 0xB5, 0xB5, 0xC6, 0xB8,          0xA3, 0x05, 0xC6, 0xC8,
      0xC5, 0xA6, 0x68, 0xC5, 0xA6, 0x68,    0x13, 0x06, 0x17, 0x16, 0x58,          0x83, 0xB5, 0x86, 0x58,
      0x63, 0x43,                            0x35, 0x66, 0x68, 0x65, 0x56, 0x68,    0x63, 0x65, 0x07, 0x67,
      0x94, 0xA7, 0xA7, 0xA7, 0xB7, 0xD7,    0x83, 0x85, 0x97, 0x87,                0x64, 0x67, 0x67, 0x97, 0x87, 0x67,
      0x65, 0x55, 0x05, 0x06, 0xD8,          0xD3, 0x07, 0xD7, 0xA6, 0x68,          0x83, 0x05, 0x06, 0xD8, 
      0xD3, 0x07, 0xD7, 0xA6, 0x68,        0x83, 0x05, 0x15,         0x63, 0x05, 0x85,                  0x1A,   
      0xB3, 0xD5, 0xF5,                    0x83, 0x05, 0xF5,         0xD3, 0xD6, 0xA8, 0xB6, 0xA8,      0x63, 0x03,        
                                  0x0};  // and the terminating '0'

void setup() {
  //Serial.begin(9600);
  pinMode(PIEZOPIN,OUTPUT);
  digitalWrite(PIEZOPIN,LOW);
}

void loop() {
  static uint8_t count=0;  
  uint8_t note=pgm_read_byte_near(melody+count);
  count++;
  if (!note) count=0;
  else play(note);
}

void play(uint8_t note) {  
  uint8_t len = note & 0x0F; // ls nibble
  note=note>>4; // ms nibble
  uint16_t notelength=pgm_read_byte_near(lengths+len);
  notelength=BPMnote(notelength);
  if (!note) {
    delay(notelength);
    return;
  }
  uint16_t ms=millis();
  notelength=notelength/4;
  
  while (((uint16_t) millis()-ms) < (notelength*3)) {
    digitalWrite(PIEZOPIN,HIGH);
    interval(note);
    if (POSCORR) delayMicroseconds(POSCORR);
    digitalWrite(PIEZOPIN,LOW);
    interval(note);
  }  
  delay(notelength);
}

uint16_t BPMnote(uint32_t nl) {
  nl=((nl*BPM)*1825)/30;
  return nl;
}

void interval(uint8_t type) {
  switch (type) {
    case 1: 
      delayMicroseconds(M64/2-CORR);
      return;
    case 2:
      delayMicroseconds(M65/2-CORR);
      return;
    case 3: 
      delayMicroseconds(M66/2-CORR);
      return;
    case 4:
      delayMicroseconds(M67/2-CORR);
      return;
    case 5: 
      delayMicroseconds(M68/2-CORR);
      return;
    case 6:
      delayMicroseconds(M69/2-CORR);
      return;
    case 7: 
      delayMicroseconds(M70/2-CORR);
      return;
    case 8:
      delayMicroseconds(M71/2-CORR);
      return;
    case 9: 
      delayMicroseconds(M72/2-CORR);
      return;
    case 10:
      delayMicroseconds(M73/2-CORR);
      return;
    case 11: 
      delayMicroseconds(M74/2-CORR);
      return;
    case 12:
      delayMicroseconds(M75/2-CORR);
      return;
    case 13: 
      delayMicroseconds(M76/2-CORR);
      return;
    case 14:
      delayMicroseconds(M77/2-CORR);
      return;
    case 15: 
      delayMicroseconds(M78/2-CORR);
      return;
  }
}

so far i've thought about tackling the compensation for the cycles that take place during the checking for the note-length, and the oscillation of the note itself, but calculating that might be more work than just tuning them. And i still have some 200 bytes left to add a slowing of the melody for the last few notes and/or some other little things. One of the issues with the ATtiny core is that there is no micros() and the parameter for delayMicroseconds() needs to be known at compile time, but a loop is going to be so inaccurate..

Oops, this nl=((nl*BPM)*1825)/30;should benl=(nl*15000)/BPM;the BPM should be in the divider d'oh !

So i got around to tuning it using my Guitar, my ears and a slight modification of the sketch which just sounds 2 notes 1 octave apart and when i decided that in my case i was nearly one and a half notes up, i was faced with either changing all the note values (which actually are the real interval values in uS referred to as midi-notes) or doing some multiplier thing... neither seemed like the way to go until i realized the cause being the internal clock just being a little faster than defined. So by adding #define F_CPU 1300000UL i was an awful lot closer, and then i actually ended up bringing the notes closer together just a tad, the compensation for the cycles used must have been excessive. (i had increased it up to 2X8 us per cycle, which brought the 'LOW E' below the one on my guitar, and the 'HIGH E' above it. ) In the end i decided on this

#define F_CPU 1290000UL  // increasing this value will lower the tuning
                        // this is chip specific the standard value is 1200000UL (1.2MHz)

#define PIEZOPIN 4
#define CORR 1 // negative us correction on delay (twice per cycle)
               // increase will increase distance between notes
#define POSCORR  0// positive correction on delay once per cycle 


#define TUNE M78  // set here the note to be tuned

#define QUART 4
#define EIGHTH 2
#define THREIGHTH 6
#define SIXTEENTH 1
#define THRSIX 3
#define HALF 8
#define WHOLE 16
#define THRQUART 12


#include "notes.h"


const uint8_t lengths[] PROGMEM = {0,WHOLE,THRQUART,HALF,THREIGHTH,QUART,THRSIX,EIGHTH,SIXTEENTH};


// rest =0, D=1, D#=2, E=3, F=4, F#=5, G=6, G#=7, A=8, A#=9, B=A, C=B, C#=C, D=D, D#=E, E=F.
// ls nibble = pitch , ms nibble = length

// const uint8_t melody[] PROGMEM = {0x31, 0xF1, 0x0};  octave 'E' used for tuning

//http://en.tapartoche.com/form/page-partition.php?miniature=7197.gif
const uint8_t melody[] PROGMEM = {0x0F, 115,     // set BPM 115
      0x03, 0x06, 0x17, 0x16, 0x18,            0x65, 0x65, 0x85, 0x85,                0xD4, 0xA7, 0x66, 0x68, 0xA6, 0x68,
      0x35,   14, 0xB5, 0xB5, 0x86, 0x58,      0x63, 0x05, 0x66, 0x88,                0xA5, 0xA5, 0xA5, 0xB6, 0xA8,
      0xA5, 0x85, 0x05, 0x86, 0xA8,            0xB5, 0xB5, 0xB5, 0xD6, 0xB8,          0xA3, 0x05, 0xD6, 0xD8,
      0xD5, 0xA6, 0x68, 0xD5, 0xA6, 0x68,      0x13, 0x06, 0x17, 0x16, 0x58,          0x83, 0xB5, 0x86, 0x58,
      0x63, 0x43,                              0x35, 0x66, 0x68, 0x65, 0x56, 0x68,    14, 0x83, 0x85, 0x07, 0x87,
      0x94, 14, 0xA7, 0xA7, 0xA7, 0xB7, 0xD7,    14, 0x83, 0x85, 0x97, 0x87,          0x64, 0x67, 0x67, 0x97, 0x87, 0x67,
      0x65, 0x55, 0x05, 0x06, 0xD8,            0xD3, 0x07, 0xD7, 0xA6, 0x68,          0x83, 0x05, 0x06, 0xD8, 
      0xD3, 0x07, 0xD7, 0xA6, 0x68,        0x83, 0x05, 0x15,         0x63, 0x05, 0x85,           0xA1,      
      0xB3, 0xD5, 0xF5,                    0x83, 0x05, 0xF5,    15, 105,   0xD3, 0xD6,    15, 90,   0xA8, 0xB6,   15, 70,    0x88,      
         0x63, 0x03,        
                                  0x0};  // and the terminating '0' 

uint8_t bpm=120; // standard initial value
bool connextnote=false;

void setup() {
  pinMode(PIEZOPIN,OUTPUT);
  digitalWrite(PIEZOPIN,LOW);
}

void loop() {
  static uint8_t count=0;  
  uint8_t note=pgm_read_byte_near(melody+count);
  if (!note) {
    count=0;
    return;  // straight back into it
  }
  if (note==0x0F) {  // BPM setting for following byte
    count++;
    bpm=pgm_read_byte_near(melody+count);
  } 
  else if (note==0x0E)  connextnote=true;  // connect the next note to it's successor
  else  play(note);
  count++;
}

void play(uint8_t note) {  
  uint8_t len = note & 0x0F; // ls nibble
  note=note>>4; // ms nibble
  uint16_t notelength=pgm_read_byte_near(lengths+len);
  notelength=BPMnote(notelength);
  if (!note) {
    delay(notelength);
    return;
  }
  uint16_t ms=millis();
  uint16_t notebreak=notelength/4;
  if (connextnote) {
    notebreak=BPMnote(1)/4;
    connextnote=false;
  }
  notelength=notelength-notebreak;
  while (((uint16_t) millis()-ms) < (notelength)) {
    digitalWrite(PIEZOPIN,HIGH);
    interval(note);
    if (POSCORR) delayMicroseconds(POSCORR);
    digitalWrite(PIEZOPIN,LOW);
    interval(note);
  }  
  if (notebreak) delay(notebreak);
}

uint16_t BPMnote(uint32_t nl) {
  nl=(nl*15000)/bpm;
  return (uint16_t) nl;
}


void interval(uint8_t type) {
  switch (type) {
    case 1: 
      delayMicroseconds(M64/2-CORR);
      return;
    case 2:
      delayMicroseconds(M65/2-CORR);
      return;
    case 3: 
      delayMicroseconds(M66/2-CORR);
      return;
    case 4:
      delayMicroseconds(M67/2-CORR);
      return;
    case 5: 
      delayMicroseconds(M68/2-CORR);
      return;
    case 6:
      delayMicroseconds(M69/2-CORR);
      return;
    case 7: 
      delayMicroseconds(M70/2-CORR);
      return;
    case 8:
      delayMicroseconds(M71/2-CORR);
      return;
    case 9: 
      delayMicroseconds(M72/2-CORR);
      return;
    case 10:
      delayMicroseconds(M73/2-CORR);
      return;
    case 11: 
      delayMicroseconds(M74/2-CORR);
      return;
    case 12:
      delayMicroseconds(M75/2-CORR);
      return;
    case 13: 
      delayMicroseconds(M76/2-CORR);
      return;
    case 14:
      delayMicroseconds(M77/2-CORR);
      return;
    case 15: 
      delayMicroseconds(M78/2-CORR);
      return;
  }
}

which includes the BPM adjusting option and a way to let the next note flow into the one that follows it adding up to 872 bytes, with a few notes to spare, and a few notes lengths and 150 bytes at 1 byte per note i am confident i can play most tunes i want to and soon when i leave the lights on in my car the French national anthem (i got the errors out that got in while copying the melody) will sound (i have a French car)