Version
menu_open

How to Create a Wwise Plug-in DLL

A Wwise plug-in DLL contains the following:

Note.gif
Note: A DLL can contain more than one plug-in. For more information, refer to Exported Functions and Wwise Plug-in XML Description Files.

It is recommended that you organize your project so that the sound engine effects are implemented in a static library linked within the plug-in DLL, and reused for linking in your game. For an example, check how the sample plug-ins provided with Wwise (Samples) have been organized.

Wwise Plug-in Object

You need to create a class derived from AK::Wwise::IAudioPlugin that implements all of that interface's methods. This class will manage everything related to the UI and SoundBank generation for your plug-in.

#include <AK/Wwise/AudioPlugin.h>

class SinePlugin
    : public AK::Wwise::IAudioPlugin
{
public:
    // All AK::Wwise::IAudioPlugin interface methods ...
    (...)

    // CompanyID and PluginID as defined in the XML file
    static const short CompanyID;
    static const short PluginID;

private:
    AK::Wwise::IPluginPropertySet * m_pPSet;
};

Implementing the functions of AK::Wwise::IAudioPlugin defines the behavior of your plug-in in response various situations in Wwise. The functions allow the plug-in to react to the following situations:

  • A property set instance is attached to the plug-in.
  • A property has changed.
  • The Wwise user has selected a different platform.
  • The plug-in is being packaged into a SoundBank.
  • The Wwise user opens dialogs to work with the plug-in.
  • You want Wwise to display user-friendly names for a property or property values.
  • The Wwise user has requested online Help.
  • The plug-in instance is being destroyed.

Some of these topics will be covered in the following sections, but you can refer to the AK::Wwise::IAudioPlugin reference for a complete description of the interface and the functions you need to implement.

Destroying the Plug-in Instance

When the Wwise user creates an instance of your source or effect plug-in, Wwise calls the AkCreatePlugin() function in your DLL (Exported Functions) to create the new instance. When the user deletes an instance of your plug-in, Wwise calls the AK::Wwise::IPluginBase::Destroy() method on that particular instance. This method must release any memory or other resources the object might consume, then deletes the object itself.

If the instance was created with the operator new, a typical implementation of AK::Wwise::IPluginBase::Destroy() would be:

void SinePlugin::Destroy()
{
    delete this;
}

Attaching an AK::Wwise::IPluginPropertySet Instance to Your Plug-in

An instance of AK::Wwise::IPluginPropertySet is automatically created for each instance of your plug-in so you don't have to implement property support yourself. When an instance of your plug-in is created, AK::Wwise::IAudioPlugin::SetPluginPropertySet() is called on your plug-in with a pointer to the AK::Wwise::IPluginPropertySet instance.

You should code your plug-in object to keep this pointer as a member so you can query it when needed. For example, it may be queried in your implementation of AK::Wwise::IAudioPlugin::GetBankParameters(). For more information, refer to Generating SoundBanks.

Note.gif
Note: The instance of AK::Wwise::IPluginPropertySet received by AK::Wwise::IAudioPlugin::SetPluginPropertySet() is guaranteed to exist until AK::Wwise::IPluginBase::Destroy() is called on your plug-in.

Using Complex Properties

If your plugin uses sophisticated data, such as curves, graphs, and so on, instead of just simple properties, the property set won't be able to keep it. You will need to keep that in your plugin class and use AK::Wwise::IPluginPropertySet::NotifyInternalDataChanged() to inform Wwise that your particular data has changed. This will let Wwise know that your data needs to be saved or transferred to the sound engine. You should not define anything in the plugin's XML for this type of data. When calling NotifyInternalDataChanged, you can specify a ParamID to limit the notification to a subset of your data. Use AK::IAkPluginParam::ALL_PLUGIN_DATA_ID to specify that all your data has changed.

Note.gif
Note: Do not use NotifyInternalDataChanged for simple properties that are declared in the XML. The notification is done automatically when AK::Wwise::IPluginPropertySet::SetValue is used.

When the user modifies a complex property and you call NotifyInternalDataChanged, Wwise will call you back through AK::Wwise::IAudioPlugin::GetPluginData to obtain the data block that will be transferred to the sound engine part of your plugin. Your sound engine plugin will receive the block through AK::IAkPluginParam::SetParam with the ParamID specified in NotifyInternalDataChanged.

