[WAVEHC] Loading Music By a String?

Hey guys, last year I built a music box that plays different tunes depending on the Skylander/Amiibo/Disney Infinity type figure placed on top. It is great for what it does but it isn't very efficient. I used a template code from the internet and have been refining it and now I need to tackle the large global/program storage space.

I have noticed it could be heavily cut down if the way it loads the music to play. Below are the snippets of the code related to it.

const char string_0[] PROGMEM = "smhbytin.wav";
const char string_1[] PROGMEM = "smhbytlp.wav";
const char string_2[] PROGMEM = "smhhllin.wav";
const char string_3[] PROGMEM = "smhhlllp.wav";
const char string_4[] PROGMEM = "smhbsrin.wav";
//etc,etc

This is where they store the filenames as Globals. In my code they follow a strict file naming format which would make it easy to generate on the fly. I use an 8 character name that comprises of the following

AAABBBCC.wav AAA - The type of figure ("smh") BBB - The character name ("byt") CC - Either "in" (for intro) or "lp" (for loop)

const char* const string_table[] PROGMEM =
{
  string_0, string_1, string_2, string_3, string_4, //etc etc
}

It then creates a global array for the strings

SdReader card; // This object holds the information for the card
FatVolume vol; // This holds the information for the partition on the card
FatReader root; // This holds the information for the volumes root directory
FatReader file; // This object represent the WAV file for a pi digit or period
WaveHC wave; // This is the only wave (audio) object, since we will only play one at a time

char buffer[71];

Loads the objects needed for it to run

strcpy_P(buffer, (char*)pgm_read_word(&(string_table[0]))); //The array number is different
playfile(buffer);

This code is run when the nfc figure has been identified (For example, "it's a Spyro Skylander figure!") I assume this is coping the value to the buffer. This part I am hazy on its function.

void playfile(char *name) {
  wave.stop();
  if (!file.open(root, name)) {
    PgmPrint("Can't open file ");
    Serial.println(name);
    return;
  }
  if (!wave.create(file)) {
    PgmPrintln("Invalid WAV");
    return;
  }
  // ok time to play!
  wave.play();
}

This is where all the code happens for the music playing

