Memory add-on options for Audio Board ...

Status
Not open for further replies.
I've come up with an approach to use AudioPlayMemory to play audio from the Audio Board's microSD card.

I wrote a utility that reads cpp files generated by wav2sketch and saves them as binary files that are essentially the same as the audio data arrays used by AudioPlayMemory. It's pretty quick to read one of these binary files from the SD card into the array since the data doesn't need to be interpreted in any way. I don't know how different these binary files are from RAW audio files, but it was worth it to me since I believe AudioPlayMemory doesn't have the same problem that AudioPlaySdRaw does if the Audio Library buffer is made smaller. You can fill up your memory pretty fast doing this, but it works well for short sound snippets.

I can provide source code if you would like.
 
I've come up with an approach to use AudioPlayMemory to play audio from the Audio Board's microSD card.

I wrote a utility that reads cpp files generated by wav2sketch and saves them as binary files that are essentially the same as the audio data arrays used by AudioPlayMemory. It's pretty quick to read one of these binary files from the SD card into the array since the data doesn't need to be interpreted in any way. I don't know how different these binary files are from RAW audio files, but it was worth it to me since I believe AudioPlayMemory doesn't have the same problem that AudioPlaySdRaw does if the Audio Library buffer is made smaller. You can fill up your memory pretty fast doing this, but it works well for short sound snippets.

I can provide source code if you would like.

This sounds amazing. If you don't mind sharing, that would be great!
 
Here's the utility and the Teensy code to play audio from the Audio Board SD card.

The code for the utility is a little clunky but it tries to be robust. I've noticed that the headers generated by wav2sketch can vary a little bit, so the header validation may need to be adjusted (or deleted).

The Teensy code was taken from a working program, but I have not debugged the extracted code pasted here. If you need to debug this code, please post your corrections.



Code:
// TeensyAudioBinary
//
// This utility is part of a process to make a WAV file playable by the
// Teensy Audio Library function AudioPlayMemory
//
// Ordinarily a utility called wav2sketch
// (https://www.pjrc.com/teensy/td_libs_AudioPlayMemory.html)
//
// is used to convert an audio WAV file into a pair of C source files that
// can be compiled directly into a Teensy sketch as an unsigned int array
// in order to make the audio avavilable for low latency playback by the function
// AudioPlayMemory.
//
// Unfortunately, there is a limited amount of memory in a Teensy (go figure)
// for audio streams to be hard coded into a sketch.  There are other audio playback
// functions besides AudioPlayMemory that can read audio files from microSD card or flash
// memory, but those functions have slower playback latencies and cannot be queued
// up for rapid playback.
//
// This utility reads in one of the CPP source files generated by wav2sketch,
// and converts the file's text, hexadecimal representation of the unsigned integer
// array, and writes it out in binary format that can be read rapidly by the
// Teensy sketch into an array in memory, making it available to the AudioPlayMemory
// function.
//
// This allows audio to be read-in on an as-needed basis to have at the ready for
// rapid, low latency playback.
//
// This utility runs numerous sanity checks to insure that it only converts
// valid files from wav2sketch.
//
// Compiled with
// gcc TeensyAudioBinary.c -o TeensyAudioBinary.exe


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>



FILE *instream, *outstream, *errorstream;
char infile[200];
char outfile[200];

char g_strInputRecord[8000];

unsigned int g_DataRecord;
int i;

int g_intLineCount;
int g_intFileCount;

int g_flagErrorInFile=0;
int g_flagErrorEncountered=0;


// Opens the error log file one and only once, if needed.
void HandleErrorFile(void)
 {if (g_flagErrorEncountered == 0)
     {g_flagErrorEncountered = 1;
      if ((errorstream = fopen("TeensyAudioBinary_errors.txt","at")) == NULL)  //open the error file
         {printf("\n\nCannot open TeensyAudioBinary_errors.txt\n");
          exit(1);
         }
      }
 }




// Counts commas on a line.
int CountCommas(char *strInputString)
 {
  int intDelimiterCount=0;
  int intStringIndex=0;
  if (strInputString[0] == '\0') return(0);

  while(strInputString[intStringIndex] != '\0')
   {
    if (strInputString[intStringIndex] == ',') intDelimiterCount++;
    intStringIndex++;
   }

  return(intDelimiterCount);
 }