Note.gif
Note: If you use complex properties, you must handle AK::IAkPluginParam::ALL_PLUGIN_DATA_ID in AK::IAkPluginParam::SetParam and AK::Wwise::IAudioPlugin::GetPluginData. It will be used at least once, when the plugin is played the first time.

Loading and saving the parameters of your plugin is done automatically when your plugin declares its properties in the XML. However, for complex data, you must provide your own persistance code. To do so, override AK::Wwise::IAudioPlugin::Save and AK::Wwise::IAudioPlugin::Load. See AK::IXmlTextReader and AK::IXmlTextWriter for more information. If your project is under source control, it is recommended that you save your data in XML to ease the merging of files. Also, the plugin code is responsible for handling the versionning of its data. Make sure your plugin knows what versions of the data it can or cannot load to avoid crashes and data corruption.

The Wwise User Changes a Property Value

Wwise users can change property values either directly through a control in the dialog, or by using the undo/redo commands. When a user changes a property value, AK::Wwise::IAudioPlugin::NotifyPropertyChanged() is called on your plug-in. This way, if your dialog is being displayed at that time, you can specify actions to be performed in the dialog such as enabling or disabling controls.

The Wwise User Changes the Current Platform

When Wwise users change the current platform using a dropdown list or a keyboard shortcut, instances of your plug-in are notified by a call to their AK::Wwise::IAudioPlugin::NotifyCurrentPlatformChanged() method. This way, if your dialog is being displayed at that time, you can adapt it to the new current platform.

Generating SoundBanks

Your must code your plug-in to store its current settings into banks when requested. You can do this by implementing the AK::Wwise::IAudioPlugin::GetBankParameters() method.

The parameters are written as a data block into the SoundBank. The block is then loaded directly from the SoundBank into your sound engine plug-in's parameter structure. Therefore, you must write the parameters in the same order they are declared in the parameter structure of your sound engine plug-in. For more information, refer to Parameter Node Interface Implementation.

For example, consider the following parameter structure:

// Parameter structure for this effect.
struct AkFXSrcSineParams
{
    AkReal32     fFrequency;     // Frequency (in Hertz).
    AkReal32     fGain;          // Gain (in dBFS).
    AkReal32     fDuration;      // Sustain duration (only valid if finite).
};

The following implementation of AK::Wwise::IAudioPlugin::GetBankParameters() gets the current value of each property and then uses the appropriate method to write the value with the AK::Wwise::IWriteData object received as a parameter. These actions are performed in the same order in which the members are defined in your parameter structure.

bool SinePlugin::GetBankParameters( const GUID & in_guidPlatform, AK::Wwise::IWriteData* in_pDataWriter ) const
{
    CComVariant varProp;
    m_pPSet->GetValue( in_guidPlatform, szSineFreq, varProp );
    in_pDataWriter->WriteReal32( varProp.fltVal );
    
    m_pPSet->GetValue( in_guidPlatform, szSineGain, varProp );
    in_pDataWriter->WriteReal32( varProp.fltVal );

    m_pPSet->GetValue( in_guidPlatform, szSineDuration, varProp );
    in_pDataWriter->WriteReal32( varProp.fltVal );
    return true;
}
Note.gif
Note: To get the current value of a property, call AK::Wwise::IPluginPropertySet::GetValue() on the instance of AK::Wwise::IPluginPropertySet that was assigned to your plug-in through AK::Wwise::IAudioPlugin::SetPluginPropertySet()).

For more information about available methods for writing different types of data, refer to AK::Wwise::IWriteData.

Dialog-Related Code

Your DLL must contain resources to define the dialogs Wwise users access to edit the properties of your plug-in (Refer to Plug-in Dialog Resources for more information). You must implement the methods related to these dialogs in the AK::Wwise::IAudioPlugin object.

The method AK::Wwise::IAudioPlugin::GetResourceHandle() lets Wwise know where to look for your dialog resources. The following is a standard implementation using MFC:

// Get access to UI resource handle.
HINSTANCE SinePlugin::GetResourceHandle() const
{
    return AfxGetStaticModuleState()->m_hCurrentResourceHandle;
}

