Parse Through Directory and do some String/Array operations

Hi,

i want to built a little drum sampler.

The first operation of the program shall be to walk through a directory (number+"/"), e.g. “/1/” of the SD Card.
the directory contains several files (samples) which have all filenames like

“36_V01.wav”
“36_V02.wav”
.
.
.
“38_V01.wav”
“38_V02.wav”

This means, the first part of the filename changes from 36 to 38 and so on (random numbers which later are used by midi-notes). The “V0x” part is later used to control velocity of the samples.

No, i want the program to count, how many files beginning with “36” are in the directory, how many beginning with “38” and so on… The results shall be saved in an array.

Here is my first try:

#include <SD.h>
#include <SPI.h>

File root;
unsigned short sampleMap[]={};
const int chipSelect = BUILTIN_SDCARD; // I use a teensy, so this is ok...

void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  int drumset=1;
  char dirname[16] = {'\0'}; //16 characters in the filename (or however long you want to make it)
  sprintf(dirname,"/%d/",drumset);
  
  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    return;
  }
  root = SD.open(dirname);
  loadSamples(root);
  Serial.println("Fertig!");
  Serial.println("Anzahl Samples" + sampleMap[1]);
  Serial.println("Anzahl Samples" + sampleMap[2]);
}

void loop()
{
  // 
}


void loadSamples(File dir) {
   String oldnoteval="";
   int samplenum=0;
   int veloCount=0;
   while(true) {
     File entry =  dir.openNextFile();
     if (! entry) {
       // no more files
       break;
     }
     String newfile=entry.name();
     int underliner_pos=newfile.indexOf("_");
     String noteval=newfile.substring(0,underliner_pos);
     if (noteval!=oldnoteval) {
        veloCount=1;
        samplenum=samplenum+1;
        sampleMap[samplenum]=veloCount;
        // Ein neues Sample wurde gefunden.
        // Nun werden alle Velocities in einen Array geschrieben
        Serial.println("___________________________________");
     } else {
        veloCount=veloCount+1;
        sampleMap[samplenum]=veloCount;
     }
     Serial.println(entry.name());
     oldnoteval=noteval;
     entry.close();
   } 
}

The code works partially. The filenames are printed.
But it seems that the Number of files (veloCount) is not saved in the array.

Maybe the error is that i don’t dont dimension the array at beginning of the program, but i can’t because i dont know how many different samples are inside the folder.

Can you help me to solve this problem?

Best regards
Daniel

unsigned short sampleMap[]={};

When you don't put a number in the square brackets, the compiler figures out the number for you, based on the number of initializers provided. Since you provided 0 initializers, that is the size of the array. ANY attempt to store data in the array is going to crap on memory you don't own.

You MUST provide a size or completely restructure your code, to use malloc() and realloc(), if needed. You should probably define a reasonable limit on the number of prefixes you will support, and use a static array.

You should probably define a reasonable limit on the number of prefixes you will support, and use a static array.

+1

Perhaps something like this:

const size_t MAX_SAMPLES = 60;

unsigned short sampleMap[ MAX_SAMPLES ];

     ...

void loadSamples(File dir) {

     ...

     if (noteval != oldnoteval) {
        veloCount=1;
        samplenum=samplenum+1;
        if (samplenum < MAX_SAMPLES-1)  {
          sampleMap[samplenum]=veloCount;
        }
        // Ein neues Sample wurde gefunden.
        // Nun werden alle Velocities in einen Array geschrieben
        Serial.println("___________________________________");
     } else {
        veloCount=veloCount+1;
        if (samplenum < MAX_SAMPLES-1)  {
          sampleMap[samplenum]=veloCount;
        }
    }

    if (samplenum >= MAX_SAMPLES)  {
      Serial.print( F("Too many samples! Only ") );
      Serial.print( MAX_SAMPLES );
      Serial.print( F(" of the ") );
      Serial.print( samplenum );
      Serial.println( F(" were stored.  Increase MAX_SAMPLES or delete some sample files.") );
    }
}

Also, Don’t use String™. Here are some reasons.

Here is one way to “parse” the filenames with char arrays (aka C string) functions (NOT the [u]S[/u]tring class):

