Sound Playback and Recording Using Auto Init DMA Mode in 8 or 16 bits

Intro

This tutorial is basically and extension of the final tutorial dealing with programming the Sound Blaster card itself and the realtime mixing schemes. Pay close attention! We renamed some of our routines and stuck in a couple of nice programming tricks like function pointers to make a sweet working system! Without further hestitation, lets get into how this all fits together! Here’s a peak at the header file additions/changes:

  1. #define DSP_HALT_16_AUTO_INIT           0xD9
  2. #define lobyte(x)(char)(x&0x00ff)
  3. #define hibyte(x)(char)((x&0xff00)>>8)
  4. Sample_ptr SampleHead;
  5. Sample_ptr SampleTail;
  6. Sample_ptr CurrentSample;
  7. Sample_ptr FrontLink;
  8. Sample_ptr EndLink;
  9. void Play(unsigned int);
  10. void FillBuffer8();
  11. void FillBuffer16();
  12. long MixSamples();
  13. char ClipChar(long);
  14. short ClipShort(long);
  15. short GetSample8(Sample_ptr);
  16. short GetSample16(Sample_ptr);
  17. short (SB16::*GetSample)(Sample_ptr);
  18. void (SB16::*MixingFunction)();
  19. void Auto8MonoRecord();
  20. void Auto8StereoRecord();
  21. void Auto16MonoRecord();
  22. void Auto16StereoRecord();
  23. void SetRecordState(char);
  24. void SetPtrFunctions();
  25. char Recording;

This is the Realtime Mixing Structures tutorial code with some adjustments. Notice the use of 8 and 16 in function names. I hope this makes things easier for us! Lets start going through the code top to bottom!

  1. SB16::SB16()
  2. {
  3.     DSPVersionNum=0;
  4.     Done=0;
  5.     SideToFill=1;                       //since starting routine will gen interrupt
  6.     TransferLength=BUFFSIZE;            //by default
  7.     SoundRate=11025;                    //by default
  8.     ModeBits=8;                         //by default, later we will load from a config file!
  9.     ModeStereoMono=0;                   //Mono by default
  10.     TransferMode=0;                     //Direct Mode by default 1=ss 2=ai
  11.     Recording=0;                        //Playback by default
  12.     GetBlasterID();
  13.     DSP_Reset();
  14.     DisplayEnv();
  15.     ReserveOldMixerSettings();
  16.     Sounds= new SoundInfo[NumberOfSounds];
  17.     WriteDSP(0xD1);                     //turn speaker on if its off
  18.     SampleHead = new SampleHeader();
  19.     if(SampleHead == NULL)
  20.     {
  21.         cout<<"SampleHead NOT Allocated!\n";exit(1);
  22.     }
  23.     SampleTail = SampleHead;
  24. }

So far we only introduce the Recording variable which will be set to 1 if we are recording, and 0 if we are going to do playback.

  1. SB16::~SB16()
  2. {
  3.     NewMixerSettings=OldMixerSettings;
  4.     if(TransferMode==2) //AutoInit
  5.     {
  6.         if(ModeBits==8)
  7.         {
  8.            WriteDSP(DSP_HALT_8_AUTO_INIT);
  9.         }
  10.         else
  11.         {
  12.            WriteDSP(DSP_HALT_16_AUTO_INIT);
  13.         }
  14.     }
  15.  
  16.     SetMixerSettings();
  17.     for(int x=0;x<NumberOfSounds;x++)
  18.     {
  19.         Unload_Sound((unsigned char*)Sounds[x].Sound);
  20.     }
  21. }

Here, we check to see if we are in AutoInitialization Mode, and if so, 8 or 16 bit mode. Send the DSP the appropriate DSP_HALT_X_AUTO_INIT to stop any current processes.

  1. void SB16::SetupDMA()
  2. {
  3.     short tsize=BUFFSIZE;
  4.  
  5.     disable();
  6.     if(ModeBits==8)
  7.     {
  8.         dma.SetDMAChannel(BLASTER.DMAChan);
  9.     }
  10.    
  11.     if(ModeBits==16)
  12.     {
  13.         dma.SetDMAChannel(BLASTER.HDMAChan);
  14.     }
  15.  
  16.     dma.DisableChannel();    //Disable DMA channel while programming it
  17.     if(TransferMode==1)
  18.     {
  19.         dma.SetControlByteMask(DemandMode,AddressIncrement,SingleCycle,ReadTransfer);
  20.         //Put SingleCycle mode
  21.     }
  22.     else if(TransferMode==2)
  23.     {
  24.         if(Recording)
  25.         {
  26.             dma.SetControlByteMask(DemandMode,AddressIncrement,AutoInit,<b>WriteTransfer</b>);
  27.         }
  28.         else
  29.         {
  30.             dma.SetControlByteMask(DemandMode,AddressIncrement,AutoInit,<b>ReadTransfer</b>);
  31.         }
  32.        
  33.         TransferLength=HALFBUFFSIZE; //for SB!!!
  34.         if(ModeBits==16)
  35.         {
  36.             TransferLength>>=1;
  37.         }
  38.     }
  39.    
  40.     dma.SetControlByte();
  41.     dma.ClearFlipFlop();        //Clear Flip-Flop
  42.     dma.SetBufferInfo();
  43.     if(ModeBits==16)
  44.     {
  45.         tsize>>=1;
  46.     }
  47.    
  48.     dma.SetTransferLength(tsize);
  49.     enable();                         //enable interrupts
  50.     dma.EnableChannel();              //enable DMA channel
  51. }

