2011-03-17 01:47:30 -04:00
#include "global.h"
#include "RageSoundPosMap.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "RageTimer.h"
2023-04-19 14:22:59 +02:00
#include <climits>
#include <cmath>
2023-04-20 12:34:12 +02:00
#include <cstdint>
2011-03-17 01:47:30 -04:00
#include <list>
2024-07-09 22:36:12 -07:00
// The number of frames we should keep pos_map data for.
// This comes out to about ~800kb in audio frames, assuming 44.1khz.
// File bitrate, metadata, etc will factor in here. If the queue is
// TOO big it will make things slow, but 200k frames is no problem.
// Making the queue larger than 200k hasn't been tested extensively.
2024-04-24 02:31:03 -07:00
const int pos_map_backlog_frames = 200000 ;
2011-03-17 01:47:30 -04:00
struct pos_map_t
{
2023-04-20 12:34:12 +02:00
std :: int64_t m_iSourceFrame ;
std :: int64_t m_iDestFrame ;
2024-04-24 02:31:03 -07:00
std :: int64_t m_iFrames ;
double m_fSourceToDestRatio ;
2011-03-17 01:47:30 -04:00
2024-04-24 02:31:03 -07:00
pos_map_t () { m_iSourceFrame = 0 ; m_iDestFrame = 0 ; m_iFrames = 0 ; m_fSourceToDestRatio = 1.0 ; }
2011-03-17 01:47:30 -04:00
};
struct pos_map_impl
{
2022-07-10 18:28:56 +03:00
std :: list < pos_map_t > m_Queue ;
2011-03-17 01:47:30 -04:00
void Cleanup ();
};
pos_map_queue :: pos_map_queue ()
{
m_pImpl = new pos_map_impl ;
}
pos_map_queue ::~ pos_map_queue ()
{
delete m_pImpl ;
}
pos_map_queue :: pos_map_queue ( const pos_map_queue & cpy )
{
* this = cpy ;
m_pImpl = new pos_map_impl ( * cpy . m_pImpl );
}
pos_map_queue & pos_map_queue :: operator = ( const pos_map_queue & rhs )
{
2024-07-09 22:36:12 -07:00
if ( this != & rhs )
{
pos_map_impl * tempImpl = new pos_map_impl ( * rhs . m_pImpl );
std :: swap ( m_pImpl , tempImpl );
delete tempImpl ;
2014-07-03 09:33:25 -04:00
}
2011-03-17 01:47:30 -04:00
return * this ;
}
2024-04-24 02:31:03 -07:00
void pos_map_queue :: Insert ( std :: int64_t iSourceFrame , std :: int64_t iFrames , std :: int64_t iDestFrame , double fSourceToDestRatio )
2011-03-17 01:47:30 -04:00
{
2024-04-24 02:31:03 -07:00
bool merged = false ;
if ( ! m_pImpl -> m_Queue . empty ())
2011-03-17 01:47:30 -04:00
{
2024-04-24 02:31:03 -07:00
// Check if the last entry can be merged with the new entry
pos_map_t & last = m_pImpl -> m_Queue . back ();
if ( last . m_iSourceFrame + last . m_iFrames == iSourceFrame &&
last . m_fSourceToDestRatio == fSourceToDestRatio &&
// llabs() is used instead of abs() because abs() would be susceptible to an integer overflow.
llabs ( last . m_iDestFrame + static_cast < int64_t > (( last . m_iFrames * last . m_fSourceToDestRatio ) + 0.5 ) - iDestFrame ) <= 1 )
2011-03-17 01:47:30 -04:00
{
2024-04-24 02:31:03 -07:00
// Merge the frames and set the merged flag to true.
2011-03-17 01:47:30 -04:00
last . m_iFrames += iFrames ;
2024-04-24 02:31:03 -07:00
merged = true ;
2011-03-17 01:47:30 -04:00
}
}
2024-04-24 02:31:03 -07:00
if ( ! merged )
{
m_pImpl -> m_Queue . push_back ( pos_map_t ());
pos_map_t & m = m_pImpl -> m_Queue . back ();
m . m_iSourceFrame = iSourceFrame ;
m . m_iDestFrame = iDestFrame ;
m . m_iFrames = iFrames ;
m . m_fSourceToDestRatio = fSourceToDestRatio ;
}
2023-04-19 14:22:59 +02:00
2011-03-17 01:47:30 -04:00
m_pImpl -> Cleanup ();
}
void pos_map_impl :: Cleanup ()
{
2022-07-10 18:28:56 +03:00
std :: list < pos_map_t >:: iterator it = m_Queue . end ();
2024-04-24 02:31:03 -07:00
std :: int64_t iTotalFrames = 0 ;
// Scan backwards until we have at least pos_map_backlog_frames.
while ( iTotalFrames < pos_map_backlog_frames )
2011-03-17 01:47:30 -04:00
{
2024-04-24 02:31:03 -07:00
if ( it == m_Queue . begin ())
2011-03-17 01:47:30 -04:00
break ;
-- it ;
iTotalFrames += it -> m_iFrames ;
}
2024-04-24 02:31:03 -07:00
m_Queue . erase ( m_Queue . begin (), it );
2011-03-17 01:47:30 -04:00
}
2024-09-13 01:58:02 -07:00
std :: int64_t pos_map_queue :: Search ( std :: int64_t iSourceFrame ) const
2011-03-17 01:47:30 -04:00
{
if ( IsEmpty () )
{
return 0 ;
}
2024-04-24 02:31:03 -07:00
// iSourceFrame is probably in pos_map. Search to figure out what position it maps to.
std :: int64_t iClosestPosition = 0 , iClosestPositionDist = std :: numeric_limits < int64_t >:: max ();
2019-06-22 12:35:38 -07:00
for ( pos_map_t const & pm : m_pImpl -> m_Queue )
2011-03-17 01:47:30 -04:00
{
2024-04-24 02:31:03 -07:00
// Loop over the queue until we know generally where iSourceFrame is
2011-03-17 01:47:30 -04:00
if ( iSourceFrame >= pm . m_iSourceFrame &&
iSourceFrame < pm . m_iSourceFrame + pm . m_iFrames )
{
2024-04-24 02:31:03 -07:00
// If we are in the correct block, calculate its current position
std :: int64_t iDiff = static_cast < std :: int64_t > ( iSourceFrame - pm . m_iSourceFrame );
iDiff = static_cast < int64_t > (( iDiff * pm . m_fSourceToDestRatio ) + 0.5 );
2011-03-17 01:47:30 -04:00
return pm . m_iDestFrame + iDiff ;
}
2024-04-24 02:31:03 -07:00
// See if the current position is close to the beginning of this block.
2023-04-20 12:34:12 +02:00
std :: int64_t dist = llabs ( pm . m_iSourceFrame - iSourceFrame );
2011-03-17 01:47:30 -04:00
if ( dist < iClosestPositionDist )
{
iClosestPositionDist = dist ;
iClosestPosition = pm . m_iDestFrame ;
}
2024-04-24 02:31:03 -07:00
// See if the current position is close to the end of this block.
2011-03-17 01:47:30 -04:00
dist = llabs ( pm . m_iSourceFrame + pm . m_iFrames - iSourceFrame );
if ( dist < iClosestPositionDist )
{
iClosestPositionDist = dist ;
2024-04-24 02:31:03 -07:00
iClosestPosition = pm . m_iDestFrame + static_cast < int64_t > (( pm . m_iFrames * pm . m_fSourceToDestRatio ) + 0.5 );
2011-03-17 01:47:30 -04:00
}
}
/*
* The frame is out of the range of data we've actually sent.
* Return the closest position.
*
2023-04-19 14:22:59 +02:00
* There are three cases when this happens:
2011-03-17 01:47:30 -04:00
* 1. Before the first CommitPlayingPosition call.
* 2. After GetDataToPlay returns EOF and the sound has flushed, but before
* SoundStopped has been called.
* 3. Underflow; we'll be given a larger frame number than we know about.
*/
static RageTimer last ;
if ( last . PeekDeltaTime () >= 1.0f )
{
2024-04-24 02:31:03 -07:00
last . Touch ();
LOG -> Trace ( "Audio frame was out of range of the data sent - possible buffer underflow? This is not always an error, however if you see it frequently there could be sound buffer problems." );
2011-03-17 01:47:30 -04:00
}
return iClosestPosition ;
}
void pos_map_queue :: Clear ()
{
m_pImpl -> m_Queue . clear ();
}
bool pos_map_queue :: IsEmpty () const
{
return m_pImpl -> m_Queue . empty ();
}
/*
* Copyright (c) 2002-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.
*/