Yet Another File Player (and recorder)

h4yn0nnym0u5e

Well-known member
Hi folks

I've been working on an improved (I think) set of objects for playing and recording WAV files from filesystems, principally SD card. You can find the preliminary effort at https://github.com/h4yn0nnym0u5e/Audio/commits/feature/buffered-SD.

The main design goals were:
  • play / record multiple files simultaneously, without glitching
  • support for multi-channel files (1, 2, 4, 6 and 8 channels)
  • removing filesystem accesses from the audio interrupt
My own testing so far has been minimal - I've got as far as successfully playing back 16 mono files while mixing them and recording to 2 stereo files, using a reasonably fresh SanDisk Extreme 64Gb SDXC card, and still having some CPU time left to do other stuff with. Clearly more testing is needed, it's highly likely that there are bugs, I need to do some demos and more documentation, and there may well be features that it would be useful to add. It would be great if those of you who have a use for such a beast would give it a go and report back on how well (or badly...) it went, to help me decide how best to spend my time on this ... or, indeed, whether I should just shelve the whole thing because no-one needs it!

The objects work pretty much as you might expect: I've updated the Audio Design Tool to allow you to add them to your sketches, and they include the usual info in the right-hand pane on the various functions they provide.

Technical stuff
The absolutely key function, which is dissimilar to the ancestral AudioPlaySdWav object, is createBuffer(). If you don't call this for every object then you'll almost certainly get poor results! It allocates buffer memory to allow filesystem reads to be a sensible size. This not only reduces the read frequency (and hence CPU overhead), but also means that if there is the occasional delay there is some chance the buffers will have enough data that the audio won't glitch. The buffer can be allocated either in heap or in PSRAM; the latter doesn't have as much impact as you might think, because transfers between SD and PSRAM seem to be about as fast as between SD and fast RAM. For the above test I allocated 64k for each playback buffer, and 128k for each record buffer, using a total of 1.25MB of the 8MB PSRAM.

The other thing to be aware of is that filesystem transfers happen outside the audio interrupt updates, within the context of your application: this is done using the EventResponder library, which means that your application must call yield() reliably often. You can do this by allowing loop() to exit, or by explicitly calling yield() in your application, or by using delay() (if you must...). There may be other library calls which yield(), but I wouldn't want to rely on them myself. "Reliably often" is hard to quantify, but the bigger your buffers the less often you need to do it. Buffer refills trigger when they're half empty, so a 64k buffer will be refilled when it gets below 32k; if you're using it for a mono WAV file that will be every 371ms; with 16 files playing, a refill will be needed on average every 23ms. I have made some effort to ensure buffer refills are staggered in time, so there's a fighting chance your application won't stutter because 16 reads taking 5ms each have all triggered at once.

For playback the buffers are pre-loaded when the playSD() function is called; because this can take some time, if synchronous starting of multiple files is important, you may wish to use cueSD() to prepare them all, followed by play() to set them going.

Roadmap
I'd really like to get feedback on:
  • bugs and misfeatures
  • simple demo sketches of cool stuff
  • missing features
  • bits you don't understand

Things I'm vaguely considering include:
  • demos and documentation
  • adding hooks to allow use of file formats other than 44kHz 16-bit PCM
  • instrumentation so we can e.g. check performance, buffer margins etc.
  • looping / starting playback in the middle of a file
 
Very interesting!

We are really in need for a more reliable way to record audio with the Teensy Audio library. At the moment, this is not possible without frequently occuring glitches and/or sample losses in the recordings.

I would like to test your modifications for audio stereo recording. Is there an example sketch for this ? If I understood correctly, there are numerous things to add to the standard recording example for your library to work (buffers, yield etc.). My target hardware would be T4.1 without PSRAM and the audio board or another I2S codec / ADC.
Would be nice if you could provide a little more detail on how I could put together a test sketch for simple audio stereo recording via I2S.
 
Very interesting!

We are really in need for a more reliable way to record audio with the Teensy Audio library. At the moment, this is not possible without frequently occuring glitches and/or sample losses in the recordings.

I would like to test your modifications for audio stereo recording. Is there an example sketch for this ? If I understood correctly, there are numerous things to add to the standard recording example for your library to work (buffers, yield etc.). My target hardware would be T4.1 without PSRAM and the audio board or another I2S codec / ADC.
Would be nice if you could provide a little more detail on how I could put together a test sketch for simple audio stereo recording via I2S.

