Setting the Sound Blaster’s Mixer Levels

Mission : Learn how to communicate with the Mixer Be able to set any level, either channel to a
certain intensity

Intro

Now that we have somewhat of an understanding of how to communicate with the Sound Blaster’s DSP, lets extend what we’ve learned to include interfacing with the mixer chip. The only tricky aspect of talking to the mixer is to make sure that 1) We are communicating through the proper port, and 2) that we are setting the appropriate bits before sending the level. That’s all there is to it. For this example we are assuming that the Sound Blaster 16 is present, so we are going to be programming the CT1745 mixer. The CT1745 has a set of ports that make it backwardly compatible with its earlier parents, we will attend to that later. For now we are just using the newer ones.
Something to mention: I refer to the following as “Ports” when actually they are referred as “Registers”. I decided to refer to them as ports because I think they are more like programming ports through the mixer chip than registers. This kind of ideology helps new people understand the interfacing better as well. In all actuality, the mixer address and data ports are the only true “ports”. Here’s the listing:

Mixer Chips

CT1335 Sound Blaster 2.0 CD Interface Card CT1345 Sound Blaster Pro CT1745 Sound Blaster 16

Old Ports

VOCVolume 0×4
MICVolume 0xA
MasterVolume   0×22
FMVolume 0×26
CDVolume 0×28
LineVolume 0x2E

New Ports

MasterLeft 0×30 LineLeft 0×38 InputGainRight 0×40
MasterRight 0×31 LineRight 0×39 OutputGainLeft 0×41
VocLeft 0×32 MicVolume 0x3A OutputGainRight 0×42
VocRight 0×33 PCSpeaker 0x3B AGC 0×43
MidiLeft 0×34 OutPutSwitches 0x3C TrebleLeft 0×44
MidiRight 0×35 InputL 0x3D TrebleRight 0×45
CDLeft 0×36 InputR 0x3E BassLeft 0×46
CDRight 0×37 InputGainLeft 0x3F BassRight 0×47

As you can see, we have a listing of the old ports that were included to be backwardly compatable along with the new ones. The old ones are actually mapped into the new ones. The only ports that need explaining are the 0x3C-0x3E. OuputSwitches is used with a 5 bit number to tell the mixer which channels should be on or off. InputL and InputR are used with a 7 bit number to tell the mixer what channels to read for input. Now that we know what registers/ports are used for what, we need to know how to communicate with the mixer chip. Remember that the before we set the mixer ports in SetIO() as:

  1. MixerAddr= (BLASTER.IOAddr + 0×0004);
  2. MixerData= (BLASTER.IOAddr + 0×0005);

This means that we still need to do the initializing the same way we did before in order to communicate with the mixer. The nice thing about talking to the mixer is that we no longer have to wait for anything. Here is a short example that sets the master volume’s left channel to half of its maximum volume:

  1. BYTE masterleft=16;
  2. outp(MixerAddr,MasterLeft);
  3. outp(MixerData,masterleft<<3);

There we go! We send the mixer's address port the port number that we want to set, then send the data to the mixer's data port! One thing that you should be wondering about is the bit sifts to the masterleft variable. Each port has its bits aligned to a certain position, and have certain bits reserved for other stuff. For this reason we have to be sure to check what port we are trying to set, and then make the appropriate shifts if necessary. Before going into that in depth, lets create a structure that can hold all of the possible mixer levels settings. This way if we want to set multiple ports at once, we can fill this puppy in and send it off to a function that traverses through it and sets all of them in 1 shot!

  1. typedef struct Blaster_Mixer_Struct
  2. {
  3.     BYTE masterleft,masterright;
  4.     BYTE vocleft,vocright;
  5.     BYTE midileft,midiright;
  6.     BYTE cdleft,cdright;
  7.     BYTE lineleft,lineright;
  8.     BYTE micvolume;
  9.     BYTE pcspeaker;
  10.     BYTE outputswitches;
  11.     BYTE inputswitchesleft;
  12.     BYTE inputswitchesright;
  13.     BYTE inputgainleft,inputgainright;
  14.     BYTE outputgainleft,outputgainright;
  15.     BYTE agc;
  16.     BYTE trebleleft,trebleright;
  17.     BYTE bassleft,bassright;
  18. }MixerStruct;

Here's our pretty structure for holding information about the mixer chip's levels. In the class we will be developing (a continuation from Lesson 1), we are going to use 2 instances of this structure. One will be the new settings, and the other will hold the settings that were saved at program start-up. Before we start screwing around with the mixer's settings, lets devise a way to save the existing levels, so we can return them after our program has finished. Lets layer it so that all we have to do is figure out the setting for 1 level, and then build on that to be able to save all levels. Here's the function that will return the setting for 1 level.

  1. BYTE SB16::GetMixerSetting(BYTE Source)
  2. {
  3.     BYTE temp;
  4.     outp(MixerAddr,Source);
  5.     temp=inp(MixerData);
  6.  
  7.     // The bit shifts are to align the bits to the lower end of the BYTE
  8.  
  9.     if(Source >=0x30 &amp;&amp; Source <=0x3A)
  10.     {
  11.         return temp>>3;
  12.     }
  13.     else
  14.     {
  15.         if(Source >=0x3F &amp;&amp; Source <=0x42)
  16.             return temp>>6;
  17.         else
  18.             if(Source >=0x44 &amp;&amp; Source <=0x47)
  19.                 return temp>>4;
  20.     }   
  21. }

