Yet Another File Player (and recorder)

Hi again @h4yn0nnym0u5e,
in order to use the playWavBuffered dynamically, how would you recommend to add the destructor?
I have seen this in play_sd_wav.h of your dynamic library:
Code:
~AudioPlaySdWav(){SAFE_RELEASE_INPUTS(); SAFE_RELEASE_MANY(2,block_left,block_right);}
any suggestion?
 
I need to take a look at this …

The SAFE_RELEASE macros try to ensure that any input audio blocks transmitted to an object are released as part of its destruction, because otherwise they’d be permanently allocated. However, that doesn’t apply to the playWAV objects because they have no inputs; it’s a different story for the recordWAV objects.

For the playWAV objects, the issue will be that they may have fired an Event to the EventResponder requesting a filesystem read, which is still pending when the object is destroyed. I need to check that the event is cancelled or fails gracefully, and that the open file is closed.

You could just try:
Code:
~AudioPlayWAVbuffered() { stop(); }
but I make no guarantee it’ll work!
 
OK, more goodness pushed to the above repo. I think use of these objects with the Dynamic Audio library is ... let's say, way better. May not be perfect, but definitely worth a try. Should still work happily with the stock audio library.

Here's my test code. Pre-load 6 files, then play up to 4 at once, for times varying from short to insanely short, by creating playback objects, playing them (starting with the pre-loaded data then continuing to the file), and stopping by deleting the playback objects.
Code:
/*
 * Thrash the preload SD card playback using dynamic objects. 
 * ONLY for use with the Dynamic Audio library!
 */
#include <Audio.h>

#define COUNT_OF(x) (sizeof x / sizeof x[0])

// GUItool: begin automatically generated code
AudioMixer4              mixerL;         //xy=527,390
AudioMixer4              mixerR; //xy=531,473
AudioOutputI2S           i2sOut;           //xy=683,434
AudioRecordQueue         sync;         //xy=699,546
AudioConnection          patchCord1(mixerL, 0, i2sOut, 0);
AudioConnection          patchCord2(mixerR, 0, i2sOut, 1);
AudioConnection          patchCord3(mixerR, sync);
AudioControlSGTL5000     sgtl5000;     //xy=688,477
// GUItool: end automatically generated code

/**********************************************************************/
// These files are what they say: all 3s long, mono, 44.1kHz
const char* files[] = {
  "sine110.wav",
  "sine220.wav",
  "sine330.wav",
  "sine440.wav",
  "sine550.wav",
  "sine660.wav"
};

AudioPreload* preloads[COUNT_OF(files)]; 

// Pre-load all the above into fairly short buffers, a different size for each
void doPreload(void)
{
  for (int i=0;i<COUNT_OF(files);i++)
  {
#define PRELOAD_SIZE(x) (1024 + x * 512) // 11.6, 17.4 ... 40.6ms
    preloads[i] = new AudioPreload(files[i],AudioBuffer::inHeap,PRELOAD_SIZE(i));
    Serial.printf("Preload %d (%s -> %s); %d valid; buffer at 0x%08X\n",i,files[i],preloads[i]->filepath,preloads[i]->valid,preloads[i]->buffer);
  }
}

/**********************************************************************/
struct bip {
  AudioPlayWAVstereo* player;
  AudioConnection* l,*r;
  uint32_t stopAt;
};

bip bippers[4];

/*
 * "bip" a file by creating a playback object, connecting it, and starting it playing
 * from the designated preload object. Note we DO permit multiple playback objects
 * to use the SAME preload object, and hence the SAME file on SD. And it works.
 */
void bipStart(int idx,AudioPreload* prld,uint32_t stopAt)
{
  bip* bp = bippers+idx;
  Serial.printf("Start %d at %d with %s for %dms\n",idx,millis(),prld->filepath,stopAt - millis());
  Serial.flush();

  if (nullptr == bp->player)
  {
    bp->player = new AudioPlayWAVstereo;
    bp->l = new AudioConnection;
    bp->r = new AudioConnection;
    bp->l->connect(*bp->player,0,mixerL,idx);
    bp->r->connect(*bp->player,1,mixerR,idx);
    bp->player->createBuffer(4096,AudioBuffer::inHeap);
  }
  
  bp->stopAt = stopAt;
  bp->player->play(*prld);
}

/*
 * Stop the "bip" by brutally deleting the playback object
 * and its connections. You could change the if (1) and
 * just stop the playback, but where's the fun in that?
 */
