It's been a while since I worked on the Propeller S/PDIF decoder, and there are really two reasons for that.
One reason was that I was having trouble wrapping my head around what the best way would be to get the subchannel data out of the subchannel decoders. The other reason was that I realized that the maximum speed (115200bps) of the FullDuplexSerial module from the Propeller library would probably not be fast enough to keep up with the subchannels.
An audio CD plays 44100 frames of audio per second, each divided into two subframes: one for the left channel, one for the right channel. Besides the audio information, each subframe also has two bits that are used to store and transmit extra information about the CD, such as track markers. Each subchannel can be regarded as a bitstream that's multiplexed into the main data stream, so the subchannel data is transferred at 88200 bits per second for each subchannel on a CD. On a DAT tape that's recorded at 48kHz, the bitrate for the subchannels is 96 kilobits per second for each subchannel; on a DAB tuner that generates 32kHz audio, the subchannels run at 32 kbps each.
There is some interesting information in the subchannels, which is what I want to get to in this project. But even though the subchannels generate a lot less data than the total volume of data coming in through S/PDIF, the maximum of 96 kbps is still a lot of data.
When I wrote the subchannel decoder module for the project, I was mostly focused on demultiplexing the subchannel bits, and putting them in memory in some efficient way to analyze them by comparing them to known values, or whatever. Though the timing of the subchannel decoder is not nearly as tight as the biphase decoder, there's still too much to do to let the subchannel decoder take care of all the decoding. Besides, each type of medium encodes the data in a different way and I wanted to be able to use the module for the Channel Status subchannel as well as the User Data subchannel.
For synchonization, the subchannels are organized in Blocks, and at every start of a block, the transmitter uses a special preamble. I wrote the original subchannel decoder to gather up all the bits in a block, and then copy all those bits to the hub at the end of the block. I used a counter to make it possible to see if a block didn't get decoded fast enough.
Subchannel Decoder Rewrite
The original implementation of the subchannel decoder turned out to not be very efficient or convenient. I decided to do a partial rewrite based on the following:
- Instead of writing an entire block at the end of an incoming block, the code now copies the subdata to the hub one longword at a time. 32 divides evenly into 384 and 192 (the number of bits per block) so this is convenient.
- Because of this, it was no longer possible for other cogs to recognize whether an incoming block of data was ready for processing. To fix this, I changed the code to use two buffers instead of one. One buffer gets written by the subchannel decoder while the other buffer is processed further, elsewhere. Also, to indicate that a buffer is ready for processing, I made the code use two locks (one per buffer).
The subchannel module makes the pointers to the buffers and the lock numbers available, which makes it relatively easy for another cog to process the buffers using Spin or PASM (though Spin is likely to be much too slow for all but the simplest decoding). Such an analysis module (which will be written in the future) would:
- Set the lock for a buffer and check if the lock was taken. If no, repeat 1.
- Process the buffer
- Optionally set the lock again to see if there is an overrun situation
- Switch to the other lock and the other buffer.
- Repeat from step 1.
Improving the Output Stage
The second problem that I wanted to deal with, was that I had run into a bit of a wall: at 48000kHz, each subchannel bit comes in at 96kbps, and if I wanted to do a hex dump of a subchannel, I would have to deal with dropping lots of data because the serial driver that's part of the Propeller library just isn't fast enough: 115200bps.
Fortunately I found an open-source module on OBEX (the Parallax Object Exchange website where programmers could store their open-source projects). It's a module called tx.spin, written by a guy called Barry Meaker. It's a module with an Assembler routine that can only transmit (not receive) but at very high speeds. He claimed speeds of almost a megabit per second, but with a few simple optimizations I actually got it up to 4 megabits per second. Now we're talking!
The module is heavily specialized towards transmitting null-terminated strings, and if you want to print a decimal or hexadecimal number, the Spin subroutine basically prepares a buffer and then stores a pointer that is picked up and printed by the PASM code. Afterwards, the PASM code resets the pointer to let other cogs know it's available.
I want to change this (EDIT: Done, see next log) so the pointer changes into a command with a buffer and a length. Commands can be:
- Print a nul-terminated string as before (no length needs to be given in the command)
- Print a buffer with a given length (that way all possible values can be printed)
- Print a single character
- Print a decimal number (I just discovered there's an easy algorithm to convert binary numbers to BCD in a few assembler instructions, called the "double dabble" algorithm)
- Print a hexadecimal number
- Do a hex dump of a buffer
The last command in that list should be particularly useful to decode the bits in the subchannel streams and analyze the values showin in the terminal window. Then at a later stage, I can add some code to do the analysis and print interesting information on the terminal at a speed that's high enough to make it possible to keep up with all the subframes.
(Note, the source code for these changes is in a new branch "subchannel_rewrite" for now. The changes will be merged back to the main branch as soon as they do something useful. Currently the code doesn't compile successfully because the subchannel decoder API changed. As soon as I get, say, a hexdump of a subchannel to work, I'll merge it back to the main branch).