diff --git a/stepmania/src/RageSoundReader_WAV.cpp b/stepmania/src/RageSoundReader_WAV.cpp index 8db83897d0..ce162b723a 100644 --- a/stepmania/src/RageSoundReader_WAV.cpp +++ b/stepmania/src/RageSoundReader_WAV.cpp @@ -1,651 +1,517 @@ +/* + * 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 "RageLog.h" #include "RageUtil.h" +#include "RageLog.h" +#include "RageFile.h" -#define BAIL_IF_MACRO(c, e, r) if (c) { SetError(e); return r; } -#define RETURN_IF_MACRO(c, r) if (c) return r; - -#define riffID 0x46464952 /* "RIFF", in ascii. */ -#define waveID 0x45564157 /* "WAVE", in ascii. */ -#define fmtID 0x20746D66 /* "fmt ", in ascii. */ -#define dataID 0x61746164 /* "data", in ascii. */ - -enum +namespace { - FMT_NORMAL= 1, /* Uncompressed waveform data. */ - FMT_ADPCM = 2, /* ADPCM compressed waveform data. */ - FMT_ITU_G711_ALAW = 6, /* ITU G.711 A-law */ - FMT_ITU_G711_MULAW = 7, /* ITU G.711 mu-law */ - FMT_IMA_ADPCM = 17, /* IMA ADPCM */ - FMT_ITU_G723_ADPCM = 20, /* ITU G.723 ADPCM */ - FMT_GSM_610 = 49, /* GSM 6.10 */ - FMT_ITU_G721_ADPCM = 64, /* ITU G.721 ADPCM */ - FMT_MPEG = 80, /* MPEG */ - FMT_MPEG_L3 = 85 /* MPEG Layer 3 */ + /* pBuf contains iSamples 8-bit samples; convert to 16-bit. pBuf must + * have enough storage to hold the resulting data. */ + void Convert8bitTo16bit( void *pBuf, int iSamples ) + { + /* Convert in reverse, so we can do it in-place. */ + const uint8_t *pIn = (uint8_t *) pBuf; + int16_t *pOut = (int16_t *) pBuf; + for( int i = iSamples-1; i >= 0; --i ) + pOut[i] = SCALE( pIn[i], 0, 255, -32768, 32767 ); + } + + /* Flip 16-bit samples if necessary. On little-endian systems, this will + * optimize out. */ + void Convert16BitFromLittleEndian( int16_t *pBuf, int iSamples ) + { + for( int i = 0; i < iSamples; ++i ) + pBuf[i] = Swap16LE( pBuf[i] ); + } }; -/* Call this to convert milliseconds to an actual byte position, based on audio data characteristics. */ -uint32_t RageSoundReader_WAV::ConvertMsToBytePos(int BytesPerSample, int channels, uint32_t ms) const +struct WavReader { - const float frames_per_ms = ((float) SampleRate) / 1000.0f; - const uint32_t frame_offset = (uint32_t) (frames_per_ms * float(ms) + 0.5f); - const uint32_t frame_size = (uint32_t) BytesPerSample * channels; - return frame_offset * frame_size; -} + WavReader( RageFile &f, const RageSoundReader_WAV::WavData &data ): + m_File(f), m_WavData(data) { } + virtual ~WavReader() { } + virtual int Read( char *buf, unsigned len ) = 0; + virtual int GetLength() const = 0; + virtual void Init() = 0; + virtual int SetPosition( int iMS ) = 0; + CString GetError() const { return m_sError; } -uint32_t RageSoundReader_WAV::ConvertBytePosToMs(int BytesPerSample, int channels, uint32_t pos) const -{ - const uint32_t frame_size = (uint32_t) BytesPerSample * channels; - const uint32_t frame_no = pos / frame_size; - const float frames_per_ms = ((float) SampleRate) / 1000.0f; - return (uint32_t) ((frame_no / frames_per_ms) + 0.5f); -} +protected: + RageFile &m_File; + const RageSoundReader_WAV::WavData &m_WavData; + CString m_sError; +}; -bool RageSoundReader_WAV::read_le16( RageFile &f, int16_t *si16 ) const +struct WavReaderPCM: public WavReader { - const int ret = f.Read( si16, sizeof(int16_t) ); - if( ret != sizeof(int16_t) ) + WavReaderPCM( RageFile &f, const RageSoundReader_WAV::WavData &data ): + WavReader(f, data) { } + + void Init() { - SetError( ret >= 0? "end of file": f.GetError().c_str() ); - return false; - } - *si16 = Swap16LE( *si16 ); - return true; -} + if( m_WavData.m_iBitsPerSample != 8 && m_WavData.m_iBitsPerSample != 16 ) + throw FileReading::FatalError( ssprintf("Unsupported sample size %i", m_WavData.m_iBitsPerSample) ); -bool RageSoundReader_WAV::read_le16( RageFile &f, uint16_t *ui16 ) const -{ - const int ret = f.Read( ui16, sizeof(uint16_t) ); - if( ret != sizeof(uint16_t) ) + m_File.Seek( m_WavData.m_iDataChunkPos ); + } + + int Read( char *buf, unsigned len ) { - SetError( ret >= 0? "end of file": f.GetError().c_str() ); - return false; - } - *ui16 = Swap16LE(*ui16); - return true; -} + if( m_WavData.m_iBitsPerSample == 8 ) + len /= 2; + const unsigned iBytesLeftInDataChunk = m_WavData.m_iDataChunkSize - (m_File.Tell() - m_WavData.m_iDataChunkPos); + len = min( len, iBytesLeftInDataChunk ); + int iGot = m_File.Read( buf, len ); -bool RageSoundReader_WAV::read_le32( RageFile &f, int32_t *si32 ) const -{ - const int ret = f.Read( si32, sizeof(int32_t) ); - if( ret != sizeof(int32_t) ) - { - SetError( ret >= 0? "end of file": f.GetError().c_str() ); - return false; - } - *si32 = Swap32LE( *si32 ); - return true; -} - -bool RageSoundReader_WAV::read_le32( RageFile &f, uint32_t *ui32 ) const -{ - const int ret = f.Read( ui32, sizeof(uint32_t) ); - if( ret != sizeof(uint32_t) ) - { - SetError( ret >= 0? "end of file": f.GetError().c_str() ); - return false; - } - *ui32 = Swap32LE( *ui32 ); - return true; -} - -bool RageSoundReader_WAV::read_uint8( RageFile &f, uint8_t *ui8 ) const -{ - const int ret = f.Read( ui8, sizeof(uint8_t) ); - if( ret != sizeof(uint8_t) ) - { - SetError( ret >= 0? "end of file": f.GetError().c_str() ); - return false; - } - return true; -} - -RageSoundReader_WAV::adpcm_t::adpcm_t() -{ - cbSize = 0; - memset( blockheaders, 0, sizeof(blockheaders) ); - wSamplesPerBlock = 0; - samples_left_in_block = 0; - nibble_state = 0; - nibble = 0; -} - - -bool RageSoundReader_WAV::read_fmt_chunk() -{ - RETURN_IF_MACRO(!read_le16(rw, &fmt.wFormatTag), false); - RETURN_IF_MACRO(!read_le16(rw, &fmt.wChannels), false); - RETURN_IF_MACRO(!read_le32(rw, &SampleRate), false); - RETURN_IF_MACRO(!read_le32(rw, &fmt.dwAvgBytesPerSec), false); - RETURN_IF_MACRO(!read_le16(rw, &fmt.wBlockAlign), false); - RETURN_IF_MACRO(!read_le16(rw, &fmt.wBitsPerSample), false); - - if( fmt.wFormatTag == FMT_ADPCM ) - { - RETURN_IF_MACRO(!read_le16(rw, &adpcm.cbSize), false); - RETURN_IF_MACRO(!read_le16(rw, &adpcm.wSamplesPerBlock), false); - uint16_t NumCoef; - RETURN_IF_MACRO(!read_le16(rw, &NumCoef), false); - - for ( int i = 0; i < NumCoef; i++ ) + switch( m_WavData.m_iBitsPerSample ) { - int16_t c1, c2; - RETURN_IF_MACRO(!read_le16(rw, &c1), false); - RETURN_IF_MACRO(!read_le16(rw, &c2), false); - - adpcm.Coef1.push_back( c1 ); - adpcm.Coef2.push_back( c2 ); + case 8: + Convert8bitTo16bit( buf, iGot ); + iGot *= 2; + break; + case 16: + Convert16BitFromLittleEndian( (int16_t *) buf, iGot/2 ); + iGot &= ~1; + break; } + return iGot; } - return true; -} - - -int RageSoundReader_WAV::read_sample_fmt_normal(char *buf, unsigned len) -{ - const int ret = this->rw.Read( buf, len ); - if( ret == -1 ) + int GetLength() const { - SetError( ret >= 0? "end of file": rw.GetError().c_str() ); - return -1; + 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; } - return ret; -} - - -int RageSoundReader_WAV::seek_sample_fmt_normal( uint32_t ms ) -{ - const int offset = ConvertMsToBytePos( BytesPerSample, Channels, ms); - const int pos = (int) (this->fmt.data_starting_offset + offset); - - const int ret = this->rw.Seek( pos ); - BAIL_IF_MACRO( ret == -1, this->rw.GetError(), -1 ); - - /* If we seek past end of ifle, leave the cursor there, so subsequent reads will return EOF. */ - if( pos >= this->rw.GetFileSize() ) - return 0; - - return ms; -} - -int RageSoundReader_WAV::get_length_fmt_adpcm() const -{ - int offset = this->rw.GetFileSize() - fmt.data_starting_offset; - - /* pcm bytes per block */ - const int bpb = (adpcm.wSamplesPerBlock * fmt.adpcm_sample_frame_size); - const int blockno = offset / fmt.wBlockAlign; - const int byteno = blockno * bpb; - - /* Seek back to the beginning of the last frame and find out how long it really is. */ - this->rw.Seek( blockno * fmt.wBlockAlign + fmt.data_starting_offset ); - - /* Don't mess up this->adpcm; we'll put the cursor back as if nothing happened. */ - adpcm_t tmp_adpcm(adpcm); - if ( !read_adpcm_block_headers(tmp_adpcm) ) - return 0; - - return ConvertBytePosToMs( BytesPerSample, Channels, byteno) + - ConvertBytePosToMs( BytesPerSample, Channels, tmp_adpcm.samples_left_in_block * fmt.adpcm_sample_frame_size); -} - - -int RageSoundReader_WAV::get_length_fmt_normal() const -{ - const int offset = this->rw.GetFileSize(); - return ConvertBytePosToMs( BytesPerSample, Channels, offset - this->fmt.data_starting_offset); -} - -#define FIXED_POINT_COEF_BASE 256 -#define FIXED_POINT_ADAPTION_BASE 256 -#define SMALLEST_ADPCM_DELTA 16 - -bool RageSoundReader_WAV::read_adpcm_block_headers( adpcm_t &out ) const -{ - ADPCMBLOCKHEADER *headers = out.blockheaders; - - for (int i = 0; i < fmt.wChannels; i++) - RETURN_IF_MACRO(!read_uint8(rw, &headers[i].bPredictor), false); - - for (int i = 0; i < fmt.wChannels; i++) - RETURN_IF_MACRO(!read_le16(rw, &headers[i].iDelta), false); - - for (int i = 0; i < fmt.wChannels; i++) - RETURN_IF_MACRO(!read_le16(rw, &headers[i].iSamp[0]), false); - - for (int i = 0; i < fmt.wChannels; i++) - RETURN_IF_MACRO(!read_le16(rw, &headers[i].iSamp[1]), false); - - out.samples_left_in_block = out.wSamplesPerBlock; - out.nibble_state = 0; - return true; -} - - -void RageSoundReader_WAV::do_adpcm_nibble(uint8_t nib, ADPCMBLOCKHEADER *header, int32_t lPredSamp) -{ - static const int32_t max_audioval = ((1<<(16-1))-1); - static const int32_t min_audioval = -(1<<(16-1)); - static const int32_t AdaptionTable[] = - { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 - }; - - int32_t lNewSamp = lPredSamp; - - if (nib & 0x08) - lNewSamp += header->iDelta * (nib - 0x10); - else - lNewSamp += header->iDelta * nib; - - lNewSamp = clamp(lNewSamp, min_audioval, max_audioval); - - int32_t delta = ((int32_t) header->iDelta * AdaptionTable[nib]) / - FIXED_POINT_ADAPTION_BASE; - - delta = max( delta, SMALLEST_ADPCM_DELTA ); - - header->iDelta = int16_t(delta); - header->iSamp[1] = header->iSamp[0]; - header->iSamp[0] = int16_t(lNewSamp); -} - - -bool RageSoundReader_WAV::decode_adpcm_sample_frame() -{ - ADPCMBLOCKHEADER *headers = adpcm.blockheaders; - - uint8_t nib = adpcm.nibble; - for (int i = 0; i < this->fmt.wChannels; i++) + int SetPosition( int iMS ) { - const int16_t iCoef1 = adpcm.Coef1[headers[i].bPredictor]; - const int16_t iCoef2 = adpcm.Coef2[headers[i].bPredictor]; - const int32_t lPredSamp = ((headers[i].iSamp[0] * iCoef1) + - (headers[i].iSamp[1] * iCoef2)) / FIXED_POINT_COEF_BASE; - - if (adpcm.nibble_state == 0) + const int iBytesPerSec = m_WavData.m_iSampleRate * m_WavData.m_iChannels * m_WavData.m_iBitsPerSample / 8; + int iByte = (int) ((int64_t(iMS) * iBytesPerSec) / 1000); + if( iByte > m_WavData.m_iDataChunkSize ) { - if( !read_uint8(this->rw, &nib) ) - return false; - adpcm.nibble_state = 1; - do_adpcm_nibble(nib >> 4, &headers[i], lPredSamp); - } - else - { - adpcm.nibble_state = 0; - do_adpcm_nibble(nib & 0x0F, &headers[i], lPredSamp); - } - } - - adpcm.nibble = nib; - return true; -} - - -void RageSoundReader_WAV::put_adpcm_sample_frame( uint16_t *buf, int frame ) -{ - ADPCMBLOCKHEADER *headers = adpcm.blockheaders; - for (int i = 0; i < fmt.wChannels; i++) - *(buf++) = headers[i].iSamp[frame]; -} - - -uint32_t RageSoundReader_WAV::read_sample_fmt_adpcm(char *buf, unsigned len) -{ - uint32_t bw = 0; - - while (bw < len) - { - /* Read a new block. */ - if( adpcm.samples_left_in_block == 0 ) - if (!read_adpcm_block_headers(adpcm)) - return bw; - - const bool first_sample_in_block = ( adpcm.samples_left_in_block == adpcm.wSamplesPerBlock ); - put_adpcm_sample_frame( (uint16_t *) (buf + bw), first_sample_in_block? 1:0 ); - adpcm.samples_left_in_block--; - bw += this->fmt.adpcm_sample_frame_size; - - if( !first_sample_in_block && adpcm.samples_left_in_block ) - { - if (!decode_adpcm_sample_frame()) - { - adpcm.samples_left_in_block = 0; - return bw; - } - } - } - - return bw; -} - - - -int RageSoundReader_WAV::seek_sample_fmt_adpcm( uint32_t ms ) -{ - const int offset = ConvertMsToBytePos( BytesPerSample, Channels, ms ); - const int bpb = (adpcm.wSamplesPerBlock * this->fmt.adpcm_sample_frame_size); - const int skipsize = (offset / bpb) * this->fmt.wBlockAlign; - - const int pos = skipsize + this->fmt.data_starting_offset; - int rc = this->rw.Seek( pos ); - BAIL_IF_MACRO(rc == -1, this->rw.GetError(), -1); - - /* The offset we need is in this block, so we need to decode to there. */ - rc = offset % bpb; /* bytes into this block we need to decode */ - adpcm.samples_left_in_block = 0; - - if( rc == 0 ) - return ms; - - if (!read_adpcm_block_headers(adpcm)) - { - adpcm.samples_left_in_block = 0; - return 0; - } - - adpcm.samples_left_in_block--; - rc -= this->fmt.adpcm_sample_frame_size; - - while (rc > 0) - { - adpcm.samples_left_in_block--; - rc -= this->fmt.adpcm_sample_frame_size; - - if (!decode_adpcm_sample_frame()) - { - adpcm.samples_left_in_block = 0; + m_File.Seek( m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos ); return 0; } + + m_File.Seek( iByte+m_WavData.m_iDataChunkPos ); + return int((int64_t(iByte) * 1000) / iBytesPerSec); } +}; - return ms; -} - - -/* Locate a chunk by ID. */ -int RageSoundReader_WAV::find_chunk( uint32_t id, int32_t &size ) +struct WavReaderADPCM: public WavReader { - uint32_t pos = this->rw.Tell(); - while (1) +public: + vector m_iaCoef1, m_iaCoef2; + int16_t m_iFramesPerBlock; + int8_t *m_pBuffer; + int m_iBufferAvail, m_iBufferUsed; + + WavReaderADPCM( RageFile &f, const RageSoundReader_WAV::WavData &data ): + WavReader(f, data) { - uint32_t id_ = 0; - if( !read_le32(rw, &id_) ) - return false; - if( !read_le32(rw, &size) ) - return false; + m_pBuffer = NULL; + } - if (id_ == id) - return true; + virtual ~WavReaderADPCM() + { + delete[] m_pBuffer; + } - if(size < 0) - return false; + void Init() + { + if( m_WavData.m_iBitsPerSample != 4 ) + throw FileReading::FatalError( ssprintf( "Unsupported ADPCM sample size %i", m_WavData.m_iBitsPerSample ) ); - pos += (sizeof (uint32_t) * 2) + size; - int ret = this->rw.Seek( pos ); - if( ret == -1 ) + m_File.Seek( m_WavData.m_iExtraFmtPos ); + + m_iFramesPerBlock = FileReading::read_16_le( m_File ); + int16_t iNumCoef = FileReading::read_16_le( m_File ); + m_iaCoef1.resize( iNumCoef ); + m_iaCoef2.resize( iNumCoef ); + for( int i = 0; i < iNumCoef; ++i ) { - SetError( this->rw.GetError() ); - return false; - } - } -} - - -SoundReader_FileReader::OpenResult RageSoundReader_WAV::WAV_open_internal() -{ - uint32_t magic1; - if( !read_le32(rw, &magic1) || magic1 != riffID ) - { - SetError( "WAV: Not a RIFF file." ); - return OPEN_UNKNOWN_FILE_FORMAT; - } - - uint32_t ignore; - read_le32(rw, &ignore); /* throw the length away; we get this info later. */ - - uint32_t magic2; - if( !read_le32( rw, &magic2 ) || magic2 != waveID ) - { - SetError( "Not a WAVE file." ); - return OPEN_UNKNOWN_FILE_FORMAT; - } - - int32_t NextChunk; - BAIL_IF_MACRO(!find_chunk(fmtID, NextChunk), "No format chunk.", OPEN_FATAL_ERROR); - NextChunk += this->rw.Tell(); - BAIL_IF_MACRO(!read_fmt_chunk(), "Can't read format chunk.", OPEN_FATAL_ERROR); - - /* I think multi-channel WAVs are possible, but I've never even seen one. */ - Channels = (uint8_t) fmt.wChannels; - ASSERT( Channels <= 2 ); - - if( fmt.wFormatTag != FMT_NORMAL && - fmt.wFormatTag != FMT_ADPCM ) - { - CString format; - switch( fmt.wFormatTag ) - { - case FMT_ITU_G711_ALAW: format = "ITU G.711 A-law"; break; - case FMT_ITU_G711_MULAW: format = "ITU G.711 mu-law"; break; - case FMT_IMA_ADPCM: format = "IMA ADPCM"; break; - case FMT_ITU_G723_ADPCM: format = "ITU G.723 ADPCM"; break; - case FMT_GSM_610: format = "GSM 6.10"; break; - case FMT_ITU_G721_ADPCM: format = "ITU G.721 ADPCM"; break; - case FMT_MPEG: format = "MPEG"; break; - case FMT_MPEG_L3: format = "MPEG Layer 3"; break; // or "other"? - default: format = ssprintf( "Unknown WAV format #%i", fmt.wFormatTag ); break; + m_iaCoef1[i] = FileReading::read_16_le( m_File ); + m_iaCoef2[i] = FileReading::read_16_le( m_File ); } - SetError( ssprintf("%s not supported", format.c_str() ) ); + m_pBuffer = new int8_t[m_iFramesPerBlock*m_WavData.m_iChannels*sizeof(int16_t)]; + m_iBufferAvail = m_iBufferUsed = 0; - /* It might be MP3 data in a WAV. (Why do people *do* that?) It's possible - * that the MAD decoder will figure that out, so let's return OPEN_UNKNOWN_FILE_FORMAT - * and keep searching for a decoder. */ - if( fmt.wFormatTag == FMT_MPEG_L3 ) - return OPEN_UNKNOWN_FILE_FORMAT; - - return OPEN_FATAL_ERROR; + m_File.Seek( m_WavData.m_iDataChunkPos ); } - if ( fmt.wBitsPerSample == 4 && this->fmt.wFormatTag == FMT_ADPCM ) + void SetEOF() { - Conversion = CONV_NONE; - BytesPerSample = 2; + m_iBufferUsed = m_iBufferAvail = 0; + m_File.Seek( m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos ); } - else if (fmt.wBitsPerSample == 8) + + /* Return false on error, true on success (even if we hit EOF). */ + void DecodeADPCMBlock() { - Conversion = CONV_8BIT_TO_16BIT; - BytesPerSample = 1; + ASSERT_M( m_iBufferUsed == m_iBufferAvail, ssprintf("%i", m_iBufferUsed) ); + + m_iBufferUsed = m_iBufferAvail = 0; + + 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 ); + for( int i = 0; i < m_WavData.m_iChannels; ++i ) + iDelta[i] = FileReading::read_16_le( m_File ); + for( int i = 0; i < m_WavData.m_iChannels; ++i ) + iSamp1[i] = FileReading::read_16_le( m_File ); + for( int i = 0; i < m_WavData.m_iChannels; ++i ) + iSamp2[i] = FileReading::read_16_le( m_File ); + + if( m_File.Tell() >= m_WavData.m_iDataChunkSize+m_WavData.m_iDataChunkPos || m_File.AtEOF() ) + return; /* past the data chunk */ + + CString sError; + + int16_t *pBuffer = (int16_t *) m_pBuffer; + int iCoef1[2], iCoef2[2]; + for( int i = 0; i < m_WavData.m_iChannels; ++i ) + { + if( iPredictor[i] >= (int) m_iaCoef1.size() ) + throw FileReading::FatalError( "Predictor out of range" ); + + 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 = 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 ); + ASSERT( pBuf != NULL ); + + int iBlockSize = m_File.Read( pBuf, iMaxSize ); + if( iBlockSize == 0 ) + return; + + for( int i = 0; i < m_WavData.m_iChannels; ++i ) + pBuffer[m_iBufferAvail++] = iSamp2[i]; + for( int i = 0; i < m_WavData.m_iChannels; ++i ) + pBuffer[m_iBufferAvail++] = iSamp1[i]; + + int8_t iBuf = 0, iBufSize = 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. */ + int iErrorDelta = iBuf >> 4; + iBuf <<= 4; + --iBufSize; + + int32_t iPredSample = (iSamp1[c] * iCoef1[c] + iSamp2[c] * iCoef2[c]) / (1<<8); + int32_t iNewSample = iPredSample + (iDelta[c] * iErrorDelta); + iNewSample = clamp( iNewSample, -32768, 32767 ); + + pBuffer[m_iBufferAvail++] = (int16_t) iNewSample; + + static const int aAdaptionTable[] = { + 768, 614, 512, 409, 307, 230, 230, 230, + 230, 230, 230, 230, 307, 409, 512, 614 + }; + iDelta[c] = int16_t( (iDelta[c] * aAdaptionTable[iErrorDelta+8]) / (1<<8) ); + iDelta[c] = max( (int16_t) 16, iDelta[c] ); + + iSamp2[c] = iSamp1[c]; + iSamp1[c] = (int16_t) iNewSample; + } + } + + m_iBufferAvail *= sizeof(int16_t); } - else if (fmt.wBitsPerSample == 16) + + int Read( char *buf, unsigned len ) { - Conversion = CONV_16LSB_TO_16SYS; - BytesPerSample = 2; + unsigned got = 0; + while( got < len ) + { + if( m_iBufferUsed == m_iBufferAvail ) + { + try { + DecodeADPCMBlock(); + } catch( const FileReading::FatalError &err ) { + m_sError = err.what(); + return -1; + } + } + if( m_iBufferAvail == 0 ) + break; /* EOF */ + + int iBytesToCopy = min( m_iBufferAvail-m_iBufferUsed, (int) (len-got) ); + memcpy( buf+got, m_pBuffer+m_iBufferUsed, iBytesToCopy ); + m_iBufferUsed += iBytesToCopy; + got += iBytesToCopy; + } + + return got; } - else - { - SetError( ssprintf("Unsupported sample size %i", fmt.wBitsPerSample) ); - return OPEN_FATAL_ERROR; - } - if( Conversion == CONV_8BIT_TO_16BIT ) - Input_Buffer_Ratio *= 2; - if( Channels == 1 ) - Input_Buffer_Ratio *= 2; + 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; - this->rw.Seek( NextChunk ); + const int iBlockHeaderSize = 7 * m_WavData.m_iChannels; + if( iExtraBytes > iBlockHeaderSize ) + { + const int iExtraADPCMNibbles = max( 0, iExtraBytes-iBlockHeaderSize )*2; + const int iExtraADPCMFrames = iExtraADPCMNibbles/m_WavData.m_iChannels; + + iFrames += 2+iExtraADPCMFrames; + } - int32_t DataSize; - BAIL_IF_MACRO(!find_chunk(dataID, DataSize), "No data chunk.", OPEN_FATAL_ERROR); + int iMS = int((int64_t(iFrames)*1000)/m_WavData.m_iSampleRate); + return iMS; + } - fmt.data_starting_offset = this->rw.Tell(); - fmt.adpcm_sample_frame_size = BytesPerSample * Channels; + int SetPosition( int iMS ) + { + const int iFrame = int((int64_t(iMS) * m_WavData.m_iSampleRate) / 1000); + const int iBlock = iFrame / m_iFramesPerBlock; - return OPEN_OK; + 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 ); + } + + try { + DecodeADPCMBlock(); + } catch( const FileReading::FatalError &err ) { + m_sError = err.what(); + return -1; + } + + const int iRemainingFrames = iFrame - iBlock*m_iFramesPerBlock; + m_iBufferUsed = iRemainingFrames * m_WavData.m_iChannels * sizeof(int16_t); + if( m_iBufferUsed > m_iBufferAvail ) + { + SetEOF(); + return 0; + } + + return iMS; + } +}; +struct NotWAV: public RageException { NotWAV(): RageException("not a WAV") { } }; + +CString ReadString( RageFile &f, int iSize ) +{ + CString sBuf; + char *pBuf = sBuf.GetBuffer( iSize ); + FileReading::ReadBytes( f, pBuf, iSize ); + sBuf.ReleaseBuffer( iSize ); + return sBuf; } +void RageSoundReader_WAV::OpenInternal() +{ + /* RIFF header: */ + if( ReadString( m_File, 4 ) != "RIFF" ) + throw NotWAV(); + FileReading::read_32_le( m_File ); /* file size */ + if( ReadString( m_File, 4 ) != "WAVE" ) + throw NotWAV(); + + int16_t iFormatTag = 0; + + bool bGotFormatChunk = false, bGotDataChunk = false; + while( !bGotFormatChunk || !bGotDataChunk ) + { + CString ChunkID = ReadString( m_File, 4 ); + + int32_t iChunkSize = FileReading::read_32_le( m_File ); + + int iNextChunk = m_File.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_File.GetPath().c_str() ); + + int32_t iBytesPerSec; + iFormatTag = FileReading::read_16_le( m_File ); + m_WavData.m_iChannels = FileReading::read_16_le( m_File ); + m_WavData.m_iSampleRate = FileReading::read_32_le( m_File ); + FileReading::read_32_le( m_File ); /* BytesPerSec */ + m_WavData.m_iBlockAlign = FileReading::read_16_le( m_File ); + m_WavData.m_iBitsPerSample = FileReading::read_16_le( m_File ); + m_WavData.m_iExtraFmtBytes = FileReading::read_16_le( m_File ); + + if( m_WavData.m_iChannels < 1 || m_WavData.m_iChannels > 2 ) + throw FileReading::FatalError( ssprintf( "Unsupported channel count: %i", m_WavData.m_iChannels) ); + + if( m_WavData.m_iSampleRate < 4000 || m_WavData.m_iSampleRate > 100000 ) /* unlikely */ + throw FileReading::FatalError( ssprintf( "Invalid sample rate: %i", m_WavData.m_iSampleRate) ); + + m_WavData.m_iExtraFmtPos = m_File.Tell(); + + bGotFormatChunk = true; + } + + if( ChunkID == "data" ) + { + m_WavData.m_iDataChunkPos = m_File.Tell(); + m_WavData.m_iDataChunkSize = iChunkSize; + + int iFileSize = m_File.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_File.GetPath().c_str(), + iMaxSize, m_WavData.m_iDataChunkSize ); + + m_WavData.m_iDataChunkSize = iMaxSize; + } + + bGotDataChunk = true; + } + m_File.Seek( iNextChunk ); + } + + switch( iFormatTag ) + { + case 1: // PCM + m_pImpl = new WavReaderPCM( m_File, m_WavData ); + break; + case 2: // ADPCM + m_pImpl = new WavReaderADPCM( m_File, m_WavData ); + break; + case 85: // MP3 + /* Return unknown, so other decoders will be tried. MAD can read MP3s embedded in WAVs. */ + throw NotWAV(); + default: + throw FileReading::FatalError( ssprintf( "Unsupported data format %i", iFormatTag) ); + } + + m_pImpl->Init(); +} SoundReader_FileReader::OpenResult RageSoundReader_WAV::Open( CString filename_ ) { - Close(); - Input_Buffer_Ratio = 1; - filename = filename_; - if( !this->rw.Open( filename ) ) + m_sFilename = filename_; + + if( !m_File.Open( m_sFilename ) ) { - SetError( ssprintf("Couldn't open file: %s", this->rw.GetError().c_str()) ); + SetError( ssprintf("wav: opening \"%s\" failed: %s", m_sFilename.c_str(), m_File.GetError().c_str()) ); return OPEN_FATAL_ERROR; } - memset(&fmt, 0, sizeof(fmt)); - - SoundReader_FileReader::OpenResult rc = WAV_open_internal(); - if ( rc != OPEN_OK ) - Close(); - - return rc; -} - - -void RageSoundReader_WAV::Close() -{ - this->rw.Close(); -} - - -int RageSoundReader_WAV::Read(char *buf, unsigned len) -{ - /* Input_Buffer_Ratio is always 2 or 4. Make sure len is always a multiple of - * Input_Buffer_Ratio; handling extra bytes is a pain and useless. */ - ASSERT( (len % Input_Buffer_Ratio) == 0); - - int ActualLen = len / Input_Buffer_Ratio; - int ret = 0; - switch (this->fmt.wFormatTag) - { - case FMT_NORMAL: - ret = read_sample_fmt_normal( buf, ActualLen ); - break; - case FMT_ADPCM: - ret = read_sample_fmt_adpcm( buf, ActualLen ); - break; - default: ASSERT(0); break; + try { + OpenInternal(); + } catch( const NotWAV &err ) { + SetError( err.what() ); + return OPEN_UNKNOWN_FILE_FORMAT; + } catch( const FileReading::FatalError &err ) { + SetError( err.what() ); + return OPEN_FATAL_ERROR; } - if( ret <= 0 ) - return ret; - - if( Conversion == CONV_16LSB_TO_16SYS ) - { - /* Do this in place. */ -#if defined(ENDIAN_BIG) - const int cnt = len / sizeof(int16_t); - int16_t *tbuf = (int16_t *) buf; - for( int i = 0; i < cnt; ++i ) - tbuf[i] = Swap16( tbuf[i] ); -#endif - } - - static int16_t *tmpbuf = NULL; - static unsigned tmpbufsize = 0; - if( len > tmpbufsize ) - { - tmpbufsize = len; - delete [] tmpbuf; - tmpbuf = new int16_t[len]; - } - if( Conversion == CONV_8BIT_TO_16BIT ) - { - for( int s = 0; s < ret; ++s ) - tmpbuf[s] = (int16_t(buf[s])-128) << 8; - memcpy( buf, tmpbuf, ret * sizeof(int16_t) ); - ret *= 2; /* 8-bit to 16-bit */ - } - - if( Channels == 1 ) - { - int16_t *in = (int16_t*) buf; - for( int s = 0; s < ret/2; ++s ) - tmpbuf[s*2] = tmpbuf[s*2+1] = in[s]; - memcpy( buf, tmpbuf, ret * sizeof(int16_t) ); - ret *= 2; /* 1 channel -> 2 channels */ - } - - return ret; -} - - -int RageSoundReader_WAV::SetPosition(int ms) -{ - switch (this->fmt.wFormatTag) - { - case FMT_NORMAL: - return seek_sample_fmt_normal( ms ); - case FMT_ADPCM: - return seek_sample_fmt_adpcm( ms ); - } - ASSERT(0); - return -1; + return OPEN_OK; } int RageSoundReader_WAV::GetLength() const { - const int origpos = this->rw.Tell(); - - int ret = 0; - switch (this->fmt.wFormatTag) - { - case FMT_NORMAL: - ret = get_length_fmt_normal(); - break; - case FMT_ADPCM: - ret = get_length_fmt_adpcm(); - break; - } + ASSERT( m_pImpl != NULL ); + return m_pImpl->GetLength(); +} - int rc = this->rw.Seek( origpos ); - BAIL_IF_MACRO( rc == -1, this->rw.GetError(), -1 ); +int RageSoundReader_WAV::SetPosition( int ms ) +{ + ASSERT( m_pImpl != NULL ); + return m_pImpl->SetPosition( ms ); +} - return ret; +int RageSoundReader_WAV::Read( char *buf, unsigned len ) +{ + ASSERT( m_pImpl != NULL ); + return m_pImpl->Read( buf, len ); } RageSoundReader_WAV::RageSoundReader_WAV() { + m_pImpl = NULL; +} + +RageSoundReader_WAV::~RageSoundReader_WAV() +{ + delete m_pImpl; } SoundReader *RageSoundReader_WAV::Copy() const { RageSoundReader_WAV *ret = new RageSoundReader_WAV; - ret->Open( filename ); + ret->Open( m_sFilename ); return ret; } -RageSoundReader_WAV::~RageSoundReader_WAV() -{ - Close(); -} - /* - * Copyright (C) 2001 Ryan C. Gordon (icculus@clutteredmind.org) - * Copyright (C) 2003-2004 Glenn Maynard - * - * 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; either - * version 2.1 of the License, or (at your option) any later version. - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * (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. */ - diff --git a/stepmania/src/RageSoundReader_WAV.h b/stepmania/src/RageSoundReader_WAV.h index 357da32495..3ff950efbe 100644 --- a/stepmania/src/RageSoundReader_WAV.h +++ b/stepmania/src/RageSoundReader_WAV.h @@ -1,122 +1,69 @@ -/* - * RageSoundReader_WAV - WAV reader - */ +/* RageSoundReader_WAV - WAV reader. */ + #ifndef RAGE_SOUND_READER_WAV_H #define RAGE_SOUND_READER_WAV_H #include "RageSoundReader_FileReader.h" #include "RageFile.h" +struct WavReader; class RageSoundReader_WAV: public SoundReader_FileReader { - mutable RageFile rw; - struct - { - int16_t wFormatTag; - uint16_t wChannels; - uint32_t dwAvgBytesPerSec; - uint16_t wBlockAlign, wBitsPerSample; - - uint32_t adpcm_sample_frame_size; - uint32_t data_starting_offset; - } fmt; - - struct ADPCMBLOCKHEADER { - uint8_t bPredictor; - uint16_t iDelta; - int16_t iSamp[2]; - }; - struct adpcm_t - { - uint16_t cbSize; - uint16_t wSamplesPerBlock; - vector Coef1, Coef2; - - ADPCMBLOCKHEADER blockheaders[2]; /* 2 channels */ - uint32_t samples_left_in_block; - int nibble_state; - int8_t nibble; - - adpcm_t(); - }; - adpcm_t adpcm; - CString filename; - - enum DataType_t { FORMAT_PCM=0, FORMAT_ADPCM=1 } DataType; - - int SampleRate; - int Channels; - int BytesPerSample; - - /* Number of bytes to read to get one output buffer byte. If converting from 8- - * to 16-bit, *2; if 1- to 2-channel, another *2. */ - int Input_Buffer_Ratio; - - enum { CONV_NONE, CONV_8BIT_TO_16BIT, CONV_16LSB_TO_16SYS } Conversion; - - int read_sample_fmt_normal( char *buf, unsigned len ); - bool read_le16( RageFile &f, int16_t *si16 ) const; - bool read_le16( RageFile &f, uint16_t *ui16 ) const; - bool read_le32( RageFile &f, int32_t *si32 ) const; - bool read_le32( RageFile &f, uint32_t *ui32 ) const; - bool read_uint8( RageFile &f, uint8_t *ui8 ) const; - - bool read_adpcm_block_headers( adpcm_t &out ) const; - bool decode_adpcm_sample_frame(); - uint32_t read_sample_fmt_adpcm( char *buf, unsigned len ); - void do_adpcm_nibble( uint8_t nib, ADPCMBLOCKHEADER *header, int32_t lPredSamp ); - void put_adpcm_sample_frame( uint16_t *buf, int frame ); - - int seek_sample_fmt_adpcm( uint32_t ms ); - int get_length_fmt_adpcm() const; - int find_chunk( uint32_t id, int32_t &size ); - bool read_fmt_chunk(); - - int seek_sample_fmt_normal( uint32_t ms ); - int get_length_fmt_normal() const; - - OpenResult WAV_open_internal(); - - int SetPosition(int ms); - - bool FindChunk( int32_t ID, int32_t &Length ); - - uint32_t ConvertMsToBytePos(int BytesPerSample, int channels, uint32_t ms) const; - uint32_t ConvertBytePosToMs(int BytesPerSample, int channels, uint32_t pos) const; - public: - OpenResult Open(CString filename); + OpenResult Open( CString m_sFilename ); void Close(); int GetLength() const; int GetLength_Fast() const { return GetLength(); } - int SetPosition_Accurate(int ms) { return SetPosition(ms); } - int SetPosition_Fast(int ms) { return SetPosition(ms); } - int Read(char *buf, unsigned len); - int GetSampleRate() const { return SampleRate; } + int SetPosition_Accurate( int ms ) { return SetPosition(ms); } + int SetPosition_Fast( int ms ) { return SetPosition(ms); } + int Read( char *buf, unsigned len ); + int GetSampleRate() const { return m_WavData.m_iSampleRate; } + unsigned GetNumChannels() const { return m_WavData.m_iChannels; } RageSoundReader_WAV(); ~RageSoundReader_WAV(); RageSoundReader_WAV( const RageSoundReader_WAV & ); /* not defined; don't use */ SoundReader *Copy() const; + + struct WavData + { + int32_t m_iDataChunkPos, m_iDataChunkSize, m_iExtraFmtPos, m_iSampleRate; + int16_t m_iChannels, m_iBitsPerSample, m_iBlockAlign, m_iExtraFmtBytes; + }; + +private: + RageFile m_File; + CString m_sFilename; + WavData m_WavData; + + WavReader *m_pImpl; + + void OpenInternal(); + int SetPosition( int ms ); }; #endif /* - * Copyright (C) 2001 Ryan C. Gordon (icculus@clutteredmind.org) - * Copyright (C) 2003-2004 Glenn Maynard - * - * 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; either - * version 2.1 of the License, or (at your option) any later version. - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * (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. */ -