/* * Output a sound from the channels of another sound. * * This is intended for splitting a FileReader, and assumes the GetStreamToSourceRatio * of the source is 1.0. * * If multiple readers are created, they should be read in parallel. If their source * position drifts apart, the data between will be buffered to prevent seeking the source. * * The resulting sounds have as many channels as the largest destination channel * specified; multiple sounds on the same channel are mixed together; empty * channels are silent. * * Create two mono sounds from one stereo sound, one playing the left channel and * one playing the right: * RageSoundSplitter split(pSource); * RageSoundReader_Split *pLeft = split.CreateSound(); * pLeft->AddSourceChannelToSound( 0, 0 ); * RageSoundReader_Split *pRight = split.CreateSound(); * pRight->AddSourceChannelToSound( 1, 0 ); * * Convert a stereo sound to mono: * RageSoundSplitter split(pSource); * RageSoundReader_Split *pMono = split.CreateSound(); * pMono->AddSourceChannelToSound( 0, 0 ); * pMono->AddSourceChannelToSound( 1, 0 ); * * Convert a mono sound to stereo (RageSoundReader_Pan will do this faster): * RageSoundSplitter split(pSource); * RageSoundReader_Split *pStereo = split.CreateSound(); * pStereo->AddSourceChannelToSound( 0, 0 ); * pStereo->AddSourceChannelToSound( 0, 1 ); */ #include "global.h" #include "RageSoundReader_ChannelSplit.h" #include "RageLog.h" #include "RageUtil.h" #include "RageSoundMixBuffer.h" #include "RageSoundUtil.h" #include "Foreach.h" #include class RageSoundReader_Split; /* This class is refcounted, freed when all RageSoundReader_Split and RageSoundSplitter * classes release it. */ class RageSoundSplitterImpl { public: RageSoundSplitterImpl( RageSoundReader *pSource ) { m_iRefCount = 1; m_pSource = pSource; m_iBufferPositionFrames = 0; } ~RageSoundSplitterImpl() { delete m_pSource; } static void Release( RageSoundSplitterImpl *pImpl ) { --pImpl->m_iRefCount; if( pImpl->m_iRefCount == 0 ) delete pImpl; } /* Request that m_sBuffer contain frames [iStartFrame,iStartFrame+iFrames). */ int ReadBuffer(); int m_iRefCount; RageSoundReader *m_pSource; set m_apSounds; /* m_sBuffer[0] corresponds to frame number m_iBufferPositionFrames. */ int m_iBufferPositionFrames; vector m_sBuffer; }; int RageSoundReader_Split::GetLength() const { return m_pImpl->m_pSource->GetLength(); } int RageSoundReader_Split::GetLength_Fast() const { return m_pImpl->m_pSource->GetLength_Fast(); } int RageSoundReader_Split::GetSampleRate() const { return m_pImpl->m_pSource->GetSampleRate(); } unsigned RageSoundReader_Split::GetNumChannels() const { return m_iNumOutputChannels; } int RageSoundReader_Split::GetNextSourceFrame() const { return m_iPositionFrame; } float RageSoundReader_Split::GetStreamToSourceRatio() const { return 1.0f; } RString RageSoundReader_Split::GetError() const { return m_pImpl->m_pSource->GetError(); } RageSoundReader_Split::RageSoundReader_Split( RageSoundSplitterImpl *pImpl ) { m_pImpl = pImpl; ++m_pImpl->m_iRefCount; m_pImpl->m_apSounds.insert( this ); m_iNumOutputChannels = 0; m_iPositionFrame = 0; m_iRequestFrames = 0; } RageSoundReader_Split::RageSoundReader_Split( const RageSoundReader_Split &cpy ): RageSoundReader( cpy ) { m_pImpl = cpy.m_pImpl; ++m_pImpl->m_iRefCount; m_pImpl->m_apSounds.insert( this ); m_aChannels = cpy.m_aChannels; m_iNumOutputChannels = cpy.m_iNumOutputChannels; m_iPositionFrame = cpy.m_iPositionFrame; m_iRequestFrames = cpy.m_iRequestFrames; } RageSoundReader_Split::~RageSoundReader_Split() { m_pImpl->m_apSounds.erase( this ); RageSoundSplitterImpl::Release( m_pImpl ); } int RageSoundReader_Split::SetPosition( int iFrame ) { m_iPositionFrame = iFrame; /* We can't tell whether we're past EOF. We can't ReadBuffer here, because the * other sounds using this same source probably haven't seeked yet, so seeking * to the beginning of the file would buffer between there and the position * of those sounds. Just return success, and we'll return EOF in Read if needed. */ return 1; } bool RageSoundReader_Split::SetProperty( const RString &sProperty, float fValue ) { return m_pImpl->m_pSource->SetProperty( sProperty, fValue ); } 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 RageSoundSplitterImpl::ReadBuffer() { /* Discard any bytes that are no longer requested by any sound. */ int iMinFrameRequested = INT_MAX; int iMaxFrameRequested = INT_MIN; FOREACHS( RageSoundReader_Split *, m_apSounds, snd ) { iMinFrameRequested = min( iMinFrameRequested, (*snd)->m_iPositionFrame ); iMaxFrameRequested = max( iMaxFrameRequested, (*snd)->m_iPositionFrame + (*snd)->m_iRequestFrames ); } if( iMinFrameRequested > m_iBufferPositionFrames ) { int iEraseFrames = iMinFrameRequested - m_iBufferPositionFrames; iEraseFrames = min( iEraseFrames, (int) m_sBuffer.size() ); m_sBuffer.erase( m_sBuffer.begin(), m_sBuffer.begin() + iEraseFrames * m_pSource->GetNumChannels() ); m_iBufferPositionFrames += iEraseFrames; } if( iMinFrameRequested != m_iBufferPositionFrames ) { m_pSource->SetPosition( iMinFrameRequested ); m_iBufferPositionFrames = iMinFrameRequested; m_sBuffer.clear(); } int iFramesBuffered = m_sBuffer.size() / m_pSource->GetNumChannels(); int iFramesToRead = iMaxFrameRequested - (m_iBufferPositionFrames + iFramesBuffered); if( iFramesToRead <= 0 ) return 1; // requested data already buffered int iSamplesToRead = iFramesToRead * m_pSource->GetNumChannels(); int iOldSizeSamples = m_sBuffer.size(); m_sBuffer.resize( iOldSizeSamples + iSamplesToRead ); int iGotFrames = m_pSource->Read( &m_sBuffer[0] + iOldSizeSamples, iFramesToRead ); if( iGotFrames < 0 ) { m_sBuffer.resize( iOldSizeSamples ); return iGotFrames; } int iGotSamples = iGotFrames * m_pSource->GetNumChannels(); m_sBuffer.resize( iOldSizeSamples + iGotSamples ); return 1; } void RageSoundReader_Split::AddSourceChannelToSound( int iFromChannel, int iToChannel ) { m_aChannels.push_back( ChannelMap(iFromChannel, iToChannel) ); m_iNumOutputChannels = max( m_iNumOutputChannels, iToChannel + 1 ); } RageSoundSplitter::RageSoundSplitter( RageSoundReader *pSource ) { m_pImpl = new RageSoundSplitterImpl( pSource ); } RageSoundSplitter::~RageSoundSplitter() { RageSoundSplitterImpl::Release( m_pImpl ); } RageSoundReader_Split *RageSoundSplitter::CreateSound() { return new RageSoundReader_Split( m_pImpl ); } /* * Copyright (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. */