Can the Teensy 4.0 + audio shield Rev D play wav files and record simultaneously?

Status
Not open for further replies.

JAU

Member
Hello, I am hoping that someone here has run into this problem before. Here's my situation:

I am working on a project that requires simultaneously playing a wav file from the SD card while recording back to the the same SD card. I haven't found much on this topic from scouring old forum posts.

I have made new program by combining Recorder.ino and WavFilePlayer.ino that can record audio fine on its own and playback wav files fine on their own, but if I try recording AND playing back a wav file simultaneously, only a half-second of the wav file is recorded or it will record the wave file with skips and changes in frequency (like sped up parts). During recording when I trigger the wav file, I will hear the file played back through the headphones normally but this is not how it is being recorded back to the card.

I know that it's possible to use SamplePlayer.ino to playback samples from a wav file that has been converted to a data array. I managed to use this method for recording and playback earlier this will not work for my project.

I want to design an easy to use audio recorder where the user can drag and drop a wav file on to the SD card and easily play it back with the press of a button while recording input from a microphone and the triggered wav file back on to the SD card. I would like to have the user do no manual file conversion (like with wav2sketch) or modification of program code (like including new .cpp and .h files that are needed in the SamplePlayer.ino sketch).


My question is: Is there a simpler or more elegant way that I can achieve this with the Teensy 4.0 and audio shield Rev D? If not, does anyone have ideas for how I could convert wav files on the SD to data arrays automatically on the Teensy, so that they can be played back using the SamplePlayer.ino sketch? That method did not create these corruptions and is my only thought for a workaround but feels a little out of my wheelhouse. My only other thought here is to use a second Teensy + audio shield but I feel like that is overkill.

Is this even within the limits of a single Teensy 4.0? Am I over thinking this problem and using the wrong type of SD card, or not using a correct buffer length, or something? I think this should be possible to do but I am all out of ideas and don't want to waste my time trying to do the impossible with this microcontroller.

Any insights would be greatly appreciated! Thank you!!
 
I'm pretty sure the Arduino SD library is not up to this task. Maybe SdFat could do it, but even then, I'm not sure.

The audio shield and audio library certainly can do simultaneous input & output. Software support for the SD card is weak link here.
 
I'm pretty sure the Arduino SD library is not up to this task. Maybe SdFat could do it, but even then, I'm not sure.

The audio shield and audio library certainly can do simultaneous input & output. Software support for the SD card is weak link here.

This was my impression based on how the sound was being recorded back to the SD but thought I would check with some folks who have had more than one week of experience with this micro controller and shield.

Paul, I appreciate your response, I will try using SdFat and seeing if it makes any difference but I have backup work-around in mind if it does not. Do you think it is possible for me to write code to automatically convert WAV files on an SD to a data array to be played back from somewhere other than the SD? When I use wav2sketch, I'm not sure what is happening for this conversion but I am wondering if this is possible to do once the card is inserted into the Teensy. I imagine it's out of my wheelhouse at my level of programming but curious what your thoughts are on the subject, that's the only other approach I can think of here.


As far as the forum rules for submitting code, I kludged together Record.ino and WavFilePlayer.ino pretty quickly with very little modification if any. This post was more about if anyone has ever simultaneously done recording to while playing wav files from the same SD card.

I can upload the code if you like but it's cringe worthy I'm sure. If anyone has any input as to how this could be modified to improve the final recording, I'm all ears! Thanks for any insights even if it's confirming that this is not possible with my current code/hardware.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s2;           //xy=105,63
AudioAnalyzePeak         peak1;          //xy=278,108
AudioRecordQueue         queue1;         //xy=281,63
AudioPlaySdRaw           playRaw1;       //xy=302,157
AudioOutputI2S           i2s1;           //xy=470,120
AudioConnection          patchCord1(i2s2, 0, queue1, 0);
AudioConnection          patchCord2(i2s2, 0, peak1, 0);
AudioConnection          patchCord3(playRaw1, 0, i2s1, 0);
AudioConnection          patchCord4(playRaw1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265,212
// GUItool: end automatically generated code

// Bounce objects to easily and reliably read the buttons
Bounce buttonRecord = Bounce(9, 8);
Bounce buttonStop =   Bounce(16, 8);  // 8 = 8 ms debounce time
Bounce buttonPlay =   Bounce(17, 8);

// which input on the audio shield will be used?
const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;

// Use these with the Teensy Audio Shield
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

// Remember which mode we're doing
int mode = 0;  // 0=stopped, 1=recording, 2=playing

// The file where data is recorded
File frec;

AudioPlaySdWav           playWav1;
AudioPlaySdWav           playWav2;
AudioPlaySdWav           playWav3;
AudioPlaySdWav           playWav4;
AudioPlaySdWav           playWav5;
//AudioPlaySdWav           playWav6;

// Use one of these 3 output types: Digital I2S, Digital S/PDIF, or Analog DAC
AudioOutputI2S           audioOutput;
//AudioOutputSPDIF       audioOutput;
//AudioOutputAnalog      audioOutput;
AudioConnection          patchCord5(playWav1, 0, audioOutput, 0);
AudioConnection          patchCord6(playWav1, 1, audioOutput, 1);
//AudioControlSGTL5000     sgtl5000_1;

// Use these with the Teensy Audio Shield
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

// Use these with the Teensy 3.5 & 3.6 SD card
//#define SDCARD_CS_PIN    BUILTIN_SDCARD
//#define SDCARD_MOSI_PIN  11  // not actually used
//#define SDCARD_SCK_PIN   13  // not actually used

// Use these for the SD+Wiz820 or other adaptors
//#define SDCARD_CS_PIN    4
//#define SDCARD_MOSI_PIN  11
//#define SDCARD_SCK_PIN   13

Bounce button0 = Bounce(0, 5);
Bounce button1 = Bounce(1, 5);  // 5 ms debounce time
Bounce button2 = Bounce(2, 5);
Bounce button3 = Bounce(3, 5);
Bounce button4 = Bounce(4, 5);

void setup() {
  Serial.begin(9600);
 
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);

  pinMode(9, INPUT_PULLUP);
  pinMode(16, INPUT_PULLUP);
  pinMode(17, INPUT_PULLUP);

  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(60);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

   // 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);
    }
  }

  // Comment these out if not using the audio adaptor board.
  // This may wait forever if the SDA & SCL pins lack
  // pullup resistors
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);

  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // stop here, but print a message repetitively
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
}