There is an example now - here's a sketch adapted straight from the "Recorder" example:
Code:
// Record sound as a stereo WAV file data to an SD card, and play it back.
//
// 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
AudioPlayWAVstereo       playWAVstereo1; //xy=107,155
AudioInputI2S            i2sIn;           //xy=130,98
AudioAnalyzePeak         peak1;          //xy=272,31
AudioOutputI2S           i2sOut;           //xy=287,155
AudioRecordWAVstereo     recordWAVstereo1; //xy=321,98
AudioConnection          patchCord1(playWAVstereo1, 0, i2sOut, 0);
AudioConnection          patchCord2(playWAVstereo1, 1, i2sOut, 1);
AudioConnection          patchCord3(i2sIn, 0, peak1, 0);
AudioConnection          patchCord4(i2sIn, 0, recordWAVstereo1, 0);
AudioConnection          patchCord5(i2sIn, 1, recordWAVstereo1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=262,210
// 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
//*/

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

const char* fileName = "Stereo.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;
  playWAVstereo1.createBuffer(sz,bufMem);
  recordWAVstereo1.createBuffer(sz,bufMem);

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


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 == 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();
}


void startRecording() 
{
  Serial.println("startRecording");
  recordWAVstereo1.recordSD(fileName);
  mode = 1;
}

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

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


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

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

void stopPlaying() {
  Serial.println("stopPlaying");
  playWAVstereo1.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 :-)
}
I've only tested it briefly, but I think it works OK on a Teensy 4.1 + audio adaptor + SanDisk Extreme 64Gb SDXC card. Let me know how it goes for you...
 
Thanks a lot! Looks good, will test in the next days.

Do I have to insert a
Code:
yield();
in
Code:
loop
to make it work?
 
No, you shouldn't have to add anything to the above code for it to work. If you look at main.cpp in cores you find:
Code:
	// Arduino's main() function just calls setup() and loop()....
	setup();
	while (1) {
		loop();
		yield();
	}
which is why I say either call yield() or let loop() return. delay() also calls yield() within it - I avoid delay() in production code, For It Is An Abomination, in my opinion!

