Write from SD Card to RAM

Dionysus

Active member
Apologies for writing a second thread immediately after my first one! I'm trying to make a drum machine, and am currently playing the files off an SD card. Everything goes well for up to three sound files, but playing four or more eventually causes everything to freeze. Increasing the delay time in my kludgey playSample() function helps, but only so much (in the code below, for example, there's a 250ms delay, and I can play four files without dying. But 250 ms is a lot.). Also, I'm aware that this is probably not the right way to access AudioPlaySdWav objects from an object, but I've written another forum thread asking for help with that aspent of this.

Is there an easy way to read the wav file from the SD card and immediately write it to RAM, and then access is from there each time, instead of loading it from the card every time? It seems like that should be possible, but I haven't been able to find anything like that. I suspect that I'm either looking in completely the wrong place, or else I have fundamentally misunderstood something that make it really difficult. Or both!

Thank you all!

Code:
///////////////////////////////////
// copy the Design Tool code here
///////////////////////////////////
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

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

// GUItool: begin automatically generated code
AudioPlaySdWav           playSdWav5;     //xy=151,254
AudioPlaySdWav           playSdWav8;     //xy=155,389
AudioPlaySdWav           playSdWav6;     //xy=161,302
AudioPlaySdWav           playSdWav1;     //xy=162,63
AudioPlaySdWav           playSdWav3;     //xy=169,162
AudioPlaySdWav           playSdWav2;     //xy=173,112
AudioPlaySdWav           playSdWav7;     //xy=180,342
AudioPlaySdWav           playSdWav4;     //xy=181,202
AudioMixer4              mixer4;         //xy=355,332
AudioMixer4              mixer3;         //xy=375,270
AudioMixer4              mixer1;         //xy=397,150
AudioMixer4              mixer2;         //xy=404,209
AudioMixer4              mixer5;         //xy=611,206
AudioAnalyzePeak         peak3;          //xy=690,427
AudioAnalyzePeak         peak1;          //xy=693,312
AudioAnalyzePeak         peak2;          //xy=716,376
AudioAnalyzePeak         peak4;          //xy=720,480
AudioOutputI2S           i2s1;           //xy=878,201
AudioConnection          patchCord1(playSdWav5, 0, mixer3, 0);
AudioConnection          patchCord2(playSdWav5, 1, mixer3, 1);
AudioConnection          patchCord3(playSdWav8, 0, mixer4, 2);
AudioConnection          patchCord4(playSdWav8, 1, mixer4, 3);
AudioConnection          patchCord5(playSdWav6, 0, mixer3, 2);
AudioConnection          patchCord6(playSdWav6, 1, mixer3, 3);
AudioConnection          patchCord7(playSdWav1, 0, mixer1, 0);
AudioConnection          patchCord8(playSdWav1, 1, mixer1, 1);
AudioConnection          patchCord9(playSdWav3, 0, mixer2, 0);
AudioConnection          patchCord10(playSdWav3, 1, mixer2, 1);
AudioConnection          patchCord11(playSdWav2, 0, mixer1, 2);
AudioConnection          patchCord12(playSdWav2, 1, mixer1, 3);
AudioConnection          patchCord13(playSdWav7, 0, mixer4, 0);
AudioConnection          patchCord14(playSdWav7, 1, mixer4, 1);
AudioConnection          patchCord15(playSdWav4, 0, mixer2, 2);
AudioConnection          patchCord16(playSdWav4, 1, mixer2, 3);
AudioConnection          patchCord17(mixer4, 0, mixer5, 3);
AudioConnection          patchCord18(mixer4, peak4);
AudioConnection          patchCord19(mixer3, 0, mixer5, 2);
AudioConnection          patchCord20(mixer3, peak3);
AudioConnection          patchCord21(mixer1, 0, mixer5, 0);
AudioConnection          patchCord22(mixer1, peak1);
AudioConnection          patchCord23(mixer2, 0, mixer5, 1);
AudioConnection          patchCord24(mixer2, peak2);
AudioConnection          patchCord25(mixer5, 0, i2s1, 0);
AudioConnection          patchCord26(mixer5, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=346,435
// GUItool: end automatically generated code
// Use these with the Teensy Audio Shield
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

int bpm=120;
int sampleObjects=0;

#define WHOLENOTE 4
#define HALFNOTE  2
#define QUARTERNOTE 1
#define EIGHTHNOTE .5
#define SIXTEENTHNOTE .25



class Sample{
  float noteType;       // i.e., what percentage of bpm is this note (e.g., a quarternote is 1 bpm, an eigthnote is .5)
  int oldTime=0;
  const char *sampleName;
  File currentDir;
  File currentSample;
  char fullSamplePath[50];
  int id;
  elapsedMillis change;
  
   public:
    void setup(const char *dn,const char *sn,float n, int beats, int rests){
      sampleName=sn;
      noteType=n;
      currentDir=loadDir(dn);
      currentSample=loadSample(sn);
      sampleObjects++;
      id=sampleObjects;
    }  
  void playSample(int id, char *fullSamplePath){
    switch(id){
      case 1:  
        playSdWav1.play(fullSamplePath);
        break;
      case 2:  
        playSdWav2.play(fullSamplePath);
        break;
      case 3:  
        playSdWav3.play(fullSamplePath);
        break;
      case 4:  
        playSdWav4.play(fullSamplePath);
        break;
      case 5:  
        playSdWav5.play(fullSamplePath);
        break;
      case 6:  
        playSdWav6.play(fullSamplePath);
        break;
      case 7:  
        playSdWav7.play(fullSamplePath);
        break;
      case 8:  
        playSdWav8.play(fullSamplePath);
        break;
    }
    delay(250);
  }
    /************************************************************************************************************************************
     *                                                                                                                    loadDir()
    ************************************************************************************************************************************/
    
    File loadDir(const char* dir){
      Serial.println(dir);
      File myFile;
      myFile=SD.open(dir);
      Serial.print("Directory: ");Serial.println(myFile.name());
      return myFile;
    
    }
    /************************************************************************************************************************************
     *                                                                                                                    loadSample()
    ************************************************************************************************************************************/
    
    File loadSample(const char* sampleName){
      File myFile;
      //char fullSamplePath[50];
      sprintf(fullSamplePath, "/%s/%s",currentDir.name(),sampleName);
          Serial.print("Name 1: ");Serial.println(fullSamplePath);
          
      myFile=SD.open(fullSamplePath);
      if(! myFile){
        Serial.println("Can't open file");
        return myFile;
      }
      else{
        return myFile;
      }
    }
    //*******************************************************************************************************************
    //                                                                                               Main Sample Loop
    //*******************************************************************************************************************
    void loop(){    
       if(change>=(noteType*(60000/bpm))){
        // e.g., an eighthnote at 120 bpm should play every 250 milliseconds, which is 4 times a second or 240 times a minute
        change=0;
        playSample(id,fullSamplePath);
      }
    }
};
Sample sample1;
Sample sample2;
Sample sample3;
Sample sample4;
Sample sample5;
Sample sample6;
Sample sample7;
Sample sample8;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  AudioMemory(8);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
  delay(1000);
  float vol=.15;
  mixer1.gain(0, vol);
  mixer1.gain(1, vol);
  mixer2.gain(0, vol);
  mixer2.gain(1, vol);
  mixer3.gain(0, vol);
  mixer3.gain(1, vol);
  mixer4.gain(0, vol);
  mixer4.gain(1, vol);
  mixer5.gain(0, .5);
  mixer5.gain(1, .5);

  
  sample1.setup("D_808", "classic.wav", SIXTEENTHNOTE,1,2);
  sample2.setup("D_808", "break.wav", EIGHTHNOTE,1,2);
  sample3.setup("D_808", "bitch.wav", QUARTERNOTE,2,1);  
  sample4.setup("D_organc", "clakclak.wav", WHOLENOTE,2,2);
  sample5.setup("D_organc", "lanaDah.wav", QUARTERNOTE,7,3);
  sample6.setup("D_organc", "lanaDah2.wav", QUARTERNOTE,3,7);
  sample7.setup("D_organc", "meaty.wav", QUARTERNOTE,1,7);
}

