Find "unplaying" playSdWav-Object or create playSdWav Object on the fly

Status
Not open for further replies.
Hi,

i use a teensy 3.6 with Audio Shield for my Standalone Drum Module Project.
Via MIDI-In the Teensy receives several Midi-notes from a connected Roland e-Drum.
Depending on the received Midi Note (e.g. 36 for Bassdrum) the corresponding WAV-File (saved on SD-Card) is to be played,
In the next steps i also want to differ which velocity the note has and put Wav-Files of different velocities on the SD-card for each Note, e.g. 36_1.wav, 36_2.wav....
But this is not the current problem.

Everything described above works fine. I hit the Bassdrum and the file "36.wav" is played.
But if i hit the bassdrum and the snaredrum at same time, the playSdWav-Object is busy and so only the Bassdrum is played.

My first try for remedy was to create on playSdWav-Object for each note and make something like

Code:
switch(note){
  case 36:
  playSdWav1.play(filename);
  break;

  case 38:
  playSdWav2.play(filename);
  break;

  case 47:
  playSdWav3.play(filename);
  break;
}

But this solution seems not to be ideal for this thing.
What if tommorrow i put a sample with a note that is not within the switch-clause, e.g. the note 123?
The again i have to create a new playSdWav4-Object and have to edit the switch-clause.

So one idea is to create a few playSdWav-Objects (maybe 8 or 10...how much cymbals and toms will the drummer hit in series? And each cymbal has long sustain means corresponding playSdWav-Object is busy for long time...) and then check, which is not busy, e.g. this way:

Code:
// Find "unplaying" playSDWav Object
bool searchSD=true;
while(searchSD) {
  if (!playSdWav1.isPlaying()) {
    playSdWav1.play(filename);
    searchSD=false;
  } else if (!playSdWav2.isPlaying()) {
    playSdWav2.play(filename);
    searchSD=false;
  } else if (!playSdWav3.isPlaying()){
    playSdWav3.play(filename);
    searchSD=false;
  } else {
    // repeat while
  }
}

Another (i think better solution) would be something like this (only in words, not in code):

1.note is received
2.create playSDWav Object and connect to mixer
3.play sound
4.delete playSDWav Object

But also this seems not as a good solution.

Do you have better ideas?
Every tip is welcome!

Daniel
 
Hi there,

i'm a little bit sad that no answers have been posted.
But i try again to get feedback /help ;-)

No i have tried another way to get all the Drum samples played.
i have created a plySdWav Object for each midi note i want to play a sample from.
The code works ok, but only if the drum pads are hit slowly.
If i hit a pad fast and multiple times in series i get a strange "beep"-sound and sometimes, no sample is played or the sample is played with a long delay.
So it seems, that i have a performance problem.
All samples are in 16 bit / 44100 kHz Wav-Format (uncompressed).

And here is the actual code:

Code:
// Includes 
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <MIDI.h>

// Used for Sample Management
File root;
const int      MAX_SAMPLES = 256;
unsigned short veloMap[ MAX_SAMPLES ];
unsigned short keyMap[ MAX_SAMPLES ];
int            samplenum;
unsigned short veloCount;
unsigned short oldnoteval;
size_t         count     = 0;
const size_t   MAX_CHARS = 64;
char           newfile[ MAX_CHARS ];
unsigned short veloadd;
int drumset=1;

// Midi-Instance
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

