Fundamentals of Audio and Video Programming for Games (Pro-Developer)

Now, return to the  RumpusSFX.cpp file. When the global variable g_lpTempSoundFXData is created, it is also initialized with all the default parameters for a CSoundFXData object that was listed previously.

CSoundFXData* g_lpTempSoundFXData = new CSoundFXData;

However, these initial values are overwritten every time the Add or Edit Sound dialog box is called up, with the following code in the retrieveSound method (which, if you remember, simply loads the current wave file name and settings for that slot in the Rumpus dialog box s table of sound effects).

soundEffect[index].getSFXData(g_lpTempSoundFXData);

With this call, the getSFXData method fills the g_lpTempSoundFXData object with all the current settings for that sound effect. The code for getSFXData simply calls the CopyAllData method on the CSoundFXData class.

void getSFXData(CSoundFXData* copyFromData) { copyFromData -> CopyAllData(SoundFXData); }

Similarly, if the user of the tool updates the special effect parameters and clicks OK, then the MainDlgProc callback function will take the temporary settings back out of g_lpTempSoundFXData , and store the changed values in the cSoundEffect object with a call to the setSFXData method. Note the use of the g_SFXOK flag to prevent any updating if the user changed the values, but then clicked Cancel.

if (g_SFXOK) soundEffect[g_cSound].setSFXData(g_lpTempSoundFXData);

Before we delve into how these settings are passed to the DirectSound SDK, we will explain how the UI works to make it easy to change the settings.

The two source files,  RumpusSFXparam.cpp and  RumpusEnvironment.cpp , contain code for the UI only; no calls into the DirectSound SDK are made from within them. The code in  RumpusSFXparam.cpp is based on one of the samples shipped with the DirectSound SDK, SoundFX.exe, which uses a masterly sense of control over sliders and radio buttons in order to use the same dialog box and controls for all eight special effects.

Figure 4.4: Change Special Effect Settings dialog box for the Chorus effect.

You will notice from the screen shot of the Change Special Effects Settings dialog box shown in Figure 4.4 that there are six sliders, three waveforms, and five phases. None of the special effects requires all of these parameters, but all use a subset of them. This dialog box UI enables only those parameters that apply to the special effect selected in the column of radio buttons. If you click on any one of the special effect radio buttons, you will see the range of enabled sliders, and waveform and phase buttons change appropriately.

One of the tricks used in this style of UI programming is to have consecutive ID numbers for the various settings. This makes it much easier to write code to manipulate the settings in this makeshift array form, but you do have to check by hand that the resource IDs are consecutive, as it is not a feature provided by Visual Studio. Start by looking at the SpecialFXDlgProc callback function, and refer to the following code snippet.

default: if( LOWORD( wParam ) >= IDC_RADIO_CHORUS && LOWORD( wParam ) <= IDC_RADIO_REVERB ) { g_dwCurrentFXType = LOWORD( wParam ) - IDC_RADIO_CHORUS; LoadParameterUI( hDlg, g_dwCurrentFXType ); }

Notice that the special effect radio buttons IDC_RADIO_CHORUS to IDC_RADIO_REVERB are consecutive, so we can write this code. The global parameter g_dwCurrentFXType will hold a value from 0 (chorus) to 7 (reverb), indicating which effect we are looking at, then the function LoadParameterUI is called to haul its current settings into the dialog box.

If you now look at the LoadParameterUI function, you will notice that it first clears the current UI with a call to ResetParameterUI , then gives the group box containing all the settings a name with the following code.

sprintf( tszstr, "Parameters for [ %s ]", SFXnames[dwFXType] ); SetDlgItemText( hwndDlg, IDC_FRAME, tszstr );

Then the LoadParameterUI function dives into a huge switch statement that loads up the current settings for that special effect. Look at the chorus special effect as an example.

