WaveplayerEx

- 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.

Normally I would not even look at source code using an incompatible open source license. Especially for features I plan to eventually implement, just viewing how it was done can influence future work with really should be done independently when the license is incompatible.

So far I have only looked at the readme as shown on the repository on github, and the license header. I stopped when I saw you used GPL3.

Most of the features explained in the readme are things I have long wanted to implement in the audio library wav player. Whether I would use the code as implemented, I do not know yet, because I have not even looked at the code.
 
Well, I think that's called "deadlock". :)

Ok, I think the player is useful for others, so I'll change to licence for some time to MIT.

That's OK.
I'll change it back to GPL if you decide not to use it. But please drop a note.
Thank you.
 
Yes, I will work with it later this month.

3 other major audio contributions are also pending. Will probably spend several days to do all 4 together.

Please be patient. I need you to understand many other important tasks need to be done, including minor PCB design changes for 3 Teensy models due to chip shortages on the diodes and voltage regulators (we just recently managed to buy the alternate parts).
 
@Paul, I'm adding a "loop" feature. Please give me a few days.


Other question: Can we use the existing "Resampler.cpp" to adapt to different samplerates?
 
@Paul, I'm adding a "loop" feature. Please give me a few days.


Other question: Can we use the existing "Resampler.cpp" to adapt to different samplerates?

Ok... had too much time today...
Some new functions:

size_t position(); - returns the current sample number. Will increase in steps of 128 (or AUDIO_BLOCK_SAMPLES)
bool setPosition(size_t sample); works only in paused mode - sets the start position for pause(false).
void loop(bool enable); - loops the whole file. Call it for an already playing or paused file. Will loop endless, until you call stop() or pause()
void loop(size_t firstSample, size_t lastSample, uint16_t count); - number of first sample, last sample, count of repeats. If lastSample = 0 it uses the last sample in the file.
int loopCount(void);- returns the count of remaining loops.

The details are sometimes not as easy as it may sound... I will describe loop() in detail, later.
 
Ok, let's assume we use the Nums_7dot1_8_44100.wav file from the examples. There is a voice, counting from 1 to 8 (each number on a different channel - but this is not interesting here)

There is a gap of exactly one second between the numbers.

1.
Code:
playWav.play("Nums_7dot1_8_44100.wav");
Plays the whole file, one time.

2.
Code:
playWav.play("Nums_7dot1_8_44100.wav");
playWav.loop(true);
Repeats the whole file until your ears bleed.

3.
Code:
playWav.play("Nums_7dot1_8_44100.wav", true);
playWav.setPosition(44100 * 5);
playWav.pause(false);
Starts in paused mode, sets the position to second 5, then un-pauses.


4.
Code:
playWav.play("Nums_7dot1_8_44100.wav");
playWav.loop(0, 0, -1);
Same as 2.

5.
Code:
playWav.play("Nums_7dot1_8_44100.wav");
playWav.loop(0, 0, 0);
Does noot loop (loop-count is zero)

6.
Code:
playWav.play("Nums_7dot1_8_44100.wav");
playWav.loop(0, 0, -1);
delay(20000);
playWav.loop(0, 0, 0);
Like 5 - stopps looping after 20 seconds. The files plays to the end! Only the loop gets disabled.

7.
Now, the tricky parts:
Code:
playWav.play("Nums_7dot1_8_44100.wav");
for (int i=7; i>0; i--) {
    playWav.loop(44100 * i, 44100 * (i + 1), 1);
    while ( playWav.loopCount() > 0 ) {;}
}
Well.. you'll hear:
"1 2 3 4 5 6 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8"
- the while waits until the looping is finished.
- it does _not_ stop or pause then, instead it plays the file to the end. IF there is no new loop, pause or stop command.

8.
Code:
playWav.play("Nums_7dot1_8_44100.wav");
  delay(5000);
  playWav.loop(0, 44100 * 3, 2);
You'll hear:
1 2 3 4 1 2 1 2 1 2 3 4 5 6 7 8
-> it counts up to 4 because of the delay.
-> after that the loop gets startet. Teensy detects that it already playing behind the stop marker of the loop and rewinds to the first sample.

Important: It does not detect that immedeately. This happens when it trys to read the next block of data from them media.
If you need exact timing, you need to define the loop _before_ the stop marker is reached.
What means "before". Hm, that#s tricky. Rule of thumb: As early as possible. It has to happen before it reads the datablock where the end-sample is located.
The size of the datablocks is variable, and depends on the number playWav (and record) objects and the AUDIO_BLOCK_SIZE.
Just give it enough millisconds and try it out.
 
Last edited:
Ok, last addition for a while.
As Teensy's LittleFS is not threadsafe and, for NAND Flash, takes way too long to open a file - (I've seen > 12 millisconds!), this can be seen as workaround, too.

You don't need to rewind the files anymore :cool:
Play() does it now for you:

Play(file | filename [, bool paused] [, bool rewind]);
So, if you set rewind to true, it will rewind the file to the start when the end of the file is reached, and set pause = true.
It can be restarted very fast now (by calling pause(false); ) , without reading the header or calling Play() again.

Example for Littlefs-NAND (but works with any other filesystem, too:
Code:
// WAV file player example
// This example code is in the public domain.

#include <LittleFS.h>

LittleFS_QPINAND myfs;
uint64_t fTot, totSize1;

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

#include <play_wav.h>

// GUItool: begin automatically generated code
AudioPlayWav             playWav1;
AudioPlayWav             playWav2;
AudioPlayWav             playWav3;
AudioPlayWav             playWav4;
AudioPlayWav             playWav5;
AudioPlayWav             playWav6;
AudioPlayWav             playWav7;
AudioPlayWav             playWav8;
AudioMixer4              mixer1;
AudioMixer4              mixer2;
AudioOutputPT8211        audioOutput;
//AudioOutputI2S           audioOutput;
//AudioOutputMQS           audioOutput;
AudioConnection          patchCord1(playWav1, 0, mixer1, 0);
AudioConnection          patchCord2(playWav2, 0, mixer1, 1);
AudioConnection          patchCord3(playWav3, 0, mixer1, 2);
AudioConnection          patchCord4(playWav4, 0, mixer1, 3);
AudioConnection          patchCord5(playWav5, 0, mixer2, 0);
AudioConnection          patchCord6(playWav6, 0, mixer2, 1);
AudioConnection          patchCord7(playWav7, 0, mixer2, 2);
AudioConnection          patchCord8(playWav8, 0, mixer2, 3);
AudioConnection          patchCord9(mixer1, 0, audioOutput, 0);
AudioConnection          patchCord10(mixer2, 0, audioOutput, 1);

const int fileCnt = 6;
const char *filename[fileCnt] = {
  "102790__mhc__acoustic-open-hihat2.wav",
  "171104__dwsd__kick-gettinglaid.wav",
  "201159__kiddpark__cash-register.wav",
  "82583__kevoy__snare-drum-4.wav",
  "86334__zgump__tom-0104.wav",
  "86773__juskiddink__gong.wav"
};

AudioPlayWav *player[fileCnt] = {
  &playWav1, &playWav2, &playWav3, &playWav4,
  &playWav5, &playWav6
};

File file[fileCnt];

void setup() {
  Serial.begin(9600);
  if (CrashReport) {
    Serial.println(CrashReport);
    CrashReport.clear();
  }

  AudioMemory(50);
  Serial.println("LittleFS Test");
  if (!myfs.begin()) {
    Serial.println("Error starting qspidisk");
    while (1) ;
  }
  Serial.printf("TotalSize (Bytes): %d\n", myfs.totalSize());

  printDirectory();

  float gain = 1.0f / 4;
  for (int i = 0; i < 4; i++) {
    mixer1.gain(i, gain);
    mixer2.gain(i, gain);
  }

  //Open all files in paused mode, autorewind
  for (int i = 0; i < fileCnt; i++)
  {
    file[i] = myfs.open(filename[i]);
    if (!file[i])
    {
      Serial.printf("Could not open \"%s\"\n", filename[i]);
      while (1);
    }
    if (!player[i]->play(file[i], true, true))
    {
      Serial.printf("Could not start \"%s\"\n", filename[i]);
      while (1);
    }
  }

  AudioProcessorUsageMaxReset();
  AudioMemoryUsageMaxReset();
}


void loop() {
  static int c = 0;
  static unsigned long t = millis();
  unsigned long m;
  if ( (m = millis()) - t > 200 )
  {    
    t = m;
    if (++c >= fileCnt) c = 0;
    if (! player[c]->isPlaying())
    {
      player[c]->pause(false);
    }

    Serial.printf("Proc = %0.2f (%0.2f),  Mem = %d (%d)\n",
                  AudioProcessorUsage(), AudioProcessorUsageMax(),
                  AudioMemoryUsage(), AudioMemoryUsageMax());
    
    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();

  }

}

void printDirectory() {
  Serial.println("printDirectory\n--------------");
  printDirectory(myfs.open("/"), 0);
  Serial.println();
}


void printDirectory(File dir, int numTabs) {
  //dir.whoami();
  uint64_t fSize = 0;
  uint32_t dCnt = 0, fCnt = 0;
  if ( 0 == dir ) {
    Serial.printf( "\t>>>\t>>>>> No Dir\n" );
    return;
  }
  while (true) {
    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      //Serial.printf("\n %u dirs with %u files of Size %u Bytes\n", dCnt, fCnt, fSize);
      fTot += fCnt;
      totSize1 += fSize;
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }

    if (entry.isDirectory()) {
      Serial.print("DIR\t");
      dCnt++;
    } else {
      Serial.print("FILE\t");
      fCnt++;
      fSize += entry.size();
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println(" / ");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    //Serial.flush();
  }
}

For LittleFS, this means: Open() your files before any file gets played, and everything is ok.

Cpu usage is pretty ok for these 6 files from LittleFS/NAND (But I had expected way less - seek() seems to be pretty slow)
Code:
Proc = 0.36 (3.88),  Mem = 2 (5)
Proc = 3.82 (3.88),  Mem = 2 (5)
Proc = 3.88 (3.88),  Mem = 2 (5)
Proc = 0.42 (3.95),  Mem = 2 (6)
Proc = 0.36 (10.79),  Mem = 2 (6)
Proc = 3.82 (3.90),  Mem = 2 (5)
Proc = 0.36 (3.89),  Mem = 2 (5)
Proc = 3.76 (3.88),  Mem = 2 (5)
Proc = 0.23 (3.82),  Mem = 2 (4)
Proc = 0.29 (3.82),  Mem = 2 (4)
Proc = 0.30 (3.88),  Mem = 2 (5)
Proc = 0.30 (3.90),  Mem = 2 (5)
 
Last edited:
Yes, I will work with it later this month.

3 other major audio contributions are also pending. Will probably spend several days to do all 4 together.

Please be patient. I need you to understand many other important tasks need to be done, including minor PCB design changes for 3 Teensy models due to chip shortages on the diodes and voltage regulators (we just recently managed to buy the alternate parts).
I'm sorry to report that WaveplayerEx and my Dynamic Audio Objects are (currently) not playing well together, at least in my "torture test". Run this with the Serial Plotter to visualise memory use and audio engine activity:
Code:
// Simple dynamic WAV file player example
//
// Create and destroy tracks played from SD card

#define USE_BOESING_PLAYER

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

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

// GUItool: begin automatically generated code
AudioMixer4              mixerL;         //xy=733,522
AudioMixer4              mixerR;         //xy=733,606
AudioOutputI2S           i2s;       //xy=907,572
AudioConnection          patchCord1(mixerL, 0, i2s, 0);
AudioConnection          patchCord2(mixerR, 0, i2s, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=908,666
// GUItool: end automatically generated code


#define SDCARD_CS_PIN    BUILTIN_SDCARD

/*********************************************************************************/
void setStereo(AudioMixer4& left,AudioMixer4& right,int channel,float level,float pan)
{
   left.gain(channel,level*(pan-1)/-2.0f);
  right.gain(channel,level*(pan+1)/ 2.0f);
}
/*********************************************************************************/
extern unsigned long _heap_end;
uint32_t FreeMem(){ 
  char* p = (char*) malloc(10000); // size should be quite big, to avoid allocating fragment!
  free(p);
  return (char *)&_heap_end - p; 
}
/*********************************************************************************/

AudioPlaySdWav* tracks[4];
AudioConnection* cables[8];

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

  AudioMemory(50);

  while (!(SD.begin(SDCARD_CS_PIN))) {
      Serial.println("Unable to access the SD card");
      delay(500);
  }

  setStereo(mixerL,mixerR,0,0.5,-0.3);
  setStereo(mixerL,mixerR,1,0.5,+0.3);
  setStereo(mixerL,mixerR,2,0.5,-0.7);
  setStereo(mixerL,mixerR,3,0.5,+0.7);
  
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);

  // Although addMemoryForRead() affects all instances via a
  // static value, it isn't itself static: create a throwaway
  // object to allow us to make the setting...
  AudioPlayWav* tmpWav = new AudioPlayWav();
  tmpWav->addMemoryForRead(4);
  delete tmpWav;
  
  Serial.println("mem unused playing stopped paused");
}