void bipStop(int idx)
{
  bip* bp = bippers+idx;
  
  Serial.printf("Stop %d at %d\n",idx, millis());
  Serial.flush();
  
  if (1)
  {
    delete bp->player;
    delete bp->l;
    delete bp->r;
  
    *bp = {0,0,0,0};
  }
  else
  {
    bp->player->stop();;
    bp->stopAt = 0;
  }
}


/*
 * Every so often, try to start another random "bip".
 * Also check for any whose time has come, and stop them.
 */
void bipUpdate(void)
{
  int rnum = random(100);
  bool tryStart = rnum < 2; // 2% chance of trying to start something
  
  for (int i=0;i<COUNT_OF(bippers);i++)
  {
    if (bippers[i].stopAt != 0 && millis() > bippers[i].stopAt)
      bipStop(i);

    if (tryStart && 0 == bippers[i].stopAt)
    {
      AudioPreload* prld = preloads[random(COUNT_OF(preloads))]; // random file
      uint32_t stopAt = random(1,15); // 1 to 14 
      stopAt *= stopAt; // 1, 4,  9, ... 196
      stopAt *= 2;      // 2, 8, 18, ... 392
      stopAt += millis(); // random play duration
      bipStart(i, prld, stopAt);
      tryStart = false;
    }
  }
}
/**********************************************************************/

void setup() 
{
  AudioMemory(20);

  while (!Serial)
    ;

  if (CrashReport)
    Serial.println(CrashReport);

  while (!SD.begin(BUILTIN_SDCARD))
  {
    Serial.println("SD fail");
    delay(250);
  }

  doPreload();

  Serial.println("Running");
  
  mixerL.gain(0,0.9f);
  mixerL.gain(1,0.7f);
  mixerL.gain(2,0.5f);
  mixerL.gain(3,0.3f);

  mixerR.gain(0,0.3f);
  mixerR.gain(1,0.5f);
  mixerR.gain(2,0.7f);
  mixerR.gain(3,0.9f);

  sgtl5000.setAddress(HIGH);
  sgtl5000.enable();
  sgtl5000.volume(0.2f);

  sync.begin();  
}

bool syncSet;
void loop() 
{
  // sync to audio engine
  while (sync.available() > 0)
  {
    sync.readBuffer();
    sync.freeBuffer();

    syncSet = true;
  }

  if (syncSet) // stuff to do just after audio update
  {
    static int syncCount = 0;
    syncSet = false;

    syncCount++;
    if (0 == syncCount % 100)
    {
      Serial.printf("Used blocks = %d\n",AudioMemoryUsage());
    }
  }

  bipUpdate();
  delay(1);
}
 
I'm struggling recording audio with teensy. If it's not one thing it's the other. I want to try this library. Do you have stereo record example, tested and works?
 
I put a re-worked example of Recorder.ino into the examples - if you get the whole Audio library from the repo you should have it already in examples/Audio/Buffered/Recorder. You can look over the code directly on GitHub here.

If you have any problems, do post back here and I'll see if I can help.
 
AudioRecordWAVstereo is a new addition to the audio library?
any chance it will go back to the main repo?
 
Last edited:
Sounds garbled, the recording.

This is one of the issues I've been having. The passthrough audio to the i2S output devices sounds OK.


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

   
AudioInputI2S            is2stereoIn;    //xy=381.4285888671875,1194.2857666015625
AudioAmplifier           amp1R;          //xy=592.2857398986816,1331.1428871154785
AudioAmplifier           amp1L;          //xy=595.2857398986816,1046.1428546905518
AudioRecordWAVstereo 			 record;        //xy=962,1193
AudioMixer4              mixer1;         //xy=808.5714912414551,1085.7143383026123
AudioMixer4              mixer2;         //xy=810.2857971191406,1318.285831451416
AudioFilterBiquad        biquad1L;       //xy=960.1428604125977,907.4286336898804
AudioFilterBiquad        biquad1R;       //xy=993.8571090698242,1543.4284753799438
AudioOutputI2S           i2sOut;           //xy=1010,1169.9999446868896
AudioAnalyzePeak         peakL;          //xy=1221.4285888671875,868.2857666015625
AudioOutputUSB           usb2;           //xy=1266.4285888671875,1148.2857666015625
AudioAnalyzePeak         peakR;          //xy=1283.8571891784668,1455.57124710083
AudioPlayWAVstereo           play ;     //xy=630.0000610351562,1197.1428031921387
AudioConnection          patchCord1(is2stereoIn, 0, amp1L, 0);
AudioConnection          patchCord2(is2stereoIn, 1, amp1R, 0);
AudioConnection          patchCord15(amp1L, 0, record, 0);
AudioConnection          patchCord13(amp1L, 0, i2sOut, 0);
AudioConnection          patchCord14(mixer1, 0, usb2, 0);
AudioConnection          patchCord17(amp1R, 0,record,1);
AudioConnection          patchCord16(mixer2, 0, usb2, 1);
AudioConnection          patchCord18(amp1R, 0, i2sOut, 1);
AudioConnection          patchCord19(biquad1L, peakL);
AudioConnection          patchCord20(biquad1R, peakR);