// GUItool: begin automatically generated code
AudioPlaySdWav           Kick;     //xy=55,48
AudioPlaySdWav           HiTom;     //xy=55,209
AudioPlaySdWav           MidTom;     //xy=55,261
AudioPlaySdWav           SD_Rim;     //xy=56,162
AudioPlaySdWav           LowTom;     //xy=58,313
AudioPlaySdWav           SD_Head;     //xy=60.5,110
AudioPlaySdWav           Crash_L;    //xy=59,578
AudioPlaySdWav           Ride_Bell;    //xy=62,538
AudioPlaySdWav           HiHat_Foot;     //xy=66,457
AudioPlaySdWav           HiHat_Open;     //xy=69,364
AudioPlaySdWav           Ride_Edge;    //xy=69,499
AudioPlaySdWav           HiHat_Closed;     //xy=73.5,413
AudioPlaySdWav           Crash_R;    //xy=76,616
AudioMixer4              Mono_L1;         //xy=324,39
AudioMixer4              Mono_L2;         //xy=324,112
AudioMixer4              Mono_R2;         //xy=323,395
AudioMixer4              Mono_L3;         //xy=325,184
AudioMixer4              Mono_R3;         //xy=324,465
AudioMixer4              Mono_L4;         //xy=325,254
AudioMixer4              Mono_R1;         //xy=325,326
AudioMixer4              Mono_R4;        //xy=325,537
AudioMixer4              Stereo_L;         //xy=509,184
AudioMixer4              Stereo_R;         //xy=512,325
AudioOutputI2S           i2s1;           //xy=690,250
AudioConnection          patchCord1(Kick, 0, Mono_L1, 0);
AudioConnection          patchCord2(Kick, 1, Mono_R1, 0);
AudioConnection          patchCord3(HiTom, 0, Mono_L1, 3);
AudioConnection          patchCord4(HiTom, 1, Mono_R1, 3);
AudioConnection          patchCord5(MidTom, 0, Mono_L2, 0);
AudioConnection          patchCord6(MidTom, 1, Mono_R2, 0);
AudioConnection          patchCord7(SD_Rim, 0, Mono_L1, 2);
AudioConnection          patchCord8(SD_Rim, 1, Mono_R1, 2);
AudioConnection          patchCord9(LowTom, 0, Mono_L2, 1);
AudioConnection          patchCord10(LowTom, 1, Mono_R2, 1);
AudioConnection          patchCord11(SD_Head, 0, Mono_L1, 1);
AudioConnection          patchCord12(SD_Head, 1, Mono_R1, 1);
AudioConnection          patchCord13(Crash_L, 0, Mono_L3, 3);
AudioConnection          patchCord14(Crash_L, 1, Mono_R3, 3);
AudioConnection          patchCord15(Ride_Bell, 0, Mono_L3, 2);
AudioConnection          patchCord16(Ride_Bell, 1, Mono_R3, 2);
AudioConnection          patchCord17(HiHat_Foot, 0, Mono_L3, 0);
AudioConnection          patchCord18(HiHat_Foot, 1, Mono_R3, 0);
AudioConnection          patchCord19(HiHat_Open, 0, Mono_L2, 2);
AudioConnection          patchCord20(HiHat_Open, 1, Mono_R2, 2);
AudioConnection          patchCord21(Ride_Edge, 0, Mono_L3, 1);
AudioConnection          patchCord22(Ride_Edge, 1, Mono_R3, 1);
AudioConnection          patchCord23(HiHat_Closed, 0, Mono_L2, 3);
AudioConnection          patchCord24(HiHat_Closed, 1, Mono_R2, 3);
AudioConnection          patchCord25(Crash_R, 0, Mono_L4, 0);
AudioConnection          patchCord26(Crash_R, 1, Mono_R4, 0);
AudioConnection          patchCord27(Mono_L1, 0, Stereo_L, 0);
AudioConnection          patchCord28(Mono_L2, 0, Stereo_L, 1);
AudioConnection          patchCord29(Mono_R2, 0, Stereo_R, 1);
AudioConnection          patchCord30(Mono_L3, 0, Stereo_L, 2);
AudioConnection          patchCord31(Mono_R3, 0, Stereo_R, 2);
AudioConnection          patchCord32(Mono_L4, 0, Stereo_L, 3);
AudioConnection          patchCord33(Mono_R1, 0, Stereo_R, 0);
AudioConnection          patchCord34(Mono_R4, 0, Stereo_R, 3);
AudioConnection          patchCord35(Stereo_L, 0, i2s1, 0);
AudioConnection          patchCord36(Stereo_R, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=631,492
// GUItool: end automatically generated code



// SD-Card connection
#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);
  Serial.begin(57600);
  AudioMemory(50); // First question: How to determine, which setting is required??
  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);
    }
  }
  // Gain Settings, to be adjustable in future by sliders/potis
  Mono_L1.gain(0, 0.5); // Kick L
  Mono_R1.gain(0, 0.5); // Kick R
  Mono_L1.gain(1, 0.5); // Snare Head L
  Mono_R1.gain(1, 0.5); // Snare Head R
  Mono_L1.gain(2, 0.5); // Snare Rim L
  Mono_R1.gain(2, 0.5); // Snare Rim R
  Mono_L1.gain(3, 0.5); // HiTom L
  Mono_R1.gain(3, 0); // HiTom R
  Mono_L2.gain(0, 0.5); // MidTom L
  Mono_R2.gain(0, 0.5); // MidTom R
  Mono_L2.gain(1, 0); // LowTom L
  Mono_R2.gain(1, 0.5); // LowTom R
  Mono_L2.gain(2, 0.5); // HiHat_Open L
  Mono_R2.gain(2, 0); // HiHat_Open R
  Mono_L2.gain(3, 0.5); // HiHat_Closed L
  Mono_R2.gain(3, 0); // HiHat_Closed R
  Mono_L3.gain(0, 0.5); // HiHat Foot L
  Mono_R3.gain(0, 0); // HiHat Foot R
  Mono_L3.gain(1, 0); // Ride Edge L
  Mono_R3.gain(1, 0.5); // Ride Edge R
  Mono_L3.gain(2, 0); // Ride Bell L
  Mono_R3.gain(2, 0.5); // Ride Bell R
  Mono_L3.gain(3, 0.5); // Crash_L L
  Mono_R3.gain(3, 0); // Crash_L R
  Mono_L4.gain(0, 0); // Crash_R L
  Mono_R4.gain(0, 0.5); // Crash_R L  

  Stereo_L.gain(0, 1.5);
  Stereo_L.gain(1, 1.5);
  Stereo_R.gain(0, 1.5);
  Stereo_R.gain(1, 1.5);
  
  delay(1000);
  // Check number of samples of each note (used for velocity control later)
  char dirname[16] = {'\0'}; //16 characters in the filename (or however long you want to make it)
  sprintf(dirname,"/%d/",drumset);
  Serial.println(dirname);
  root = SD.open(dirname);
  loadSamples(root);
}

unsigned long t=0;

void loop() {
  int type, note, velocity, channel, d1, d2;
  if (MIDI.read()) {                    // Is there a MIDI message incoming ?
    byte type = MIDI.getType();
    switch (type) {
      case midi::NoteOn:
        note = MIDI.getData1();
        velocity = MIDI.getData2();
        channel = MIDI.getChannel();
        if (velocity > 0) {
          // Map Velocity depending on the number of available samples
          short velomapped=map(velocity, 0, 127, 0, veloMap[note]);
          velomapped=veloMap[note]-velomapped+1;
          char   filename[24];
          size_t maxChars = sizeof(filename);
          formatFilename( filename, maxChars, drumset, velomapped, note );
          Serial.println(filename);

          switch(note){
            case 33:
              HiHat_Open.play(filename);
              break;
            case 36:
              Kick.play(filename);
              break;
            case 38:
              SD_Head.play(filename);
              break;
            case 40:
              SD_Rim.play(filename);
              break;
            case 43:
              LowTom.play(filename);
              break;
            case 44:
              HiHat_Foot.play(filename);
              break;
            case 45:
              MidTom.play(filename);
              break;
            case 47:
              HiTom.play(filename);
              break;
            case 48:
              HiHat_Closed.play(filename);
              break;
            case 51:
              Ride_Edge.play(filename);
              break;
            case 54:
              Ride_Bell.play(filename);
              break;
            case 55:
              Crash_L.play(filename);
              break;
            case 57:
              Crash_R.play(filename);
              break;
          }

        } else {
          // Velocity is zero --> Nothing to do
        }
        break;
      case midi::NoteOff:
        note = MIDI.getData1();
        velocity = MIDI.getData2();
        channel = MIDI.getChannel();
        break;
      default:
        d1 = MIDI.getData1();
        d2 = MIDI.getData2();
    }
    t = millis();
  }
  if (millis() - t > 10000) {
    t += 10000;
  }

}

void loadSamples(File dir) {
  while(true) {
    File entry =  dir.openNextFile();
    if (! entry) {
     // no more files
     break;
    }
    char *newfile = entry.name();
    unsigned short noteval;
    unsigned short velocity;
    
    if (parseFilename( newfile, noteval, velocity )) {
      // Now we have noteval and velocity.  Update the veloMap.
      if (oldnoteval != noteval) {
        oldnoteval = noteval;
        veloCount  = 1;
        // Don't write past the end of the array
        if (samplenum < MAX_SAMPLES-1)  {
          veloMap[noteval]=veloCount;
        }
      } else {
        veloCount=veloCount+1;
        // Don't write past the end of the array
        if (samplenum < MAX_SAMPLES-1)  {
          veloMap[noteval]=veloCount;
        }
      }
    
    }
    entry.close();
  }
}




bool parseFilename( char *newfile, unsigned short & noteval, unsigned short & velocity )
{
  bool okFilename = false;
  noteval = atoi( newfile ); // uses consecutive digits, up to the '_'
  if (noteval > 0) {
    char *ptr = strstr( newfile, "_V" ); // find the separator
    if (ptr != nullptr) {
      // ptr now "points at" the underscore character
      ptr += 2;                 // step over the "_V" characters
      velocity   = atoi( ptr ); // use consecutive digits, up to the '.'
      okFilename = true;
    }
  }
  return okFilename;
} // parseFilename

