/* * Straightforward WAV reading. This only supports 8-bit and 16-bit PCM, * 4-bit ADPCM with one or two channels. No other decompressors are planned: * this format is only useful for fast uncompressed audio, and ADPCM is only * supported to retain compatibility. * * http://www.saettler.com/RIFFNEW/RIFFNEW.htm * http://www.kk.iij4u.or.jp/~kondo/wave/wavecomp.htm * http://www.sonicspot.com/guide/wavefiles.html */ #include "global.h" #include "RageSoundReader_WAV.h" #include "RageUtil.h" #include "RageLog.h" #include "RageFileBasic.h" #include #include namespace { /* pBuf contains iSamples 8-bit samples; convert to 16-bit. pBuf must * have enough storage to hold the resulting data. */ void Convert8bitToFloat( void *pBuf, int iSamples ) { /* Convert in reverse, so we can do it in-place. */ const uint8_t *pIn = (uint8_t *) pBuf; float *pOut = (float *) pBuf; for( int i = iSamples-1; i >= 0; --i ) { int iSample = pIn[i]; iSample -= 128; /* 0..255 -> -128..127 */ pOut[i] = iSample / 128.0f; } } /* Flip 16-bit samples if necessary. On little-endian systems, this will * optimize out. */ void ConvertLittleEndian16BitToFloat( void *pBuf, int iSamples ) { /* Convert in reverse, so we can do it in-place. */ const int16_t *pIn = (int16_t *) pBuf; float *pOut = (float *) pBuf; for( int i = iSamples-1; i >= 0; --i ) { int16_t iSample = Swap16LE( pIn[i] ); pOut[i] = iSample / 32768.0f; } } void ConvertLittleEndian24BitToFloat( void *pBuf, int iSamples ) { /* Convert in reverse, so we can do it in-place. */ const unsigned char *pIn = (unsigned char *) pBuf; float *pOut = (float *) pBuf; pIn += iSamples * 3; for( int i = iSamples-1; i >= 0; --i ) { pIn -= 3; int32_t iSample = (int(pIn[0]) << 0) | (int(pIn[1]) << 8) | (int(pIn[2]) << 16); /* Sign-extend 24-bit to 32-bit: */ if( iSample & 0x800000 ) iSample |= 0xFF000000; pOut[i] = iSample / 8388608.0f; } } void ConvertLittleEndian32BitToFloat( void *pBuf, int iSamples ) { /* Convert in reverse, so we can do it in-place. */ const int32_t *pIn = (int32_t *) pBuf; float *pOut = (float *) pBuf; for( int i = iSamples-1; i >= 0; --i ) { int32_t iSample = Swap32LE( pIn[i] ); pOut[i] = iSample / 2147483648.0f; } } }; struct WavReader { WavReader( RageFileBasic &f, const RageSoundReader_WAV::WavData &data ): m_File(f), m_WavData(data) { } virtual ~WavReader() { } virtual int Read( float *pBuf, int iFrames ) = 0; virtual int GetLength() const = 0; virtual bool Init() = 0; virtual int SetPosition( int iFrame ) = 0; virtual int GetNextSourceFrame() const = 0; RString GetError() const { return m_sError; } protected: RageFileBasic &m_File; const RageSoundReader_WAV::WavData &m_WavData; RString m_sError; }; struct WavReaderPCM: public WavReader { WavReaderPCM( RageFileBasic &f, const RageSoundReader_WAV::WavData &data ): WavReader(f, data) { } bool Init() { if( QuantizeUp(m_WavData.m_iBitsPerSample, 8) < 8 || QuantizeUp(m_WavData.m_iBitsPerSample, 8) > 32 ) { m_sError = ssprintf("Unsupported sample size %i", m_WavData.m_iBitsPerSample); return false; } if( m_WavData.m_iFormatTag == 3 && m_WavData.m_iBitsPerSample != 32 ) { m_sError = ssprintf( "Unsupported float sample size %i", m_WavData.m_iBitsPerSample ); return false; } m_File.Seek( m_WavData.m_iDataChunkPos ); return true; } int Read( float *buf, int iFrames ) { int iBytesPerSample = QuantizeUp(m_WavData.m_iBitsPerSample, 8) / 8; int len = iFrames * m_WavData.m_iChannels; len *= iBytesPerSample; const int iBytesLeftInDataChunk = m_WavData.m_iDataChunkSize - (m_File.Tell() - m_WavData.m_iDataChunkPos); if( !iBytesLeftInDataChunk ) return RageSoundReader::END_OF_FILE; len = std::min( len, iBytesLeftInDataChunk ); int iGot = m_File.Read( buf, len ); int iGotSamples = iGot / iBytesPerSample; if( m_WavData.m_iFormatTag == 1 ) { switch( iBytesPerSample ) { case 1: Convert8bitToFloat( buf, iGotSamples ); break; case 2: ConvertLittleEndian16BitToFloat( buf, iGotSamples ); break; case 3: ConvertLittleEndian24BitToFloat( buf, iGotSamples ); break; case 4: ConvertLittleEndian32BitToFloat( buf, iGotSamples ); /* otherwise 3; already a float */ break; } } return iGotSamples / m_WavData.m_iChannels; } int GetLength() const { const int iBytesPerSec = m_WavData.m_iSampleRate * m_WavData.m_iChannels * m_WavData.m_iBitsPerSample / 8; int64_t iMS = (int64_t(m_WavData.m_iDataChunkSize) * 1000) / iBytesPerSec; return (int) iMS; } int SetPosition( int iFrame ) { int iByte = (int) (int64_t(iFrame) * m_WavData.m_iChannels * m_WavData.m_iBitsPerSample / 8); if( iByte > m_WavData.m_iDataChunkSize ) { m_File.Seek( m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos ); return 0; } m_File.Seek( iByte+m_WavData.m_iDataChunkPos ); return 1; } // XXX: untested int GetNextSourceFrame() const { int iByte = m_File.Tell() - m_WavData.m_iDataChunkPos; int iFrame = iByte / (m_WavData.m_iChannels * m_WavData.m_iBitsPerSample / 8); return iFrame; } }; struct WavReaderADPCM: public WavReader { public: std::vector m_iaCoef1, m_iaCoef2; int16_t m_iFramesPerBlock; float *m_pBuffer; int m_iBufferAvail, m_iBufferUsed; WavReaderADPCM( RageFileBasic &f, const RageSoundReader_WAV::WavData &data ): WavReader(f, data) { m_pBuffer = nullptr; } virtual ~WavReaderADPCM() { delete[] m_pBuffer; } bool Init() { if( m_WavData.m_iBitsPerSample != 4 ) { m_sError = ssprintf( "Unsupported ADPCM sample size %i", m_WavData.m_iBitsPerSample ); return false; } m_File.Seek( m_WavData.m_iExtraFmtPos ); m_iFramesPerBlock = FileReading::read_16_le( m_File, m_sError ); int16_t iNumCoef = FileReading::read_16_le( m_File, m_sError ); m_iaCoef1.resize( iNumCoef ); m_iaCoef2.resize( iNumCoef ); for( int i = 0; i < iNumCoef; ++i ) { m_iaCoef1[i] = FileReading::read_16_le( m_File, m_sError ); m_iaCoef2[i] = FileReading::read_16_le( m_File, m_sError ); } if( m_sError.size() != 0 ) return false; m_pBuffer = new float[m_iFramesPerBlock*m_WavData.m_iChannels]; m_iBufferAvail = m_iBufferUsed = 0; m_File.Seek( m_WavData.m_iDataChunkPos ); return true; } void SetEOF() { m_iBufferUsed = m_iBufferAvail = 0; m_File.Seek( m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos ); } /* Return false on error, true on success (even if we hit EOF). */ bool DecodeADPCMBlock() { ASSERT_M( m_iBufferUsed == m_iBufferAvail, ssprintf("%i", m_iBufferUsed) ); m_iBufferUsed = m_iBufferAvail = 0; m_sError = ""; if( m_File.Tell() >= m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos || m_File.AtEOF() ) return true; /* past the data chunk */ int8_t iPredictor[2]; int16_t iDelta[2], iSamp1[2], iSamp2[2]; for( int i = 0; i < m_WavData.m_iChannels; ++i ) iPredictor[i] = FileReading::read_8( m_File, m_sError ); for( int i = 0; i < m_WavData.m_iChannels; ++i ) iDelta[i] = FileReading::read_16_le( m_File, m_sError ); for( int i = 0; i < m_WavData.m_iChannels; ++i ) iSamp1[i] = FileReading::read_16_le( m_File, m_sError ); for( int i = 0; i < m_WavData.m_iChannels; ++i ) iSamp2[i] = FileReading::read_16_le( m_File, m_sError ); if( m_sError.size() != 0 ) return false; float *pBuffer = m_pBuffer; int iCoef1[2], iCoef2[2]; for( int i = 0; i < m_WavData.m_iChannels; ++i ) { if( iPredictor[i] >= (int) m_iaCoef1.size() ) { LOG->Trace( "%s: predictor out of range", m_File.GetDisplayPath().c_str() ); /* XXX: silence this block? */ iPredictor[i] = 0; } iCoef1[i] = m_iaCoef1[iPredictor[i]]; iCoef2[i] = m_iaCoef2[iPredictor[i]]; } /* We've read the block header; read the rest. Don't read past the end of the data chunk. */ int iMaxSize = std::min( (int) m_WavData.m_iBlockAlign - 7 * m_WavData.m_iChannels, (m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos) - m_File.Tell() ); char *pBuf = (char *) alloca( iMaxSize ); int iBlockSize = m_File.Read( pBuf, iMaxSize ); if( iBlockSize == 0 ) return true; if( iBlockSize == -1 ) { m_sError = m_File.GetError(); return false; } for( int i = 0; i < m_WavData.m_iChannels; ++i ) pBuffer[m_iBufferAvail++] = (int16_t)iSamp2[i] / 32768.0f; for( int i = 0; i < m_WavData.m_iChannels; ++i ) pBuffer[m_iBufferAvail++] = (int16_t)iSamp1[i] / 32768.0f; int8_t iBufSize = 0; uint8_t iBuf = 0; bool bDone = false; for( int i = 2; !bDone && i < m_iFramesPerBlock; ++i ) { for( int c = 0; !bDone && c < m_WavData.m_iChannels; ++c ) { if( iBufSize == 0 ) { if( !iBlockSize ) { bDone = true; continue; } iBuf = *pBuf; ++pBuf; --iBlockSize; iBufSize = 2; } /* Store the nibble in signed char, so we get an arithmetic shift. */ int8_t iErrorDelta = (int8_t)(iBuf) >> 4; uint8_t iErrorDeltaUnsigned = iBuf >> 4; iBuf <<= 4; --iBufSize; int32_t iPredSample = (iSamp1[c] * iCoef1[c] + iSamp2[c] * iCoef2[c]) / (1<<8); if( iPredSample < -32768 ) iPredSample = -32768; if( iPredSample > 32767 ) iPredSample = 32767; int16_t iNewSample = (int16_t)iPredSample + (iDelta[c] * iErrorDelta); pBuffer[m_iBufferAvail++] = iNewSample / 32768.0f; static const int aAdaptionTable[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; iDelta[c] = int16_t( (iDelta[c] * aAdaptionTable[iErrorDeltaUnsigned]) / (1<<8) ); iDelta[c] = std::max( (int16_t) 16, iDelta[c] ); iSamp2[c] = iSamp1[c]; iSamp1[c] = iNewSample; } } return true; } int Read( float *buf, int iFrames ) { int iGotFrames = 0; int iSample = 0; while( iGotFrames < (int) iFrames ) { if( m_iBufferUsed == m_iBufferAvail ) { if( !DecodeADPCMBlock() ) return RageSoundReader::ERROR; } if( m_iBufferAvail == 0 ) { if( !iGotFrames ) return RageSoundReader::END_OF_FILE; else return iGotFrames; } for( int c = 0; c < m_WavData.m_iChannels; c ++ ) { buf[iSample++] = m_pBuffer[m_iBufferUsed++]; } iGotFrames++; } return iGotFrames; } int GetLength() const { const int iNumWholeBlocks = m_WavData.m_iDataChunkSize / m_WavData.m_iBlockAlign; const int iExtraBytes = m_WavData.m_iDataChunkSize - (iNumWholeBlocks*m_WavData.m_iBlockAlign); int iFrames = iNumWholeBlocks * m_iFramesPerBlock; const int iBlockHeaderSize = 7 * m_WavData.m_iChannels; if( iExtraBytes > iBlockHeaderSize ) { const int iExtraADPCMNibbles = std::max( 0, iExtraBytes-iBlockHeaderSize )*2; const int iExtraADPCMFrames = iExtraADPCMNibbles/m_WavData.m_iChannels; iFrames += 2+iExtraADPCMFrames; } int iMS = int((int64_t(iFrames)*1000)/m_WavData.m_iSampleRate); return iMS; } int SetPosition( int iFrame ) { const int iBlock = iFrame / m_iFramesPerBlock; m_iBufferUsed = m_iBufferAvail = 0; { const int iByte = iBlock*m_WavData.m_iBlockAlign; if( iByte >= m_WavData.m_iDataChunkSize ) { /* Past EOF. */ SetEOF(); return 0; } m_File.Seek( iByte+m_WavData.m_iDataChunkPos ); } if( !DecodeADPCMBlock() ) return -1; const int iRemainingFrames = iFrame - iBlock*m_iFramesPerBlock; m_iBufferUsed = iRemainingFrames * m_WavData.m_iChannels; if( m_iBufferUsed > m_iBufferAvail ) { SetEOF(); return 0; } return 1; } // XXX: untested int GetNextSourceFrame() const { int iByte = m_File.Tell() - m_WavData.m_iDataChunkPos; int iBlock = iByte / m_WavData.m_iBlockAlign; int iFrame = iBlock * m_iFramesPerBlock; int iBufferRemainingBytes = m_iBufferAvail - m_iBufferUsed; int iBufferRemainingFrames = iBufferRemainingBytes / (m_WavData.m_iChannels * sizeof(int16_t)); iFrame -= iBufferRemainingFrames; return iFrame; } }; RString ReadString( RageFileBasic &f, int iSize, RString &sError ) { if( sError.size() != 0 ) return RString(); char *buf = new char[iSize + 1]; std::fill(buf, buf + iSize + 1, '\0'); FileReading::ReadBytes( f, buf, iSize, sError ); RString ret(buf); delete [] buf; return ret; } #define FATAL_ERROR(s) \ { \ if( sError.size() == 0 ) sError = (s); \ SetError( sError ); \ return OPEN_FATAL_ERROR; \ } RageSoundReader_FileReader::OpenResult RageSoundReader_WAV::Open( RageFileBasic *pFile ) { m_pFile = pFile; RString sError; /* RIFF header: */ if( ReadString( *m_pFile, 4, sError ) != "RIFF" ) { SetError( "Not a WAV file" ); return OPEN_UNKNOWN_FILE_FORMAT; } FileReading::read_32_le( *m_pFile, sError ); /* file size */ if( ReadString( *m_pFile, 4, sError ) != "WAVE" ) { SetError( "Not a WAV file" ); return OPEN_UNKNOWN_FILE_FORMAT; } bool bGotFormatChunk = false, bGotDataChunk = false; while( !bGotFormatChunk || !bGotDataChunk ) { RString ChunkID = ReadString( *m_pFile, 4, sError ); int32_t iChunkSize = FileReading::read_32_le( *m_pFile, sError ); if( sError.size() != 0 ) { SetError( sError ); return OPEN_FATAL_ERROR; } int iNextChunk = m_pFile->Tell() + iChunkSize; /* Chunks are always word-aligned: */ iNextChunk = (iNextChunk+1)&~1; if( ChunkID == "fmt " ) { if( bGotFormatChunk ) LOG->Warn( "File %s has more than one fmt chunk", m_pFile->GetDisplayPath().c_str() ); m_WavData.m_iFormatTag = FileReading::read_16_le( *m_pFile, sError ); m_WavData.m_iChannels = FileReading::read_16_le( *m_pFile, sError ); m_WavData.m_iSampleRate = FileReading::read_32_le( *m_pFile, sError ); FileReading::read_32_le( *m_pFile, sError ); /* BytesPerSec */ m_WavData.m_iBlockAlign = FileReading::read_16_le( *m_pFile, sError ); m_WavData.m_iBitsPerSample = FileReading::read_16_le( *m_pFile, sError ); m_WavData.m_iExtraFmtBytes = FileReading::read_16_le( *m_pFile, sError ); if( m_WavData.m_iChannels < 1 || m_WavData.m_iChannels > 2 ) FATAL_ERROR( ssprintf( "Unsupported channel count: %i", m_WavData.m_iChannels) ); if( m_WavData.m_iSampleRate < 4000 || m_WavData.m_iSampleRate > 100000 ) /* unlikely */ FATAL_ERROR( ssprintf( "Invalid sample rate: %i", m_WavData.m_iSampleRate) ); m_WavData.m_iExtraFmtPos = m_pFile->Tell(); bGotFormatChunk = true; } if( ChunkID == "data" ) { m_WavData.m_iDataChunkPos = m_pFile->Tell(); m_WavData.m_iDataChunkSize = iChunkSize; int iFileSize = m_pFile->GetFileSize(); int iMaxSize = iFileSize-m_WavData.m_iDataChunkPos; if( iMaxSize < m_WavData.m_iDataChunkSize ) { LOG->Warn( "File %s truncated (%i < data chunk size %i)", m_pFile->GetDisplayPath().c_str(), iMaxSize, m_WavData.m_iDataChunkSize ); m_WavData.m_iDataChunkSize = iMaxSize; } bGotDataChunk = true; } m_pFile->Seek( iNextChunk ); } if( sError.size() != 0 ) { SetError( sError ); return OPEN_FATAL_ERROR; } switch( m_WavData.m_iFormatTag ) { case 1: // PCM case 3: // FLOAT m_pImpl = new WavReaderPCM( *m_pFile, m_WavData ); break; case 2: // ADPCM m_pImpl = new WavReaderADPCM( *m_pFile, m_WavData ); break; case 85: // MP3 /* Return unknown, so other decoders will be tried. MAD can read MP3s embedded in WAVs. */ return OPEN_UNKNOWN_FILE_FORMAT; default: FATAL_ERROR( ssprintf( "Unsupported data format %i", m_WavData.m_iFormatTag) ); } if( !m_pImpl->Init() ) { SetError( m_pImpl->GetError() ); return OPEN_FATAL_ERROR; } return OPEN_OK; } int RageSoundReader_WAV::GetLength() const { ASSERT( m_pImpl != nullptr ); return m_pImpl->GetLength(); } int RageSoundReader_WAV::SetPosition( int iFrame ) { ASSERT( m_pImpl != nullptr ); return m_pImpl->SetPosition( iFrame ); } int RageSoundReader_WAV::GetNextSourceFrame() const { ASSERT( m_pImpl != nullptr ); return m_pImpl->GetNextSourceFrame(); } int RageSoundReader_WAV::Read( float *pBuf, int iFrames ) { ASSERT( m_pImpl != nullptr ); return m_pImpl->Read( pBuf, iFrames ); } RageSoundReader_WAV::RageSoundReader_WAV() { m_pImpl = nullptr; } RageSoundReader_WAV::~RageSoundReader_WAV() { delete m_pImpl; } RageSoundReader_WAV *RageSoundReader_WAV::Copy() const { RageSoundReader_WAV *ret = new RageSoundReader_WAV; RageFileBasic *pFile = m_pFile->Copy(); pFile->Seek( 0 ); ret->Open( pFile ); return ret; } /* * (c) 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. */