uint32_t next;

char* waves[]={
  "sine110.wav",
  "sine220.wav",
  "sine330.wav",
  "sine440.wav",
  "sine550.wav",
  "sine660.wav"
};


// Start a wave file playing on a specific track
void playTrack(int trackNum,int waveNum)
{ 
  tracks[trackNum]->play(waves[waveNum],false);
}


void loop() {  

  if (millis() > next) // time to pick a new random action
  {
    uint32_t intvl = random(40,265); // next action is after interval ... 
    if (intvl > 250) // ... long interval: lengthen further
    {
      intvl = (intvl - 250)*250; 
    }
    next = millis() + intvl;

    // pick random action, track and wave file
    int trackNum = random(4);
    int waveNum=random(6);
    int action=random(9);

    // If we're going to interact with this track, it'd better exist...
    if (NULL == tracks[trackNum])
    {
      tracks[trackNum] = new AudioPlaySdWav();
      cables[trackNum*2+0] = new AudioConnection();
      cables[trackNum*2+0]->connect(*tracks[trackNum],0,mixerL,trackNum);
      cables[trackNum*2+1] = new AudioConnection();
      cables[trackNum*2+1]->connect(*tracks[trackNum],0,mixerR,trackNum);
    }
    
    switch (action)
    {
      case 0:
        tracks[trackNum]->stop();
        break;
        
      case 1:
      case 3:
      case 4:
        playTrack(trackNum,waveNum);
        break;
        
      case 2:
      case 5:
#define ALLOW_DELETE 1      
        if (ALLOW_DELETE)
        {
          delete tracks[trackNum];
          delete cables[trackNum*2+0];
          delete cables[trackNum*2+1];
          tracks[trackNum] = NULL;
        }
        else
          tracks[trackNum]->stop();      
        break;
        
      case 6:
      case 7:
      case 8:
        tracks[trackNum]->pause(!tracks[trackNum]->isPaused());
        break;
    }
  }

  // create numbers to plot
  int nx=0,nr=0,ns=0,np=0;
  for (int i=0;i<4;i++)
  {
    char c = 'x';
    if (NULL != tracks[i]) 
    {
      c = '?';
      c = tracks[i]->isPlaying()?'r':c;
      c = tracks[i]->isStopped()?'s':c;
      c = tracks[i]->isPaused()?'p':c;
    }
    
    switch (c)
    {
      case 'x': nx++; break;
      case 'r': nr++; break;
      case 's': ns++; break;
      case 'p': np++; break;
    }
  }

  // plot memory use, number of wave player instances, and counts of
  // the instance states (playing, stopped, paused)
  Serial.printf("%d %d %d %d %d\n",(FreeMem() - 00000)/100,nx*1000,nr*1000,ns*1000,np*1000);
  delay(50);
}

