int RageSoundReader_Split::Read( float *pBuf, int iFrames )
{
	m_iRequestFrames = iFrames;
	int iRet = m_pImpl->ReadBuffer();

	int iSamplesAvailable = m_pImpl->m_sBuffer.size();
	const float *pSrc = &m_pImpl->m_sBuffer[0];
	if( m_pImpl->m_iBufferPositionFrames < m_iPositionFrame )
	{
		int iSkipFrames = m_iPositionFrame - m_pImpl->m_iBufferPositionFrames;
		int iSkipSamples = iSkipFrames * m_pImpl->m_pSource->GetNumChannels();
		pSrc += iSkipSamples;
		iSamplesAvailable -= iSkipSamples;
	}

	int iFramesWanted = iFrames;
	int iFramesAvailable = iSamplesAvailable / (m_pImpl->m_pSource->GetNumChannels());

	/* Report any errors from Read() if we don't have any data buffered to
	 * return.  If we do have data, finish returning it first. */
	if( iFramesAvailable == 0 && iRet < 0 )
		return iRet;

	iFramesAvailable = min( iFramesAvailable, iFramesWanted );

	{
		RageSoundMixBuffer mix;
		for( int i = 0; i < (int) m_aChannels.size(); ++i )
		{
			const ChannelMap &chan = m_aChannels[i];
			mix.SetWriteOffset( chan.m_iToChannel );
			mix.write( pSrc + chan.m_iFromChannel, iFramesAvailable, m_pImpl->m_pSource->GetNumChannels(), m_iNumOutputChannels );
		}

		mix.read( pBuf );
	}

	m_iPositionFrame += iFramesAvailable;

	/* We no longer need the data we requested.  Clear our request, so the
	 * memory can be freed. */
	m_iRequestFrames = 0;
	m_pImpl->ReadBuffer();
	return iFramesAvailable;
}
int RageSoundReader_Chain::ReadBlock( int16_t *pBuffer, int iFrames )
{
	/* How many samples should we read before we need to start up a sound? */
	int iFramesToRead = INT_MAX;
	if( m_iNextSound < m_Sounds.size() )
	{
		int iStartFrame = m_iCurrentFrame;
		int iOffsetFrame = m_Sounds[m_iNextSound].GetOffsetFrame(m_iActualSampleRate);
		ASSERT_M( iOffsetFrame >= iStartFrame, ssprintf("%i %i", iOffsetFrame, iStartFrame) );
		iFramesToRead = iOffsetFrame - iStartFrame;
	}

	iFramesToRead = min( iFramesToRead, iFrames );

	if( iFramesToRead > 0 && m_apActiveSounds.size() == 1 &&
		m_apActiveSounds.front().fPan == 0 &&
		m_apActiveSounds.front().pSound->GetNumChannels() == m_iChannels &&
		m_apActiveSounds.front().pSound->GetSampleRate() == m_iActualSampleRate )
	{
		/* We have only one source, and it matches our target.  Don't mix; read
		 * directly from the source into the destination.  This is to optimize
		 * the common case of having one BGM track and no autoplay sounds. */
		int iBytes = m_apActiveSounds.front().pSound->Read( (char *) pBuffer, iFramesToRead * sizeof(int16_t) * m_iChannels );
		if( iBytes == 0 )
			ReleaseSound( 0 );
		return iBytes / (sizeof(int16_t) * m_iChannels);
	}

	if( iFramesToRead > 0 && !m_apActiveSounds.empty() )
	{
		RageSoundMixBuffer mix;
		/* Read iFramesToRead from each sound. */
		int16_t Buffer[2048];
		iFramesToRead = min( iFramesToRead, 1024 );
		int iMaxFramesRead = 0;
		for( unsigned i = 0; i < m_apActiveSounds.size(); )
		{
			ActiveSound &s = m_apActiveSounds[i];
			SoundReader *pSound = s.pSound;
			int iSamples = min( iFramesToRead * pSound->GetNumChannels(), ARRAYLEN(Buffer) );
			int iBytesRead = pSound->Read( (char *) Buffer, iSamples*sizeof(int16_t) );
			if( iBytesRead == -1 || iBytesRead == 0 )
			{
				/* The sound is at EOF.  Release it. */
				ReleaseSound( i );
				continue;
			}

			int iSamplesRead = iBytesRead / sizeof(int16_t);
			int iFramesRead = iSamplesRead / pSound->GetNumChannels();

			iMaxFramesRead = max( iMaxFramesRead, iFramesRead );

			if( m_iChannels == 2 && pSound->GetNumChannels() == 1 )
			{
				RageSoundUtil::ConvertMonoToStereoInPlace( Buffer, iSamplesRead );
				iSamplesRead *= 2;
			}

			if( fabsf(s.fPan) > 0.0001f )
				RageSoundUtil::Pan( Buffer, iFramesRead, s.fPan );

			mix.write( Buffer, iSamplesRead );
			++i;
		}

		/* Read mixed frames into the output buffer. */
		mix.read( (int16_t *) pBuffer );
		return iMaxFramesRead;
	}

	/* If we have more sounds ahead of us, pretend we read the entire block, since
	 * there's silence in between.  Otherwise, we're at EOF. */
	if( iFramesToRead > 0 )
	{
		memset( pBuffer, 0, iFramesToRead * m_iChannels * sizeof(int16_t) );
		return iFramesToRead;
	}

	return 0;
}