// 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);
}