void sdErrorCheck(void) {
  if (!card.errorCode()) return;
  PgmPrint("\r\nSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  PgmPrint(", ");
  Serial.println(card.errorData(), HEX);
  while (1);
}

This is the error code it throws if there is a problem with playing the music.

The rest of the code is based on loading the nfc figure and determining what track is played. If I could pass in a string to play the music file (for example, str = "smhbytin.wav" , we could then generate the name of the file as we discover which figure it is. It would make the need to store the globals redundant and cut a lot of code out as well reduce the amount of code that is added per figure.

The problem is I wouldn't know where to begin. This isn't my usual sort of coding so I am in the dark on how to work on it.

Can anyone advise me on a way to pass in a string to load a music file?

EDIT: I have been working on multiple solutions and none of them have worked. If anyone does have a solution I'd love to know how it works.

Can anyone advise me on a way to pass in a string to load a music file?

Pass a string to what function? If you want to pass a string to playfile(), that is easy:

char *fileName = "smhbytin.wav";
playfile(fileName);

If you mean something else, you’ll need to elaborate.

PaulS: Pass a string to what function? If you want to pass a string to playfile(), that is easy:

char *fileName = "smhbytin.wav";
playfile(fileName);

If you mean something else, you'll need to elaborate.

Would I need to delete the pointer after I finish using it?

Yes, that is the base of it :D What I would like is to achieve this.

String f = "smhbytin.wav"; char *filename = f; playfile(filename);

And yet when I do, I get a "cannot convert String to char* in initialization". This seems to be the problem I am having with all my attempts.

A String (object) is not a string (null terminated character array).

groundfungus:
A String (object) is not a string (null terminated character array).

Dahhh! You serious? How does Arduino deal with strings?

There is a language reference under the Learning tab on this site and from the Help tab in the IDE.

http://www.arduino.cc/en/Reference/String

http://www.arduino.cc/en/Reference/StringObject

I did some work on it last night and I am still suffering problems with it. I have never been good with pointers. I declared a global variable and its pointer as well as tried the following

char filestr[13] = {0};
char *filename = filestr;

Variable initialization

strcpy(filestr, "smhtlklp.wav");
playfile(filename);

While the code now works (hooray!), it is having some nasty effects on the serial monitor

Abo detected #2853711201
Last Abo: 2853711201
Current Card:2853711201
Waiting.
Abo detec&�&�2853711201
Last Abo: 2853711201
Current Card:2853711201
Waiting.
Abo det/þ/2�

I have put the Serial printing code below. Any ideas why it is a problem?

Serial.print(F("Abo detected #"));
    cardidentifier = uid[3];
    cardidentifier <<= 8; cardidentifier |= uid[2];
    cardidentifier <<= 8; cardidentifier |= uid[1];
    cardidentifier <<= 8; cardidentifier |= uid[0];
    Serial.println(cardidentifier);

    (lastcard = currentcard);
    (currentcard = cardidentifier);

    Serial.print(F("Last Abo: "));
    Serial.println(lastcard);
    Serial.print(F("Current Card:"));
    Serial.println(currentcard);

What I’d guess is that the Serial is being screwed up by the new code in some manner. What I don’t understand is why and how to fix it. I found removing the new str_cpy and playfile code returns the Serial output to a nice format.

Why do you need an array AND a pointer to the array? The array IS a pointer. Get rid of the filename variable.

    (lastcard = currentcard);
    (currentcard = cardidentifier);

Get rid of the stupid parentheses.

Any ideas why it is a problem?

I'm going to guess that cardidentifier is a char array, but that it is NOT a string. You are expecting it to be a string. A string is a NULL-terminated array of chars. Without the NULL terminator, the array is NOT a string, and should not be passed to a function that expects a string.

PaulS: Why do you need an array AND a pointer to the array? The array IS a pointer. Get rid of the filename variable.

    (lastcard = currentcard);
    (currentcard = cardidentifier);

Get rid of the stupid parentheses. I'm going to guess that cardidentifier is a char array, but that it is NOT a string. You are expecting it to be a string. A string is a NULL-terminated array of chars. Without the NULL terminator, the array is NOT a string, and should not be passed to a function that expects a string.

There are still parts of the code I have yet to clean up. Why the original coder put them in brackets is anyone's guess. As for the array, I am just experimenting to see if I could get it work. And it looks like what I want to achieve could be possible.

The cardidentifier is simply used to provide the current nfc identifier. It is an int variable and I am definitely not expecting that variable to tell me.

So what you are saying is that I should be able to just pass in the filestr array? I'll give it a go

EDIT: I removed the filename variable and it does work thanks for that :) The serial monitor output is still screwy though. Any thoughts on that?

Any thoughts on that?

You can visit http://snippets-r-us.com for help with your snippets OR you can post ALL of your code. Your choice.

#include <WaveHC.h>
#include <WaveUtil.h>
#include <Wire.h>
#include <Adafruit_NFCShield_I2C.h>
#include <avr/pgmspace.h>

const char string_0[] PROGMEM = "smhbytin.wav";
const char string_1[] PROGMEM = "smhbytlp.wav";
const char string_2[] PROGMEM = "smhbytl2.wav";
const char string_3[] PROGMEM = "smhbytl3.wav";
const char string_4[] PROGMEM = "smhbsrin.wav";
const char string_5[] PROGMEM = "smhbsrlp.wav";
const char string_6[] PROGMEM = "smhbsrl2.wav";
const char string_7[] PROGMEM = "smhbsrl3.wav";
const char string_8[] PROGMEM = "smhbjrin.wav";
const char string_9[] PROGMEM = "smhbjrlp.wav";
const char string_10[] PROGMEM = "smhbjrl2.wav";
const char string_11[] PROGMEM = "smhbjrl3.wav";

//UPGRADE

const char* const string_table[] PROGMEM =
{
  string_0, string_1, string_2, string_3, string_4, string_5, string_6, string_7, string_8, string_9, 
  string_10, string_11, 
};

char buffer[71];

#define IRQ 6 // this trace must be cut and rewired!
#define RESET 8

Adafruit_NFCShield_I2C nfc(IRQ, RESET);

SdReader card; // This object holds the information for the card
FatVolume vol; // This holds the information for the partition on the card
FatReader root; // This holds the information for the volumes root directory
FatReader file; // This object represent the WAV file for a pi digit or period
WaveHC wave; // This is the only wave (audio) object, since we will only play one at a time

#define error(msg) error_P(PSTR(msg))

uint32_t versiondata;
uint32_t lastcard = 0;
uint32_t currentcard = 1;
uint32_t CID = 0;
uint32_t DID = 0;
boolean songplaying = false;
uint32_t cardidentifier = 0;
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type)

