Shop OBEX P1 Docs P2 Docs Learn Events
Multifile WAV player — Parallax Forums

Multifile WAV player

I've been experimenting with playing two or more WAV files at once.

I got this to work by tweaking Kye's sd code to return an sd file's beginning data sector address when the file is opened. My code reads data via FAT routines to get the WAV parameters, but then reads data by raw sd sector to read the audio data. That way, several audio files can be opened, their parameters read and saved, then data reads can be done in interleaved file order.

It works, but after playing with it for awhile, I think it would be a cleaner approach if there were FAT library code that had the ability to open more than one file at a time, then use FAT routines to read data.

My code makes an array of file reader objects, where each object uses a cog to read and deliver data to the PWM player section. Again, it works, but since even in the P2 there are only 8 cogs, I think this is too limiting of an approach. Better to have one reader cog for all files, even though it would be more complicated than my simple array of buffers and reader cogs.

My code does have some nice features. It can play WAV files of any combination of mono or stereo, 8, 16, 24 or 32 bits, any data rate (as long as the overall data retrieval rate is less than about 280K bytes per second.)

For example, it can play a 1 channel 24 bit 48000 rate WAV together with a 1 channel 8 bit 8000 WAV with a combined rate of 152,000 bytes per second, but it can't cleanly play two 2 channel, 16 bit 44100 files because the sd retrieval can't keep up with the 352800 byte rate. They will play, but because of buffer underruns, it'll sound choppy as buffers wait to be updated.

As I was looking for sound effect WAV files to test, I kept seeing 24 bit WAV files, and out of curiosity for how they were implemented, I developed code to play them. In the process of doing that, it turned out to only need a few more lines to add the ability to play 32 bit WAVes, so I added them for completeness, although I don't know where 32 bit WAV files would ever be used.

The code generally works well, but for some reason, out of a dozen sample files, there are two WAV files that play great by themselves but when loaded to play together, one corrupts the other and I haven't figured out why.

I made an optional debug display to run as the files play to monitor the first sector buffer, showing remaining sectors to play and to show if there are buffer underruns. But it only currently monitors the first file being played. It could be enhanced to show the status of all files when multiple files are played, but I didn't get that far in development.

I know the FSRW sd code is faster than Kye's, but I used Kye's for two reasons. First, my version of FSRW fails to read more than about 80 files in a FAT folder, where Kye's code had no problem reading up to almost 1000. Second, when playing files on a 4-sd RAISD propeller board, it seemed that Kye's code was more reliable in switching between sd card sockets at runtime, where FSRW would often fail to remount sd cards.

I don't think I'll be doing any more on this particular project for awhile because my daughter gave me a propeller 2 for Christmas, and I've been busy getting started with that. But eventually, I think a second version of this would be a great P2 project.

In regard to the P2, I'd like to give a thanks to JonnyMac for his P2 tutorials and example code. I've watched the P2 development over the years but have never really followed it, and wouldn't have known where to start except for JonnyMac's getting started tutorial and for his clean, easily readable programming style.

David






