Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 5 FirstFirst 1 2 3 4 ... LastLast
Results 26 to 50 of 113

Thread: WaveplayerEx

  1. #26
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    @wmxz,
    yes the name is subject to change..

    @mjs: Thank you! I've added your code as example.

  2. #27
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    7,557
    Quote Originally Posted by Frank B View Post
    @wmxz,
    yes the name is subject to change..

    @mjs: Thank you! I've added your code as example.
    Morning Frank
    Out of curiosity I decided to test as a SPI 1G NAND chip, so I put a bunch of files other than the SDTEST files:
    Code:
    Instances: 1
    1
    LittleFS Test
    TotalSize (Bytes): 131596288
    started
    printDirectory
    --------------
    FILE	Nums_7dot1_16_44100.wav		6385552
    FILE	SDTEST1.wav		16787550
    FILE	SDTEST2.wav		16425698
    FILE	SDTEST3.wav		13617358
    FILE	SDTEST4.wav		17173152
    FILE	calculations.wav		426300
    FILE	completed.wav		276460
    FILE	dangerous_to_remain.wav		372892
    FILE	enough_info.wav		513388
    FILE	functional.wav		237356
    FILE	odd1.wav		553004
    FILE	one_moment.wav		202236
    FILE	operational.wav		772140
    FILE	sorry_dave.wav		791164
    FILE	stop.wav		200844
    
     0 dirs with 15 files of Size 74735094 Bytes
    
    Playing file: Nums_7dot1_16_44100.wav
    Format:65534 Bits:16
    Playing file: SDTEST1.wav
    Format:1 Bits:16
    Playing file: calculations.wav
    Format:1 Bits:16
    Playing file: dangerous_to_remain.wav
    Format:1 Bits:16
    Playing file: odd1.wav
    Format:1 Bits:16
    One bit of warning though - when using LittleFS files names are case sensitive. So while this worked when using the SD Card:
    Code:
    playFile("SDTEST1.WAV");
    it will not work for LittleFS since in actuality on the disk its:
    Code:
    playFile("SDTEST1.wav");
    where wav is in lower case - so just be forewarned

  3. #28
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Oh, then I must have renamed my files at some point.

  4. #29
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    7,557
    Quote Originally Posted by Frank B View Post
    Oh, then I must have renamed my files at some point.
    Probably me working on it too early in the morning. Took awhile for the light bulb to come on. This is what is the example currently.

    Code:
      playFile("Nums_7dot1_16_44100.wav");
      delay(500);
      playFile("SDTEST1.WAV");  // filenames are always uppercase 8.3 format
      delay(500);
      playFile("SDTEST2.WAV");
    .....
    It ran fine from the SD Card even though the file extension was all in caps. But when I ran it from Flash (SPI or QSPI) with LittleFS had to change the file extension to lower case since that was what was the acutally format for the filename SDTEST2.wav for instance - was driving me crazy because only the number file would play - then the light bulb came on

  5. #30
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Quote Originally Posted by mjs513 View Post
    then the light bulb came on
    :-)


    Added support for 8 bit samples.

  6. #31
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Ok, I think its almost ready to use now.
    Needs some more testing...

    Latest version

    Example audio files are on github.

    Features:


    • Sample rate agnostic (does not check if its correct - so easy to use with other samplerates, (edit audiostream.h)
    • up to 8 Channels
    • 8 or 16Bit
    • delay() after start not needed anymore
    • any audio block size
    • interleaved reads: only one file access on each audio-cycle
    • lastErr(void) returns the last error
    • addMemoryForRead(size_t bytes) adds memory
    • Several files can start synchronized.


    It uses malloc() to manage its memory. For example files with 8 voices need way more memory than stereo. The interleaved reads increase the amount of needed memory, too.
    The amount is calculated automatically, so addMemoryForRead() is *not* needed in most cases.

    It's not that dead simple to calculate the optional additional memory - i'll descrivbe that later. Too less additonal memory makes no sense and gets ignored.
    It needs to be a multiple of # of instances, samplesize, audioblock-size etc...

    Looks like this in the code

    I renamed it to AudioPlayWav.
    I think, it can replace the old player completely..

    @Paul? What do you think? Have you tried it?
    @Wcalvert: Synced start is possible. Start all waves in paused mode: play(filename, true), then start them together with pause(false), surrounded by AudioNoInterrupts. They will start emitting Audio data with the next audio cycle.
    Last edited by Frank B; 07-28-2021 at 09:14 AM.

  7. #32
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Public functions:
    Code:
        bool play(File file);
        bool play(File file, bool paused);
    
        bool play(const char *filename);
        bool play(const char *filename, bool paused); //start in paused state?
    
        bool addMemoryForRead(size_t bytes); //add memory
    
        void togglePlayPause(void);
        void pause(bool pause);
        void stop(void);
    
        bool isPlaying(void);
        bool isPaused(void);
        bool isStopped(void);
    
        uint32_t positionMillis(void);
        uint32_t lengthMillis(void);
        uint32_t numBits(void);
        uint32_t numChannels(void);
        uint32_t sampleRate(void);
    
        uint8_t lastErr(void);         // returns last error
    Error values:
    Code:
    #define APW_ERR_OK              0 // no Error
    #define APW_ERR_FORMAT          1 // not supported Format
    #define APW_ERR_FILE            2 // File not readable (does ist exist? large enough?)
    #define APW_ERR_OUT_OF_MEMORY   3 // Not enough dynamic memory available
    #define APW_ERR_NO_AUDIOBLOCKS  4
    Last edited by Frank B; 07-27-2021 at 06:07 AM.

  8. #33
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    @MJS: Hm, the PJRC example wave files are uppercase ".WAV", too.. . what happens here?


    Ok, tested with Teensy LC.
    Looks like it is just to slow.

    Still needs 128 Bytes block size. No chance..
    But.. it works - with 2 channels (Stereo) max
    Example for Teensy LC with audio shield is here: https://github.com/FrankBoesing/Teen...layer_TeensyLC
    Last edited by Frank B; 07-28-2021 at 07:22 AM.

  9. #34
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    312
    Sweet!

    This is probably old news since you appear to have it working, but...

    With the Tympan_Library, I write 4-channel WAV files all the time. I always use 16-bit int, but I do a variety of sample rates...everything from 20 kHz up to 96 kHz. I think that I tried 192kHz once, but I don't remember. Once I recorded the files, I pulled the SD card and read them in via Audacity.

    The only trick to making this whole thing happen was getting the WAV header correct (see here, skip down to the method "wavHeaderInt16"). But, if I figured it out, you probably figured it out even more easily.

    As for reading audio from an SD, I've been stuck reading reading only stereo files (see here, skip down to the "update" method). I like that you thought to extend this to a greater number of channels!

    Chip

  10. #35
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    You can try if my code can read your files
    Or, would you upload or send me one? I'd like to try it..

  11. #36
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    7,557
    Quote Originally Posted by Frank B View Post
    @MJS: Hm, the PJRC example wave files are uppercase ".WAV", too.. . what happens here?
    Funny because when I downloaded the SDTESTn.wav files they downloaded as lowercase - unless my windows made them lowercase?

  12. #37
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343

  13. #38
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    7,557
    Yep thats where I downloaded from. When I do right click and do a save it comes down as lower case - maybe because my default player is VLC? See screenshot below:
    Click image for larger version. 

Name:	Capture.PNG 
Views:	23 
Size:	13.1 KB 
ID:	25379

  14. #39
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Looks like there is a big fat bug re: additional Mem and intervleave... i've set the repo to private until it is fixed..
    Last edited by Frank B; 07-28-2021 at 01:04 PM.

  15. #40
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Quote Originally Posted by Frank B View Post
    Looks like there is a big fat bug re: additional Mem and intervleave... i've set the repo to private until it is fixed..
    Phew.. fixed that.

  16. #41
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    7,557
    Quote Originally Posted by Frank B View Post
    Phew.. fixed that.
    Cool you got it fixed - sounded worried for a minute

  17. #42
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Yes I had no Idea how to do it... could have been a show stopper.
    *still testing*


    Oh my,... now concurrent play does not work anymore.
    I'll take a break.
    Last edited by Frank B; 07-29-2021 at 11:47 AM.

  18. #43
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Quote Originally Posted by Frank B View Post
    I'll take a break.
    ..and it was helpful. issue fixed.

    So, a description.
    The way addMemoryForRead() works has changed.
    I think it is much easier to use now. There are not many cases where it is necessary, nevertheless it may be useful in some cases.
    Normally the waveplayer itself knows how much memory it needs. This is calculated for each file individually:

    Memory:
    AUDIO_BLOCK_SAMPLES * Channels * BytesPerSample * NumberOfInstances

    This looks like a lot, but it is not. In the normal case (1 instance) it leads to exactly the same memory usage as the old player.
    With 8 channels, 16Bit you get 128 * 8 * 2 * 1 = 2048 bytes. This is a not much for the T4, but also a T3.2 should be able to do it.
    (With a blocksize of 128, even the Teensy LC can play a stereo file)

    The player tries to maximize the SD throughput. It reads as much as possible, i.e. it fills its entire memory when it accesses the file.
    This saves the expensive addressing when multiple players are running at the same time.

    In the following we are talking about multiple concurrent instances - and only then addMemoryForRead() can be useful.
    A simple concurrent start can look like this, for example:

    (1)
    Code:
      playWav1.play("Nums_7dot1_16_44100.wav");    
      playWav2.play("SDTEST3.WAV");
    This is not quite optimal, because an audio interrupt can happen between both calls. I.e. the starts would be about 3 milliseconds apart in this case.
    Often this may not matter.

    This attempt to prevent this is not good:
    Code:
      AudioNoInterrupts();
      playWav1.play("Nums_7dot1_16_44100.wav");    
      playWav2.play("SDTEST3.WAV");    
      AudioInterrupts();
    This may lead to a short sound dropout of the library if it happens just before an interrupt.

    Better is this:
    (2)
    Code:
      playWav1.play("Nums_7dot1_16_44100.wav", true); //start in paused mode
      playWav2.play("SDTEST3.WAV", true);
      AudioNoInterrupts();
      playWav1.pause(false); //un-pause, start playing
      playWav2.pause(false);
      AudioInterrupts();
    This is a sychronized start. Both files start granted at the same time.

    Now we are slowly approaching the point where additional storage becomes interesting. Probably.

    (2) looks always like this - and in most cases (1) looks indentical:
    Click image for larger version. 

Name:	pic_9_2.png 
Views:	21 
Size:	9.5 KB 
ID:	25444
    (yes, the probes are not calibrated - sorry for this, could not find the tool)

    So, you see that it channel 2( blue line ) gets read from SD in middle between the reads of channel 1 (yellow)

    In some cases of (1) - if the second files starts an interrupt later, it can look like this:
    Click image for larger version. 

Name:	pic_9_1.png 
Views:	24 
Size:	9.5 KB 
ID:	25445

    And only for this - i.e. when not synchronized start - AND when it is somehow interesting for the main program (there are very few cases... I can't even think of a simple example)
    additional memory becomes interesting:

    Code:
    playWav1.addMemoryForRead(2);
    playWav2.addMemoryForRead(2);
    (This must happen before play() )

    A values of 2 doubles the used memory, 3 would be the tripple (use it for three files for example)
    Now, it always looks like (1).
    This is needed, because in the default case, the player just does not have memory to play 3 milliseconds without accessing the file (you remember - the 2nd file may start later (if no sync start))
    Now, it looks good again:
    Click image for larger version. 

Name:	pic_9_2.png 
Views:	21 
Size:	9.5 KB 
ID:	25444

    So... I hope this explanation was a little helpful.

    Back to a single file:

    The normal frequency of reads is 172Hz. If you want a lower frequency, you can use addMemoryForRead(), too.
    i.e. "addMemoryForRead(2)" would half this frequency to 86Hz. If you need that.

    And, last:
    - addMemoryForRead(0) and addMemoryForRead(1) do nothing.
    Edit "- if you use it, use it for every player. Not using it for all instances makes no sense." - changed in latest version. It's sufficient to change it for one instance - all other instances will us it, too.
    Last edited by Frank B; 08-01-2021 at 12:05 PM.

  19. #44
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    You may see, that the first file ( it is a 8 channel 16 bit file) does not need that much more time for the reads.
    If someone sends me a 10, 12, 14 and a 16 channel file, i can test it, and perhaps increase the max number in the code. I wonder if that will work.

    Edit:
    Just tried to play the Nums_7dot1_16_44100.wav twice. SO.. 16 channels, 16 bit parallel - 8 channels from each file. Seems to work How far can we go?

    Edit again:
    Increased the smple freqency to 88.2 khz: Now I hear 2x Mickey Mouse. I think there are still no artifacts. So... calc back.. 32 channels(?!)

    Edit again:
    There is still *much* room, even with the last experiment.
    Last edited by Frank B; 07-31-2021 at 11:03 AM.

  20. #45
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    I have changed the behaviour of addMemoryForRead().
    It is now a global setting - setting it for one instance sets it for all others, too.
    I think that's better and prevents bugs.

  21. #46
    Senior Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    124
    Struggling a bit with this, though possibly because I'm trying something which is not the correct use case... I have 6 mono WAV files which I want to play simultaneously, taking advantage of the round-robin buffering. But I can't get even two to work nicely!
    Code:
    // Simple WAV file player example, adapted
    
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    #define AudioPlaySdWav AudioPlayWav // divert normal player to Frank's one
    
    // GUItool: begin automatically generated code
    AudioPlaySdWav             track1;     //xy=323,171
    AudioPlaySdWav             track2;     //xy=323,171
    AudioPlaySdWav             track3;     //xy=323,171
    AudioPlaySdWav             track4;     //xy=323,171
    AudioPlaySdWav             track5;     //xy=323,171
    AudioPlaySdWav             track6;     //xy=323,171
    AudioRecordQueue         queue1;         //xy=401,90
    AudioRecordQueue         queue2;         //xy=402,132
    AudioMixer4              mixer1;         //xy=647,123
    AudioMixer4              mixer3;         //xy=648,212
    //AudioOutputPT8211        pt8211_1;       //xy=828,169
    AudioOutputI2S           pt8211_1;           //xy=953,400
    
    AudioConnection          patchCord1(track1, 0, mixer1, 0);
    AudioConnection          patchCord2(track2, 0, mixer3, 0);
    AudioConnection          patchCord3(track3, 0, mixer1, 1);
    AudioConnection          patchCord4(track4, 0, mixer3, 1);
    AudioConnection          patchCord5(track5, 0, mixer1, 2);
    AudioConnection          patchCord6(track6, 0, mixer3, 2);
    AudioConnection          patchCord7(track1, 0, queue1, 0);
    AudioConnection          patchCord8(track2, 0, queue2, 0);
    AudioConnection          patchCord9(mixer1, 0, pt8211_1, 0);
    AudioConnection          patchCord10(mixer3, 0, pt8211_1, 1);
    AudioControlSGTL5000     sgtl5000_1;     //xy=942,297
    // GUItool: end automatically generated code
    
    #define SDCARD_CS_PIN    BUILTIN_SDCARD
    #define SDCARD_MOSI_PIN  11  // not actually used
    #define SDCARD_SCK_PIN   13  // not actually used
    
    
    
    /*********************************************************************************/
    
    void setup() {
      Serial.begin(9600);
      if (CrashReport) {
        Serial.println(CrashReport);
        CrashReport.clear();
      }
    
      AudioMemory(50);
    
      SPI.setMOSI(SDCARD_MOSI_PIN);
      SPI.setSCK(SDCARD_SCK_PIN);
      while (!(SD.begin(SDCARD_CS_PIN))) {
        // stop here, but print a message repetitively
        //while (1) {
    //      Serial.println("Unable to access the SD card");
          delay(500);
        //}
      }
    
      mixer1.gain(0,0.2);
      mixer1.gain(1,0.0);
      mixer1.gain(2,0.0);
      mixer1.gain(3,0.0);
      mixer3.gain(0,0.2);
      mixer3.gain(1,0.2);
      mixer3.gain(2,0.2);
      mixer3.gain(3,0.2);
      
      sgtl5000_1.enable();
      sgtl5000_1.volume(0.05);
    
      Serial.println(F("block filepos1 filepos2 wav1 wav2"));
    }
    
    
    void loop() {
      int count = 497;
    
      track2.play("sine110.wav",true);
      track1.play("sine440.wav",true);
      
      Serial.print(track1.filePos());
      Serial.print(' ');
      Serial.print(track2.filePos());  
      Serial.println();
    #if 1  // doesn't make any difference to enable these
      track3.play("sine330.wav",true);
      track4.play("sine220.wav",true);
      track5.play("sine550.wav",true);
      track6.play("sine660.wav",true);
    #endif
      queue1.begin();
      queue2.begin();
      delay(4);
      AudioNoInterrupts();
      track1.pause(false);
      AudioInterrupts();
      delay(6);
      AudioNoInterrupts();
      track2.pause(false);
      track3.pause(false);
      track4.pause(false);
      track5.pause(false);
      track6.pause(false);
      AudioInterrupts();
    
      while (count > 0)
      {
        if (queue1.available() > 0)
        {
          int16_t* q1 = queue1.readBuffer();
          int16_t* q2 = queue2.readBuffer();
          int pulse = 16000;
          
          for (int i=0;i<128 && count > 0;i+=4)
          {
            Serial.print(pulse);
            Serial.print(' ');
            pulse = 0;
                  
            Serial.print(track1.filePos() * 1.0f); // N.B. filePos() added for debug
            Serial.print(' ');
      
            Serial.print(track2.filePos() * 1.0f);
            Serial.print(' ');
      
            if (NULL != q1)
              Serial.print(q1[i]);
            Serial.print(' ');
            if (NULL != q2)
              Serial.print(q2[i]);
    
            Serial.println();
            count--;
          }
      
          queue1.freeBuffer();
          queue2.freeBuffer();
          pulse = 16000;
          
        }
      }
      AudioNoInterrupts();
      while (1)
        ;
    }
    I've added filepos() to the play_wav object for debug purposes:

    Code:
    uint32_t AudioPlayWav::filePos(void)
    {
    	if (wavfile)
    		return wavfile.position();
    	else
    		return 12345678UL;
    }
    The test WAV files "sineNN0.wav" are what you'd expect: 3s of 44100Hz sine waves at 110*N Hz - can send these if you need them. AudioRecordQueue is my modified one which returns a proper silent block as needed (this issue was how I found that problem!).

    Using the serial plotter, you'd expect to see a couple of sine waves starting at an offset time from one another, due to the delay. Instead I get:

    Click image for larger version. 

Name:	2021-08-24 08_38_05-NVIDIA GeForce Overlay.jpg 
Views:	10 
Size:	87.3 KB 
ID:	25646
    blue spikes: one per audio block; red / yellow traces: file position and audio for track1; green / magenta traces: same for track2

    It appears not to be pre-loading the second WAV file on the call to play() - note the small filePos value (green trace: looks like the header read has occurred OK), compared to the red trace. Then somehow track2 is managing to output a block of the track1 data before loading and playing its own data. Very odd. I've puzzled over your code for a bit and simply can't see how this could happen, so I have to throw it over to you to figure out!

    Best regards

    Jonathan

  22. #47
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Yes, playing some files with sync start is not the ideal case - if you don't really need it. SD accesses will be pretty random, and the adressing on SD needs much time. Better is to use one file with all channels needed.

    But, nevertheless, there indeed seems to be a problem. I've not looked at the code for a longer time now, so I have to re-read it myself and it is of course possible that there is a problem, or something I did not think of.
    But it has to wait a few days, till weekend.

  23. #48
    Senior Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    124
    Sure, no hurry. Multiple separate files is probably a more common use case than 8-track WAVs, judging by the number of posts about it! I've been trying to think of a sane way of doing the SD reads outside the audio interrupt, but so far nothing has occurred to me that's really clean to use...

    best regards

    Jonathan

  24. #49
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,343
    Agreed, it is more common, but it's still not good

  25. #50
    Senior Member
    Join Date
    Apr 2021
    Location
    Cambridgeshire, UK
    Posts
    124
    "Good" is just a matter of opinion Harder to do, definitely, but few of the posts I've seen made me think "you're taking totally the wrong approach, think again". Well, not from the SD playback perspective, anyway...

    I have a nice little multi-track recorder (Tascam DP-008) which uses up to 32Gb SDHC cards, and can play 8 tracks while recording 2 (hmmm ... actually, it's only 8 track ... so play 6 and record 2), plus some limited effects. It's pretty old, been around since at least 2013, probably not much RAM or CPU under the hood, so I'd think a 2021 Teensy should be able to do at least that much. And I'm sure it could, if only bright folk like us can just crack the SD access problem in a fairly usable way.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •