Files
itgmania212121/src/TimingData.cpp
T

1092 lines
31 KiB
C++
Raw Normal View History

2011-03-17 01:47:30 -04:00
#include "global.h"
#include "TimingData.h"
#include "PrefsManager.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "NoteTypes.h"
#include "Foreach.h"
#include <float.h>
TimingData::TimingData(float fOffset) : m_fBeat0OffsetInSeconds(fOffset)
{
2011-03-17 01:47:30 -04:00
}
TimingData::~TimingData()
{
// This is causing weird crashes, probably due to someone hanging onto pointers
// for too long. Commenting this out until we can track it down... -- vyhd
#if 0
/* Delete all pointers owned by this TimingData. */
FOREACH_TimingSegmentType( tst )
{
vector<TimingSegment*> &vSegs = m_avpTimingSegments[tst];
for( unsigned i = 0; i < vSegs.size(); ++i )
delete vSegs[i];
vSegs.clear();
}
#endif
}
bool TimingData::empty() const
{
FOREACH_TimingSegmentType( tst )
if( !GetTimingSegments(tst).empty() )
return false;
return true;
}
2011-07-26 14:13:10 -04:00
TimingData TimingData::CopyRange(int startRow, int endRow) const
{
TimingData ret;
FOREACH_TimingSegmentType( tst )
{
const vector<TimingSegment*> &vSegs = GetTimingSegments(tst);
for (unsigned i = 0; i < vSegs.size(); i++)
{
const TimingSegment *seg = vSegs[i];
int row = seg->GetRow();
if (row >= startRow && row < endRow)
{
TimingSegment *cpy = seg->Copy();
// offset rows as though startRow were beat 0.
cpy->SetRow(seg->GetRow() - startRow);
ret.AddSegment(cpy);
}
}
}
return ret;
}
2011-07-03 13:57:22 -04:00
void TimingData::GetActualBPM( float &fMinBPMOut, float &fMaxBPMOut, float highest ) const
2011-03-17 01:47:30 -04:00
{
fMinBPMOut = FLT_MAX;
fMaxBPMOut = 0;
const vector<TimingSegment*> &bpms = GetTimingSegments(SEGMENT_BPM);
2011-07-14 15:22:32 -04:00
for (unsigned i = 0; i < bpms.size(); i++)
2011-03-17 01:47:30 -04:00
{
const float fBPM = ToBPM(bpms[i])->GetBPM();
2011-07-03 13:57:22 -04:00
fMaxBPMOut = clamp(max( fBPM, fMaxBPMOut ), 0, highest);
2011-03-17 01:47:30 -04:00
fMinBPMOut = min( fBPM, fMinBPMOut );
}
}
float TimingData::GetNextSegmentBeatAtRow(TimingSegmentType tst, int row) const
{
const vector<TimingSegment *> segs = GetTimingSegments(tst);
2011-07-14 23:54:06 -04:00
for (unsigned i = 0; i < segs.size(); i++ )
{
if( segs[i]->GetRow() <= row )
{
continue;
}
return segs[i]->GetBeat();
2011-07-14 23:54:06 -04:00
}
return NoteRowToBeat(row);
}
float TimingData::GetPreviousSegmentBeatAtRow(TimingSegmentType tst, int row) const
2011-07-14 23:54:06 -04:00
{
float backup = -1;
const vector<TimingSegment *> segs = GetTimingSegments(tst);
2011-07-14 23:54:06 -04:00
for (unsigned i = 0; i < segs.size(); i++ )
{
if( segs[i]->GetRow() >= row )
{
break;
}
backup = segs[i]->GetBeat();
2011-07-14 23:54:06 -04:00
}
return (backup > -1) ? backup : NoteRowToBeat(row);
}
static const int INVALID_INDEX = -1;
2011-03-17 01:47:30 -04:00
int TimingData::GetSegmentIndexAtRow(TimingSegmentType tst, int iRow ) const
2011-03-17 01:47:30 -04:00
{
const vector<TimingSegment*> &vSegs = GetTimingSegments(tst);
2011-03-17 01:47:30 -04:00
if( vSegs.empty() )
return INVALID_INDEX;
2011-07-09 02:28:15 -04:00
int min = 0, max = vSegs.size() - 1;
int l = min, r = max;
while( l <= r )
2011-05-25 22:44:41 +07:00
{
int m = ( l + r ) / 2;
if( ( m == min || vSegs[m]->GetRow() <= iRow ) && ( m == max || iRow < vSegs[m + 1]->GetRow() ) )
{
return m;
}
else if( vSegs[m]->GetRow() <= iRow )
{
l = m + 1;
}
else
{
r = m - 1;
}
2011-05-16 02:00:17 -04:00
}
return INVALID_INDEX; // this should not be reached. :(
2011-05-15 13:48:10 -04:00
2011-05-25 22:44:41 +07:00
}
struct ts_less : binary_function <TimingSegment*, TimingSegment*, bool>
2011-05-16 02:00:17 -04:00
{
bool operator() (const TimingSegment *x, const TimingSegment *y) const
2011-05-16 02:00:17 -04:00
{
return (*x) < (*y);
2011-05-16 02:00:17 -04:00
}
};
2011-05-16 02:00:17 -04:00
2011-03-17 01:47:30 -04:00
// Multiply the BPM in the range [fStartBeat,fEndBeat) by fFactor.
void TimingData::MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor )
{
// Change all other BPM segments in this range.
vector<TimingSegment *> &bpms = m_avpTimingSegments[SEGMENT_BPM];
2011-07-14 22:19:32 -04:00
for( unsigned i=0; i<bpms.size(); i++ )
2011-03-17 01:47:30 -04:00
{
BPMSegment *bs = ToBPM(bpms[i]);
2011-07-14 22:19:32 -04:00
const int iStartIndexThisSegment = bs->GetRow();
const bool bIsLastBPMSegment = i == bpms.size()-1;
const int iStartIndexNextSegment = bIsLastBPMSegment ? INT_MAX : bpms[i+1]->GetRow();
2011-03-17 01:47:30 -04:00
if( iStartIndexThisSegment <= iStartIndex && iStartIndexNextSegment <= iStartIndex )
continue;
/* If this BPM segment crosses the beginning of the range,
* split it into two. */
if( iStartIndexThisSegment < iStartIndex && iStartIndexNextSegment > iStartIndex )
{
2011-07-14 22:19:32 -04:00
BPMSegment * b = new BPMSegment(iStartIndexNextSegment,
bs->GetBPS());
bpms.insert(bpms.begin()+i+1, b);
2011-03-17 01:47:30 -04:00
/* 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 )
{
2011-07-14 22:19:32 -04:00
BPMSegment * b = new BPMSegment(iEndIndex,
bs->GetBPS());
bpms.insert(bpms.begin()+i+1, b);
2011-03-17 01:47:30 -04:00
}
else if( iStartIndexNextSegment > iEndIndex )
continue;
2011-07-14 22:19:32 -04:00
bs->SetBPM(bs->GetBPM() * fFactor);
2011-03-17 01:47:30 -04:00
}
}
bool TimingData::IsWarpAtRow( int iNoteRow ) const
{
const vector<TimingSegment *> &warps = GetTimingSegments(SEGMENT_WARP);
2011-07-14 22:19:32 -04:00
if( warps.empty() )
2011-03-25 23:40:03 +07:00
return false;
int i = GetSegmentIndexAtRow( SEGMENT_WARP, iNoteRow );
const WarpSegment *s = ToWarp(warps[i]);
2011-05-31 10:59:18 -04:00
float beatRow = NoteRowToBeat(iNoteRow);
2011-07-14 22:19:32 -04:00
if( s->GetBeat() <= beatRow && beatRow < (s->GetBeat() + s->GetLength() ) )
{
// Allow stops inside warps to allow things like stop, warp, stop, warp, stop, and so on.
if( GetTimingSegments(SEGMENT_STOP).empty() && GetTimingSegments(SEGMENT_DELAY).empty() )
{
return true;
}
if( GetStopAtRow(iNoteRow) != 0.0f || GetDelayAtRow(iNoteRow) != 0.0f )
{
return false;
}
return true;
}
return false;
}
2011-05-16 02:00:17 -04:00
bool TimingData::IsFakeAtRow( int iNoteRow ) const
{
const vector<TimingSegment *> &fakes = GetTimingSegments(SEGMENT_FAKE);
2011-07-14 22:19:32 -04:00
if( fakes.empty() )
2011-05-16 02:00:17 -04:00
return false;
int i = GetSegmentIndexAtRow( SEGMENT_FAKE, iNoteRow );
const FakeSegment *s = ToFake(fakes[i]);
2011-05-31 00:51:25 -04:00
float beatRow = NoteRowToBeat(iNoteRow);
2011-07-14 22:19:32 -04:00
if( s->GetBeat() <= beatRow && beatRow < ( s->GetBeat() + s->GetLength() ) )
2011-05-16 02:00:17 -04:00
{
return true;
}
return false;
}
/* DummySegments: since our model relies on being able to get a segment at will,
* whether one exists or not, we have a bunch of dummies to return if there is
* no segment. It's kind of kludgy, but when we have functions making
* indiscriminate calls to get segments at arbitrary rows, I think it's the
* best solution we've got for now.
*
* Note that types whose SegmentEffectAreas are "Indefinite" are NULL here,
* because they should never need to be used; we always have at least one such
* segment in the TimingData, and if not, we'll crash anyway. -- vyhd */
static const TimingSegment* DummySegments[NUM_TimingSegmentType] =
{
NULL, // BPMSegment
new StopSegment,
new DelaySegment,
NULL, // TimeSignatureSegment
new WarpSegment,
NULL, // LabelSegment
NULL, // TickcountSegment
NULL, // ComboSegment
NULL, // SpeedSegment
NULL, // ScrollSegment
new FakeSegment
};
2011-03-17 01:47:30 -04:00
const TimingSegment* TimingData::GetSegmentAtRow( int iNoteRow, TimingSegmentType tst ) const
2011-03-25 00:54:33 -04:00
{
const vector<TimingSegment*> &vSegments = GetTimingSegments(tst);
2011-03-25 00:54:33 -04:00
if( vSegments.empty() )
return DummySegments[tst];
2011-05-15 13:48:10 -04:00
int index = GetSegmentIndexAtRow( tst, iNoteRow );
const TimingSegment *seg = vSegments[index];
switch( seg->GetEffectType() )
{
case SegmentEffectType_Indefinite:
{
// this segment is in effect at this row
return seg;
}
default:
{
// if the returned segment isn't exactly on this row,
// we don't want it, return a dummy instead
if( seg->GetRow() == iNoteRow )
return seg;
else
return DummySegments[tst];
}
}
ASSERT( 0 );
2011-05-25 22:44:41 +07:00
}
TimingSegment* GetSegmentAtRow( int iNoteRow, TimingSegmentType tst )
2011-03-25 00:54:33 -04:00
{
return const_cast<TimingSegment*>( GetSegmentAtRow(iNoteRow, tst) );
2011-03-25 00:54:33 -04:00
}
static void EraseSegment( vector<TimingSegment*> &vSegs, int index, TimingSegment *cur )
2011-03-25 00:54:33 -04:00
{
LOG->Trace( "EraseSegment(%d, %p)", index, cur );
cur->DebugPrint();
vSegs.erase( vSegs.begin() + index );
SAFE_DELETE( cur );
2011-03-25 00:54:33 -04:00
}
// NOTE: the pointer we're passed is a reference to a temporary,
// so we must deep-copy it (with ::Copy) for new allocations.
void TimingData::AddSegment( const TimingSegment *seg )
2011-03-17 01:47:30 -04:00
{
LOG->Trace( "AddSegment( %s )", TimingSegmentTypeToString(seg->GetType()).c_str() );
seg->DebugPrint();
TimingSegmentType tst = seg->GetType();
vector<TimingSegment*> &vSegs = m_avpTimingSegments[tst];
// OPTIMIZATION: if this is our first segment, push and return.
if( vSegs.empty() )
{
vSegs.push_back( seg->Copy() );
return;
}
int index = GetSegmentIndexAtRow( tst, seg->GetRow() );
ASSERT( index != INVALID_INDEX );
TimingSegment *cur = vSegs[index];
bool bIsNotable = seg->IsNotable();
bool bOnSameRow = seg->GetRow() == cur->GetRow();
// ignore changes that are zero and don't overwrite an existing segment
if( !bIsNotable && !bOnSameRow )
return;
switch( seg->GetEffectType() )
{
case SegmentEffectType_Row:
case SegmentEffectType_Range:
{
// if we're overwriting a change with a non-notable
// one, take it to mean deleting the existing segment
if( bOnSameRow && !bIsNotable )
{
EraseSegment( vSegs, index, cur );
return;
}
break;
}
case SegmentEffectType_Indefinite:
{
TimingSegment *prev = cur;
// get the segment before last; if we're on the same
// row, get the segment in effect before 'cur'
if( bOnSameRow && index > 0 )
prev = vSegs[index - 1];
// if true, this is redundant segment change
if( prev != cur && (*prev) == (*seg) )
{
EraseSegment( vSegs, index, cur );
return;
}
break;
}
}
// the segment at or before this row is equal to the new one; ignore it
if( bOnSameRow && (*cur) == (*seg) )
{
LOG->Trace( "equals previous segment, ignoring" );
return;
}
// Copy() the segment (which allocates a new segment), assign it
// to the position of the old one, then delete the old pointer.
TimingSegment *cpy = seg->Copy();
if( bOnSameRow )
{
// delete the existing pointer and replace it
SAFE_DELETE( cur );
vSegs[index] = cpy;
}
else
{
// copy and insert a new segment
vector<TimingSegment*>::iterator it;
it = upper_bound( vSegs.begin(), vSegs.end(), cpy, ts_less() );
vSegs.insert( it, cpy );
}
2011-03-17 01:47:30 -04:00
}
bool TimingData::DoesLabelExist( const RString& sLabel ) const
{
const vector<TimingSegment *> &labels = GetTimingSegments(SEGMENT_LABEL);
2011-07-14 23:15:48 -04:00
for (unsigned i = 0; i < labels.size(); i++)
{
if (ToLabel(labels[i])->GetLabel() == sLabel)
return true;
}
return false;
}
2011-03-17 01:47:30 -04:00
void TimingData::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpLengthOut ) const
{
fElapsedTime += PREFSMAN->m_fGlobalOffsetSeconds;
GetBeatAndBPSFromElapsedTimeNoOffset( fElapsedTime, fBeatOut, fBPSOut, bFreezeOut, bDelayOut, iWarpBeginOut, fWarpLengthOut );
}
enum
2011-03-17 01:47:30 -04:00
{
FOUND_WARP,
FOUND_WARP_DESTINATION,
FOUND_BPM_CHANGE,
FOUND_STOP,
FOUND_DELAY,
2011-08-15 18:12:12 -04:00
FOUND_STOP_DELAY, // we have these two on the same row.
FOUND_MARKER,
NOT_FOUND
};
2011-03-17 01:47:30 -04:00
void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpDestinationOut ) const
{
const vector<TimingSegment *> * segs = m_avpTimingSegments;
2011-07-15 00:54:11 -04:00
vector<TimingSegment *>::const_iterator itBPMS = segs[SEGMENT_BPM].begin();
vector<TimingSegment *>::const_iterator itWS = segs[SEGMENT_WARP].begin();
vector<TimingSegment *>::const_iterator itSS = segs[SEGMENT_STOP].begin();
vector<TimingSegment *>::const_iterator itDS = segs[SEGMENT_DELAY].begin();
bFreezeOut = false;
bDelayOut = false;
iWarpBeginOut = -1;
int iLastRow = 0;
float fLastTime = -m_fBeat0OffsetInSeconds;
2011-06-12 03:37:10 -04:00
float fBPS = GetBPMAtRow(0) / 60.0f;
float bIsWarping = false;
2011-06-12 03:37:10 -04:00
float fWarpDestination = 0;
for( ;; )
2011-03-17 01:47:30 -04:00
{
int iEventRow = INT_MAX;
int iEventType = NOT_FOUND;
if( bIsWarping && BeatToNoteRow(fWarpDestination) < iEventRow )
2011-03-17 01:47:30 -04:00
{
iEventRow = BeatToNoteRow(fWarpDestination);
iEventType = FOUND_WARP_DESTINATION;
2011-03-17 01:47:30 -04:00
}
2011-07-15 00:54:11 -04:00
if (itBPMS != segs[SEGMENT_BPM].end() &&
(*itBPMS)->GetRow() < iEventRow )
2011-03-17 01:47:30 -04:00
{
2011-07-15 00:54:11 -04:00
iEventRow = (*itBPMS)->GetRow();
iEventType = FOUND_BPM_CHANGE;
2011-03-17 01:47:30 -04:00
}
if (itDS != segs[SEGMENT_DELAY].end() &&
(*itDS)->GetRow() < iEventRow)
{
iEventRow = (*itDS)->GetRow();
iEventType = FOUND_DELAY;
}
if (itSS != segs[SEGMENT_STOP].end() &&
2011-08-15 17:54:06 -04:00
(*itSS)->GetRow() < iEventRow ) // && iEventType != FOUND_DELAY )
2011-03-17 01:47:30 -04:00
{
2011-08-15 18:12:12 -04:00
int tmpRow = iEventRow;
2011-07-15 00:54:11 -04:00
iEventRow = (*itSS)->GetRow();
2011-08-15 18:12:12 -04:00
iEventType = (tmpRow == iEventRow) ? FOUND_STOP_DELAY : FOUND_STOP;
2011-03-17 01:47:30 -04:00
}
2011-07-15 00:54:11 -04:00
if (itWS != segs[SEGMENT_WARP].end() &&
(*itWS)->GetRow() < iEventRow )
{
2011-07-15 00:54:11 -04:00
iEventRow = (*itWS)->GetRow();
iEventType = FOUND_WARP;
}
if( iEventType == NOT_FOUND )
2011-03-17 01:47:30 -04:00
{
break;
}
float fTimeToNextEvent = bIsWarping ? 0 : NoteRowToBeat( iEventRow - iLastRow ) / fBPS;
float fNextEventTime = fLastTime + fTimeToNextEvent;
if ( fElapsedTime < fNextEventTime )
{
break;
}
fLastTime = fNextEventTime;
switch( iEventType )
{
case FOUND_WARP_DESTINATION:
bIsWarping = false;
break;
case FOUND_BPM_CHANGE:
fBPS = ToBPM(*itBPMS)->GetBPS();
itBPMS ++;
break;
case FOUND_DELAY:
2011-08-15 19:23:43 -04:00
case FOUND_STOP_DELAY:
{
const DelaySegment *ss = ToDelay(*itDS);
fTimeToNextEvent = ss->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent;
if ( fElapsedTime < fNextEventTime )
{
bFreezeOut = false;
bDelayOut = true;
fBeatOut = ss->GetBeat();
fBPSOut = fBPS;
return;
}
fLastTime = fNextEventTime;
itDS ++;
2011-08-15 19:23:43 -04:00
if (iEventType == FOUND_DELAY)
break;
}
case FOUND_STOP:
{
const StopSegment *ss = ToStop(*itSS);
2011-07-15 00:54:11 -04:00
fTimeToNextEvent = ss->GetPause();
2011-03-26 11:59:48 -05:00
fNextEventTime = fLastTime + fTimeToNextEvent;
if ( fElapsedTime < fNextEventTime )
{
bFreezeOut = true;
bDelayOut = false;
2011-07-15 00:54:11 -04:00
fBeatOut = ss->GetBeat();
2011-03-26 11:59:48 -05:00
fBPSOut = fBPS;
return;
}
fLastTime = fNextEventTime;
itSS ++;
2011-08-15 18:12:12 -04:00
break;
}
case FOUND_WARP:
{
bIsWarping = true;
const WarpSegment *ws = ToWarp(*itWS);
2011-07-15 00:54:11 -04:00
float fWarpSum = ws->GetLength() + ws->GetBeat();
if( fWarpSum > fWarpDestination )
{
2011-05-16 22:05:23 +07:00
fWarpDestination = fWarpSum;
}
iWarpBeginOut = iEventRow;
fWarpDestinationOut = fWarpDestination;
itWS ++;
break;
}
2011-03-17 01:47:30 -04:00
}
iLastRow = iEventRow;
2011-03-17 01:47:30 -04:00
}
fBeatOut = NoteRowToBeat( iLastRow ) + (fElapsedTime - fLastTime) * fBPS;
fBPSOut = fBPS;
2011-03-17 01:47:30 -04:00
}
float TimingData::GetElapsedTimeFromBeat( float fBeat ) const
{
return TimingData::GetElapsedTimeFromBeatNoOffset( fBeat ) - PREFSMAN->m_fGlobalOffsetSeconds;
}
float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const
{
const vector<TimingSegment *> * segs = m_avpTimingSegments;
2011-07-15 00:54:11 -04:00
vector<TimingSegment *>::const_iterator itBPMS = segs[SEGMENT_BPM].begin();
vector<TimingSegment *>::const_iterator itWS = segs[SEGMENT_WARP].begin();
vector<TimingSegment *>::const_iterator itSS = segs[SEGMENT_STOP].begin();
vector<TimingSegment *>::const_iterator itDS = segs[SEGMENT_DELAY].begin();
int iLastRow = 0;
float fLastTime = -m_fBeat0OffsetInSeconds;
2011-06-12 03:37:10 -04:00
float fBPS = GetBPMAtRow(0) / 60.0f;
float bIsWarping = false;
2011-06-12 03:37:10 -04:00
float fWarpDestination = 0;
for( ;; )
2011-03-17 01:47:30 -04:00
{
int iEventRow = INT_MAX;
int iEventType = NOT_FOUND;
if( bIsWarping && BeatToNoteRow(fWarpDestination) < iEventRow )
2011-03-17 01:47:30 -04:00
{
iEventRow = BeatToNoteRow(fWarpDestination);
iEventType = FOUND_WARP_DESTINATION;
2011-03-17 01:47:30 -04:00
}
2011-07-15 00:54:11 -04:00
if (itBPMS != segs[SEGMENT_BPM].end() &&
(*itBPMS)->GetRow() < iEventRow )
{
2011-07-15 00:54:11 -04:00
iEventRow = (*itBPMS)->GetRow();
iEventType = FOUND_BPM_CHANGE;
}
if (itDS != segs[SEGMENT_DELAY].end() &&
(*itDS)->GetRow() < iEventRow ) // delays (come before marker)
{
iEventRow = (*itDS)->GetRow();
iEventType = FOUND_DELAY;
}
if( BeatToNoteRow(fBeat) < iEventRow )
{
iEventRow = BeatToNoteRow(fBeat);
iEventType = FOUND_MARKER;
}
if (itSS != segs[SEGMENT_STOP].end() &&
2011-07-15 00:54:11 -04:00
(*itSS)->GetRow() < iEventRow ) // stops (come after marker)
{
2011-07-15 00:54:11 -04:00
iEventRow = (*itSS)->GetRow();
iEventType = FOUND_STOP;
}
2011-07-15 00:54:11 -04:00
if (itWS != segs[SEGMENT_WARP].end() &&
(*itWS)->GetRow() < iEventRow )
{
2011-07-15 00:54:11 -04:00
iEventRow = (*itWS)->GetRow();
iEventType = FOUND_WARP;
}
float fTimeToNextEvent = bIsWarping ? 0 : NoteRowToBeat( iEventRow - iLastRow ) / fBPS;
float fNextEventTime = fLastTime + fTimeToNextEvent;
fLastTime = fNextEventTime;
switch( iEventType )
{
case FOUND_WARP_DESTINATION:
bIsWarping = false;
break;
case FOUND_BPM_CHANGE:
fBPS = ToBPM(*itBPMS)->GetBPS();
itBPMS ++;
break;
case FOUND_STOP:
fTimeToNextEvent = ToStop(*itSS)->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent;
fLastTime = fNextEventTime;
itSS ++;
break;
case FOUND_DELAY:
fTimeToNextEvent = ToDelay(*itDS)->GetPause();
fNextEventTime = fLastTime + fTimeToNextEvent;
fLastTime = fNextEventTime;
itDS ++;
break;
case FOUND_MARKER:
return fLastTime;
case FOUND_WARP:
{
bIsWarping = true;
WarpSegment *ws = ToWarp(*itWS);
2011-07-15 00:54:11 -04:00
float fWarpSum = ws->GetLength() + ws->GetBeat();
if( fWarpSum > fWarpDestination )
{
2011-05-16 22:05:23 +07:00
fWarpDestination = fWarpSum;
}
itWS ++;
break;
}
}
iLastRow = iEventRow;
2011-03-17 01:47:30 -04:00
}
// won't reach here, unless BeatToNoteRow(fBeat == INT_MAX) (impossible)
2011-03-17 01:47:30 -04:00
}
2011-05-25 22:44:41 +07:00
float TimingData::GetDisplayedBeat( float fBeat ) const
{
2011-06-12 03:37:10 -04:00
float fOutBeat = 0;
unsigned i;
const vector<TimingSegment *> &scrolls = m_avpTimingSegments[SEGMENT_SCROLL];
for( i=0; i<scrolls.size()-1; i++ )
2011-05-25 22:44:41 +07:00
{
if( scrolls[i+1]->GetBeat() > fBeat )
break;
fOutBeat += (scrolls[i+1]->GetBeat() - scrolls[i]->GetBeat()) * ToScroll(scrolls[i])->GetRatio();
2011-05-25 22:44:41 +07:00
}
fOutBeat += (fBeat - scrolls[i]->GetBeat()) * ToScroll(scrolls[i])->GetRatio();
2011-05-25 22:44:41 +07:00
return fOutBeat;
}
void TimingData::ScaleRegion( float fScale, int iStartIndex, int iEndIndex, bool bAdjustBPM )
2011-03-17 01:47:30 -04:00
{
ASSERT( fScale > 0 );
ASSERT( iStartIndex >= 0 );
ASSERT( iStartIndex < iEndIndex );
int length = iEndIndex - iStartIndex;
int newLength = lrintf( fScale * length );
FOREACH_TimingSegmentType( tst )
for (unsigned j = 0; j < m_avpTimingSegments[tst].size(); j++)
m_avpTimingSegments[tst][j]->Scale(iStartIndex, length, newLength);
// adjust BPM changes to preserve timing
if( bAdjustBPM )
{
int iNewEndIndex = iStartIndex + newLength;
float fEndBPMBeforeScaling = GetBPMAtRow(iNewEndIndex);
vector<TimingSegment *> &bpms = m_avpTimingSegments[SEGMENT_BPM];
// adjust BPM changes "between" iStartIndex and iNewEndIndex
2011-07-15 00:54:11 -04:00
for ( unsigned i = 0; i < bpms.size(); i++ )
{
BPMSegment *bpm = ToBPM(bpms[i]);
2011-07-15 00:54:11 -04:00
const int iSegStart = bpm->GetRow();
if( iSegStart <= iStartIndex )
continue;
else if( iSegStart >= iNewEndIndex )
continue;
else
2011-07-15 00:54:11 -04:00
bpm->SetBPM( bpm->GetBPM() * fScale );
}
// set BPM at iStartIndex and iNewEndIndex.
SetBPMAtRow( iStartIndex, GetBPMAtRow(iStartIndex) * fScale );
SetBPMAtRow( iNewEndIndex, fEndBPMBeforeScaling );
}
2011-03-17 01:47:30 -04:00
}
void TimingData::InsertRows( int iStartRow, int iRowsToAdd )
{
FOREACH_TimingSegmentType( tst )
2011-03-17 01:47:30 -04:00
{
vector<TimingSegment *> &segs = m_avpTimingSegments[tst];
2011-07-14 15:22:32 -04:00
for (unsigned j = 0; j < segs.size(); j++)
{
TimingSegment *seg = segs[j];
if (seg->GetRow() < iStartRow)
continue;
seg->SetRow(seg->GetRow() + iRowsToAdd);
}
2011-05-26 17:38:31 -04:00
}
2011-03-17 01:47:30 -04:00
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. */
vector<TimingSegment *> &bpms = m_avpTimingSegments[SEGMENT_BPM];
2011-07-14 15:22:32 -04:00
ASSERT_M( bpms.size() > 0, "There must be at least one BPM Segment in the chart!" );
bpms[0]->SetRow(0);
2011-03-17 01:47:30 -04:00
}
}
// 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 = GetBPMAtBeat( NoteRowToBeat(iStartRow+iRowsToDelete) );
2011-03-17 01:47:30 -04:00
/* We're moving rows up. Delete any BPM changes and stops in the region
* being deleted. */
FOREACH_TimingSegmentType( tst )
2011-03-17 01:47:30 -04:00
{
vector<TimingSegment *> &segs = m_avpTimingSegments[tst];
2011-07-14 15:22:32 -04:00
for (unsigned j = 0; j < segs.size(); j++)
2011-03-17 01:47:30 -04:00
{
2011-07-14 15:22:32 -04:00
TimingSegment *seg = segs[j];
// Before deleted region:
if (seg->GetRow() < iStartRow)
continue;
// Inside deleted region:
if (seg->GetRow() < iStartRow + iRowsToDelete)
{
segs.erase(segs.begin()+j, segs.begin()+j+1);
--j;
continue;
}
2011-07-14 15:22:32 -04:00
// After deleted regions:
seg->SetRow(seg->GetRow() - iRowsToDelete);
2011-05-16 02:00:17 -04:00
}
}
SetBPMAtRow( iStartRow, fNewBPM );
2011-03-17 01:47:30 -04:00
}
2011-05-24 12:29:55 +07:00
float TimingData::GetDisplayedSpeedPercent( float fSongBeat, float fMusicSeconds ) const
{
/* HACK: Somehow we get called into this function when there is no
* TimingData to work with. This seems to happen the most upon
* leaving the editor. Still, cover our butts in case this instance
* isn't existing. */
/* ...but force a crash, so debuggers will catch it and stop here.
* That'll make us keep this bug in mind. -- vyhd */
if( !this )
{
DEBUG_ASSERT( this );
return 1.0f;
}
const vector<TimingSegment *> &speeds = GetTimingSegments(SEGMENT_SPEED);
2011-07-15 00:54:11 -04:00
if( speeds.size() == 0 )
return 1.0f;
const int index = GetSegmentIndexAtBeat( SEGMENT_SPEED, fSongBeat );
const SpeedSegment *seg = ToSpeed(speeds[index]);
2011-07-15 00:54:11 -04:00
float fStartBeat = seg->GetBeat();
float fStartTime = GetElapsedTimeFromBeat( fStartBeat ) - GetDelayAtBeat( fStartBeat );
float fEndTime;
float fCurTime = fMusicSeconds;
if( seg->GetUnit() == SpeedSegment::UNIT_SECONDS )
{
fEndTime = fStartTime + seg->GetDelay();
}
else
{
fEndTime = GetElapsedTimeFromBeat( fStartBeat + seg->GetDelay() )
- GetDelayAtBeat( fStartBeat + seg->GetDelay() );
}
SpeedSegment *first = ToSpeed(speeds[0]);
if( ( index == 0 && first->GetDelay() > 0.0 ) && fCurTime < fStartTime )
{
2011-06-12 03:37:10 -04:00
return 1.0f;
}
else if( fEndTime >= fCurTime && ( index > 0 || first->GetDelay() > 0.0 ) )
{
const float fPriorSpeed = (index == 0) ? 1 :
ToSpeed(speeds[index-1])->GetRatio();
float fTimeUsed = fCurTime - fStartTime;
float fDuration = fEndTime - fStartTime;
float fRatioUsed = fDuration == 0.0 ? 1 : fTimeUsed / fDuration;
2011-07-15 00:54:11 -04:00
float fDistance = fPriorSpeed - seg->GetRatio();
float fRatioNeed = fRatioUsed * -fDistance;
return (fPriorSpeed + fRatioNeed);
}
else
{
2011-07-15 00:54:11 -04:00
return seg->GetRatio();
}
}
2011-05-10 16:00:47 +07:00
void TimingData::TidyUpData()
{
// If there are no BPM segments, provide a default.
vector<TimingSegment *> *segs = m_avpTimingSegments;
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_BPM].empty() )
2011-05-10 16:00:47 +07:00
{
LOG->UserLog( "Song file", m_sFile, "has no BPM segments, default provided." );
AddSegment( BPMSegment(0, 60) );
2011-05-10 16:00:47 +07:00
}
// Make sure the first BPM segment starts at beat 0.
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_BPM][0]->GetRow() != 0 )
segs[SEGMENT_BPM][0]->SetRow(0);
2011-05-10 16:00:47 +07:00
// If no time signature specified, assume default time for the whole song.
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_TIME_SIG].empty() )
AddSegment( TimeSignatureSegment(0) );
2011-07-15 00:54:11 -04:00
// Likewise, if no tickcount signature is specified, assume 4 ticks
// per beat for the entire song. The default of 4 is chosen more
// for compatibility with the main Pump series than anything else.
// (TickcountSegment's constructor handles that now. -- vyhd)
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_TICKCOUNT].empty() )
AddSegment( TickcountSegment(0) );
2011-05-10 16:00:47 +07:00
// Have a default combo segment of one just in case.
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_COMBO].empty() )
AddSegment( ComboSegment(0) );
2011-05-10 16:00:47 +07:00
// Have a default label segment just in case.
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_LABEL].empty() )
AddSegment( LabelSegment(0, "Song Start") );
2011-05-15 13:48:10 -04:00
// Always be sure there is a starting speed.
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_SPEED].empty() )
AddSegment( SpeedSegment(0) );
2011-05-25 22:44:41 +07:00
// Always be sure there is a starting scrolling factor.
2011-07-15 00:54:11 -04:00
if( segs[SEGMENT_SCROLL].empty() )
AddSegment( ScrollSegment(0) );
2011-05-10 16:00:47 +07:00
}
void TimingData::SortSegments( TimingSegmentType tst )
2011-05-16 02:00:17 -04:00
{
vector<TimingSegment*> &vSegments = m_avpTimingSegments[tst];
sort( vSegments.begin(), vSegments.end() );
2011-05-16 02:00:17 -04:00
}
2011-05-15 13:48:10 -04:00
bool TimingData::HasSpeedChanges() const
{
const vector<TimingSegment *> &speeds = GetTimingSegments(SEGMENT_SPEED);
return (speeds.size()>1 || ToSpeed(speeds[0])->GetRatio() != 1);
2011-05-15 13:48:10 -04:00
}
2011-05-25 22:44:41 +07:00
bool TimingData::HasScrollChanges() const
{
const vector<TimingSegment *> &scrolls = GetTimingSegments(SEGMENT_SCROLL);
return (scrolls.size()>1 || ToScroll(scrolls[0])->GetRatio() != 1);
2011-05-25 22:44:41 +07:00
}
2011-03-17 01:47:30 -04:00
void TimingData::NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const
{
iMeasureIndexOut = 0;
const vector<TimingSegment *> &tSigs = GetTimingSegments(SEGMENT_TIME_SIG);
2011-07-15 00:54:11 -04:00
for (unsigned i = 0; i < tSigs.size(); i++)
2011-03-17 01:47:30 -04:00
{
TimeSignatureSegment *curSig = ToTimeSignature(tSigs[i]);
2011-07-15 00:54:11 -04:00
int iSegmentEndRow = (i + 1 == tSigs.size()) ? INT_MAX : curSig->GetRow();
2011-07-15 00:54:11 -04:00
int iRowsPerMeasureThisSegment = curSig->GetNoteRowsPerMeasure();
2011-03-17 01:47:30 -04:00
2011-07-15 00:54:11 -04:00
if( iNoteRow >= curSig->GetRow() )
2011-03-17 01:47:30 -04:00
{
// iNoteRow lands in this segment
2011-07-15 00:54:11 -04:00
int iNumRowsThisSegment = iNoteRow - curSig->GetRow();
2011-03-17 01:47:30 -04:00
int iNumMeasuresThisSegment = (iNumRowsThisSegment) / iRowsPerMeasureThisSegment; // don't round up
iMeasureIndexOut += iNumMeasuresThisSegment;
iBeatIndexOut = iNumRowsThisSegment / iRowsPerMeasureThisSegment;
iRowsRemainder = iNumRowsThisSegment % iRowsPerMeasureThisSegment;
return;
}
else
{
// iNoteRow lands after this segment
2011-07-15 00:54:11 -04:00
int iNumRowsThisSegment = iSegmentEndRow - curSig->GetRow();
int iNumMeasuresThisSegment = (iNumRowsThisSegment + iRowsPerMeasureThisSegment - 1)
/ iRowsPerMeasureThisSegment; // round up
2011-03-17 01:47:30 -04:00
iMeasureIndexOut += iNumMeasuresThisSegment;
}
}
ASSERT(0);
return;
}
2011-07-27 22:33:50 -04:00
vector<RString> TimingData::ToVectorString(TimingSegmentType tst, int dec) const
2011-07-17 16:06:40 -04:00
{
const vector<TimingSegment *> segs = GetTimingSegments(tst);
2011-07-17 16:06:40 -04:00
vector<RString> ret;
2011-07-17 16:06:40 -04:00
for (unsigned i = 0; i < segs.size(); i++)
{
ret.push_back(segs[i]->ToString(dec));
}
return ret;
}
2011-03-17 01:47:30 -04:00
// lua start
#include "LuaBinding.h"
/** @brief Allow Lua to have access to the TimingData. */
2011-03-17 01:47:30 -04:00
class LunaTimingData: public Luna<TimingData>
{
public:
static int HasStops( T* p, lua_State *L ) { lua_pushboolean(L, p->HasStops()); return 1; }
static int HasDelays( T* p, lua_State *L ) { lua_pushboolean(L, p->HasDelays()); return 1; }
2011-03-17 01:47:30 -04:00
static int HasBPMChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBpmChanges()); return 1; }
2011-03-24 20:21:43 -04:00
static int HasWarps( T* p, lua_State *L ) { lua_pushboolean(L, p->HasWarps()); return 1; }
2011-05-16 02:00:17 -04:00
static int HasFakes( T* p, lua_State *L ) { lua_pushboolean(L, p->HasFakes()); return 1; }
2011-05-15 13:48:10 -04:00
static int HasSpeedChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasSpeedChanges()); return 1; }
2011-06-01 10:12:10 -04:00
static int HasScrollChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasScrollChanges()); return 1; }
static int GetWarps( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_WARP), L);
return 1;
}
static int GetFakes( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_FAKE), L);
return 1;
}
static int GetScrolls( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_SCROLL), L);
return 1;
}
static int GetSpeeds( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_SPEED), L);
return 1;
}
static int GetTimeSignatures( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_TIME_SIG), L);
return 1;
}
static int GetCombos( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_COMBO), L);
return 1;
}
static int GetTickcounts( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_TICKCOUNT), L);
return 1;
}
2011-03-17 01:47:30 -04:00
static int GetStops( T* p, lua_State *L )
{
2011-07-27 22:33:50 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_STOP), L);
2011-03-17 01:47:30 -04:00
return 1;
}
static int GetDelays( T* p, lua_State *L )
{
2011-07-27 22:33:50 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_DELAY), L);
2011-03-17 01:47:30 -04:00
return 1;
}
static int GetBPMs( T* p, lua_State *L )
{
vector<float> vBPMs;
const vector<TimingSegment *> &bpms = p->GetTimingSegments(SEGMENT_BPM);
2011-07-14 15:22:32 -04:00
for (unsigned i = 0; i < bpms.size(); i++)
vBPMs.push_back( ToBPM(bpms[i])->GetBPM() );
2011-03-17 01:47:30 -04:00
LuaHelpers::CreateTableFromArray(vBPMs, L);
return 1;
}
2011-04-04 23:03:10 -04:00
static int GetLabels( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_LABEL), L);
2011-04-04 23:03:10 -04:00
return 1;
}
2011-03-17 01:47:30 -04:00
static int GetBPMsAndTimes( T* p, lua_State *L )
{
2011-07-17 16:06:40 -04:00
LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_BPM), L);
2011-03-17 01:47:30 -04:00
return 1;
}
static int GetActualBPM( T* p, lua_State *L )
{
// certainly there's a better way to do it than this? -aj
float fMinBPM, fMaxBPM;
p->GetActualBPM( fMinBPM, fMaxBPM );
vector<float> fBPMs;
fBPMs.push_back( fMinBPM );
fBPMs.push_back( fMaxBPM );
LuaHelpers::CreateTableFromArray(fBPMs, L);
return 1;
}
2011-06-09 18:16:23 -04:00
static int HasNegativeBPMs( T* p, lua_State *L ) { lua_pushboolean(L, p->HasWarps()); return 1; }
2011-03-17 01:47:30 -04:00
// formerly in Song.cpp in sm-ssc private beta 1.x:
static int GetBPMAtBeat( T* p, lua_State *L ) { lua_pushnumber(L, p->GetBPMAtBeat(FArg(1))); return 1; }
static int GetBeatFromElapsedTime( T* p, lua_State *L ) { lua_pushnumber(L, p->GetBeatFromElapsedTime(FArg(1))); return 1; }
static int GetElapsedTimeFromBeat( T* p, lua_State *L ) { lua_pushnumber(L, p->GetElapsedTimeFromBeat(FArg(1))); return 1; }
LunaTimingData()
{
ADD_METHOD( HasStops );
ADD_METHOD( HasDelays );
2011-03-17 01:47:30 -04:00
ADD_METHOD( HasBPMChanges );
2011-03-24 20:21:43 -04:00
ADD_METHOD( HasWarps );
2011-05-16 02:00:17 -04:00
ADD_METHOD( HasFakes );
2011-05-15 13:48:10 -04:00
ADD_METHOD( HasSpeedChanges );
2011-06-01 10:12:10 -04:00
ADD_METHOD( HasScrollChanges );
2011-03-17 01:47:30 -04:00
ADD_METHOD( GetStops );
ADD_METHOD( GetDelays );
ADD_METHOD( GetBPMs );
ADD_METHOD( GetWarps );
ADD_METHOD( GetFakes );
ADD_METHOD( GetTimeSignatures );
ADD_METHOD( GetTickcounts );
ADD_METHOD( GetSpeeds );
ADD_METHOD( GetScrolls );
ADD_METHOD( GetCombos );
2011-04-04 23:03:10 -04:00
ADD_METHOD( GetLabels );
2011-03-17 01:47:30 -04:00
ADD_METHOD( GetBPMsAndTimes );
ADD_METHOD( GetActualBPM );
ADD_METHOD( HasNegativeBPMs );
// formerly in Song.cpp in sm-ssc private beta 1.x:
ADD_METHOD( GetBPMAtBeat );
ADD_METHOD( GetBeatFromElapsedTime );
ADD_METHOD( GetElapsedTimeFromBeat );
}
};
LUA_REGISTER_CLASS( TimingData )
// lua end
/*
* (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.
*/