This test simply starts, pauses, stops and deletes up to four random instances of the wave player, at random intervals. As it stands it works fairly poorly for a while, then stops emitting the plotter output. Changing to #define ALLOW_DELETE 0 emulates the static audio engine, and everything behaves OK, even given the fact my SD card is not optimal.

"works fairly poorly": obviously random stops and pauses are bound to result in some clicks, but there are also clicks while no actions are occurring. I'd guess this is because the algorithm used to try to keep the filesystem reads out of alignment with one another doesn't work well with dynamically-varying numbers of instances.

"stops emitting plotter output": this isn't a simple crash, as I don't believe we get a CrashReport. A brief look at the code suggests the apparent number of active AudioPlayWav instances (uint8_t AudioBaseWav::_instances) grows without limit, as it's incremented on object construction, but not decremented when the object is deleted.

Cheers

Jonathan
 
I'm sorry to report that WaveplayerEx and my Dynamic Audio Objects are (currently) not playing well together

Yes, decreasing will not help as it also breaks the (time-)order if you add new ones after.
Simple solution: Don't use it dynamic or don't use the waveplayer. At the moment, these have contrary requirements - interleaving needs a absolute "position" so that the player can calculate the amount of data that has to be read at a given time.
It will not work anymore if the position changes - at least not if it is playing.
Edit:
I don't see a real world use-case for dynamic players, too (where it would not work without "dynamic")

