Multifile WAV player
David B
Posts: 592
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
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
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...).
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.
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
This is the tiny bit of code that plays a buffer while the other one is being filled.
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.
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.
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.
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.
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)
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...
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.
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.
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).
It's in spin2. I'm just getting started on P2 programming, and did this to start learning spin2 coding.
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?
But I would think that patching your FAT32 routines would be the better way though.
btw, I'm guessing you meant 2G, not 2M
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.
I don't bother supporting FAT16 or really really old byte addressable SD cards because there's no point in it.