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.
Teensy Setup
Section titled “Teensy Setup”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.
Teensy Code
Section titled “Teensy Code”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 codeAudioInputI2S i2s2; //xy=105,63AudioRecordQueue queue1; //xy=281,63AudioConnection patchCord1(i2s2, 0, queue1, 0);// GUItool: end automatically generated code
// Bounce objects to easily and reliably read the buttonsBounce buttonRecord = Bounce(0, 8);Bounce buttonStop = Bounce(1, 8); // 8 = 8 ms debounce timeBounce 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 doingint mode = 0; // 0=stopped, 1=recording, 2=playing
// The file where data is recordedFile 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;}
Converting files to WAV
Section titled “Converting files to WAV”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
Daisy Setup
Section titled “Daisy Setup”Daisy Code
Section titled “Daisy Code”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
Recording example code
Section titled “Recording example code”#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); }}