The DirectMusic volume controls you've learned about so far are all relative to the system's mixer volumes. The mixer volumes are what the user changes when he double clicks the speaker icon in the system tray and adjusts the sliders. Usually, sliders are provided for the main volume, as well as specifically for wave audio output, line in, and so on (see Figure 5.3).
Figure 5.3: Windows provides a GUI to the mixer volumes.
Tip |
Tread lightly—the mixer controls adjust the volume of everything on the system, so most users don't like having them changed behind their back. For example, if your game adjusts the master volume, it will not only change its volume, but also the volume of every other application running as well. This might be really rude—consider a user who is listening to MP3s in the background as he Plays your game. If the user goes to your game and adjusts the sound effect or music volume, he would expect the volume of his MP3s to remain constant. Do not use the mixer volume controls as a cheap way to avoid learning how to control your own application's output. |
Adjusting these mixer values programmatically is possible, though it's a maddening exercise of drilling down through several layers of structures and pseudo-objects. This section will show you how.
The architecture of the mixer is summarized in Figure 5.4. There are a lot of structures and pseudo-objects at Play here, so you need to move slowly. If you move too fast and substitute a mixer ID where a control ID should go, you're going to spend a lot of time in the debugger.
Figure 5.4: The mixer API architecture is divided into several different pseudo-objects.
At the top of the mixer architecture is the mixer. Each mixer contains several sources and destinations. You don't modify these sources and destinations directly; instead, you modify imaginary lines that run between them. You can ask the API for the ID of the imaginary line running between any two sources and destinations, or you can ask for the line ID of all sources to one particular destination (for example, the master speaker output).
Each line has multiple controls of various types. You've got your standard issue volume and panning controls, plus potentially more exotic controls, such as meters and equalizers, depending on what sound hardware is in the box. It's worth mentioning that the API also supports completely custom control types—controls that don't fit into any of the pre-defined types. These controls are responsible for disPlaying their own GUI.
Each control usually has the same number of channels as the line. For example, on a stereo line, each control will usually have two channels (left volume and right volume). This is not always the case (most soundcard drivers give the mute control only one channel), so you need to pay attention to the members of the structures given back to you by the API.
Each channel has a value. Normally this value is a signed or unsigned integer, but it can be other things. The valid range for a given value depends on the sound card.
Now that you've gotten a taste of the architecture, here's a recipe for changing a specific value on a mixer:
Open a mixer handle by calling the mixerOpen API function. Specify zero for the second parameter, uMxId, to tell Windows that you want to open the first mixer device. Usually this is okay because the first mixer device is usually the only mixer device, but if you're doing something complicated and/or the user has more than one sound card, you'll need to enumerate the available mixers using the mixerGetNumDevs and mixerGetDevCaps API calls.
Figure out what line you want to modify and specify it in terms of source and destination connections (for example, for the wave output volume, the source would be wave out and the destination would be speakers).
Give this information to mixerGetLineInfo, which will give you back a line ID, and the number of channels on that line (for stereo wave out, this will be 2).
Figure out the control within that line that you want to adjust. The easiest way to do this is to ask the line to give you a control ID of a specific type (for example, volume). Call mixerGetLineControls specifying the MIXER_GETLINECONTROLSF_ONEBYTYPE flag, and put the control type you want inside the dwControlType member of the MIXERLINECONTROLS structure.
You'll get back a MIXERCONTROL structure describing your control. Save the dwControlID of this structure; it specifies the control you want.
Figure out the type of control details you need (for example, a volume control has MIXERCONTROLDETAILS_UNSIGNED), and make an array of "value holder" structures of this type. The array needs to be as big as the number of channels on the line. Fill in a MIXERCONTROLDETAILS structure, giving it the control ID, along with your array of value holder structures.
If you want to query the control, call mixerGetControlDetails. This function will fill in each of the value structures with the current value for that particular channel (for example, the first element in your array will correspond to channel 1, the second to channel 2, and so on).
If you want to set new values for the control, you have to set all of the values or none of them—there's no way to say, "Windows, I'd like to set the value for channel 1 on this 2-channel line." What you must do instead is query for all of the channel values, change the ones you want, and then pump the new values (and the old unchanged values) back into the system.
Close the mixer handle you opened in step 1.
I bet you never thought volume controls could be so complicated, right? Now is the time to encapsulate all of this complexity into a user-friendly CMixer class. I'm not going to go over CMixer line by line (it's all pretty straightforward); instead, I'm going to explain its interface along with my design philosophy, and then leave it to you to figure out how the innards of the class actually work based on the code and the previous section.
I built CMixer mainly to handle volume and muting controls. You might want to extend it to handle other types of controls (for example, the gain control on a microphone, or bass and treble controls).
CMixer uses a dumb class CMixerLine to store a connection from a source to a destination. I call CMixerLine dumb because it does not have methods for setting the volume and muting. In other words, you don't say, "CMixerLine, mute thyself," you say, "CMixer mute this CMixerLine."
A different designer might have made CMixer more into a CMixerLine factory; in other words, you give CMixer a source and destination, and if that forms a valid line, CMixer gives you back a CMixerLine. You would then call the methods of CMixerLine to change the controls on that line.
Tip |
One thing I didn't address in the CMixer class is the preservation of panning. Most sound cards expose two channels for volume: left speaker and right speaker. To preserve panning when setting these values, you need to make sure they're in the same ratio (or, set the panning value explicitly later). I leave this for you to implement should you need it. |
For a more complex mixer interface, that's probably the way you want to do it, but for a simple volume-only control, I eschewed flexibility in favor of simplicity; with my design, the only class you need to worry about is CMixer.
If you look in the class declaration for CMixer, you'll notice that it includes several static const CMixerLine members. These store the source and destination of commonly used lines, and masquerade as constants. For example, if you want to set the master volume, you'd use the built-in Master member of CMixer in the call to SetVolume, like so:
// set master volume at 50% m_Mixer.SetVolume(CMixer::Master, 32767);
Of course, if you want to get fancy, you can always specify your own source and destination connections:
// set telephone (modem) volume at 50% m_Mixer.SetVolume( CMixerLine( MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS ), 32767); // about halfway
The preceding code creates a temporary CMixerLine class that specifies a telephone source and a speaker destination.
Tip |
There are many freeware mixer classes available for download on sites like http://www.codeguru.com and http://www.codeproject.com. Check there, and you just might get lucky. |
The Ch5p5_MixerVolume sample program illustrates how to use CMixer, and I've also included a couple links regarding the mixer API on your CD.