Your plug-in object must also implement AK::Wwise::IAudioPlugin::GetDialog() so Wwise can retrieve information about the dialog. This information includes the dialog's resource ID, and an optional AK::Wwise::PopulateTableItem.

Note.gif
Note: Effect plug-ins have only one dialog, which is displayed in the Effect Settings tab of the Effect Editor. However, source plug-ins can be edited in two different places: the Contents Editor and the Source Plug-in Property Editor. Use the AK::Wwise::IAudioPlugin::eDialog parameter to return the appropriate information.

Here is a typical implementation of AK::Wwise::IAudioPlugin::GetDialog() for a source plug-in:

// Bind non static text UI controls to properties for property view
AK_BEGIN_POPULATE_TABLE(ToneGenProp)
    AK_POP_ITEM(IDC_CHECK_SWEEPFREQ, szSweepFreq)
    AK_POP_ITEM(IDC_RADIO_FREQSWEEPLIN, szSweepFreqType)
    AK_POP_ITEM(IDC_RADIO_FIXLENGTH, szDurMode)
AK_END_POPULATE_TABLE()

// Determine what dialog has been called and set the property names to a UI control binding populated table.
bool ToneGenPlugin::GetDialog( eDialog in_eDialog, UINT & out_uiDialogID, PopulateTableItem *& out_pTable ) const
{
    CComVariant varProp;

    switch ( in_eDialog )
    {
    case SettingsDialog:
        out_uiDialogID = IDD_TONEGENPLUGIN_BIG;
        out_pTable = ToneGenProp;

        return true;

    case ContentsEditorDialog:
        out_uiDialogID = IDD_TONEGENPLUGIN_SMALL;
        out_pTable = NULL;

        return true;
    }

    return false;
}

And here is AK::Wwise::IAudioPlugin::GetDialog() implemented for an effect plug-in:

// Set the property names to a UI control binding populated table.
bool DelayPlugin::GetDialog( eDialog in_eDialog, UINT & out_uiDialogID, PopulateTableItem *& out_pTable ) const
{
    assert( in_eDialog == SettingsDialog );

    out_uiDialogID = IDD_DELAY;
    out_pTable = NULL;

    return true;
}

Refer to Plug-in Dialog Resources for details regarding the actual dialog resources, including format, controls, and so on.

Refer to How to Bind Regular Controls to Properties for information on when and how to use AK::Wwise::PopulateTableItem and associated macros with your plug-in's dialogs.

If UI changes require specific actions, such as enabling or disabling controls, you can carry these out by interpreting the window messages received in the AK::Wwise::IAudioPlugin::WindowProc() function:

// Standard window function allowing the user to intercept whatever message is of interest when implementing UI behavior.
bool ToneGenPlugin::WindowProc( eDialog in_eDialog, HWND in_hWnd, UINT in_message, WPARAM in_wParam, LPARAM in_lParam, LRESULT & out_lResult )
{

    if ( in_message == WM_INITDIALOG )
    {   
        // Perform anything you need to do on dialog window initialization ...
    }

    // For example, catch window command actions (only for the main dialog) to enable/disable controls
    else if ( in_eDialog == SettingsDialog && in_message == WM_COMMAND )
    {
        // Notification code
        switch ( HIWORD( in_wParam ) )
        {
        case BN_CLICKED:
            // Check which button was clicked
            switch ( LOWORD( in_wParam ) )
            {
            case IDC_CHECK_SWEEPFREQ:
                // Verify if checkbox was checked or unchecked
                if ( IsDlgButtonChecked( in_hWnd, IDC_CHECK_SWEEPFREQ ) == BST_CHECKED )
                {
                    // Enable some controls ...
                }
                else if ( IsDlgButtonChecked( in_hWnd, IDC_CHECK_SWEEPFREQ ) == BST_UNCHECKED )
                {
                    // Disable some controls ...
                }
                break;
            }           
        } // End switch hi word (notification code)

    } // End command window event

    // Return False to let the parent window deal with the message. Return True
    // for messages you don't want the parent window to handle.

    return false;
}

User-Friendly Text for Property Names and Property Values