void formatFilename
  (
    char        *filename,
    size_t       maxChars,
    unsigned int drumset,
    unsigned int velocity, 
    unsigned int note
  )
{
  // Write a filename of the form "/123/12_V09.WAV"

  char *ptr = filename;          // start writing at the beginning

  *ptr++ = '/';                  // set element 0 and advance ptr to element 1

  utoa( drumset, ptr, 10 );      // write drumset digits starting at element 1
  size_t len = strlen( ptr );    // see how many digits were written (max 5)
  ptr += len;                    // step past digits

  *ptr++ = '/';                  // append the next slash (overwrites NUL byte) and advance to next element

  utoa( note, ptr, 10 );     // write velocity digits
  len = strlen( ptr );           // see how many digits were written (max 5)
  ptr += len;                    // step past digits

  *ptr++ = '_';                  // append "_V" while
  *ptr++ = 'V';                  //   advancing pointer to next element(s)

  if (velocity < 10)
    *ptr++ = '0';                // append leading zero if necessary
  utoa( velocity, ptr, 10 );         // write note digits
  len = strlen( ptr );           // see how many digits were written (max 5)
  ptr += len;                    // step past digits

  strcpy_P( ptr, PSTR(".wav") ); // append extension and NUL terminator byte

} // formatFilename

Maybe you have ideas how to get better performance.
Is the "AudioMemory()" setting ok? First it was (8), now i tried (50) with no performance increasement.

Best regards

Daniel
 
Why all this focus on creating and deleting, and connecting and disconnecting the objects? The normal way is to just create them as global scope, statically allocated and leave them connected.
 
Oh, i have not seen, that you already answered.
What do you mean with "The normal way is to just create them as global scope, statically allocated and leave them connected."?
At beginning i make all connections and then leave them connected, don't i?
Or what do you mean with "create them as global scope".
My english is not so good, sorry ;-(
Can you make an example or directly comment in my code?

Best regards
Daniel
 
I've just noticed, that the YouTube Link didn't work ;-(
No the link works.

I hope you can help me to increase/optimize the performance of the sketch.
Have i made any mistakes? What about AudioMemory()? Which value to choose?

Thanx for your support.

Daniel
 
OK, i think the code is ok.
The Problem seems to be related with restarting a sample while the previouly started sample has not yet been played completely til end.
I've tested this behaviour in Steinberg WaveLab. When i play the Hi-Tom sample one time it sounds perfect.
But if i start playing the sample and restart playing while the sample has not yet been completely played a kind of interrupting sound is generated.
Do you have ideas, how this can be fixed?

In Theory, if e.g. i would have two playSdWav Objects for the HiTom, hit the Tom and hit it directly again, i could check, if playSdWav_1 Object is already playing the tom sample and use playSdWav_2 Object for the second hit.
But what if i hit 3 or 5 or 10 Times? Then i would need multiple playSdWav objects...
 
Another question: Would SDPlayRaw be better for my project? The samples are all 16bit 44100 khz Mono.
Or would AudioPlayMemory better? But as said i want multiple folders (drumset_1, drumset_2) which contain multiple samples with multiple velocities: Is that possible? Can i have multiple folders within the Teensy's built-in program memory? And how much samples (converted via wav2sketch) can be stored within the built-in program memory?
 
Yeah, that's a good idea. However, you may have problems playing as many files simultaneously from the SD card. I would try to use a flash chip instead.
 
Yeah, that's a good idea.
Which idea do you mean? SDPlayRaw or AudioPlayMemory?

The Problem seems to be related with restarting a sample while the previouly started sample has not yet been played completely til end.
[...]
Do you have ideas, how this can be fixed?

In Theory, if e.g. i would have two playSdWav Objects for the HiTom, hit the Tom and hit it directly again, i could check, if playSdWav_1 Object is already playing the tom sample and use playSdWav_2 Object for the second hit.
But what if i hit 3 or 5 or 10 Times? Then i would need multiple playSdWav objects...

Does anyone have ideas how this can be solved? Paul maybe? :eek:

Daniel
 
With playMem or playFlashRaw you can do something like this:

Pseudocode:
Code:
function playSound(filename) {

 if (not player1.isPlaying) {player1.play(filename); return;}
 if (not player2.isPlaying) {player2.play(filename); return;}
 if (not player3.isPlaying) {player3.play(filename); return;}
...
 if (not playerN.isPlaying) {playerN.play(filename); return;}
return
}

..means, I'd create N players ( i.e. playFlashRaw) and to play a sound, I'd call a function like above. It searches for the first "not playing" player and starts.... connect all with a mixer.
From SD, you'llget sooner or later problems because SD Cards do not like random accesses and are too slow.

 
Last edited:
Status
Not open for further replies.
Back
Top