Audio recorder
recorder.ino
#include <M5Cardputer.h> // Use M5Cardputer library instead of M5Stack
#include <driver/i2s.h>
#include <SD.h>
File audioFile;
bool isRecording = false;
uint32_t data_size = 0; // Track the size of the recorded data
String msg = "Press any key to start or stop recording.";
const String baseName = "/recording"; // Base filename for recordings. Files will not be overwritten; a unique number will be appended if a file already exists
// I2S configuration for the SPM1423 MEMS microphone
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
.sample_rate = 64000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 1024,
};
// I2S pin configuration (for M5Cardputer)
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_PIN_NO_CHANGE, // Not used for PDM
.ws_io_num = 43, // Clock pin
.data_out_num = I2S_PIN_NO_CHANGE, // No output needed
.data_in_num = 46, // Data pin
};
// WAV file header structure
struct WAVHeader {
char riff_header[4]; // "RIFF"
uint32_t wav_size; // Size of the wav portion of the file, excluding the first 8 bytes
char wave_header[4]; // "WAVE"
char fmt_header[4]; // "fmt "
uint32_t fmt_chunk_size;// Size of the fmt chunk (16 for PCM)
uint16_t audio_format; // Audio format (1 for PCM)
uint16_t num_channels; // Number of channels (1 for mono)
uint32_t sample_rate; // Sampling rate (e.g., 64,000)
uint32_t byte_rate; // Number of bytes per second (sample_rate * num_channels * bits_per_sample / 8)
uint16_t sample_alignment; // num_channels * bits_per_sample / 8
uint16_t bit_depth; // Bits per sample
char data_header[4]; // "data"
uint32_t data_bytes; // Number of bytes in the data portion of the file
};
void displayMsg(String message) {
int x = 0; // Starting X position
int y = 0; // Starting Y position
int maxWidth = 200; // Screen width in pixels
int maxHeight = 135; // Screen height in pixels
int lineHeight = 20; // Line height based on text size
M5Cardputer.Lcd.fillScreen(TFT_BLACK); // Clear the screen
M5Cardputer.Lcd.setTextColor(TFT_WHITE);
M5Cardputer.Lcd.setTextSize(2); // Adjust text size as needed
String currentLine = "";
for (int i = 0; i < message.length(); i++) {
currentLine += message[i];
// Check if current line exceeds the screen width or it's a newline character
int lineWidth = M5Cardputer.Lcd.textWidth(currentLine);
if (lineWidth > maxWidth || message[i] == '\n') {
M5Cardputer.Lcd.setCursor(x, y);
M5Cardputer.Lcd.println(currentLine); // Print the line
currentLine = ""; // Reset for the next line
y += lineHeight; // Move cursor to the next line
// If we've reached the bottom of the screen, clear and reset
if (y + lineHeight > maxHeight) {
y = 0; // Reset Y to the top of the screen
M5Cardputer.Lcd.fillScreen(TFT_BLACK); // Clear the screen
}
}
}
// Print any remaining text that didn’t trigger a wrap
if (currentLine.length() > 0) {
M5Cardputer.Lcd.setCursor(x, y);
M5Cardputer.Lcd.println(currentLine);
}
}
String generateFilename() {
String extension = ".wav";
int fileIndex = 0;
String filename = baseName + extension;
// Check if the base filename exists, if it does, increment the number
while (SD.exists(filename)) {
fileIndex++; // Increment the number
filename = baseName + String(fileIndex) + extension; // Create the new filename
}
return filename;
}
// Function to create the WAV file header
void createWAVHeader(WAVHeader *header, uint32_t sample_rate, uint16_t bit_depth, uint16_t num_channels, uint32_t data_size) {
memcpy(header->riff_header, "RIFF", 4);
header->wav_size = data_size + 36; // Data size + header size - 8
memcpy(header->wave_header, "WAVE", 4);
memcpy(header->fmt_header, "fmt ", 4);
header->fmt_chunk_size = 16;
header->audio_format = 1; // PCM format
header->num_channels = num_channels;
header->sample_rate = sample_rate;
header->byte_rate = sample_rate * num_channels * bit_depth / 8;
header->sample_alignment = num_channels * bit_depth / 8;
header->bit_depth = bit_depth;
memcpy(header->data_header, "data", 4);
header->data_bytes = data_size;
}
void setup() {
M5Cardputer.begin(); // Initialize M5Cardputer (instead of M5.begin)
Serial.begin(115200);
// Initialize SD card
if (!SD.begin()) {
msg = "SD Card initialization failed!";
displayMsg(msg);
Serial.println(msg);
return;
}
// Check free space (wonder why I added this!)
if (SD.totalBytes() - SD.usedBytes() < 2048 ) {
msg = "Not enough space on the SD Card!";
displayMsg(msg);
Serial.println(msg);
return;
}
displayMsg(msg);
// I2S setup
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
}
void startRecording() {
// Open the WAV file in write mode to overwrite the previous recording
String filename = generateFilename();
audioFile = SD.open(filename, FILE_WRITE);
if (!audioFile) {
String msg = "Failed to open file for writing!";
displayMsg(msg);
Serial.println(msg);
return;
}
// Write WAV header placeholder
WAVHeader header;
data_size = 0; // Reset data size for the new recording
createWAVHeader(&header, 64000, 16, 1, data_size);
audioFile.write((uint8_t*)&header, sizeof(WAVHeader)); // Write initial header
msg = "Recording started...";
displayMsg(msg);
Serial.println(msg);
isRecording = true;
}
void stopRecording() {
// Update the WAV header with the correct data size
audioFile.seek(0); // Move to the start of the file to overwrite the header
WAVHeader header;
createWAVHeader(&header, 64000, 16, 1, data_size); // Update the size in the header
audioFile.write((uint8_t*)&header, sizeof(WAVHeader)); // Overwrite with correct header
audioFile.close(); // Close the file
msg = "Recording stopped and saved to SD card.";
displayMsg(msg);
Serial.println(msg);
isRecording = false;
}
void loop() {
M5Cardputer.update(); // Update the M5Cardputer state
// Check if any key has been pressed
if (M5Cardputer.Keyboard.isChange() && M5Cardputer.Keyboard.isPressed()) {
if (!isRecording) {
startRecording();
} else {
stopRecording();
}
}
// Continuously read from I2S and write to file during recording
if (isRecording) {
const int bufferSize = 1024;
int16_t i2sBuffer[bufferSize];
size_t bytes_read = 0;
// Define an amplification factor (e.g., 3x amplification) -- otherwise very low volume
const int amplificationFactor = 3;
// Continuously read data until stopRecording is called
i2s_read(I2S_NUM_0, (void*)i2sBuffer, bufferSize * sizeof(int16_t), &bytes_read, portMAX_DELAY);
// Only write if data was read
if (bytes_read > 0) {
// Amplify each sample in the buffer
for (int i = 0; i < bytes_read / sizeof(int16_t); i++) {
i2sBuffer[i] *= amplificationFactor;
// Prevent clipping by limiting values to 16-bit range
if (i2sBuffer[i] > INT16_MAX) i2sBuffer[i] = INT16_MAX;
if (i2sBuffer[i] < INT16_MIN) i2sBuffer[i] = INT16_MIN;
}
audioFile.write((uint8_t*)i2sBuffer, bytes_read); // Write PCM data to file
data_size += bytes_read; // Accumulate the total size of data written
}
}
}