Implementing the AK::Wwise::IAudioPlugin::DisplayNameForProp() function allows Wwise to retrieve a 'user-friendly' name for that property, which will be displayed in several places in the interface. These places include the RTPC Manager, in the edit menu for the undo/redo commands after a property change, and so on.

The in_szPropertyName parameter corresponds to the property name specified in the plug-in's XML definition file (Wwise Plug-in XML Description Files). Here is a sample implementation of this method:

bool SinePlugin::DisplayNameForProp( LPCWSTR in_szPropertyName, LPWSTR out_szDisplayName, UINT in_unCharCount ) const
{
    // Get resource handle
    HINSTANCE hInst = AfxGetStaticModuleState()->m_hCurrentResourceHandle;
    if ( ! wcscmp( in_szPropertyName, szSineFreq ) )
    {
        ::LoadString( hInst, IDS_SINEFREQ, out_szDisplayName, in_unCharCount );
        return true;    
    }
    else if ( ! wcscmp( in_szPropertyName, szSineGain ) )
    {
        ::LoadString( hInst, IDS_SINEGAIN, out_szDisplayName, in_unCharCount );
        return true;    
    }
    // ...
    return false;
}

If a certain property has non-numeric values, such as a boolean property that means On/Off or Yes/No, or an enumeration for a curve type, or if some values have a special meaning, you can specify custom text to be displayed for some or all of the property's possible values in your implementation of the AK::Wwise::IAudioPlugin::DisplayNamesForPropValues() method. The custom text will be used in the RTPC graph view for those values on the Y axis.

Note.gif

Note: The format of the string that is built by DisplayNamesForPropValues() is the same as that of the "Options" attribute you can specify on Combo Box controls in your dialog (Refer to Wwise Plug-in Dialog Reference for more information). The string contains value/text pairs separated by commas, and each pair contains the numeric value and the text separated by a colon. For example:

  • Boolean property: "0:Off,1:On"
  • Numeric property seen as an enumeration: "0:Low Pass,1:High Pass,2:Band Pass"
  • Numeric property with some values that have a special meaning: "-100:Left,0:Center,100:Right"

For bool properties, use 0 for false and 1 for true on the value side of a value/text pair.

In the sample code below, the name is retrieved from the plug-in's resources:

// Allow Wwise to retrieve a user friendly name for that property's value (for example, RTPCs).
bool DelayPlugin::DisplayNamesForPropValues( LPCWSTR in_szPropertyName, LPWSTR out_szValuesName, UINT in_unCharCount ) const
{
    bool bFound = false;

    if ( !wcscmp( in_szPropertyName, szFeedbackEnabled ) )
    {
        WCHAR szValueName[128];

        CString csValuesName( L"0:" );
        ::LoadString( AfxGetStaticModuleState()->m_hCurrentResourceHandle, 
            IDS_PROPVALUENAME_FEEDBACKENABLED_OFF, szValueName, 128 );  
        csValuesName += szValueName;

        csValuesName += L",1:";
        ::LoadString( AfxGetStaticModuleState()->m_hCurrentResourceHandle, 
            IDS_PROPVALUENAME_FEEDBACKENABLED_ON, szValueName, 128 );
        csValuesName += szValueName;

        _tcsncpy( out_szValuesName, csValuesName, in_unCharCount );
        
        bFound = true;
    }
    else if ( !wcscmp( in_szPropertyName, szWetDryMix ) )
    {
        WCHAR szValueName[128];

        CString csValuesName( L"0:" );
        ::LoadString( AfxGetStaticModuleState()->m_hCurrentResourceHandle, 
            IDS_PROPVALUENAME_WETDRYMIX_DRY, szValueName, 128 );    
        csValuesName.AppendFormat( L"(%s) 0", szValueName );

        csValuesName += L",100:";
        ::LoadString( AfxGetStaticModuleState()->m_hCurrentResourceHandle, 
            IDS_PROPVALUENAME_WETDRYMIX_WET, szValueName, 128 );
        csValuesName.AppendFormat( L"(%s) 100", szValueName );

        _tcsncpy( out_szValuesName, csValuesName, in_unCharCount );
        
        bFound = true;
    }

    return bFound;
}

Displaying Help for a Plug-in

