multichannel WAV player
Here is a propeller-based WAV file player built to provide two separate soundtracks for a vintage airplane museum exhibit.
One sound channel plays an airplane engine starting sound when a person pushes a big "START" button in the cockpit, while the other channel provides background continuous air traffic control sound.
The board is a standard propeller proto-board with an added SD card socket, terminal connectors and a bunch of resistors and capacitors for I/O filtering. I forgot to get a photo of it before boxing it for shipping, but I'll try to get the client to photo it as installed in the airplane.
The code worked out to fit nicely within the Spin cog/object structure. Here's an ASCII-art code layout diagram:
'
'· top
wavPlayer[noparse][[/noparse]0] ---- fileReader[noparse][[/noparse]0]
cardReader
'········ ··········· \ DAC cog_0·········································· /······ \
'·············································································· /····· ··· \ SD cog
'···· ············· wavPlayer[noparse][[/noparse]1] ---- fileReader[noparse][[/noparse]1]
/
'······· ··········· \ DAC cog_1······························ ······· /
'········································································· /
'·········· ······· wavPlayer[noparse][[/noparse]2] ---- fileReader[noparse][[/noparse]2]
/
'············· ····· \ DAC cog_2·································· /
'····································································· /
'·················· ·...············································ /
'·································································· /
'················ · wavPlayer[noparse][[/noparse]5] ---- fileReader[noparse][[/noparse]5] ---/
'··················· ·\ DAC cog_5························
'
The "top" method declares an array of wave players. They all share a single instance of an SD card reader, but apart from that, the players are independent. They can start and play different files simultaneously, as fast as the card reader can deliver data.
Each wave player works with its own file reader. A wave player asks its file reader to open a named WAV file. The file reader searches the FAT16 root directory, locates the file and returns its first data sector. The wave player reads the WAV header to find the playback speed of this WAV file, then starts an assembly cog runnning an audio output DAC.
The wave player waits for a person to hit a start button. When the button pin goes low, the player starts to play. Each wave player keeps its own sector cache. If it is empty, it asks its file reader to deliver the next sector of data, passing the address of its buffer. The file reader traverses the FAT16 file, determines the next sector to deliver and asks the card reader to deliver it to the passed-in address. The card reader reads the requested sector from the card and writes the sector data via the passed-in address directly into the wave player buffer.
Each wave player assembly DAC cog also has its own cache. While data is in its cache, it plays sounds via its DAC, timed according to its particular WAV file playback rate. Between notes, it checks its cache, and when room is available in the cache, and there is time before playing the next note, it reads another long from its hub wav player buffer to repopulate its cache. When the file ends, the DAC shuts down and the wave player waits for the user input button to be hit again. Or it plays continuously if the button is held low.
Each file reader manages its own file, tracking the file sectors through the FAT, traversing each cluster. Each has its own FAT cache, so when each cluster is finished, it first checks its cached FAT to try to find the next cluster, reloading its cached FAT only if the next cluster isn't found in it.
The card reader just processes requests to retrieve a sector of data, writing the sector data to the passed-in address, regardless of which channel is requesting data, or whether it is delivering WAV data to a wave player buffer or a FAT sector to a file reader buffer.
When I started thinking about how to expand a wave player beyond being a single channel player, I didn't want to just go from a hardcoded single channel to a hardcoded two channel player. It seemed like it would be a nice design to make the player completely general, to be able to handle as many channels as available cogs. Then, since it was possible, we started talking about how cool it would be to have two more channels that would play a "fly-by" sound track, where a plane sound sweeps across the museum from one side to the other, so I added two more audio I/O hardware channels - input switch and output channel filtering resistors and caps, and external terminal connectors. (Input and output filter circuitry is documented at the top of the "wavPlayer.spin" file.)
We never did develop the fly-by idea, but since the hardware was in place, I enabled a third channel on this player to be able to play another audio WAV file of a mono fly-by sound. I didn't do any hard speed tests, but the board is able to play all three WAV files simultaneously (each is about 11 kHz, 8 bit mono). Since I've played 44 kHz stereo in the past, I think that this would be able to manage all six channels of slower rate WAVs.
The channels play generic filenames "SOUND1.WAV", "SOUND2.WAV", etc, so the sounds can be changed by simply plugging the SD card into a PC and copying different files to "SOUNDn.WAV". The size of file that can be played is limited only by the 2 gig limit of the FAT16 filesystem.
There are a few remaining glitches. The main problem was that something in the cache initialization isn't right, so on the very first play after power-up (but only then) the DACs would always play a nasty burst of noise before playing the actual WAV file. To get this project out onto the museum floor, I made a work-around where the DAC is muted for about the first quarter second of play. Its an ugly fix, but it works. I beat my brains out trying to track down that initialization bug but never did track it down.
Also I used a few shortcuts. Ideally the player would play every DAC sample, starting from where the WAV header specified, and only playing the specified number of samples. But to make it easy, I just started playing from the second sector of the WAV file onwards, and played through the last full file sector. With thousands of sectors in the typical WAV file, these shortcuts make very little difference, but eventually I'd prefer to get it just right, if there ever is a version 2 of this project.
I am far from an expert programmer, so any beginners, please don't take this project as the best way to do propeller coding. It was fun, and it works, but I cut too many corners to be comfortable with it. Error-reporting, in particular, is basically nonexistant. I do make many error checks - does the SD card initialize properly, does it contain a valid filesystem, does each sound file exist, do they contain valid WAV headers, etc., but this board doesn't do anything on receipt of an error. I'd like to at least make it flash an LED or something.
When I first was learning how to get the propeller to read memory cards, I developed the capability to manage both SD and MMC cards, and both FAT12 and FAT16 filesystems. And with WAV files, I initially included the ability to play 4, 8 or 16 bit files, mono or stereo, at any data rate. But to simplify this project I ripped out just about every one of them options. So this player only reads FAT16 files from the root of an SD card and plays 8 bit mono files. But the playback rate adjustment is so trivial that this maintains the ability to play just about any digitization rate. Also, this player only plays uncompressed WAV type (PCM) sound files.
This project works quite well. Turn it on, push a button and clear, rich sound plays! Push the other button and the second channel plays! You can even have the two channels play the same file, but at slightly staggered start times for some weird echo effects. This was a fun project, and would be well worth developing an updated version someday.
David
One sound channel plays an airplane engine starting sound when a person pushes a big "START" button in the cockpit, while the other channel provides background continuous air traffic control sound.
The board is a standard propeller proto-board with an added SD card socket, terminal connectors and a bunch of resistors and capacitors for I/O filtering. I forgot to get a photo of it before boxing it for shipping, but I'll try to get the client to photo it as installed in the airplane.
The code worked out to fit nicely within the Spin cog/object structure. Here's an ASCII-art code layout diagram:
'
'· top
wavPlayer[noparse][[/noparse]0] ---- fileReader[noparse][[/noparse]0]
cardReader
'········ ··········· \ DAC cog_0·········································· /······ \
'·············································································· /····· ··· \ SD cog
'···· ············· wavPlayer[noparse][[/noparse]1] ---- fileReader[noparse][[/noparse]1]
/
'······· ··········· \ DAC cog_1······························ ······· /
'········································································· /
'·········· ······· wavPlayer[noparse][[/noparse]2] ---- fileReader[noparse][[/noparse]2]
/
'············· ····· \ DAC cog_2·································· /
'····································································· /
'·················· ·...············································ /
'·································································· /
'················ · wavPlayer[noparse][[/noparse]5] ---- fileReader[noparse][[/noparse]5] ---/
'··················· ·\ DAC cog_5························
'
The "top" method declares an array of wave players. They all share a single instance of an SD card reader, but apart from that, the players are independent. They can start and play different files simultaneously, as fast as the card reader can deliver data.
Each wave player works with its own file reader. A wave player asks its file reader to open a named WAV file. The file reader searches the FAT16 root directory, locates the file and returns its first data sector. The wave player reads the WAV header to find the playback speed of this WAV file, then starts an assembly cog runnning an audio output DAC.
The wave player waits for a person to hit a start button. When the button pin goes low, the player starts to play. Each wave player keeps its own sector cache. If it is empty, it asks its file reader to deliver the next sector of data, passing the address of its buffer. The file reader traverses the FAT16 file, determines the next sector to deliver and asks the card reader to deliver it to the passed-in address. The card reader reads the requested sector from the card and writes the sector data via the passed-in address directly into the wave player buffer.
Each wave player assembly DAC cog also has its own cache. While data is in its cache, it plays sounds via its DAC, timed according to its particular WAV file playback rate. Between notes, it checks its cache, and when room is available in the cache, and there is time before playing the next note, it reads another long from its hub wav player buffer to repopulate its cache. When the file ends, the DAC shuts down and the wave player waits for the user input button to be hit again. Or it plays continuously if the button is held low.
Each file reader manages its own file, tracking the file sectors through the FAT, traversing each cluster. Each has its own FAT cache, so when each cluster is finished, it first checks its cached FAT to try to find the next cluster, reloading its cached FAT only if the next cluster isn't found in it.
The card reader just processes requests to retrieve a sector of data, writing the sector data to the passed-in address, regardless of which channel is requesting data, or whether it is delivering WAV data to a wave player buffer or a FAT sector to a file reader buffer.
When I started thinking about how to expand a wave player beyond being a single channel player, I didn't want to just go from a hardcoded single channel to a hardcoded two channel player. It seemed like it would be a nice design to make the player completely general, to be able to handle as many channels as available cogs. Then, since it was possible, we started talking about how cool it would be to have two more channels that would play a "fly-by" sound track, where a plane sound sweeps across the museum from one side to the other, so I added two more audio I/O hardware channels - input switch and output channel filtering resistors and caps, and external terminal connectors. (Input and output filter circuitry is documented at the top of the "wavPlayer.spin" file.)
We never did develop the fly-by idea, but since the hardware was in place, I enabled a third channel on this player to be able to play another audio WAV file of a mono fly-by sound. I didn't do any hard speed tests, but the board is able to play all three WAV files simultaneously (each is about 11 kHz, 8 bit mono). Since I've played 44 kHz stereo in the past, I think that this would be able to manage all six channels of slower rate WAVs.
The channels play generic filenames "SOUND1.WAV", "SOUND2.WAV", etc, so the sounds can be changed by simply plugging the SD card into a PC and copying different files to "SOUNDn.WAV". The size of file that can be played is limited only by the 2 gig limit of the FAT16 filesystem.
There are a few remaining glitches. The main problem was that something in the cache initialization isn't right, so on the very first play after power-up (but only then) the DACs would always play a nasty burst of noise before playing the actual WAV file. To get this project out onto the museum floor, I made a work-around where the DAC is muted for about the first quarter second of play. Its an ugly fix, but it works. I beat my brains out trying to track down that initialization bug but never did track it down.
Also I used a few shortcuts. Ideally the player would play every DAC sample, starting from where the WAV header specified, and only playing the specified number of samples. But to make it easy, I just started playing from the second sector of the WAV file onwards, and played through the last full file sector. With thousands of sectors in the typical WAV file, these shortcuts make very little difference, but eventually I'd prefer to get it just right, if there ever is a version 2 of this project.
I am far from an expert programmer, so any beginners, please don't take this project as the best way to do propeller coding. It was fun, and it works, but I cut too many corners to be comfortable with it. Error-reporting, in particular, is basically nonexistant. I do make many error checks - does the SD card initialize properly, does it contain a valid filesystem, does each sound file exist, do they contain valid WAV headers, etc., but this board doesn't do anything on receipt of an error. I'd like to at least make it flash an LED or something.
When I first was learning how to get the propeller to read memory cards, I developed the capability to manage both SD and MMC cards, and both FAT12 and FAT16 filesystems. And with WAV files, I initially included the ability to play 4, 8 or 16 bit files, mono or stereo, at any data rate. But to simplify this project I ripped out just about every one of them options. So this player only reads FAT16 files from the root of an SD card and plays 8 bit mono files. But the playback rate adjustment is so trivial that this maintains the ability to play just about any digitization rate. Also, this player only plays uncompressed WAV type (PCM) sound files.
This project works quite well. Turn it on, push a button and clear, rich sound plays! Push the other button and the second channel plays! You can even have the two channels play the same file, but at slightly staggered start times for some weird echo effects. This was a fun project, and would be well worth developing an updated version someday.
David
Comments
That sounds like a fun project! Great Job!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Hm... that sounds like stuff that'd be useful to add to the SD card object in the Object Exchange.
later,
Marty
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Lunch cures all problems! have you had lunch?
So I went to an older project I'd made and used my own MMC/SD access codes, which, while sort of primitive as compared to Rokikis, had the benefit that I understood them and could figure out how to decouple the card media access from the filesystem layer so multiple files could be opened at once.
Since then, I've been hacking around with lots of different types of data storage - hard disks, floppy drives, MMC and SD cards, FAT12, FAT16 and FAT32 filesystems. From all that, I've been trying to sort of mentally map out a useful set of storage layers sort of like this -
hardware layer - SD/MMC card, hard drive, floppy, etc. Initialization, read sector, write sector, shut down.
filesystem layer - FATnn or something else; directory search, query available remaining space, formatting, etc.
file operations - open, close, read, write, create, delete.
higher level data operations - play WAV sounds, read or edit text files, etc.
The idea would be that many common methods would be available for developers, and a person could select and develop or plug in whichever layer his particular project needed, without having to be an expert in every underlying layer as well.
So in a way, this WAVE player is a start in that direction. I even tested this in early stages by reading text files from the SD card and displaying them on an LCD, so the layers are separated enough to be pretty versatile.
If I'm able to make much progress with such a thing, I'm thinking that'd be be a great object for the exchange.
This definitely would beef up the sound effects on the Hydra as a dedicated sound effects PROP along side a dedicated video PROP.
I am waiting for an SDcard interface that is in transit.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
"Everything in the world is purchased by labour; and our passions are the only causes of labor." -- David·Hume (1711-76)········
I agree! that's really Excellent work!... I wanted to do something like that as far as making a crude interface to a Floppy Disk drive, and then move up to a HDD controller, but never got past studying the communication aspects of such an attempt.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
Along the same lines, I'll post my SX52 hard disk interface board here pretty soon, which I see as one more necessary step towards this idea of enabling general purpose data storage.
It's funny; although the hard disk interface will probably be more useful, my propeller 3 1/2 inch floppy drive interface, which I posted in the propeller forum a few weeks ago, was way more challenging and fun to get working. So I'm probably going to also try to get a 5 1/4 inch floppy disk interface working pretty soon, too.