void SDsetup() {
 
	// Initialize the SD card
	SPI.setMOSI(7);
	SPI.setSCK(14);
	if (!(SD.begin(254))) {
		delay(600);
	}
	
}

 
void setup() {
 
	SDsetup();
	AudioMemory(20);
	Serial.begin(9600);
 
	amp1L.gain(1.5);  //
	amp1R.gain(1.5);  //

}

 
int testStarted = 0;
int testCompleted = 0;
void loop() {

	
	digitalWrite(36, HIGH); //needed for activating my i2S device
	digitalWrite(31, HIGH); //needed for activating my i2S device

	if (!testStarted)
	{
		startRecording();
		testStarted = 1;
	}
	if (testStarted && !testCompleted && millis() > 5000)
	{
		stopRecording();
		testCompleted = 1;
	}
	return;
	 

} 

void stopRecording() {
	 
	record.stop();
}
const char* startRecording() {

	short nameIncrement = 0;
	String fileName = "X" + String(nameIncrement) + ".wav";
	if(SD.exists(fileName.c_str())) {
		SD.remove(fileName.c_str());
	}
	record.recordSD(fileName.c_str());
	return fileName.c_str();
}
rename to .WAV:
View attachment X0.wav.txt
 
It's really important to configure a suitably sized buffer for each and every buffered playback or record object. In this instance, in setup() you need something like:
Code:
record.createBuffer(8192,AudioBuffer::inHeap);
The size needed will depend on how good your SD card is, how long other processes may block loop() before it exits, how many objects are running at once, and so on. loop() needs to exit because that results in a call to yield(), which is where the buffering magic really takes place. You can sprinkle your sketch with yield() calls if you prefer. If you have PSRAM fitted, you can create the buffer in that by using AudioBuffer::inExt, which doesn't have too bad a speed impact, and gives loads more buffer space to play with.
 
It's really important to configure a suitably sized buffer for each and every buffered playback or record object. In this instance, in setup() you need something like:
Code:
record.createBuffer(8192,AudioBuffer::inHeap);
Is there an upper limit to the Buffer size? I would like to use a large Buffer, because of the high latency of SD cards for recording.
 
The forked Audio library + the record.createBuffer(8192,AudioBuffer::inHeap);
worked

I been battling this for months lol. Thanks h4yn0nnym0u5e.
 
Is there an upper limit to the Buffer size? I would like to use a large Buffer, because of the high latency of SD cards for recording.
The only limit is the amount of spare memory you have. If your heap is mostly unused on a Teensy 4.x, then you have about 5s of buffer for a mono signal, or 2.5s for stereo. With 8MB of PSRAM that grows to 90s or 45s. Each object has its own buffer which can be in either region, so you could go with a smaller buffer in heap for playback, and a big one in PSRAM for recording.
 
The forked Audio library + the record.createBuffer(8192,AudioBuffer::inHeap);
worked

I been battling this for months lol. Thanks h4yn0nnym0u5e.
Good news! Do come back if you have further questions, I’d like to be as sure as possible that it’s in good shape before it gets into a PR or beta.
 
Hi there! Been trying this library out a bit, but i cant seem to figure out what im doing wrong. When I reprogram the teensy board, it sometimes needs a full power cycle to start, but that doesnt matter. Point is, when I run my code, which is a bit of an abomination at the moment, it will play the very beginning of the audio files (im going with the simultaneous playing currently) but then it'll freeze, like what happens when a computer BSODs while playing audio. This is my code so far, and I know for a fact that it is an abomination but I don't really know how to make it better. I've basically made a mini scrap book of different code bits from threads and libraries of other people's.
Code:
//Play 14 files simultanously
//
//Download the file "Mellotron Violin samples (zip)" from here: https://robertsonics.com/tsunami-downloads/

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