case eSFX_chorus: { LoadSingleParameter( hwndDlg, IDC_PARAM_NAME1, TEXT( "Wet/Dry Mix (%)" ), g_lpTempSoundFXData->m_paramsChorus.fWetDryMix, DSFXCHORUS_WETDRYMIX_MIN, DSFXCHORUS_WETDRYMIX_MAX ); LoadSingleParameter( hwndDlg, IDC_PARAM_NAME2, TEXT( "Depth (%)" ), g_lpTempSoundFXData->m_paramsChorus.fDepth, DSFXCHORUS_DEPTH_MIN, DSFXCHORUS_DEPTH_MAX ); LoadSingleParameter( hwndDlg, IDC_PARAM_NAME3, TEXT( "Feedback (%)" ), g_lpTempSoundFXData->m_paramsChorus.fFeedback, DSFXCHORUS_FEEDBACK_MIN, DSFXCHORUS_FEEDBACK_MAX ); LoadSingleParameter( hwndDlg, IDC_PARAM_NAME4, TEXT( "Frequency (Hz)" ), g_lpTempSoundFXData->m_paramsChorus.fFrequency, DSFXCHORUS_FREQUENCY_MIN, DSFXCHORUS_FREQUENCY_MAX ); LoadSingleParameter( hwndDlg, IDC_PARAM_NAME5, TEXT( "Delay (ms)" ), g_lpTempSoundFXData->m_paramsChorus.fDelay, DSFXCHORUS_DELAY_MIN, DSFXCHORUS_DELAY_MAX ); LoadWaveformRadio( hwndDlg, g_lpTempSoundFXData->m_paramsChorus.lWaveform, DSFXCHORUS_WAVE_TRIANGLE, -1, DSFXCHORUS_WAVE_SIN ); LoadPhaseRadio( hwndDlg, g_lpTempSoundFXData->m_paramsChorus.lPhase, DSFXCHORUS_PHASE_NEG_180, DSFXCHORUS_PHASE_NEG_90, DSFXCHORUS_PHASE_ZERO, DSFXCHORUS_PHASE_90, DSFXCHORUS_PHASE_180 ); break;

Notice that there are five calls to the LoadSingleParameter function, which load information into the first five of the six sliders of the dialog box. The parameters for the LoadSingleParameter function, following the inevitable handle, are the ID of the slider, the text to describe the parameter, the current value, and then the minimum and maximum possible values. This function provides a clever way of re-using a slider for many purposes. The LoadSingleParameter function first calls another function, EnableSingleParameter , to enable the five dialog box resources for that parameter. Then, depending on the appropriate decimal precision for the range of values, it sends dialog box messages to display the current value, and the minimum and maximum values.

A call to the LoadWaveformRadio function has as its parameters the dialog box handle and current setting, and then the different waveform settings that apply to this effect. For the chorus effect, the triangle and sine waveforms apply, but not the square waveform (which is removed from consideration by the -1 in the parameter list).

Similarly, a call to the LoadPhaseRadio function indicates that wave phases apply, and the parameters supplied are the dialog box handle and the current setting. In this case, if any of the five phases are possible, all are. The call to this function seems complicated, with all five of the possible phases sent in the parameter list. However, this does mirror the define statements in dsound .h, which seem to cater for the possibility that a 90-degree phase change to one special effect may need another value than the same phase change to another effect. It perhaps would have been cleaner if all the special effects used the same definitions as well as the same values, but they do not, and this complexity makes this code look far more awkward than it should.

If you go through the rest of the LoadParameterUI function, you will see that for each special effect, the appropriate number of calls are made to the LoadSingleParameter, LoadWaveformRadio and LoadPhaseRadio functions.

All of the code just described is run when the user of the Rumpus tool clicks on one radio button in the column with the simple title FX.

Back in the SpecialFXDlgProc function, the other major thread to follow is for the function OnEffectChanged . The OnEffectChanged function is called whenever a change occurs to a slider, a waveform, or a phase radio button.

If you look at the code for OnEffectChanged in the  RumpusSFXparam.cpp source file, you will see a long switch statement that is very similar to the one in LoadParameterUI . Basically, every time that a user of the tool makes any change to any setting, all of the settings are recorded back into the g_lpTempSoundFXData object. Again, taking the chorus effect as an example, the code is written as follows .

case eSFX_chorus: { SaveSingleParameter( hwndDlg, IDC_PARAM_NAME1, &g_lpTempSoundFXData->m_paramsChorus.fWetDryMix, DSFXCHORUS_WETDRYMIX_MIN, DSFXCHORUS_WETDRYMIX_MAX ); SaveSingleParameter( hwndDlg, IDC_PARAM_NAME2, &g_lpTempSoundFXData->m_paramsChorus.fDepth, DSFXCHORUS_DEPTH_MIN, DSFXCHORUS_DEPTH_MAX ); SaveSingleParameter( hwndDlg, IDC_PARAM_NAME3, &g_lpTempSoundFXData->m_paramsChorus.fFeedback, DSFXCHORUS_FEEDBACK_MIN, DSFXCHORUS_FEEDBACK_MAX ); SaveSingleParameter( hwndDlg, IDC_PARAM_NAME4, &g_lpTempSoundFXData->m_paramsChorus.fFrequency, DSFXCHORUS_FREQUENCY_MIN, DSFXCHORUS_FREQUENCY_MAX ); SaveSingleParameter( hwndDlg, IDC_PARAM_NAME5, &g_lpTempSoundFXData->m_paramsChorus.fDelay, DSFXCHORUS_DELAY_MIN, DSFXCHORUS_DELAY_MAX ); if( IsDlgButtonChecked( hwndDlg, IDC_RADIO_TRIANGLE ) == BST_CHECKED ) g_lpTempSoundFXData-> m_paramsChorus.lWaveform = DSFXCHORUS_WAVE_TRIANGLE; else g_lpTempSoundFXData->m_paramsChorus.lWaveform = DSFXCHORUS_WAVE_SIN; if( IsDlgButtonChecked( hwndDlg, IDC_RADIO_NEG_180 )==BST_CHECKED ) g_lpTempSoundFXData->m_paramsChorus.lPhase = DSFXCHORUS_PHASE_NEG_180; else if( IsDlgButtonChecked( hwndDlg, IDC_RADIO_NEG_90 ) == BST_CHECKED ) g_lpTempSoundFXData->m_paramsChorus.lPhase = DSFXCHORUS_PHASE_NEG_90; else if( IsDlgButtonChecked( hwndDlg, IDC_RADIO_ZERO ) == BST_CHECKED ) g_lpTempSoundFXData->m_paramsChorus.lPhase = DSFXCHORUS_PHASE_ZERO; else if( IsDlgButtonChecked( hwndDlg, IDC_RADIO_90 )==BST_CHECKED ) g_lpTempSoundFXData->m_paramsChorus.lPhase = DSFXCHORUS_PHASE_90; else g_lpTempSoundFXData->m_paramsChorus.lPhase = DSFXCHORUS_PHASE_180; break;

Clearly, the SaveSingleParameter function performs more or less the opposite of LoadSingleParameter, with the following code calculating the value.

FLOAT percent = ( FLOAT ) ( pos - DEFAULT_SLIDER_MIN ) / ( FLOAT ) ( DEFAULT_SLIDER_MAX - DEFAULT_SLIDER_MIN ); *val = percent * ( max - min ) + min;

The percent value receives the position of the slider as a percentage of its range from left to right. The resulting value is used to calculate the actual parameter from the minimum and maximum possible which are supplied to the function in the min and max parameters.

The *val parameter points to the g_lpTempSoundFXData object.

This time, the waveform and phase radio buttons do not require an additional function, a simple set of if statements step through the radio buttons, and if the radio button is checked, the waveform or phase value is updated.

That wraps up the purpose and coding of the  RumpusSFXparam.cpp file.

The other source file handling only UI,  RumpusEnvironment.cpp , is comparatively trivial. There is very little to say about it, other than the selected environment number is loaded into the g_Environment global variable.

Now, back to the main  RumpusSFX.cpp file, to learn how to actually attach special effects to a sound buffer.

Категории