Yet Another File Player (and recorder)

Is there any way this can be made into a separate library instead of having to replace the official audio library?
Probably, but it'd be a maintenance nightmare...

There are 7 code files needed for the functionality, but there's also documentation, a changed Design Tool with the extra objects available, and multiple examples, some of which won't work properly without fixes to standard library objects (like AudioEffectEnvelope, which gets stuck if fed NULL block pointers).

I'm hopeful that it may get rolled into the official library at some point, but as quite routine bugfix PRs aren't getting merged, I'm not even submitting it for consideration right now. I do try to keep this synced up with the few changes that do happen to the official library, so replacing shouldn't lose you any functionality.
 
My only concern is that I'm using platformio and I'm not sure how to properly overwrite the audio library in that context. Is it the same process as when using the Arduino IDE? Also, what is the most up to date link to the code to use, and is there documentation on how to integrate the project? My apologies, I just am not sure if I'll find outdated information by reading the thread.

Also, my main goal for using a separate library / or possibly your branch is being able to play between 4 and 16 audio sample files simultaneously, since I'm building a groove box. And I've found it challenging when reading wav files directly from an SD card.

This library (teensy-sample-flashloader) has been working for my needs, simultaneous playback of multiple RAW files in flash memory is working great so far, but I would love to be able to use a 256MB NAND flash chip via QSPI that I've soldered onto the bottom of the Teensy, so I can store and read more files in that larger flash memory space, but that library only uses the external PSRAM memory space.

Thanks in advance.
 
Hm, ok so I tried your buffered-sd Audio lib branch out, and I'm playing back a sequenced drum pattern with only 3 mono 16-bit WAV samples that are all only like max 1s long, using the AudioPlayWAVstereo objects. It's okay at a bpm of 120, but going to a bpm of 170 the Teensy crashes with this report:

CrashReport:
A problem occurred at (system time) 11:21:38
Code was executing from address 0x1B870
CFSR: 82
(DACCVIOL) Data Access Violation
(MMARVALID) Accessed Address: 0x6FFFC02A
Temperature inside the chip was 49.75 °C
Startup CPU clock speed is 600MHz
Reboot was caused by auto reboot after fault or bad interrupt detected


Whereas using the teensy-sample-flashloader approach, I don't see any crashing at that fast of a tempo with 3 mono RAW samples being read from flash memory.
 
Sounds like you figured out the PlatformIO bit, which is lucky because I have zero idea!

The most recent commit to https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD should get you the up to date version. Obviously I try not to push to GitHub until I’m fairly sure any new feature is working, but occasionally I might do so if there’s test code I need someone to try out, so do look at the commit log if you’re unsure.

Playing 3 mono files should be easy. The “classic” issue is not to put in any needed calls to createBuffer(), mainly because it wasn’t a requirement before. You can see an example in https://github.com/h4yn0nnym0u5e/Au...AudioTestPlayMultiSD/AudioTestPlayMultiSD.ino , and mentioned in post#1 (easily missed though, as it’s a long post).

If that doesn’t fix things, please come up with as short an example as you can manage, using the Arduino IDE and no special hardware requirements. Post that here in code tags and I can try to replicate the issue.

One other thought does occur to me - I don’t know if PlatformIO has the Arduino loop() function, which calls yield() when it exits. This is also touched on in post #1, and is important.
 
Sounds like you figured out the PlatformIO bit, which is lucky because I have zero idea!

The most recent commit to https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD should get you the up to date version. Obviously I try not to push to GitHub until I’m fairly sure any new feature is working, but occasionally I might do so if there’s test code I need someone to try out, so do look at the commit log if you’re unsure.

Playing 3 mono files should be easy. The “classic” issue is not to put in any needed calls to createBuffer(), mainly because it wasn’t a requirement before. You can see an example in https://github.com/h4yn0nnym0u5e/Au...AudioTestPlayMultiSD/AudioTestPlayMultiSD.ino , and mentioned in post#1 (easily missed though, as it’s a long post).

