
   ASoundPlayer is the base class for abstracting any platform specific 
implementation to perform actual sound playback.  The derived class is to 
implement the platform specific code.  CSoundPlayerChannels are logically 
attached to an ASoundPlayer object and when the protected method,
ASoundPlayer::mixSoundPlayerChannels(), is called by the derived class for 
more data to output, all these outstanding objects in a playing state perform 
software mixing onto the given buffer.  

   The derived class is supposed to constantly feed audio to the playback 
device for that platform and get its data from calling 
ASoundPlayer::mixSoundPlayerChannels(...) which fills a buffer with the 
currently playing audio data.  


--------------------------


Now, here's how I prebuffer a data for each CSoundPlayerChannel object and 
still know where the current playing position is.  Normally, if you just wrote 
2 seconds of sound to the card the known play position would be 2 seconds ahead 
of what's coming out of the sound card.  One might say just to always subtract 
2 seconds from the known play position.  However, this would not be able to 
handle looped sections of sound and skipped sections of sound without some very
 complicated fixups.  

Furthermore, from looking at the source, the Linux emu10k driver seems to limit 
the amount of prebuffered data to a maximum of 64k -- only allowing about 1/3 of 
a second worth of CD quality sound.  

Since ReZound does heavy disk reading while playing, this would sometimes causes 
a momentary gap in the audio, especially when playing several channels of sound 
simultaneously.  Perhaps other sound card drives don't have this limit, and ALSA 
may not have that problem either.

However, to be able to prebuffer data and still be able to know the play position 
(actually, you can never know exactly what's coming out of the speakers unless you 
know the exactly hardware latency of the card as well) I have two fifos:

	The 'audio' fifo contains the actual prebuffered audio data and the other 
	'play position' fifo contains a play position for every N samples (where 
	N is something like 1000 so the second fifo passes 1000th the data of the 
	first).  

The general idea is that I consume the 'play position' fifo at 1/Nth the rate at 
which I consume data from the audio fifo.  And each time I read an element from the 
'play position' fifo I update the play position value being read by the frontend.  

The frontend calls CSoundPlayerChannel::getPlayPostion() which returns 
CSoundPlayerChannel::playPosition while CSoundPlayerChannel::prebufferPosition is 
the pointer into the audio where we prebuffer from.

Then the play thread reads from the fifos and the prebuffer thread (per 
CSoundPlayerChannel object) writes to these fifos.  The writing to the fifo is 
will block, while the reading from the fifo does not.  The read is non-blocked for 
the sake of JACK.  Its rules state that you aren't to do anything that could block 
in your callback.  This also means that the memory allocated to the fifo also has 
to be locked into RAM.

Doing it this way allows me to control just how much data is actually 
prebuffered beyond what the audio driver allows.

   
The ASoundPlayer object knows of each CSoundPlayerChannel object (it 
instantiated them).  Each CSoundPlayerChannel object has a thread running which 
is constantly feeding the write end of the fifos which does block on writes when 
pipe is full. Care must be taken to invalidate the prebuffered data whenever a 
change is made to the data, loop positions, or the prebuffer position.  The 
prebuffered data is invalidated by simply calling a method that clears the fifos.  
It may sound ludicris at first to have one thread per CSoundPlayerChannel object, 
but most of the time these threads should be in a wait-state while the prebuffer 
pipe is full or while the channel is not playing.  



Also, I use a condition variable waiting mechanism to cause non-playing channels 
to wait.  Hopefully, the thread library on whatever platform should be decent 
enough to schedule well.  

The thread which is actually reading the data from a CSoundPlayerChannel's audio 
fifo (by calling CSoundPlayerChannel::mixOntoBuffer()) is doing non-blocking reads 
so that if the data is not available for a channel, then it will simply be silent 
for that chunk of data (again, to follow JACK's rules).  Furthermore, having a 
separate thread for each object will make better use of multiple CPUs if they're 
available (i.e. two channels could be calculating their data at exactly the same 
time).

All the calculation of sample-rate-conversion/play-speed are calculated based on 
the data read from the pipe instead of calculating it before writing to the pipe.  
This allows a more real-time control of these parameters.  

Someday I will probably have a configurable signal bus that the data being written 
to the pipe should be passed through so that I could have non-destructive effects 
on the sound, however *these* signal modifications would be prebuffered calculations.  