char filestr[13] = {0};

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

  PgmPrintln("Scanner");

  if (!card.init()) {
    error("Card init fail");
  }
  if (!vol.init(card)) {
    error("No partition");
  }
  if (!root.openRoot(vol)) {
    error("Can't open dir");
  }

  PgmPrintln("Files found:");
  root.ls();

  nfc.begin();

  versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print(F("Can't find PN53x"));
    while (1); // halt
  }
  // Got ok data, print it out!
  Serial.print(F("Found chip PN5")); Serial.println((versiondata >> 24) & 0xFF, HEX);
  Serial.print(F("Firmware ")); Serial.print((versiondata >> 16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);

  nfc.SAMConfig();
  nfc.setPassiveActivationRetries(50);

}

unsigned digit = 0;

void loop()
{
  //Memory Checker
  //Serial.print(F("Memory Available = "));
  //Serial.println(freeMemory());

  Serial.println(F("Waiting."));

  // Wait for an ISO14443A type cards (Mifare, etc.). When one is found
  // 'uid' will be populated with the UID, and uidLength will indicate
  // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);

  cardidentifier = 0;
  CID = 999;
  DID = 999;

  if (success)
  {
    Serial.print(F("Abo detected: "));
    cardidentifier = uid[3];
    cardidentifier <<= 8; cardidentifier |= uid[2];
    cardidentifier <<= 8; cardidentifier |= uid[1];
    cardidentifier <<= 8; cardidentifier |= uid[0];
    Serial.println(cardidentifier);

    lastcard = currentcard;
    currentcard = cardidentifier;      
    
    boolean foundmisc = false;
    boolean newcharacter = (currentcard != lastcard);
    int32_t characterid = -1;

    Serial.print(F("Last Abo: "));
    Serial.println(lastcard);
    Serial.print(F("Current Card:"));
    Serial.println(currentcard);
    
    uint8_t charID[32];
    success = nfc.mifareultralight_ReadPage (21, charID);

    if (success)
    {
      CID = charID[6];
      CID <<= 8; CID |= charID[6];
      CID <<= 8; CID |= charID[5];
      CID <<= 8; CID |= charID[4];
      CID <<= 8; CID |= charID[3];
      CID <<= 8; CID |= charID[2];
      CID <<= 8; CID |= charID[1];
      CID <<= 8; CID |= charID[0];
      Serial.println("Char No: ");
      Serial.println(CID);

      uint8_t detailID[32];
      success = nfc.mifareultralight_ReadPage (22, detailID);
      DID = detailID[6];
      DID <<= 8; DID |= detailID[6];
      DID <<= 8; DID |= detailID[5];
      DID <<= 8; DID |= detailID[4];
      DID <<= 8; DID |= detailID[3];
      DID <<= 8; DID |= detailID[2];
      DID <<= 8; DID |= detailID[1];
      DID <<= 8; DID |= detailID[0];
      Serial.println("Char Sub No: ");
      Serial.println(DID);

      if (success)
      {
        switch (CID)
        {
          case 1280: {
            if (DID == 33634560)
              characterid = 252; //Party - Bowser
            else
              characterid = 4; //Smash - Bowser
          }; break;
          case 1536: characterid = 8; break; //Smash - Bowser Jr
        }
//
        if (characterid >= 0) //UPGRADE
        {
          if (newcharacter)
          {
            strcpy_P(buffer, (char*)pgm_read_word(&(string_table[characterid])));
            playcomplete(buffer);
          }
          else
          {
            if (!songplaying)
            {
              strcpy_P(buffer, (char*)pgm_read_word(&(string_table[characterid + 1])));
              playfile(buffer);
            }
            songplaying = wave.isplaying;
          }
        }
      }
      else
        killsong();
    }
    else
    {
      switch (currentcard)
      {
        case 255024689: characterid = 388; break; //Charles
        case 2853711201: characterid = 360; break; //HW Link
      }

      if (characterid >= 0) //UPGRADE
      {
        foundmisc = true;

        if (newcharacter)
        {
          strcpy_P(buffer, (char*)pgm_read_word(&(string_table[characterid])));
          //playcomplete(buffer);

          strcpy(filestr,"lozhwlin.wav");
          playcomplete(filestr);
        }
        else
        {
          if (!songplaying)
          {
            strcpy_P(buffer, (char*)pgm_read_word(&(string_table[characterid + 1])));
            playfile(buffer);
            
            strcpy(filestr,"lozhwllp.wav");
            playfile(filestr);
          }
          songplaying = wave.isplaying;
        }
        /*if (newcharacter)
        {
          strcpy_P(buffer, (char*)pgm_read_word(&(string_table[characterid])));
          playcomplete(buffer);
        }
        else
        {
          if (!songplaying)
          {
            strcpy_P(buffer, (char*)pgm_read_word(&(string_table[characterid + 1])));
            playfile(buffer);
          }
          songplaying = wave.isplaying;
        }*/
      }
      
      if (!foundmisc)
        killsong();
    }
  }
  else
        killsong();
}

