XAPO example code and related questions/general wonderings.

Last post 04-01-2008, 1:42 PM by Dugan. 1 replies.
Sort Posts: Previous Next
  •  02-13-2008, 6:32 AM

    XAPO example code and related questions/general wonderings.

    In trying to implement an simple APO for use with XAudio2, I encountered a few problems, mainly because this is my first experience with COM interfaces. Having completed the APO I would like to demonstrate what I did to make it work, and get your feedback with regards to how I could have done it better. I see others have asked APO questions in the past, so I hope to lure out some experts and hear their concrete suggestions based on a simple example.

    The effect is a basic occlusion filter effect, using a one-pole low pass filter implemented by the APO. Code is at the end of this post.

    While implementing it I thought of the following questions:

    1) While it was not too hard to implement the required COM interface once I had read a little about it, I wonder if it wouldnt be possible to have the COM support in the  CXAPOBase class, or if it would be worth it for me to create another base effect class that handles this for me, if I need to implement many effects?

    2) Does it make sense to bypass the CXAPOBase class and work directly off the IXAPO interface, possibly implementing your own base class?

    3) Currently implementing only Process and LockForProcess is enough for me. Does anyone know of cases / have examples of more advanced APO´s where more of the interface is implemented?

    4) As far as I can see, the GUID create function CoCreateGuid is not available on the Xbox 360. I am just setting the ID to some value myself, can that cause any problems?

    I hope others can find the questions and code in this post relevant, and appologise for not being able to keep the post smaller :)

    - Peter

    Code:

    APO header:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    class OcclusionFilterEffectXAudio2: public CXAPOBase, public IXAPOParameters {
    public:
      OcclusionFilterEffectXAudio2(const XAPO_REGISTRATION_PROPERTIES *pRegProperties);
      virtual ~OcclusionFilterEffectXAudio2() {}
      
      // XAPO methods
      STDMETHOD_(void, Process) (UINT32 InputProcessParameterCount, XAPO_PROCESS_BUFFER_PARAMETERS** ppInputProcessParameters, UINT32 OutputProcessParameterCount, XAPO_PROCESS_BUFFER_PARAMETERS** ppOutputProcessParameters);
      STDMETHOD(LockForProcess) (UINT32 InputLockedParameterCount, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS** ppInputProcessParameters, UINT32 OutputLockedParameterCount, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS** ppOutputProcessParameters);
      
      // COM interface methods.
      STDMETHOD_(ULONG, Release) () {
        --referenceCount;
        if ( referenceCount == 0 ) {
          delete this;
          return 0;
        }
        return referenceCount;
      }
      
      STDMETHOD_(ULONG, AddRef) () {return ++referenceCount;}
      
      STDMETHOD(QueryInterface) (  REFIID riid, void **ppvObject) {
        *ppvObject = NULL;
        if(riid == IID_IUnknown) {
          *ppvObject = (IUnknown*)(IXAPO*)this;  
        }
        else if(riid == IID_IXAPO) {
          *ppvObject = (IXAPO*)this;  
        }
        else if(riid == IID_IXAPOParameters) {
          *ppvObject = (IXAPOParameters*)this;  
        }
        if(*ppvObject( != NULL)) {
          ((IUnknown*)(*ppvObject))->AddRef();
          return S_OK;
        }
        return E_NOINTERFACE;
      }
      
      // Parameter Interface implementation.
      // Expects to handle a single floating point value (occlusionLevel).
      STDMETHOD_(void, GetParameters)  ( void *pParameters, UINT32 ParametersByteSize ) {
        assert(ParametersByteSize == 4);
        *(float*)pParameters = occlusionLevel;
      }
      
      STDMETHOD_(void, SetParameters)  (const void *pParameters, UINT32 ParametersByteSize ) {
        assert(ParametersByteSize == 4);
        occlusionLevel = *((float*)pParameters);
      }

    private:
      int referenceCount;
      int channels;
      int bytesPerSample;
      float occlusionLevel;
      float lastOutput[6];
    };

    APO Implementation:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    OcclusionFilterEffectXAudio2::OcclusionFilterEffectXAudio2(const XAPO_REGISTRATION_PROPERTIES *pRegProperties) :CXAPOBase(pRegProperties) {
      referenceCount = 0;
      occlusionLevel = 0.5f;
      channels = 1;
      bytesPerSample = 4;

      lastOutput[0] = 0.0f;
      lastOutput[1] = 0.0f;
      lastOutput[2] = 0.0f;
      lastOutput[3] = 0.0f;
      lastOutput[4] = 0.0f;
      lastOutput[5] = 0.0f;
    }

    STDMETHODIMP OcclusionFilterEffectXAudio2::LockForProcess(UINT32 InputLockedParameterCount, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS** ppInputProcessParameters, UINT32 OutputLockedParameterCount, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS** ppOutputProcessParameters) {
      channels = ppInputProcessParameters[0]->pFormat->nChannels;
      bytesPerSample = (ppInputProcessParameters[0]->pFormat->wBitsPerSample >> 3);
      
      return CXAPOBase::LockForProcess(
        InputLockedParameterCount,
        ppInputProcessParameters,
        OutputLockedParameterCount,
        ppOutputProcessParameters);
    }

    STDMETHODIMP_(void) OcclusionFilterEffectXAudio2::Process(UINT32 InputProcessParameterCount, XAPO_PROCESS_BUFFER_PARAMETERS** ppInputProcessParameters, UINT32 OutputProcessParameterCount, XAPO_PROCESS_BUFFER_PARAMETERS** ppOutputProcessParameters) {
      
      void* pInputBuffer = ppInputProcessParameters[0]->pBuffer;
      assert(pInputBuffer != NULL);

      void* pOutputBuffer = ppOutputProcessParameters[0]->pBuffer;
      assert(pOutputBuffer != NULL);
        
      // one-pole low pass:
      float sampleRate = 48000.0f;
      float cutOffFreq = 500.0f;
      float lowFrequencyRatio = 0.25f; // ie. attenuate -2.5db at f=0

      if(occlusionLevel<0.01f) occlusionLevel = 0.01f;
      if(occlusionLevel>0.99f) occlusionLevel = 0.99f;

      float w = 2.0f * PI * cutOffFreq / sampleRate;
      float g = (1.0f-occlusionLevel);
      float cosw = Cos(w);
      float a = (1.0f - g*cosw - Sqrt(2.0f*g*(1.0f-cosw)-g*g*(1.0f-cosw*cosw))) / (1.0f-g);
      float k = g;

    for(int i=0; i<channels; i++) {
        for(int j=0; j<ppInputProcessParameters[0]->ValidFrameCount; j++) {
    float sample = ((float*)pInputBuffer)[i*ppInputProcessParameters[0]->ValidFrameCount + j];
          lastOutput[i] = (1.0f-a) * sample + a*lastOutput[i];
          ((float*)pOutputBuffer)[i*ppInputProcessParameters[0]->ValidFrameCount + j] = lastOutput[i];
        }
      }

      ppOutputProcessParameters[0]->ValidFrameCount = ppInputProcessParameters[0]->ValidFrameCount;
      ppOutputProcessParameters[0]->BufferFlags = ppInputProcessParameters[0]->BufferFlags;
    }

    Effect chain setup:

    XAUDIO2_EFFECT_DESCRIPTOR descriptor;
        descriptor.InitialState = true;
        descriptor.OutputChannels = 1;
        
        IID apoIid = {1};
        //CoCreateGuid( (GUID*)(&apoIid) );
        rep = NEW(ALLOC_LIBS_SOUND, ALLOCATOR_DEFAULT, CXAPORegistrationProperties<1>( apoIid, L"Occlusion Filter APO", L"Deadline Games A/S, 2008", XAPO_FLAG_DEFAULT, 1, 1));
        
        occlusionFilterAPO = NEW(ALLOC_LIBS_SOUND, ALLOCATOR_DEFAULT, OcclusionFilterEffectXAudio2((const XAPO_REGISTRATION_PROPERTIES*)(rep)));
        occlusionFilterAPO->Initialize(NULL, 0);

        descriptor.pEffect = (CXAPOBase*)(occlusionFilterAPO);
        occlusionFilterAPO->AddRef();

        XAUDIO2_EFFECT_CHAIN chain;
        chain.EffectCount = 1;
        chain.pEffectDescriptors = &descriptor;
        xaudio2Check(sourceVoice->SetEffectChain(&chain));
        occlusionFilterAPO->Release();
  •  04-01-2008, 1:42 PM

    Re: XAPO example code and related questions/general wonderings.

    First of all, thanks for being an early adopter of the XAPO plugin model.  I'm excited about this feature as I think it's going to let people do some pretty cool DSP magic with their audio.

    Now, going step by step -

    1. The XAPO base class does implement basic COM support (AddRef, Release and QueryInterface).  If you derive from this class you should have to add very little to implement your effect.  Have you looked at the XAPO sample from the March release?

    2. I'd strongly recomment using our base class rather than rolling your own; we provide a lot of basic services and I don't see an obvious reason why you'd avoid it.  Writing your own would be a significant amount of work.

    3. There are cases when you might want to override more of the base implementation (e.g. IXAPO::Initialize), but I'd wait until you have a definite requirement to do so.

    4. Where are you seeing a call to CoCreateGuid?

    At a quick glance your XAPO code looks fine to me - except that you shouldn't need to implement AddRef and Release yourself.

    Dugan Porter
    XAudio2 developer (MS)


    Dugan Porter [MS]
    Game Audio Team
View as RSS news feed in XML
©2007 Microsoft Corporation. All rights reserved. Privacy Statement Terms of Use Code of Conduct Feedback