Files
itgmania212121/src/RageSoundReader_Resample_Good.cpp
T
2023-04-20 11:21:29 +02:00

756 lines
21 KiB
C++

/*
* This implements audio resampling, using the method described at:
* http://www.dspguru.com/info/faqs/mrfaq.htm
*
* Each conversion ratio uses some memory, but the resulting table is
* shared, so the memory overhead per stream is negligible.
*/
#include "global.h"
#include "RageSoundReader_Resample_Good.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "RageMath.h"
#include "RageThreads.h"
#include <cmath>
#include <cstddef>
#include <numeric>
/* Filter length. This must be a power of 2. */
#define L 8
namespace
{
float sincf( float f )
{
if( f == 0 )
return 1;
return std::sin(f) / f;
}
/* Modified Bessel function I0. From Abramowitz and Stegun "Handbook of Mathematical
* Functions", "Modified Bessel Functions I and K". */
float BesselI0( float fX )
{
float fAbsX = std::abs( fX );
if( fAbsX < 3.75f )
{
float y = fX / 3.75f;
y *= y;
float fRet = 1.0f+y*(+3.5156229f+y*(+3.0899424f+y*(+1.2067492f+y*(+0.2659732f+y*(+0.0360768f+y*+0.0045813f)))));
return fRet;
}
else
{
float y = 3.75f/fAbsX;
float fRet = (std::exp(fAbsX)/std::sqrt(fAbsX)) *
(+0.39894228f+y*(+0.01328592f+y*(+0.00225319f+y*(-0.00157565f+y*(0.00916281f+
y*(-0.02057706f+y*(+0.02635537f+y*(-0.01647633f+y*+0.00392377f))))))));
return fRet;
}
}
/*
* Kaiser window:
*
* K(n) = I0( B*sqrt(1-(n/p)^2) )
* -----------------------
* I0(B)
*
* where B is the beta parameter, p is len/2, and n is in [-len/2,+len/2].
*/
void ApplyKaiserWindow( float *pBuf, int iLen, float fBeta )
{
const float fDenom = BesselI0(fBeta);
float p = (iLen-1)/2.0f;
for( int n = 0; n < iLen; ++n )
{
float fN1 = std::abs((n-p)/p);
float fNum = fBeta * std::sqrt( std::max(1.0f - fN1*fN1, 0.0f) );
fNum = BesselI0( fNum );
float fVal = fNum/fDenom;
pBuf[n] *= fVal;
}
}
void MultiplyVector( float *pStart, float *pEnd, float f )
{
for( ; pStart != pEnd; ++pStart )
*pStart *= f;
}
void GenerateSincLowPassFilter( float *pFIR, int iWinSize, float fCutoff )
{
float p = (iWinSize-1)/2.0f;
for( int n = 0; n < iWinSize; ++n )
{
float fN1 = (n-p);
float fVal = sincf(2*PI*fCutoff * fN1)*(2*fCutoff);
// printf( "n %i, %f, %f -> %f\n", n, p, fN1, fVal );
pFIR[n] = fVal;
}
#if 0
float *pFIRp = pFIR+iWinSize/2;
for(int i=-iWinSize/2;i<=iWinSize/2;i++)
{
float ff = sinc(2*M_PI*fCutoff * (i + 0.0))*(2*fCutoff);
printf( "%i: %f\n", i, ff );
pFIRp[i]=ff;
}
for( int i=0; i < iWinSize; i++ )
printf( "sinc: %i: %f\n", i, pFIR[i] );
#endif
}
void NormalizeVector( float *pBuf, int iSize )
{
float fTotal = std::accumulate( &pBuf[0], &pBuf[iSize], 0.0f );
MultiplyVector( &pBuf[0], &pBuf[iSize], 1/fTotal );
}
int GCD( int i1, int i2 )
{
return std::gcd(i1, i2);
}
}
#if 0
void RunFIRFilter( float *pIn, float *pOut, int iInputValues, float *pFIR, int iWinSize )
{
for( int i = 0; i < iInputValues; ++i )
{
float fSum = 0;
const float *pInData = &pIn[i];
for( int j = 0; j < iWinSize; ++j )
{
float in = pInData[j];
fSum += in*pFIR[j];
printf( "%i: in %f * %f, += %f\n", j, pInData[j], pFIR[j], in*pFIR[j] );
}
pOut[i] = fSum;
}
}
#endif
template<typename T>
class AlignedBuffer
{
public:
AlignedBuffer( int iSize )
{
m_iSize = iSize;
m_pBuf = new T[m_iSize];
}
AlignedBuffer( const AlignedBuffer &cpy )
{
m_iSize = cpy.m_iSize;
m_pBuf = new T[m_iSize];
memcpy( m_pBuf, cpy.m_pBuf, sizeof(T)*m_iSize );
}
~AlignedBuffer()
{
delete [] m_pBuf;
}
operator T*() { return m_pBuf; }
operator const T*() const { return m_pBuf; }
private:
T& operator=( T &rhs );
int m_iSize;
T *m_pBuf;
};
struct PolyphaseFilter
{
struct State
{
State( int iUpFactor ):
m_fBuf( L * 2 )
{
m_iPolyIndex = iUpFactor-1;
m_iFilled = 0;
m_iBufNext = 0;
}
int m_iPolyIndex;
int m_iFilled;
/* This buffer is duplicated. If the circular buffer is size L, the actual buffer
* is size L*2, and data at buf[N] is also at buf[N+L]. That way, we can access
* up to buf[N*2-1] without having to wrap. */
AlignedBuffer<float> m_fBuf;
int m_iBufNext;
};
friend struct State;
PolyphaseFilter( int iUpFactor ):
m_pPolyphase( L*iUpFactor )
{
m_iUpFactor = iUpFactor;
}
void Generate( const float *pFIR );
int RunPolyphaseFilter( State &State, const float *pIn, int iSamplesIn, int iDownFactor,
float *pOut, int iSamplesOut, int iSampleStride ) const;
int GetLatency() const { return L/2; }
int NumInputsForOutputSamples( const State &State, int iOut, int iDownFactor ) const;
private:
AlignedBuffer<float> m_pPolyphase;
int m_iUpFactor;
};
/*
* Convert an FIR filter to a polyphase filter.
*
* pFIR is the input FIR filter, which has iL*iUpFactor values.
* iL is the number of real samples each output sample looks at.
* iUpFactor is the actual upsampling factor; the amount of zero-stuffing between each real sample.
* pOutput is the 2D output polyphase filter, with iL*iL values.
*
* With an upsampling factor (iUpFactor) of 3, and a sinc filter length of 12 (iL*iUpFactor),
*
* input first output sample (before decimation)
* sample second output sample
* third output sample
*
* 0 0
* 0 1 0
* 1592 2 1 0
* 0 3 2 1
* 0 4 3 2
* 1623 5 4 3
* 0 6 5 4
* 0 7 6 5
* 1682 8 7 6
* 0 9 8 7
* 0 10 9 8
* 1730 11 10 9
* 0 11 10
* 0 11
*
* first row: 2, 5, 8, 11
* second: 1, 4, 7, 10
* third: 0, 3, 6, 9
* Read a new sample after passing the last line.
*/
void PolyphaseFilter::Generate( const float *pFIR )
{
float *pOutput=m_pPolyphase;
int iInputSize = L*m_iUpFactor;
for( int iRow = 0; iRow < m_iUpFactor; ++iRow )
{
int iInputOffset = (m_iUpFactor-iRow-1) % m_iUpFactor;
for( int iCol = 0; iCol < L; ++iCol )
{
*pOutput = pFIR[iInputOffset];
++pOutput;
iInputOffset += m_iUpFactor;
iInputOffset %= iInputSize;
}
}
}
/*
* We only want one boundary check when running the filter; either on the
* number of inputs used, or the number of outputs produced. Otherwise, we'll
* have to maintain two counters, and check two values per iteration.
*
* First, call NumInputsForOutputSamples(out), to find out how many inputs to supply to get
* the desired number of outputs. Then, pass the data, the input count
* and the output count to RunPolyphaseFilter.
*
* - When downsampling, we use the number of inputs as the boundary. For example,
* if the ratio is 1:3 (downsample x3), and the user gives us 10 samples, then we
* process until we've consumed all of the input. (This will result in exactly
* the number of samples the user asked for with NumInputsForOutputSamples.)
*
* - When upsampling, we use the number of outputs as the boundary. For example,
* if the ratio is 3:1 (upsample x3), and the user wants 8 samples to be output,
* we'll have been given 3 samples as input. Process until we've produced 8
* samples.
*
* In both cases, we have overlap. In the first, it's possible that we could
* have consumed an additional input without producing an output. In the second,
* it's possible that we could have produced an additional output without
* consuming an input.
*/
int PolyphaseFilter::RunPolyphaseFilter(
State &State,
const float *pIn, int iSamplesIn, int iDownFactor,
float *pOut, int iSamplesOut,
int iSampleStride ) const
{
ASSERT( iSamplesIn >= 0 );
float *pOutOrig = pOut;
const float *pInEnd = pIn + iSamplesIn*iSampleStride;
const float *pOutEnd = pOut + iSamplesOut*iSampleStride;
int iFilled = State.m_iFilled;
int iPolyIndex = State.m_iPolyIndex;
while( pOut != pOutEnd )
{
if( iFilled < L )
{
if( pIn == pInEnd )
break;
State.m_fBuf[State.m_iBufNext] = *pIn;
State.m_fBuf[State.m_iBufNext + L] = *pIn;
++State.m_iBufNext;
State.m_iBufNext &= L-1;
pIn += iSampleStride;
++iFilled;
continue;
}
while( pOut != pOutEnd )
{
const float *pCurPoly = &m_pPolyphase[iPolyIndex*L];
const float *pInData = &State.m_fBuf[State.m_iBufNext];
float fTot = 0;
for( int j = 0; j < L; ++j )
fTot += pInData[j]*pCurPoly[j];
*pOut = fTot;
pOut += iSampleStride;
iPolyIndex += iDownFactor;
if( iPolyIndex >= m_iUpFactor )
break;
}
iFilled -= iPolyIndex/m_iUpFactor;
iPolyIndex %= m_iUpFactor;
}
State.m_iFilled = iFilled;
State.m_iPolyIndex = iPolyIndex;
int iRetSamples = pOut - pOutOrig;
int iRetFrames = iRetSamples / iSampleStride;
return iRetFrames;
}
/*
* Return the number of input samples needed to produce the given number of output
* samples. This is dependent on the number of bytes in the buffer and the current
* position of the stream.
*/
int PolyphaseFilter::NumInputsForOutputSamples( const State &State, int iOut, int iDownFactor ) const
{
int iIn = 0;
int iFilled = State.m_iFilled;
int iPolyIndex = State.m_iPolyIndex;
#if 0
while( iOut > 0 )
{
if( iFilled < L )
{
int iToFill = L-iFilled;
iIn += iToFill;
iFilled += iToFill;
}
while( iFilled == L && iOut )
{
--iOut;
iPolyIndex += iDownFactor;
if( iPolyIndex >= m_iUpFactor )
break;
}
iFilled -= iPolyIndex/m_iUpFactor;
iPolyIndex %= m_iUpFactor;
}
#endif
if( iOut > 0 )
{
if( iFilled < L )
{
int iToFill = L-iFilled;
iIn += iToFill;
}
// The -1 here is because we don't refill m_fBuf after writing the last output.
iPolyIndex += iDownFactor*(iOut-1);
iIn += iPolyIndex/m_iUpFactor;
}
return iIn;
}
/** @brief Utilities for working with the PolyphaseFilter cache. */
namespace PolyphaseFilterCache
{
/* Cache filter data, and reuse it without copying. All operations after creation
* are const, so this doesn't cause thread-safety problems. */
typedef std::map<std::pair<int, float>, PolyphaseFilter*> FilterMap;
static RageMutex PolyphaseFiltersLock("PolyphaseFiltersLock");
static FilterMap g_mapPolyphaseFilters;
const PolyphaseFilter *MakePolyphaseFilter( int iUpFactor, float fCutoffFrequency )
{
PolyphaseFiltersLock.Lock();
std::pair<int, float> params( std::make_pair(iUpFactor, fCutoffFrequency) );
FilterMap::const_iterator it = g_mapPolyphaseFilters.find(params);
if( it != g_mapPolyphaseFilters.end() )
{
/* We already have a filter for this upsampling factor and cutoff; use it. */
PolyphaseFilter *pPolyphase = it->second;
PolyphaseFiltersLock.Unlock();
return pPolyphase;
}
int iWinSize = L*iUpFactor;
float *pFIR = new float[iWinSize];
GenerateSincLowPassFilter( pFIR, iWinSize, fCutoffFrequency );
ApplyKaiserWindow( pFIR, iWinSize, 8 );
NormalizeVector( pFIR, iWinSize );
MultiplyVector( &pFIR[0], &pFIR[iWinSize], (float) iUpFactor );
PolyphaseFilter *pPolyphase = new PolyphaseFilter( iUpFactor );
pPolyphase->Generate( pFIR );
delete [] pFIR;
g_mapPolyphaseFilters[params] = pPolyphase;
PolyphaseFiltersLock.Unlock();
return pPolyphase;
}
const PolyphaseFilter *FindNearestPolyphaseFilter( int iUpFactor, float fCutoffFrequency )
{
/* Find a cached filter with the same iUpFactor and a nearby cutoff frequency.
* Round the cutoff down, if possible; it's better to filter out too much than
* too little. */
PolyphaseFiltersLock.Lock();
std::pair<int, float> params( std::make_pair(iUpFactor, fCutoffFrequency + 0.0001f) );
FilterMap::const_iterator it = g_mapPolyphaseFilters.upper_bound( params );
if( it != g_mapPolyphaseFilters.begin() )
--it;
ASSERT( it->first.first == iUpFactor );
PolyphaseFilter *pPolyphase = it->second;
PolyphaseFiltersLock.Unlock();
return pPolyphase;
}
}
/*
* Interface to PolyphaseFilter, providing a simple resampling interface. This handles
* reuse of PolyphaseFilters. This does not handle delay, flushing, or multiple channels.
*/
class RageSoundResampler_Polyphase
{
public:
/* Note that going outside of [iMinDownFactor,iMaxDownFactor] while resampling isn't
* fatal. It'll only cause aliasing, by not having a LPF that's low enough, or cause
* too much filtering, by not having a LPF that's high enough. */
RageSoundResampler_Polyphase( int iUpFactor, int iMinDownFactor, int iMaxDownFactor )
{
/* Cache filters between iMinDownFactor and iMaxDownFactor. Do them in
* iFilterIncrement increments; we'll round down to the closest match
* when filtering. This will only cause the low-pass filter to be rounded;
* the conversion ratio will always be exact. */
m_iUpFactor = iUpFactor;
m_pPolyphase = nullptr;
int iFilterIncrement = std::max( (iMaxDownFactor - iMinDownFactor)/10, 1 );
for( int iDownFactor = iMinDownFactor; iDownFactor <= iMaxDownFactor; iDownFactor += iFilterIncrement )
{
float fCutoffFrequency = GetCutoffFrequency( iDownFactor );
PolyphaseFilterCache::MakePolyphaseFilter( m_iUpFactor, fCutoffFrequency );
}
SetDownFactor( iUpFactor );
m_pState = new PolyphaseFilter::State( iUpFactor );
}
~RageSoundResampler_Polyphase()
{
delete m_pState;
}
void SetDownFactor( int iDownFactor )
{
m_iDownFactor = iDownFactor;
m_pPolyphase = GetFilter( m_iDownFactor );
}
int Run( const float *pIn, int iSamplesIn, float *pOut, int iSamplesOut, int iSampleStride ) const
{
return m_pPolyphase->RunPolyphaseFilter( *m_pState, pIn, iSamplesIn, m_iDownFactor, pOut, iSamplesOut, iSampleStride );
}
void Reset()
{
delete m_pState;
m_pState = new PolyphaseFilter::State( m_iUpFactor );
}
int NumInputsForOutputSamples( int iOut ) const { return m_pPolyphase->NumInputsForOutputSamples(*m_pState, iOut, m_iDownFactor); }
int GetLatency() const { return m_pPolyphase->GetLatency(); }
int GetFilled() const { return m_pState->m_iFilled; }
RageSoundResampler_Polyphase( const RageSoundResampler_Polyphase &cpy )
{
m_pPolyphase = cpy.m_pPolyphase; // don't copy
m_pState = new PolyphaseFilter::State(*cpy.m_pState);
m_iUpFactor = cpy.m_iUpFactor;
m_iDownFactor = cpy.m_iDownFactor;
}
private:
float GetCutoffFrequency( int iDownFactor ) const
{
/*
* If we're upsampling, we want the low-pass filter to cut off at the
* nyquist frequency of the original sample.
*
* If we're downsampling, we want the low-pass filter to cut off at the
* nyquist frequency of the new sample.
*/
float fCutoffFrequency;
fCutoffFrequency = 1.0f / (2*m_iUpFactor);
fCutoffFrequency = std::min( fCutoffFrequency, 1.0f / (2*iDownFactor) );
return fCutoffFrequency;
}
const PolyphaseFilter *GetFilter( int iDownFactor ) const
{
float fCutoffFrequency = GetCutoffFrequency( iDownFactor );
return PolyphaseFilterCache::FindNearestPolyphaseFilter( m_iUpFactor, fCutoffFrequency );
}
const PolyphaseFilter *m_pPolyphase;
PolyphaseFilter::State *m_pState;
int m_iUpFactor;
int m_iDownFactor;
};
int RageSoundReader_Resample_Good::GetNextSourceFrame() const
{
int64_t iPosition = m_pSource->GetNextSourceFrame();
iPosition -= m_apResamplers[0]->GetFilled();
iPosition *= m_iSampleRate;
iPosition /= m_pSource->GetSampleRate();
return (int) iPosition;
}
bool RageSoundReader_Resample_Good::SetProperty( const RString &sProperty, float fValue )
{
if( sProperty == "Rate" )
{
SetRate( fValue );
return true;
}
return m_pSource->SetProperty( sProperty, fValue );
}
float RageSoundReader_Resample_Good::GetStreamToSourceRatio() const
{
float fRatio = m_pSource->GetStreamToSourceRatio();
if( m_fRate != -1 )
fRatio *= m_fRate;
return fRatio;
}
RageSoundReader_Resample_Good::RageSoundReader_Resample_Good( RageSoundReader *pSource, int iSampleRate ):
RageSoundReader_Filter( pSource )
{
m_iSampleRate = iSampleRate;
m_fRate = -1;
ReopenResampler();
}
/* Call this if the input position is changed or reset. */
void RageSoundReader_Resample_Good::Reset()
{
for( std::size_t iChannel = 0; iChannel < m_pSource->GetNumChannels(); ++iChannel )
m_apResamplers[iChannel]->Reset();
}
void RageSoundReader_Resample_Good::GetFactors( int &iDownFactor, int &iUpFactor ) const
{
iDownFactor = m_pSource->GetSampleRate();
iUpFactor = m_iSampleRate;
{
int iGCD = GCD( iUpFactor, iDownFactor );
iUpFactor /= iGCD;
iDownFactor /= iGCD;
}
bool bRateChangingEnabled = m_fRate != -1;
if( bRateChangingEnabled )
{
iUpFactor *= 100;
iDownFactor *= 100;
}
}
/* Call this if the sample factor changes. */
void RageSoundReader_Resample_Good::ReopenResampler()
{
for( std::size_t iChannel = 0; iChannel < m_apResamplers.size(); ++iChannel )
delete m_apResamplers[iChannel];
m_apResamplers.clear();
int iDownFactor, iUpFactor;
GetFactors( iDownFactor, iUpFactor );
for( std::size_t iChannel = 0; iChannel < m_pSource->GetNumChannels(); ++iChannel )
{
int iMinDownFactor = iDownFactor;
int iMaxDownFactor = iDownFactor;
if( m_fRate != -1 )
iMaxDownFactor *= 5;
RageSoundResampler_Polyphase *p = new RageSoundResampler_Polyphase( iUpFactor, iMinDownFactor, iMaxDownFactor );
m_apResamplers.push_back( p );
}
if( m_fRate != -1 )
iDownFactor = std::lrint( m_fRate * iDownFactor );
for( std::size_t iChannel = 0; iChannel < m_apResamplers.size(); ++iChannel )
m_apResamplers[iChannel]->SetDownFactor( iDownFactor );
}
RageSoundReader_Resample_Good::~RageSoundReader_Resample_Good()
{
for( std::size_t iChannel = 0; iChannel < m_apResamplers.size(); ++iChannel )
delete m_apResamplers[iChannel];
}
/* iFrame is in the destination rate. Seek the source in its own sample rate. */
int RageSoundReader_Resample_Good::SetPosition( int iFrame )
{
Reset();
iFrame = (int) SCALE( iFrame, 0, (int64_t) m_iSampleRate, 0, (int64_t) m_pSource->GetSampleRate() );
return m_pSource->SetPosition( iFrame );
}
int RageSoundReader_Resample_Good::Read( float *pBuf, int iFrames )
{
int iChannels = m_apResamplers.size();
int iFramesRead = 0;
/* If the ratio is 1:1, then we're effectively disabled, and we can read
* directly into the buffer. */
int iDownFactor, iUpFactor;
GetFactors( iDownFactor, iUpFactor );
if( m_apResamplers[0]->GetFilled() == 0 && iDownFactor == iUpFactor && GetRate() == 1.0f )
return m_pSource->Read( pBuf, iFrames );
{
int iFramesNeeded = m_apResamplers[0]->NumInputsForOutputSamples(iFrames);
float *pTmpBuf = (float *) alloca( iFramesNeeded * sizeof(float) * iChannels );
int iFramesIn = m_pSource->Read( pTmpBuf, iFramesNeeded );
if( iFramesIn < 0 )
return iFramesIn;
for( int iChannel = 0; iChannel < iChannels; ++iChannel )
{
int iGotFrames = m_apResamplers[iChannel]->Run( pTmpBuf + iChannel, iFramesIn, pBuf + iChannel, iFrames, iChannels );
ASSERT( iGotFrames <= iFrames );
if( iChannel == 0 )
iFramesRead += iGotFrames;
}
}
return iFramesRead;
}
/*
* A resampler is commonly used for two things: to change the sample rate of audio,
* in order to give an audio driver what it wants (SetSampleRate), and to change the
* sound of audio, changing its speed and pitch (SetRate). These are the same
* operation, and we do both in the same pass; the only difference is that SetSampleRate
* causes GetSampleRate() to change, while SetRate() causes GetStreamToSourceRatio() to change.
*
* Changing these values will take effect immediately, with a buffering latency of L/4
* frames.
*/
void RageSoundReader_Resample_Good::SetRate( float fRatio )
{
ASSERT( fRatio > 0 );
bool bRateChangingWasEnabled = m_fRate != -1;
m_fRate = fRatio;
if( !bRateChangingWasEnabled )
ReopenResampler();
int iDownFactor, iUpFactor;
GetFactors( iDownFactor, iUpFactor );
if( m_fRate != -1 )
iDownFactor = std::lrint( m_fRate * iDownFactor );
/* Set m_fRate to the actual rate, after quantization by iUpFactor. */
m_fRate = float(iDownFactor) / iUpFactor;
for( std::size_t iChannel = 0; iChannel < m_apResamplers.size(); ++iChannel )
m_apResamplers[iChannel]->SetDownFactor( iDownFactor );
}
float RageSoundReader_Resample_Good::GetRate() const
{
if( m_fRate == -1 )
return 1.0f;
else
return m_fRate;
}
RageSoundReader_Resample_Good::RageSoundReader_Resample_Good( const RageSoundReader_Resample_Good &cpy ):
RageSoundReader_Filter(cpy)
{
for( std::size_t i = 0; i < cpy.m_apResamplers.size(); ++i )
this->m_apResamplers.push_back( new RageSoundResampler_Polyphase(*cpy.m_apResamplers[i]) );
this->m_iSampleRate = cpy.m_iSampleRate;
this->m_fRate = cpy.m_fRate;
}
RageSoundReader_Resample_Good *RageSoundReader_Resample_Good::Copy() const
{
return new RageSoundReader_Resample_Good( *this );
}
/*
* (c) 2006 Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/