This has a couple of minor changes that make a LARGE difference! We first set the dma channel according to how many bits we are using, 8 bit use the normal DMA channel, but if 16 bit, use the high DMA channel. When it comes time to call SetControlByteMask, notice the parameters in bold. These have been changed. Not only did we add an if statement to control if we are writing to or reading from memory (Recording vs. Playback), but we also changed the statement for playback. It used to say WriteTransfer. This DID work since oddly, I had assigned that what ReadTransfer is now! That's what made discovering this horrible error a good thing! Anyway, if we are using 16 bits we have to tell the DMA to transfer half as much as we really want since it will be working with 16 bits. For the same reason we tell the Sound Blaster the same thing with the TransferLength variable. Let's move on!

  1. void SB16::ServiceAI()
  2. {
  3.     char temp;
  4.     outp(MixerAddr,0x82);
  5.     MixingFunction();
  6.     temp=inp(MixerData);
  7.     if(temp & 1)
  8.     {
  9.         inp(DSPStatus);
  10.     }
  11.     else
  12.     {
  13.         if(temp & 2) //is the interupt for 16 bit?
  14.         {
  15.          inp(DSPIntAck); //read in from the port       
  16.         }
  17.     }
  18. }

This function has been totally changed, although the functioning is still the same. When we get and interrupt from the Sound Blaster, we need to know who's yelling. The Sound Blaster uses 1 interrupt for 4 different processes. 8 Bit DSP, 16 Bit DSP, MPU401 and MIDI. In order to be able to do Midi with sound playback, we need to figure out which section is requesting the interrupt and act accordingly. To figure this out, we write a 0x82 to the mixer address port, and then read a byte from the mixer data port. The bits in that char will be set to who's calling the interrupt. Bit 1 if the 8 bit Digitized Io or Midi is yelling, 2 if the 16 bit Digitized IO is yelling and bit 3 if it is for the MPU401. Inside each we do the usual acknowledging the interrupt. Also notice that we are calling MixingFunction. This is one of our function pointer. It will be pointing to either the 8 or 16 bit version of our original 8 bit algorithm.

  1. void SB16::SetupAutoInitDSP()
  2. {
  3.     if(ModeBits==8)
  4.     {
  5.         if(!ModeStereoMono)
  6.         {
  7.             if(Recording)      //8 bit Mono Record
  8.             {
  9.                 Auto8MonoRecord();
  10.             }
  11.             else
  12.             {
  13.                 Auto8MonoPlay();    //8 bit Mono Playback
  14.             }
  15.         }
  16.         else
  17.         {
  18.             if(Recording)      //8 bit Stereo Record
  19.             {
  20.                 Auto8StereoRecord();
  21.             }
  22.             else
  23.             {
  24.                 Auto8StereoPlay()//8 bit Stereo Playback
  25.             }
  26.         }
  27.     }
  28.     else
  29.     {
  30.         if(!ModeStereoMono)
  31.         {
  32.             if(Recording)     //16 bit Mono Record
  33.             {
  34.                 Auto16MonoRecord();
  35.             }
  36.             else
  37.             {
  38.                 Auto16MonoPlay()//16 bit Mono Playback
  39.             }
  40.         }
  41.         else
  42.         {
  43.             if(Recording)    //16 bit Stereo Record
  44.             {
  45.                 Auto16StereoRecord();
  46.             }
  47.             else
  48.             {   
  49.                 Auto16StereoPlay();//16 bit Stereo Playback
  50.             }
  51.         }
  52.     }
  53. }

