Skip to content

Recording

This article details the recording system, including the code and setup required to work both with a Teensy 4.1 and a Daisy Seed.

The setup described here is based on Teensy’s Recorder example sketch [add link], although the Audio Shield has been replaced with a CJMCU PCM1802. These boards can be found on AliExpress for cheap but, as described here, some batches might be faulty. Refer to the forum discussions to learn which one you are safe to purchase.

Schematic showing the connection between a Teensy 4.1 and a CJMCU ADC board Lightbox shot of the breadboard prototype showing the ADC board in the front A different lightbox shot of the breadboar prototype showing the ADC board in the back

This code is a modified version of Teensy’s Recorder.ino example that you can find in the Arduino IDE under Examples > Audio > Recorder after you select a Teensy board as the target. This code is modified to work with a generic I2S interface instead of the the Teensy Audio Board. It also manages the naming of the file recordings by formatting the naming as RECORDXX.RAW, where the XX represent a double digit number from 00 to 99.

#include <Bounce.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// GUItool: begin automatically generated code
AudioInputI2S i2s2; //xy=105,63
AudioRecordQueue queue1; //xy=281,63
AudioConnection patchCord1(i2s2, 0, queue1, 0);
// GUItool: end automatically generated code
// Bounce objects to easily and reliably read the buttons
Bounce buttonRecord = Bounce(0, 8);
Bounce buttonStop = Bounce(1, 8); // 8 = 8 ms debounce time
Bounce buttonPlay = Bounce(2, 8);
// Use these with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11 // not actually used
#define SDCARD_SCK_PIN 13 // not actually used
// Remember which mode we're doing
int mode = 0; // 0=stopped, 1=recording, 2=playing
// The file where data is recorded
File frec;
void setup() {
// Configure the pushbutton pins
pinMode(0, INPUT_PULLUP);
pinMode(1, INPUT_PULLUP);
pinMode(2, INPUT_PULLUP);
// Audio connections require memory, and the record queue
// uses this memory to buffer incoming audio.
AudioMemory(60);
// Initialize the SD card
SPI.setMOSI(SDCARD_MOSI_PIN);
SPI.setSCK(SDCARD_SCK_PIN);
if (!(SD.begin(SDCARD_CS_PIN))) {
// stop here if no SD card, but print a message
while (1) {
Serial.println("Unable to access the SD card");
delay(500);
}
}
}
void loop() {
// First, read the buttons
buttonRecord.update();
buttonStop.update();
buttonPlay.update();
// Respond to button presses
if (buttonRecord.fallingEdge()) {
Serial.println("Record Button Press");
if (mode == 0) startRecording();
}
if (buttonStop.fallingEdge()) {
Serial.println("Stop Button Press");
if (mode == 1) stopRecording();
}
// If we're playing or recording, carry on...
if (mode == 1) {
continueRecording();
}
}
void startRecording() {
char filename[13] = "RECORD00.RAW";
int index = 0;
// Find an unused filename
while (SD.exists(filename)) {
index++;
// tutorialspoint.com/c_standard_library/c_function_sprintf.htm
// sprintf() formats strings and stores the result to a character
// array instead of printing to the main output like printf()
sprintf(filename, "RECORD%02d.RAW", index);
}
}
void continueRecording() {
if (queue1.available() >= 2) {
byte buffer[512];
// Fetch 2 blocks from the audio library and copy
// into a 512 byte buffer. The Arduino SD library
// is most efficient when full 512 byte sector size
// writes are used.
memcpy(buffer, queue1.readBuffer(), 256);
queue1.freeBuffer();
memcpy(buffer+256, queue1.readBuffer(), 256);
queue1.freeBuffer();
// write all 512 bytes to the SD card
//elapsedMicros usec = 0;
frec.write(buffer, 512);
}
}
void stopRecording() {
Serial.println("stopRecording");
queue1.end();
if (mode == 1) {
while (queue1.available() > 0) {
frec.write((byte*)queue1.readBuffer(), 256);
queue1.freeBuffer();
}
frec.close();
}
mode = 0;
}

When working with the Teensy I often times wanted to hear the recordings besides using them for sound analysis. To do this I used SoX to convert the files to the wav format. While at first it’s manageable to do this manually from the terminally, I quickly noticed that this process was becoming frustrating. For this reason, I wrote a quick helper script that I can use to do the conversion automatically. The script is made to work on macOS.

