#include "global.h" #include "DSoundHelpers.h" #include "RageUtil.h" #include "RageLog.h" #include "archutils/Win32/DirectXHelpers.h" #include "archutils/Win32/GetFileInformation.h" #if defined(_WINDOWS) #include #endif #define DIRECTSOUND_VERSION 0x0700 #include #if defined(_MSC_VER) #pragma comment(lib, "dsound.lib") #endif BOOL CALLBACK DSound::EnumCallback( LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext ) { RString sLine = ssprintf( "DirectSound Driver: %s", lpcstrDescription ); if( lpcstrModule[0] ) { sLine += ssprintf( " %s", lpcstrModule ); #ifndef _XBOX RString sPath = FindSystemFile( lpcstrModule ); if( sPath != "" ) { RString sVersion; if( GetFileVersion(sPath, sVersion) ) sLine += ssprintf( " %s", sVersion.c_str() ); } #endif } LOG->Info( "%s", sLine.c_str() ); return TRUE; } void DSound::SetPrimaryBufferMode() { #ifndef _XBOX DSBUFFERDESC format; memset( &format, 0, sizeof(format) ); format.dwSize = sizeof(format); format.dwFlags = DSBCAPS_PRIMARYBUFFER; format.dwBufferBytes = 0; format.lpwfxFormat = NULL; IDirectSoundBuffer *pBuffer; HRESULT hr = this->GetDS()->CreateSoundBuffer( &format, &pBuffer, NULL ); if( FAILED(hr) ) { LOG->Warn(hr_ssprintf(hr, "Couldn't create primary buffer")); return; } WAVEFORMATEX waveformat; memset( &waveformat, 0, sizeof(waveformat) ); waveformat.cbSize = 0; waveformat.wFormatTag = WAVE_FORMAT_PCM; waveformat.wBitsPerSample = 16; waveformat.nChannels = 2; waveformat.nSamplesPerSec = 44100; waveformat.nBlockAlign = 4; waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec * waveformat.nBlockAlign; // Set the primary buffer's format hr = IDirectSoundBuffer_SetFormat( pBuffer, &waveformat ); if( FAILED(hr) ) LOG->Warn( hr_ssprintf(hr, "SetFormat on primary buffer") ); DWORD got; hr = pBuffer->GetFormat( &waveformat, sizeof(waveformat), &got ); if( FAILED(hr) ) LOG->Warn( hr_ssprintf(hr, "GetFormat on primary buffer") ); else if( waveformat.nSamplesPerSec != 44100 ) LOG->Warn( "Primary buffer set to %i instead of 44100", waveformat.nSamplesPerSec ); /* * MS docs: * * When there are no sounds playing, DirectSound stops the mixer engine and halts DMA * (direct memory access) activity. If your application has frequent short intervals of * silence, the overhead of starting and stopping the mixer each time a sound is played * may be worse than the DMA overhead if you kept the mixer active. Also, some sound * hardware or drivers may produce unwanted audible artifacts from frequent starting and * stopping of playback. If your application is playing audio almost continuously with only * short breaks of silence, you can force the mixer engine to remain active by calling the * IDirectSoundBuffer::Play method for the primary buffer. The mixer will continue to run * silently. * * However, I just added the above code and I don't want to change more until it's tested. */ // pBuffer->Play( 0, 0, DSBPLAY_LOOPING ); pBuffer->Release(); #endif } DSound::DSound() { HRESULT hr; if( FAILED( hr = CoInitialize(NULL) ) ) RageException::Throw( hr_ssprintf(hr, "CoInitialize") ); m_pDS = NULL; } RString DSound::Init() { HRESULT hr; if( FAILED( hr = DirectSoundCreate(NULL, &m_pDS, NULL) ) ) return hr_ssprintf( hr, "DirectSoundCreate" ); #ifndef _XBOX static bool bShownInfo = false; if( !bShownInfo ) { bShownInfo = true; DirectSoundEnumerate( EnumCallback, 0 ); DSCAPS Caps; Caps.dwSize = sizeof(Caps); HRESULT hr; if( FAILED(hr = m_pDS->GetCaps(&Caps)) ) { LOG->Warn( hr_ssprintf(hr, "m_pDS->GetCaps failed") ); } else { LOG->Info( "DirectSound sample rates: %i..%i %s", Caps.dwMinSecondarySampleRate, Caps.dwMaxSecondarySampleRate, (Caps.dwFlags & DSCAPS_CONTINUOUSRATE)?"(continuous)":"" ); } } /* Try to set primary mixing privileges */ hr = m_pDS->SetCooperativeLevel( GetDesktopWindow(), DSSCL_PRIORITY ); #endif SetPrimaryBufferMode(); return RString(); } DSound::~DSound() { if( m_pDS != NULL ) m_pDS->Release(); CoUninitialize(); } bool DSound::IsEmulated() const { #ifndef _XBOX /* Don't bother wasting time trying to create buffers if we're * emulated. This also gives us better diagnostic information. */ DSCAPS Caps; Caps.dwSize = sizeof(Caps); HRESULT hr; if( FAILED(hr = m_pDS->GetCaps(&Caps)) ) { LOG->Warn( hr_ssprintf(hr, "m_pDS->GetCaps failed") ); /* This is strange, so let's be conservative. */ return true; } return !!(Caps.dwFlags & DSCAPS_EMULDRIVER); #else return false; #endif } DSoundBuf::DSoundBuf() { m_pBuffer = NULL; m_pTempBuffer = NULL; } RString DSoundBuf::Init( DSound &ds, DSoundBuf::hw hardware, int iChannels, int iSampleRate, int iSampleBits, int iWriteAhead ) { m_iChannels = iChannels; m_iSampleRate = iSampleRate; m_iSampleBits = iSampleBits; m_iWriteAhead = iWriteAhead * bytes_per_frame(); m_iVolume = -1; /* unset */ m_bBufferLocked = false; m_iWriteCursorPos = m_iWriteCursor = m_iBufferBytesFilled = 0; m_iExtraWriteahead = 0; m_iLastPosition = 0; m_bPlaying = false; ZERO( m_iLastCursors ); /* The size of the actual DSound buffer. This can be large; we generally * won't fill it completely. */ m_iBufferSize = 1024*64; m_iBufferSize = max( m_iBufferSize, m_iWriteAhead ); WAVEFORMATEX waveformat; memset( &waveformat, 0, sizeof(waveformat) ); waveformat.cbSize = 0; waveformat.wFormatTag = WAVE_FORMAT_PCM; bool bNeedCtrlFrequency = false; if( m_iSampleRate == DYNAMIC_SAMPLERATE ) { m_iSampleRate = 44100; bNeedCtrlFrequency = true; } int bytes = m_iSampleBits / 8; waveformat.wBitsPerSample = WORD(m_iSampleBits); waveformat.nChannels = WORD(m_iChannels); waveformat.nSamplesPerSec = DWORD(m_iSampleRate); waveformat.nBlockAlign = WORD(bytes*m_iChannels); waveformat.nAvgBytesPerSec = m_iSampleRate * bytes*m_iChannels; /* Try to create the secondary buffer */ DSBUFFERDESC format; memset( &format, 0, sizeof(format) ); format.dwSize = sizeof(format); #ifdef _XBOX format.dwFlags = 0; #else format.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME; #endif #ifndef _XBOX /* Don't use DSBCAPS_STATIC. It's meant for static buffers, and we * only use streaming buffers. */ if( hardware == HW_HARDWARE ) format.dwFlags |= DSBCAPS_LOCHARDWARE; else format.dwFlags |= DSBCAPS_LOCSOFTWARE; #endif if( bNeedCtrlFrequency ) format.dwFlags |= DSBCAPS_CTRLFREQUENCY; format.dwBufferBytes = m_iBufferSize; #ifndef _XBOX format.dwReserved = 0; #else DSMIXBINVOLUMEPAIR dsmbvp[8] = { { DSMIXBIN_FRONT_LEFT, DSBVOLUME_MAX }, // left channel { DSMIXBIN_FRONT_RIGHT, DSBVOLUME_MAX }, // right channel { DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX }, // left channel { DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX }, // right channel { DSMIXBIN_BACK_LEFT, DSBVOLUME_MAX }, // left channel { DSMIXBIN_BACK_RIGHT, DSBVOLUME_MAX }, // right channel { DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX }, // left channel { DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX } // right channel }; DSMIXBINS dsmb; dsmb.dwMixBinCount = 8; dsmb.lpMixBinVolumePairs = dsmbvp; format.lpMixBins = &dsmb; #endif format.lpwfxFormat = &waveformat; HRESULT hr = ds.GetDS()->CreateSoundBuffer( &format, &m_pBuffer, NULL ); if( FAILED(hr) ) return hr_ssprintf( hr, "CreateSoundBuffer failed (%i hz)", m_iSampleBits ); #ifndef _XBOX /* I'm not sure this should ever be needed, but ... */ DSBCAPS bcaps; bcaps.dwSize=sizeof(bcaps); hr = m_pBuffer->GetCaps( &bcaps ); if( FAILED(hr) ) return hr_ssprintf( hr, "m_pBuffer->GetCaps" ); if( int(bcaps.dwBufferBytes) != m_iBufferSize ) { LOG->Warn( "bcaps.dwBufferBytes (%i) != m_iBufferSize(%i); adjusting", bcaps.dwBufferBytes, m_iBufferSize ); m_iBufferSize = bcaps.dwBufferBytes; m_iWriteAhead = min( m_iWriteAhead, m_iBufferSize ); } if( !(bcaps.dwFlags & DSBCAPS_CTRLVOLUME) ) LOG->Warn( "Sound channel missing DSBCAPS_CTRLVOLUME" ); if( !(bcaps.dwFlags & DSBCAPS_GETCURRENTPOSITION2) ) LOG->Warn( "Sound channel missing DSBCAPS_GETCURRENTPOSITION2" ); DWORD got; hr = m_pBuffer->GetFormat( &waveformat, sizeof(waveformat), &got ); if( FAILED(hr) ) LOG->Warn( hr_ssprintf(hr, "GetFormat on secondary buffer") ); else if( (int) waveformat.nSamplesPerSec != m_iSampleRate ) LOG->Warn( "Secondary buffer set to %i instead of %i", waveformat.nSamplesPerSec, m_iSampleRate ); #endif m_pTempBuffer = new char[m_iBufferSize]; return RString(); } void DSoundBuf::SetSampleRate( int hz ) { m_iSampleRate = hz; HRESULT hr = m_pBuffer->SetFrequency( hz ); if( FAILED(hr) ) RageException::Throw( hr_ssprintf(hr, "m_pBuffer->SetFrequency(%i)", hz) ); } void DSoundBuf::SetVolume( float fVolume ) { ASSERT_M( fVolume >= 0 && fVolume <= 1, ssprintf("%f",fVolume) ); if( fVolume == 0 ) fVolume = 0.001f; // fix log10f(0) == -INF float iVolumeLog2 = log10f(fVolume) / log10f(2); /* vol log 2 */ /* Volume is a multiplier; SetVolume wants attenuation in hundredths of a decibel. */ const int iNewVolume = max( int(1000 * iVolumeLog2), DSBVOLUME_MIN ); if( m_iVolume == iNewVolume ) return; HRESULT hr = m_pBuffer->SetVolume( iNewVolume ); if( FAILED(hr) ) { static bool bWarned = false; if( !bWarned ) LOG->Warn( hr_ssprintf(hr, "DirectSoundBuffer::SetVolume(%i) failed", iNewVolume) ); bWarned = true; return; } m_iVolume = iNewVolume; } /* Determine if "pos" is between "start" and "end", for a circular buffer. Note that * a start/end pos is ambiguous when start == end; it can mean that the buffer is * completely full or completely empty; this function treats it as completely empty. */ static bool contained( int iStart, int iEnd, int iPos ) { if( iEnd >= iStart ) /* iStart ... iPos ... iEnd */ return iStart <= iPos && iPos < iEnd; else return iPos >= iStart || iPos < iEnd; } DSoundBuf::~DSoundBuf() { if( m_pBuffer != NULL ) m_pBuffer->Release(); delete [] m_pTempBuffer; } /* Check to make sure that, given the current writeahead and chunksize, we're * capable of filling the prefetch region entirely. If we aren't, increase * the writeahead. If this happens, we're underruning. */ void DSoundBuf::CheckWriteahead( int iCursorStart, int iCursorEnd ) { /* If we're in a recovering-from-underrun state, stop. */ if( m_iExtraWriteahead ) return; /* If the driver is requesting an unreasonably large prefetch, ignore it entirely. * Some drivers seem to give broken write cursors sporadically, requesting that * almost the entire buffer be filled. There's no reason a driver should ever need * more than 8k frames of writeahead. */ int iPrefetch = iCursorEnd - iCursorStart; wrap( iPrefetch, m_iBufferSize ); if( iPrefetch >= 1024*32 ) { static bool bLogged = false; if( bLogged ) return; bLogged = true; LOG->Warn("Sound driver is requesting an overly large prefetch: wants %i (cursor at %i..%i), writeahead not adjusted", iPrefetch / bytes_per_frame(), iCursorStart, iCursorEnd ); return; } if( m_iWriteAhead >= iPrefetch ) return; /* We need to increase the writeahead. */ LOG->Trace("insufficient writeahead: wants %i (cursor at %i..%i), writeahead adjusted from %i to %i", iPrefetch / bytes_per_frame(), iCursorStart, iCursorEnd, m_iWriteAhead, iPrefetch ); m_iWriteAhead = iPrefetch; } /* Figure out if we've underrun, and act if appropriate. */ void DSoundBuf::CheckUnderrun( int iCursorStart, int iCursorEnd ) { /* If the buffer is full, we can't be underrunning. */ if( m_iBufferBytesFilled >= m_iBufferSize ) return; /* If nothing is expected to be filled, we can't underrun. */ if( iCursorStart == iCursorEnd ) return; /* If we're already in a recovering-from-underrun state, stop. */ if( m_iExtraWriteahead ) return; int iFirstByteFilled = m_iWriteCursor - m_iBufferBytesFilled; wrap( iFirstByteFilled, m_iBufferSize ); /* If the end of the play cursor has data, we haven't underrun. */ if( m_iBufferBytesFilled > 0 && contained(iFirstByteFilled, m_iWriteCursor, iCursorEnd) ) return; /* Extend the writeahead to force fill as much as required to stop underrunning. * This has a major benefit: if we havn't skipped so long we've passed a whole * buffer (64k = ~350ms), this doesn't break stride. We'll skip forward, but * the beat won't be lost, which is a lot easier to recover from in play. */ /* XXX: If this happens repeatedly over a period of time, increase writeahead. */ /* XXX: What was I doing here? This isn't working. We want to know the writeahead * value needed to fill from the current iFirstByteFilled all the way to iCursorEnd. */ // int iNeededWriteahead = (iCursorStart + writeahead) - m_iWriteCursor; int iNeededWriteahead = iCursorEnd - iFirstByteFilled; wrap( iNeededWriteahead, m_iBufferSize ); if( iNeededWriteahead > m_iWriteAhead ) { m_iExtraWriteahead = iNeededWriteahead - m_iWriteAhead; m_iWriteAhead = iNeededWriteahead; } int iMissedBy = iCursorEnd - m_iWriteCursor; wrap( iMissedBy, m_iBufferSize ); RString s = ssprintf( "underrun: %i..%i (%i) filled but cursor at %i..%i; missed it by %i", iFirstByteFilled, m_iWriteCursor, m_iBufferBytesFilled, iCursorStart, iCursorEnd, iMissedBy ); if( m_iExtraWriteahead ) s += ssprintf( "; extended writeahead by %i to %i", m_iExtraWriteahead, m_iWriteAhead ); s += "; last: "; for( int i = 0; i < 4; ++i ) s += ssprintf( "%i, %i; ", m_iLastCursors[i][0], m_iLastCursors[i][1] ); LOG->Trace( "%s", s.c_str() ); } bool DSoundBuf::get_output_buf( char **pBuffer, unsigned *pBufferSize, int iChunksize ) { ASSERT( !m_bBufferLocked ); iChunksize *= bytes_per_frame(); DWORD iCursorStart, iCursorEnd; HRESULT result; /* It's easiest to think of the cursor as a block, starting and ending at * the two values returned by GetCurrentPosition, that we can't write to. */ result = m_pBuffer->GetCurrentPosition( &iCursorStart, &iCursorEnd ); #ifndef _XBOX if( result == DSERR_BUFFERLOST ) { m_pBuffer->Restore(); result = m_pBuffer->GetCurrentPosition( &iCursorStart, &iCursorEnd ); } if( result != DS_OK ) { LOG->Warn( hr_ssprintf(result, "DirectSound::GetCurrentPosition failed") ); return false; } #endif memmove( &m_iLastCursors[0][0], &m_iLastCursors[1][0], sizeof(int)*6 ); m_iLastCursors[3][0] = iCursorStart; m_iLastCursors[3][1] = iCursorEnd; /* Some cards (Creative AudioPCI) have a no-write area even when not playing. I'm not * sure what that means, but it breaks the assumption that we can fill the whole writeahead * when prebuffering. */ if( !m_bPlaying ) iCursorEnd = iCursorStart; /* * Some cards (Game Theater XP 7.1 hercwdm.sys 5.12.01.4101 [466688b, 01-10-2003]) * have odd behavior when starting a sound: the start/end cursors go: * * 0,0 end cursor forced equal to start above (normal) * 4608, 1764 end cursor trailing the write cursor; except with old emulated * WaveOut devices, this shouldn't happen; it indicates that the * driver expects almost the whole buffer to be filled. Also, the * play cursor is too far ahead from the last call for the amount * of actual time passed. * 704, XXX start cursor moves back to where it should be. I don't have an exact * end cursor position, but in general from now on it stays about 5kb * ahead of start (which is where it should be). * * The second call is completely wrong; both the start and end cursors are meaningless. * Detect this: if the end cursor is close behind the start cursor, don't do anything. * (We can't; we have no idea what the cursors actually are.) */ { int iPrefetch = iCursorEnd - iCursorStart; wrap( iPrefetch, m_iBufferSize ); if( m_iBufferSize - iPrefetch < 1024*4 ) { LOG->Trace( "Strange DirectSound cursor ignored: %i..%i", iCursorStart, iCursorEnd ); return false; } } /* Update m_iBufferBytesFilled. */ { int iFirstByteFilled = m_iWriteCursor - m_iBufferBytesFilled; wrap( iFirstByteFilled, m_iBufferSize ); /* The number of bytes that have been played since the last time we got here: */ int bytes_played = iCursorStart - iFirstByteFilled; wrap( bytes_played, m_iBufferSize ); m_iBufferBytesFilled -= bytes_played; m_iBufferBytesFilled = max( 0, m_iBufferBytesFilled ); if( m_iExtraWriteahead ) { int used = min( m_iExtraWriteahead, bytes_played ); RString s = ssprintf("used %i of %i (%i..%i)", used, m_iExtraWriteahead, iCursorStart, iCursorEnd ); s += "; last: "; for( int i = 0; i < 4; ++i ) s += ssprintf( "%i, %i; ", m_iLastCursors[i][0], m_iLastCursors[i][1] ); LOG->Trace("%s", s.c_str()); m_iWriteAhead -= used; m_iExtraWriteahead -= used; } } CheckWriteahead( iCursorStart, iCursorEnd ); CheckUnderrun( iCursorStart, iCursorEnd ); /* If we already have enough bytes written ahead, stop. */ if( m_iBufferBytesFilled >= m_iWriteAhead ) return false; /* If we don't have enough free space in the buffer to fill a whole chunk, stop. */ if( m_iBufferSize - m_iBufferBytesFilled < iChunksize ) return false; int iNumBytesEmpty = m_iWriteAhead - m_iBufferBytesFilled; iNumBytesEmpty = QuantizeUp( iNumBytesEmpty, iChunksize ); // LOG->Trace("gave %i at %i (%i, %i) %i filled", iNumBytesEmpty, m_iWriteCursor, cursor, write, m_iBufferBytesFilled); /* Lock the audio buffer. */ result = m_pBuffer->Lock( m_iWriteCursor, iNumBytesEmpty, (LPVOID *) &m_pLockedBuf1, (DWORD *) &m_iLockedSize1, (LPVOID *) &m_pLockedBuf2, (DWORD *) &m_iLockedSize2, 0 ); #ifndef _XBOX if( result == DSERR_BUFFERLOST ) { m_pBuffer->Restore(); result = m_pBuffer->Lock( m_iWriteCursor, iNumBytesEmpty, (LPVOID *) &m_pLockedBuf1, (DWORD *) &m_iLockedSize1, (LPVOID *) &m_pLockedBuf2, (DWORD *) &m_iLockedSize2, 0 ); } #endif if( result != DS_OK ) { LOG->Warn( hr_ssprintf(result, "Couldn't lock the DirectSound buffer.") ); return false; } *pBuffer = m_pTempBuffer; *pBufferSize = m_iLockedSize1 + m_iLockedSize2; m_iWriteCursor += iNumBytesEmpty; if( m_iWriteCursor >= m_iBufferSize ) m_iWriteCursor -= m_iBufferSize; m_iBufferBytesFilled += iNumBytesEmpty; m_iWriteCursorPos += iNumBytesEmpty / bytes_per_frame(); m_bBufferLocked = true; return true; } void DSoundBuf::release_output_buf( char *pBuffer, unsigned iBufferSize ) { memcpy( m_pLockedBuf1, pBuffer, m_iLockedSize1 ); memcpy( m_pLockedBuf2, pBuffer+m_iLockedSize1, m_iLockedSize2 ); m_pBuffer->Unlock( m_pLockedBuf1, m_iLockedSize1, m_pLockedBuf2, m_iLockedSize2 ); m_bBufferLocked = false; } int64_t DSoundBuf::GetPosition() const { DWORD iCursor, iJunk; HRESULT hr = m_pBuffer->GetCurrentPosition( &iCursor, &iJunk ); #ifndef _XBOX if( hr == DSERR_BUFFERLOST ) { m_pBuffer->Restore(); hr = m_pBuffer->GetCurrentPosition( &iCursor, &iJunk ); } if( hr != DS_OK ) { LOG->Warn( hr_ssprintf(hr, "DirectSound::GetPosition failed") ); iCursor = 0; } #endif /* This happens occasionally on "Realtek AC97 Audio". */ if( (int) iCursor == m_iBufferSize ) iCursor = 0; ASSERT_M( (int) iCursor < m_iBufferSize, ssprintf("%i, %i", iCursor, m_iBufferSize) ); int iCursorFrames = int(iCursor) / bytes_per_frame(); int iWriteCursorFrames = m_iWriteCursor / bytes_per_frame(); int iFramesBehind = iWriteCursorFrames - iCursorFrames; /* iFramesBehind will be 0 if we're called before the buffer starts playing: * both iWriteCursorFrames and iCursorFrames will be 0. */ if( iFramesBehind < 0 ) iFramesBehind += buffersize_frames(); /* unwrap */ int64_t iRet = m_iWriteCursorPos - iFramesBehind; /* Failsafe: never return a value smaller than we've already returned. * This can happen once in a while in underrun conditions. */ iRet = max( m_iLastPosition, iRet ); m_iLastPosition = iRet; return iRet; } void DSoundBuf::Play() { if( m_bPlaying ) return; m_pBuffer->Play( 0, 0, DSBPLAY_LOOPING ); m_bPlaying = true; } void DSoundBuf::Stop() { if( !m_bPlaying ) return; m_pBuffer->Stop(); m_pBuffer->SetCurrentPosition(0); m_iWriteCursorPos = m_iWriteCursor = m_iBufferBytesFilled = 0; m_iLastPosition = 0; m_iWriteAhead -= m_iExtraWriteahead; m_iExtraWriteahead = 0; /* When stopped and rewound, the play and write cursors should both be 0. */ /* This isn't true on some broken cards. */ // DWORD iPlay, iWrite; // m_pBuffer->GetCurrentPosition( &iPlay, &iWrite ); // ASSERT_M( iPlay == 0 && iWrite == 0, ssprintf("%i, %i", iPlay, iWrite) ); m_bPlaying = false; } /* * (c) 2002-2004 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. */