/** * @file windgen.h * @brief Templated wind noise generation * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #ifndef WINDGEN_H #define WINDGEN_H #include "llcommon.h" #include "llrand.h" template class LLWindGen { public: LLWindGen(const U32 sample_rate = 44100) : mTargetGain(0.f), mTargetFreq(100.f), mTargetPanGainR(0.5f), mInputSamplingRate(sample_rate), mSubSamples(2), mFilterBandWidth(50.f), mBuf0(0.0f), mBuf1(0.0f), mBuf2(0.0f), mY0(0.0f), mY1(0.0f), mCurrentGain(0.f), mCurrentFreq(100.f), mCurrentPanGainR(0.5f), mLastSample(0.f) { mSamplePeriod = (F32)mSubSamples / (F32)mInputSamplingRate; mB2 = expf(-F_TWO_PI * mFilterBandWidth * mSamplePeriod); } const U32 getInputSamplingRate() { return mInputSamplingRate; } const F32 getNextSample(); const F32 getClampedSample(bool clamp, F32 sample); // newbuffer = the buffer passed from the previous DSP unit. // numsamples = length in samples-per-channel at this mix time. // NOTE: generates L/R interleaved stereo MIXBUFFERFORMAT_T* windGenerate(MIXBUFFERFORMAT_T *newbuffer, int numsamples) { MIXBUFFERFORMAT_T *cursamplep = newbuffer; // Filter coefficients F32 a0 = 0.0f, b1 = 0.0f; // No need to clip at normal volumes bool clip = mCurrentGain > 2.0f; bool interp_freq = false; //if the frequency isn't changing much, we don't need to interpolate in the inner loop if (llabs(mTargetFreq - mCurrentFreq) < (mCurrentFreq * 0.112)) { // calculate resonant filter coefficients mCurrentFreq = mTargetFreq; b1 = (-4.0f * mB2) / (1.0f + mB2) * cosf(F_TWO_PI * (mCurrentFreq * mSamplePeriod)); a0 = (1.0f - mB2) * sqrtf(1.0f - (b1 * b1) / (4.0f * mB2)); } else { interp_freq = true; } while (numsamples) { F32 next_sample; // Start with white noise // This expression is fragile, rearrange it and it will break! next_sample = getNextSample(); // Apply a pinking filter // Magic numbers taken from PKE method at http://www.firstpr.com.au/dsp/pink-noise/ mBuf0 = mBuf0 * 0.99765f + next_sample * 0.0990460f; mBuf1 = mBuf1 * 0.96300f + next_sample * 0.2965164f; mBuf2 = mBuf2 * 0.57000f + next_sample * 1.0526913f; next_sample = mBuf0 + mBuf1 + mBuf2 + next_sample * 0.1848f; if (interp_freq) { // calculate and interpolate resonant filter coefficients mCurrentFreq = (0.999f * mCurrentFreq) + (0.001f * mTargetFreq); b1 = (-4.0f * mB2) / (1.0f + mB2) * cosf(F_TWO_PI * (mCurrentFreq * mSamplePeriod)); a0 = (1.0f - mB2) * sqrtf(1.0f - (b1 * b1) / (4.0f * mB2)); } // Apply a resonant low-pass filter on the pink noise next_sample = a0 * next_sample - b1 * mY0 - mB2 * mY1; mY1 = mY0; mY0 = next_sample; mCurrentGain = (0.999f * mCurrentGain) + (0.001f * mTargetGain); mCurrentPanGainR = (0.999f * mCurrentPanGainR) + (0.001f * mTargetPanGainR); // For a 3dB pan law use: // next_sample *= mCurrentGain * ((mCurrentPanGainR*(mCurrentPanGainR-1)*1.652+1.413); next_sample *= mCurrentGain; // delta is used to interpolate between synthesized samples F32 delta = (next_sample - mLastSample) / (F32)mSubSamples; // Fill the audio buffer, clipping if necessary for (U8 i=mSubSamples; i && numsamples; --i, --numsamples) { mLastSample = mLastSample + delta; MIXBUFFERFORMAT_T sample_right = (MIXBUFFERFORMAT_T)getClampedSample(clip, mLastSample * mCurrentPanGainR); MIXBUFFERFORMAT_T sample_left = (MIXBUFFERFORMAT_T)getClampedSample(clip, mLastSample - (F32)sample_right); *cursamplep = sample_left; ++cursamplep; *cursamplep = sample_right; ++cursamplep; } } return newbuffer; } public: F32 mTargetGain; F32 mTargetFreq; F32 mTargetPanGainR; private: U32 mInputSamplingRate; U8 mSubSamples; F32 mSamplePeriod; F32 mFilterBandWidth; F32 mB2; F32 mBuf0; F32 mBuf1; F32 mBuf2; F32 mY0; F32 mY1; F32 mCurrentGain; F32 mCurrentFreq; F32 mCurrentPanGainR; F32 mLastSample; }; template inline const F32 LLWindGen::getNextSample() { return (F32)rand() * (1.0f / (F32)(RAND_MAX / (U16_MAX / 8))) + (F32)(S16_MIN / 8); } template<> inline const F32 LLWindGen::getNextSample() { return ll_frand()-.5f; } template inline const F32 LLWindGen::getClampedSample(bool clamp, F32 sample) { return clamp ? (F32)llclamp((S32)sample,(S32)S16_MIN,(S32)S16_MAX) : sample; } template<> inline const F32 LLWindGen::getClampedSample(bool clamp, F32 sample) { return sample; } #endif