To read a setting, we specify the port as normal through the MixerAddr port, then all we have to do is do an inp on the mixer's data port (MixerData). The only problem is that the mixer returns the value of the port with the bits still aligned to its weird scheme. To correct this we see what port was being looked at. Was it between 0x30 and 0x3A? If so shift it right 3 bits. Was it between 0x3F and 0x42? If so then shift it 6 bits to the right. Was it between 0x44 and 0x47? Again, if so shift it 4 bits to the right. This will give us a real value that we can use that is properly aligned. Now that we can read the value of one port, lets create a function that goes through all of the
mixer's ports, and also traverses through the elements in our structure to save the settings before we mess them all up!

  1. void SB16::ReserveOldMixerSettings()
  2. {
  3.     BYTE *pointer=&amp;OldMixerSettings.masterleft;
  4.  
  5.     for(BYTE index=0x30;index<=0x47;index++)
  6.     {
  7.         *pointer=GetMixerSetting(index);
  8.         pointer++;
  9.     }
  10.     NewMixerSettings=OldMixerSettings; //Initialize new levels to old ones
  11. }

Initially, this was a huge function that did a GetMixerSetting for every item, with everything written out. About 30 minutes later i realized that i could just use a pointer and an index to traverse through both, effectively cutting my function size down 70%, and making me feel reeeeealy stupid :) All this function is doing is creating a pointer to the 1st element in the OldMixerSettings structure, and creating an index (index) that will move through each port. Through each cycle, it goes to the next port number (index++) and to the next element in the structure (pointer++). This wouldn't work too well if the data types were different, but we lucked out and they are all BYTE's!!! The only thing left on our agenda is to create a function that sets the channels we want to the levels desired. These functions assume that NewMixerSettings and OldMixerSettings are both instances of the MixerStruct structure. I decided to do it this way instead of creating a function that is passed the address of an instance simply because we won't be messing with volume levels too often. Hey, it works for me!

  1. void SB16::SetMixerSettings()
  2. {
  3.     BYTE *pointer=&amp;NewMixerSettings.masterleft;
  4.  
  5.     for(BYTE index=MasterLeft;index<=MicVolume;index++)
  6.     {
  7.         outp(MixerAddr,index);
  8.         outp(MixerData,(*pointer<<3));
  9.         pointer++;
  10.     }
  11.  
  12.     outp(MixerAddr,PCSpeaker);
  13.     outp(MixerData,NewMixerSettings.pcspeaker<<6);
  14.  
  15.     pointer = &amp;NewMixerSettings.outputswitches;
  16.     for(BYTE index=OutPutSwitches;index<=InputR;index++)
  17.     {
  18.         outp(MixerAddr,index);
  19.         outp(MixerData,*pointer);
  20.         pointer++;
  21.     }
  22.  
  23.     pointer = &amp;NewMixerSettings.inputgainleft;
  24.     for(BYTE index=InputGainLeft;index<=OutputGainRight;index++)
  25.     {
  26.         outp(MixerAddr,index);
  27.         outp(MixerData,(*pointer)<<6);
  28.         pointer++;
  29.     }
  30.    
  31.     outp(MixerAddr,AGC);
  32.     outp(MixerData,NewMixerSettings.agc);
  33.  
  34.     pointer = &amp;NewMixerSettings.trebleleft;
  35.     for(BYTE index=TrebleLeft;index<=BassRight;index++)
  36.     {
  37.         outp(MixerAddr,index);
  38.         outp(MixerData,(*pointer)<<4);
  39.         pointer++;
  40.     }
  41. }

This function looks the weirdest by far, but it is saving us a LOT of hassle. All the programmer needs to know, are the maximum levels for each channel that they are trying to set. Knowing this we can simply set a level without knowing its bit shifting scheme. This means that the NewMixerSettings and the OldMixerSettings structures contain UNALIGNED data. When the settings are read in, they are shifted before being returned. When the settings are finally set, they are shifted back into there correct positions creating harmony once again :) The only thing left to look at is the table with all of the bit positions so we know the maximum levels we can set stuff at! Here it is:

Registers/Port Listing

Bit Number
Index 7 6 5 4 3 2 1 0
0x00 Reset Mixer
0x04 Voice Volume (L) Voice Volume (R)
0x0A . Mic Volume
0x22 Master Volume (L) Master Volume (R)
0x26 MIDI Volume (L) MIDI Volume (R)
0x28 CD Volume (L) CD Volume (R)
0x2E Line Volume (L) Line Volume (R)
0x30 Master Volume (L) .
0x31 Master Volume (R) .
0x32 Voice Volume (L) .
0x33 Voice Volume (R) .
0x34 MIDI Volume (L) .
0x35 MIDI Volume (R) .
0x36 CD Volume (L) .
0x37 CD Volume (R) .
0x38 Line Volume (L) .
0x39 Line Volume (R) .
0x3A Mic Volume .
0x3B PC Speaker Volume .
0x3C Output Mixer Switches
. . Line.L Line.R CD.L CD.R Mic
0x3D Input Mixer (L) Switches
. . MIDI.L MIDI.R Line.L Line.R CD.L CD.R Mic
0x3E Input Mixer (R) Switches
. . MIDI.L MIDI.R Line.L Line.R CD.L CD.R Mic
0x3F Input Gain (L) .
0x40 Input Gain (R) .
0x41 Output Gain (L) .
0x42 Output Gain (R) .
0x43 . AGC
0x44 Treble (L) .
0x45 Treble (R) .
0x46 Bass (L) .
0x47 Bass (R) .

Well there we go! Those couple of functions give us complete control over the Sound Blaster's
Mixing Chip!

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>