Comments

  • Cool project.

    I've done something very similar, yet also very different. Might be interesting to you. It's called TinySDDA. It only plays one 16bit stereo 32kHz file and one A-law compressed mono 32kHz file at once (and has a bunch of quirks, such as not having any FAT or SD init code), but it only uses one cog (that includes the SD access!) and basically no memory at all (so it is suitable for adding audio to games...).
    David B wrote: »
    I know the FSRW sd code is faster than Kye's, but I used Kye's for two reasons. First, my version of FSRW fails to read more than about 80 files in a FAT folder, where Kye's code had no problem reading up to almost 1000. Second, when playing files on a 4-sd RAISD propeller board, it seemed that Kye's code was more reliable in switching between sd card sockets at runtime, where FSRW would often fail to remount sd cards.

    Yeah, FSRW is massively faster (especially if you figure out to switch to mb_rawb_spi.spin), but also super jank. Although it works fine for me with regards to files... I have loads of files (but nowhere near 1000) in my card root at all times and it can open 'em perfectly fine. Your cards might be weirdly formatted. Failing to mount is a new one. Anyways, grafting FSRW's fast SD block layers onto KyeFAT has been on my "do eventually" list for a while.

  • Wow, @"David B",

    I wasn't aware that anyone used the RAISD-Kit. @MacTuxLin made the kits for me, I gave 50? to @"Oldbitcollector (Jeff)" to sell on propellerpowered but then he disappeared.

    I still have around 30ish kits left, sitting in a box and getting dusty. If you find a use for them I could mail you some.

    PM your address if interested.

    Enjoy!

    Mike
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2020-12-31 02:12
    You will find that if you copied wav files to an SD card, that they will unfagmented and as much as I have tried over many many years I have never found any files fragmented on an SD card. Therefore I safely assume that once I have the starting sector for a file that I can do continual raw sector reads. Even at 44100 16-bit samples requires a sector read every 5.8ms I could easily mix 8 files at 200MHz. I should be able to set this up later and try it out, it should be very quick to create a multimix player.

    This is the tiny bit of code that plays a buffer while the other one is being filled.
    pub PLAYBUF ( buf -- )
    	wavblk# ADO
    	  BEGIN *pause SET? UNTIL		--- pause?
    	  I W@ AUDIO_OUT -2 wavsz +!	--- output next sample
    	2 +LOOP
    	;
    

    This is the one-liner that reads in the next buffer. It is setup to read a large enough block to keep it going as it is playing a 320x240 video but normally would only need to read a sector.
    --- read a wav block (3072 bytes) into a buffer - 3.6ms @300MHz
    pri GETWAV ( buf -- )
    	wavsect @ SWAP wavblk# SDRDS wavblk# 9>> wavsect +!
    	;
    
  • You will find that if you copied wav files to an SD card, that they will unfagmented and as much as I have tried over many many years I have never found any files fragmented on an SD card.

    I've found two surefire ways to get a fragmented file (for testing purposes):
    - The obvious one is to fill up the card with small files, then delete some of them until there's space for the target file. Since there isn't enough contiguous space, it must be fragmented.
    - The other one is more of a Windows quirk, I fear: Copy a small FILE_A to the card. Copy any FILE_B to the card. Overwrite FILE_A with a larger file of the same name. FILE_A is now fragmented. I think this only works if the free space is on the card is mostly contiguous.
  • I try to find them, I didn't try to make them fragmented :smile:
    It is best for the embedded system if the card is not fragmented and since it is hard to fragment and easy to fix, then there should never be any problem. When I want to clean up the card I just copy the files to a folder on my pc, delete the files on the card or reformat, and then copy the files back.
  • Thanks to everyone who responded.

    Mike, I left a PM. Let me know if you didn't get it; I don't get onto the forum much and might not have posted it properly.

    I worried about fragmented files when I first started experimenting with this, but after testing many hundreds of files, I only ran across one fragmented file, and it was a file near the end of an almost full sd card in which some files had previously been erased, forcing Windows to reuse their clusters.

    Wuerfel, I've also experimented a little using an sd card in raw sector form without a filesystem, but it seemed like for the kind of uses I was putting them to, it usually was more convenient to maintain Windows access to the files via the FAT filesystem.

    I could see a place for reading raw files in specialized applications, like for games, or in clockloop's model train project sound effect files, but I'd also like to see a general propeller FAT library that's flexible enough to be able to do things like this if someone wants.


  • I use the FAT filesytem for finding the file's start sector, and that's about it. The wave file size can be gleaned from the file header which is what I use. So effectively after that, it is "raw".
  • Wuerfel_21Wuerfel_21 Posts: 5,105
    edited 2021-01-03 02:57
    I use the FAT filesytem for finding the file's start sector, and that's about it. The wave file size can be gleaned from the file header which is what I use. So effectively after that, it is "raw".

    That's about what I do, too. Except I use unheadered files and get the size from the filesystem. I also make sure the file is not fragmented (just today I had to defragment my SD... It really does happen)
    David B wrote: »
    but I'd also like to see a general propeller FAT library that's flexible enough to be able to do things like this if someone wants.

    I think that hypothetical FSRW+KyeFAT mashup I talked of before would really be something lots of people could get use out of... Maybe that just moved up a step in priority.

    Using tinySDDA as a block layer for FSRW has also been on the table at least once...



    David B wrote: »
    Wuerfel, I've also experimented a little using an sd card in raw sector form without a filesystem, but it seemed like for the kind of uses I was putting them to, it usually was more convenient to maintain Windows access to the files via the FAT filesystem.

    I could see a place for reading raw files in specialized applications, like for games, or in clockloop's model train project sound effect files
    Games are exactly where you don't want to use raw sectors, because you usually want more than one on your SD at once :)
    My favorite solution is to have a seperate loader program that has a full FAT driver figure out the sector adresses of all relevant files (and make sure they're defragmented...) before booting into the main program, which then can treat the card as a raw sector device.
  • I see what's going on with fsrw and FAT32 directories.

    Fsrw assumes the FAT32 root directory is fixed in size, when actually it can expand via the cluster chain just like files do.

    Most of the time fsrw's assumption works fine, but that fixed-size assumption bit me after I put years of accumulated WAV files onto a much-used 32 gig sd card.

    Since many of my 32 gig sd directory slots were already used up with deleted file and long filename entries, there were only about 200 slots available of the 512 total in the first directory cluster, so FAT32 decided to allocate another cluster to store the rest of the root directory entries, and since fsrw doesn't follow the FAT32 directory cluster chain, it listed only the first 200 filenames but couldn't list the remaining directory entries.

    A quick fix is pretty simple:

    Make a global variable which I called dir32.

    In the opendir() method, set "dir32 := (filesystem == 2)" (which will be true for FAT32).

    In the nextfile() method, use "if not dir32" to skip the entry that does fclust++, because for FAT32 directories the cluster numbers can't just be incremented; they have to be looked up from the FAT table just like for a file.

    In the pfillbuf() method, use "if not dir32" to skip all lines that process or depend on the "filesize" variable because the FAT32 directory doesn't use "filesize".

    With just those changes, fsrw properly navigated the FAT table and displayed the full contents of my 32 gig FAT32 sd!

    For ordinary file use, "dir32" probably would need to be set false when nextfile is finished but so far I've only tested directory listing. I also haven't tried popen() on all files; it might also need repairs.

    I haven't tested it very thoroughly (I'm having a hard time finding sd cards having small FAT16 filesystems to test with.) so I can't say whether it's a robust fix. But it seems to work for directory listing.

    This issue probably won't affect anyone unless you want to put hundreds of files in a FAT32 root folder.

  • Wuerfel_21Wuerfel_21 Posts: 5,105
    edited 2021-01-07 17:57
    Oh, can you please post such modifications as an actual file? Doing the changes manually is error-prone.

    You should be able to format any card as FAT16 by resizing the partition to be smaller first (well, generally you'd first delete the existing partition, then create a small one and then format that as FAT16).

  • Sure; here's a project zip.

    It's in spin2. I'm just getting started on P2 programming, and did this to start learning spin2 coding.
  • In Windows 7, I didn't see any way to resize an sd partition.

    Mint Linux fdisk was willing to do it using a partition table, so I created a 2M partition on a 1gig sd card.

    After that, Windows did recognize it as a 2M partition and was able to format a 2M FAT16 filesystem and write a couple of dozen little text files onto it, almost filling it, but fsrw wouldn't mount the card.

    I'd guess that fsrw doesn't expect to see a partition table on an sd card.

    Is there a utility that will resize a single partition on an sd card without creating a partition table?
  • Wuerfel_21Wuerfel_21 Posts: 5,105
    edited 2021-01-09 00:04
    Ah, didn't expect that complication. Of course, there's no partion table on a spec-compilant card. (or is there a special kind? Maybe i'm just dumb) However on linux you should be able to format a 16MB FAT16 filesystem like this (after unmounting it)
    mkdosfs -F 16 /dev/whateveritshowsupas 32768
    
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2021-01-08 22:49
    gparted is your GUI partitioning friend on Linux. Any card that has been formatted (and all cards are preformatted) will have a partition table located in the MBR (sector 0) with up to 4 primary partitions. You can resize a partition down and leave unallocated storage and create another partition in that or add to the adjoining partition etc.

    But I would think that patching your FAT32 routines would be the better way though.

    btw, I'm guessing you meant 2G, not 2M
    774 x 525 - 31K
  • I've seen quite a lot of sometimes conflicting information about sd card formatting, so I started experimenting with different formats done by Windows, Linux, fsrw, etc. to see what works and what doesn't.

    So yes, I did deliberately partition a 1 gig card to contain only a 2 Meg filesystem, just for testing purposes.

    Windows also has a tool "diskpart" that some people say is pretty versatile.

    I've got a few other things going on and don't have very much time for sd experimenting right now but eventually I hope to do more, so I do appreciate all the comments and information.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2021-01-10 00:34
    If you load a binary of TAQOZ RELOADED on a P2 you can also format SDHC cards as FAT32 but with more control and it formats an SD card in the SD Card Association's recommended way with unused sectors before the first partition. You can even do partial format such as formatting the MBR only etc. It also has disk reporting utilities too.

    I don't bother supporting FAT16 or really really old byte addressable SD cards because there's no point in it.
    TAQOZ# .DISK ---  CARD: SANDISK   SD SL08G REV$80 #188035386 DATE:2016/7                                    
                                                                                                                
                       *** OCR ***                                                                              
        VALUE........................... $C0FF_8000                                                             
        RANGE........................... 2.7V to 3.6V                                                           
                                                                                                                
                       *** CSD ***                                                                              
        CARD TYPE....................... SDHC                                                                   
        LATENCY......................... 1ms+1400 clocks                                                        
        SPEED........................... 50MHz                                                                  
        CLASSES......................... 0 1 0 1 1 0 1 1 0 1 0 1                                                
        BLKLEN.......................... 512                                                                    
        SIZE............................ 7,761MB                                                                
        Iread Vmin...................... 100ma                                                                  
        Iread Vmax...................... 25ma                                                                   
        Iwrite Vmin..................... 1ma                                                                    
        Iwrite Vmax..................... 45ma                                                                   
                                                                                                                
                     *** SPEEDS ***                                                                             
        LATENCY......................... 206us,284us,223us,235us,226us,235us,244us,209us,                       
        SECTOR.......................... 430us,460us,457us,467us,460us,469us,478us,443us,                       
        BLOCKS.......................... 2,107kB/s @200MHz                                                      
                                                                                                                
                       *** MBR ***                                                                              
        PARTITION....................... 0 00 INACTIVE                                                          
        FILE SYSTEM..................... FAT32 LBA                                                              
        CHS START....................... 1023,254,63                                                            
        CHS END......................... 0,0,0                                                                  
        FIRST SECTOR.................... $0000_2000                                                             
        TOTAL SECTORS................... 15,515,648 = 7,944MB                                                   
                                                                                                                
    00170: 0000_0000 0000_0001 0002_0000 506F_7250     '............ProP'                                       
                                                                                                                
                      *** FAT32 ***                                                                             
        OEM............................. TAQOZ P2                                                               
        Byte/Sect....................... 512                                                                    
        Sect/Clust...................... 16 = 8KB                                                               
        FATs............................ 2                                                                      
        Media........................... F8                                                                     
        Sect/Track...................... $003F                                                                  
        Heads........................... $00FF                                                                  
        Hidden Sectors.................. 8,192 = 4MB                                                            
        Sect/Part....................... 15,515,648 = 7,944MB                                                   
        Sect/FAT........................ 7,576 = 3MB                                                            
        Flags........................... 0                                                                      
        Ver............................. 00 00                                                                  
        ROOT Cluster.................... $0000_0002 SECTOR: $0000_5B50                                          
        INFO Sector..................... $0001 = $0000_2001                                                     
        Backup Sector................... $0006 = $0000_2006                                                     
        res............................. 00 00 00 00 00 00 00 00 00 00 00 00                                    
        Drive#.......................... 128                                                                    
        Ext sig......................... $29 OK!                                                                
        Part Serial#.................... $50AD_0021 #1353515041                                                 
        Volume Name..................... P2 CARD    FAT32    ok                                                 
    TAQOZ#
    
Sign In or Register to comment.