#define COUNT_OF(x) (sizeof x / sizeof x[0])

// GUItool: begin automatically generated code
AudioPlayWAVstereo       playWav1;
AudioPlayWAVstereo       playWav2;
AudioPlayWAVstereo       playWav3;
AudioPlayWAVstereo       playWav4;
AudioPlayWAVstereo       playWav5;
AudioPlayWAVstereo       playWav6;
AudioPlayWAVstereo       playWav7;
AudioMixer4              mixer1;         //xy=512,151
AudioMixer4              mixer2;         //xy=515,226
AudioMixer4              mixer3;         //xy=516,304
AudioMixer4              mixer4;         //xy=516,377
AudioMixer4              mixer5;         //xy=517,448
AudioMixer4              mixer6;         //xy=518,521
AudioMixer4              mixer7;         //xy=519,601
AudioMixer4              mixer8;         //xy=521,682
AudioMixer4              mixer9;         //xy=745,277
AudioMixer4              mixer10;        //xy=756,528
AudioOutputMQS           audioOutput;       //
AudioConnection          patchCord5(playWav6, 0, mixer2, 1);
AudioConnection          patchCord6(playWav6, 1, mixer6, 1);
AudioConnection          patchCord9(playWav5, 0, mixer2, 0);
AudioConnection          patchCord10(playWav5, 1, mixer6, 0);
AudioConnection          patchCord13(playWav7, 0, mixer2, 2);
AudioConnection          patchCord14(playWav7, 1, mixer6, 2);
AudioConnection          patchCord17(playWav3, 0, mixer1, 2);
AudioConnection          patchCord18(playWav3, 1, mixer5, 2);
AudioConnection          patchCord19(playWav4, 0, mixer1, 3);
AudioConnection          patchCord20(playWav4, 1, mixer5, 3);
AudioConnection          patchCord23(playWav1, 0, mixer1, 0);
AudioConnection          patchCord24(playWav1, 1, mixer5, 0);
AudioConnection          patchCord27(playWav2, 0, mixer1, 1);
AudioConnection          patchCord28(playWav2, 1, mixer5, 1);
AudioConnection          patchCord29(mixer1, 0, mixer9, 0);
AudioConnection          patchCord30(mixer2, 0, mixer9, 1);
AudioConnection          patchCord31(mixer3, 0, mixer9, 2);
AudioConnection          patchCord32(mixer4, 0, mixer9, 3);
AudioConnection          patchCord33(mixer5, 0, mixer10, 0);
AudioConnection          patchCord34(mixer6, 0, mixer10, 1);
AudioConnection          patchCord35(mixer7, 0, mixer10, 2);
AudioConnection          patchCord36(mixer8, 0, mixer10, 3);
AudioConnection          patchCord37(mixer9, 0, audioOutput, 0);
AudioConnection          patchCord38(mixer10, 0, audioOutput, 1);
// GUItool: end automatically generated code

const int numFiles = 6;

AudioPlayWAVstereo *player[numFiles] = {
  &playWav1,  &playWav2,  &playWav3, &playWav4,
  &playWav5,  &playWav6,  //&playWav7
};

const char* filename[numFiles] = {
  "AMBPIANO.WAV",
  "BASSSYNTH.WAV",
  "CELLO.WAV",
  "MAINSYNTH.WAV",
  //"ORCHESTRA.WAV",
  "PIANO.WAV",
  "SECRET.WAV",
};


int cnt = 0;

AudioPreload* preloads[COUNT_OF(filename)]; 

void doPreload(void)
{
  for (int i=0;i<numFiles;i++)
  {
    #define PRELOAD_SIZE(x) (1024 + x * 512) // 11.6, 17.4 ... 40.6ms
    preloads[i] = new AudioPreload(filename[i],AudioBuffer::inHeap,PRELOAD_SIZE(i));
  }
}

void setup() {
  Serial.begin(9600);
  AudioMemory(16);
  delay(500);
  if (CrashReport) {
    pinMode(13, OUTPUT);
    digitalWriteFast(13, 1);
    Serial.println(CrashReport);
    CrashReport.clear();
    delay(30000);
  }

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

  // set up mixers
  float gain = 1.0 / numFiles;
  for (int i = 0; i < 4; i++) {
    mixer1.gain(i, gain);
    mixer2.gain(i, gain);
    mixer3.gain(i, gain);
    mixer4.gain(i, gain);
    mixer5.gain(i, gain);
    mixer6.gain(i, gain);
    mixer7.gain(i, gain);
    mixer8.gain(i, gain);
  }

  gain = 1.0 / 4;
  for (int i = 0; i < 4; i++) {
    mixer9.gain(i, gain);
    mixer10.gain(i, gain);
  }
  for (int i = 0; i < numFiles; i++)
  {
    Serial.printf("Start %02d : %s\n", i + 1, filename[i]);
    player[i]->createBuffer(32768,AudioBuffer::inHeap);
  }
  doPreload();
}

void loop() {
  for (int i = 0; i < numFiles; i++)
  {
    player[i]->cueSD(filename[i]);
  }
  for (int i = 0; i < numFiles; i++)
  {
    Serial.printf("Start %02d : %s\n", i + 1, filename[i]);
    player[i]->playSD(filename[i]);
  }
  AudioNoInterrupts();
  for (int i = 0; i < numFiles; i++) {
    player[i]->togglePlayPause();
  }
  AudioInterrupts();
  for (int i = 0; i < numFiles; i++) {
    player[i]->togglePlayPause();
  }
  int playing;
  do {

    playing = 0;
    for (int i = 0; i < numFiles; i++)
    {
      if (player[i]->isPlaying())
        playing++;
    }

  } while (playing > 0);

  Serial.println("All stopped.");

}
 
Ah, yes. That's the other vital thing. (I wonder how many more I forgot to emphasise.) You need yield() to be called so the EventResponder can fill the audio buffers. Try this change, near the end of loop():
Code:
  int playing;
  do {

    playing = 0;
    for (int i = 0; i < numFiles; i++)
    {
      if (player[i]->isPlaying())
        playing++;
    }
    [COLOR="#FF0000"]yield();[/COLOR]

  } while (playing > 0);
The proper way to do this is to allow loop() to exit often - the clue is in the name. There's significant housekeeping between calls to loop(), so it's worth doing, though I'll confess I don't always. But I'm aware of the consequences...
 
it sounds absolutely AMAZING now, thank you so much. I don't here any crazy whines or blips or anything along those lines. My next goal is to make the mixer adjustable, so that I can change the volume of each of the seven tracks! Probably going to be hard, but honestly its SO worth it. Thanks for the help!
 
Continuing conversation that started here, on this more relevant thread. The last question was:
Thanks for the replies!

OK. I've found the repository, https://github.com/h4yn0nnym0u5e/Audio.

I assume this is an updated version of the teensy audio library that comes pre-installed in the TeensyDuino IDE? Updated with the features of WavPlayerEx?

I'm going to try to figure out how to install it...

Thanks!
Yes, that's the correct repository, but I've made many branches fixing various bugs and developing features. Assuming you want the simple guide:
  • the branch you want is called feature/buffered-SD, and can be found at https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD
  • click the green Code button, and select the Download ZIP option
  • inside the zip file there's a folder called Audio-feature-buffered-SD...
  • ... copy it to your* libraries folder, and rename it to just Audio
  • compile any sketch that uses Audio, and check when it says "Multiple Audio libraries found", this one is the one used
  • the version of the Design Tool in this library includes the new playback and record objects, so you can place them and import them as usual
*your libraries folder is the one in the folder where you keep your sketches. If you've not installed any libraries it may not exist - just create it if not.

It is pretty much up to date with the official Teensy Audio library, though I don't check very often to see if Paul has done any changes.

This bears no relation to the WavePlayerEx code, apart from the intent to provide a decent SD card playback ability, and recording (which didn't really work in WavePlayerEx). It was definitely inspired by it, though.
 
Coming back to this after a brief break, my next goal with this is to have live adjustable gain on each of the seven tracks. How easy is that overall?
 
Thanks for putting me on the proper topic! Your instructions to install your library worked perfectly and I ran the "PlayWAVstereo" code you posted earlier in this topic and it compiled and ran perfectly.

I'm "rolling my own" multitrack recorder for a blind friend of mine because *all* the hundreds of existing digital recorders rely on displays and menus. So I'm building a totally tactile control surface and hope that Teensy with your code will be the brains.

Thanks again for all your work on this and your support! (I'm sure further questions will follow...)
 
Sounds very cool, do let us know how you get on.

Questions always welcome, especially when you can post code, error messages and pictures to help us figure out what you’re doing…
 
Back
Top