// Check that string is valid 8 digit hex number (with following comma)
int ValidHex(char *strInputString)
 {
  int i;

  if (strInputString[0] != '0') return(0);
  if (strInputString[1] != 'x') return(0);

  for (i=2; i<10; i++)
   {
    if (strInputString[i] < '0') return(0);
    if (strInputString[i] > 'F') return(0);
    if ((strInputString[i] > '9') && (strInputString[i] < 'A')) return(0);
   }
  if (strInputString[10] != ',') return(0);
  return(1);
 }





int main(int argc, char **argv)

{
 int j;
 int count;
 int len;
 char strNumberIn[12];

 if (argc < 2)
    { printf("\nYou must enter a file name following\n");
      printf("\"TeensyAudioBinary\" on the command line\n");
      printf("\nFile names with wildcards \"?\" or \"*\" are OK\n");
      printf("\n\n(Files without .CPP filename extension will be ignored.)");
      printf("\n\n(Size of an unsigned int is %d on this system.)\n\n", sizeof(unsigned int));
      printf("\n\nPress any key to continue.\n\n");
      return(-1);
    }



 // Loop once for each file.
 for (g_intFileCount = 1; g_intFileCount <= argc-1; ++g_intFileCount)
 {

  strcpy(infile,argv[g_intFileCount]); // get the file name from the command shell

  // skip any files that aren't ".CPP"
  len = strlen(infile);
  if (strcasecmp(infile + len - 4, ".cpp") != 0) continue; // use pointer arithmetic to get last 4 chars

  printf("%s -> ",infile); // start status line


  if ((instream = fopen(infile,"rt")) == NULL)  // open the input file
     { printf("\n\nCan not open input file %s\n", infile);
       return(-1);
     }

  // Output file has same name, but with a "bin" extension.
  strcpy(outfile, infile);
  outfile[len-3] = 'b';
  outfile[len-2] = 'i';
  outfile[len-1] = 'n';


  // reset counter for the new input file
  g_intLineCount = 0;
  

  // Read 1st line of the header
  if (fgets(g_strInputRecord,8000,instream) != NULL)
     {g_intLineCount++;
      if (strstr(g_strInputRecord, "Audio data converted from WAV file by wav2sketch") == NULL)
         {
          HandleErrorFile();
          fprintf(errorstream, "\n\n First line of file %s does not say 'Audio data converted from WAV file by wav2sketch'\n", argv[g_intFileCount]);
          fclose(instream);
          continue;
         }
     }
  else
     {
      HandleErrorFile();
      fprintf(errorstream, "\n\n Can't read line 1 of file %s\n", argv[g_intFileCount]);
      fclose(instream);
      continue;
     }



  // Skip 2nd line of the header
  if (fgets(g_strInputRecord,8000,instream) != NULL)
     {g_intLineCount++;
     }
  else
     {
      HandleErrorFile();
      fprintf(errorstream, "\n\n Can't read 2nd line of file %s\n", argv[g_intFileCount]);
      fclose(instream);
      continue;
     }


  // Read 3rd line of the header
  if (fgets(g_strInputRecord,8000,instream) != NULL)
     {g_intLineCount++;
      if (strstr(g_strInputRecord, "#include") == NULL)
         {
          HandleErrorFile();
          fprintf(errorstream, "\n\n 3rd line of file %s does not have '#include' line\n", argv[g_intFileCount]);
          fclose(instream);
          continue;
         }
     }
  else
     {
      HandleErrorFile();
      fprintf(errorstream, "\n\n Can't read 3rd line of file %s\n", argv[g_intFileCount]);
      fclose(instream);
      continue;
     }



  // Skip 4th line of the header
  if (fgets(g_strInputRecord,8000,instream) != NULL)
     {g_intLineCount++;
     }
  else
     {
      HandleErrorFile();
      fprintf(errorstream, "\n\n Can't read 4th line of file %s\n", argv[g_intFileCount]);
      fclose(instream);
      continue;
     }



  // Read 5th line of the header
  if (fgets(g_strInputRecord,8000,instream) != NULL)
     {g_intLineCount++;
      if (strstr(g_strInputRecord, "Converted from") == NULL)
         {
          HandleErrorFile();
          fprintf(errorstream, "\n\n 5th line of file %s does not say 'Converted from'\n", argv[g_intFileCount]);
          fclose(instream);
          continue;
         }
     }
  else
     {
      HandleErrorFile();
      fprintf(errorstream, "\n\n Can't read 5th line of file %s\n", argv[g_intFileCount]);
      fclose(instream);
      continue;
     }



  // Read 6th line of the header
  if (fgets(g_strInputRecord,8000,instream) != NULL)
     {g_intLineCount++;
      if (strstr(g_strInputRecord, "const unsigned int") == NULL)
         {
          HandleErrorFile();
          fprintf(errorstream, "\n\n 6th line of file %s does not say 'const unsigned int'\n", argv[g_intFileCount]);
          fclose(instream);
          continue;
         }
     }
  else
     {
      HandleErrorFile();
      fprintf(errorstream, "\n\n Can't read 6th line of file %s\n", argv[g_intFileCount]);
      fclose(instream);
      continue;
     }


  // Open the binary file for output

  if ((outstream = fopen(outfile,"wb")) == NULL)  // open the binary output file
     { printf("\n\nCan not open output file %s\n", outfile);
       return(-1);
     }


  // Start reading in data lines.

  while (fgets(g_strInputRecord,8000,instream) != NULL) // read in entire lines of data until the EOF is encountered
   {
    g_intLineCount++;

    len = strlen(g_strInputRecord);
    len --;  // discount newline character at end

    // Line too long
    if (len > 88)
       {
        HandleErrorFile();
        fprintf(errorstream, "\n\n Line %d of file %s too long.  %d characters\n%s\n",
                g_intLineCount, argv[g_intFileCount], len, g_strInputRecord);
        g_flagErrorInFile = 1;
        break;
       }

    // Last line of the file.  (Don't have to stick aroubnd for the EOF.)
    if (strstr(g_strInputRecord, "};") != NULL) break;    

    // Sanity check on delimiters.  There should be between 1-8 commas on this line
    count = CountCommas(g_strInputRecord);
    if ((count > 8) || (count < 1))
       {
        HandleErrorFile();
        fprintf(errorstream, "\n\n Line %d of file %s wrong number of commas.  %d commas\n",
                g_intLineCount, argv[g_intFileCount], count);
        g_flagErrorInFile = 1;
        break;
       }

    // Line length should be comma-count x 11  (Each number has "0x" + "FFFFFFFF" + "," characters)
    if (len != count*11)
       {
        HandleErrorFile();
        fprintf(errorstream, "\n\n Line %d of file %s wrong line length for the number of commas.\n",
                g_intLineCount, argv[g_intFileCount]);
        g_flagErrorInFile = 1;
        break;
       }
 
        
    // Go though the line 11 characters at a time.  (Each number has "0x" + "FFFFFFFF" + "," characters)
    for (i=0; i<count*11; i+=11)
      {
       strNumberIn[0] = g_strInputRecord[i];
       strNumberIn[1] = g_strInputRecord[i+1];
       strNumberIn[2] = g_strInputRecord[i+2];
       strNumberIn[3] = g_strInputRecord[i+3];
       strNumberIn[4] = g_strInputRecord[i+4];
       strNumberIn[5] = g_strInputRecord[i+5];
       strNumberIn[6] = g_strInputRecord[i+6];
       strNumberIn[7] = g_strInputRecord[i+7];
       strNumberIn[8] = g_strInputRecord[i+8];
       strNumberIn[9] = g_strInputRecord[i+9];
       strNumberIn[10] = g_strInputRecord[i+10];

       if (ValidHex(strNumberIn) == 0)
          {
           HandleErrorFile();
           fprintf(errorstream, "\n\n Line %d of file %s bad hex value: %s\n",
                   g_intLineCount, argv[g_intFileCount], strNumberIn);
           g_flagErrorInFile = 1;
           break;
          }

       g_DataRecord = (unsigned) strtoul(strNumberIn, NULL, 16);
       fwrite(&g_DataRecord , sizeof(unsigned int), 1, outstream);

//     fprintf(outstream,"%s %lx\n", strNumberIn, g_DataRecord);

      }

    if (g_flagErrorInFile == 1) break;
   }  // End of data line processing loop



  // Close the input and output files.
  fclose(instream);  
  fclose(outstream);

  // Finish the screen update.
  printf("%s\n", outfile);

 }  // End of file processing loop


 if (g_flagErrorEncountered == 1) 
    {fclose(errorstream);
     printf("\n\n Errors encountered.  View TeensyAudioBinary_errors.txt for details.\n\n");
    }
 exit(0);
}



