Yet Another File Player (and recorder)

A couple of (hopefully) quick questions:

There is a recordWAVmono object but I don't see playWAVmono. I've been using the playWAVstereo which works fine but does that use more resources than necessary?

For my multitrack recording project:
Your (h4yn0nnym0u5e 's) recordWAV... and playWAV... objects seem very efficient and work well. I'm just wondering if working with RAW format audio files would be as or more efficient, since I'd be working with multiple files and don't care about the audio format until a final "mix down" of the files I've recorded. At that point I'd have a stereo audio file which hopefully could be saved as .wav (or even mp3 somehow.)

Thanks as always!
 
There's no AudioPlayWAVmono, in the same way that there's only one AudioPlaySdWav - if it's a mono file, both just route the signal to both outputs, which is a minuscule overhead in the case where the second output is unconnected. In fact, all the AudioPlayWAV objects are exactly the same as one another, we only need different names so the Design Tool can show the correct number of outputs.

RAW audio files might have a very small performance gain when you start playback, as there'd be no need to parse the header. But you lose out as soon as you consider multi-channel files, where the programmer suddenly needs to magic up the channel count from somewhere. Plus there's no information on the sampling frequency or sample format, so you could throw a 24/96 stereo file at it and wonder why there's a horrible noise... I don't think it's really worth supporting, personally. But my mind could be changed by a coherent argument as to why I'm wrong!
 
Thanks again for the info!

Yeah, everything's working fine with the playWAVstereo with mono wav files. I'm currently playing 5, 20-second (for now) audio files , starting simultaneously and have these on a loop. It's been running for a couple of hours and I haven't heard a glitch or anything and they're playing totally in sync. Plus I'm not doing anything "fancy" in starting playing the files.

I have 5 static playWAVstereo objects. I'm just creating creating buffers for each in setup():

Code:
 const size_t sz = 65536;
  const AudioBuffer::bufType bufMem1 = AudioBuffer::inHeap;
  playWAVstereo1.createBuffer(sz,bufMem1);

Then starting playback of each file consecutively with a simple
Code:
playWAVstereo1:playSD()

The pause() and play() functions have been working flawlessly as well.

I have no yield() in the main loop.

The SD card is a 64GB SanDisk Ultra
This is on a Teensy 3.6 btw!
 
Great news, sounds like you're giving it a good workout. Slightly curious how you fit 5x 64k buffers on a Teensy 3.6, which only has 256k of RAM... It's probably worth checking the return value from createBuffer() - if it's non-zero then the buffer didn't actually get allocated. Or of course you're only ever giving playWAVstereo1 buffer memory, and the other 4 don't have any, you won't run out of RAM ... but I don't think you'd have a working system at that point.

If you let loop() exit often, you don't need yield(); also, delay() includes a call to yield() internally. It ought to "just work" in an ideal world, but displays often take a long time updating which could cause issues.

The real crunch comes with recording, when SD cards can get a bit laggy and you need chunky buffers to last through writes occasionally taking a long time. You might want a Teensy 4.1 with PSRAM at that point!
 
Yeah, the buffers are a curiosity. I put in print statements to show the return values of createBuffer() but for some reason they are not printing! Serial.print is working everywhere else in the program even in setup() for the SD card error. My setup() function is show below:

Code:
void setup() {
  // Configure the pushbutton pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);

  Serial.begin(9600); // I added this. It didn't change anything

  Serial.println("Starting setup..."); // this doesn't print out
  
  // Audio connections require memory:
  AudioMemory(16);

  // SD audio objects need buffers configuring:
  const size_t sz = 65536;
  const AudioBuffer::bufType bufMem1 = AudioBuffer::inHeap;
  Serial.print("Buf 1 ret val: "); Serial.println(playWAVstereo1.createBuffer(sz,bufMem1)); // non-zero means buffer didn't get created
  recordWAVstereo1.createBuffer(sz,bufMem1);

   const AudioBuffer::bufType bufMem2 = AudioBuffer::inHeap;
  Serial.print("Buf 2 ret val: "); Serial.println(playWAVstereo2.createBuffer(sz,bufMem2));
      
  const AudioBuffer::bufType bufMem3 = AudioBuffer::inHeap;
  Serial.print("Buf 3 ret val: "); Serial.println(playWAVstereo2.createBuffer(sz,bufMem3));
      
  const AudioBuffer::bufType bufMem4 = AudioBuffer::inHeap;
  Serial.print("Buf 4 ret val: "); Serial.println(playWAVstereo2.createBuffer(sz,bufMem4));
      
  const AudioBuffer::bufType bufMem5 = AudioBuffer::inHeap;
  Serial.print("Buf 5 ret val: "); Serial.println(playWAVstereo2.createBuffer(sz,bufMem5));
      
  // (none of the above print statement print )

  // Enable the audio shield, select input, and enable output
  //sgtl5000_1.setAddress(HIGH); // if needed, mostly isn't
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(1);

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

So how do you exit loop()? I thought loop() *was* the program essentially. So you'd have to restart the Teensy to exit loop()?

I haven't added recording yet but I've had 5-4 minute files playing without issue. Working great!

I'll probably get the Teensy 4.1. I'm just trying to see how far I can push the 3.6.
 
Yeah, the buffers are a curiosity. I put in print statements to show the return values of createBuffer() but for some reason they are not printing! Serial.print is working everywhere else in the program even in setup() for the SD card error. My setup() function is show below:
Your PC takes some amount of time after connecting for the serial link to be established. Try delay(1000) after Serial.begin() so your Serial.println's will actually be displayed
 
(I posted this before I saw thebigg's post.) Thanks!

I fixed the printing issue. I added just after Serial.begin(9600):

Code:
while (!Serial);
  delay(500);

Now, indeed, I see that the 1st 2 buffers return 0 but the last 3 return 4. But as I say, everything works fine.

How would I determine my buffer requirement?
 
You haven’t done what you think there. It’s waiting for Serial to start, and then a further 500ms. Doesn’t matter much here, but it’s the sort of thing that drives you bonkers finding it when it does matter!

If your buffer allocation doesn’t work, or you forget, the code allocates 1k of heap, which is presumably just enough in your system, with the other two having big buffers available. For now you’ll have to drop the buffers to 24k each, which should be fine for playback.

I really should add some proper instrumentation to these objects to allow you to look at performance. All there is at the moment is a class variable called lowWater. If you monitor e.g. playWAVstereo1.lowWater, it tells you the lowest level that buffer got to at the time a read was just about to happen. (Not the best choice, it ought to be just after the read, really.) Reads are triggered when the buffer is half empty, so for 24k buffers you’d expect a number under 12k. Slightly under means you’ve loads in hand, way under means you’re marginal.

A more useful scheme would be to measure the worst case file read time. Then you make your buffers a bit bigger than twice that, figuring 12ms/1k for mono files, 6ms/1k for stereo, and so on.

Here’s what happens with loop(), code copied straight from the TeensyDuino source:
Code:
 // Arduino's main() function just calls setup() and loop()....
	setup();
	while (1) {
		loop();
		yield();
	}
All sorts of clever stuff is packed into yield(), so it’s highly recommended to let loop() exit. Fortunately, anyone who doesn’t is likely to use delay(), which also calls yield(), so they end up getting away with it!
 
(I posted this before I saw thebigg's post.) Thanks!

I fixed the printing issue. I added just after Serial.begin(9600):

Code:
while (!Serial){};
  delay(500);

Now, indeed, I see that the 1st 2 buffers return 0 but the last 3 return 4. But as I say, everything works fine.

How would I determine my buffer requirement?

You should really use the code below. while (!Serial) waits for the PC serial connection to assert itself. If it doesn't for some reason your program will loop forever.

Code:
while (!Serial && millis()<5000)
// will wait for up to 5 seconds for [I]Serial[/I] to appear
 
Thanks again for the buffer clarification. If I run into issues I'll check how low the water gets...!

Regarding exiting loop(), are you suggesting that it's good practice to put a "break" statement in loop()?
 
That's not a bad suggestion from@BriComp, but please make it
Code:
while (!Serial && millis()<5000) // will wait for up to 5 seconds for Serial to appear
      ; // this semi-colon is crucial!
It's really up to you whether you put in a timeout, and how long it is, but my word how many times have we seen posts where "it won't start except on the PC"! And I still get caught out myself ... so ... yeah.

In other news I've just pushed a new commit with buffer size and filesystem read time instrumentation. If you look at the Audio/Examples/Buffered/*_i folders you'll find a couple of examples on how it's used:
  • <object>.bufferAvail instruments the available buffer space
  • <object>.readMicros instruments the filesystem read or write time, in microseconds; yes, it is readMicros for a record object, even though it's writing - I might fix that
  • <instrument.reset() resets the readings
  • <instrument>.getLowest() gets the lowest value observed (since the last reset)
  • <instrument>.getHighest() gets the highest value observed
  • <instrument>.getLast() gets the last value observed
  • <instrument>.getUpdates() gets the number of values recorded: if zero, the other values are probably meaningless
So playWAVstereo1.readMicros.getHighest() tells you the worst (highest) read time from your card, in microseconds, since you last called playWAVstereo1.readMicros.reset(). For my setup it looks like worst case is about 63ms using a 96k buffer to record a 6-channel WAV file, at the same time as a 4-channel and 8-channel one. That's the RecSynthMusicV2_i demo. I'm not 100% sure I've got the buffer space code correct, it should have been more marginal than the log showed on that one...
 
Thanks again for the buffer clarification. If I run into issues I'll check how low the water gets...!

Regarding exiting loop(), are you suggesting that it's good practice to put a "break" statement in loop()?
A lot of people put in a return statement, as loop() is a function. I personally hate an "early return", they can make it extremely hard to debug your code. But it's up to you...
 
Thanks for those updates! I'm curious to see the file access stats.

How's the best way to update your library? I think I can just copy the files on your github that have changed. Or is it better to download the latest zip and overwrite the existing libraries/Audio folder?
 
It's probably much easier and less error-prone to download the .zip again and overwrite. Before you do, you could move the existing libraries/Audio folder to unused-libraries/Audio; Arduino will then ignore it, and you have a "safety copy" you can bring back if it all goes sideways. Or just keep the .zip you already have somewhere safe.
 
Regarding my multitrack recorder project, (and if this should be a new topic, please let me know), as I've said with your new library I'm able to playback a sufficient number of tracks simultaneously without issue. Thanks!!

Now I'm testing a basic overdubbing situation where I. need to record, just a single file, while simultaneously playing back one or more pre-recorded files. In my initial test with just one playback file and one record file, I start the recording and playback at the same time. It begins working as expected but playback of the "backing track" will usually at some random point stop. The hopefully pertinent bits of code are shown below:

In setup():

Code:
  // Audio connections require memory:
  AudioMemory(20);

  // SD audio objects need buffers configuring:
  const size_t sz = 65536;
  const AudioBuffer::bufType bufMem = AudioBuffer::inHeap;
  playWAVstereo1.createBuffer(sz,bufMem);
  recordWAVstereo1.createBuffer(sz,bufMem);

  const AudioBuffer::bufType bufMem2 = AudioBuffer::inHeap;
  playWAVstereo2.createBuffer(sz,bufMem2);

  // Enable the audio shield, select input, and enable output
  control.enable();  // do these last so no speaker thump!
  control.inputSelect(myInput);
  control.lineInLevel(5);
  control.volume(1);

Then starting record with:

Code:
void startRecording() 
{
  Serial.println("startRecording");
  playWAVstereo2.playSD(backingFile);
  recordWAVstereo1.recordSD(fileName);
  
  mode = 1;
  
}
And your instrumentation output:

Code:
Record Button Press
startRecording
PlayWAV2: low-water: 0; worst read time: 5009us; updates: 2
RecWAV1: low-water: 4294967295; worst read time: 0us; updates: 0
PlayWAV2: low-water: 31700; worst read time: 6008us; updates: 2
RecWAV1: low-water: 33836; worst read time: 148721us; updates: 2
PlayWAV2: low-water: 32212; worst read time: 3474us; updates: 1
RecWAV1: low-water: 33836; worst read time: 2347us; updates: 1
PlayWAV2: low-water: 32212; worst read time: 3450us; updates: 1
RecWAV1: low-water: 33836; worst read time: 2355us; updates: 1
PlayWAV2: low-water: 32212; worst read time: 3524us; updates: 2
PlayWAV2: low-water: 32212; worst read time: 3550us; updates: 2
RecWAV1: low-water: 33324; worst read time: 5040us; updates: 1
PlayWAV2: low-water: 32212; worst read time: 3525us; updates: 1
RecWAV1: low-water: 32812; worst read time: 2460us; updates: 1
PlayWAV2: low-water: 32212; worst read time: 3522us; updates: 1
RecWAV1: low-water: 32812; worst read time: 2462us; updates: 2
PlayWAV2: low-water: 32212; worst read time: 3736us; updates: 2
RecWAV1: low-water: 33324; worst read time: 3161us; updates: 1
RecWAV1: low-water: 65068; worst read time: 270978us; updates: 1 <--------- Note long read time. Then PlayWAV2 stops playing
RecWAV1: low-water: 32812; worst read time: 9789us; updates: 2

So this gets me wondering if it's even theoretically possible to play (read) and record (write) at the same time to the same SD card. Do you think there's an approach that would make this possible? I am still using a Teensy 3.6. Maybe a 4.1 would work better? With PSRAM even better? I don't know if this is a memory issue or a (lack of) coordinated read/write issue.

Thanks as always in advance...!
 
Yes, this absolutely should be workable, but maybe not on your Teensy 3.6. I took the decision to stop playback / recording if there's a buffer underrun / overrun, as its really obvious, whereas stuttering isn't. With a stereo file, 64k corresponds to about 372ms of total buffer size (64k / 2 bytes / 2 samples / 44100 samples/s), so reads or writes will be taking place roughly every 186ms. 270ms is too long, so it's failed. Plus, I think we concluded 3x 64k buffers is too much to ask of a Teensy 3.6: I suspect the playWAVstereo2 won't have a decent buffer.

You could try with mono files for now, and use 32k buffers for each of the playback objects - keep record at 64k. That should fit in RAM. Note that the AudioRecordWAVmono class does exist!

I added an example in Examples/Audio/Buffered/RecSynthMusicV2 - it should appear on your Arduino examples menu, or just look for it in the examples folder of the extracted library. It records 3 files at once, with 4, 6 and 8 tracks in each, using N*64k buffers to record, then N*32k when it plays them back. So for stereo recording I'd have used a 128k buffer to record, which I think would work on your SD card. Recording is undoubtedly more challenging for the card, as is multi-file access, but there's no fundamental reason this shouldn't work. The library takes care of co-ordination, but there's no getting round the fact you can really only read/write one file at a time - the others need enough buffer to wait their turn.

The real key is plenty of fairly fast memory, like PSRAM - unfortunately, that's only possible on a Teensy 4.1.
 
Last edited:
Thanks again very much for the quick and informative reply! Since you say it's fundamentally doable then I'm excited! I will definitely order a Teensy 4.1 with both PSRAM chips just so at least the hardware won't be the issue... In the meantime I'll lower the buffer sizes as you suggest and tinker with that.

Thanks and I'll report back...
 
I switched to using AudioRecordWAVmono while simultaneously playing back a stereo WAV file and it works much better. The buffers seem to hold up while recording.

However, the recorded mono file does not play back. (I'm playing it back via a separate AudioRecordWAVstereo object.) I noticed when I look at the files on the SD card in Mac Finder that the stereo files register as wav files and play but Finder does not recognize the type of file that the mono file is.

Any ideas?
Thanks!

PS: I ordered a Teensy 4.1 with 16MB PSRAM...
 
I switched to using AudioRecordWAVmono while simultaneously playing back a stereo WAV file and it works much better. The buffers seem to hold up while recording.

However, the recorded mono file does not play back. (I'm playing it back via a separate AudioRecordWAVstereo object.) I noticed when I look at the files on the SD card in Mac Finder that the stereo files register as wav files and play but Finder does not recognize the type of file that the mono file is.

Any ideas?
Thanks!

PS: I ordered a Teensy 4.1 with 16MB PSRAM...
Nothing obvious - can you post a short mono WAV file that you've recorded but can't play? You'll have to (a) put the WAV in a ZIP file, because this forum doesn't allow you to attach a WAV directly, (b) click the Go Advanced button, and (c) scroll down to the Manage Attachments button and follow that rather convoluted dialogue...

Unless the problem is you're really trying to play using AudioRecordWAVstereo, rather than AudioPlayWAVstereo ... in which case, I think I can see what's going wrong!
 
As a test, I recorded a file and before playing it back with my code, transferred it to my Mac so I could evaluate it. And although Mac doesn't list any wav file info--duration, sample rate, bits/sample, etc. like it does for other wav files, this file *does* play and sounds fine.

I'm going to test again after my code plays it back. It seems the problem lies in there somewhere. If I still can't find the issue, I'll post the wav file as you suggested.

I didn't want you to have to slog through all my code but I posted it just so you have the full context...


Code:
// Record sound as a stereo WAV file data to an SD card, and play it back.
// (Note: modified by D.L. June, 2023 to record a sound file while simultaneously playing back a sound file.)
//
// Requires the audio shield:
//   http://www.pjrc.com/store/teensy3_audio.html
//
// Three pushbuttons need to be connected:
//   Record Button: pin 0 to GND
//   Stop Button:   pin 1 to GND
//   Play Button:   pin 2 to GND
//
// This example code is in the public domain.

#include <Bounce.h>
#include <Audio.h>


// GUItool: begin automatically generated code
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2sIn;          //xy=304,319
AudioMixer4              mixer3;         //xy=585,250
AudioAnalyzePeak         peakR;          //xy=606,161
AudioAnalyzePeak         peakL;          //xy=613,115
AudioPlayWAVstereo       playWAVstereo2; //xy=626,491
AudioPlayWAVstereo       playWAVstereo1; //xy=629,422
AudioRecordWAVmono       recordWAVmono1; //xy=772,214
AudioMixer4              mixer1;         //xy=869,310
AudioMixer4              mixer2;         //xy=873,408
AudioOutputI2S           i2sOut;         //xy=1029,353

AudioConnection          patchCord1(i2sIn, 0, peakL, 0);
AudioConnection          patchCord2(i2sIn, 0, mixer1, 0);
AudioConnection          patchCord3(i2sIn, 0, mixer3, 0);
AudioConnection          patchCord4(i2sIn, 1, peakR, 0);
AudioConnection          patchCord5(i2sIn, 1, mixer2, 0);
AudioConnection          patchCord6(i2sIn, 1, mixer3, 1);
AudioConnection          patchCord7(mixer3, recordWAVmono1);
AudioConnection          patchCord8(playWAVstereo2, 0, mixer1, 2);
AudioConnection          patchCord9(playWAVstereo2, 1, mixer2, 2);
AudioConnection          patchCord10(playWAVstereo1, 0, mixer1, 1);
AudioConnection          patchCord11(playWAVstereo1, 1, mixer2, 1);
AudioConnection          patchCord12(mixer1, 0, i2sOut, 0);
AudioConnection          patchCord13(mixer2, 0, i2sOut, 1);

AudioControlSGTL5000     control;        //xy=406,489
// 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);
Bounce buttonFileInfo = Bounce(3, 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
//*/

// Use these with the Teensy 3.5, 3.6 and 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
//*/

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

void findSGTL5000(AudioControlSGTL5000& sgtl5000_1)
{
  // search for SGTL5000 at both I²C addresses
  for (int i=0;i<2;i++)
  {
    uint8_t levels[] {LOW,HIGH};
    
    sgtl5000_1.setAddress(levels[i]);
    sgtl5000_1.enable();
    if (sgtl5000_1.volume(0.2))
    {
      Serial.printf("SGTL5000 found at %s address\n",i?"HIGH":"LOW");
      break;
    }
  }
}

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

const char* recFile = "newtrack.wav";
const char* backingFile = "EchosMX.wav";

float volume;
boolean monitorOn = false;

void setup() {
  // Configure the pushbutton pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);

  Serial.begin(9600);
  delay(500);
  
  // Audio connections require memory:
  AudioMemory(20); // started at 16 I think. Then 20, 

  // SD audio objects need buffers configuring:
  
  AudioBuffer::bufType bufMem = AudioBuffer::inHeap;
  size_t sz = 4096;
  Serial.print("Play Buf1 ret val: ");
  Serial.println(playWAVstereo1.createBuffer(8*sz,bufMem));
  
  Serial.print("Rec Buf ret val: ");
  Serial.println(recordWAVmono1.createBuffer(16*sz,bufMem));

  Serial.print("Play Buf2 ret val: ");
  Serial.println(playWAVstereo2.createBuffer(8*sz,bufMem));

  // setup mixer gains
  mixer1.gain(0,0.3); // input (mic/line) channes
  mixer1.gain(1,0.3);
  mixer1.gain(2,.5);  // playback channels
  mixer2.gain(0,0.3); // input (mic/line) channes
  mixer2.gain(1,0.3);
  mixer2.gain(2,.5);  // playback channels

  findSGTL5000(control);
  
  // Enable the audio shield, select input, and enable output
  
  control.inputSelect(myInput);
  control.lineInLevel(5);
  control.volume(0);
  control.enable();  // do these last so no speaker thump!
  
  // 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);
  }
}


void loop() {

  if(!monitorOn)
  {
      monitorOn = true;
      control.volume(1); // do it here so no thump
  }
  // First, read the buttons
  buttonRecord.update();
  buttonStop.update();
  buttonPlay.update();
  buttonFileInfo.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();
  }

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

   // Volume control
  //  uncomment if you have a volume pot soldered to your audio shield
  /*
  int n = analogRead(A0);
  if (n != volume) {
    volume = n;
    control.volume((float)n / 1023);
  }
  */

  // show buffer read/write data
  static uint32_t next = 0;
  if (millis() >= next)
  {
    next = millis() + 250;

     if (playWAVstereo1.isPlaying())
      printPlayInstrument(playWAVstereo1,"PlayWAV1");
      
    if (playWAVstereo2.isPlaying())
      printPlayInstrument(playWAVstereo2,"PlayWAV2");
    
    if(recordWAVmono1.isRecording())
      printRecInstrument(recordWAVmono1, "RecWAV1");
  }

  // list file info on button press
  /*if(buttonFileInfo.fallingEdge()) {
      
  }*/
  
  yield();
} // end loop()


void startRecording() 
{
 // uint32_t start1, start2;
  Serial.println("startRecording");
  //start1 = millis();
  playWAVstereo2.playSD(backingFile);
  //start2 = millis();
  recordWAVmono1.recordSD(recFile);

  //Serial.print("time diff: "); Serial.println(start2-start1);
  
  mode = 1;
  
}

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

void stopRecording() 
{
  Serial.println("stopRecording");
  recordWAVmono1.stop();  
  playWAVstereo2.stop();
  mode = 0;
  // mute input
  
}


void startPlaying() 
{
  Serial.println("startPlaying");
  playWAVstereo1.cueSD(recFile);
  playWAVstereo2.cueSD(backingFile);
  playWAVstereo1.play();
  playWAVstereo2.play();
  mode = 2;
}

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

   if(!playWAVstereo2.isPlaying())
   {
      Serial.println("playWAV2 - endOfPlayback");
      mode = 0;
   }
  
}

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

void adjustMicLevel() 
{
  // TODO: read the peak1 object and adjust control.micGain()
  // if anyone gets this working, please submit a github pull request :-)
}

void printPlayInstrument(AudioPlayWAVstereo& o, const char* nam)
{
  Serial.printf("%s: low-water: %u; worst read time: %uus; updates: %u\n",
                nam,
                o.bufferAvail.getLowest(), 
                o.readMicros.getHighest(), 
                o.bufferAvail.getUpdates());
  o.bufferAvail.reset(); 
  o.readMicros.reset(); 
}

void printRecInstrument(AudioRecordWAVmono& o, const char* nam)
{
  Serial.printf("%s: low-water: %u; worst read time: %uus; updates: %u\n",
                nam,
                o.bufferAvail.getLowest(), 
                o.readMicros.getHighest(), 
                o.bufferAvail.getUpdates());
  o.bufferAvail.reset(); 
  o.readMicros.reset(); 
}

void showLevels()
{
  static uint32_t nextOutput;

  if (millis() > nextOutput)
  {
    int lp = 0, rp = 0, scale = 20;
    char cl='?',cr='?';
    char buf[scale*2+10];
    
    nextOutput = millis() + 1000;

    if (peakL.available())
    {
      lp = peakL.read() * scale; 
      cl = '#'; 
    }
    if (peakR.available())
    {
      rp = peakR.read() * scale;  
      cr = '#'; 
    }

    for (int i=0;i<scale;i++)
    {
      buf[scale-i-1] = lp>=i?cl:' ';
      buf[scale+i+1] = rp>=i?cr:' ';
      buf[scale] = '|';
      buf[scale*2+2] = 0;
    }
    Serial.println(buf);
  }
}
 
Can't see anything massively wrong with your code, and my testing suggests I can play back a mono file I just recorded on both the Teensy and after downloading to my PC. So, a bit mysterious...

I've just pushed up a few changes which correct some shortcomings in the instrumentation (like reporting a playback low-water value of zero because the buffer is empty when you start ... doh!); the RecSynthMusicV2_i.ino also got changes so it's reporting playback low-water and recording high-water values.
 
Yeah, the "mono" problem is my issue. Something in my code. It's strange. If I record and then stop. The recorded file will be fine. (Testing file on Mac) But if after I stop recording I initiate play, both the recorded file and backing track immediately report EOF. If then I remove power and restart Teensy, the backing track will play fine but the recorded file will just report EOF and external testing will show the file is corrupt.

But that's my coding issue so I'll track it down.

And in case anyone is looking at the code, my "anti-thump" mechanisms *don't* work... I may have to solve that in hardware.

Thanks for the continued updates to the library!
 
Gave it another try, because I'd put a 250ms delay between recording and playback, but still can't reproduce your issue. It might just barely be worth trying a call to yield() (or two .. or three...) after you stop() things, to allow housekeeping events to trigger. I don't think they're needed, but I could be wrong.

Don't think you posted code with "anti-thump" mechanisms, that I can see. I'd be putting in AudioEffectFade objects "as needed", probably just in front of the record objects' inputs. Should work, I think. No need on the playback, because all your recordings are already thump-free.

EDIT: sigh. AudioEffectFade is yet another of those objects that fails to do its job if it's not being fed a signal - NULL blocks signifying silence won't do it, it sticks at its current fade level until it gets incoming audio.
 
Last edited:
Thanks for sticking this out with me! I actually got things to work. That is, the code below is able to "overdub" a mono file over a stereo "backing track". On a Teensy 3.6! It's really just a slight modification of your "recorder" example in the Buffered folder. I just changed the record object to mono and added another playWAVstereo object.

Code:
// Record sound as a stereo WAV file data to an SD card, and play it back.
// Modified by D.L. June, 2023
//
// Requires the audio shield:
//   http://www.pjrc.com/store/teensy3_audio.html
//
// Three pushbuttons need to be connected:
//   Record Button: pin 0 to GND
//   Stop Button:   pin 1 to GND
//   Play Button:   pin 2 to GND
//
// This example code is in the public domain.

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

// GUItool: begin automatically generated code
AudioPlayWAVstereo       playWAVstereo1; //xy=440,335
AudioPlayWAVstereo       playWAVstereo2; //xy=448,386
AudioInputI2S            i2sIn;          //xy=463,278
AudioAnalyzePeak         peakL;          //xy=535,140
AudioAnalyzePeak         peakR;          //xy=613,183
AudioMixer4              mixer1;         //xy=762,226
AudioMixer4              mixer2;         //xy=856,342
AudioMixer4              mixer3;         //xy=874,437
AudioRecordWAVmono       recordWAVmono1; //xy=949,224
AudioOutputI2S           i2sOut;         //xy=1039,362

AudioConnection          patchCord1(playWAVstereo1, 0, mixer2, 1);
AudioConnection          patchCord2(playWAVstereo1, 1, mixer3, 1);
AudioConnection          patchCord3(playWAVstereo2, 0, mixer2, 2);
AudioConnection          patchCord4(playWAVstereo2, 1, mixer3, 2);
AudioConnection          patchCord5(i2sIn, 0, peakL, 0);
AudioConnection          patchCord6(i2sIn, 0, mixer1, 0);
AudioConnection          patchCord7(i2sIn, 0, mixer2, 0);
AudioConnection          patchCord8(i2sIn, 1, peakR, 0);
AudioConnection          patchCord9(i2sIn, 1, mixer1, 1);
AudioConnection          patchCord10(i2sIn, 1, mixer3, 0);
AudioConnection          patchCord11(mixer1, recordWAVmono1);
AudioConnection          patchCord12(mixer2, 0, i2sOut, 0);
AudioConnection          patchCord13(mixer3, 0, i2sOut, 1);

AudioControlSGTL5000     control;        //xy=447,463
// 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);


// 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
//*/

// Use these with the Teensy 3.5, 3.6 and 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
//*/

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

void findSGTL5000(AudioControlSGTL5000& sgtl5000_1)
{
  // search for SGTL5000 at both I²C addresses
  for (int i=0;i<2;i++)
  {
    uint8_t levels[] {LOW,HIGH};
    
    sgtl5000_1.setAddress(levels[i]);
    sgtl5000_1.enable();
    if (sgtl5000_1.volume(0.2))
    {
      Serial.printf("SGTL5000 found at %s address\n",i?"HIGH":"LOW");
      break;
    }
  }
}

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

//const char* fileName = "Stereo2.wav";
const char* recFile = "newtrack.wav";
const char* backingTrack = "EchosMX.wav";

void setup() {
  // Configure the pushbutton pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);

  // Audio connections require memory:
  AudioMemory(10);

  // SD audio objects need buffers configuring:
  const size_t sz = 65536;
  const AudioBuffer::bufType bufMem = AudioBuffer::inHeap;
  if(playWAVstereo1.createBuffer(sz/2,bufMem)) Serial.println("Play1 Buffer failed");
  if(playWAVstereo2.createBuffer(sz/2, bufMem)) Serial.println("Play2 Buffer failed");
  if(recordWAVmono1.createBuffer(sz,bufMem)) Serial.println("Record1 Buffer failed");

  // Enable the audio shield, select input, and enable output
  findSGTL5000(control);
  control.inputSelect(myInput);
  control.lineInLevel(2);
  control.volume(0.5);
  
  // 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);
  }
}

int prevMode = mode;
boolean 1isPlaying = false;
boolean 2isPlaying = false;

void loop() {
  // First, read the buttons
  buttonRecord.update();
  buttonStop.update();
  buttonPlay.update();

  if(prevMode != mode)
  {
      Serial.print("Mode: "); 
      Serial.println(mode);
      prevMode = mode;
  }
  // 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();
  }

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


void startRecording() 
{
  Serial.println("startRecording");
  playWAVstereo2.playSD(backingTrack);
  recordWAVmono1.recordSD(recFile);
  mode = 1;
}

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

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


void startPlaying() 
{
  Serial.println("startPlaying");
  playWAVstereo1.playSD(recFile);
  playWAVstereo2.playSD(backingTrack);
  mode = 2;
}

void continuePlaying() 
{
  int n=0;
  if (!playWAVstereo1.isPlaying()) 
  {
    Serial.println(" PlayWAV1 endOfPlayback");
    n++;
  } 

   if (!playWAVstereo2.isPlaying()) 
  {
    Serial.println(" PlayWAV2 endOfPlayback");
    n++;
  }

  if(n>=2)
    mode = 0;
}

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

void adjustMicLevel() 
{
  // TODO: read the peak1 object and adjust control.micGain()
  // if anyone gets this working, please submit a github pull request :-)
}

void showLevels()
{
  static uint32_t nextOutput;

  if (millis() > nextOutput)
  {
    int lp = 0, rp = 0, scale = 20;
    char cl='?',cr='?';
    char buf[scale*2+10];
    
    nextOutput = millis() + 1000;

    if (peakL.available())
    {
      lp = peakL.read() * scale; 
      cl = '#'; 
    }
    if (peakR.available())
    {
      rp = peakR.read() * scale;  
      cr = '#'; 
    }

    for (int i=0;i<scale;i++)
    {
      buf[scale-i-1] = lp>=i?cl:' ';
      buf[scale+i+1] = rp>=i?cr:' ';
      buf[scale] = '|';
      buf[scale*2+2] = 0;
    }
    Serial.println(buf);
  }
}

As for the "thump", I get that when the sgtl5000 object is enabled. It doesn't happen on file recording or playback.

Next steps in my digital multitrack recorder project are adding more playback (backing track) files for overdubbing, keeping track of recorded files for a "song" and bouncing files together, mainly file manipulation.

My Teensy 4.1 should arrive tomorrow so...I'm excited!
 
Back
Top