I'll add a coment that they are not compatible to the sourcecode.

IF you have a solution, I'd be happy to see a PR. It should work with already playing files, of course.
 
Last edited:
Yes, decreasing will not help as it also radomizes the order if you add new ones after.
Simple solution: Don't use it dynamic or don't use the waveplayer. These have contrary requirements - interleaving needs a absolute "position" so the the player can calucalte the amount of data that has to be read.

I'll add a coment that they are not compatible to the sourcecode.

IF you have a solution, I'd be happy to see a PR. It should work with already playing files, of course.
Just posting here as they're two of the four "major contributions" that @PaulStoffregen says he's got pending, and an early warning might save later grief! If the incompatibility results in one or the other being deferred pending a fix, so be it.

Another solution may be to define the required AudioPlayWav instances statically, and other objects dynamically - I've not tested this.

Cheers

Jonathan
 
Edit:
I don't see a real world use-case for dynamic players, too (where it would not work without "dynamic")
On the roadmap page?

Dynamic Updates & Web Designer Control
Today, the audio objects really need to be statically allocated. Some distant future version will feature working object destructors, and constructors capable of adding objects and connections into a live system.
Eventually, I want to create an example that listens for commands to create & delete objects, and control many of their functions, and of course corresponding code in the GUI design tool. As you drag objects onto the canvas, and connect them, messages would be sent to your Teensy running that example, to actually implement as you draw.​
 
On the roadmap page?
Dynamic Updates & Web Designer Control
Today, the audio objects really need to be statically allocated. Some distant future version will feature working object destructors, and constructors capable of adding objects and connections into a live system.
Eventually, I want to create an example that listens for commands to create & delete objects, and control many of their functions, and of course corresponding code in the GUI design tool. As you drag objects onto the canvas, and connect them, messages would be sent to your Teensy running that example, to actually implement as you draw.​

I guess, in this case the roadmap has a problem. Pauls wanted interleaved reads are not compatible to that.
Think twice:

Worst case you have to restart (->read vom SD) *all* players if you add one. Do that in 3ms. He wants 14 ...
Yes, with an RTOS possible, perhaps. But an RTOS is not wanted, too.

However, I don't care. You can stay with the old player or can write a new one.
Not sure about the intention of your post here.
 
Yes, I will work with it later this month.

Ok, that was last month, and the new month is again 3 weeks old.
Any news?
If not, (or again no answer - a simple "yes" or "no I will no use it " would have been enough...) I'd prefer to change the licence back.

Thanks.
 
Hi!
I just tested out the MidiPiano example posted in this thread: https://forum.pjrc.com/threads/69373-Best-Method-for-Fast-Playing-of-Samples-with-8-voice-Polyphony

I am using Teensy 4.1 on-board card reader and a Kingston Canvas React 32GB micro sd card and when playing multiple sounds simultaneously, I am getting a lot of crackling. This happens when triggering four or more notes in fast succession (in under half a second) on my midi keyboard. Before buying a new card, I just want to confirm that no crackling is happening in a case like this on the card recommended in the example, Kingston Canvas Go! Plus, >=64GB?

