273 lines
9.0 KiB
C++
273 lines
9.0 KiB
C++
/*
|
|
* 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 <set>
|
|
|
|
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<RageSoundReader_Split *> m_apSounds;
|
|
|
|
/* m_sBuffer[0] corresponds to frame number m_iBufferPositionFrames. */
|
|
int m_iBufferPositionFrames;
|
|
vector<float> 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.
|
|
*/
|