If that doesn’t fix things, please come up with as short an example as you can manage, using the Arduino IDE and no special hardware requirements. Post that here in code tags and I can try to replicate the issue.

One other thought does occur to me - I don’t know if PlatformIO has the Arduino loop() function, which calls yield() when it exits. This is also touched on in post #1, and is important.

Thanks, yeah I have the latest commit pulled down, and I have the calls to createBuffer as follows:

Code:
[COLOR=#CCCCCC][FONT=Menlo][/FONT][/COLOR][FONT=Menlo]  rraw_a1.createBuffer(16384,AudioBuffer::inExt);
  rraw_a2.createBuffer(16384,AudioBuffer::inExt);
  rraw_a3.createBuffer(16384,AudioBuffer::inExt);[/FONT]

I will try to make a simple enough sketch to replicate the issue, thanks. Latest crash report:

CrashReport:
A problem occurred at (system time) 14:50:4
Code was executing from address 0x1B870
CFSR: 82
(DACCVIOL) Data Access Violation
(MMARVALID) Accessed Address: 0x6FFFC00B
Temperature inside the chip was 47.63 °C
Startup CPU clock speed is 600MHz
Reboot was caused by auto reboot after fault or bad interrupt detected
Breadcrumb #2 was 443396305 (0x1A6DB0D1)
Breadcrumb #4 was 377694945 (0x16832AE1)
Breadcrumb #5 was 182083620 (0xADA6024)
 
That scuppers the easy ideas. The 0x6FFFC00B address looks like about 16k before the start of EXTMEM, which is probably a Clue. Are you able to run addr2line on your .elf file and find out what piece of code is at 0x1B870? That might help, but I think a simple sketch is going to be our best bet, if you can figure one out.

Is the 170bpm enough that you're re-triggering a playback object before its previous play has completed, but 120bpm is not? That should be OK, but it could be I've missed something.
 
Are you able to run addr2line on your .elf file and find out what piece of code is at 0x1B870?

addr2line was just outputting ??:? for me, odd.

That might help, but I think a simple sketch is going to be our best bet, if you can figure one out.

Yeah, I was able to get a sketch going with just the sequencer code and the audio files being triggered, no other hardware (OLED updates, encoder scanning, LED driver updating, etc), and it works fine at 170 BPM with the same samples and drum pattern.

It's weird because the logs I'm printing to evaluate the CPU and memory usage don't suggest that I'm maxing out CPU or have a memory leak or anything, but the Teensy stutters, freezes up, and then reboots.

At least it doesn't appear to be a problem with streaming from the SD card, which is nice.

Coincidentally, I also purchased a 64GB SDXC A2 uSD card to test with to see if my 32GB A1 card was not good enough. But I don't think it was the card that changed the outcome, I think it's something else in my program which is locking the Teensy up, but I'm not sure what it is yet.

Sorry for the noise, and thanks for the hard work on this!
 
addr2line was just outputting ??:? for me, odd.

...

Confirm the addr2line being used is from the current Teensy tools install for IDE 2 on Windows. The chain changed and an older one on my system worked before - but stopped after 1.58 or 1.59 and the one from here was copied to default path and renamed as addr2line: "C:\Users\_namehere_\AppData\Local\Arduino15\packages\teensy\tools\teensy-compile\11.3.1\arm\bin\arm-none-eabi-addr2line.exe"
 
Confirm the addr2line being used is from the current Teensy tools install for IDE 2 on Windows. The chain changed and an older one on my system worked before - but stopped after 1.58 or 1.59 and the one from here was copied to default path and renamed as addr2line: "C:\Users\_namehere_\AppData\Local\Arduino15\packages\teensy\tools\teensy-compile\11.3.1\arm\bin\arm-none-eabi-addr2line.exe"

Thanks, but I am using MacOS, I had to download addr2line through homebrew (`brew install binutils`).
 
but I think a simple sketch is going to be our best bet, if you can figure one out.

Ok, so I am able to replicate the issue. I'm able to consistently get my Teensy to stutter and hang with the following sketch. I can provide the audio files as well, but they're just some one-shot 16-bit mono WAV samples.

Code:
[COLOR=#DAE3E3][FONT=Menlo][COLOR=#000000]#include <uClock.h>[/COLOR]
[COLOR=#000000]#include <Audio.h>[/COLOR]
[COLOR=#000000]#include <SD.h>[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]#define sd1 SD[/COLOR]
[COLOR=#000000]#define SDCARD_CS_PIN    BUILTIN_SDCARD[/COLOR]
[COLOR=#000000]#define SDCARD_MOSI_PIN  43  // not actually used[/COLOR]
[COLOR=#000000]#define SDCARD_SCK_PIN   45  // not actually used[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]#define MAX_PATTERNS 16[/COLOR]
[COLOR=#000000]#define MAX_TRACKS 16[/COLOR]
[COLOR=#000000]#define MAX_STEPS 64[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]// GUItool: begin automatically generated code[/COLOR]
[COLOR=#000000]AudioPlayWAVstereo       rraw_a1;       //xy=302,157[/COLOR]
[COLOR=#000000]AudioPlayWAVstereo       rraw_a2;       //xy=302,157[/COLOR]
[COLOR=#000000]AudioPlayWAVstereo       rraw_a3;       //xy=302,157[/COLOR]
[COLOR=#000000]AudioMixer4              mixer1;         //xy=638,399[/COLOR]
[COLOR=#000000]AudioMixer4              mixer2;         //xy=638,399[/COLOR]
[COLOR=#000000]AudioMixer4              mixer3;         //xy=638,399[/COLOR]
[COLOR=#000000]AudioOutputI2S           audioOutput;           //xy=787,407[/COLOR]
[COLOR=#000000]AudioConnection          patchCord3(rraw_a1, 0, mixer1, 0);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord4(rraw_a1, 0, mixer1, 1);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord1(rraw_a2, 0, mixer1, 2);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord2(rraw_a2, 0, mixer1, 3);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord5(rraw_a3, 0, mixer2, 0);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord6(rraw_a3, 0, mixer2, 1);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord7(mixer1, 0, mixer3, 0);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord8(mixer1, 0, mixer3, 1);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord9(mixer2, 0, mixer3, 2);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord10(mixer2, 0, mixer3, 3);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord11(mixer3, 0, audioOutput, 0);[/COLOR]
[COLOR=#000000]AudioConnection          patchCord12(mixer3, 0, audioOutput, 1);[/COLOR]
[COLOR=#000000]AudioControlSGTL5000     sgtl5000_1;     //xy=793,542[/COLOR]
[COLOR=#000000]// GUItool: end automatically generated code[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]uint8_t bpm_blink_timer = 1;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]// Sequencer data [/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]enum TRACK_TYPE {[/COLOR]
[COLOR=#000000]  SAMPLE = 0,[/COLOR]
[COLOR=#000000]};[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]enum TRACK_STEP_STATE {[/COLOR]
[COLOR=#000000]  OFF = 0,[/COLOR]
[COLOR=#000000]  ON = 1,[/COLOR]
[COLOR=#000000]};[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]typedef struct[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  TRACK_STEP_STATE state = OFF;[/COLOR]
[COLOR=#000000]} TRACK_STEP;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]typedef struct[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  TRACK_TYPE track_type = SAMPLE;[/COLOR]
[COLOR=#000000]  TRACK_STEP steps[MAX_STEPS];[/COLOR]
[COLOR=#000000]  int8_t last_step = 16;[/COLOR]
[COLOR=#000000]  int8_t sample_id = 0;[/COLOR]
[COLOR=#000000]} TRACK;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]typedef struct[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  TRACK tracks[MAX_TRACKS];[/COLOR]
[COLOR=#000000]} PATTERN;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]typedef struct[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  PATTERN patterns[MAX_PATTERNS];[/COLOR]
[COLOR=#000000]} BANK;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]typedef struct[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  BANK banks[1];[/COLOR]
[COLOR=#000000]} SEQUENCER;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]enum SEQUENCER_PLAYBACK_STATE {[/COLOR]
[COLOR=#000000]  STOPPED = 0,[/COLOR]
[COLOR=#000000]  RUNNING = 1,[/COLOR]
[COLOR=#000000]  PAUSED = 2[/COLOR]
[COLOR=#000000]};[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]typedef struct[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  SEQUENCER seq;[/COLOR]
[COLOR=#000000]  SEQUENCER_PLAYBACK_STATE playback_state = STOPPED;[/COLOR]
[COLOR=#000000]  int8_t current_step = 1;[/COLOR]
[COLOR=#000000]} SEQUENCER_STATE;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]EXTMEM SEQUENCER_STATE _seq_state;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]int8_t current_selected_pattern = 0;[/COLOR]
[COLOR=#000000]int8_t current_selected_track = 0;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void onClockStop();[/COLOR]
[COLOR=#000000]void onClockStart();[/COLOR]
[COLOR=#000000]void ClockOut96PPQN(uint32_t tick);[/COLOR]
[COLOR=#000000]void triggerAllStepsForGlobalStep(void);[/COLOR]
[COLOR=#000000]void playFile(uint8_t num);[/COLOR]
[COLOR=#000000]void stopFiles(void);[/COLOR]
[COLOR=#000000]void fillOutSequencer(void);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void setup() {[/COLOR]
[COLOR=#000000]  Serial.begin(9600);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  if (CrashReport) {[/COLOR]
[COLOR=#000000]    Serial.print(CrashReport);[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  // Audio connections require memory to work.  For more[/COLOR]
[COLOR=#000000]  // detailed information, see the MemoryAndCpuUsage example[/COLOR]
[COLOR=#000000]  AudioMemory(40);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  // Comment these out if not using the audio adaptor board.[/COLOR]
[COLOR=#000000]  // This may wait forever if the SDA & SCL pins lack[/COLOR]
[COLOR=#000000]  // pullup resistors[/COLOR]
[COLOR=#000000]  sgtl5000_1.enable();[/COLOR]
[COLOR=#000000]  sgtl5000_1.volume(0.3);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  mixer1.gain(0, 0.5);[/COLOR]
[COLOR=#000000]  mixer1.gain(1, 0.5);[/COLOR]
[COLOR=#000000]  mixer1.gain(2, 0.7);[/COLOR]
[COLOR=#000000]  mixer1.gain(3, 0.7);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  rraw_a1.createBuffer(8192, AudioBuffer::inExt);[/COLOR]
[COLOR=#000000]  rraw_a2.createBuffer(8192, AudioBuffer::inExt);[/COLOR]
[COLOR=#000000]  rraw_a3.createBuffer(8192, AudioBuffer::inExt);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  SPI.setMOSI(SDCARD_MOSI_PIN);[/COLOR]
[COLOR=#000000]  SPI.setSCK(SDCARD_SCK_PIN);[/COLOR]
[COLOR=#000000]  if (!(SD.begin(SDCARD_CS_PIN))) {[/COLOR]
[COLOR=#000000]    // stop here, but print a message repetitively[/COLOR]
[COLOR=#000000]    while (1) {[/COLOR]
[COLOR=#000000]      Serial.println("Unable to access the SD card");[/COLOR]
[COLOR=#000000]      delay(500);[/COLOR]
[COLOR=#000000]    }[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  delay(100);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  // Setup our clock system[/COLOR]
[COLOR=#000000]  // Inits the clock[/COLOR]
[COLOR=#000000]  uClock.init();[/COLOR]
[COLOR=#000000]  // Set the callback function for the clock output to send MIDI Sync message.[/COLOR]
[COLOR=#000000]  uClock.setClock96PPQNOutput(ClockOut96PPQN);[/COLOR]
[COLOR=#000000]  // Set the callback function for MIDI Start and Stop messages.[/COLOR]
[COLOR=#000000]  uClock.setOnClockStartOutput(onClockStart);  [/COLOR]
[COLOR=#000000]  uClock.setOnClockStopOutput(onClockStop);[/COLOR]
[COLOR=#000000]  // Set the clock BPM to 120 BPM[/COLOR]
[COLOR=#000000]  uClock.setTempo(170);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  Serial.println("fill test sequencer out");[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  delay(100);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  // build test sequencer out with basic data[/COLOR]
[COLOR=#000000]  fillOutSequencer();[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  delay(100);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  Serial.println("Done filling test sequencer out");[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  uClock.start();[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  // play for only 1m[/COLOR]
[COLOR=#000000]  delay(60000);[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  uClock.stop();[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void loop(void)[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  //[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]// Internal clock handlers[/COLOR]
[COLOR=#000000]void ClockOut96PPQN(uint32_t tick) {[/COLOR]
[COLOR=#000000]  if (uClock.state==0) {[/COLOR]
[COLOR=#000000]    return;[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  if ((_seq_state.playback_state == SEQUENCER_PLAYBACK_STATE::RUNNING) && !(tick % (6)) ) {[/COLOR]
[COLOR=#000000]    // handle current sixteenth steps for all tracks[/COLOR]
[COLOR=#000000]    triggerAllStepsForGlobalStep();[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]    if (_seq_state.current_step <= 16) {[/COLOR]
[COLOR=#000000]      if (_seq_state.current_step < 16) {[/COLOR]
[COLOR=#000000]        ++_seq_state.current_step; // advance current step for sequencer[/COLOR]
[COLOR=#000000]      } else {[/COLOR]
[COLOR=#000000]        _seq_state.current_step = 1; // reset current step[/COLOR]
[COLOR=#000000]      }[/COLOR]
[COLOR=#000000]    }[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void onClockStart() {[/COLOR]
[COLOR=#000000]  _seq_state.playback_state = SEQUENCER_PLAYBACK_STATE::RUNNING;[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void onClockStop() {[/COLOR]
[COLOR=#000000]  _seq_state.playback_state = SEQUENCER_PLAYBACK_STATE::STOPPED;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  stopFiles();[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  Serial.println("stopped!");[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void triggerAllStepsForGlobalStep(void)[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  int8_t currGlobalStep = _seq_state.current_step - 1;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  BANK currentBank = _seq_state.seq.banks[0];[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  PATTERN currentPattern = currentBank.patterns[current_selected_pattern];[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  const int MAX_PATTERN_TRACK_SIZE = MAX_TRACKS;[/COLOR]
[COLOR=#000000]  for (int t = 0; t < MAX_PATTERN_TRACK_SIZE; t++) {[/COLOR]
[COLOR=#000000]    TRACK currTrack = currentPattern.tracks[t];[/COLOR]
[COLOR=#000000]    TRACK_STEP currTrackStep = currTrack.steps[currGlobalStep];[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]    if (currTrackStep.state == TRACK_STEP_STATE::ON) {[/COLOR]
[COLOR=#000000]      if (currTrack.track_type == TRACK_TYPE::SAMPLE) {[/COLOR]
[COLOR=#000000]        playFile(currTrack.sample_id);[/COLOR]
[COLOR=#000000]      }[/COLOR]
[COLOR=#000000]    }[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void playFile(uint8_t num)[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  if (num == 0) {[/COLOR]
[COLOR=#000000]    rraw_a1.play("BD_FIXED.wav", sd1);[/COLOR]
[COLOR=#000000]  } else if (num == 1) {[/COLOR]
[COLOR=#000000]    rraw_a2.play("SD_FIXED.wav", sd1);[/COLOR]
[COLOR=#000000]  } else if (num == 2) {[/COLOR]
[COLOR=#000000]    rraw_a3.play("CH_FIXED.wav", sd1);[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void stopFiles()[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  rraw_a1.stop();[/COLOR]
[COLOR=#000000]  rraw_a2.stop();[/COLOR]
[COLOR=#000000]  rraw_a3.stop();[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  delay(10);[/COLOR]
[COLOR=#000000]}[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]void fillOutSequencer(void)[/COLOR]
[COLOR=#000000]{[/COLOR]
[COLOR=#000000]  // fill out top level stuff[/COLOR]
[COLOR=#000000]  _seq_state.current_step = 1;[/COLOR]
[COLOR=#000000]  _seq_state.playback_state = STOPPED;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  for (int i = 0; i< 16; i++) {[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[0].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[1].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[2].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[3].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[4].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[5].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[6].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[7].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[8].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[9].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[10].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[11].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[12].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[13].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]    _seq_state.seq.banks[0].patterns[0].tracks[14].steps[i].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  }[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].track_type = TRACK_TYPE::SAMPLE;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].sample_id = 0;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[0].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[1].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[2].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[3].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[4].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[5].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[6].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[7].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[8].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[9].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[10].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[11].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[12].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[13].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[14].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[0].steps[15].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].track_type = TRACK_TYPE::SAMPLE;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].sample_id = 1;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[0].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[1].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[2].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[3].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[4].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[5].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[6].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[7].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[8].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[9].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[10].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[11].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[12].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[13].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[14].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[1].steps[15].state = TRACK_STEP_STATE::OFF;[/COLOR]
[COLOR=#000000]
[/COLOR][COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].track_type = TRACK_TYPE::SAMPLE;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].sample_id = 2;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[0].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[1].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[2].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[3].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[4].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[5].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[6].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[7].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[8].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[9].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[10].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[11].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[12].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[13].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[14].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]  _seq_state.seq.banks[0].patterns[0].tracks[2].steps[15].state = TRACK_STEP_STATE::ON;[/COLOR]
[COLOR=#000000]}[/COLOR]
[/FONT][/COLOR]
 
Ah, right, I think I see the root cause of the issue. The uclock library is calling the audio functions from an interrupt, which is super bad news for SD card access when there are also access calls in user code … which there are, because the buffer reloads are done in yield() when your loop() exits.

I’ll have a think and see if I can figure a way around it while still using uclock as intended.

Edit: OK, your loop() isn’t relevant, but delay() also calls yield()…
 
Ah, right, I think I see the root cause of the issue. The uclock library is calling the audio functions from an interrupt, which is super bad news for SD card access when there are also access calls in user code … which there are, because the buffer reloads are done in yield() when your loop() exits.

I’ll have a think and see if I can figure a way around it while still using uclock as intended.

Edit: OK, your loop() isn’t relevant, but delay() also calls yield()…

Thanks. If it's more so just a conflict with the way the uClock library works, I can try and find a different clock solution that doesn't rely (as much) on interrupts.

Another thought is just that I think it would make sense to cache these smaller mono samples in memory so I don't have to stream from the SD card so many times for such small files. Or like, if when selecting a pattern, I could pre-cache the set of samples from the SD card for that specific pattern at a lower interrupt priority?

For longer samples though (minutes long maybe), especially stereo WAV files, being cached in RAM/PSRAM doesn't seem so feasible. For those sorts of samples, I think streaming directly from the SD card would make the most sense, but I'm not sure how it could work with the current clock library I'm using.
 
There’s a facility to preload the first part of a file, to improve latency - take a look at the updated Design Tool info pane for AudioPlayWAVstereo … you’ll need to scroll to near the end. This reduces the problems but won’t eliminate them, I believe.

I think I can fix the issue to some extent, but working with Teensyduino 1.59 beta 3 I’m having issues with the IntervalTimer callback function apparently not knowing it’s in an ISR.

Stay tuned…

EDIT: in further news .. I'm a numpty. Right, now I can try and do some real fixing!
 
Last edited:
OK, so I've made some changes on a new branch, which you can find at https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD-interrupt-safe-01. The changes only affect playback, so please don't try to record (yet) by making calls from uclock; it might work from your sketch, though...

I've given it a bit of testing and it's not crashing, which it was before. You will possibly notice, if you measure it, that playback latency is a bit worse. This is because the code has to wait for your sketch to yield() before playback can even be started, then for the file to be loaded, and then for the next audio update. You can mitigate it a bit by sprinkling yield() calls throughout your application, or letting loop() exit frequently, but if you have e.g. a display update that takes 10ms to run, then you will get the occasional ragged sample start. All that will largely go away if you use the preload facility I noted in post #138.

Please give this a go and let me know if it seems OK at first glance. If I then don't get any adverse comments for a while, I'll assume it's OK and make similar changes to the AudioRecordWAVbuffered objects.
 
OK, so I've made some changes on a new branch, which you can find at https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD-interrupt-safe-01. The changes only affect playback, so please don't try to record (yet) by making calls from uclock; it might work from your sketch, though...

I've given it a bit of testing and it's not crashing, which it was before. You will possibly notice, if you measure it, that playback latency is a bit worse. This is because the code has to wait for your sketch to yield() before playback can even be started, then for the file to be loaded, and then for the next audio update. You can mitigate it a bit by sprinkling yield() calls throughout your application, or letting loop() exit frequently, but if you have e.g. a display update that takes 10ms to run, then you will get the occasional ragged sample start. All that will largely go away if you use the preload facility I noted in post #138.

Please give this a go and let me know if it seems OK at first glance. If I then don't get any adverse comments for a while, I'll assume it's OK and make similar changes to the AudioRecordWAVbuffered objects.

Wow, great, thanks! So, the timing is spot on now, no locking or lag with respect to the clock timing, I've experienced no crashes. However, one thing I now notice is that sometimes a sample may not play here or there, like one of the files may fail to play occasionally. It seems to be fine though at a lower BPM (e.g., 120), I don't experience any missing hits. I'm wondering if maybe doing more yield()'s or preloading like you mentioned might solve for that, though.
 
"Decidedly we make progress, mon cher Hastings!"

Should I be able to reproduce the failure to play using your code from post #135? Any particular bpm that seems particularly prone to this? Could it be an attempt to start playing just before / after a sample is ending? I probably don't need your actual samples (though you may be able to zip them and attach them to a post here), but it would be good to know exactly how long each is, so I can whip up something similar.
 
OK, I've pushed another set of revisions up to GitHub which seem to fix the issues with both direct playing from the sample file, and when using preload objects to get better latency. So far no crashes and no obviously missing samples.

Let me know how you get on :)
 
OK, I've pushed another set of revisions up to GitHub which seem to fix the issues with both direct playing from the sample file, and when using preload objects to get better latency. So far no crashes and no obviously missing samples.

Let me know how you get on :)

Works great!! Thanks a bunch!
 
Another question, are you planning on having your audio library incorporate variable playback, like what's supported by the teensy-variable-playback library? It would be great to be able to play buffered stereo WAV files and do some things like vary the play speed, lower the bit rate, loop, etc. Thanks again!
 
No plans to do that, no. I did do some work on porting my pre-loading approach to Nic's teensy-variable-playback, which you can take a look at in my feature branch here. This was based on a request for the benefit of the MicroDexed effort, but they didn't seem to be able to get it to work or produce an Arduino IDE based demonstration of the issues they were encountering, so it's been parked for quite a while, and I don't feel much motivation to go back to it! As it currently stands, I'm pretty sure it'll have the same issues with uClock's IntervalTimer callback that my code did...
 
Hi h4yn0nnym0u5e, I was trying to private message you but your private message inbox appears to be full and I'm unable to send you a private message. I was wondering if you'd be willing to consult with me on my project. If you'd like, can you maybe message me an email address I can contact you at?
 
Hi there. Unfortunately, as I already work for a consulting firm, my job contract prohibits me from doing any other consulting work... Happy to support any questions you have on the forum, of course. Bug fixes will either happen (or get documented as a feature :) ), and I will consider adding features if they don't stray too far from 1x playback / record.
 
Back
Top