#include "global.h" #include "TimingData.h" #include "PrefsManager.h" #include "RageUtil.h" #include "RageLog.h" #include "NoteTypes.h" #include "Foreach.h" #include TimingData::TimingData() { m_fBeat0OffsetInSeconds = 0; } void TimingData::GetActualBPM( float &fMinBPMOut, float &fMaxBPMOut ) const { fMinBPMOut = FLT_MAX; fMaxBPMOut = 0; FOREACH_CONST( BPMSegment, m_BPMSegments, seg ) { const float fBPM = seg->GetBPM(); fMaxBPMOut = max( fBPM, fMaxBPMOut ); fMinBPMOut = min( fBPM, fMinBPMOut ); } } void TimingData::AddBPMSegment( const BPMSegment &seg ) { m_BPMSegments.insert( upper_bound(m_BPMSegments.begin(), m_BPMSegments.end(), seg), seg ); } void TimingData::AddStopSegment( const StopSegment &seg ) { m_StopSegments.insert( upper_bound(m_StopSegments.begin(), m_StopSegments.end(), seg), seg ); } /* Change an existing BPM segment, merge identical segments together or insert a new one. */ void TimingData::SetBPMAtRow( int iNoteRow, float fBPM ) { float fBPS = fBPM / 60.0f; unsigned i; for( i=0; i= iNoteRow ) break; if( i == m_BPMSegments.size() || m_BPMSegments[i].m_iStartIndex != iNoteRow ) { // There is no BPMSegment at the specified beat. If the BPM being set differs // from the last BPMSegment's BPM, create a new BPMSegment. if( i == 0 || fabsf(m_BPMSegments[i-1].m_fBPS - fBPS) > 1e-5f ) AddBPMSegment( BPMSegment(iNoteRow, fBPM) ); } else // BPMSegment being modified is m_BPMSegments[i] { if( i > 0 && fabsf(m_BPMSegments[i-1].m_fBPS - fBPS) < 1e-5f ) m_BPMSegments.erase( m_BPMSegments.begin()+i, m_BPMSegments.begin()+i+1 ); else m_BPMSegments[i].m_fBPS = fBPS; } } void TimingData::SetStopAtRow( int iRow, float fSeconds ) { unsigned i; for( i=0; i 0 ) AddStopSegment( StopSegment(iRow, fSeconds) ); } else // StopSegment being modified is m_StopSegments[i] { if( fSeconds > 0 ) m_StopSegments[i].m_fStopSeconds = fSeconds; else m_StopSegments.erase( m_StopSegments.begin()+i, m_StopSegments.begin()+i+1 ); } } float TimingData::GetStopAtRow( int iNoteRow ) const { for( unsigned i=0; i iStartIndex ) { BPMSegment b = m_BPMSegments[i]; b.m_iStartIndex = iStartIndexNextSegment; m_BPMSegments.insert( m_BPMSegments.begin()+i+1, b ); /* Don't apply the BPM change to the first half of the segment we just split, * since it lies outside the range. */ continue; } /* If this BPM segment crosses the end of the range, split it into two. */ if( iStartIndexThisSegment < iEndIndex && iStartIndexNextSegment > iEndIndex ) { BPMSegment b = m_BPMSegments[i]; b.m_iStartIndex = iEndIndex; m_BPMSegments.insert( m_BPMSegments.begin()+i+1, b ); } else if( iStartIndexNextSegment > iEndIndex ) continue; m_BPMSegments[i].m_fBPS = m_BPMSegments[i].m_fBPS * fFactor; } } float TimingData::GetBPMAtBeat( float fBeat ) const { int iIndex = BeatToNoteRow( fBeat ); unsigned i; for( i=0; i iIndex ) break; return m_BPMSegments[i].GetBPM(); } int TimingData::GetBPMSegmentIndexAtBeat( float fBeat ) { int iIndex = BeatToNoteRow( fBeat ); int i; for( i=0; i<(int)(m_BPMSegments.size())-1; i++ ) if( m_BPMSegments[i+1].m_iStartIndex > iIndex ) break; return i; } BPMSegment& TimingData::GetBPMSegmentAtBeat( float fBeat ) { static BPMSegment empty; if( m_BPMSegments.empty() ) return empty; int i = GetBPMSegmentIndexAtBeat( fBeat ); return m_BPMSegments[i]; } void TimingData::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const { fElapsedTime += PREFSMAN->m_fGlobalOffsetSeconds; GetBeatAndBPSFromElapsedTimeNoOffset( fElapsedTime, fBeatOut, fBPSOut, bFreezeOut ); } void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const { // LOG->Trace( "GetBeatAndBPSFromElapsedTime( fElapsedTime = %f )", fElapsedTime ); fElapsedTime += m_fBeat0OffsetInSeconds; for( unsigned i=0; i= m_StopSegments[j].m_iStartRow ) continue; if( !bIsLastBPMSegment && m_StopSegments[j].m_iStartRow > iStartRowNextSegment ) continue; // this freeze lies within this BPMSegment const int iRowsBeatsSinceStartOfSegment = m_StopSegments[j].m_iStartRow - iStartRowThisSegment; const float fBeatsSinceStartOfSegment = NoteRowToBeat(iRowsBeatsSinceStartOfSegment); const float fFreezeStartSecond = fBeatsSinceStartOfSegment / fBPS; if( fFreezeStartSecond >= fElapsedTime ) break; // the freeze segment is <= current time fElapsedTime -= m_StopSegments[j].m_fStopSeconds; if( fFreezeStartSecond >= fElapsedTime ) { /* The time lies within the stop. */ fBeatOut = NoteRowToBeat(m_StopSegments[j].m_iStartRow); fBPSOut = fBPS; bFreezeOut = true; return; } } const float fBeatsInThisSegment = fStartBeatNextSegment - fStartBeatThisSegment; const float fSecondsInThisSegment = fBeatsInThisSegment / fBPS; if( bIsLastBPMSegment || fElapsedTime <= fSecondsInThisSegment ) { // this BPMSegment IS the current segment fBeatOut = fStartBeatThisSegment + fElapsedTime*fBPS; fBPSOut = fBPS; bFreezeOut = false; return; } // this BPMSegment is NOT the current segment fElapsedTime -= fSecondsInThisSegment; } } float TimingData::GetElapsedTimeFromBeat( float fBeat ) const { return TimingData::GetElapsedTimeFromBeatNoOffset( fBeat ) - PREFSMAN->m_fGlobalOffsetSeconds; } float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const { float fElapsedTime = 0; fElapsedTime -= m_fBeat0OffsetInSeconds; int iRow = BeatToNoteRow(fBeat); for( unsigned j=0; j=, not >. */ if( m_StopSegments[j].m_iStartRow >= iRow ) break; fElapsedTime += m_StopSegments[j].m_fStopSeconds; } for( unsigned i=0; i 0 ); ASSERT( iStartIndex >= 0 ); ASSERT( iStartIndex < iEndIndex ); for ( unsigned i = 0; i < m_BPMSegments.size(); i++ ) { const int iSegStart = m_BPMSegments[i].m_iStartIndex; if( iSegStart < iStartIndex ) continue; else if( iSegStart > iEndIndex ) m_BPMSegments[i].m_iStartIndex += lrintf( (iEndIndex - iStartIndex) * (fScale - 1) ); else m_BPMSegments[i].m_iStartIndex = lrintf( (iSegStart - iStartIndex) * fScale ) + iStartIndex; } for( unsigned i = 0; i < m_StopSegments.size(); i++ ) { const int iSegStartRow = m_StopSegments[i].m_iStartRow; if( iSegStartRow < iStartIndex ) continue; else if( iSegStartRow > iEndIndex ) m_StopSegments[i].m_iStartRow += lrintf((iEndIndex - iStartIndex) * (fScale - 1)); else m_StopSegments[i].m_iStartRow = lrintf((iSegStartRow - iStartIndex) * fScale) + iStartIndex; } } void TimingData::InsertRows( int iStartRow, int iRowsToAdd ) { for( unsigned i = 0; i < m_BPMSegments.size(); i++ ) { BPMSegment &bpm = m_BPMSegments[i]; if( bpm.m_iStartIndex < iStartRow ) continue; bpm.m_iStartIndex += iRowsToAdd; } for( unsigned i = 0; i < m_StopSegments.size(); i++ ) { StopSegment &stop = m_StopSegments[i]; if( stop.m_iStartRow < iStartRow ) continue; stop.m_iStartRow += iRowsToAdd; } if( iStartRow == 0 ) { /* If we're shifting up at the beginning, we just shifted up the first BPMSegment. That * segment must always begin at 0. */ ASSERT( m_BPMSegments.size() > 0 ); m_BPMSegments[0].m_iStartIndex = 0; } } /* Delete BPMChanges and StopSegments in [iStartRow,iRowsToDelete), and shift down. */ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete ) { /* Remember the BPM at the end of the region being deleted. */ float fNewBPM = this->GetBPMAtBeat( NoteRowToBeat(iStartRow+iRowsToDelete) ); /* We're moving rows up. Delete any BPM changes and stops in the region being * deleted. */ for( unsigned i = 0; i < m_BPMSegments.size(); i++ ) { BPMSegment &bpm = m_BPMSegments[i]; /* Before deleted region: */ if( bpm.m_iStartIndex < iStartRow ) continue; /* Inside deleted region: */ if( bpm.m_iStartIndex < iStartRow+iRowsToDelete ) { m_BPMSegments.erase( m_BPMSegments.begin()+i, m_BPMSegments.begin()+i+1 ); --i; continue; } /* After deleted region: */ bpm.m_iStartIndex -= iRowsToDelete; } for( unsigned i = 0; i < m_StopSegments.size(); i++ ) { StopSegment &stop = m_StopSegments[i]; /* Before deleted region: */ if( stop.m_iStartRow < iStartRow ) continue; /* Inside deleted region: */ if( stop.m_iStartRow < iStartRow+iRowsToDelete ) { m_StopSegments.erase( m_StopSegments.begin()+i, m_StopSegments.begin()+i+1 ); --i; continue; } /* After deleted region: */ stop.m_iStartRow -= iRowsToDelete; } this->SetBPMAtRow( iStartRow, fNewBPM ); } bool TimingData::HasBpmChanges() const { return m_BPMSegments.size()>1; } bool TimingData::HasStops() const { return m_StopSegments.size()>0; } /* * (c) 2001-2004 Chris Danford, 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. */