The only reason I emphasise it is that EventResponder is triggered from yield() (the way I'm using it), and I've seen many sketches which have fairly tight while loops inside loop(), and if you do that there's no way for EventResponder to work its magic.
 
Thanks for your help! WORKING
* I have the sketch working now on a Teensy 4.0 with PCM5102 and PCM1808 and SD card holder soldered with flying wires to the 4 bit SDIO port (Stereo WAV recording and Stereo WAV playback)
* SD card is a Sandisk Ultra 16GB A1 HC I
* already in the first test recording there was a short audio glitch after about a minute of recording --> indication of a too small buffer for this SD cards´ latency???
* but I will investigate this further and
* will report back here!

EDIT: I am also a little irritated where my buffers are located ? I was looking for two buffers, each 128kB big... (size_t is 16bits?)
What is "padding" ? And what is "inHeap", it does not correspond to any of the categories below and here: https://www.pjrc.com/store/teensy40.html.
I was thinking that the buffer would be found in RAM2 DMAMEM . . .

I think I do not understand the Memory usage nomenclature...because it has no "DMAMEM" indicated and I do not know what "padding" is

Code:
Memory Usage on Teensy 4.0:
  FLASH: code:74460, data:9352, headers:8344   free for files:1939460
   RAM1: variables:12000, code:71880, padding:26424   free for local variables:413984
   RAM2: variables:16032  free for malloc/new:508256
 
Last edited:
Great that you've made at least some progress. I suspect I need to stop being lazy and do a version of the library which can monitor the read/write times and delays, so we can pinpoint where issues are and try to correct them.

If you've not already done it, you might like to try running Bill Greiman's SD card test code from https://forum.pjrc.com/threads/68418-Which-SD-is-Best-for-Audio-Projects. Stereo recording only needs 176KB/s, and the 64k buffer I put in the example should allow for up to 180ms latency, so it's not immediately obvious why a glitch has occurred. Having said that, I have a card with >1400ms write latency - it's just I wouldn't expect it of your SanDisk Ultra. I think I've got one the same as yours so I'll give it a try, hopefully tonight.

You won't see the buffers in the memory map, because they're allocated at run time by the calls to createBuffer(); using AudioBuffer::inHeap tells the system to put them in the "free for malloc/new:508256" area, which is also DMAMEM. The AudioBuffer:: part tells the compiler that inHeap is defined as part of my library additions, you won't find it documented anywhere apart from here and in the GUI info pane. Early days...

The buffers are 64k each, not 128k: the type size_t just says "a variable with enough bits to index every byte in the system's address space individually" - it's probably a uint32_t "under the hood", for a Teensy 4.1, it would need to be a uint64_t for a system with more than 4Gb of address space. You could bump the buffer size used by changing the value assigned to sz from 65536 to 131072, or by changing just the record buffer, e.g. recordWAVstereo1.createBuffer(131072,bufMem);

"padding" is some weird NXP thing (to ensure the code memory is protected against writing by the program - I think). 71880+26424=3*32768, and the protection has to be in 32k units, so effectively there's 26424 bytes of unusable memory in RAM1, for this program. Looked at another way, the program could grow by up to that much before you end up with less RAM usable for variables.
 
Excellent, thanks for the explanation, that clears many things up for me!
When I find some more time, I will try to make the record buffer larger.
If I remember correctly, I tested the SD cards a while ago and many of them (of the same brand SanDisk Ultra A1 HC I) had latencies of 300-400ms quite often.
Will report here, when I have more experience with your lib!
 
Thanks, it's great to have some feedback.

I did a 4m30s recording on my SanDisk Ultra 16Gb and it seemed to be fine, apart from some noise which I put down to very poor wiring to the audio adaptor. I also ran the Examples/SDfat/SdInfo sketch and got the following report:
Code:
20:55:35.768 -> SdFat version: 2.1.2
20:55:35.768 -> Assuming an SDIO interface.
20:55:35.768 -> 
20:55:35.768 -> type any character to start
20:55:42.019 -> init time: 21 ms
20:55:42.019 -> 
20:55:42.019 -> Card type: SDHC
20:55:42.019 -> 
20:55:42.019 -> Manufacturer ID: 0X3
20:55:42.019 -> OEM ID: SD
20:55:42.019 -> Product: SC16G
20:55:42.019 -> Version: 8.0
20:55:42.019 -> Serial number: 0XB13FFFB1
20:55:42.019 -> Manufacturing date: 6/2021
20:55:42.019 -> 
20:55:42.019 -> cardSize: 15931.54 MB (MB = 1,000,000 bytes)
20:55:42.019 -> flashEraseSize: 128 blocks
20:55:42.019 -> eraseSingleBlock: true
20:55:42.019 -> 
20:55:42.019 -> OCR: 0XC0FF8000
20:55:42.019 -> 
20:55:42.019 -> SD Partition Table
20:55:42.019 -> part,boot,bgnCHS[3],type,endCHS[3],start,length
20:55:42.019 -> 1,0X0,0X82,0X3,0X0,0XC,0XFE,0XFF,0XFF,8192,31108096
20:55:42.019 -> 2,0X0,0X0,0X0,0X0,0X0,0X0,0X0,0X0,0,0
20:55:42.019 -> 3,0X0,0X0,0X0,0X0,0X0,0X0,0X0,0X0,0,0
20:55:42.019 -> 4,0X0,0X0,0X0,0X0,0X0,0X0,0X0,0X0,0,0
20:55:42.019 -> 
20:55:42.019 -> Scanning FAT, please wait.
20:55:42.111 -> 
20:55:42.111 -> Volume is FAT32
20:55:42.111 -> sectorsPerCluster: 64
20:55:42.111 -> clusterCount:      485936
20:55:42.111 -> freeClusterCount:  425019
20:55:42.111 -> fatStartSector:    8790
20:55:42.111 -> dataStartSector:   16384
20:55:42.111 -> 
20:55:42.111 -> type any character to start
 
Diagram added to .md showing what's going on under the hood.
buffering.jpg
Full explanation can be found here.
 
I've added a second version of one of the demos: RecSynthMusic -> RecSynthMusicV2.

This uses an IntervalTimer along with a super-low-priority "slave" interrupt to significantly improve the timing accuracy of the player, even with a marginal SD card (SanDisk Ultra 16Gb SDHC A1). Because the RecSynthMusic example still uses the original PlaySynthMusic method of timing using calls to delay(), it suffers from weird stretched note or rest lengths whenever an SD write takes a long time. There's a ReadMe.md with a graphic in the example folder linked above.
 
Hi!

Looks to be a great project. I am wondering how this would work in the following use case:
Say, I have a set of 88 piano samples in wav files, or it could even be as high as 264 wav files if there are three separate files for different loudness for each note. In an optimal situation, each of these 264 wav files would have the beginning buffered at all times even though there would be just 8 note stereo polyphony. Is this possible with your library, or is it limited to one wav file buffer per player? Anyhow there probably would not be enough space for buffering all of the files at the same time in PSRAM? Maybe there is some much more clever way to approach this than buffering all the files.
 
Hi there. A fair question and one that in recent weeks 'as been much on my mind...

8Mb of PSRAM can hold 4Msamples, or about 95s of audio. That's about 360ms for each of your 264 samples, so unlikely to allow you to hold all the samples at once. However, you could hold ~250ms of the start of each sample, leaving ~29s available for buffering your 8 voices or other usage. So long as you can cue up an audio file in <250ms, you could proceed thus:
  • load all beginning samples to PSRAM
  • on note trigger, start playing the beginning from PSRAM...
  • ...cue up the "rest of note" file...
  • ...exactly as the beginning is done, resume the cued up note
For this to work, the beginning has to be a multiple of 128 samples. It looks like AudioPlayMemory will do the job, the data format needed in PSRAM is documented at https://www.pjrc.com/teensy/td_libs_AudioPlayMemory.html. The trick is to get the transition from AudioPlayMemory to AudioPlayWAVstereo::play() to happen at exactly the right moment. To start with you could just sit in a really tight polling loop, but ongoing it might be sensible to consider a custom AudioStream object that can look at the AudioPlayMemory object's status and resume the matching AudioPlayWAVstereo one within an audio update. Should be doable.

How big is your piano sample set (Mbytes)? If it's open source, then it might make a good demo. You may have to talk me through its correct use, in terms of choosing the right one-of-three samples and setting the mixer level, based on MIDI volume.
 
Last edited:
That approach sounds good. Here is a set of samples I have been using for testing:
https://theremin.music.uiowa.edu/MISpiano.html

So far I've tested them with FrankB's waveplayer, which plays the samples straight from the SD card I believe. It has been working surprisingly well, but I have a feeling that the way you process the audio data is more robust.
The way I have been using the piano samples is just choosing one of the three loudness levels based on MIDI velocity with splits at somewhere around 40 and 80 velocity. How to handle the volume of the mixer is still open for me. One way would be to pre-process the samples to be on the same loudness level and then just adjust the mixer level straight from the midi velocity.
 
That approach sounds good. Here is a set of samples I have been using for testing:
https://theremin.music.uiowa.edu/MISpiano.html

So far I've tested them with FrankB's waveplayer, which plays the samples straight from the SD card I believe. It has been working surprisingly well, but I have a feeling that the way you process the audio data is more robust.
The way I have been using the piano samples is just choosing one of the three loudness levels based on MIDI velocity with splits at somewhere around 40 and 80 velocity. How to handle the volume of the mixer is still open for me. One way would be to pre-process the samples to be on the same loudness level and then just adjust the mixer level straight from the midi velocity.

That's good, I found a copy of those samples already converted to WAV, which saves a bit of work! Those also have filenames implying splits at velocities 48 and 96. It looks as if they do all have the same peak level, so your scheme is probably a good place to start. Interestingly, in my set there are only 259 sample files - need to check that out. FrankB's wave player is pretty good - I worked with him a bit on it, but then he went off in a direction I wan't keen on, so I stopped.

I've realised that I miscalculated on a couple of points:
  • the files are stereo, so you can only pre-buffer about 170ms in one 8MB PSRAM
  • the AudioPlayMemory object is mono, so either we do a stereo version or use two of them to play two pre-buffers
For simplicity and as a bonus feature I think I need to allow for playing / cueing up a WAV file starting from a point other than its first sample - should be fairly straightforward, and may even (in the dim distant future) lead to some sort of file looping capability. The latter needs care, as I want to stick to always loading in at a sector boundary.
 
I've pushed a new version with (undocumented ... sorry ... do it soon) ability to start or cue playback at a chosen point in the file. An optional float parameter to play(file) or cue(file) is in milliseconds, e.g. playWAVstereo1.cueSD("/piano/loud/C4-97-127.wav",174.14966) will start playing 174.14966ms into the file when you call playWAVstereo1.play().

positionMillis() returns the progress relative to the file start, so you can see it's working.

Early testing with a SanDisk Extreme Plus 64Gb SDXC A2 card suggests that it's taking about 3ms to cue up a file, so the 174ms pre-load buffer is plenty. But this is only for playing one note ... more brutal testing needed. Pre-loading the starts of 259 files into just under 8MB of PSRAM at boot takes about 3 seconds, which is very gratifying.
 
Last edited:
Documentation added, along with a preliminary SDpiano example. The latter is not currently behaving very well when stressed, i.e. if you hit a lot of (short?) notes in quick succession, so it needs working on to be a truly convincing example. Fairly sure it's mostly my sketch, with probably a few gremlins lurking in AudioPlayWAVstereo object as well. I've started digging into it, but it's a bear to debug real-time stuff like this.

Turns out I've also stress-tested the SDfat library (found a place where it could loop forever ... the author is in the process of fixing that, though I suspect my code is doing something nasty to provoke it). I also found a bug in the AudioEffectEnvelope, which I think stops altogether if its source doesn't feed it audio blocks: the audio output is still silence, as it should be, but the timing stops as well, so if this happens during the release phase the envelope never goes idle, with the result that .isActive() will never return false. The latter change is in my latest commit, and I've put in a PR for Paul to consider. In his copious free time...
 
Updated further, the SDpiano example is now working OK as far as I can tell. Turns out it's a Bad Idea to close a file on the SD from within an ISR, while another file is being read from.
 
Updated further, the SDpiano example is now working OK as far as I can tell. Turns out it's a Bad Idea to close a file on the SD from within an ISR, while another file is being read from.

Sounds good! I’ll check it out as soon as I get my testing hardware back together!
 
Error compiling revised Audio library example

Hi,

I downloaded the .zip from your github here (under the code tab at https://github.com/h4yn0nnym0u5e/Audio ) and copied the code below into the arduino ide V 1.8.19, compiling for the teensy 4.0 with audio shield rev D. Note that I also have a teensy 4.1 in case it would be helpful...

1. I'd really like to see the full example that @h4yn0nnym0u5e mentioned, with simultaneous recording and playback for 16 mono files and 8 channel audio files. Can anyone share an example? I'm trying to make a music looper system with support for multiple layers.

2. Note (in case this matters) that I am still getting an error where multiple audio libraries were found on my computer (the default C path, and the path I have for my sketch. It was defaulting to my C path so I manually put the full path in the include:

Code:
#include <Bounce.h>
#include <D:\All My Saves\Saves\Arduino-Teensy\libraries\Audio-master\Audio.h>


3. Here is the error I'm seeing with the demo code:

Code:
AudioRecorder_Multi_v1:18: error: 'AudioPlayWAVstereo' does not name a type
 AudioPlayWAVstereo       playWAVstereo1; //xy=107,155
 ^
AudioRecorder_Multi_v1:22: error: 'AudioRecordWAVstereo' does not name a type
 AudioRecordWAVstereo     recordWAVstereo1; //xy=321,98
 ^
AudioRecorder_Multi_v1:23: error: 'playWAVstereo1' was not declared in this scope
 AudioConnection          patchCord1(playWAVstereo1, 0, i2sOut, 0);
                                     ^
AudioRecorder_Multi_v1:24: error: 'playWAVstereo1' was not declared in this scope
 AudioConnection          patchCord2(playWAVstereo1, 1, i2sOut, 1);
                                     ^
AudioRecorder_Multi_v1:26: error: 'recordWAVstereo1' was not declared in this scope
 AudioConnection          patchCord4(i2sIn, 0, recordWAVstereo1, 0);
                                               ^
AudioRecorder_Multi_v1:27: error: 'recordWAVstereo1' was not declared in this scope
 AudioConnection          patchCord5(i2sIn, 1, recordWAVstereo1, 1);
                                               ^
AudioRecorder_Multi_v1: In function 'void setup()':
AudioRecorder_Multi_v1:79: error: 'AudioBuffer' does not name a type
   const AudioBuffer::bufType bufMem = AudioBuffer::inHeap;
         ^
AudioRecorder_Multi_v1:80: error: 'playWAVstereo1' was not declared in this scope
   playWAVstereo1.createBuffer(sz,bufMem);
   ^
AudioRecorder_Multi_v1:80: error: 'bufMem' was not declared in this scope
   playWAVstereo1.createBuffer(sz,bufMem);
                                  ^
AudioRecorder_Multi_v1:81: error: 'recordWAVstereo1' was not declared in this scope
   recordWAVstereo1.createBuffer(sz,bufMem);
   ^
AudioRecorder_Multi_v1: In function 'void startRecording()':
AudioRecorder_Multi_v1:140: error: 'recordWAVstereo1' was not declared in this scope
   recordWAVstereo1.recordSD(fileName);
   ^
AudioRecorder_Multi_v1: In function 'void stopRecording()':
AudioRecorder_Multi_v1:150: error: 'recordWAVstereo1' was not declared in this scope
   recordWAVstereo1.stop();  
   ^
AudioRecorder_Multi_v1: In function 'void startPlaying()':
AudioRecorder_Multi_v1:157: error: 'playWAVstereo1' was not declared in this scope
   playWAVstereo1.playSD(fileName);
   ^
AudioRecorder_Multi_v1: In function 'void continuePlaying()':
AudioRecorder_Multi_v1:162: error: 'playWAVstereo1' was not declared in this scope
   if (!playWAVstereo1.isPlaying()) 
        ^
AudioRecorder_Multi_v1: In function 'void stopPlaying()':
AudioRecorder_Multi_v1:171: error: 'playWAVstereo1' was not declared in this scope
   playWAVstereo1.stop();
   ^
Multiple libraries were found for "SD.h"
 Used: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD
 Not used: C:\Program Files (x86)\Arduino\libraries\SD
'AudioPlayWAVstereo' does not name a type


Here is the demo code I'm using:
There is an example now - here's a sketch adapted straight from the "Recorder" example:
Code:
// Record sound as a stereo WAV file data to an SD card, and play it back.
//
// 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
AudioPlayWAVstereo       playWAVstereo1; //xy=107,155
AudioInputI2S            i2sIn;           //xy=130,98
AudioAnalyzePeak         peak1;          //xy=272,31
AudioOutputI2S           i2sOut;           //xy=287,155
AudioRecordWAVstereo     recordWAVstereo1; //xy=321,98
AudioConnection          patchCord1(playWAVstereo1, 0, i2sOut, 0);
AudioConnection          patchCord2(playWAVstereo1, 1, i2sOut, 1);
AudioConnection          patchCord3(i2sIn, 0, peak1, 0);
AudioConnection          patchCord4(i2sIn, 0, recordWAVstereo1, 0);
AudioConnection          patchCord5(i2sIn, 1, recordWAVstereo1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=262,210
// 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
//*/

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

const char* fileName = "Stereo.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;
  playWAVstereo1.createBuffer(sz,bufMem);
  recordWAVstereo1.createBuffer(sz,bufMem);

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


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 == 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();
}


void startRecording() 
{
  Serial.println("startRecording");
  recordWAVstereo1.recordSD(fileName);
  mode = 1;
}

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

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


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

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

void stopPlaying() {
  Serial.println("stopPlaying");
  playWAVstereo1.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 :-)
}
I've only tested it briefly, but I think it works OK on a Teensy 4.1 + audio adaptor + SanDisk Extreme 64Gb SDXC card. Let me know how it goes for you...
 
Hi @TeensySam!

Thanks for giving this a go. Here's a few suggestions to help get you started:
  • you need to download the feature/buffered-SD branch from https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD *
  • ideally, you should put the library in \<your sketches folder>\libraries\Audio; at compile time you should get a message saying it got chosen, but no error **
  • (to disable it, I have a \<your sketches folder>\libraries-unused\ folder to move stuff to - Arduino IDE doesn't pick up on stuff there) ***
  • in Arduino IDE, you should then find some stuff in the Examples > Examples from custom libraries > Audio > Buffered (see image below)
  • the RecSynthMusicV2 example plays 16 tracks, recording to 3 files of 4, 6 and 8 tracks (some unused), then plays the recorded files back
2022-11-11 20_49_25-RecSynthMusicV2 _ Arduino 1.8.19.png

* assuming the link you posted to my github repo is exactly right, you've got the "master" branch, which just duplicates Paul's most up-to-date library; this would explain the "AudioRecorder_Multi_v1:18: error: 'AudioPlayWAVstereo' does not name a type" error, and most of the following errors result from that

** having the library folder called Audio-master may actually work fine, but why risk it! You shouldn't need the full path to the header, #include <Audio.h> should be enough

*** if you find these examples, then you got the correct branch at step 1

Good luck, do let us know how it goes for you
 
The latest commit adds the ability to specify the filesystem in use for playing and recording, on a per-object basis. Example code (which is in the commit, in examples/Buffered/AudioTestPlayMultiSD) plays from both the built-in SD slot and the audio adaptor slot on a Teensy 4.1. Change buttons and filenames to suit your system!
Code:
// Play back sounds from multiple SD cards
// Tested on Teensy 4.1, changes may be needed for 3.x

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

#define SDCARD_CS_PIN 10 // audio adaptor

// #define sd1 SD // can do this for one card
SDClass sd1;
SDClass sd2;
File frec;

// GUItool: begin automatically generated code
AudioInputI2S            i2s2;           //xy=105,63
AudioAnalyzePeak         peak1;          //xy=278,108
AudioRecordQueue         queue1;         //xy=281,63
AudioPlayWAVstereo           playRaw1;       //xy=302,157
AudioPlayWAVstereo           playRaw2;       //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(playRaw2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265,212
// GUItool: end automatically generated code

// Bounce objects to easily and reliably read the buttons
Bounce buttonPlay1 =   Bounce(30, 8);
Bounce buttonPlay2 =   Bounce(31, 8);

//==================================================================
void setup() {

  // Configure the pushbutton pins
  pinMode(30, INPUT_PULLUP);
  pinMode(31, INPUT_PULLUP);

  // Audio connections require memory
  AudioMemory(10);

  while (!Serial)
    ;
  Serial.println("Started!");

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.setAddress(HIGH);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.07);

  playRaw1.createBuffer(2048,AudioBuffer::inHeap);
  playRaw2.createBuffer(2048,AudioBuffer::inHeap);

  // initialize the internal Teensy 4.1 SD card
  if (!sd1.begin(BUILTIN_SDCARD)) {
    Serial.println("error sd1.begin");
  }

  // initialize the Audio Adapter SD card
  if (!sd2.begin(SDCARD_CS_PIN)) {
    Serial.println("error sd2.begin");
  }
}


//==================================================================
void loop() {
  buttonPlay1.update();
  buttonPlay2.update();

  if (buttonPlay1.fallingEdge()) {
    Serial.println("Play 1");
    // playRaw1.play("sine220.wav"); // if sd1 is #defined as SD, this works
    playRaw1.play("sine220.wav", sd1);
  }
  if (buttonPlay2.fallingEdge()) {
    Serial.println("Play 2");
    playRaw2.play("sine330.wav", sd2);
  }
}
Due credit to the OP and contributors on this thread for inspiration and demo code...
 
Awesome job on all this so far!

I'm working on an audio looper project and am currently using newdigate's awesome Teensy Variable Playback library, but would love the ability "play / record multiple files simultaneously" which I've found isn't possible with variable playback library. But your buffered code doesn't have variable pitch control or looping features. Looks like you've looked into this, so just probably bugging you at this point :)
 
You probably are :D but thanks for the encouragement!

Development is a bit stalled at the moment, as I’ve been working on adding the ability to pre-load the start of a file for low latency applications; this will simplify the SDPiano demo, for example. However, I’ve broken something somewhere so it’s not ready for a public announcement yet … oops

I have corresponded with Nic about adding my approach to his library, as it would clearly be useful - this was prompted by @positionhigh who wants it for the MicroDexed project. Nic has no bandwidth but is open to a suitable PR, so it’s on my radar to at least take a look at, though that’s unlikely to be for several weeks yet due to my other commitments.
 
Back
Top