void error_P(const char *str) {
  PgmPrint("Error: ");
  SerialPrint_P(str);
  sdErrorCheck();
  while (1);
}
void sdErrorCheck(void) {
  if (!card.errorCode()) return;
  PgmPrint("\r\nSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  PgmPrint(", ");
  Serial.println(card.errorData(), HEX);
  while (1);
}
int randInt(int n) {
  return random(n) + 1;
}
void killsong() {
  wave.stop();
  lastcard = 123456;
  currentcard = 654321;
  songplaying = false;  
}
void playcomplete(char *name) {
  playfile(name);
  while (wave.isplaying);
  sdErrorCheck();
}
void playfile(char *name) {
  wave.stop();
  if (!file.open(root, name)) {
    PgmPrint("Can't open file ");
    Serial.println(name);
    return;
  }
  if (!wave.create(file)) {
    PgmPrintln("Invalid WAV");
    return;
  }
  wave.play();
}
/*void playfile(char *name) {
  wave.stop();
  if (!file.open(root, name)) {
    PgmPrint("Can't open file ");
    Serial.println(name);
    return;
  }
  if (!wave.create(file)) {
    PgmPrintln("Invalid WAV");
    return;
  }
  wave.play();
}*/

There you go. Have a look at it :slight_smile:

EDIT: While I remember, the only different between this and the actual code is that the “string_table and the variables related to it are reached the 400 variable mark” and that the code that sets the characterid to a certain value based on the figure (characterid = 4 for example)

I cut it down so I could squeeze it into the post for your convenience. I have attached the full code so you can have a look

I have been using the area tagged //HW Link for my testing of the new code

EDIT 2: I updated the attachment to make it easier for you. I have also made a Google Drive upload for you

Google Drive url for Code

MusicBoxCode.txt (7.59 KB)