Record while playing a stereo wav file

notahardwareguy

Active member
Sorry if this is too obvious. I have searched the examples, forums, and some of the code libraries [could be just not seeing it] and am unable to find a complete, "simple" example of playing a stereo wav file from an SD card, running it through an effect, and recording it back to an SD card with a new name. As an example, read/play SDTEST1.wav from an SD card, run it through reverb, and save it recorded as SDTEST1Out.wav. Not sure if it can be saved to the same SD that it is read from but that would be preferred.

Backstory: I was doing a Teensy audio project early last year and ran in to a blocker. Started to get a little frustrated, so I stepped away and worked on a couple of ATTiny things, all the while noodling on this one in the back of my mind. I am now ready to pick this project back up. Thought of a couple of things to try and some things to do to validate my results. This would involve saving a copy of the results and comparing it visually to the starting wav. So I want to read a standard stereo wav, pump it through a filter, play it (so I can hear it) while also saving the results to another wav file. I have both the audio shield and the PT8211, so if the recorded wav needs to be on a different SD, that can be supported.

Does a working example of this (or something really close) already exist? Is there a reason this can not be done? Again, sorry if this is "too obvious", and thanks.
 
OK, I was able to put together a program to play a stereo wav file from an SD card, record it back as a stereo wav to another file on that SD card while outputting the initial file to the line out (of either the PT8211 or audio shield) - swiped most from various example programs. So far so good BUT the resulting file is larger than the original file (using either device) and not always the very same [wrong] size. Am trying to trouble-shoot that to find out why. I am using: AudioPlayWAVstereo and AudioRecordWAVstereo. Each has its own buffer. Minimal code example below. Any idea why the resulting wav is larger that the original? If you put the sample wav file on an SD, load the program, and open a console it should just go. The issue right now is if I compare the resulting file to the original [in Audacity] they don't match. Any thoughts/suggestions appreciated. Thanks.

Code:
// Play a wav file, record it and play it (same time)
// This works, reads a wave file (SDTEST1.wav), plays it out the audio shield or PT8211 jack while recording it to the same SD it is read from.
//
// arduino 1.8.19, Teensy 4.1, and Teensy Loader 1.58

#include <Audio.h>

#define SerialStart(a) ({Serial.begin(19200); while (!Serial && (millis( )<(a))); Serial.print( "\n" __DATE__ " --- " __TIME__ " --- " __FILE__ "\n" );} )

// **** only uncomment ONE of these...
//#define OUTPUT_PT8211
#define OUTPUT_SHIELD

// GUItool: begin automatically generated code
AudioPlayWAVstereo       playWAVstereo;
AudioAnalyzePeak         peakR;
#ifdef OUTPUT_SHIELD
  AudioOutputI2S           i2sOut;                                    // this when audio-shield
#else
  AudioOutputPT8211        i2sOut;                                    // this when PT8211
#endif
AudioAnalyzePeak         peakL;
AudioRecordWAVstereo     recordWAVstereo;

AudioConnection          patchCord01( playWAVstereo, 0, i2sOut, 0 );
AudioConnection          patchCord02( playWAVstereo, 1, i2sOut, 1 );
AudioConnection          patchCord03( playWAVstereo, 0, peakL, 0 );
AudioConnection          patchCord06( playWAVstereo, 1, peakR, 0 );
AudioConnection          patchCord04( playWAVstereo, 0, recordWAVstereo, 0 );
AudioConnection          patchCord05( playWAVstereo, 1, recordWAVstereo, 1 );

#ifdef OUTPUT_SHIELD
  AudioControlSGTL5000     sgtl5000_1;
#endif
// GUItool: end automatically generated code

#ifdef OUTPUT_SHIELD
  #define SDCARD_CS_PIN    10
#else
  #define SDCARD_CS_PIN    BUILTIN_SDCARD
#endif
// Use these with the Teensy 3.5, 3.6 and 4.1 SD card
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

const char* fileName = "SDTEST1.wav";
const char* fileNameOut = "SDTEST1OUT.wav";

int mode = 0;

void setup( )
{
  SerialStart( 60000 );   // wait up to a minute to have time open console window...
#ifdef OUTPUT_PT8211
  Serial.println( "Audio output to PT8211..." );
#endif
#ifdef OUTPUT_SHIELD
  Serial.println( "Audio output to Audio Shield..." );
#endif

  AudioMemory( 10 );

  // SD audio objects need buffers configuring:
  const size_t sz = 65536;
  const AudioBuffer::bufType bufMemIn = AudioBuffer::inHeap;
  const AudioBuffer::bufType bufMemOut = AudioBuffer::inHeap;
  playWAVstereo.createBuffer( sz, bufMemIn );
  recordWAVstereo.createBuffer( sz, bufMemOut );

  // Initialize the SD card
  SPI.setMOSI( SDCARD_MOSI_PIN );
  SPI.setSCK( SDCARD_SCK_PIN );
  while ( !( SD.begin( SDCARD_CS_PIN ) ) )
    { // loop here if no SD card, printing a message
    Serial.println("Unable to access the SD card");
    delay(500);
  }

#ifdef OUTPUT_SHIELD
  sgtl5000_1.enable( );
  sgtl5000_1.volume( 0.5 );
#endif

  delay( 1000 );
  startRecording( );
  startPlaying( );

  Serial.println("Playing");
}

