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();