When a Wwise user clicks on the '?' icon in a plug-in's dialog titlebar, AK::Wwise::IAudioPlugin::Help() is called on that plug-in. You can implement Help with various tools, including HTMLHelp, WinHelp, or a third-party Help browser. The function receives a window handle that can be used as the parent for any window you want to display. The AK::Wwise::IAudioPlugin::eDialog parameter allows you to choose a specific Help topic to match the dialog the Wwise user currently has open. This function must return true if you handled the Help request, or false otherwise, in which case Wwise will display a Help topic related to the Plug-in Manager.

Note.gif
Note: As previously mentionned, effect plug-ins have only one dialog, while source plug-ins have two. The AK::Wwise::IAudioPlugin::eDialog parameter should be used if your source plug-in has separate Help topics for its two dialogs.

Here is an example of a source plug-in that uses HTML help:

// Implement online help when the user clicks on the "?" icon
bool ToneGenPlugin::Help( HWND in_hWnd, eDialog in_eDialog ) const
{
    AFX_MANAGE_STATE( ::AfxGetStaticModuleState() ) ;

    DWORD dwTopic = ONLINEHELP::Tone_Generator_Settings;
    if ( in_eDialog == AK::Wwise::IAudioPlugin::ContentsEditorDialog )
        dwTopic = ONLINEHELP::Tone_Generator_ContentsEditor;

    // Note: Do NOT call AfxGetApp()->HtmlHelp() as it will launch the Help
    // window from the wrong parent window which will make floating views
    // behave unexpectedly.
    ::HtmlHelp( NULL, AfxGetApp()->m_pszHelpFilePath, HH_HELP_CONTEXT, dwTopic );

    return true;
}
Caution.gif
Caution: The Help() method should NOT call AfxGetApp()->HtmlHelp() as it will launch the Help window from the wrong parent window which will make floating views behave unexpectedly. Instead, use HtmlHelp() with a NULL window handle, as shown in the example above.

Plug-in Dialog Resources

Your DLL must contain resources for your plug-in's dialog or dialogs. The resource ID for each dialog is the same ID returned by AK::Wwise::IAudioPlugin::GetDialog(). Refer to Dialog-Related Code for more information.

Refer to Wwise Plug-in Dialog Reference for a complete description of the controls you can use in your dialog, how to bind them to properties, and so on.

Exported Functions

Each plug-in DLL must export two functions:

Both functions receive the Company ID and Plug-in ID as parameters. These IDs correspond to those defined in the plug-in XML definition file (Refer to Wwise Plug-in XML Description Files for more information). If your DLL and XML file contain multiple plug-ins, these parameters will let you know which plug-in is being requested.

Here is an example of the exported functions for a plug-in DLL containing one plug-in:

#include <AK/Wwise/Utilities.h>
#include <AK/SoundEngine/Common/IAkPlugin.h>
#include <assert.h>

// Wwise UI plug-in factory
AK::Wwise::IPluginBase* __stdcall AkCreatePlugin( unsigned short in_usCompanyID, unsigned short in_usPluginID )
{
    if ( in_usCompanyID == SinePlugin::CompanyID && in_usPluginID == SinePlugin::PluginID )
        return new MyPlugin; // return AK::Wwise::IAudioPlugin-derived object in the case of a source plug-in.

    // If this function is called with the wrong company/plug-in ID, it is because
    // the IDs in the plug-in XML definition file and those in the code don't match.
    assert( false );

    return NULL;
}

// Sound Engine callback provider
bool __stdcall AkGetSoundEngineCallbacks( unsigned short in_usCompanyID, unsigned short in_usPluginID, AkCreatePluginCallback & out_funcEffect, AkCreateParamCallback & out_funcParam )
{
    if ( in_usCompanyID == SinePlugin::CompanyID && in_usPluginID == SinePlugin::PluginID )
    {
        out_funcEffect = CreateMyEffect;
        out_funcParam = CreateMyEffectParam;
        return true;
    }

    // If this function is called with the wrong company/plug-in ID, it is because
    // the IDs in the plug-in XML definition file and those in the code don't match.
    assert( false );

    return false;
}

In the library description file (.def) you should explicitly mention the two functions to be exported:

LIBRARY      "MyPlugin"

