Storing a .wav file on the SD card and analyzing it on the ESP32

Hello there!

I want to put a .wav file on my SD card and do a FFT on it using the ESP32. My .wav file is 8-bit unsigned PCM 3 seconds long, 16KHz sample rate thus 48000 samples thus 48KB.

The idea sounds very simple but I'm not sure how to go about it. In MATLAB, for example, you use audioread(filename) to automatically read the whole .wav file into an array. I want to do the same using the ESP32 and then execute an FFT on the array. I think the FFT portion sounds pretty simple but I don't know how to store my .wav file into an array on the ESP32.

Any ideas? Thanks.

The generic approach

Read a byte, place in array.
2)
Read next byte, place in next position in array
3)
Back to (2) till there are no more bytes to read from the file.

I thought about that but I'm having problems doing the same thing via serial for example since I'm not getting the correct results back when I send 48000 samples.

Show your code so we can understand what you're doing.

Just in case you have not done so yet, please read How to use this forum - please read. - Installation & Troubleshooting - Arduino Forum, specifically point #7 about posting code.

I haven’t tried importing the .wav file from the SD to the ESP32 because I don’t know how and googling didn’t help. What I’ve done is try to write the file to the SD as text however I can’t get the whole file (for example, 200 samples here) to be written on the SD card. It only writes up to 7 values.

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "Arduino.h"
#include "SPIFFS.h"

#define SD_CS 5

//Required declaration for checksum calculations
int INPUT_COUNTER = 0;
const int dataLength = 200;
float *sentData = NULL;
float checksum = 0;
const int ledPin = 26;
int CNT = 0;

//Required declarations for SD card read/write
String dataMsg;
File corona;

void setup()
{
   //Initialize the serial port for data transmission
   Serial.begin(460800);
   sentData = new float[dataLength];
   Serial.setRxBufferSize(1024);
   pinMode(ledPin, OUTPUT);
   //Initialize the microSD card
   corona = SD.open("/test.txt");
   SD.begin(SD_CS);
   if (!SD.begin())
   {
       Serial.println("Card Mount Failed");
       return;
   }
   uint8_t cardType = SD.cardType();

   if (cardType == CARD_NONE)
   {
       Serial.println("No SD card attached");
       return;
   }

   Serial.print("SD Card Type: ");
   if (cardType == CARD_MMC)
   {
       Serial.println("MMC");
   }
   else if (cardType == CARD_SD)
   {
       Serial.println("SDSC");
   }
   else if (cardType == CARD_SDHC)
   {
       Serial.println("SDHC");
   }
   else
   {
       Serial.println("UNKNOWN");
   }

   uint64_t cardSize = SD.cardSize() / (1024 * 1024);
   Serial.printf("SD Card Size: %lluMB\n", cardSize);

  
   while (INPUT_COUNTER < dataLength)
   {
       if (Serial.available() >= sizeof(float))
       {
           sentData[INPUT_COUNTER] = Serial.parseFloat();
           INPUT_COUNTER++;
       }
   }
   delay(100);
   digitalWrite(ledPin, HIGH);
   for (size_t x = 0; x < dataLength; ++x)
   {
       checksum += sentData[x];
       CNT++;
   }
   if (CNT == dataLength)
   {
       if (!corona)
       {
           int newCNT = 0;
           Serial.println("File doesn't exist");
           Serial.println("Creating file... ");
           Serial.println(dataLength);
           writeFile(SD, "/test.txt", "Corona Values: MULTIPLIED BY 100,000 TO ACHIEVE PROPER PRECISION \r\n");
           while (newCNT <= dataLength)
           {
               dataMsg = String(sentData[newCNT]*100000);
               delay(50);
               Serial.print("Saving data: ");
               Serial.println(dataMsg);
               appendFile(SD, "/test.txt", dataMsg.c_str());
               ++newCNT;
           }
       }
       printFloat(checksum, 8);
   }
   delay(100);
   digitalWrite(ledPin, LOW);
}

Below something to get you started receiving and sending a wav file.

The basics for the Arduino side

const uint8_t chunkSize = 32;
uint8_t buffer[chunkSize];
uint8_t index;

uint8_t cs;

void setup()
{
  Serial.begin(57600);
  // send a character so PC knows that Arduino is ready
  Serial.write('A');
}

void loop()
{
  if (Serial.available())
  {
    buffer[index] = Serial.read();
    cs += buffer[index];
    index++;
  }
  // if enough bytes received
  if (index >= chunkSize)
  {
    // you can write received bytes to SD card here
    ...
    ...
    // clear the checksum and index for the next block
    cs = 0;
    index = 0;

    // send the checksum back to the PC
    // indicates to PC to send the next chunk
    Serial.write(cs);
  }
}

The code demonstrates how to receive bytes (representing floats) in binary format and caculate the checksum. Although there is a checksum clculation, there is no validation.

For my own learning, I wrote a small Python script to send a file to the Arduino

import sys
import serial

chunkSize = 32

try:
    # open serial port; adjust to your needs
    ser = serial.Serial('COM20', baudrate=57600)

    # wait for reply from Arduino after opening the port
    numBytes = 0
    while (numBytes == 0):
        numBytes = ser.inWaiting()
    serBytes = ser.read()
    print(serBytes)

except:
    print("Unexpected error:", sys.exc_info()[0])
    print("Unexpected error:", sys.exc_info()[1])

    # exit app
    sys.exit(1)

try:
    # open file in binary mode
    fp = open("myData.wav", "rb")
    
except:
    print("Unexpected error:", sys.exc_info()[0])
    print("Unexpected error:", sys.exc_info()[1])
    
    # close serial port
    print("closing port")
    ser.close()
    # exit app
    sys.exit(1)

try:
    #read file
    while True:
        data = fp.read(chunkSize)
        if not data:
            break
        # print for debugging
        print (data)
        # send to Arduino
        ser.write(data)
        # wait for reply
        numBytes = 0    
        while (numBytes == 0):
            numBytes = ser.inWaiting()
        serBytes = ser.read()
        print(serBytes)
except:
    print("Unexpected error:", sys.exc_info()[0])
    print("Unexpected error:", sys.exc_info()[1])

# close file
print("closing file")
fp.close()

# close port
print("closing port")
ser.close()

sys.exit(0)

There are some weak points.
1)
The file needs to be an exact multiple of chunkSize; if not, the Arduino will wait forever for the rest of the packet before processing it
2)
The sender will hang if the Arduino does not reply.

Further a chunkSize of 512 bytes (both sides) might be more optimal.

Changes to make
Calculate checksum at sender side. Validate checksum at arduino side before saving to SD file; in that case you must e.g. 0 back if it matches or 1 if it failed. The sender can either abort or resend the chunk.

PS
Tested with a Nano, I don't have an ESP.