Best,
Miro
 
Hi!
I just tested out the MidiPiano example posted in this thread: https://forum.pjrc.com/threads/69373-Best-Method-for-Fast-Playing-of-Samples-with-8-voice-Polyphony

I am using Teensy 4.1 on-board card reader and a Kingston Canvas React 32GB micro sd card and when playing multiple sounds simultaneously, I am getting a lot of crackling. This happens when triggering four or more notes in fast succession (in under half a second) on my midi keyboard. Before buying a new card, I just want to confirm that no crackling is happening in a case like this on the card recommended in the example, Kingston Canvas Go! Plus, >=64GB?

Best,
Miro


I used USB Audio as output and connected headphones.
Yes, now I can hear that too. (Note, for the loud tones, there is some saturation because the mixers are not set.) But there is a a quiet background noise.. %&/(
Hmm...
Not good.
 
I used USB Audio as output and connected headphones.
Yes, now I can hear that too. (Note, for the loud tones, there is some saturation because the mixers are not set.) But there is a a quiet background noise.. %&/(
Hmm...
Not good.

It only seems to happen in the first second of the file, after that multiple notes play just fine. Here is my current code with some changes to the mixer volumes. I also changed the max amount of voices to eight, to see if that would help, but it's still the same crackling apparent at the start of playing a new sound. I'll try to do a FIFO note management next to stop duplicate notes from playing.

Code:
/*
 * AudioWavPlaySD_MidiPiano example
 * (c) Frank B. jan/2022
 * 
 * Sample loaded from https://theremin.music.uiowa.edu/MISpiano.html
 * and processed with ffmpeg to make them louder and to remove silence from the beginnings
 * 
 * Howto:
 * - Connect a MIDI keyboard to the Teensy 4.1 USB Host
 * - Use SDFORMAT to format a FAST SD Card - recommended is a Kingston Canvas Go! Plus, >=64GB
 * - Create a Folder "Piano" and copy all *.aiff files to the folder
 * 
 */
#include <Audio.h>
#include <SD.h>
#include <USBHost_t36.h>

#include <play_wav.h>

// GUItool: begin automatically generated code
AudioPlayWav             playWav1;       //xy=245,78
AudioPlayWav             playWav2;       //xy=243,205
AudioPlayWav             playWav3;       //xy=234,333
AudioPlayWav             playWav4;       //xy=236,458
AudioPlayWav             playWav5;       //xy=236,582
AudioPlayWav             playWav6;       //xy=236,690
AudioPlayWav             playWav7;       //xy=238,813
AudioPlayWav             playWav8;       //xy=238,936
AudioMixer4              mixer1;         //xy=546,195
AudioMixer4              mixer2;         //xy=549,270
AudioMixer4              mixer3;         //xy=550,348
AudioMixer4              mixer4;         //xy=550,421
AudioMixer4              mixer5;         //xy=727,261
AudioMixer4              mixer6;         //xy=730,371
AudioAmplifier           amp1;           //xy=890,291
AudioAmplifier           amp2;           //xy=892,327
AudioOutputI2S           i2s1;           //xy=1045,304
AudioConnection          patchCord1(playWav3, 0, mixer1, 2);
AudioConnection          patchCord2(playWav3, 1, mixer3, 2);
AudioConnection          patchCord3(playWav4, 0, mixer1, 3);
AudioConnection          patchCord4(playWav4, 1, mixer3, 3);
AudioConnection          patchCord5(playWav5, 0, mixer2, 0);
AudioConnection          patchCord6(playWav5, 1, mixer4, 0);
AudioConnection          patchCord7(playWav6, 0, mixer2, 1);
AudioConnection          patchCord8(playWav6, 1, mixer4, 1);
AudioConnection          patchCord9(playWav7, 0, mixer2, 2);
AudioConnection          patchCord10(playWav7, 1, mixer4, 2);
AudioConnection          patchCord11(playWav8, 0, mixer2, 3);
AudioConnection          patchCord12(playWav8, 1, mixer4, 3);
AudioConnection          patchCord13(playWav2, 0, mixer1, 1);
AudioConnection          patchCord14(playWav2, 1, mixer3, 1);
AudioConnection          patchCord15(playWav1, 0, mixer1, 0);
AudioConnection          patchCord16(playWav1, 1, mixer3, 0);
AudioConnection          patchCord17(mixer1, 0, mixer5, 0);
AudioConnection          patchCord18(mixer2, 0, mixer5, 1);
AudioConnection          patchCord19(mixer3, 0, mixer6, 0);
AudioConnection          patchCord20(mixer4, 0, mixer6, 1);
AudioConnection          patchCord21(mixer5, amp1);
AudioConnection          patchCord22(mixer6, amp2);
AudioConnection          patchCord23(amp1, 0, i2s1, 0);
AudioConnection          patchCord24(amp2, 0, i2s1, 1);
// GUItool: end automatically generated code


USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);
MIDIDevice midi1(myusb);

AudioMixer4 *mixers[6] = {&mixer1, &mixer2, &mixer3, &mixer4, &mixer5, &mixer6};
float power = 0.25;

const int numConcurrendFiles = 8;

typedef struct {
  AudioPlayWav* player;
  unsigned long millis;
  int note;
} tPlayer;

tPlayer plnotes[numConcurrendFiles] = {
  { &playWav1, 0, -1}, { &playWav2, 0, -1}, { &playWav3, 0, -1}, { &playWav4, 0, -1},
  { &playWav5, 0, -1}, { &playWav6, 0, -1}, { &playWav7, 0, -1}, { &playWav8, 0, -1}
};

const int maxlen_filename = 32;
const char* notes[12] = {"C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"};
const int octave_min = 0;
const int octave_max = 7; //0..7 = 8
const int midiChannel = 1;
const int velocity_max = 2; //0..2 = 3

char filename[velocity_max + 1][octave_max + 1][12][maxlen_filename + 1];

FLASHMEM
void createFilenames(const char* velocityStr, int velocity)
{
  for (uint octave = 0; octave < octave_max + 1; octave++)
    for (uint note = 0; note < 12; note++) {
      snprintf(filename[velocity][octave][note], maxlen_filename, "Piano/Piano.%s.%s%d.aiff", velocityStr, (char*)notes[note], octave);
      if (!SD.exists(filename[velocity][octave][note]))
        filename[velocity][octave][note][0] = 0;
      // else Serial.println(filename[velocity][octave][note]);
    }
}

float vol = 0.5;

FLASHMEM
void setup()
{
  AudioMemory(40);

  Serial.begin(9600);
  while (millis() < 3000 && !Serial); // wait for Arduino Serial Monitor

  Serial.println("Midi Piano Example");
  Serial.print("Initializing SD card...");

  if (!SD.begin(BUILTIN_SDCARD)) {
    Serial.println("initialization failed!");
    abort();
  }
  Serial.println("SD initialization done.");
  createFilenames("pp", 0);
  createFilenames("mf", 1);
  createFilenames("ff", 2);

  for (int i = 0; i < 6; i++) {
    if (i < 4) {
      power = 0.25;
    } else {
      power = 0.5;
    }
    mixers[i]->gain(0, power);
    mixers[i]->gain(1, power);
    mixers[i]->gain(2, power);
    mixers[i]->gain(3, power);
  }

  myusb.begin();
  midi1.setHandleNoteOn(OnNoteOn);
  midi1.setHandleNoteOff(OnNoteOff);
  midi1.setHandleControlChange(OnControlChange);

  amp1.gain(vol);
  amp2.gain(vol);
  
  Serial.println("Ready to play ;)");
}


int getNextPlayer(int note)
{
  unsigned long oldestTime = 0x7fffffff;
  int next = 0;

  for (uint i = 0; i < numConcurrendFiles; i++) {
    /*
        - On a real piano you use the same string, of course, to play if the same note is played again.
        Restarting the same player however results in a audible click, sometimes -
        this could be fixed with an additional effort, but would go beyond the scope of this simple example.        
    */

#if 0
    if ( note == plnotes[i].note ) //if same note, restart same player
      return i;
#endif

    if ( plnotes[i].player->isStopped() )
      return i; //found a unused player

    if ( plnotes[i].millis < oldestTime ) {
      oldestTime = plnotes[i].millis;
      next = i;
    }
  }

  return next;
}


void playNote(byte note, byte velocity) {
  int octave = note / 12;
  if (octave > octave_max) return;
  int n = note % 12;
  int v = roundf(((float)(velocity_max) / 127) * velocity);

  if (filename[v][octave][n][0]) {
    int pl = getNextPlayer(note);
    plnotes[pl].player->play(filename[v][octave][n]);
    plnotes[pl].millis = millis();
    plnotes[pl].note = note;
    Serial.printf("Player: %i, Note: %s, Octave: %i, Velocity: %i, File: %s\n",
                  pl + 1, notes[n], octave, v, filename[v][octave][n]);
  } else {
    Serial.printf("Note: %s, Octave: %i, File: %s NOT FOUND\n",
                  notes[n], octave, filename[v][octave][n]);

  }
}



void OnNoteOn(byte channel, byte note, byte velocity)
{
  if (midiChannel == channel)
    playNote(note, velocity);
  else 
    Serial.printf("Unknown channel: %u\n", channel);
  //Serial.printf("Note On, ch=%u, note=%u, velocity=%u\n", channel, note, velocity);
}

void OnNoteOff(byte channel, byte note, byte velocity)
{
  //TODO... for you :) - beyond the scope here

#if 0
  Serial.printf("Note Off, ch=%u, note=%u, velocity=%u %s%u\n",
                channel, note, velocity, notes[note % 12], note / 12);
#endif
}

void OnControlChange(byte channel, byte control, byte value)
{
  //Todo... pedal, etc
#if 0
  Serial.print("Control Change, ch=");
  Serial.print(channel);
  Serial.print(", control=");
  Serial.print(control);
  Serial.print(", value=");
  Serial.print(value);
  Serial.println();
#endif
}


void loop()
{
  myusb.Task();
  midi1.read();
}
 
Can you upload a short recording?


I'd like to know where the quiet background noise comes from... that makes me nervous...:) i have no crackling, only too high levels and sometimes a single short click with some notes. I think I have to upload new samples too...
 
HA!! Found the reason for the noise.
I can't fix that. Seems to be a weird issue with the SD Slot or SD library. It's audible with USB-Audio, too so can't be a "electrical" noise..... It can't be a problem with the waveplayer, too, - patched it to output zeros only, and you still hear the quite noise. So It is not the waveplayer, too. The noise appears as soon the SD gets read. Replaced the SD reads... and the noise disappeared.

However, this is a completely different issue from that that you described.
I'd like to hear a recording.
 
Here is a short recording done from Teensy 4.1 through USB audio on my Mac.
The crackle happens as new notes are triggered, but not after that.
I guess it has something to do with retrieving data from the SD?

View attachment WavePlayerEX_sample.aif.zip

-Miro

Edit: Ah, you were just a bit quicker. I have faced this same problem many times while browsing the forums, but have found no solutions yet. Some sort of a buffer that prefetches a small part of the beginning of the audio file from SD to RAM or flash could be a workable solution. Unfortunately my coding skills currently are not good enough for that. :D
 
Hi, yes your recording sounds like a speed problem.
You could try to increase AUDIO_BLOCK_SAMPLES to 512 - does it disappear?

I'll upload a recording later... killed everything here and windows is confused with usb audio now.. does not work anymore :) I hope i get it running again :)
 
Here my recording.
With the headphone I hear some klicks, too... but less than you.

I guess I have to write a better example... using raw files perhaps.. not sure where where the klicks - in this case - come from.

Possible reasons:

1) - The samples
2) - SD takes too long to find and open the file
3) - SD takes too long to read the file header

I think we could fix the first and the last - by looking at the sample and by using raw files. But 2) is difficult..


give me some days.
 

Attachments

  • unbenannt.zip
    46.7 KB · Views: 50
Back
Top