EXPORTS
    AkCreatePlugin
    AkGetSoundEngineCallbacks

Legal Text (Optional)

The Wwise authoring tool can display a message box the first time your plugin is added to a project, reminding your users of their legal obligations. You may embed this text within your plugin, although this is not mandatory. Skip this section if you do not wish to use this feature.

In your plugin's code, export the function AkGetFirstTimeCreationMessage(). Like the other exported functions, this function receives the Company ID and Plug-in ID as parameters (see Exported Functions above). This function also has two output arguments, the message string to display, and the string of the registry key that is used to know if the plugin has already been created before.

Here is an example of an implementation of this function:

// Message to display when creating the plugin for the first time
bool __stdcall AkGetFirstTimeCreationMessage(
    unsigned short in_usCompanyID,
    unsigned short in_usPluginID,
    BSTR& out_message,
    BSTR& out_key
    )
{
    assert( in_usCompanyID == SinePlugin::CompanyID && in_usPluginID == SinePlugin::PluginID );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    CString csText;
    csText.LoadString( IDS_SINEPLUGIN_LICENSEREMINDER );
    out_message = csText.AllocSysString();

    csText = _T("SinePlugin");
    out_key = csText.AllocSysString();

    return true;
}

In this example, the license reminder text was stored in the plugin's string table under IDS_SINEPLUGIN_LICENSEREMINDER.

The text "SinePlugin" that is returned via out_key is the name of the registry key that Wwise creates the first time this function is called, here: HKEY_CURRENT_USER/Software/Audiokinetic Inc./Wwise/PluginUserAcknowledge/Projects/{current project}/SinePlugin. Obviously, you will want to find a name that is unique to your plugin and company. When Wwise calls this function again and finds this key in the registry, it will not display your message.

In the library description file (.def) add this function to the list of functions that need to be exported:

LIBRARY      "MyPlugin"

EXPORTS
    AkCreatePlugin
    AkGetSoundEngineCallbacks
    AkGetFirstTimeCreationMessage

Handling Monitoring Data

An effect plug-in inside the master-mixer hierarchy (applied to a bus) can receive monitoring information from its associated run-time component in the sound engine. Refer to Posting Monitoring Data for Wwise Plug-ins for details on how to post monitoring data from the sound engine side of the plug-in.

When a monitoring data block is received by the plug-in instance (monitoring must be enabled in Wwise), the plug-in can parse the data properly and trigger actions like statistic compilations and or UI events (such as VU meters for instance). The code below show a simple example of how to react to monitoring data in the implementation of IAudioPlugin::NotifyMonitorData() in a way that handles byte swapping as necessary when the platform that posted the data has different endianness than that of the Wwise plug-in.

void MyPlugin::NotifyMonitorData( void * in_pData, unsigned int in_uDataSize, bool in_bNeedsByteSwap )
{
    // Parse the data block that was sent from your sound engine plug-in and 
    // byte swap if plaform's endianness is reported to be different than PC.
    unsigned int * pData = (unsigned int *) in_pData;
    unsigned int uNumChannels = in_bNeedsByteSwap ? _byteswap_ulong( *pData++ ) : *pData++;
    float fChannelPeaks[MAX_NUM_CHANNELS];
    
    if ( in_bNeedsByteSwap )
    {
        for ( unsigned int i = 0; i < uNumChannels; ++i )
        {
            fChannelPeaks[i] = (float) _byteswap_ulong( *pData++ );
        }
    }
    else
    {
        memcpy( fChannelPeaks, pData, uNumChannels*sizeof(float) );
    }   
    // Create some UI response to the incoming data...
}

Sample Code

See Samples for a list of available sample plug-in projects you can look at for more information.

Troubleshooting

If you run into any problems, Help is available in the Wwise Source and Effect Plug-in Troubleshooting Guide.


Cette page a-t-elle été utile ?

Besoin d'aide ?

Des questions ? Des problèmes ? Besoin de plus d'informations ? Contactez-nous, nous pouvons vous aider !

Visitez notre page d'Aide

Décrivez-nous de votre projet. Nous sommes là pour vous aider.

Enregistrez votre projet et nous vous aiderons à démarrer sans aucune obligation !

Partir du bon pied avec Wwise