Record sound in .wav format with Arduino MKR zero

Hi,
I am trying to save data from Adafruit I2S MEMS Microphone Breakout - SPH0645LM4H to onboard sdcard of arduino MKR zero in .wav format.
I am able to record audio data in .wav format but it contains too much noise and volume is too less. Can anyone help me with this?

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

#define TIME_LIMIT 30// (seconds)

File rec;

uint8_t buffer[512];
//uint8_t avg_buffer[512];
volatile int available = 0;
volatile int read = 0;
unsigned long recordStart = 0;
bool recordInProgress = false;

unsigned long fileSize = 0L;
unsigned long waveChunk = 16;                     //bit size
unsigned int waveType = 1;                        //
unsigned int numChannels = 1;                     //1:mono    2:stereo
unsigned long sampleRate = 15625;                  //samples per sec
unsigned long bytesPerSec = 46875;          //samplerate*NumChannels*(Bitpersample/8)
unsigned int blockAlign = 3;                      //NumChannels*Bitpersample/8
unsigned int bitsPerSample = 24;            
unsigned long dataSize = 0L;
unsigned long recByteSaved = 0L;
byte byte1, byte2, byte3, byte4,byte5;
byte bufferToWrite[2048];
int lenOfBufferToWrite = 0;
int temp = 0;


void setup()
{
 
    Serial.begin(115200);
    while (!Serial)
    {
        ; // wait for serial port to connect. Needed for native USB port only
    }

    // see if the card is present and can ben initialized
    if (!SD.begin())
    {
        Serial.println("Card SD failed or not present");
        return; // do nothing
    }

    I2S.onReceive(onI2SReceive);

    int sampleRateForI2S = 31250;  // 48,000,000 / sampleRate must be a multiple of 64
    if (!I2S.begin(I2S_PHILIPS_MODE, sampleRateForI2S, 32))
    {
        Serial.println("Failed to initialize I2S!");
        while (1)
            ; // do nothing
    }

    StartRec();
    I2S.read(); // Needed to start the interrupt handler
}

void loop()
{
     
    if (millis() - recordStart <= TIME_LIMIT * 1000)
    {
        if (available)
        {
            for (int i = 0; i < 32; i++)//32
            {
                // buffer[0] to buffer[3] are zeros (it is the other channel), so we skip those
                // buffer[4] is always zero
                // buffer[5], [6] and [7] are the interesting data, on 24 bits (6 of which are always zeros)
                // Etc
                // We only want the 256 first values, hence we stope at 8 * 31 + 7 = 255
                // More info on robsgreen's great post: https://forums.adafruit.com/viewtopic.php?f=19&t=115089&start=15#wrap
               memcpy(&bufferToWrite[lenOfBufferToWrite + 3 * i], &buffer[(8 * i) + 5], 3);       //3
             } 
            lenOfBufferToWrite += 96;     //96
               
            if (lenOfBufferToWrite == 2016)
            {
                // Write on SD card by batches of 2048 bits for better performance
                rec.write(bufferToWrite, 2016);
                lenOfBufferToWrite = 0;
                recByteSaved += 2016;
                Serial.println(recByteSaved);
            }
          available = 0;
        }
    }
    else if (recordInProgress)
    {
        Serial.println(recByteSaved); // Generally, we record 14000 to 15000 bytes per second
        StopRec();
    }
}

void StartRec()
{ // begin recording process
    writeWavHeader();
    Serial.println(F("Starting recording"));
    recordStart = millis();                       //Returns the number of milliseconds since the Arduino board began running the current program
    recordInProgress = true;
}

void StopRec()
{ // stop recording process, update WAV header, close file
    Serial.println(F("Ending recording"));
    writeOutHeader();
    recordInProgress = false;
}

void writeOutHeader()
{ // update WAV header with final filesize/datasize

    // rec.seekSet(4);
    rec.seek(4);
    byte1 = recByteSaved & 0xff; 
    byte2 = (recByteSaved >> 8) & 0xff;
    byte3 = (recByteSaved >> 16) & 0xff;
    byte4 = (recByteSaved >> 24) & 0xff;      //24

    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
   
    // rec.seekSet(40);
    rec.seek(40); 
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);   
    
    rec.close();
}

void onI2SReceive()
{
    // This function will run at a frequency of (sampleRate / 64)
    // At 31.25khz, this is every 1962.5 microseconds so make sure any processing here takes less
    // time than that

    I2S.read(buffer, 512);  // Will actually read 256 bytes
    available = 1;
}