#!/bin/zsh
DIRECTORY="/Volumes/[name-of-SD-card-volume]"
echo "Checking if directory exists: ${DIRECTORY}"
if test -d "${DIRECTORY}"
then
echo "Directory exists"
cd "${DIRECTORY}"
# Use a loop to iterate over files matching the pattern
for file in RECORD[0-9][0-9].RAW; do
if test -f "$file"; then
echo "File exists: $file"
# Extract the base name (without extension)
basename="${file%.*}"
sox -r 44100 -e signed -b 16 -c 1 "$file" "${basename}.wav"
echo "File converted successfully: ${basename}.wav"
fi
done
cd -
else
echo "SD card not found"
fi

libDaisy, the hardware library for the Daisy platform, provides a helpful recording module called WavWriter that abstracts the complexities of recording a wav file to an SD card. If you don’t have experience with the platform, it might be unclear at first that the instructions provided in the documentation are not complete to successfully record sound.

While WavWriter does take care of recording, you first need to configure the SDMMC Handler, start the FatFSInterface and mount the SD card. If this sounds scary, don’t panic! Among the examples bundled with the Daisy Examples repository you can find one named SDMMC. You can reference the SDMMC example to see how you prepare the Daisy to interface with an SD card. In my code I ended up creating this helper function to take care of everything:

// This function needs to be called inside 'main()'
// before opening a file for recording
//
// This function depends on two global variables:
// SdmmcHandler sd
// FatFSInterface fsi
void mountSD()
{
SdmmcHandler::Config sd_cfg;
sd_cfg.Defaults();
// Recording is unsuccessful with faster speeds
sd_cfg.speed = daisy::SdmmcHandler::Speed::MEDIUM_SLOW;
sd.Init(sd_cfg);
fsi.Init(FatFSInterface::Config::MEDIA_SD);
f_mount(&fsi.GetSDFileSystem(), "/", 1);
}

Writing to SD card on the Daisy is done through FatFS, a filesystem module for embedded systems. As you might have noticed if you’ve taken a look at the SDMMC example code, FatFS is not part of libDaisy and must therefore be included in the build. When working withing the Daisy Examples folder structure, this step is pretty easy. You simply have to add the following flag to the makefile:

USE_FATFS = 1
#include "daisy_seed.h"
#include "daisysp.h"
#include "fatfs.h"
using namespace daisy;
using namespace daisysp;
DaisySeed hw;
// Per the libDaisy documentation, it is recommended
// to use a size of 16384 or 32768 for optimal performance.
WavWriter<16384> writer;
SdmmcHandler sd;
FatFSInterface fsi;
void AudioCallback(AudioHandle::InputBuffer in,
AudioHandle::OutputBuffer out,
size_t size)
{
for (size_t i = 0; i < size; i++)
{
writer.Sample(in[0]); // Only recording the left channel
}
}
void mountSD()
{
SdmmcHandler::Config sd_cfg;
sd_cfg.Defaults();
// Recording is unsuccessful with faster speeds
sd_cfg.speed = daisy::SdmmcHandler::Speed::MEDIUM_SLOW;
sd.Init(sd_cfg);
fsi.Init(FatFSInterface::Config::MEDIA_SD);
f_mount(&fsi.GetSDFileSystem(), "/", 1);
}
int main(void)
{
hw.Init();
hw.StartLog(true);
// Writer configuration
WavWriter<16384>::Config w_config;
w_config.bitspersample = 16;
w_config.samplerate = 48000;
w_config.channels = 1;
writer.Init(w_config);
mountSD();
writer.OpenFile("recording.wav");
hw.PrintLine("Opened file");
hw.SetAudioBlockSize(4);
hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
hw.StartAudio(AudioCallback);
uint32_t timeout = 10 * 100; // More or less 10 seconds
bool isRecording = true;
hw.PrintLine("Recording...");
while(isRecording)
{
writer.Write();
timeout--;
if (timeout == 0)
{
isRecording = false;
}
System::Delay(10);
}
// Save file and notify state with blinking led
writer.SaveFile();
hw.PrintLine("Saved file to SD");
// Setup led
bool ledstate = true;
while(1)
{
hw.SetLed(ledstate);
ledstate = !ledstate;
System::Delay(100);
}
}