This function has simply been expanded to reflect the possibility that we are trying to record in any of the usual modes. Since we already have covered the playback abilities, lets cover the steps to programming the DSP for the recording. Note that we are writing a 0x42 in each of these functions. We also added the WriteDSP(0x41); for all of the playback functions at the very beginning. This used to be sent with the SetFrequency function, but I moved it outside it to increase modularity. Each of the following functions has been formatted to work with DSP version 4.xx +.

  1. void SB16::Auto8MonoRecord()
  2. {
  3.     WriteDSP(0x42); //select record
  4.     SendFrequency();
  5.     WriteDSP(0xCE);
  6.     WriteDSP(0x00);
  7.     SendLength();
  8. }
  9.  
  10. void SB16::Auto8StereoRecord()
  11. {
  12.     WriteDSP(0x42);
  13.     SendFrequency();
  14.     WriteDSP(0xCE);
  15.     WriteDSP(0x20);
  16.     SendLength();
  17. }
  18.  
  19. void SB16::Auto16MonoRecord()
  20. {
  21.     WriteDSP(0x42);
  22.     SendFrequency();
  23.     WriteDSP(0xBE);
  24.     WriteDSP(0x10);
  25.     SendLength();
  26. }
  27.  
  28. void SB16::Auto16StereoRecord()
  29. {
  30.     WriteDSP(0x42);
  31.     SendFrequency();
  32.     WriteDSP(0xBE);
  33.     WriteDSP(0x30);
  34.     SendLength();
  35. }
  36.  
  37. void SB16::SetPtrFunctions()
  38. {
  39.     if(ModeBits==16)
  40.     {
  41.      MixingFunction=FillBuffer16;
  42.         GetSample=GetSample16;
  43.     }
  44.     else
  45.     {
  46.      MixingFunction=FillBuffer8; //default 8 bit mixing scheme
  47.         GetSample=GetSample8;
  48.     }
  49. }
  50.  
  51. void SB16::SetRecordState(char flag)
  52. {
  53.     Recording=flag;
  54. }
  55.  
  56. void SB16::SetStereoMono(char stereomono)
  57. {
  58.     ModeStereoMono=stereomono
  59. }
  60.  
  61. void SB16::SetFrequency(long frequency)
  62. {
  63.     SoundRate=frequency;
  64. }

This function actually sets our two function pointers to valid functions. Without setting them, we will totally crash the system, TRUST ME :) We also add an access function to the Recording flag, and to two other functions which should have had them before.

  1. void SB16::FillBuffer16()
  2. {
  3.     char *start=(char*)dma.MK_FP(dma.SegInfo.rm_segment,0);
  4.     long sample=0,i;
  5.  
  6.     SideToFill^=1;
  7.     if(SideToFill)
  8.     {
  9.      start+=HALFBUFFSIZE;
  10.     }
  11.     for(i=0;i<(HALFBUFFSIZE>>1);i++,start+=2)
  12.     {
  13.       sample=ClipShort(MixSamples());
  14.      *start=    lobyte(sample);
  15.      *(start+1)=hibyte(sample);
  16.     }
  17. }

If we are in 16 bit mode, MixingFunction will be set to this function. The only big difference is in the for loop. We traverse through our DMA buffer 2 bytes at a time. The DSP wants the low byte, followed by the high byte of the 16 bit sample. The sample is returned by MixSamples() which I should add has been changed to always return a short. I think that about covers this one!

  1. short SB16::GetSample16(Sample_ptr S)
  2. {
  3.     short temp=0;
  4.  
  5.     temp=(Sounds[S->SoundNumber].Sound[S->Position+1]<<8);
  6.     temp+=Sounds[S->SoundNumber].Sound[S->Position];
  7.     S->Position+=2;
  8.     return temp;
  9. }

When we need to fetch a sample from our 16 bit sound, we will get 16 bits at a time, Buuuut since we ready EVERY sample 8 bits at a time (i.e. store it in a unsigned char array) we have to go through some special steps to construct the entire 16 bits from 2 separate 8 bit values. We simply take the second sample, the high bits, shift them up 8 bits and add the lower 8 bits. We then return our newly aligned 16 bit value!

  1. short SB16::ClipShort(long num)
  2. {
  3.     if(num > 32767)
  4.     {
  5.       num = 32767;
  6.     }
  7.     if(num < -32768)
  8.     {
  9.        num = -32768;
  10.     }
  11.  
  12.     return (short)num;
  13. }

We're going to need this little clipping function to keep our mixed samples within the data type!

Possible Implementation

  1. ISRS *isrs = new ISRS();
  2.     isrs->GetSBIRQ(sb16.BLASTER.SBIntr);
  3.     sb16.GetISRPtr(isrs);
  4.     sb16.SetModeBits(16);
  5.     sb16.SetTransferMode(2);
  6.     sb16.SetStereoMono(2);
  7.     sb16.SetFrequency(44100);
  8.     sb16.SetPtrFunctions();
  9.     sb16.NewMixerSettings.vocleft=26;
  10.     sb16.NewMixerSettings.vocright=26;
  11.     sb16.NewMixerSettings.masterright=26;
  12.     sb16.NewMixerSettings.masterleft=26;
  13.     sb16.NewMixerSettings.cdleft=0;
  14.     sb16.NewMixerSettings.cdright=0;
  15.     sb16.SetMixerSettings();
  16.     sb16.Load_Sounds();
  17.     isrs->SetupSBISR();
  18.     sb16.SetupDMA();
  19.     sb16.SetupDSP();

We've made some subtle changes in the code and dramatically increased its usability. Now we load up our sample no matter WHAT type it is 8 or 16, stereo mono and any frequency from 44k - 5k (DSP v4.x+) and just go with it! We are also mixing these samples in real-time with the same efficiency, and with the use of function pointers, we are not checking to see what mode we are in every loop iteration so we are really saving some time!

Thanks for trying to read this. I realize that some of these changes will be hard to understand. Now that the code has been developed up to this point, we can proceed ahead unbound and ready to get into some nice, well deserved special effects! Take care!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>