WaveplayerEx

It's very late here, and I've only had a quick look at it for now.
One could of course call yield() in isPlaying() (etc).

If the whole thing is optional, i.e. - by default - runs the reads in the interrupt, and you could switch to the eventResponder variant somehow, it would really be worth considering to adopt that.
It would only be needed that it is by default compatible to the old player.

But if Paul doesn't say anything about it in the next weeks (at the moment it doesn't look like he will), the compatibility doesn't matter. There is no chance that the player will be adopted some timer later, and in this case I will change the license to GPL.

Cheers,
Frank
 
Agree about setting the default, as noted that’s very easy. It’d be good to get some response from Paul on whether the player might be adopted at some point, and guidance on what would improve the likelihood. We seem to be working towards sorting out some of the issues that keep requiring support, and if the recording branch works out that’s another box ticked for him. Obviously he has other priorities taking his time at the moment, but a quick nod of approval might be on the cards!

I like your decoder function concept: will merge that in soon, got to work today :(

Cheers

Jonathan
 
All merged in and pushed up to my repo.

Just done a test with 16x mono 16-bit 44kHz WAV files: CPU load looks like 12.5% with the event version, sounds as if it's working OK (hard to tell with that many tracks...). Switched to interrupt mode it's 160% and sounds horrible, but doesn't crash. I've built 3 files with 4, 6 and 8 tracks containing the same data as the 16 separate files (one silent track on each of the 6- and 8-track WAVs) - about to write a sketch to check that works OK.

Cheers

Jonathan
 
Yes.. .that's the reason why single files are bad :) I can play 3 or more (i stopped at 3) 8 chanell files with marginal CPU%...
But the speedup is really amazing. Wonder where it comes from? Does SDFat not work well when used from an ISR, perhaps?
 
Forgot to say, those figures are with addMemoryForRead(4). With the 16 tracks and addMemoryForRead(1), I get 30% and 125%, though oddly the interrupt version actually still sounds OK.
 
Clearly not. I get 5% load for event-driven, 88-95% interrupt-driven for the 4+6+8 track test. Both sound OK.

Hm. I have 25% for 8+8+2 with my current test.
Anyway, there clearly seems to be an issue with SDFat and ISR.
What do you think?

P.s. how do you test the cpu% ?
 
Mainly using the Audio library’s figures, plus I put a wrapper around the event response read from the SD, as that’s obviously happening outside where the library can count it. Interrupt counts the read, of course, so I ignore the % for the response code in that case.

Could easily have an error in there I guess. Also could be quite dependent on the SD card, but I’m only using the one so results should be comparable from one run to the next.
 
Thx, i've merged it.

I'll write a short test sketch to see the difference of read() in interrupt and main program.
 
Great. Note that it's "safe" to switch the read() method while actively playing, as far as I can tell. May be some minor pops, but it doesn't crash.

I've just done some documentation in the GUI designer, and a PR for that. Should have done it first, really. I left out some functions, not sure if you only put them in for debug purposes.

Cheers

Jonathan
 
Great. Note that it's "safe" to switch the read() method while actively playing, as far as I can tell. May be some minor pops, but it doesn't crash.

I've just done some documentation in the GUI designer, and a PR for that. Should have done it first, really. I left out some functions, not sure if you only put them in for debug purposes.

Cheers

Jonathan

Thank you!
And... indeed... the speed difference pretty amazing:

edit: code had an issue.. deleted.

... now... the result... :
Code:
seek()+read() in main needed 22 microseconds
seek()+read() in software isr needed 313 microseconds

WOW.
 
Last edited:
EH.. NO:

It had issues.


This is better:
Code:
#include <SD.h>


volatile unsigned long t;
char buf[512];
File file;


void rd()
{  
  t = micros();
  file.seek(1024);
  file.read(buf, sizeof(buf));
  t = micros() - t;  
}




void setup() {
  if (!(SD.begin(BUILTIN_SDCARD))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
  
  attachInterruptVector(IRQ_SOFTWARE, rd);
  NVIC_SET_PRIORITY(IRQ_SOFTWARE, 208); // 255 = lowest priority
  NVIC_ENABLE_IRQ(IRQ_SOFTWARE);


  file = SD.open("SDTEST3.WAV");
  file.read(buf, sizeof(buf));
  file.seek(0);


  delay(10);
  rd();
  delay(1);
  Serial.printf("seek()+read() in main needed %d microseconds\n", t);
  
  delay(10);
  NVIC_SET_PENDING(IRQ_SOFTWARE);
  delay(1);
  Serial.printf("seek()+read() in software isr needed %d microseconds\n", t); 
  
}


void loop() {}

and now it prints the same time. So there is no speedup..
 
Looks valid to me, there must be something going wrong in my measurements. Need to think about it... There's no logical reason why the SD card speed should vary, just the way it interacts in practice with other things might (does?) cause problems.

Agree about the concurrency - with no interrupt reads other filesystem activity should be fine.

Slight change I'd make (in many such files) is to use:
Code:
  while (!(SD.begin(BUILTIN_SDCARD))) 
  {
      Serial.println("Unable to access the SD card");
      delay(500);
  }
My SD card usually doesn't start first time, and the original code only gives it one chance!

Cheers

Jonathan
 
Found part of the problem and created a PR - I wasn't taking into account the sample size (8- or 16-bit), or the number of channels, when estimating the CPU load from the SD card reads.

With this in place I ran this code:
Code:
// Simple WAV file player example testing multi-file CPU load

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

#define USE_BOESING_PLAYER

#if defined(USE_BOESING_PLAYER)
  #define AudioPlaySdWav AudioPlayWav // divert normal player to Frank's one
#endif // defined(USE_BOESING_PLAYER)

// GUItool: begin automatically generated code
AudioPlaySdWav           track16; //xy=214,1053
AudioPlaySdWav           track15; //xy=216,981
AudioPlaySdWav           track14; //xy=219,915
AudioPlaySdWav           track8; //xy=221,521
AudioPlaySdWav           track13; //xy=221,843
AudioPlaySdWav           track7; //xy=223,449
AudioPlaySdWav           track12; //xy=223,776
AudioPlaySdWav           track11; //xy=224,716
AudioPlaySdWav           track6;         //xy=226,383
AudioPlaySdWav           track10; //xy=225,662
AudioPlaySdWav           track9; //xy=226,603
AudioPlaySdWav           track5;         //xy=228,311
AudioPlaySdWav           track4;         //xy=230,244
AudioPlaySdWav           track3;         //xy=231,184
AudioPlaySdWav           track2;         //xy=232,130
AudioPlaySdWav           track1;         //xy=233,71
AudioMixer4              mixerR3; //xy=467,895
AudioMixer4              mixerL3; //xy=470,680
AudioMixer4              mixerR4; //xy=470,966
AudioMixer4              mixerL4; //xy=471,757
AudioMixer4              mixerR1; //xy=474,363
AudioMixer4              mixerL1; //xy=477,148
AudioMixer4              mixerR2; //xy=477,434
AudioMixer4              mixerL2; //xy=478,225
AudioMixer4              mixerL;         //xy=733,522
AudioMixer4              mixerR;         //xy=733,606
AudioOutputI2S           i2s;       //xy=907,572
AudioConnection          patchCord1(track16, 0, mixerL4, 3);
AudioConnection          patchCord2(track16, 0, mixerR4, 3);
AudioConnection          patchCord3(track15, 0, mixerR4, 2);
AudioConnection          patchCord4(track15, 0, mixerL4, 2);
AudioConnection          patchCord5(track14, 0, mixerL4, 1);
AudioConnection          patchCord6(track14, 0, mixerR4, 1);
AudioConnection          patchCord7(track8, 0, mixerL2, 3);
AudioConnection          patchCord8(track8, 0, mixerR2, 3);
AudioConnection          patchCord9(track13, 0, mixerL4, 0);
AudioConnection          patchCord10(track13, 0, mixerR4, 0);
AudioConnection          patchCord11(track7, 0, mixerR2, 2);
AudioConnection          patchCord12(track7, 0, mixerL2, 2);
AudioConnection          patchCord13(track12, 0, mixerL3, 3);
AudioConnection          patchCord14(track12, 0, mixerR3, 3);
AudioConnection          patchCord15(track11, 0, mixerL3, 2);
AudioConnection          patchCord16(track11, 0, mixerR3, 2);
AudioConnection          patchCord17(track6, 0, mixerL2, 1);
AudioConnection          patchCord18(track6, 0, mixerR2, 1);
AudioConnection          patchCord19(track10, 0, mixerL3, 1);
AudioConnection          patchCord20(track10, 0, mixerR3, 1);
AudioConnection          patchCord21(track9, 0, mixerL3, 0);
AudioConnection          patchCord22(track9, 0, mixerR3, 0);
AudioConnection          patchCord23(track5, 0, mixerL2, 0);
AudioConnection          patchCord24(track5, 0, mixerR2, 0);
AudioConnection          patchCord25(track4, 0, mixerL1, 3);
AudioConnection          patchCord26(track4, 0, mixerR1, 3);
AudioConnection          patchCord27(track3, 0, mixerL1, 2);
AudioConnection          patchCord28(track3, 0, mixerR1, 2);
AudioConnection          patchCord29(track2, 0, mixerL1, 1);
AudioConnection          patchCord30(track2, 0, mixerR1, 1);
AudioConnection          patchCord31(track1, 0, mixerL1, 0);
AudioConnection          patchCord32(track1, 0, mixerR1, 0);
AudioConnection          patchCord33(mixerR3, 0, mixerR, 2);
AudioConnection          patchCord34(mixerL3, 0, mixerL, 2);
AudioConnection          patchCord35(mixerR4, 0, mixerR, 3);
AudioConnection          patchCord36(mixerL4, 0, mixerL, 3);
AudioConnection          patchCord37(mixerR1, 0, mixerR, 0);
AudioConnection          patchCord38(mixerL1, 0, mixerL, 0);
AudioConnection          patchCord39(mixerR2, 0, mixerR, 1);
AudioConnection          patchCord40(mixerL2, 0, mixerL, 1);
AudioConnection          patchCord41(mixerL, 0, i2s, 0);
AudioConnection          patchCord42(mixerR, 0, i2s, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=908,666
// GUItool: end automatically generated code

AudioPlaySdWav* tracks[]={&track1,&track2,&track3,&track4,&track5,&track6,
                          &track7,&track8,&track9,&track10,
                          &track11,&track12,&track13,&track14,&track15,&track16};

#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

/*********************************************************************************/

uint32_t next;
void setup() {
  Serial.begin(115200);
  while (!Serial)
    ;
  
  if (CrashReport) {
    Serial.println(CrashReport);
    CrashReport.clear();
  }
  Serial.println("Started!");

  AudioMemory(50);

  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  while (!(SD.begin(SDCARD_CS_PIN))) {
//      Serial.println("Unable to access the SD card");
      delay(500);
  }

  mixerL.gain(0,0.02);
  mixerL.gain(1,0.02);
  mixerL.gain(2,0.02);
  mixerL.gain(3,0.02);
  mixerR.gain(0,0.02);
  mixerR.gain(1,0.02);
  mixerR.gain(2,0.02);
  mixerR.gain(3,0.02);

  mixerL1.gain(0,0.3);
  mixerL1.gain(1,0.2);
  mixerL1.gain(2,0.1);
  mixerL1.gain(3,0.35);
  mixerL2.gain(0,0.6);
  mixerL2.gain(1,0.5);
  mixerL2.gain(2,0.4);
  mixerL2.gain(3,0.35);

  mixerR1.gain(0,0.4);
  mixerR1.gain(1,0.5);
  mixerR1.gain(2,0.6);
  mixerR1.gain(3,0.55);
  mixerR2.gain(0,0.1);
  mixerR2.gain(1,0.2);
  mixerR2.gain(2,0.3);
  mixerR2.gain(3,0.15);

  mixerL3.gain(0,0.3);
  mixerL3.gain(1,0.2);
  mixerL3.gain(2,0.1);
  mixerL3.gain(3,0.35);
  mixerL4.gain(0,0.6);
  mixerL4.gain(1,0.5);
  mixerL4.gain(2,0.4);
  mixerL4.gain(3,0.35);

  mixerR3.gain(0,0.4);
  mixerR3.gain(1,0.5);
  mixerR3.gain(2,0.6);
  mixerR3.gain(3,0.55);
  mixerR4.gain(0,0.1);
  mixerR4.gain(1,0.2);
  mixerR4.gain(2,0.3);
  mixerR4.gain(3,0.15);

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);

  track1.addMemoryForRead(1);
  next = millis();

}

bool useEventReading = false;
void toggleEventReading(void)
{
  useEventReading = !useEventReading;
  track1.enableEventReading(useEventReading);
}


void loop() {  
  int pulse = 20;  
  
  track1.play("sine440.wav",true);
  track2.play("sine110.wav",true);
  track3.play("sine330.wav",true);
  track4.play("sine220.wav",true);
  track5.play("sine550.wav",true);
  track6.play("sine660.wav",true);  
  track7.play("sine110.wav",true);
  track8.play("sine440.wav",true);
  track9.play("sine330.wav",true);
  track10.play("sine220.wav",true);
  track12.play("sine110.wav",true);
  track11.play("sine440.wav",true);
  track13.play("sine330.wav",true);
  track14.play("sine220.wav",true);
  track15.play("sine550.wav",true);
  track16.play("sine660.wav",true);
  AudioNoInterrupts();
  track1.pause(false);
  track2.pause(false);
  track3.pause(false);
  track4.pause(false);
  track5.pause(false);
  track6.pause(false);
  track7.pause(false);
  track8.pause(false);
  track9.pause(false);
  track10.pause(false);
  track11.pause(false);
  track12.pause(false);
  track13.pause(false);
  track14.pause(false);
  track15.pause(false);
  track16.pause(false);
  AudioInterrupts();
  
  while (track1.isPlaying())
  {
    float pct=0.0f;
    for (int i=0;i<16;i++)
    {
      pct += tracks[i]->getCPUload();
    }
    
    Serial.print(AudioProcessorUsageMax());
    AudioProcessorUsageMaxReset();
    Serial.print(' ');
    Serial.print(pct); 
    Serial.print(' ');
    Serial.print(useEventReading?90:110);
    Serial.print(' ');
    Serial.print(pulse);
    pulse=0;
    
    Serial.println();
    delay(50); 

    if (millis() > next)
    {
      next += 10000;
      toggleEventReading();
    }
  }
  
  delay(250);
}
to create this graph:
2021-09-02 21_06_12-audio.ods - LibreOffice Calc.png
Note the CPU load is plotting the AudioProcessorUsageMax over 50ms, whereas the SD read load is just taking the most recent cycle's load: this may account for the larger variations in CPU load. As expected the SD card read load is constant, and about what would be expected for 16x 16-bit channels. The yellow trace is high (110) when reading during interrupt, and low (90) when event driven; the green trace shows when the tracks are re-started (the WAV files are all 3 seconds long). The CPU load overall seems be roughly 3.3% plus SD card read load. However, there are a couple of times when the SD reads take a longer time, which affects the interrupt load badly, and causes audible glitches. Also, starting paused, and then un-pausing makes a big difference.

Cheers

Jonathan
 
My SD card usually doesn't start first time, and the original code only gives it one chance!
Hm. It should not be like that! That's not normal - it really should work at first try. Every time.
For the other issues.. is it the same for all your SD Cards?
Did you try to format them with th official SD Formatter tool? (Bill Greiman, the author of the SD library recommends that, and I use it for more than a decade now)

You're using the inbuilt SD Slot, right?

Time for a ice cold good-night beer now ;)
 
I’m only using one microSD for my testing so far. No, it’s had no special treatment, I’ve probably never formatted it, just got it out of the pack and used it ... just like a real user :D It always seems to respond on the second try so it’s only a half second delay, and if I format it I may lose the occasional slow reads which are useful for testing the robustness of the buffering.
 
The cards are not equal. Did you know there is a 8-BIT cpu inside? It's better than a AVR 8-Bit arduino ;)
Not sure about the 8-bit.. got that info long years ago.. quite possible the cpus are even better today.
 
Suspect it's a dedicated controller / ASIC these days, or maybe an ARM processor. In the good ones. Cheap ones, who knows...
 
Thought I can make a little advertisement for the new player.

Features


  • Sample rate agnostic (does not check it)
  • (up to) 8 Channels / 8- or 16-bit
  • delay() after start no longer needed
  • all audio block sizes
  • interleaved reads / writes: only one file access on each audio-cycle
  • lastErr() returns last error
  • synchronized start
  • recording (not tested much)
  • plays from SD or File() object (the latter must be opened before calling "play(..)" )
  • Can play from memory with use of "MemFile" library, or File()
  • Simultanous playing & recording possible.
Play: Formats


  • n channel 8 bit unsigned *.wav
  • n channel u-law *.wav
  • n channel 16 bit signed *.wav
  • n channel 24 bit signed *.wav
  • n channel 8 bit signed *.aiff
  • n channel 8 bit unsigned *.aifc
  • n channel μlaw *.aifc (Apple, non ccitt)
  • n channel 16 bit signed *.aiff
  • n channel 8 bit signed, unsigned or μlaw RAW
  • n channel 16 bit signed, unsigned or 16 bit big-endian signed RAW
n = 1..8 channels.

Attention μlaw ist not compatible with wav2sketch. The player uses the "official" μlaw as used by aiff and wav.

Record: Formats


  • n channel 8 bit unsigned *.wav
  • n channel 16 bit signed *.wav
  • 4 channels max.



Teensy LC ist not supported anymore. Dropped support for EventResponder.
All depends on the speed of the storage medium.
For simultanous play, use a _fast SD Card. One file with many channels is not a problem - many files parallel are a problem - the Card has to do a seek() for every block - that is pretty slow. Only new, very fast cards can do that with many files. Best in this case is to use QSPI-Flash or PSRAM with Teensy 4.1
If you want SD, use a Kingston CANVAS Go! Plus or better. Use Teensy 3.6 or 4.1 with inbuilt SD slot, no SPI.


For issues, questions, etc please use Github "Issues". I do not visit the forum regularly at the moment. No email pls :)
 
Last edited:
- Yesterday I noticed that pause() does not work correctly with multiple files. This should be fixed - unfortunately it is a bit complicated. I'll do it when I have the necessary patience for it.

- Unfortunately it needs the rather crude fileHelper (apwFile) class - mainly because FS.h does not support preallocate(). It also exists in the hope that at some point FS will also know which interface is being used and can then flag SPI or other as being used and that I can add the code, then. However, it would be better if that class would not be needed and "File" would do it, instead.


If you have an Idea how to make it better: Let me know.

- I don't plan to do any major support (answering questions etc).

- If you say at some point that you really want to include it in the official version, I will change the license. But not before.
 
Last edited:
@all: Iv'e added an example that plays 14 stereo files simultanously - Needs a good card and a Teensy with inbuilt SD (But tested with T4.1 only)
 
Back
Top