const int      MAX_SAMPLES = 3;
unsigned short sampleMap[ MAX_SAMPLES ];
int            samplenum;
unsigned short veloCount;
unsigned short oldnoteval;

size_t         count     = 0;
const size_t   MAX_CHARS = 64;
char           newfile[ MAX_CHARS ];


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

  resetSamples();
}

void loop()
{
  //----------------------------------------------------------
  // Just for testing, get filenames from the Serial Monitor
  if (not lineReady())
    return;

  // An empty line means no more filenames from Serial Monitor
  if (strlen( newfile ) == 0) {
    printSamples();
    resetSamples();
    return;
  }
  //----------------------------------------------------------

//  Your code did this to get each newfile
//File entry =  dir.openNextFile();
//if (! entry) {
// // no more files
// break;
//}
//char *newfile = entry.name();   <-- not String!

  unsigned short noteval;
  unsigned short velocity;

  if (parseFilename( newfile, noteval, velocity )) {

    // Now we have noteval and velocity.  Update the sampleMap.

    if (oldnoteval != noteval) {
      oldnoteval = noteval;
      veloCount  = 1;
      samplenum  = samplenum+1;

      // Don't write past the end of the array
      if (samplenum < MAX_SAMPLES-1)  {
        sampleMap[samplenum]=veloCount;
      }
      // Ein neues Sample wurde gefunden.
      // Nun werden alle Velocities in einen Array geschrieben
      Serial.println( F("___________________________________") );

    } else {
      veloCount=veloCount+1;

      // Don't write past the end of the array
      if (samplenum < MAX_SAMPLES-1)  {
        sampleMap[samplenum]=veloCount;
      }
    }

  }

} // loop


bool lineReady()
{
  bool          newline = false;
  const char    endMarker = '\n';

  while (Serial.available()) {

    char c = Serial.read();

    if (c != endMarker) {
      // Only save the printable characters, if there's room
      if ((' ' <= c) and (count < MAX_CHARS-1)) {
        newfile[ count++ ] = c;
      }
    } else {
      newfile[count] = '\0'; // terminate the string
      count          = 0;    // reset for next time
      newline        = true;
      break;
    }
  }

  return newline;

} // lineReady


bool parseFilename( char *newfile, unsigned short & noteval, unsigned short & velocity )
{
  bool okFilename = false;

  noteval = atoi( newfile ); // uses consecutive digits, up to the '_'

  if (noteval > 0) {
    char *ptr = strstr( newfile, "_V" ); // find the separator

    if (ptr != nullptr) {
      // ptr now "points at" the underscore character
      ptr += 2;                 // step over the "_V" characters
      velocity   = atoi( ptr ); // use consecutive digits, up to the '.'
      okFilename = true;

      Serial.print( newfile );
      Serial.print( ' ' );
      Serial.print( noteval );
      Serial.print( ' ' );
      Serial.println( velocity );
    }
  }

  if (not okFilename) {
    Serial.print( F("Invalid filename : '") );
    Serial.print( newfile );
    Serial.println( '\'' );
  }

  return okFilename;

} // parseFilename


void printSamples()
{
  if (samplenum >= MAX_SAMPLES)  {
    Serial.print( F("Too many samples! Only ") );
    Serial.print( MAX_SAMPLES );
    Serial.print( F(" of the ") );
    Serial.print( samplenum );
    Serial.println( F(" were stored.  Increase MAX_SAMPLES or delete some sample files.") );

    // Only print up to the end of the sample array
    samplenum = MAX_SAMPLES-1;
  }

  for (int i=0; i <= samplenum; i++) {
    Serial.print( i );
    Serial.print( ' ' );
    Serial.println( sampleMap[ i ] );
  }

} // printSamples


void resetSamples()
{
  oldnoteval = 9999; // unlikely note value
  samplenum  = -1;

  Serial.println( F("Enter filenames, empty line to quit:") );

} // resetSamples

This test program lets you enter filenames from the Serial Monitor window. It uses atoi to get an integer and strstr to find the “_V” in the filename. There are many C string functions, so feel free to ask what functions you might need for a particular task.

Hey,

thanx a lot for your great support. I will test the program tommorrow and come back!

