Files
itgmania212121/src/RageSoundReader_SpeedChange.cpp
T
Ben "root" Anderson fdb53ccbbf Unused header
2014-01-11 15:46:53 -06:00

351 lines
11 KiB
C++

#include "global.h"
#include "RageSoundReader_SpeedChange.h"
#include "RageUtil.h"
#include "RageLog.h"
static const int WINDOW_SIZE_MS = 30;
RageSoundReader_SpeedChange::RageSoundReader_SpeedChange( RageSoundReader *pSource ):
RageSoundReader_Filter( pSource )
{
m_Channels.resize( pSource->GetNumChannels() );
m_fSpeedRatio = m_fTrailingSpeedRatio = 1.0f;
m_iDataBufferAvailFrames = 0;
SetSpeedRatio( 1.0f );
Reset();
}
void RageSoundReader_SpeedChange::SetSpeedRatio( float fRatio )
{
m_fSpeedRatio = fRatio;
/* If we haven't read any data yet, put the new delta into effect immediately. */
if( m_iDataBufferAvailFrames == 0 )
m_fTrailingSpeedRatio = m_fSpeedRatio;
}
int RageSoundReader_SpeedChange::GetWindowSizeFrames() const
{
return (WINDOW_SIZE_MS * GetSampleRate()) / 1000;
}
void RageSoundReader_SpeedChange::Reset()
{
m_fTrailingSpeedRatio = m_fSpeedRatio;
m_iDataBufferAvailFrames = 0;
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ChannelInfo &c = m_Channels[i];
c.m_iCorrelatedPos = 0;
c.m_iLastCorrelatedPos = 0;
}
m_iUncorrelatedPos = 0;
m_iPos = 0;
m_fErrorFrames = 0;
}
static int FindClosestMatch( const float *pBuffer, int iBufferSize, const float *pCorrelateBuffer, int iCorrelateBufferSize, int iStride )
{
if( iBufferSize <= iCorrelateBufferSize )
return 0;
/* Optimization: if the buffers are the same, they always corellate at 0. */
if( pBuffer == pCorrelateBuffer )
return 0;
int iBufferDistanceToSearch = iBufferSize - iCorrelateBufferSize;
int iBestOffset = 0;
float fBestScore = 0;
for( int i = 0; i < iBufferDistanceToSearch; i += iStride )
{
float fScore = 0;
const float *pFrames = pBuffer + i;
for( int j = 0; j < iCorrelateBufferSize; j += iStride )
{
float fDiff = pFrames[j] - pCorrelateBuffer[j];
fScore += fabsf(fDiff);
}
if( i == 0 || fScore < fBestScore )
{
fBestScore = fScore;
iBestOffset = i;
}
}
return iBestOffset;
}
int RageSoundReader_SpeedChange::FillData( int iMaxFrames )
{
/* XXX: If the rate or source frame offset changes in the source, we should stop
* at that point, so the changes propagate upward. That's tricky, since we want
* to process a block at a time. We could just flush what we have, and maybe
* tweak m_iDeltaFrames for the next block based on how much we processed, so
* we average out the short block. */
while( iMaxFrames > 0 )
{
int iFramesToRead = iMaxFrames - m_iDataBufferAvailFrames;
int iBytesToRead = iFramesToRead * m_Channels.size() * sizeof(float);
if( iBytesToRead <= 0 )
return m_iDataBufferAvailFrames;
float *pTempBuffer = (float *) alloca( iBytesToRead );
int iGotFrames = m_pSource->Read( pTempBuffer, iFramesToRead );
if( iGotFrames < 0 )
{
if( iGotFrames == END_OF_FILE && m_iDataBufferAvailFrames )
return m_iDataBufferAvailFrames;
return iGotFrames;
}
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ChannelInfo &c = m_Channels[i];
if( (int) c.m_DataBuffer.size() < iMaxFrames )
c.m_DataBuffer.resize( iMaxFrames );
const float *pIn = pTempBuffer + i;
float *pOut = &c.m_DataBuffer[m_iDataBufferAvailFrames];
for( int j = 0; j < iGotFrames; ++j )
{
*pOut = *pIn;
pIn += m_Channels.size();
++pOut;
}
}
m_iDataBufferAvailFrames += iGotFrames;
}
return m_iDataBufferAvailFrames;
}
void RageSoundReader_SpeedChange::EraseData( int iFramesToDelete )
{
ASSERT( iFramesToDelete <= m_iDataBufferAvailFrames );
ASSERT( m_iDataBufferAvailFrames >= iFramesToDelete );
ASSERT( m_iUncorrelatedPos >= iFramesToDelete );
int iFramesToMove = m_iDataBufferAvailFrames - iFramesToDelete;
m_iDataBufferAvailFrames -= iFramesToDelete;
m_iUncorrelatedPos -= iFramesToDelete;
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ChannelInfo &c = m_Channels[i];
if( iFramesToMove )
memmove( &c.m_DataBuffer[0], &c.m_DataBuffer[iFramesToDelete], iFramesToMove * sizeof(float) );
ASSERT( c.m_iCorrelatedPos >= iFramesToDelete );
c.m_iCorrelatedPos -= iFramesToDelete;
}
}
int RageSoundReader_SpeedChange::Step()
{
if( m_iDataBufferAvailFrames == 0 )
return FillData( GetWindowSizeFrames() );
/* If m_iPos is non-zero, we just finished playing a previous block, so advance forward. */
if( m_iPos )
{
/* Advance m_iCorrelatedPos past the data that was just copied, to point to the
* sound that we would have played if we had continued copying at that point. */
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ASSERT( m_Channels[i].m_iCorrelatedPos + m_iPos <= m_iDataBufferAvailFrames );
m_Channels[i].m_iCorrelatedPos += m_iPos;
}
/* Advance m_iUncorrelatedPos to the position we'd prefer to continue playing from.
* This rounds the position, making the ratio imprecise. Record the amount of
* error introduced here. If we want to advance by 2.3 frames, we'll advance
* by 2.0 frames, and advance by 0.3 more the next time around. */
float fAdvanceFrames = GetWindowSizeFrames() * m_fTrailingSpeedRatio;
fAdvanceFrames += m_fErrorFrames;
int iTrailingDeltaFrames = lrintf( fAdvanceFrames );
m_fErrorFrames = fAdvanceFrames - iTrailingDeltaFrames;
m_iUncorrelatedPos += iTrailingDeltaFrames;
m_iPos = 0;
}
/* Update m_fTrailingSpeedRatio. Do this after advancing m_iUncorrelatedPos,
* so changes to m_iUncorrelatedPos line up with GetNextSourceFrame. */
m_fTrailingSpeedRatio = m_fSpeedRatio;
/* We don't need any data before the earlier of m_iUncorrelatedPos or m_iCorrelatedPos. */
int iToDelete = m_iUncorrelatedPos;
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ChannelInfo &c = m_Channels[i];
ASSERT( c.m_iCorrelatedPos <= m_iDataBufferAvailFrames );
iToDelete = min( iToDelete, c.m_iCorrelatedPos );
//iToDelete = min( iToDelete, m_iDataBufferAvailFrames );
}
EraseData( iToDelete );
/* Fill as much data as we might need to do the search and use the result. */
{
int iMaxPositionNeeded = m_iUncorrelatedPos + GetToleranceFrames() + GetWindowSizeFrames();
for( size_t i = 0; i < m_Channels.size(); ++i )
iMaxPositionNeeded = max( iMaxPositionNeeded, m_Channels[i].m_iCorrelatedPos + GetWindowSizeFrames() );
int iGot = FillData( iMaxPositionNeeded );
if( iGot < 0 )
return iGot;
if( iMaxPositionNeeded > m_iDataBufferAvailFrames )
{
/* We're at EOF. Flush the remaining data, if any. */
m_iUncorrelatedPos = m_Channels[0].m_iCorrelatedPos;
return m_iDataBufferAvailFrames;
}
}
/* Starting at our preferred position (m_iUncorrelatedPos), within GetToleranceFrames(),
* search for data that looks like the sound we would have continued playing if we
* kept going from the old position (m_iCorrelatedPos). */
int iCorrelatedToMatch = GetWindowSizeFrames()/4;
int iUncorrelatedToMatch = GetToleranceFrames() + iCorrelatedToMatch; // maximum distance to search
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ChannelInfo &c = m_Channels[i];
ASSERT( c.m_iCorrelatedPos >= 0 );
ASSERT( c.m_iCorrelatedPos < m_iDataBufferAvailFrames );
int iBest = FindClosestMatch( &c.m_DataBuffer[m_iUncorrelatedPos], iUncorrelatedToMatch, &c.m_DataBuffer[c.m_iCorrelatedPos], iCorrelatedToMatch, m_Channels.size() );
c.m_iLastCorrelatedPos = c.m_iCorrelatedPos;
c.m_iCorrelatedPos = iBest + m_iUncorrelatedPos;
ASSERT( m_Channels[i].m_iCorrelatedPos + GetWindowSizeFrames() <= m_iDataBufferAvailFrames );
}
return m_iDataBufferAvailFrames;
}
int RageSoundReader_SpeedChange::GetCursorAvail() const
{
int iCursorAvail = GetWindowSizeFrames() - m_iPos;
for( size_t i = 0; i < m_Channels.size(); ++i )
{
int iCursorAvailForChannel = (m_iDataBufferAvailFrames-m_Channels[i].m_iCorrelatedPos) - m_iPos;
iCursorAvail = min( iCursorAvail, iCursorAvailForChannel );
}
return iCursorAvail;
}
int RageSoundReader_SpeedChange::Read( float *pBuf, int iFrames )
{
while( 1 )
{
if( m_iDataBufferAvailFrames == 0 && m_fTrailingSpeedRatio == m_fSpeedRatio && m_fSpeedRatio == 1.0f )
{
/* Fast path: the buffer is empty, and we're not scaling the audio. Read directly
* into the output buffer, to eliminate memory and copying overhead. */
return m_pSource->Read( pBuf, iFrames );
}
int iCursorAvail = GetCursorAvail();
if( iCursorAvail == 0 )
{
int iRet = Step();
if( iRet < 0 )
return iRet;
if( !GetCursorAvail() )
return END_OF_FILE;
continue;
}
/* copy GetWindowSizeFrames() from iCorrelatedPos */
int iFramesLen = iFrames;
int iFramesAvail = min( iCursorAvail, iFramesLen );
iFrames -= iFramesAvail;
int iFramesRead = iFramesAvail;
int iWindowSizeFrames = GetWindowSizeFrames();
while( iFramesAvail-- )
{
for( size_t i = 0; i < m_Channels.size(); ++i )
{
ChannelInfo &c = m_Channels[i];
float i1 = c.m_DataBuffer[c.m_iCorrelatedPos+m_iPos];
float i2 = c.m_DataBuffer[c.m_iLastCorrelatedPos+m_iPos];
*pBuf++ = SCALE( m_iPos, 0, iWindowSizeFrames, i2, i1 );
}
++m_iPos;
}
return iFramesRead;
}
}
/* We prefer to be able to seek precisely, so seeking to a position produces data
* equal to what you'd get if you read data up to that point. This filter can't do
* that, because the exact selection of slices is dependent on the previous selection. */
int RageSoundReader_SpeedChange::SetPosition( int iFrame )
{
Reset();
return RageSoundReader_Filter::SetPosition( iFrame );
}
bool RageSoundReader_SpeedChange::SetProperty( const RString &sProperty, float fValue )
{
if( sProperty == "Speed" )
{
SetSpeedRatio( fValue );
return true;
}
return RageSoundReader_Filter::SetProperty( sProperty, fValue );
}
int RageSoundReader_SpeedChange::GetNextSourceFrame() const
{
float fRatio = m_fTrailingSpeedRatio;
int iSourceFrame = RageSoundReader_Filter::GetNextSourceFrame();
int iPos = lrintf(m_iPos * fRatio);
iSourceFrame -= m_iDataBufferAvailFrames;
iSourceFrame += m_iUncorrelatedPos + iPos;
return iSourceFrame;
}
float RageSoundReader_SpeedChange::GetStreamToSourceRatio() const
{
/* If the ratio has changed and GetCursorAvail() is 0, the new ratio will
* become the real ratio on the next read. Otherwise, we'll continue using
* our old ratio for a bit longer. */
if( GetCursorAvail() == 0 )
return m_fSpeedRatio * RageSoundReader_Filter::GetStreamToSourceRatio();
else
return m_fTrailingSpeedRatio * RageSoundReader_Filter::GetStreamToSourceRatio();
}
/*
* 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.
*/