Mission : Learn how to communicate with the Mixer
Be able to set any level, either channel to a certain intensity
Download the Expansion Pack!
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 | | | 0x4
|
| MICVolume | | 0xA
|
| MasterVolume | | 0x22
|
| FMVolume | | 0x26
|
| CDVolume | | 0x28
|
| LineVolume | | 0x2E
|
New Ports

| MasterLeft | 0x30 | | | LineLeft | 0x38 | | | InputGainRight | 0x40
|
| MasterRight | 0x31 | | LineRight | 0x39 | | OutputGainLeft | 0x41
|
| VocLeft | 0x32 | | MicVolume | 0x3A | | OutputGainRight | 0x42
|
| VocRight | 0x33 | | PCSpeaker | 0x3B | | AGC | 0x43
|
| MidiLeft | 0x34 | | OutPutSwitches | 0x3C | | TrebleLeft | 0x44
|
| MidiRight | 0x35 | | InputL | 0x3D | | TrebleRight | 0x45
|
| CDLeft | 0x36 | | InputR | 0x3E | | BassLeft | 0x46
|
| CDRight | 0x37 | | InputGainLeft | 0x3F | | BassRight | 0x47
|
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:
MixerAddr= (BLASTER.IOAddr + 0x0004);
MixerData= (BLASTER.IOAddr + 0x0005);
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:
BYTE masterleft=16;
outp(MixerAddr,MasterLeft);
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!
typedef struct Blaster_Mixer_Struct
{BYTE masterleft,masterright;
BYTE vocleft,vocright;
BYTE midileft,midiright;
BYTE cdleft,cdright;
BYTE lineleft,lineright;
BYTE micvolume;
BYTE pcspeaker;
BYTE outputswitches;
BYTE inputswitchesleft;
BYTE inputswitchesright;
BYTE inputgainleft,inputgainright;
BYTE outputgainleft,outputgainright;
BYTE agc;
BYTE trebleleft,trebleright;
BYTE bassleft,bassright;
}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.
BYTE SB16::GetMixerSetting(BYTE Source)
{BYTE temp;
outp(MixerAddr,Source);
temp=inp(MixerData);
// The bit shifts are to align the bits to the lower end of the BYTE
if(Source >=0x30 && Source <=0x3A)
{return temp>>3;
}
else
if(Source >=0x3F && Source <=0x42)
{return temp>>6;
}
else
if(Source >=0x44 && Source <=0x47)
{return temp>>4;
}
}
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!
void SB16::ReserveOldMixerSettings()
{BYTE *pointer=&OldMixerSettings.masterleft;
for(BYTE index=0x30;index<=0x47;index++)
{*pointer=GetMixerSetting(index);
pointer++;
}
NewMixerSettings=OldMixerSettings; //Initialize new levels to old ones
}
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!
void SB16::SetMixerSettings()
{
BYTE *pointer=&NewMixerSettings.masterleft;
for(BYTE index=MasterLeft;index<=MicVolume;index++)
{outp(MixerAddr,index);
outp(MixerData,(*pointer<<3));
pointer++;
}
outp(MixerAddr,PCSpeaker);
outp(MixerData,NewMixerSettings.pcspeaker<<6);
pointer = &NewMixerSettings.outputswitches;
for(BYTE index=OutPutSwitches;index<=InputR;index++)
{outp(MixerAddr,index);
outp(MixerData,*pointer);
pointer++;
}
pointer = &NewMixerSettings.inputgainleft;
for(BYTE index=InputGainLeft;index<=OutputGainRight;index++)
{outp(MixerAddr,index);
outp(MixerData,(*pointer)<<6);
pointer++;
}
outp(MixerAddr,AGC);
outp(MixerData,NewMixerSettings.agc);
pointer = &NewMixerSettings.trebleleft;
for(BYTE index=TrebleLeft;index<=BassRight;index++)
{outp(MixerAddr,index);
outp(MixerData,(*pointer)<<4);
pointer++;
}
}
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! If you have any comments, feedback, pointers, suggestions or anything else, please
give me some Feedback