Great forum :)

Daniel

Hey,

i’ve tested your code and integrated it (partially) in my previous code.

This is the new version, which works perfectly for me:

#include <SD.h>
#include <SPI.h>

File root;

const int      MAX_SAMPLES = 60;
unsigned short veloMap[ MAX_SAMPLES ];
unsigned short keyMap[ MAX_SAMPLES ];
int            samplenum;
unsigned short veloCount;
unsigned short oldnoteval;

size_t         count     = 0;
const size_t   MAX_CHARS = 64;
char           newfile[ MAX_CHARS ];
const int chipSelect = BUILTIN_SDCARD;

void setup()
{
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  Serial.println("Hello world!");
  //resetSamples();
  int drumset=1;
  char dirname[16] = {'\0'}; //16 characters in the filename (or however long you want to make it)
  sprintf(dirname,"/%d/",drumset);
  Serial.println("Dirname:");
  Serial.println(dirname);
  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    return;
  }
  root = SD.open(dirname);
  Serial.println("Kurz vor loadSamples!");
  loadSamples(root);
  printSamples();
}

void loop()
{
  

} // loop


void loadSamples(File dir) {
  while(true) {
    File entry =  dir.openNextFile();
    if (! entry) {
     // no more files
     break;
    }
    char *newfile = entry.name();
    unsigned short noteval;
    unsigned short velocity;
    
    if (parseFilename( newfile, noteval, velocity )) {
      // Now we have noteval and velocity.  Update the veloMap.
      if (oldnoteval != noteval) {
       
        oldnoteval = noteval;
        veloCount  = 1;
        samplenum  = samplenum+1;
        keyMap[samplenum]=noteval;
        // Don't write past the end of the array
        if (samplenum < MAX_SAMPLES-1)  {
          veloMap[samplenum]=veloCount;
        }
      } else {
        veloCount=veloCount+1;
        // Don't write past the end of the array
        if (samplenum < MAX_SAMPLES-1)  {
          veloMap[samplenum]=veloCount;
        }
      }
    
    }
    entry.close();
  }
}




bool parseFilename( char *newfile, unsigned short & noteval, unsigned short & velocity )
{
  bool okFilename = false;
  noteval = atoi( newfile ); // uses consecutive digits, up to the '_'
  if (noteval > 0) {
    char *ptr = strstr( newfile, "_V" ); // find the separator
    if (ptr != nullptr) {
      // ptr now "points at" the underscore character
      ptr += 2;                 // step over the "_V" characters
      velocity   = atoi( ptr ); // use consecutive digits, up to the '.'
      okFilename = true;
    }
  }
  return okFilename;
} // parseFilename


void printSamples()
{
  if (samplenum >= MAX_SAMPLES)  {
    // Only print up to the end of the sample array
    samplenum = MAX_SAMPLES-1;
  }
  for (int i=0; i <= samplenum; i++) {
    Serial.print( i );
    Serial.print( ' ' );
    Serial.println( keyMap[ i ] );
  }
  for (int i=0; i <= samplenum; i++) {
    Serial.print( i );
    Serial.print( ' ' );
    Serial.println( veloMap[ i ] );
  }

} // printSamples


void resetSamples()
{
  oldnoteval = 9999; // unlikely note value
  samplenum  = -1;
} // resetSamples

I have deleted a lot of “Serial.println”-parts i don’t need.

Thanx for your help.
If you still find errors / points for optimization of the code please answer.

Daniel

If you still find errors / points for optimization of the code please answer.

Well, sprintf is really big for what you are using it for. Just format your own C string. Instead of this:

  sprintf(dirname,"/%d/",drumset);

You can build your dirname with this:

  dirname[0] = '/';                  // start with a slash
  itoa( drumset, &dirname[1], 10 );  // write in the digits for drumset (NUL terminated)
  size_t len = strlen( dirname );    // see how long that was
  dirname[ len ] = '/';              // append slash (overwrites NUL)
  dirname[ len+1 ] = '\0';           // NUL terminator character

It uses the itoa function to "print" the drumset integer value into the dirname array. You should see the program size drop by about 1500 bytes when you don't use sprintf.