void loop( )
{
  // If we're playing or recording, carry on...
  if ( mode != 0 )
    {
    continueRecording( );
    continuePlaying( );
    }
  else
    {
    // end of play, stop
    stopRecording( );

    while ( true )
      {
      delay( 2000 );
      }
    }
}

void startRecording( )
{
  Serial.println( "startRecording" );
  recordWAVstereo.recordSD( fileNameOut );
  mode = 1;
}

void continueRecording( )
{
  // nothing to do here!
}

void stopRecording( )
{
  Serial.println( "stopRecording" );
  recordWAVstereo.stop( ); 
  mode = 0;
}

void startPlaying( )
{
  Serial.println( "startPlaying" );
  playWAVstereo.playSD( fileName );
  mode = 2;
}

void continuePlaying( )
{
  if ( !playWAVstereo.isPlaying( ) )
    {
    Serial.println( "endOfPlayback" );
    mode = 0;
    }
}

void stopPlaying( )
{
  Serial.println( "stopPlaying" );
  playWAVstereo.stop( );
  mode = 0;
}

// eof
 
OK, did things.

Move the startRecording( ) from before, to after startPaying( ). I also fetched the latest buffered-SD audio source from h4yn0nnym0u5e.

Now when I do a fresh compile and load of code to the Teensy, the initial run produced an exact copy of the source wav file in size and content (as confirmed by Audacity) but not every subsequent run. Mostly the first load/run of the code to the Teensy results in a size and content match (via properties and Audacity) but the other runs typically result in a file that does not match and is smaller in size (slightly) than the original. The resulting wav file size is 16786988 (little smaller) instead of 16787550 and it no longer matches in Audacity. I have tried removing the temp file before the output file is created/written but it still results in mostly the resulting file not matching the source. Still searching/working but closer...
 
There's at least a couple of things going on here. One is that the SDTEST1.WAV file has additional information in its header, making the header 138 bytes long, rather than the 44 bytes that's generated by the AudioRecordWAVstereo object. The other is that it's only possible to record multiples of 128 samples (the audio block size), and SDTEST1.WAV is 4,196,843 samples long, which is not a multiple of 128...

Another thing that may occasionally cause a variation in the length of the recording is how long it takes to start playback, which can be a few milliseconds as the file header is parsed and the buffer loaded. This accounts for why it's better to put startRecording() second - unless you're very unlucky, the first recorded block will catch the first one played back, on the next audio update after startRecording() is called. Even better would be:
C++:
  playWAVstereo.playSD( fileName, true ); // start playback in paused mode

  AudioNoInterrupts(); // briefly turn off audio updates
  recordWAVstereo.recordSD( fileNameOut );
  playWAVstereo.play(); // allow playback to resume
  AudioInterrupts(); // resume audio updates
 
Thanks VERY much for that info. I actually ran into [I believe it was you] informing someone else on another much earlier thread on how to "pause" the interrupts so you could setup for recording but I could not remember exactly how it was worded, spent an hour doing different forum searches to no avail. Will absolutely make note of this in Evernote so I will have it handy.

That said, I have already modified the code to record two wav files concurrently from the one source/play and this is what I am seeing:

SDTEST1.wav 16787500
SDTEST1O1.wav 16787500
SDTEST1O2.wav 16788524

On a fresh load of the code to the Teensy, the first of the two recordings matches the source size and matches in Audacity perfectly but the second is always a little longer. I will add the audio interrupt in and run some more.

Again thanks for your info!
 
OK, pulled all three files (source, recording-1, and recording-2) in Audacity. Reviewed each in detail, recording-1 was 1 one-hundredth of a second longer than the original wav file but the extra was "dead air" so apparently Audacity ignored that and when I inverted the source and merged the two file, they zeroed out. The other recording, recording-3 was 10+ hundredth of seconds longer, again dead air but the extra length turned out to be too much in this case when initially compared. When I cut off the end of the tracks to get it down to the same length as the recording-1, it was a perfect match (per the compare). So I am "happy" and can move on with why I wanted this; I want to "see" that my custom effect is working by seeing the difference between the unmodified recording and the recording with the effect applied. I should now be able to do this. Thanks for your assistance!
 
That looks like the equal and opposite problem … ending a recording and tidying up takes a few milliseconds, so the other recording has (by the look of it) time to get a couple of buffers recorded before you stop it. I may not have tested it, but you can pause recording, which takes minimal time, and should be perfect if wrapped in Audio[No]Interrupts().
 
Back
Top