void loop() {
  sample1.loop();
  sample2.loop();
  sample3.loop();
  sample4.loop();
 // sample5.loop();
//sample6.loop();
//  sample7.loop();  
}
 
Oh, I should have added that this is on a delicious Teensy 4.0, using the Audio Sheild Rev D!

One other question I had—assuming that it is freezing because I'm demanding that it load too many samples from the SD card at the same time, why specifically is that causing things to freeze, as opposed to just skipping or delaying the file read? And is there a way to recover from it? Thanks again!
 
It occurs to me that an easier way to phrase this would be to ask if there's a simple way to implement wav2sketch on the fly. So if just reads in the data from the SD card, sticks it in a giant const unsigned int or whatever, and then I can use playMem().

I haven't found anything on the forums about this, just a lot of posts that imply that installing flash memory is the solution here. Which is something I can look into, but a lot of those poses were for older versions of Teensy. I'm hoping that the 4.0 is magical enough that I wouldn't need anything else. :)
 
Hi,
If you use a flash memory chip soldered onto the Audio board (i.e. using SPI) then, from experience, you should not have a problem playing enough simultaneous samples for a drum machine using the standard Audio library code. The main question is how large your sound files are - that will determine how straightforward things will be for you. Do you want to use layered sounds?
When getting an idea of how much memory you need, start with crash samples...
 
Last edited:
Answering your original question, I would advise you to first convert the sound files to RAW format (you could use Audacity, for example). RAW files don't have headers so it will be easier for you to read the data you need from the SD card and store it in an array. For simplicity, you could use a single array to store the samples and separate arrays to store where each sample starts and their lengths. Then create a modified version of the AudioPlayMemory class to play a sample from that array.
But, before you try any of that, check how much memory you would need and whether that will work. If you need more memory, or if you want to avoid all of that, add a flash chip to the Audio board.
 
Yes, layering multiple sounds is the goal here—polyphonic drums. In the ideal world I'm sure eight wave files playing all at once is more than enough—I doubt my ears could differentiate that many sounds! Realistically, I had hoped to play four or five sound files at the same time.