void writeWavHeader()
{ // write out original WAV header to file
    recByteSaved = 0;
    // rec.open("rec00000.wav", O_CREAT | O_TRUNC | O_RDWR);
    String filename = "rec00000.wav";
    if (SD.exists(filename))
    {
        SD.remove(filename);
    }
    rec = SD.open(filename, FILE_WRITE);
    rec.write("RIFF");
    byte1 = fileSize & 0xff;
    byte2 = (fileSize >> 8) & 0xff;   
    byte3 = (fileSize >> 16) & 0xff;
    byte4 = (fileSize >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    rec.write("WAVE");
    rec.write("fmt ");
    byte1 = waveChunk & 0xff;
    byte2 = (waveChunk >> 8) & 0xff;
    byte3 = (waveChunk >> 16) & 0xff;
    byte4 = (waveChunk >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    byte1 = waveType & 0xff;
    byte2 = (waveType >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    byte1 = numChannels & 0xff;
    byte2 = (numChannels >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    byte1 = sampleRate & 0xff;
    byte2 = (sampleRate >> 8) & 0xff;
    byte3 = (sampleRate >> 16) & 0xff;
    byte4 = (sampleRate >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    byte1 = bytesPerSec & 0xff;
    byte2 = (bytesPerSec >> 8) & 0xff;
    byte3 = (bytesPerSec >> 16) & 0xff;
    byte4 = (bytesPerSec >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
    byte1 = blockAlign & 0xff;
    byte2 = (blockAlign >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    byte1 = bitsPerSample & 0xff;
    byte2 = (bitsPerSample >> 8) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write("data");   
    byte1 = dataSize & 0xff;        //dataSize
    byte2 = (dataSize >> 8) & 0xff;
    byte3 = (dataSize >> 16) & 0xff;
    byte4 = (dataSize >> 24) & 0xff;
    rec.write(byte1);
    rec.write(byte2);
    rec.write(byte3);
    rec.write(byte4);
}

Are you playing the WAV back on your computer, so you know it's a recording problem, and not a playback problem?

Noise is an analog problem. What's the nature of the noise? If it's not acoustic room noise (which any mic will pick up) then it's probably getting-in from the power supply. You can try powering the microphone module from a battery and if the noise goes away you know it's coming from the power supply.

Or, if you're getting power-line hum/buzz it could be electromagnetic interference from the power lines all-around you. If moving the mic module around, or putting your hand near it changes the hum you may need to mount the mic in a grounded metal case.

Noise should be independent of the signal, so it's more noticeable when there is no signal, or with a very-quiet signal and it usually gets masked (drowned-out) with a strong signal.

Distortion is damage or corruption of the signal and that can be an analog or digital problem. (There is no distortion when there is no signal.)

The low-level is probably normal. I don't see a gain/sensitivity control on the board. And, looking at the datasheet for the mic, it has an upper limit of 120dB SPL, so you won't get "full volume" unless you're in the front row of a rock concert . You could easily be 40dB or more below that so you'll probably need to amplify (digitally*) during recording or after recording.

If you add gain during recording you'll generally need some kind of "VU" meter and if you want the best quality you'll have to leave some headroom to prevent clipping. So depending on what you're doing you may need to amplify after recording.

Of course when you amplify, you'll also amplify the noise...

*Amplification is multiplication... 20dB is a factor of 10 so if you want to increase by +20dB you simply multiply all of the samples by 10. 40dB is a factor of 100. +6dB is a factor of two, so if you left-shift all of the bits in the sample one-place, that's +6dB. (Just remember you're bit-shifting words (24-bits, I believe), not bytes.

There's one important thing to watch-out for when amplifying... If the result exceeds your bit-depth (if you have 24-bit samples and the result is 25-bits) you'll overflow and loose the most-significant bit(s). That creates nasty-nasty distortion (much worse than clipping). The proper way to handle that is to [u]clip[/u] the data (write the maximum value) and then notify the user that it's clipping so they can turn it down or adjust the gain down, etc.

In the real world, most DSP is done in floating-point, which makes all of this a lot easier... Eventually, you need integers for the DAC or "regular" WAV files, but floating-point makes the math/processing easier. (But if you don't have a floating-point processor so you probably don't have the processing power to use floating-point in real-time.)

Thank you for providing details regarding noise. I am playing wav file on computer, so i think its a recording problem. As per the microphone document i am powering the microphone through Vcc of MKR Zero and noise in the wav file is more like a buzz sound.

Probably too late to be of help, but maybe this will help someone else who stumbles onto this thread.

I suspect the problem in your code might be that the data comes back from the device as big-endian (suggested by the fact that buffer[4] is always zero while buffer[5-7] contain actual data), while WAV files using "RIFF" should be encoded using little-endian. I would try reordering the bytes for the data you write out just like you reorder the bytes when writing out the header fields.

I expect that this kind of error would create a file that just sounds like a buzzy noise as you experienced.

1 Like