//------------------------------------------------------------------------------ // File: Gargle.cpp // // Desc: DirectShow sample code - implementation of CGargle class. // // Copyright (c) 2000-2001 Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ #include "stdafx.h" #define FIX_LOCK_NAME #include #include #include // needed to define GUID_TIME_REFERENCE from medparam.h #include #include "Gargle.h" #include // DirectShow media type guids #define DEFAULT_GARGLE_RATE 20 #define CHECK_PARAM(lo, hi) \ if (value < lo || value > hi) {return E_INVALIDARG;} ; ///////////////////////////////////////////////////////////////////////////// // // CGargle // CGargle::CGargle( ) : m_ulShape(0), m_ulGargleFreqHz(DEFAULT_GARGLE_RATE), m_fDirty(true), m_bInitialized(FALSE) { m_pUnkMarshaler = NULL; #ifdef DEBUG HRESULT hr = Init(); assert( SUCCEEDED( hr ) ); #else Init(); #endif } const MP_CAPS g_capsAll = MP_CAPS_CURVE_JUMP | MP_CAPS_CURVE_LINEAR | MP_CAPS_CURVE_SQUARE | MP_CAPS_CURVE_INVSQUARE | MP_CAPS_CURVE_SINE; static ParamInfo g_params[] = { // index type caps min, max, neutral, unit text, label, pwchText?? GFP_Rate, MPT_INT, g_capsAll, GARGLE_FX_RATEHZ_MIN, GARGLE_FX_RATEHZ_MAX, 20, L"Hz", L"Rate", L"", GFP_Shape, MPT_ENUM, g_capsAll, GARGLE_FX_WAVE_TRIANGLE, GARGLE_FX_WAVE_SQUARE, GARGLE_FX_WAVE_TRIANGLE, L"", L"WaveShape", L"Triangle,Square", }; ////////////////////////////////////////////////////////////////////////////// // // CGargle::Init // HRESULT CGargle::Init() { HRESULT hr = S_OK; if( !m_bInitialized ) { hr = InitParams(1, &GUID_TIME_REFERENCE, 0, 0, sizeof(g_params)/sizeof(*g_params), g_params); } if( SUCCEEDED( hr ) ) { // compute the period m_ulPeriod = m_ulSamplingRate / m_ulGargleFreqHz; m_bInitialized = TRUE; } return hr; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::Clone // HRESULT CGargle::Clone(IMediaObjectInPlace **ppCloned) { if (!ppCloned) return E_POINTER; HRESULT hr = S_OK; CGargle * pNewGargle = new CComObject; if( !pNewGargle ) hr = E_OUTOFMEMORY; hr = pNewGargle->Init(); IMediaObject * pCloned = NULL; if( SUCCEEDED( hr ) ) { IUnknown *pUnk; hr = pNewGargle->QueryInterface( IID_IUnknown, (void **) &pUnk ); if( SUCCEEDED( hr ) ) { hr = pUnk->QueryInterface( IID_IMediaObject, (void **) &pCloned ); pUnk->Release(); } } else { return hr; } // // Copy parameter control information // if (SUCCEEDED(hr)) hr = pNewGargle->CopyParamsFromSource((CParamsManager *) this); // Copy current parameter values GargleFX params; if (SUCCEEDED(hr)) hr = GetAllParameters(¶ms); if (SUCCEEDED(hr)) hr = pNewGargle->SetAllParameters(¶ms); if (SUCCEEDED(hr)) { // Copy the input and output types DMO_MEDIA_TYPE mt; DWORD cInputStreams = 0; DWORD cOutputStreams = 0; GetStreamCount(&cInputStreams, &cOutputStreams); for (DWORD i = 0; i < cInputStreams && SUCCEEDED(hr); ++i) { hr = GetInputCurrentType(i, &mt); if (hr == DMO_E_TYPE_NOT_SET) { hr = S_OK; // great, don't need to set the cloned DMO } else if (SUCCEEDED(hr)) { hr = pCloned->SetInputType(i, &mt, 0); MoFreeMediaType( &mt ); } } for (i = 0; i < cOutputStreams && SUCCEEDED(hr); ++i) { hr = GetOutputCurrentType(i, &mt); if (hr == DMO_E_TYPE_NOT_SET) { hr = S_OK; // great, don't need to set the cloned DMO } else if (SUCCEEDED(hr)) { hr = pCloned->SetOutputType(i, &mt, 0); MoFreeMediaType( &mt ); } } if (SUCCEEDED(hr)) hr = pCloned->QueryInterface(IID_IMediaObjectInPlace, (void**)ppCloned); // Release the object's original ref. If clone succeeded (made it through QI) then returned pointer // has one ref. If we failed, refs drop to zero, freeing the object. pCloned->Release(); } return hr; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::GetLatency // STDMETHODIMP CGargle::GetLatency(THIS_ REFERENCE_TIME *prt) { *prt = 0; return S_OK; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::Discontinuity // HRESULT CGargle::Discontinuity() { m_ulPhase = 0; return NOERROR; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::FBRProcess // HRESULT CGargle::FBRProcess(DWORD cQuanta, BYTE *pIn, BYTE *pOut) { if (!m_bInitialized) return DMO_E_TYPE_NOT_SET; DWORD cSample, cChannel; for (cSample = 0; cSample < cQuanta; cSample++) { // If m_Shape is 0 (triangle) then we multiply by a triangular waveform // that runs 0..Period/2..0..Period/2..0... else by a square one that // is either 0 or Period/2 (same maximum as the triangle) or zero. // // m_Phase is the number of samples from the start of the period. // We keep this running from one call to the next, // but if the period changes so as to make this more // than Period then we reset to 0 with a bang. This may cause // an audible click or pop (but, hey! it's only a sample!) // ++m_ulPhase; if (m_ulPhase > m_ulPeriod) m_ulPhase = 0; ULONG ulM = m_ulPhase; // m is what we modulate with if (m_ulShape == 0) { // Triangle if (ulM > m_ulPeriod / 2) ulM = m_ulPeriod - ulM; // handle downslope } else { // Square wave if (ulM <= m_ulPeriod / 2) ulM = m_ulPeriod / 2; else ulM = 0; } for (cChannel = 0; cChannel < m_cChannels; cChannel++) { if (m_b8bit) { // sound sample, zero based int i = pIn[cSample * m_cChannels + cChannel] - 128; // modulate i = (i * (signed)ulM * 2) / (signed)m_ulPeriod; // 8 bit sound uses 0..255 representing -128..127 // Any overflow, even by 1, would sound very bad. // so we clip paranoically after modulating. // I think it should never clip by more than 1 // if (i > 127) i = 127; if (i < -128) i = -128; // reset zero offset to 128 pOut[cSample * m_cChannels + cChannel] = (unsigned char)(i + 128); } else { // 16 bit sound uses 16 bits properly (0 means 0) // We still clip paranoically // int i = ((short*)pIn)[cSample * m_cChannels + cChannel]; // modulate i = (i * (signed)ulM * 2) / (signed)m_ulPeriod; // clip if (i > 32767) i = 32767; if (i < -32768) i = -32768; ((short*)pOut)[cSample * m_cChannels + cChannel] = (short)i; } } } return NOERROR; } ////////////////////////////////////////////////////////////////////////////// // // GetClassID // HRESULT CGargle::GetClassID(CLSID *pClsid) { if (pClsid==NULL) { return E_POINTER; } *pClsid = CLSID_Gargle; return NOERROR; } // GetClassID ////////////////////////////////////////////////////////////////////////////// // // CGargle::GetPages // HRESULT CGargle::GetPages(CAUUID * pPages) { pPages->cElems = 1; pPages->pElems = static_cast(CoTaskMemAlloc(sizeof(GUID))); if (pPages->pElems == NULL) return E_OUTOFMEMORY; *(pPages->pElems) = CLSID_GargDMOProp; return S_OK; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::SetAllParameters // STDMETHODIMP CGargle::SetAllParameters(THIS_ LPCGargleFX pParm) { HRESULT hr = S_OK; // Check that the pointer is not NULL if (pParm == NULL) hr = E_POINTER; // Set the parameters if (SUCCEEDED(hr)) hr = SetParam(GFP_Rate, static_cast(pParm->dwRateHz)); if (SUCCEEDED(hr)) hr = SetParam(GFP_Shape, static_cast(pParm->dwWaveShape)); m_fDirty = true; return hr; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::GetAllParameters // STDMETHODIMP CGargle::GetAllParameters(THIS_ LPGargleFX pParm) { HRESULT hr = S_OK; MP_DATA var; if (pParm == NULL) { return E_POINTER; } #define GET_PARAM_DWORD(x,y) \ if (SUCCEEDED(hr)) { \ hr = GetParam(x, &var); \ if (SUCCEEDED(hr)) pParm->y = (DWORD)var; \ } GET_PARAM_DWORD(GFP_Rate, dwRateHz); GET_PARAM_DWORD(GFP_Shape, dwWaveShape); return hr; } ////////////////////////////////////////////////////////////////////////////// // // CGargle::SetParam // HRESULT CGargle::SetParamInternal(DWORD dwParamIndex, MP_DATA value, bool fSkipPasssingToParamManager) { switch (dwParamIndex) { case GFP_Rate: CHECK_PARAM(GARGLE_FX_RATEHZ_MIN,GARGLE_FX_RATEHZ_MAX); m_ulGargleFreqHz = (unsigned)value; if (m_ulGargleFreqHz < 1) m_ulGargleFreqHz = 1; if (m_ulGargleFreqHz > 1000) m_ulGargleFreqHz = 1000; // Init is where m_ulPeriod is updated, so call it here // Would be better to do this outside of Init though Init(); break; case GFP_Shape: CHECK_PARAM(GARGLE_FX_WAVE_TRIANGLE,GARGLE_FX_WAVE_SQUARE); m_ulShape = (unsigned)value; break; } // Let base class set this so it can handle all the rest of the param calls. // Skip the base class if fSkipPasssingToParamManager. This indicates that we're calling the function // internally using values that came from the base class -- thus there's no need to tell it values it // already knows. return fSkipPasssingToParamManager ? S_OK : CParamsManager::SetParam(dwParamIndex, value); } ////////////////////////////////////////////////////////////////////////////// // // CGargle::Process // HRESULT CGargle::Process(ULONG ulQuanta, LPBYTE pcbData, REFERENCE_TIME rtStart, DWORD dwFlags) { // Update parameter values from any curves that may be in effect. // We pick up the current values stored in the CParamsManager helper for time rtStart. // Note that we are using IMediaParams in a less than // perfect way. We update at the beginning of every time slice instead of smoothly over the curve. // This is okay for an effect like gargle as long as the time slice is consistently small (which // it conveniently is when hosted in DSound.) // However, in the future we will update this sample to use a more appropriate and accurate // mechanism. // Here are some suggestions of how it can be done, with increasing degree of accuracy. Different // types of effects and effect parameters require different levels of accuracy, so no solution is the best // solution for all (especially if you are concerned about CPU cost.) // 1) Break the time slice up into mini pieces of some number of milliseconds // each and run through all the steps in Process for each sub slice. This guarantees the // stair stepping is small enough not to be noticable. This approach will work well for parameters // that don't create an audible stair stepping noise (or "zipper") noise when controled in this way. // Control over volume, for example, does not work well. // 2) Use the above mechanism, but pass the start and end values for each parameter to the // processing engine. It, in turn, applies linear interpolation to each parameter. This results // in a smooth approximation of the parameter curve and removes all but the most subtle aliasing noise. // 3) Pass the curves directly to the processing engine, which accurately calculates each sample // mathematically. This is obviously the best, but most complex and CPU intensive. this->UpdateActiveParams(rtStart, *this); DMO_MEDIA_TYPE mt; HRESULT hr = GetInputCurrentType(0, &mt); if( FAILED( hr ) ) return hr; // convert bytes to samples for the FBRProcess call assert(mt.formattype == FORMAT_WaveFormatEx); ulQuanta /= LPWAVEFORMATEX(mt.pbFormat)->nBlockAlign; MoFreeMediaType( &mt ); return FBRProcess(ulQuanta, pcbData, pcbData); }