The filesize is clearly a problem. I can play little high hats at 17kb all day, but once the kicks come in, a 345kb wav file, that's when it starts to lock up. But why?

Am I right in understanding that the longer files require more time to read from the SD card, so it's more likely that there will be a clash where it tries to read more than X files at the same time, and that this sort of clash will inevitably cause the system to freeze up? (I understand that I can increase X by getting a better SD card, but there will always be some limit). I'd like to I understand why the system freezes (as opposed to, for example, just ignoring me when I tell it to access six files at once) so I might be able to figure out how to prevent it, even if I can't overcome the poyphonic limit.

And I was also hoping (possibly in vain!) that there was a way to just stick all that sound data into RAM and only use the SD card very rarely. 345kb is a lot, for sure, but it seems like the Teensy 4 would have room (as with wav2sketch)? Assuming I'm careful with what other data I'm storing.

I can come up with some workarounds—I might enforce a hard limit of how many files are being played at the same time, for example. And I realized that I could use FM synthesis to make kicks, as well, which I didn't even realize was an option when I started work on this! It also sounds like buying flash memory is the way to go—I'm just a little nervous about the surface mount soldering, but I can learn. Anyway, I'm still curious about the details.

Thank you all!
 
Hi,
A SOIC-8 chip is quite easy to solder - but I would recommend getting a magnifier if you don't have one. I can do it despite shaky hands and bad eyesight!

When I said layered sounds, I meant having different samples for different ranges of velocities (e.g. a different sound for a "soft" snare hit than for a "hard" snare hit). That obviously has a big effect on the memory requirements.

Depending on the sound quality you are looking for, you may find that you can resample sounds at 22050 Hz - or even some at 11025 Hz. You can try it in Audacity and compare the results.

A lot of the freely available drum samples may be longer than you need - it may be worthwhile trimming off the ends.
SD cards use NAND flash, but you need NOR flash if you want to play many samples simultaneously. There may be little speed difference between them if you are reading data sequentially, but the difference is significant when you are randomly accessing data. I assume your problem with the longer samples is simply down to the number playing simultaneously.

I would advise you to change your code so the AudioPlaySdWav objects are in an array. Then, when you need to start a new sound, you can loop through them looking for one that has stopped.

If you want to be able to choke cymbals, then add an AudioEffectEnvelope after each player. With the right settings, it will work well.

Good luck with it.
 
Last edited:
Hey Dionysus!

Did you ever manage to solve how to transfer files from SD to ram on the fly? I am currently working on a similar project (albeit with T4.1 and PSRAM chip) and would really appreciate any guidance!

Best,
Miro
 
Hey Dionysus!

Did you ever manage to solve how to transfer files from SD to ram on the fly? I am currently working on a similar project (albeit with T4.1 and PSRAM chip) and would really appreciate any guidance!

Best,
Miro

Nope! I'm excited to read Paul's response because maybe it will work now. For me, it was just too slow to load the samples in realtime. I ended up storing all of my samples on a flash chip (W25Q126JVSIQ-ND memory chip from digi-key) and loading them from there. It was incredibly fiddly to get the chip working along with everything else (the full saga is in this thread, I think, with my summary here.

My full code is on github, and the loading stuff is on line 293 here.

Good luck!
 
Please give 1.54-beta5 a try. We've switched to SdFat where SD is just a thin wrapper. I also put some improvements into AudioPlaySdWav.

https://forum.pjrc.com/threads/64592-Teensyduino-1-54-Beta-5

Maybe these changes will help?

Hi Paul!

Thanks for the reply. I am currently using the newest beta with T4.1.

Limitations in performance come for me when trying to play eight samples at the same time, triggering them with buttons. I have tried both playSdRaw and playSdWav. With these, AudioProcessorUsageMax easily peaks at well over hundred, around 130-150. This causes the whole thing to freeze for a while.

For comparison, I have ran my audio samples through the wav2sketch program and played them with AudioPlayMemory. With this method, AudioProcessorUsageMax stays around 5 even when triggering all the files at the same time. Unfortunately I cannot use this method as the Teensy doesn't have enough memory for all my samples.

This lead me to buying the PSRAM chip. Maybe that would be a good middle ground between the two? By storing the audio files on an SD card and loading them into PSRAM only when needed I could have the performance of the AudioPlayMemory and the large storage of the SD?

Best,
Miro
 
Nope! I'm excited to read Paul's response because maybe it will work now. For me, it was just too slow to load the samples in realtime. I ended up storing all of my samples on a flash chip (W25Q126JVSIQ-ND memory chip from digi-key) and loading them from there. It was incredibly fiddly to get the chip working along with everything else (the full saga is in this thread, I think, with my summary here.

My full code is on github, and the loading stuff is on line 293 here.

Good luck!


Thanks, these will be helpful!
 
Hey Miro, did you get any further with this? It's pretty much what I'm looking to do now as well...

Looks like h4yn0nnym0u5e already answered, I would second using his library for these purposes. I have been meaning to try it for the longest time but I've had too much other stuff going and have not had yet time to test it out. I believe that it will be perfect for my use-case at least.
 
Back
Top