void playFile(const char *filename)
{
  Serial.print("Playing file: ");
  Serial.println(filename);

  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playWav1.play(filename);

  // A brief delay for the library read WAV info
  delay(5);

  // Simply wait for the file to finish playing.
  while (playWav1.isPlaying()) {
    // uncomment these lines if you audio shield
    // has the optional volume pot soldered
    //float vol = analogRead(15);
    //vol = vol / 1024;
    // sgtl5000_1.volume(vol);
  }
}

void loop() {
  // First, read the buttons
  buttonRecord.update();
  buttonStop.update();
  buttonPlay.update();
  
  button0.update();
  button1.update();
  button2.update();
  button3.update();
  button4.update();

   // Respond to button presses
  if (buttonRecord.fallingEdge()) {
    Serial.println("Record Button Press");
    if (mode == 2) stopPlaying();
    if (mode == 0) startRecording();
  }
  if (buttonStop.fallingEdge()) {
    Serial.println("Stop Button Press");
    if (mode == 1) stopRecording();
    if (mode == 2) stopPlaying();
  }
  if (buttonPlay.fallingEdge()) {
    Serial.println("Play Button Press");
    if (mode == 1) stopRecording();
    if (mode == 0) startPlaying();
  }

    // If we're playing or recording, carry on...
  if (mode == 1) {
    continueRecording();
  }
  if (mode == 2) {
    continuePlaying();
  }

  // when using a microphone, continuously adjust gain
  if (myInput == AUDIO_INPUT_MIC) adjustMicLevel();

  // When the buttons are pressed, just start a sound playing.
  // The audio library will play each sound through the mixers
  // so any combination can play simultaneously.
  //
  if (button0.fallingEdge()) {
   playFile("SDTEST0.WAV");  // filenames are always uppercase 8.3 format
  delay(500);
  }
  if (button1.fallingEdge()) {
  playFile("SDTEST1.WAV");  // filenames are always uppercase 8.3 format
  delay(500);
  }
  if (button2.fallingEdge()) {
  playFile("SDTEST2.WAV");
  delay(500);
  }
  if (button3.fallingEdge()) {
  playFile("SDTEST3.WAV");
  delay(500);
  }
  if (button4.fallingEdge()) {

  playFile("SDTEST4.WAV");
  delay(500);
  }
}

void startRecording() {
  Serial.println("startRecording");
  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
  }
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  if (frec) {
    queue1.begin();
    mode = 1;
  }
}

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);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
  }
}

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;
}

void startPlaying() {
  Serial.println("startPlaying");
  playRaw1.play("RECORD.RAW");
  mode = 2;
}

void continuePlaying() {
  if (!playRaw1.isPlaying()) {
    playRaw1.stop();
    mode = 0;
  }
}

void stopPlaying() {
  Serial.println("stopPlaying");
  if (mode == 2) playRaw1.stop();
  mode = 0;
}

void adjustMicLevel() {
  // TODO: read the peak1 object and adjust sgtl5000_1.micGain()
  // if anyone gets this working, please submit a github pull request :-)
}
 
Last edited:
Do you think it is possible for me to write code to automatically convert WAV files on an SD to a data array to be played back from somewhere other than the SD?

Normally WAV files are just raw audio data with a small metadata header. If you know the data is 16 bits and stereo or mono, and if you don't mind a small glitch sound, you can just play the entire file as if it were raw data. Or write a little code to parse the section headers to find the actual data. The format is very simple. The only gotcha is if the WAV file is very unusual data format. But almost all are just raw 16 bit data.
 
Normally WAV files are just raw audio data with a small metadata header. If you know the data is 16 bits and stereo or mono, and if you don't mind a small glitch sound, you can just play the entire file as if it were raw data. Or write a little code to parse the section headers to find the actual data. The format is very simple. The only gotcha is if the WAV file is very unusual data format. But almost all are just raw 16 bit data.

Hrm. Well, I will invest some more time into this project then I guess. I'm still pretty new to programming and working with audio files and micro controllers in general. I think all of the WAV files I am using are in the usual format, if I am understanding you correctly are you saying that I could use something like playSdRaw to play WAV files as RAW files with no conversion from the SD card on the Teensy?

I look more into parsing section headers for finding actual data. This sounds a little advanced for me, but if it is well documented I'm sure I can hack and slash my way through it. All hope is not lost for this project yet! :eek: Thanks for developing the Teensy and being available to answer questions about it.
 
Status
Not open for further replies.
Back
Top