How to record audio and save it to wav file using Electret Mic Amp - MAX9814?

I have had the time to cobble together some code but wasn’t able to test yet.
This also creates the WAV header

#include <SdFat.h>
#include <ADCDMAGen3_RK.h>

//SYSTEM_THREAD(ENABLED);

SerialLogHandler logHandler(LOG_LEVEL_WARN, { // Logging level for non-application messages
    { "app", LOG_LEVEL_INFO } // Logging level for application messages
});
// WAV header spec information:
//https://web.archive.org/web/20140327141505/https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
//http://www.topherlee.com/software/pcm-tut-wavformat.html
typedef struct wav_header {
  // RIFF Header
  char     riff_header[4];   // Contains "RIFF"
  uint32_t wav_size;         // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8
  char     wave_header[4];   // Contains "WAVE"
    
  // Format Header
  char     fmt_header[4];    // Contains "fmt " (includes trailing space)
  uint32_t fmt_chunk_size;   // Should be 16 for PCM
  uint16_t audio_format;     // Should be 1 for PCM. 3 for IEEE Float
  uint16_t num_channels;
  uint32_t sample_rate;
  uint32_t byte_rate;        // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample
  uint16_t sample_alignment; // num_channels * Bytes Per Sample
  uint16_t bit_depth;        // Number of bits per sample
    
  // Data
  char     data_header[4];   // Contains "data"
  uint32_t data_bytes;       // Number of bytes in data. Number of samples * num_channels * sample byte size
  // uint8_t bytes[];        // Remainder of wave file is bytes
} WavHeader_t;

const  int        SD_SS                   = A2; 
const  int        SAMPLE_PIN              = A0;
const  int        SAMPLE_CHANNELS         =     1; // 1 .. mono, 2 .. stereo     
const  size_t     SAMPLE_FREQ             = 16000; // Hz
const  size_t     SAMPLES_IN_BUFFER       =  1024;
const  uint32_t   MAX_RECORDING_LENGTH_MS = 30000; // milliseconds

nrf_saadc_value_t *bufferReady            = 0;
nrf_saadc_value_t buffer0[SAMPLES_IN_BUFFER];
nrf_saadc_value_t buffer1[SAMPLES_IN_BUFFER];

int               fileCount               = 0;
uint32_t          recordingStart;
enum              State { STATE_WAITING, STATE_CONNECT, STATE_RUNNING, STATE_FINISH };
State             state                   = STATE_WAITING;

SdFat             sd;
File              myFile;
WavHeader_t       wh;

ADCDMAGen3 adc;

void myBufferCallback(nrf_saadc_value_t *buf, size_t size);

void setup() {
  Serial.begin(9600);
  System.on(button_click, buttonHandler);
  pinMode(D7, OUTPUT);
	
  ret_code_t err = adc
	.withSampleFreqHz(SAMPLE_FREQ)
	.withDoubleBuffer(SAMPLES_IN_BUFFER, buffer0, buffer1)
	.withSamplePin(A0)
	.withBufferCallback(myBufferCallback)
	.init();
  Log.info("adc.init %lu", err);

  if (sd.begin(SD_SS, SPI_FULL_SPEED)) 
    Log.info("SD initialised");
  else
    Log.warn("failed to open card");
}

void loop() {
  switch(state) {
	case STATE_WAITING:
	  // Waiting for the user to press the SETUP button. The setup button handler
	  // will bump the state into STATE_CONNECT
	break;

	case STATE_CONNECT:
	{
      char fileName[128];
      snprintf(fileName, sizeof(fileName), "rec%04d.wav", fileCount+1);
      if (myFile.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) {
    	strcpy(wh.riff_header, "RIFF");
  	    strcpy(wh.wave_header, "WAVE");
  	    strcpy(wh.fmt_header , "fmt ");
	    strcpy(wh.data_header, "data");
        wh.fmt_chunk_size   = 16;
        wh.audio_format     = 1; 
        wh.num_channels     = SAMPLE_CHANNELS;
        wh.bit_depth        = 16;
        wh.sample_rate      = SAMPLE_FREQ;
        wh.sample_alignment = wh.num_channels * wh.bit_depth / 8; 
        wh.byte_rate        = wh.sample_rate * wh.sample_alignment; 
        wh.data_bytes       = 0;
        wh.wav_size         = sizeof(wh) - 8;
        if (myFile.write((uint8_t *)&wh, sizeof(wh)) < sizeof(wh)) {
  		  Log.warn("error writing WAV header");
  		  myFile.close();
		  state = STATE_WAITING;
          return;
		}
        
        fileCount++;
        digitalWrite(D7, HIGH);
	    Log.info("Writing to %s", fileName);
		recordingStart = millis();
        adc.start();
        state = STATE_RUNNING;
      }
      else {
	    Log.warn("opening %s for write failed", fileName);
		state = STATE_WAITING;
	  }
	}
    break;

	case STATE_RUNNING:
      if (bufferReady) {
	    int16_t *samples = (int16_t*)bufferReady;
	    bufferReady = 0;
	    for (int i = 0; i < SAMPLES_IN_BUFFER; i++) {
	      samples[i] -= 2048;  // transpose 0 .. +4095 to -2048 .. +2047 
	      samples[i] <<= 4;    // 12bit -> 16bit
	    }
        int count = myFile.write((uint8_t *)samples, SAMPLES_IN_BUFFER * 2);
        wh.wav_size   += count;
		wh.data_bytes += count;
		if (count == SAMPLES_IN_BUFFER * 2)
		  Log.trace("%d samples written", count/2);
		else {
  		  Log.warn("error writing %d", count/2);
		  state = STATE_FINISH;
		}
      }
      if (millis() - recordingStart >= MAX_RECORDING_LENGTH_MS) {
		state = STATE_FINISH;
	  }
	break;

	case STATE_FINISH:
		adc.stop();
		Log.info("stopping");
		
        myFile.seek(0);
        if (myFile.write((uint8_t *)&wh, sizeof(wh)) < sizeof(wh))
  		  Log.warn("error writing WAV header");

		myFile.close();

		digitalWrite(D7, LOW);
		state = STATE_WAITING;
		break;
	}
}

void buttonHandler(system_event_t event, int data) {
	switch(state) {
	case STATE_WAITING:
		state = STATE_CONNECT;
		break;

	case STATE_RUNNING:
		state = STATE_FINISH;
		break;
	}
}

void myBufferCallback(nrf_saadc_value_t *buf, size_t size) {
  bufferReady = buf;
}

You can start/stop a recording with the MODE button and via MAX_RECORDING_LENGTH_MS (currently 30 seconds) you can limit the length of a recording.

2 Likes