Code:
#include <Audio.h>
#include <Wire.h>         // Allows you to communicate with I2C / TWI devices
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>


// GUItool: begin automatically generated code
AudioPlayMemory          playMem1;                              // virtual device plays audio stream from array
AudioPlaySdRaw           playSdRaw1;                            // virtual device plays audio RAW file from SD Card
AudioMixer4              mixer1;                                // Left channel
AudioMixer4              mixer2;                                // Right channel
AudioOutputI2S           i2s1;                                  // all audio output channels thru this
AudioConnection          patchCord1(playMem1, 0, mixer1, 0);    // patchcords define how the other commponents are connected to each other
AudioConnection          patchCord2(playMem1, 0, mixer2, 0);
AudioConnection          patchCord3(playSdRaw1, 0, mixer1, 1);
AudioConnection          patchCord4(playSdRaw1, 0, mixer2, 1);
AudioConnection          patchCord5(mixer1, 0, i2s1, 0);
AudioConnection          patchCord6(mixer2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;                           // this is required for the audio board to work
// GUItool: end automatically generated code

unsigned int  AudioData[25000];  // This is enough buffer space comfortably hold 1 second of sound (~22000) at 44.1 kHz.

int SERIAL_DEBUG=1;

void setup()
{
// Initialize the Serial object.
// The baud rate is ignored and communication always occurs at full USB speed (12 Mbit/sec). 
Serial.begin(9600);

// This line for debugging.  It forces execution to wait for opening of "Serial Monitor".
if(SERIAL_DEBUG) while (!Serial) {};

if (SERIAL_DEBUG) Serial.println("Serial initialized");

// AudioMemory(numberBlocks)
// Allocate the memory for all audio connections.
// The numberBlocks input specifies how much memory to reserve for audio data.
// Each block holds 128 audio samples, or approx 2.9 ms of sound.
// (Usually an initial guess is made for numberBlocks and the actual usage is checked with AudioMemoryUsageMax().)
AudioMemory(15);  // The blocks allocated here are not used to contain the audio samples played by AudioPlayMemory().

if (SERIAL_DEBUG) Serial.println("AudioMemory initialized");


sgtl5000_1.enable();        // Enable audio
sgtl5000_1.volume(0.5);
mixer1.gain(0,0.5);
mixer2.gain(0,0.5);
if (SERIAL_DEBUG) Serial.println("sgt15000 initialized");

// Setup access to play audio files from Micro SD card on the Teensy Audio Shield
SPI.setMOSI(SDCARD_MOSI_PIN);
if (SERIAL_DEBUG) Serial.println("SPI.setMOSI");

SPI.setSCK(SDCARD_SCK_PIN);
if (SERIAL_DEBUG) Serial.println("SPI.setSCK");

if (!(SD.begin(SDCARD_CS_PIN))) Serial.println("SDCARD_CS_PIN error");
if (SERIAL_DEBUG) Serial.println("SD.begin");

}  // End of setup()


//////////////////////////////////////////////////////////////////////////////


void loop()
{
  ReadAudioDataFile("SAMPLE.BIN");
  playMem1.play(AudioData);
  while(playMem1.isPlaying());
  delay(1000);
}



// Read binary Audio Data file into array for use by AudioPlayMemory()
int ReadAudioDataFile(char* FileName)
{ 
 File  myFile;      // file handle
 char inBuffer[4];  // 4 byte data buffer to read-in 32 bits at a time from the file
 int  i=0;          // array index

 // open the file. 
 myFile = SD.open(FileName, FILE_READ);
  
 if (!myFile) Serial.println("Error opening file");

 // Read from the file until there's nothing else in it.
 while (myFile.available()) {

      // Read in 4 bytes at a time.
      myFile.read(inBuffer,4*sizeof(unsigned char));

      // Copy the 4 bytes into a single unsigned 32 bit integer data array element.
      AudioData[i] = inBuffer[3] << 24 | inBuffer[2] << 16 | inBuffer[1] << 8 | inBuffer[0];
      i++;
      }
 myFile.close();
 return(i);
}
 
Status
Not open for further replies.
Back
Top