[splittiming -> default] Bring it in.

Any bugs left in here will be fixed as a team.

Now let's kick some StepMania 5 booty!
This commit is contained in:
Jason Felds
2011-05-19 00:01:23 -04:00
81 changed files with 4158 additions and 2539 deletions
+19 -2
View File
@@ -34,6 +34,8 @@ REGISTER_ACTOR_CLASS_WITH_NAME( HiddenActor, Actor );
float Actor::g_fCurrentBGMTime = 0, Actor::g_fCurrentBGMBeat;
float Actor::g_fCurrentBGMTimeNoOffset = 0, Actor::g_fCurrentBGMBeatNoOffset = 0;
vector<float> Actor::g_vfCurrentBGMBeatPlayer(NUM_PlayerNumber, 0);
vector<float> Actor::g_vfCurrentBGMBeatPlayerNoOffset(NUM_PlayerNumber, 0);
Actor *Actor::Copy() const { return new Actor(*this); }
@@ -68,6 +70,12 @@ void Actor::SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBe
g_fCurrentBGMBeatNoOffset = fBeatNoOffset;
}
void Actor::SetPlayerBGMBeat( PlayerNumber pn, float fBeat, float fBeatNoOffset )
{
g_vfCurrentBGMBeatPlayer[pn] = fBeat;
g_vfCurrentBGMBeatPlayerNoOffset[pn] = fBeatNoOffset;
}
void Actor::SetBGMLight( int iLightNumber, float fCabinetLights )
{
ASSERT( iLightNumber < NUM_CabinetLight );
@@ -150,8 +158,7 @@ Actor::Actor()
lua_setfield( L, -2, "ctx" );
lua_pop( L, 1 );
LUA->Release( L );
m_size = RageVector2( 1, 1 );
InitState();
m_pParent = NULL;
@@ -676,6 +683,16 @@ void Actor::UpdateInternal( float fDeltaTime )
m_fSecsIntoEffect = g_fCurrentBGMBeat;
break;
case CLOCK_BGM_BEAT_PLAYER1:
m_fEffectDelta = g_vfCurrentBGMBeatPlayer[PLAYER_1] - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[PLAYER_1];
break;
case CLOCK_BGM_BEAT_PLAYER2:
m_fEffectDelta = g_vfCurrentBGMBeatPlayer[PLAYER_2] - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[PLAYER_2];
break;
case CLOCK_BGM_TIME:
m_fEffectDelta = g_fCurrentBGMTime - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_fCurrentBGMTime;
+6
View File
@@ -1,6 +1,7 @@
#ifndef ACTOR_H
#define ACTOR_H
#include "PlayerNumber.h"
#include "RageTypes.h"
#include "RageUtil_AutoPtr.h"
#include "LuaReference.h"
@@ -109,6 +110,7 @@ public:
virtual void LoadFromNode( const XNode* pNode );
static void SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBeatNoOffset );
static void SetPlayerBGMBeat( PlayerNumber pn, float fBeat, float fBeatNoOffset );
static void SetBGMLight( int iLightNumber, float fCabinetLights );
/**
@@ -131,6 +133,8 @@ public:
CLOCK_BGM_BEAT,
CLOCK_BGM_TIME_NO_OFFSET,
CLOCK_BGM_BEAT_NO_OFFSET,
CLOCK_BGM_BEAT_PLAYER1,
CLOCK_BGM_BEAT_PLAYER2,
CLOCK_LIGHT_1 = 1000,
CLOCK_LIGHT_LAST = 1100,
NUM_CLOCKS
@@ -713,6 +717,8 @@ protected:
// global state
static float g_fCurrentBGMTime, g_fCurrentBGMBeat;
static float g_fCurrentBGMTimeNoOffset, g_fCurrentBGMBeatNoOffset;
static vector<float> g_vfCurrentBGMBeatPlayer;
static vector<float> g_vfCurrentBGMBeatPlayerNoOffset;
private:
// commands
+12 -12
View File
@@ -58,7 +58,7 @@ void AdjustSync::ResetOriginalSyncData()
s_pTimingDataOriginal = new TimingData;
if( GAMESTATE->m_pCurSong )
*s_pTimingDataOriginal = GAMESTATE->m_pCurSong->m_Timing;
*s_pTimingDataOriginal = GAMESTATE->m_pCurSong->m_SongTiming;
else
*s_pTimingDataOriginal = TimingData();
s_fGlobalOffsetSecondsOriginal = PREFSMAN->m_fGlobalOffsetSeconds;
@@ -87,7 +87,7 @@ void AdjustSync::SaveSyncChanges()
{
if( GAMESTATE->IsCourseMode() )
return;
if( GAMESTATE->m_pCurSong && *s_pTimingDataOriginal != GAMESTATE->m_pCurSong->m_Timing )
if( GAMESTATE->m_pCurSong && *s_pTimingDataOriginal != GAMESTATE->m_pCurSong->m_SongTiming )
{
if( GAMESTATE->IsEditing() )
{
@@ -110,7 +110,7 @@ void AdjustSync::RevertSyncChanges()
if( GAMESTATE->IsCourseMode() )
return;
PREFSMAN->m_fGlobalOffsetSeconds.Set( s_fGlobalOffsetSecondsOriginal );
GAMESTATE->m_pCurSong->m_Timing = *s_pTimingDataOriginal;
GAMESTATE->m_pCurSong->m_SongTiming = *s_pTimingDataOriginal;
ResetOriginalSyncData();
s_fStandardDeviation = 0.0f;
s_fAverageError = 0.0f;
@@ -186,7 +186,7 @@ void AdjustSync::AutosyncOffset()
switch( GAMESTATE->m_SongOptions.GetCurrent().m_AutosyncType )
{
case SongOptions::AUTOSYNC_SONG:
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += mean;
GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += mean;
break;
case SongOptions::AUTOSYNC_MACHINE:
PREFSMAN->m_fGlobalOffsetSeconds.Set( PREFSMAN->m_fGlobalOffsetSeconds + mean );
@@ -232,15 +232,15 @@ void AdjustSync::AutosyncTempo()
if( !CalcLeastSquares( s_vAutosyncTempoData, fSlope, fIntercept, fFilteredError ) )
return;
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += fIntercept;
GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += fIntercept;
const float fScaleBPM = 1.0f/(1.0f - fSlope);
FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments, i )
FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments, i )
i->SetBPM( i->GetBPM() * fScaleBPM );
// We assume that the stops were measured as a number of beats.
// Therefore, if we change the bpms, we need to make a similar
// change to the stops.
FOREACH( StopSegment, GAMESTATE->m_pCurSong->m_Timing.m_StopSegments, i )
FOREACH( StopSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments, i )
i->m_fStopSeconds *= 1.0f - fSlope;
SCREENMAN->SystemMessage( AUTOSYNC_CORRECTION_APPLIED.GetValue() );
@@ -296,7 +296,7 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
{
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_fBeat0OffsetInSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds, 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
@@ -309,10 +309,10 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
}
}
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments.size(); i++ )
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments.size(); i++ )
{
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_BPMSegments[i].GetBPM(), 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments[i].GetBPM(), 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments[i].GetBPM(), 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
@@ -330,10 +330,10 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
}
}
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_Timing.m_StopSegments.size(); i++ )
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments.size(); i++ )
{
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_StopSegments[i].m_fStopSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_StopSegments[i].m_fStopSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments[i].m_fStopSeconds, 0.001f );
float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f )
+79 -22
View File
@@ -67,13 +67,13 @@ static float GetNoteFieldHeight( const PlayerState* pPlayerState )
namespace
{
float g_fExpandSeconds = 0;
struct PerPlayerData
{
float m_fMinTornadoX[MAX_COLS_PER_PLAYER];
float m_fMaxTornadoX[MAX_COLS_PER_PLAYER];
float m_fInvertDistance[MAX_COLS_PER_PLAYER];
float m_fBeatFactor;
float m_fExpandSeconds;
};
PerPlayerData g_EffectData[NUM_PLAYERS];
};
@@ -82,22 +82,25 @@ void ArrowEffects::Update()
{
const Style* pStyle = GAMESTATE->GetCurrentStyle();
{
static float fLastTime = 0;
float fTime = RageTimer::GetTimeSinceStartFast();
if( !GAMESTATE->m_bFreeze || !GAMESTATE->m_bDelay )
{
g_fExpandSeconds += fTime - fLastTime;
g_fExpandSeconds = fmodf( g_fExpandSeconds, PI*2 );
}
fLastTime = fTime;
}
FOREACH_PlayerNumber( pn )
{
const Style::ColumnInfo* pCols = pStyle->m_ColumnInfo[pn];
const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming
? GAMESTATE->m_pPlayerState[pn]->m_Position : GAMESTATE->m_Position;
PerPlayerData &data = g_EffectData[pn];
{
static float fLastTime = 0;
float fTime = RageTimer::GetTimeSinceStartFast();
if( !position.m_bFreeze || !position.m_bDelay )
{
data.m_fExpandSeconds += fTime - fLastTime;
data.m_fExpandSeconds = fmodf( data.m_fExpandSeconds, PI*2 );
}
fLastTime = fTime;
}
// Update Tornado
for( int iColNum = 0; iColNum < MAX_COLS_PER_PLAYER; ++iColNum )
{
@@ -176,7 +179,7 @@ void ArrowEffects::Update()
// Update Beat
do {
float fAccelTime = 0.2f, fTotalTime = 0.5f;
float fBeat = GAMESTATE->m_fSongBeatVisible + fAccelTime;
float fBeat = position.m_fSongBeatVisible + fAccelTime;
const bool bEvenBeat = ( int(fBeat) % 2 ) != 0;
@@ -208,6 +211,50 @@ void ArrowEffects::Update()
}
}
float GetSpeedMultiplier( float fSongBeat, float fMusicSeconds, const TimingData &tim )
{
if( tim.m_SpeedSegments.size() == 0 )
return 1.0;
const int index = tim.GetSpeedSegmentIndexAtBeat( fSongBeat );
const SpeedSegment &seg = tim.m_SpeedSegments[index];
float fStartBeat = NoteRowToBeat(seg.m_iStartRow);
float fStartTime = tim.GetElapsedTimeFromBeat( fStartBeat ) - tim.GetDelayAtBeat( fStartBeat );
float fEndTime;
float fCurTime = fMusicSeconds;
if( seg.m_usMode == 1 ) // seconds
{
fEndTime = fStartTime + seg.m_fWait;
}
else
{
fEndTime = tim.GetElapsedTimeFromBeat( fStartBeat + seg.m_fWait ) - tim.GetDelayAtBeat( fStartBeat + seg.m_fWait );
}
if( ( index == 0 && tim.m_SpeedSegments[0].m_fWait > 0.0 ) && fCurTime < fStartTime )
{
return 1.0;
}
else if( fEndTime >= fCurTime && ( index > 0 || tim.m_SpeedSegments[0].m_fWait > 0.0 ) )
{
const float fPriorSpeed = ( index == 0 ? 1 : tim.m_SpeedSegments[index - 1].m_fPercent );
float fTimeUsed = fCurTime - fStartTime;
float fDuration = fEndTime - fStartTime;
float fRatioUsed = fDuration == 0.0 ? 1 : fTimeUsed / fDuration;
float fDistance = fPriorSpeed - seg.m_fPercent;
float fRatioNeed = fRatioUsed * -fDistance;
return (fPriorSpeed + fRatioNeed);
}
else
{
return seg.m_fPercent;
}
}
/* For visibility testing: if bAbsolute is false, random modifiers must return
* the minimum possible scroll speed. */
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
@@ -217,21 +264,27 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
bIsPastPeakOut = true;
float fYOffset = 0;
const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming
? pPlayerState->m_Position : GAMESTATE->m_Position;
float fSongBeat = position.m_fSongBeatVisible;
Steps *pCurSteps = GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber];
/* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or
* entirely time spacing (respectively). Occasionally, we tween between them. */
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 1.0f )
{
float fSongBeat = GAMESTATE->m_fSongBeatVisible;
float fBeatsUntilStep = fNoteBeat - fSongBeat;
float fYOffsetBeatSpacing = fBeatsUntilStep;
fYOffset += fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing);
float fSpeedMultiplier = ( GAMESTATE->m_bInStepEditor || !GAMESTATE->m_bIsUsingStepTiming ) ? 1.0 : GetSpeedMultiplier( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible, pCurSteps->m_Timing );
fYOffset += fSpeedMultiplier * fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing);
}
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 0.0f )
{
float fSongSeconds = GAMESTATE->m_fMusicSecondsVisible;
float fNoteSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fNoteBeat);
float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible;
float fNoteSeconds = pCurSteps->m_Timing.GetElapsedTimeFromBeat(fNoteBeat);
float fSecondsUntilStep = fNoteSeconds - fSongSeconds;
float fBPM = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollBPM;
float fBPS = fBPM/60.f;
@@ -242,7 +295,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
// TODO: If we allow noteskins to have metricable row spacing
// (per issue 24), edit this to reflect that. -aj
fYOffset *= ARROW_SPACING;
// don't mess with the arrows after they've crossed 0
if( fYOffset < 0 )
return fYOffset * pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
@@ -288,6 +341,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
// Factor in scroll speed
float fScrollSpeed = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fRandomSpeed > 0 && !bAbsolute )
{
// Generate a deterministically "random" speed for each arrow.
@@ -307,7 +361,10 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 )
{
float fExpandMultiplier = SCALE( RageFastCos(g_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY),
// TODO: Don't index by PlayerNumber.
PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber];
float fExpandMultiplier = SCALE( RageFastCos(data.m_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY),
EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH,
EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH );
fScrollSpeed *= SCALE( fAccels[PlayerOptions::ACCEL_EXPAND],
@@ -511,7 +568,7 @@ float ArrowEffects::GetRotationZ( const PlayerState* pPlayerState, float fNoteBe
// As usual, enable dizzy hold heads at your own risk. -Wolfman2000
if( fEffects[PlayerOptions::EFFECT_DIZZY] != 0 && ( DIZZY_HOLD_HEADS || !bIsHoldHead ) )
{
const float fSongBeat = GAMESTATE->m_fSongBeatVisible;
const float fSongBeat = pPlayerState->m_Position.m_fSongBeatVisible;
float fDizzyRotation = fNoteBeat - fSongBeat;
fDizzyRotation *= fEffects[PlayerOptions::EFFECT_DIZZY];
fDizzyRotation = fmodf( fDizzyRotation, 2*PI );
@@ -528,7 +585,7 @@ float ArrowEffects::ReceptorGetRotationZ( const PlayerState* pPlayerState )
if( fEffects[PlayerOptions::EFFECT_CONFUSION] != 0 )
{
float fConfRotation = GAMESTATE->m_fSongBeatVisible;
float fConfRotation = pPlayerState->m_Position.m_fSongBeatVisible;
fConfRotation *= fEffects[PlayerOptions::EFFECT_CONFUSION];
fConfRotation = fmodf( fConfRotation, 2*PI );
fConfRotation *= -180/PI;
@@ -680,7 +737,7 @@ float ArrowEffects::GetBrightness( const PlayerState* pPlayerState, float fNoteB
if( GAMESTATE->IsEditing() )
return 1;
float fSongBeat = GAMESTATE->m_fSongBeatVisible;
float fSongBeat = pPlayerState->m_Position.m_fSongBeatVisible;
float fBeatsUntilStep = fNoteBeat - fSongBeat;
float fBrightness = SCALE( fBeatsUntilStep, 0, -1, 1.f, 0.f );
+8 -6
View File
@@ -11,9 +11,10 @@ void Attack::GetAttackBeats( const Song *pSong, float &fStartBeat, float &fEndBe
{
ASSERT( pSong );
ASSERT_M( fStartSecond >= 0, ssprintf("StartSecond: %f",fStartSecond) );
fStartBeat = pSong->GetBeatFromElapsedTime( fStartSecond );
fEndBeat = pSong->GetBeatFromElapsedTime( fStartSecond+fSecsRemaining );
const TimingData &timing = pSong->m_SongTiming;
fStartBeat = timing.GetBeatFromElapsedTime( fStartSecond );
fEndBeat = timing.GetBeatFromElapsedTime( fStartSecond+fSecsRemaining );
}
/* Get the range for an attack that's being applied in realtime, eg. during battle
@@ -31,12 +32,13 @@ void Attack::GetRealtimeAttackBeats( const Song *pSong, const PlayerState* pPlay
ASSERT( pSong );
/* If reasonable, push the attack forward 8 beats so that notes on screen don't change suddenly. */
fStartBeat = min( GAMESTATE->m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat );
fStartBeat = min( GAMESTATE->m_Position.m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat );
fStartBeat = truncf(fStartBeat)+1;
const float lStartSecond = pSong->GetElapsedTimeFromBeat( fStartBeat );
const TimingData &timing = pSong->m_SongTiming;
const float lStartSecond = timing.GetElapsedTimeFromBeat( fStartBeat );
const float fEndSecond = lStartSecond + fSecsRemaining;
fEndBeat = pSong->GetBeatFromElapsedTime( fEndSecond );
fEndBeat = timing.GetBeatFromElapsedTime( fEndSecond );
fEndBeat = truncf(fEndBeat)+1;
// loading the course should have caught this.
+1 -1
View File
@@ -99,7 +99,7 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain )
if( tn[pn].iKeysoundIndex >= 0 )
{
RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex];
float fSeconds = pSong->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency();
float fSeconds = GAMESTATE->m_pCurSteps[pn]->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency();
float fPan = 0;
if( !bSoundIsGlobal )
+1 -1
View File
@@ -277,7 +277,7 @@ SongBPMDisplay::SongBPMDisplay()
void SongBPMDisplay::Update( float fDeltaTime )
{
float fGameStateBPM = GAMESTATE->m_fCurBPS * 60.0f;
float fGameStateBPM = GAMESTATE->m_Position.m_fCurBPS * 60.0f;
if( m_fLastGameStateBPM != fGameStateBPM )
{
m_fLastGameStateBPM = fGameStateBPM;
+5 -5
View File
@@ -419,7 +419,7 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
int iStartRow = BeatToNoteRow(fFirstBeat);
int iEndRow = BeatToNoteRow(fEndBeat);
const TimingData &timing = m_pSong->m_Timing;
const TimingData &timing = m_pSong->m_SongTiming;
// change BG every time signature change or 4 measures
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
@@ -697,7 +697,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
float fBeat, fBPS, fThrowAway;
bool bFreeze;
int iThrowAway;
pSong->m_Timing.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );
pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );
// Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it.
const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
@@ -762,7 +762,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
m_pCurrentBGA->PlayCommand( "GainFocus" );
/* How much time of this BGA have we skipped? (This happens with SetSeconds.) */
const float fStartSecond = pSong->m_Timing.GetElapsedTimeFromBeat( change.m_fStartBeat );
const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( change.m_fStartBeat );
/* This is affected by the music rate. */
fDeltaTime = fCurrentTime - fStartSecond;
@@ -800,9 +800,9 @@ void BackgroundImpl::Update( float fDeltaTime )
FOREACH_BackgroundLayer( i )
{
Layer &layer = m_Layer[i];
layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_fMusicSeconds, m_mapNameToTransition );
layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_Position.m_fMusicSeconds, m_mapNameToTransition );
}
m_fLastMusicSeconds = GAMESTATE->m_fMusicSeconds;
m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
}
void BackgroundImpl::DrawPrimitives()
+8 -8
View File
@@ -191,7 +191,7 @@ void BeginnerHelper::ShowStepCircle( PlayerNumber pn, int CSTEP )
m_sStepCircle[pn][isc].StopEffect();
m_sStepCircle[pn][isc].SetZoom( 2 );
m_sStepCircle[pn][isc].StopTweening();
m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_fCurBPS/3, TWEEN_LINEAR );
m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_Position.m_fCurBPS/3, TWEEN_LINEAR );
m_sStepCircle[pn][isc].SetZoom( 0 );
}
@@ -318,19 +318,19 @@ void BeginnerHelper::Step( PlayerNumber pn, int CSTEP )
ShowStepCircle( pn, ST_DOWN );
m_pDancer[pn]->StopTweening();
m_pDancer[pn]->PlayAnimation( "Step-JUMPLR", 1.5f );
m_pDancer[pn]->BeginTweening( GAMESTATE->m_fCurBPS/8, TWEEN_LINEAR );
m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS/8, TWEEN_LINEAR );
m_pDancer[pn]->SetRotationY( 90 );
m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_fCurBPS * 2) ); //sleep between jump-frames
m_pDancer[pn]->BeginTweening( GAMESTATE->m_fCurBPS /6, TWEEN_LINEAR );
m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_Position.m_fCurBPS * 2) ); //sleep between jump-frames
m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /6, TWEEN_LINEAR );
m_pDancer[pn]->SetRotationY( 0 );
break;
}
m_sFlash.StopEffect();
m_sFlash.StopTweening();
m_sFlash.Sleep( GAMESTATE->m_fCurBPS/16 );
m_sFlash.Sleep( GAMESTATE->m_Position.m_fCurBPS/16 );
m_sFlash.SetDiffuseAlpha( 1 );
m_sFlash.BeginTweening( 1/GAMESTATE->m_fCurBPS * 0.5f );
m_sFlash.BeginTweening( 1/GAMESTATE->m_Position.m_fCurBPS * 0.5f );
m_sFlash.SetDiffuseAlpha( 0 );
}
@@ -340,7 +340,7 @@ void BeginnerHelper::Update( float fDeltaTime )
return;
// the row we want to check on this update
int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat + 0.4f );
int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat + 0.4f );
FOREACH_EnabledPlayer( pn )
{
for( int iRow=m_iLastRowChecked; iRow<iCurRow; iRow++ )
@@ -369,7 +369,7 @@ void BeginnerHelper::Update( float fDeltaTime )
m_pDancePad->Update( fDeltaTime );
m_sFlash.Update( fDeltaTime );
float beat = fDeltaTime*GAMESTATE->m_fCurBPS;
float beat = fDeltaTime*GAMESTATE->m_Position.m_fCurBPS;
// If this is not a human player, the dancer is not shown
FOREACH_HumanPlayer( pu )
{
+7 -7
View File
@@ -175,7 +175,7 @@ int Neg1OrPos1() { return RandomInt( 2 ) ? -1 : +1; }
void DancingCharacters::Update( float fDelta )
{
if( GAMESTATE->m_bFreeze || GAMESTATE->m_bDelay )
if( GAMESTATE->m_Position.m_bFreeze || GAMESTATE->m_Position.m_bDelay )
{
// spin the camera Matrix-style
m_CameraPanYStart += fDelta*40;
@@ -184,7 +184,7 @@ void DancingCharacters::Update( float fDelta )
else
{
// make the characters move
float fBPM = GAMESTATE->m_fCurBPS*60;
float fBPM = GAMESTATE->m_Position.m_fCurBPS*60;
float fUpdateScale = SCALE( fBPM, 60.f, 300.f, 0.75f, 1.5f );
CLAMP( fUpdateScale, 0.75f, 1.5f );
@@ -209,8 +209,8 @@ void DancingCharacters::Update( float fDelta )
}
bWasGameplayStarting = bGameplayStarting;
static float fLastBeat = GAMESTATE->m_fSongBeat;
float fThisBeat = GAMESTATE->m_fSongBeat;
static float fLastBeat = GAMESTATE->m_Position.m_fSongBeat;
float fThisBeat = GAMESTATE->m_Position.m_fSongBeat;
if( fLastBeat < GAMESTATE->m_pCurSong->m_fFirstBeat &&
fThisBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat )
{
@@ -220,7 +220,7 @@ void DancingCharacters::Update( float fDelta )
fLastBeat = fThisBeat;
// time for a new sweep?
if( GAMESTATE->m_fSongBeat > m_fThisCameraEndBeat )
if( GAMESTATE->m_Position.m_fSongBeat > m_fThisCameraEndBeat )
{
if( RandomInt(6) >= 4 )
{
@@ -248,7 +248,7 @@ void DancingCharacters::Update( float fDelta )
m_fLookAtHeight = CAMERA_STILL_LOOK_AT_HEIGHT;
}
int iCurBeat = (int)GAMESTATE->m_fSongBeat;
int iCurBeat = (int)GAMESTATE->m_Position.m_fSongBeat;
iCurBeat -= iCurBeat%8;
m_fThisCameraStartBeat = (float) iCurBeat;
@@ -313,7 +313,7 @@ void DancingCharacters::DrawPrimitives()
if(m_fThisCameraStartBeat == m_fThisCameraEndBeat)
fPercentIntoSweep = 0;
else
fPercentIntoSweep = SCALE(GAMESTATE->m_fSongBeat, m_fThisCameraStartBeat, m_fThisCameraEndBeat, 0.f, 1.f );
fPercentIntoSweep = SCALE(GAMESTATE->m_Position.m_fSongBeat, m_fThisCameraStartBeat, m_fThisCameraEndBeat, 0.f, 1.f );
float fCameraPanY = SCALE( fPercentIntoSweep, 0.f, 1.f, m_CameraPanYStart, m_CameraPanYEnd );
float fCameraHeight = SCALE( fPercentIntoSweep, 0.f, 1.f, m_fCameraHeightStart, m_fCameraHeightEnd );
+8 -8
View File
@@ -41,9 +41,9 @@ void Foreground::LoadFromSong( const Song *pSong )
bga.m_fStartBeat = change.m_fStartBeat;
bga.m_bFinished = false;
const float fStartSecond = pSong->m_Timing.GetElapsedTimeFromBeat( bga.m_fStartBeat );
const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( bga.m_fStartBeat );
const float fStopSecond = fStartSecond + bga.m_bga->GetTweenTimeLeft();
bga.m_fStopBeat = pSong->m_Timing.GetBeatFromElapsedTime( fStopSecond );
bga.m_fStopBeat = pSong->m_SongTiming.GetBeatFromElapsedTime( fStopSecond );
bga.m_bga->SetVisible( false );
@@ -65,7 +65,7 @@ void Foreground::Update( float fDeltaTime )
{
LoadedBGA &bga = m_BGAnimations[i];
if( GAMESTATE->m_fSongBeat < bga.m_fStartBeat )
if( GAMESTATE->m_Position.m_fSongBeat < bga.m_fStartBeat )
{
// The animation hasn't started yet.
continue;
@@ -82,12 +82,12 @@ void Foreground::Update( float fDeltaTime )
bga.m_bga->SetVisible( true );
bga.m_bga->PlayCommand( "On" );
const float fStartSecond = m_pSong->m_Timing.GetElapsedTimeFromBeat( bga.m_fStartBeat );
lDeltaTime = GAMESTATE->m_fMusicSeconds - fStartSecond;
const float fStartSecond = m_pSong->m_SongTiming.GetElapsedTimeFromBeat( bga.m_fStartBeat );
lDeltaTime = GAMESTATE->m_Position.m_fMusicSeconds - fStartSecond;
}
else
{
lDeltaTime = GAMESTATE->m_fMusicSeconds - m_fLastMusicSeconds;
lDeltaTime = GAMESTATE->m_Position.m_fMusicSeconds - m_fLastMusicSeconds;
}
// This shouldn't go down, but be safe:
@@ -95,7 +95,7 @@ void Foreground::Update( float fDeltaTime )
bga.m_bga->Update( lDeltaTime / fRate );
if( GAMESTATE->m_fSongBeat > bga.m_fStopBeat )
if( GAMESTATE->m_Position.m_fSongBeat > bga.m_fStopBeat )
{
// Finished.
bga.m_bga->SetVisible( false );
@@ -104,7 +104,7 @@ void Foreground::Update( float fDeltaTime )
}
}
m_fLastMusicSeconds = GAMESTATE->m_fMusicSeconds;
m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
}
/*
+14 -14
View File
@@ -146,7 +146,7 @@ static void StartMusic( MusicToPlay &ToPlay )
SSCLoader::LoadFromSSCFile(ToPlay.m_sTimingFile, song) )
{
ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_Timing;
ToPlay.m_TimingData = song.m_SongTiming;
// get cabinet lights if any
Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet );
if( pStepsCabinetLights )
@@ -156,7 +156,7 @@ static void StartMusic( MusicToPlay &ToPlay )
SMLoader::LoadFromSMFile(ToPlay.m_sTimingFile, song) )
{
ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_Timing;
ToPlay.m_TimingData = song.m_SongTiming;
// get cabinet lights if any
Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet );
if( pStepsCabinetLights )
@@ -201,7 +201,7 @@ static void StartMusic( MusicToPlay &ToPlay )
{
/* This song has no real timing data. The offset is arbitrary. Change it so
* the beat will line up to where we are now, so we don't have to delay. */
float fDestBeat = fmodfp( GAMESTATE->m_fSongBeatNoOffset, 1 );
float fDestBeat = fmodfp( GAMESTATE->m_Position.m_fSongBeatNoOffset, 1 );
float fTime = NewMusic->m_NewTiming.GetElapsedTimeFromBeatNoOffset( fDestBeat );
NewMusic->m_NewTiming.m_fBeat0OffsetInSeconds = fTime;
@@ -223,7 +223,7 @@ static void StartMusic( MusicToPlay &ToPlay )
* common when starting a precached sound, but our sound isn't, so it'll
* probably take a little longer. Nudge the latency up. */
const float fPresumedLatency = SOUNDMAN->GetPlayLatency() + 0.040f;
const float fCurSecond = GAMESTATE->m_fMusicSeconds + fPresumedLatency;
const float fCurSecond = GAMESTATE->m_Position.m_fMusicSeconds + fPresumedLatency;
const float fCurBeat = g_Playing->m_Timing.GetBeatFromElapsedTimeNoOffset( fCurSecond );
/* The beat that the new sound will start on. */
@@ -236,9 +236,9 @@ static void StartMusic( MusicToPlay &ToPlay )
const float fSecondToStartOn = g_Playing->m_Timing.GetElapsedTimeFromBeatNoOffset( fCurBeatToStartOn );
const float fMaximumDistance = 2;
const float fDistance = min( fSecondToStartOn - GAMESTATE->m_fMusicSeconds, fMaximumDistance );
const float fDistance = min( fSecondToStartOn - GAMESTATE->m_Position.m_fMusicSeconds, fMaximumDistance );
when = GAMESTATE->m_LastBeatUpdate + fDistance;
when = GAMESTATE->m_Position.m_LastBeatUpdate + fDistance;
}
/* Important: don't hold the mutex while we load and seek the actual sound. */
@@ -552,8 +552,8 @@ void GameSoundManager::Update( float fDeltaTime )
if( !g_Playing->m_Music->IsPlaying() )
{
/* There's no song playing. Fake it. */
CHECKPOINT_M( ssprintf("%f, delta %f", GAMESTATE->m_fMusicSeconds, fDeltaTime) );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
CHECKPOINT_M( ssprintf("%f, delta %f", GAMESTATE->m_Position.m_fMusicSeconds, fDeltaTime) );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_Position.m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
return;
}
@@ -570,8 +570,8 @@ void GameSoundManager::Update( float fDeltaTime )
//
if( PREFSMAN->m_bLogSkips && !g_Playing->m_bTimingDelayed )
{
const float fExpectedTimePassed = (tm - GAMESTATE->m_LastBeatUpdate) * g_Playing->m_Music->GetPlaybackRate();
const float fSoundTimePassed = fSeconds - GAMESTATE->m_fMusicSeconds;
const float fExpectedTimePassed = (tm - GAMESTATE->m_Position.m_LastBeatUpdate) * g_Playing->m_Music->GetPlaybackRate();
const float fSoundTimePassed = fSeconds - GAMESTATE->m_Position.m_fMusicSeconds;
const float fDiff = fExpectedTimePassed - fSoundTimePassed;
static RString sLastFile = "";
@@ -580,7 +580,7 @@ void GameSoundManager::Update( float fDeltaTime )
/* If fSoundTimePassed < 0, the sound has probably looped. */
if( sLastFile == ThisFile && fSoundTimePassed >= 0 && fabsf(fDiff) > 0.003f )
LOG->Trace("Song position skip in %s: expected %.3f, got %.3f (cur %f, prev %f) (%.3f difference)",
Basename(ThisFile).c_str(), fExpectedTimePassed, fSoundTimePassed, fSeconds, GAMESTATE->m_fMusicSeconds, fDiff );
Basename(ThisFile).c_str(), fExpectedTimePassed, fSoundTimePassed, fSeconds, GAMESTATE->m_Position.m_fMusicSeconds, fDiff );
sLastFile = ThisFile;
}
@@ -599,7 +599,7 @@ void GameSoundManager::Update( float fDeltaTime )
{
/* We're still waiting for the new sound to start playing, so keep using the
* old timing data and fake the time. */
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_Position.m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
}
else
{
@@ -614,7 +614,7 @@ void GameSoundManager::Update( float fDeltaTime )
{
static int iBeatLastCrossed = 0;
float fSongBeat = GAMESTATE->m_fSongBeat;
float fSongBeat = GAMESTATE->m_Position.m_fSongBeat;
int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow );
@@ -638,7 +638,7 @@ void GameSoundManager::Update( float fDeltaTime )
NoteData &lights = g_Playing->m_Lights;
if( lights.GetNumTracks() > 0 ) // lights data was loaded
{
const float fSongBeat = GAMESTATE->m_fLightSongBeat;
const float fSongBeat = GAMESTATE->m_Position.m_fLightSongBeat;
const int iSongRow = BeatToNoteRowNotRounded( fSongBeat );
static int iRowLastCrossed = 0;
+33 -66
View File
@@ -127,7 +127,9 @@ GameState::GameState() :
m_pEditSourceSteps( Message_EditSourceStepsChanged ),
m_stEditSource( Message_EditSourceStepsTypeChanged ),
m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ),
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged )
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ),
m_bIsUsingStepTiming( true ),
m_bInStepEditor( false )
{
g_pImpl = new GameStateImpl;
@@ -893,22 +895,15 @@ const float GameState::MUSIC_SECONDS_INVALID = -5000.0f;
void GameState::ResetMusicStatistics()
{
m_fMusicSeconds = 0; // MUSIC_SECONDS_INVALID;
// todo: move me to FOREACH_EnabledPlayer( p ) after [NUM_PLAYERS]ing
m_fSongBeat = 0;
m_fSongBeatNoOffset = 0;
m_fCurBPS = 10;
//m_bStop = false;
m_bFreeze = false;
m_bDelay = false;
m_iWarpBeginRow = -1; // Set to -1 because some song may want to warp to row 0. -aj
m_fWarpDestination = -1; // Set when a warp is encountered. also see above. -aj
m_fMusicSecondsVisible = 0;
m_fSongBeatVisible = 0;
m_Position.Reset();
Actor::SetBGMTime( 0, 0, 0, 0 );
FOREACH_PlayerNumber( p )
{
m_pPlayerState[p]->ClearHopoState();
m_pPlayerState[p]->m_Position.Reset();
}
}
void GameState::ResetStageStatistics()
@@ -947,57 +942,23 @@ void GameState::ResetStageStatistics()
m_iStageSeed = rand();
}
static Preference<float> g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f );
void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp )
void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp, bool bUpdatePlayers )
{
if( !timestamp.IsZero() )
m_LastBeatUpdate = timestamp;
else
m_LastBeatUpdate.Touch();
m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp );
// xxx testing: only do this on monotune survivor
/*
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
LOG->Trace( ssprintf("[GameState::UpdateSongPosition] cur BPS = %f, fPositionSeconds = %f",m_fCurBPS,fPositionSeconds) );
*/
timing.GetBeatAndBPSFromElapsedTime( fPositionSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze, m_bDelay, m_iWarpBeginRow, m_fWarpDestination );
// "Crash reason : -243478.890625 -48695.773438"
ASSERT_M( m_fSongBeat > -2000, ssprintf("Song beat %f at %f seconds", m_fSongBeat, fPositionSeconds) );
m_fMusicSeconds = fPositionSeconds;
m_fLightSongBeat = timing.GetBeatFromElapsedTime( fPositionSeconds + g_fLightsAheadSeconds );
m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
float fThrowAway, fThrowAway2;
bool bThrowAway;
int iThrowAway;
timing.GetBeatAndBPSFromElapsedTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fThrowAway, bThrowAway, bThrowAway, iThrowAway, fThrowAway2 );
/*
// xxx testing: only do this on monotune survivor
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
if( bUpdatePlayers )
{
// and only do it in the known negative bpm region. HACKITY HACK
if(m_fSongBeat >= 445.490f && m_fSongBeat <= 453.72f)
FOREACH_EnabledPlayer( pn )
{
LOG->Trace( ssprintf("fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
//LOG->Trace( ssprintf("Music seconds visible %f = fPositionSeconds %f - g_fVisualDelaySeconds %f", m_fMusicSecondsVisible,fPositionSeconds,g_fVisualDelaySeconds.Get()) );
}
else if(m_fSongBeat == 445.500f)
{
LOG->Trace( ssprintf("[beat 445.500] fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
if( m_pCurSteps[pn] )
{
m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, m_pCurSteps[pn]->m_Timing, timestamp );
Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset );
}
}
}
*/
Actor::SetBGMTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fPositionSeconds, m_fSongBeatNoOffset );
Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset );
// LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze );
}
@@ -2291,11 +2252,16 @@ public:
DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() )
DEFINE_METHOD( IsEventMode, IsEventMode() )
DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() )
DEFINE_METHOD( GetSongBeat, m_fSongBeat )
DEFINE_METHOD( GetSongBeatVisible, m_fSongBeatVisible )
DEFINE_METHOD( GetSongBPS, m_fCurBPS )
DEFINE_METHOD( GetSongFreeze, m_bFreeze )
DEFINE_METHOD( GetSongDelay, m_bDelay )
/*DEFINE_METHOD( GetSongBeat, m_Position.m_fSongBeat )
DEFINE_METHOD( GetSongBeatVisible, m_Position.m_fSongBeatVisible )
DEFINE_METHOD( GetSongBPS, m_Position.m_fCurBPS )
DEFINE_METHOD( GetSongFreeze, m_Position.m_bFreeze )
DEFINE_METHOD( GetSongDelay, m_Position.m_bDelay )*/
static int GetSongPosition( T* p, lua_State *L )
{
p->m_Position.PushSelf(L);
return 1;
}
DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn )
DEFINE_METHOD( GetCoins, m_iCoins )
DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check<PlayerNumber>(L, 1)] )
@@ -2434,7 +2400,7 @@ public:
static int JoinPlayer( T* p, lua_State *L ) { p->JoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; }
static int UnjoinPlayer( T* p, lua_State *L ) { p->UnjoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; }
static int GetSongPercent( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongPercent(FArg(1))); return 1; }
DEFINE_METHOD( GetCurMusicSeconds, m_fMusicSeconds )
DEFINE_METHOD( GetCurMusicSeconds, m_Position.m_fMusicSeconds )
DEFINE_METHOD( GetWorkoutGoalComplete, m_bWorkoutGoalComplete )
static int GetCharacter( T* p, lua_State *L ) { p->m_pCurCharacters[Enum::Check<PlayerNumber>(L, 1)]->PushSelf(L); return 1; }
@@ -2500,11 +2466,12 @@ public:
ADD_METHOD( GetHardestStepsDifficulty );
ADD_METHOD( IsEventMode );
ADD_METHOD( GetNumPlayersEnabled );
ADD_METHOD( GetSongBeat );
/*ADD_METHOD( GetSongBeat );
ADD_METHOD( GetSongBeatVisible );
ADD_METHOD( GetSongBPS );
ADD_METHOD( GetSongFreeze );
ADD_METHOD( GetSongDelay );
ADD_METHOD( GetSongDelay );*/
ADD_METHOD( GetSongPosition );
ADD_METHOD( GetGameplayLeadIn );
ADD_METHOD( GetCoins );
ADD_METHOD( IsSideJoined );
+44 -29
View File
@@ -10,6 +10,7 @@
#include "RageTimer.h"
#include "PlayerOptions.h"
#include "SongOptions.h"
#include "SongPosition.h"
#include "Preference.h"
#include <map>
@@ -43,7 +44,8 @@ public:
void ResetPlayer( PlayerNumber pn );
void ApplyCmdline(); // called by Reset
void ApplyGameCommand( const RString &sCommand, PlayerNumber pn=PLAYER_INVALID );
void BeginGame(); // called when first player joins
/** @brief Start the game when the first player joins in. */
void BeginGame();
void JoinPlayer( PlayerNumber pn );
void UnjoinPlayer( PlayerNumber pn );
bool JoinInput( PlayerNumber pn );
@@ -55,13 +57,25 @@ public:
bool HaveProfileToSave();
void SaveLocalData();
void LoadCurrentSettingsFromProfile( PlayerNumber pn );
void SaveCurrentSettingsToProfile( PlayerNumber pn ); // called at the beginning of each stage
/**
* @brief Save the specified player's settings to his/her profile.
*
* This is called at the beginning of each stage.
* @param pn the PlayerNumber to save the stats to. */
void SaveCurrentSettingsToProfile( PlayerNumber pn );
Song* GetDefaultSong() const;
void Update( float fDelta );
// Main state info
void SetCurGame( const Game *pGame ); // Call this instead of m_pCurGame.Set to make sure PREFSMAN->m_sCurrentGame stays in sync
/**
* @brief State what the current game is.
*
* Call this instead of m_pCurGame.Set to make sure that
* PREFSMAN->m_sCurrentGame stays in sync.
* @param pGame the game to start using. */
void SetCurGame( const Game *pGame );
BroadcastOnChangePtr<const Game> m_pCurGame;
BroadcastOnChangePtr<const Style> m_pCurStyle;
/** @brief Determine which side is joined.
@@ -129,6 +143,9 @@ public:
bool IsCourseMode() const;
bool IsBattleMode() const; // not Rave
/**
* @brief Do we show the W1 timing judgment?
* @return true if we do, or false otherwise. */
bool ShowW1() const;
BroadcastOnChange<RString> m_sPreferredSongGroup; // GROUP_ALL denotes no preferred group
@@ -141,7 +158,11 @@ public:
SortOrder m_PreferredSortOrder; // used by MusicWheel
EditMode m_EditMode;
bool IsEditing() const { return m_EditMode != EditMode_Invalid; }
bool m_bDemonstrationOrJukebox; // ScreenGameplay does special stuff when this is true
/**
* @brief Are we in the demonstration or jukebox mode?
*
* ScreenGameplay often does special things when this is set to true. */
bool m_bDemonstrationOrJukebox;
bool m_bJukeboxUsesModifiers;
int m_iNumStagesOfThisSong;
/**
@@ -194,29 +215,9 @@ public:
bool m_bBackedOutOfFinalStage;
// Music statistics:
// Arcade - the current stage (one song).
// Oni/Endless - a single song in a course.
// Let a lot of classes access this info here so they don't have to keep their own copies.
// todo: [NUM_PLAYERS] this for split bpm lolol -aj
float m_fMusicSeconds; // time into the current song, not scaled by music rate
float m_fSongBeat;
float m_fSongBeatNoOffset;
float m_fCurBPS;
float m_fLightSongBeat; // g_fLightsFalloffSeconds ahead
//bool m_bStop; // in the middle of a stop (freeze or delay)
/** @brief A flag to determine if we're in the middle of a freeze/stop. */
bool m_bFreeze;
/** @brief A flag to determine if we're in the middle of a delay (Pump style stop). */
bool m_bDelay;
/** @brief The row used to start a warp. */
int m_iWarpBeginRow;
/** @brief The beat to warp to afterwards. */
float m_fWarpDestination;
RageTimer m_LastBeatUpdate; // time of last m_fSongBeat, etc. update
BroadcastOnChange<bool> m_bGameplayLeadIn;
SongPosition m_Position;
float m_fMusicSecondsVisible;
float m_fSongBeatVisible;
BroadcastOnChange<bool> m_bGameplayLeadIn;
// if re-adding noteskin changes in courses, add functions and such here -aj
void GetAllUsedNoteSkins( vector<RString> &out ) const;
@@ -224,7 +225,7 @@ public:
static const float MUSIC_SECONDS_INVALID;
void ResetMusicStatistics(); // Call this when it's time to play a new song. Clears the values above.
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer );
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer, bool bUpdatePlayers = false );
float GetSongPercent( float beat ) const;
bool AllAreInDangerOrWorse() const;
@@ -259,8 +260,11 @@ public:
// Options stuff
ModsGroup<SongOptions> m_SongOptions;
// True if the current mode has changed the default NoteSkin, such as Edit/Sync Songs does.
// Note: any mode that wants to use it must set it
/**
* @brief Did the current game mode change the default Noteskin?
*
* This is true if it has: see Edit/Sync Songs for a common example.
* Note: any mode that wants to use this must set it explicitly. */
bool m_bDidModeChangeNoteSkin;
void GetDefaultPlayerOptions( PlayerOptions &po );
@@ -327,6 +331,17 @@ public:
Premium GetPremium() const;
// Edit stuff
/**
* @brief Is the game right now using Song timing or Steps timing?
*
* Different options are available depending on this setting. */
bool m_bIsUsingStepTiming;
/**
* @brief Are we presently in the Step Editor, where some rules apply differently?
*
* TODO: Find a better way to implement this. */
bool m_bInStepEditor;
BroadcastOnChange<StepsType> m_stEdit;
BroadcastOnChange<CourseDifficulty> m_cdEdit;
BroadcastOnChangePtr<Steps> m_pEditSourceSteps;
+6 -6
View File
@@ -25,9 +25,9 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
* will start coming out the speaker. Compensate for this by boosting fPositionSeconds
* ahead. This is just to make sure that we request the sound early enough for it to
* come out on time; the actual precise timing is handled by SetStartTime. */
float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f;
const TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;
const TimingData &timing = GAMESTATE->m_pCurSong->m_SongTiming;
const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) );
@@ -47,11 +47,11 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{
const float fTickBeat = NoteRowToBeat( iClapRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds;
float fSecondsUntil = fTickSecond - GAMESTATE->m_Position.m_fMusicSeconds;
fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */
RageSoundParams p;
p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
p.m_StartTime = GAMESTATE->m_Position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
m_soundAssistClap.Play( &p );
}
}
@@ -83,11 +83,11 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{
const float fTickBeat = NoteRowToBeat( iMetronomeRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds;
float fSecondsUntil = fTickSecond - GAMESTATE->m_Position.m_fMusicSeconds;
fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */
RageSoundParams p;
p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
p.m_StartTime = GAMESTATE->m_Position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
if( bIsMeasure )
m_soundAssistMetronomeMeasure.Play( &p );
else
+1 -1
View File
@@ -121,7 +121,7 @@ void Inventory::Update( float fDelta )
// use items if this player is CPU-controlled
if( m_pPlayerState->m_PlayerController != PC_HUMAN &&
GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
{
// every 1 seconds, try to use an item
int iLastSecond = (int)(RageTimer::GetTimeSinceStartFast() - fDelta);
+3 -3
View File
@@ -230,12 +230,12 @@ void LightsManager::Update( float fDeltaTime )
static float fLastBeat;
static int iLight;
if( fracf(GAMESTATE->m_fLightSongBeat) < fracf(fLastBeat) )
if( fracf(GAMESTATE->m_Position.m_fLightSongBeat) < fracf(fLastBeat) )
{
++iLight;
wrap( iLight, 4 );
}
fLastBeat = GAMESTATE->m_fLightSongBeat;
fLastBeat = GAMESTATE->m_Position.m_fLightSongBeat;
switch( iLight )
{
case 0: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT] = true; break;
@@ -405,7 +405,7 @@ void LightsManager::Update( float fDeltaTime )
// If not joined, has enough credits, and not too late to join, then
// blink the menu buttons rapidly so they'll press Start
{
int iBeat = (int)(GAMESTATE->m_fLightSongBeat*4);
int iBeat = (int)(GAMESTATE->m_Position.m_fLightSongBeat*4);
bool bBlinkOn = (iBeat%2)==0;
FOREACH_PlayerNumber( pn )
{
+4 -4
View File
@@ -41,9 +41,9 @@ void LyricDisplay::Update( float fDeltaTime )
return;
// If the song has changed (in a course), reset.
if( GAMESTATE->m_fMusicSeconds < m_fLastSecond )
if( GAMESTATE->m_Position.m_fMusicSeconds < m_fLastSecond )
Init();
m_fLastSecond = GAMESTATE->m_fMusicSeconds;
m_fLastSecond = GAMESTATE->m_Position.m_fMusicSeconds;
if( m_iCurLyricNumber >= GAMESTATE->m_pCurSong->m_LyricSegments.size() )
return;
@@ -51,7 +51,7 @@ void LyricDisplay::Update( float fDeltaTime )
const Song *pSong = GAMESTATE->m_pCurSong;
const float fStartTime = (pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime) - IN_LENGTH.GetValue();
if( GAMESTATE->m_fMusicSeconds < fStartTime )
if( GAMESTATE->m_Position.m_fMusicSeconds < fStartTime )
return;
// Clamp this lyric to the beginning of the next or the end of the music.
@@ -59,7 +59,7 @@ void LyricDisplay::Update( float fDeltaTime )
if( m_iCurLyricNumber+1 < GAMESTATE->m_pCurSong->m_LyricSegments.size() )
fEndTime = pSong->m_LyricSegments[m_iCurLyricNumber+1].m_fStartTime;
else
fEndTime = pSong->GetElapsedTimeFromBeat( pSong->m_fLastBeat );
fEndTime = pSong->m_SongTiming.GetElapsedTimeFromBeat( pSong->m_fLastBeat );
const float fDistance = fEndTime - pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime;
const float fTweenBufferTime = IN_LENGTH.GetValue() + OUT_LENGTH.GetValue();
+2 -1
View File
@@ -373,7 +373,8 @@ ScoreDisplayBattle.cpp ScoreDisplayBattle.h \
ScoreDisplayCalories.cpp ScoreDisplayCalories.h \
ScoreDisplayLifeTime.cpp ScoreDisplayLifeTime.h \
ScoreDisplayNormal.cpp ScoreDisplayNormal.h ScoreDisplayOni.cpp ScoreDisplayOni.h \
ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h
ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h \
SongPosition.cpp SongPosition.h
PCRE = ../extern/pcre/get.c ../extern/pcre/internal.h ../extern/pcre/maketables.c ../extern/pcre/pcre.c ../extern/pcre/pcre.h ../extern/pcre/study.c
EXTRA_DIST += ../extern/pcre/chartables.c
+3 -3
View File
@@ -73,9 +73,9 @@ void SongMeterDisplay::Update( float fDeltaTime )
{
if( GAMESTATE->m_pCurSong )
{
float fSongStartSeconds = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fFirstBeat );
float fSongEndSeconds = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
float fPercentPositionSong = SCALE( GAMESTATE->m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f );
float fSongStartSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fFirstBeat );
float fSongEndSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
float fPercentPositionSong = SCALE( GAMESTATE->m_Position.m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f );
CLAMP( fPercentPositionSong, 0, 1 );
SetPercent( fPercentPositionSong );
+2 -2
View File
@@ -2215,7 +2215,7 @@ void NoteDataUtil::AddTapAttacks( NoteData &nd, Song* pSong )
for( float sec=15; sec<pSong->m_fMusicLengthSeconds; sec+=30 )
{
float fBeat = pSong->GetBeatFromElapsedTime( sec );
float fBeat = pSong->m_SongTiming.GetBeatFromElapsedTime( sec );
int iBeat = (int)fBeat;
int iTrack = iBeat % nd.GetNumTracks(); // deterministically calculates track
TapNote tn(
@@ -2448,7 +2448,7 @@ void NoteDataUtil::SetHopoPossibleFlags( const Song *pSong, NoteData& ndInOut )
FOREACH_NONEMPTY_ROW_ALL_TRACKS( ndInOut, r )
{
float fBeat = NoteRowToBeat( r );
float fSeconds = pSong->GetElapsedTimeFromBeat( fBeat );
float fSeconds = pSong->m_SongTiming.GetElapsedTimeFromBeat( fBeat );
int iLastTapTrack = ndInOut.GetLastTrackWithTapOrHoldHead( r );
if( iLastTapTrack != -1 && fSeconds <= fLastRowMusicSeconds + g_fTimingWindowHopo )
+1 -1
View File
@@ -286,7 +286,7 @@ void NoteDisplay::Update( float fDeltaTime )
void NoteDisplay::SetActiveFrame( float fNoteBeat, Actor &actorToSet, float fAnimationLength, bool bVivid )
{
/* -inf ... inf */
float fBeatOrSecond = cache->m_bAnimationBasedOnBeats ? GAMESTATE->m_fSongBeat : GAMESTATE->m_fMusicSeconds;
float fBeatOrSecond = cache->m_bAnimationBasedOnBeats ? m_pPlayerState->m_Position.m_fSongBeat : m_pPlayerState->m_Position.m_fMusicSeconds;
/* -len ... +len */
float fPercentIntoAnimation = fmodf( fBeatOrSecond, fAnimationLength );
/* -1 ... 1 */
+216 -92
View File
@@ -32,6 +32,33 @@ static ThemeMetric<float> FADE_FAIL_TIME( "NoteField", "FadeFailTime" );
static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); }
static ThemeMetric1D<RString> ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS );
inline const TimingData *GetRealTiming(const PlayerState *pPlayerState)
{
if( GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber] != NULL )
return &GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber]->m_Timing;
return NULL;
}
inline const TimingData *GetDisplayedTiming(const PlayerState *pPlayerState)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
return &GAMESTATE->m_pCurSong->m_SongTiming;
return GetRealTiming(pPlayerState);
}
inline const SongPosition *GetRealPosition(const PlayerState *pPlayerState)
{
return &pPlayerState->m_Position;
}
inline const SongPosition *GetDisplayedPosition(const PlayerState *pPlayerState)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
return &GAMESTATE->m_Position;
return GetRealPosition(pPlayerState);
}
NoteField::NoteField()
{
m_pNoteData = NULL;
@@ -240,7 +267,7 @@ void NoteField::Update( float fDeltaTime )
ActorFrame::Update( fDeltaTime );
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
const float fCurrentBeat = GAMESTATE->m_fSongBeat;
const float fCurrentBeat = GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK
if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 )
{
@@ -427,6 +454,8 @@ static ThemeMetric<RageColor> TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignature
static ThemeMetric<RageColor> TICKCOUNT_COLOR ( "NoteField", "TickcountColor" );
static ThemeMetric<RageColor> COMBO_COLOR ( "NoteField", "ComboColor" );
static ThemeMetric<RageColor> LABEL_COLOR ( "NoteField", "LabelColor" );
static ThemeMetric<RageColor> SPEED_COLOR ( "NoteField", "SpeedColor" );
static ThemeMetric<RageColor> FAKE_COLOR ("NoteField", "FakeColor" );
static ThemeMetric<bool> BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" );
static ThemeMetric<bool> STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" );
static ThemeMetric<bool> DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" );
@@ -435,6 +464,8 @@ static ThemeMetric<bool> TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatu
static ThemeMetric<bool> TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" );
static ThemeMetric<bool> COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" );
static ThemeMetric<bool> LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" );
static ThemeMetric<bool> SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" );
static ThemeMetric<bool> FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" );
static ThemeMetric<float> BPM_OFFSETX ( "NoteField", "BPMOffsetX" );
static ThemeMetric<float> STOP_OFFSETX ( "NoteField", "StopOffsetX" );
static ThemeMetric<float> DELAY_OFFSETX ( "NoteField", "DelayOffsetX" );
@@ -443,6 +474,8 @@ static ThemeMetric<float> TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOf
static ThemeMetric<float> TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" );
static ThemeMetric<float> COMBO_OFFSETX ( "NoteField", "ComboOffsetX" );
static ThemeMetric<float> LABEL_OFFSETX ( "NoteField", "LabelOffsetX" );
static ThemeMetric<float> SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" );
static ThemeMetric<float> FAKE_OFFSETX ( "NoteField", "FakeOffsetX" );
void NoteField::DrawBPMText( const float fBeat, const float fBPM )
{
@@ -572,6 +605,40 @@ void NoteField::DrawLabelText( const float fBeat, RString sLabel )
m_textMeasureNumber.Draw();
}
void NoteField::DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
const float xBase = GetWidth()/2.f;
const float xOffset = SPEED_OFFSETX * fZoom;
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( SPEED_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( SPEED_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f\n%s\n%.3f", fPercent, (usMode == 1 ? "S" : "B"), fWait) );
m_textMeasureNumber.SetXY( (SPEED_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
void NoteField::DrawFakeText( const float fBeat, const float fNewBeat )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
const float xBase = GetWidth()/2.f;
const float xOffset = FAKE_OFFSETX * fZoom;
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( FAKE_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( FAKE_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f", fNewBeat) );
m_textMeasureNumber.SetXY( (FAKE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
void NoteField::DrawAttackText( const float fBeat, const Attack &attack )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
@@ -606,7 +673,7 @@ void NoteField::DrawBGChangeText( const float fBeat, const RString sNewBGName )
// change this probing to binary search
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels )
{
float fFirstBeatToDraw = GAMESTATE->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes.
float fFirstBeatToDraw = GetDisplayedPosition(pPlayerState)->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes.
/* In Boomerang, we'll usually have two sections of notes: before and after
* the peak. We always start drawing before the peak, and end after it, or
@@ -618,7 +685,7 @@ float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistance
bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0);
}
while( fFirstBeatToDraw < GAMESTATE->m_fSongBeat )
while( fFirstBeatToDraw < GetDisplayedPosition(pPlayerState)->m_fSongBeat )
{
bool bIsPastPeakYOffset;
float fPeakYOffset;
@@ -640,7 +707,7 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
// Probe for last note to draw. Worst case is 0.25x + boost.
// Adjust search distance so that notes don't pop onto the screen.
float fSearchDistance = 10;
float fLastBeatToDraw = GAMESTATE->m_fSongBeat+fSearchDistance;
float fLastBeatToDraw = GetDisplayedPosition(pPlayerState)->m_fSongBeat+fSearchDistance;
const int NUM_ITERATIONS = 20;
@@ -669,6 +736,19 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
return fLastBeatToDraw;
}
inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow )
{
/*
if( GAMESTATE->m_bIsUsingStepTiming )
{
*/
return NoteRowToBeat(iRow);
/*
}
return GetDisplayedTiming(pPlayerState)->GetBeatFromElapsedTime(GetRealTiming(pPlayerState)->GetElapsedTimeFromBeat(NoteRowToBeat(iRow)));
*/
}
bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
{
// TRICKY: If boomerang is on, then ones in the range
@@ -739,10 +819,13 @@ void NoteField::DrawPrimitives()
cur->m_ReceptorArrowRow.Draw();
}
const TimingData *pTiming = GetDisplayedTiming(m_pPlayerState);
// Draw beat bars
if( GAMESTATE->IsEditing() || SHOW_BEAT_BARS )
if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL )
{
const vector<TimeSignatureSegment> &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments;
const TimingData &timing = *pTiming;
const vector<TimeSignatureSegment> &vTimeSignatureSegments = timing.m_vTimeSignatureSegments;
int iMeasureIndex = 0;
FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter )
{
@@ -781,12 +864,14 @@ void NoteField::DrawPrimitives()
}
}
if( GAMESTATE->IsEditing() )
if( GAMESTATE->IsEditing() && pTiming != NULL )
{
ASSERT(GAMESTATE->m_pCurSong);
const TimingData &timing = *pTiming;
// BPM text
FOREACH_CONST( BPMSegment, GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments, seg )
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -797,7 +882,7 @@ void NoteField::DrawPrimitives()
}
// Freeze text
FOREACH_CONST( StopSegment, GAMESTATE->m_pCurSong->m_Timing.m_StopSegments, seg )
FOREACH_CONST( StopSegment, timing.m_StopSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -808,18 +893,21 @@ void NoteField::DrawPrimitives()
}
// Warp text
FOREACH_CONST( WarpSegment, GAMESTATE->m_pCurSong->m_Timing.m_WarpSegments, seg )
if( GAMESTATE->m_bIsUsingStepTiming )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, seg )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawWarpText( fBeat, seg->m_fEndBeat );
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawWarpText( fBeat, seg->m_fLengthBeats );
}
}
}
// Time Signature text
FOREACH_CONST( TimeSignatureSegment, GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments, seg )
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -829,30 +917,36 @@ void NoteField::DrawPrimitives()
}
}
// Tickcount text
FOREACH_CONST( TickcountSegment, GAMESTATE->m_pCurSong->m_Timing.m_TickcountSegments, seg )
if( GAMESTATE->m_bIsUsingStepTiming )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
// Tickcount text
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, seg )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawTickcountText( fBeat, seg->m_iTicks );
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawTickcountText( fBeat, seg->m_iTicks );
}
}
}
// Combo text
FOREACH_CONST( ComboSegment, GAMESTATE->m_pCurSong->m_Timing.m_ComboSegments, seg )
if( GAMESTATE->m_bIsUsingStepTiming )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
// Combo text
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, seg )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawComboText( fBeat, seg->m_iCombo );
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawComboText( fBeat, seg->m_iCombo );
}
}
}
// Label text
FOREACH_CONST( LabelSegment, GAMESTATE->m_pCurSong->m_Timing.m_LabelSegments, seg )
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
@@ -861,6 +955,33 @@ void NoteField::DrawPrimitives()
DrawLabelText( fBeat, seg->m_sLabel );
}
}
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawSpeedText( fBeat, seg->m_fPercent, seg->m_fWait, seg->m_usMode );
}
}
}
// Speed text
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawFakeText( fBeat, seg->m_fLengthBeats );
}
}
}
// Course mods text
const Course *pCourse = GAMESTATE->m_pCurCourse;
@@ -872,7 +993,7 @@ void NoteField::DrawPrimitives()
FOREACH_CONST( Attack, ce.attacks, a )
{
float fSecond = a->fStartSecond;
float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond );
float fBeat = timing.GetBeatFromElapsedTime( fSecond );
if( BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
BeatToNoteRow(fBeat) <= iLastRowToDraw)
@@ -882,71 +1003,74 @@ void NoteField::DrawPrimitives()
}
}
}
// BGChange text
switch( GAMESTATE->m_EditMode )
if( !GAMESTATE->m_bIsUsingStepTiming )
{
case EditMode_Home:
case EditMode_CourseMods:
case EditMode_Practice:
break;
case EditMode_Full:
{
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
FOREACH_BackgroundLayer( i )
iter[i] = GAMESTATE->m_pCurSong->GetBackgroundChanges(i).begin();
while( 1 )
// BGChange text
switch( GAMESTATE->m_EditMode )
{
case EditMode_Home:
case EditMode_CourseMods:
case EditMode_Practice:
break;
case EditMode_Full:
{
float fLowestBeat = FLT_MAX;
vector<BackgroundLayer> viLowestIndex;
vector<BackgroundChange>::iterator iter[NUM_BackgroundLayer];
FOREACH_BackgroundLayer( i )
iter[i] = GAMESTATE->m_pCurSong->GetBackgroundChanges(i).begin();
while( 1 )
{
if( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() )
continue;
float fBeat = iter[i]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
viLowestIndex.push_back( i );
}
else if( fBeat == fLowestBeat )
{
viLowestIndex.push_back( i );
}
}
if( viLowestIndex.empty() )
{
float fLowestBeat = FLT_MAX;
vector<BackgroundLayer> viLowestIndex;
FOREACH_BackgroundLayer( i )
ASSERT( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() );
break;
}
if( IS_ON_SCREEN(fLowestBeat) )
{
vector<RString> vsBGChanges;
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
{
ASSERT( iter[*i] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*i).end() );
const BackgroundChange& change = *iter[*i];
RString s = change.GetTextDescription();
if( *i!=0 )
s = ssprintf("%d: ",*i) + s;
vsBGChanges.push_back( s );
if( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() )
continue;
float fBeat = iter[i]->m_fStartBeat;
if( fBeat < fLowestBeat )
{
fLowestBeat = fBeat;
viLowestIndex.clear();
viLowestIndex.push_back( i );
}
else if( fBeat == fLowestBeat )
{
viLowestIndex.push_back( i );
}
}
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
if( viLowestIndex.empty() )
{
FOREACH_BackgroundLayer( i )
ASSERT( iter[i] == GAMESTATE->m_pCurSong->GetBackgroundChanges(i).end() );
break;
}
if( IS_ON_SCREEN(fLowestBeat) )
{
vector<RString> vsBGChanges;
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
{
ASSERT( iter[*i] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*i).end() );
const BackgroundChange& change = *iter[*i];
RString s = change.GetTextDescription();
if( *i!=0 )
s = ssprintf("%d: ",*i) + s;
vsBGChanges.push_back( s );
}
DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) );
}
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
iter[*i]++;
}
FOREACH_CONST( BackgroundLayer, viLowestIndex, i )
iter[*i]++;
}
}
break;
default:
ASSERT(0);
break;
default:
ASSERT(0);
}
}
// Draw marker bars
@@ -1012,8 +1136,8 @@ void NoteField::DrawPrimitives()
float fThrowAway;
bool bStartIsPastPeak = false;
bool bEndIsPastPeak = false;
float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iStartRow), fThrowAway, bStartIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iEndRow), fThrowAway, bEndIsPastPeak );
float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak );
bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels;
bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels;
@@ -1043,7 +1167,7 @@ void NoteField::DrawPrimitives()
displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT );
bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > GAMESTATE->m_fSongBeat;
bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bAnyUpcomingInThisCol |= bNoteIsUpcoming;
}
}
@@ -1085,7 +1209,7 @@ void NoteField::DrawPrimitives()
continue; // skip
ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw,
iFirstRowToDraw, GAMESTATE->m_fSongBeat, GAMESTATE->m_fMusicSeconds) );
iFirstRowToDraw, GetDisplayedPosition(m_pPlayerState)->m_fSongBeat, GetDisplayedPosition(m_pPlayerState)->m_fMusicSeconds) );
// See if there is a hold step that begins on this index.
// Only do this if the noteskin cares.
@@ -1110,12 +1234,12 @@ void NoteField::DrawPrimitives()
bool bIsHopoPossible = (tn.bHopoPossible);
bool bUseAdditionColoring = bIsAddition || bIsHopoPossible;
NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn];
displayCols->display[c].DrawTap( tn, c, NoteRowToBeat(q), bHoldNoteBeginsOnThisBeat,
displayCols->display[c].DrawTap( tn, c, NoteRowToVisibleBeat(m_pPlayerState, q), bHoldNoteBeginsOnThisBeat,
bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels,
FADE_BEFORE_TARGETS_PERCENT );
bool bNoteIsUpcoming = NoteRowToBeat(q) > GAMESTATE->m_fSongBeat;
bool bNoteIsUpcoming = NoteRowToBeat(q) > GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bAnyUpcomingInThisCol |= bNoteIsUpcoming;
}
+5 -1
View File
@@ -1,6 +1,8 @@
#ifndef NOTE_FIELD_H
#define NOTE_FIELD_H
#include "TimingData.h"
#include "SongPosition.h"
#include "Sprite.h"
#include "ActorFrame.h"
#include "BitmapText.h"
@@ -62,10 +64,12 @@ protected:
void DrawTickcountText( const float fBeat, int iTicks );
void DrawComboText( const float fBeat, int iCombo );
void DrawLabelText( const float fBeat, RString sLabel );
void DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode );
void DrawFakeText( const float fBeat, const float fNewBeat );
void DrawAttackText( const float fBeat, const Attack &attack );
void DrawBGChangeText( const float fBeat, const RString sNewBGName );
float GetWidth() const;
const NoteData *m_pNoteData;
float m_fPercentFadeToFail; // -1 if not fading to fail
-2
View File
@@ -43,11 +43,9 @@ bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set<RString> &Bl
SMLoader::GetApplicableFiles( sPath, list );
if (!list.empty() )
return SMLoader::LoadFromDir( sPath, out );
#if defined(_MSC_VER) || defined(MACOSX)
SMALoader::GetApplicableFiles( sPath, list );
if (!list.empty() )
return SMALoader::LoadFromDir( sPath, out );
#endif
DWILoader::GetApplicableFiles( sPath, list );
if( !list.empty() )
return DWILoader::LoadFromDir( sPath, out, BlacklistedImages );
+232 -178
View File
@@ -435,9 +435,121 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
static const int BEATS_PER_MEASURE = 4;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out,
const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex )
static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map<RString, int> &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex )
{
// Search for memoized file names:
{
RString nDataToSearchFor = nDataOriginal;
nDataToSearchFor.MakeLower();
map<RString, int>::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor);
if (it != mapFilenameToKeysoundIndex.end()) {
outKeysoundIndex = it->second;
return true;
}
}
// FIXME: garbled song names seem to crash the app.
// this might not be the best place to put this code.
if( !utf8_is_valid(nDataOriginal) )
return false;
/* Due to bugs in some programs, many BMS files have a "WAV" extension
* on files in the BMS for files that actually have some other extension.
* Do a search. Don't do a wildcard search; if sData is "song.wav",
* we might also have "song.png", which we shouldn't match. */
RString nData = nDataOriginal;
if( !IsAFile(out.GetSongDir()+nData) )
{
const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
for( unsigned i = 0; exts[i] != NULL; ++i )
{
RString fn = SetExtension( nData, exts[i] );
if( IsAFile(out.GetSongDir()+fn) )
{
nData = fn;
break;
}
}
}
if( !IsAFile(out.GetSongDir()+nData) )
{
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
return false;
}
// Let's again search for memoized file names (we got the normalized one!):
{
RString nDataToSearchFor = nData;
nDataToSearchFor.MakeLower();
map<RString, int>::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor);
if (it != mapFilenameToKeysoundIndex.end()) {
outKeysoundIndex = it->second;
{
RString nDataToAdd = nDataOriginal;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
return true;
}
}
// Now this is a new sample.
out.m_vsKeysoundFile.push_back( nData );
outKeysoundIndex = out.m_vsKeysoundFile.size() - 1;
{
RString nDataToAdd = nDataOriginal;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
{
RString nDataToAdd = nData;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
return true;
}
static bool SearchForKeysound( const RString &sPath, RString sNoteId, const NameToData_t &mapNameToData, map<RString, int> &mapIdToKeysoundIndex, map<RString, int> &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex )
{
sNoteId.MakeLower();
{
map<RString, int>::iterator it = mapIdToKeysoundIndex.find(sNoteId);
if (it != mapIdToKeysoundIndex.end())
{
outKeysoundIndex = it->second;
return outKeysoundIndex >= 0;
}
}
RString sTagToLookFor = ssprintf( "#wav%s", sNoteId.c_str() );
RString nDataOriginal;
if( !GetTagFromMap( mapNameToData, sTagToLookFor, nDataOriginal ) )
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
return false;
}
bool retval = SearchForKeysound(sPath, nDataOriginal, mapFilenameToKeysoundIndex, out, outKeysoundIndex);
mapIdToKeysoundIndex[sNoteId] = outKeysoundIndex;
return retval;
}
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, Song &outSong, map<RString, int> &mapFilenameToKeysoundIndex )
{
map<RString, int> mapIdToKeysoundIndex;
MeasureToTimeSig_t sigAdjustments;
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
out.m_StepsType = StepsType_Invalid;
@@ -453,11 +565,121 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
NoteData ndNotes;
ndNotes.SetNumTracks( NUM_BMS_TRACKS );
// Read BPM
if( GetTagFromMap(mapNameToData, "#bpm", sData) )
{
const float fBPM = StringToFloat( sData );
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.m_Timing.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
/* Read time signatures. Note that these can differ across files in the same
* song. */
MeasureToTimeSig_t mapMeasureToTimeSig;
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
for( NameToData_t::const_iterator it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
RString nData = it->second;
int totalPairs = nData.size() / 2;
for( int i = 0; i < totalPairs; ++i )
{
RString sPair = nData.substr( i*2, 2 );
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
float fBeat = NoteRowToBeat( iRow );
int iVal = 0;
sscanf( sPair, "%x", &iVal );
if (sPair == "00")
{
continue;
}
switch( iBMSTrackNo )
{
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.m_Timing.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %d.",
fBeat, iVal );
}
break;
case BMS_TRACK_BPM_REF:
{
RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() );
RString sBPM;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.m_Timing.SetBPMAtBeat( fBeat, fBPM );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
case BMS_TRACK_STOP:
{
if( iVal == 0 )
{
break;
}
RString sTagToLookFor = ssprintf( "#stop%02x", iVal );
RString sBeats;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.m_Timing.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
}
}
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, outSong, sigAdjustments );
int iHoldStarts[NUM_BMS_TRACKS];
TapNote iHoldHeads[NUM_BMS_TRACKS];
@@ -487,10 +709,9 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
RString sNoteId = sNoteData.substr( i, 2 );
if( sNoteId != "00" )
{
vTapNotes.push_back( TAP_ORIGINAL_TAP );
map<RString,int>::const_iterator rInt = idToKeySoundIndex.find( sNoteId );
if( rInt != idToKeySoundIndex.end() )
vTapNotes.back().iKeysoundIndex = rInt->second;
TapNote tn = TAP_ORIGINAL_TAP;
SearchForKeysound( sPath, sNoteId, mapNameToData, mapIdToKeysoundIndex, mapFilenameToKeysoundIndex, outSong, tn.iKeysoundIndex );
vTapNotes.push_back( tn );
}
else
{
@@ -781,7 +1002,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
return true;
}
static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map<RString,int> &idToKeySoundIndexOut )
static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out )
{
RString sData;
if( GetTagFromMap(mapNameToData, "#title", sData) )
@@ -792,172 +1013,6 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile );
GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile );
if( GetTagFromMap(mapNameToData, "#bpm", sData) )
{
const float fBPM = StringToFloat( sData );
if( PREFSMAN->m_bQuirksMode )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
if( fBPM > 0.0f )
LOG->Trace( "Inserting new positive BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
else
LOG->Trace( "Inserting new negative BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
}
NameToData_t::const_iterator it;
for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName.Left(4) != "#wav" )
continue;
// this is keysound file name. Looks like "#WAV1A"
RString nData = it->second;
RString sWavID = sName.Right(2);
// FIXME: garbled song names seem to crash the app.
// this might not be the best place to put this code.
if( !utf8_is_valid(nData) )
continue;
/* Due to bugs in some programs, many BMS files have a "WAV" extension
* on files in the BMS for files that actually have some other extension.
* Do a search. Don't do a wildcard search; if sData is "song.wav",
* we might also have "song.png", which we shouldn't match. */
if( !IsAFile(out.GetSongDir()+nData) )
{
const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
for( unsigned i = 0; exts[i] != NULL; ++i )
{
RString fn = SetExtension( nData, exts[i] );
if( IsAFile(out.GetSongDir()+fn) )
{
nData = fn;
break;
}
}
}
if( !IsAFile(out.GetSongDir()+nData) )
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
sWavID.MakeUpper(); // HACK: undo the MakeLower()
out.m_vsKeysoundFile.push_back( nData );
idToKeySoundIndexOut[ sWavID ] = out.m_vsKeysoundFile.size()-1;
LOG->Trace( "Inserting keysound index %u '%s'", unsigned(out.m_vsKeysoundFile.size()-1), sWavID.c_str() );
}
// Time signature tags affect all other global timing tags, so read them first.
MeasureToTimeSig_t mapMeasureToTimeSig;
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
RString nData = it->second;
int totalPairs = nData.size() / 2;
for( int i = 0; i < totalPairs; ++i )
{
RString sPair = nData.substr( i*2, 2 );
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
float fBeat = NoteRowToBeat( iRow );
int iVal = 0;
sscanf( sPair, "%x", &iVal );
if (sPair == "00")
{
continue;
}
switch( iBMSTrackNo )
{
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %d.",
fBeat, iVal );
}
break;
case BMS_TRACK_BPM_REF:
{
RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() );
RString sBPM;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.SetBPMAtBeat( fBeat, fBPM );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
case BMS_TRACK_STOP:
{
if( iVal == 0 )
{
break;
}
RString sTagToLookFor = ssprintf( "#stop%02x", iVal );
RString sBeats;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
}
}
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, out, sigAdjustmentsOut );
}
static void SlideDuplicateDifficulties( Song &p )
@@ -1031,7 +1086,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
// Create a Steps for each.
vector<Steps*> apSteps;
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
apSteps.push_back( new Steps );
apSteps.push_back( out.CreateSteps() );
// Now, with our fancy little substring, trim the titles and
// figure out where each goes.
@@ -1100,9 +1155,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
if( apSteps[i]->GetDifficulty() == Difficulty_Medium )
iMainDataIndex = i;
MeasureToTimeSig_t sigAdjustments;
map<RString,int> idToKeysoundIndex;
ReadGlobalTags( aBMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex );
ReadGlobalTags( aBMSData[iMainDataIndex], out );
// The brackets before the difficulty are in common substring, so remove them if it's found.
if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' )
@@ -1124,10 +1177,11 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
// Now that we've parsed the keysound data, load the Steps from the rest
// of the .bms files.
map<RString, int> mapFilenameToKeysoundIndex;
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
{
Steps* pNewNotes = apSteps[i];
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes, sigAdjustments, idToKeysoundIndex );
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes, out, mapFilenameToKeysoundIndex );
if( ok )
out.AddSteps( pNewNotes );
else
+7 -7
View File
@@ -509,11 +509,11 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
if( PREFSMAN->m_bQuirksMode )
{
out.AddBPMSegment( BPMSegment(0, fBPM) );
out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
}
else{
if( fBPM > 0.0f )
out.AddBPMSegment( BPMSegment(0, fBPM) );
out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
else
LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
@@ -545,7 +545,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
else if( sValueName.EqualsNoCase("GAP") )
// the units of GAP is 1/1000 second
out.m_Timing.m_fBeat0OffsetInSeconds = -StringToInt( sParams[1] ) / 1000.0f;
out.m_SongTiming.m_fBeat0OffsetInSeconds = -StringToInt( sParams[1] ) / 1000.0f;
else if( sValueName.EqualsNoCase("SAMPLESTART") )
out.m_fMusicSampleStartSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]);
@@ -570,7 +570,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
int iFreezeRow = BeatToNoteRow( StringToFloat(arrayFreezeValues[0]) / 4.0f );
float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ) / 1000.0f;
out.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) );
out.m_SongTiming.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) );
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds );
}
}
@@ -595,7 +595,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
if( fBPM > 0.0f )
{
BPMSegment bs( iStartIndex, fBPM );
out.AddBPMSegment( bs );
out.m_SongTiming.AddBPMSegment( bs );
}
else
{
@@ -610,7 +610,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
sValueName.EqualsNoCase("COUPLE") ||
sValueName.EqualsNoCase("SOLO") )
{
Steps* pNewNotes = new Steps;
Steps* pNewNotes = out.CreateSteps();
LoadFromDWITokens(
sParams[0],
sParams[1],
@@ -656,7 +656,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
// do nothing. We don't care about this value name
}
}
out.TidyUpData();
return true;
}
+2 -2
View File
@@ -139,7 +139,7 @@ static void Deserialize( Song &out, const Json::Value &root )
out.m_sLyricsFile = root["LyricsFile"].asString();
out.m_sCDTitleFile = root["CDTitle"].asString();
out.m_sMusicFile = root["Music"].asString();
out.m_Timing.m_fBeat0OffsetInSeconds = (float)root["Offset"].asDouble();
out.m_SongTiming.m_fBeat0OffsetInSeconds = (float)root["Offset"].asDouble();
out.m_fMusicSampleStartSeconds = (float)root["SampleStart"].asDouble();
out.m_fMusicSampleLengthSeconds = (float)root["SampleLength"].asDouble();
RString sSelectable = root["Selectable"].asString();
@@ -167,7 +167,7 @@ static void Deserialize( Song &out, const Json::Value &root )
out.m_fSpecifiedBPMMax = (float)root["SpecifiedBpmMax"].asDouble();
}
Deserialize( out.m_Timing, root["TimingData"] );
Deserialize( out.m_SongTiming, root["TimingData"] );
JsonUtil::DeserializeVectorObjects( out.m_LyricSegments, Deserialize, root["LyricSegments"] );
{
+187 -82
View File
@@ -9,6 +9,51 @@
#include "Song.h"
#include "Steps.h"
static void HandleBunki( TimingData &timing, const float fEarlyBPM,
const float fCurBPM, const float fGap,
const float fPos )
{
const float BeatsPerSecond = fEarlyBPM / 60.0f;
const float beat = (fPos + fGap) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos %f, beat %f",
fEarlyBPM, BeatsPerSecond, fPos, beat );
timing.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) );
}
static bool HandlePipeChars( TimingData &timing, const RString sNoteRow,
const float fCurBeat, int &iTickCount )
{
RString temp = sNoteRow.substr(2,sNoteRow.size()-3);
float numTemp = StringToFloat(temp);
if (BeginsWith(sNoteRow, "|T"))
{
iTickCount = static_cast<int>(numTemp);
timing.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) );
return true;
}
else if (BeginsWith(sNoteRow, "|B"))
{
timing.SetBPMAtBeat( fCurBeat, numTemp );
return true;
}
else if (BeginsWith(sNoteRow, "|E"))
{
// Finally! the |E| tag is working as it should. I can die happy now -DaisuMaster
float fCurDelay = 60 / timing.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
fCurDelay += timing.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
return true;
}
else if (BeginsWith(sNoteRow, "|D"))
{
float fCurDelay = timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += numTemp / 1000;
timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
return true;
}
return false;
}
static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, bool bKIUCompliant )
{
LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );
@@ -25,6 +70,9 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
// According to Aldo_MX, there is a default BPM and it's 60. -aj
bool bDoublesChart = false;
TimingData stepsTiming;
float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
@@ -32,8 +80,87 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
RString sValueName = sParams[0];
sValueName.MakeUpper();
// handle the data
if( sValueName=="TICKCOUNT" )
/* handle the data...well, not this data: not related to steps.
* Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */
if (sValueName=="TITLE" || EndsWith(sValueName, "INTRO")
|| EndsWith(sValueName, "FILE") )
{
;
}
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
stepsTiming.AddBPMSegment( BPMSegment(0, BPM1) );
}
else if( sValueName=="BPM2" )
{
if (bKIUCompliant)
{
BPM2 = StringToFloat( sParams[1] );
}
else
{
// LOG an error.
}
}
else if( sValueName=="BPM3" )
{
if (bKIUCompliant)
{
BPM3 = StringToFloat( sParams[1] );
}
else
{
// LOG an error.
}
}
else if( sValueName=="BUNKI" )
{
if (bKIUCompliant)
{
BPMPos2 = StringToFloat( sParams[1] ) / 100.0f;
}
else
{
// LOG an error.
}
}
else if( sValueName=="BUNKI2" )
{
if (bKIUCompliant)
{
BPMPos3 = StringToFloat( sParams[1] ) / 100.0f;
}
else
{
// LOG an error.
}
}
else if( sValueName=="STARTTIME" )
{
SMGap1 = -StringToFloat( sParams[1] )/100;
stepsTiming.m_fBeat0OffsetInSeconds = SMGap1;
}
// This is currently required for more accurate KIU BPM changes.
else if( sValueName=="STARTTIME2" )
{
if (bKIUCompliant)
{
SMGap2 = -StringToFloat( sParams[1] )/100;
}
else
{
// LOG an error.
}
}
else if ( sValueName=="STARTTIME3" )
{
// STARTTIME3 only ensures this is a KIU compliant simfile.
bKIUCompliant = true;
}
else if( sValueName=="TICKCOUNT" )
{
iTickCount = StringToInt( sParams[1] );
if( iTickCount <= 0 )
@@ -41,13 +168,9 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount );
return false;
}
stepsTiming.AddTickcountSegment(TickcountSegment(0, iTickCount));
}
else if( sValueName=="STEP" )
{
RString theSteps = sParams[1];
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
else if( sValueName=="DIFFICULTY" )
{
out.SetMeter( max(StringToInt(sParams[1]), 0) );
@@ -60,13 +183,34 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
if( sPlayer.find( "double" ) != string::npos )
bDoublesChart = true;
}
// This should always be last.
else if( sValueName=="STEP" )
{
RString theSteps = sParams[1];
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
}
if( iTickCount == -1 )
{
iTickCount = 2; // Direct Move 0.5 has a default value of 4... -aj
iTickCount = 4;
LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount );
}
// Prepare BPM stuff already if the file uses KSF syntax.
if( bKIUCompliant )
{
if( BPM2 > 0 && BPMPos2 > 0 )
{
HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 );
}
if( BPM3 > 0 && BPMPos3 > 0 )
{
HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 );
}
}
NoteData notedata; // read it into here
@@ -133,6 +277,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
case StepsType_pump_single: notedata.SetNumTracks( 5 ); break;
case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break;
case StepsType_pump_double: notedata.SetNumTracks( 10 ); break;
case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this?
case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break;
default: FAIL_M( ssprintf("%i", out.m_StepsType) );
}
@@ -146,6 +291,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
int newTick = -1;
float fCurBeat = 0.0f;
float prevBeat = 0.0f; // Used for hold tails.
for( unsigned r=0; r<vNoteRows.size(); r++ )
{
RString& sRowString = vNoteRows[r];
@@ -155,7 +301,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
continue; // skip
// All 2s indicates the end of the song.
if( sRowString == "2222222222222" )
else if( sRowString == "2222222222222" )
{
// Finish any holds that didn't get...well, finished.
for( t=0; t < notedata.GetNumTracks(); t++ )
@@ -171,29 +317,21 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
break;
}
// Why do this? Rows made with precise DM05 tags can go up to 13 too -DaisuMaster
//if( sRowString.size() != 13 )
//this is wrong in many ways...
/*if( bKIUCompliant )
else if( BeginsWith(sRowString, "|") )
{
LOG->UserLog( "Song file", sPath, "has illegal syntax \"%s\" which can't be in KIU complient files.",
sRowString.c_str() );
return false;
//In other words: you can't mix ksf's with DM05 tags and ksf's without any DM05 tags
//Either one set or another will be read...
}*/
if( BeginsWith(sRowString, "|B") || BeginsWith(sRowString, "|D") || BeginsWith(sRowString, "|E") )
{
// These don't have to be worried about here: the changes and stops were already added.
continue;
}
else if ( BeginsWith(sRowString, "|T") )
{
RString temp = sRowString.substr(2,sRowString.size()-3);
newTick = StringToInt(temp);
bTickChangeNeeded = true;
if (bKIUCompliant)
{
// Log an error, ignore the line.
continue;
}
if ( !HandlePipeChars( stepsTiming, sRowString, fCurBeat, iTickCount ) )
{
// LOG it first.
}
continue;
}
else
{
// Is this why improper ksf or some kiucompilant ksf mixed with dm05 ksf are ignored?? -DaisuMaster
@@ -270,6 +408,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
}
out.SetNoteData( notedata );
out.m_Timing = stepsTiming;
out.TidyUpData();
@@ -358,7 +497,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
out.AddBPMSegment( BPMSegment(0, BPM1) );
out.m_SongTiming.AddBPMSegment( BPMSegment(0, BPM1) );
}
else if( sValueName=="BPM2" )
{
@@ -383,7 +522,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="STARTTIME" )
{
SMGap1 = -StringToFloat( sParams[1] )/100;
out.m_Timing.m_fBeat0OffsetInSeconds = SMGap1;
out.m_SongTiming.m_fBeat0OffsetInSeconds = SMGap1;
}
// This is currently required for more accurate KIU BPM changes.
else if( sValueName=="STARTTIME2" )
@@ -409,7 +548,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
TickcountSegment tcs;
tcs.m_iStartRow = BeatToNoteRow(0.0f);
tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount;
out.m_Timing.AddTickcountSegment( tcs );
out.m_SongTiming.AddTickcountSegment( tcs );
}
else if ( sValueName=="STEP" )
{
@@ -465,21 +604,12 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
{
if( BPM2 > 0 && BPMPos2 > 0 )
{
const float BeatsPerSecond = BPM1 / 60.0f;
const float beat = (BPMPos2 + SMGap1) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos2 %f, beat %f",
BPM1, BeatsPerSecond, BPMPos2, beat );
out.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), BPM2) );
HandleBunki( out.m_SongTiming, BPM1, BPM2, SMGap1, BPMPos2 );
}
if( BPM3 > 0 && BPMPos3 > 0 )
{
const float BeatsPerSecond = BPM2 / 60.0f;
//The line below isn't perfect, but works better than previous versions.
const float beat = (BPMPos3 + SMGap2) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos3 %f, beat %f",
BPM2, BeatsPerSecond, BPMPos3, beat );
out.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), BPM3) );
HandleBunki( out.m_SongTiming, BPM2, BPM3, SMGap2, BPMPos3 );
}
}
else
@@ -507,40 +637,11 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
BeginsWith(NoteRowString, "|D") || BeginsWith(NoteRowString, "|E") )
{
bDMRequired = true;
RString temp = NoteRowString.substr(2,NoteRowString.size()-3);
float numTemp = StringToFloat(temp);
if (BeginsWith(NoteRowString, "|T"))
if ( !HandlePipeChars( out.m_SongTiming, NoteRowString, fCurBeat, iTickCount ) )
{
iTickCount = (int)numTemp;
TickcountSegment tcs;
tcs.m_iStartRow = BeatToNoteRow(fCurBeat);
tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount;
out.m_Timing.AddTickcountSegment( tcs );
continue;
}
else if (BeginsWith(NoteRowString, "|B"))
{
float fCurBpm = (float)numTemp;
//out.m_Timing.AddBPMSegment( BPMSegment( BeatToNoteRow(fCurBeat), (float)numTemp ) );
out.m_Timing.SetBPMAtBeat( fCurBeat, fCurBpm );
continue;
}
else if (BeginsWith(NoteRowString, "|E"))
{
// Finally! the |E| tag is working as it should. I can die happy now -DaisuMaster
float fCurDelay = 60 / out.m_Timing.GetBPMAtBeat(fCurBeat) * (float)numTemp / iTickCount;
fCurDelay += out.m_Timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
out.m_Timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
continue;
}
else if (BeginsWith(NoteRowString, "|D"))
{
float fCurDelay = out.m_Timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += (float)numTemp / 1000;
out.m_Timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
continue;
// LOG it first.
}
continue;
}
else
{
@@ -580,16 +681,19 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
ASSERT( arrayKSFFileNames.size() );
bool bKIUCompliant = false;
/* If only the first file is read, it will cause problems for other simfiles with
* different BPM changes and tickcounts. This command will probably have to be
* changed in the future. */
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[0], out, bKIUCompliant) )
/* With Split Timing, there has to be a backup Song Timing in case
* anything goes wrong. As these files are kept in alphabetical
* order (hopefully), it is best to use the LAST file for timing
* purposes, for that is the "normal", or easiest difficulty.
* Usually. */
unsigned files = arrayKSFFileNames.size();
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[files - 1], out, bKIUCompliant) )
return false;
// load the Steps from the rest of the KSF files
for( unsigned i=0; i<arrayKSFFileNames.size(); i++ )
for( unsigned i=0; i<files; i++ )
{
Steps* pNewNotes = new Steps;
Steps* pNewNotes = out.CreateSteps();
if( !LoadFromKSFFile(out.GetSongDir() + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) )
{
delete pNewNotes;
@@ -598,6 +702,7 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
out.AddSteps( pNewNotes );
}
out.TidyUpData();
return true;
}
+5 -4
View File
@@ -682,7 +682,7 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
double fSecondsPerBeat = (iter->tickSeconds * GUITAR_MIDI_COUNTS_PER_BEAT);
bpmSeg.m_fBPS = float( 1. / fSecondsPerBeat );
songOut.m_Timing.AddBPMSegment( bpmSeg );
songOut.m_SongTiming.AddBPMSegment( bpmSeg );
}
FOREACH_CONST( MidiFileIn::TimeSignatureChange, midi.timeSignatureEvents_, iter )
@@ -692,7 +692,7 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
seg.m_iNumerator = iter->numerator;
seg.m_iDenominator = iter->denominator;
songOut.m_Timing.AddTimeSignatureSegment( seg );
songOut.m_SongTiming.AddTimeSignatureSegment( seg );
}
@@ -877,13 +877,13 @@ skip_track:
}
}
Steps *pSteps = new Steps;
Steps *pSteps = songOut.CreateSteps();
pSteps->m_StepsType = StepsType_guitar_five;
pSteps->SetDifficulty( (Difficulty)(gd+1) );
pSteps->SetNoteData( noteData );
songOut.AddSteps( pSteps );
}
return true;
}
@@ -956,6 +956,7 @@ bool MidiLoader::LoadFromDir( const RString &sDir, Song &out )
if( !LoadFromMidi(sDir+vsFiles[0], out) )
return false;
out.TidyUpData();
return true;
}
+8 -8
View File
@@ -591,7 +591,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
@@ -673,7 +673,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
case PMS_TRACK_BPM:
if( iVal > 0 )
{
out.SetBPMAtBeat( fBeat, (float) iVal );
out.m_SongTiming.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
@@ -694,7 +694,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( BeatToNoteRow(fBeat), fBPM );
out.AddBPMSegment( newSeg );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg.GetBPM() );
}
else
@@ -716,12 +716,12 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBPS = out.m_SongTiming.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.AddStopSegment( newSeg );
out.m_SongTiming.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
@@ -750,7 +750,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f )
{
BPMSegment newSeg( iStepIndex, fBPM );
out.AddBPMSegment( newSeg );
out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(newSeg.m_iStartRow), newSeg.GetBPM() );
}
@@ -843,7 +843,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
/* Create a Steps for each. */
vector<Steps*> apSteps;
for( unsigned i=0; i<arrayPMSFileNames.size(); i++ )
apSteps.push_back( new Steps );
apSteps.push_back( out.CreateSteps() );
// Now, with our fancy little substring, trim the titles and
// figure out where each goes.
@@ -940,7 +940,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
ConvertString( out.m_sArtist, "utf-8,japanese" );
ConvertString( out.m_sGenre, "utf-8,japanese" );
out.TidyUpData();
return true;
}
+339 -318
View File
@@ -15,11 +15,6 @@
/** @brief The maximum file size for edits. */
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB
/**
* @brief The highest allowable speed before Warps come in.
*
* This was brought in from StepMania 4's recent betas. */
const float FAST_BPM_WARP = 9999999.f;
void SMLoader::LoadFromSMTokens(
RString sStepsType,
@@ -103,6 +98,331 @@ bool SMLoader::LoadTimingFromFile( const RString &fn, TimingData &out )
return true;
}
void SMLoader::ProcessBGChanges( Song &out, const RString &sValueName, const RString &sPath, const RString &sParam )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParam, ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
}
void SMLoader::ProcessAttacks( Song &out, MsdFile::value_t sParams )
{
// Build the RString vector here so we can write it to file again later
for( unsigned s=1; s < sParams.params.size(); ++s )
out.m_sAttackString.push_back( sParams[s] );
Attack attack;
float end = -9999;
for( unsigned j=1; j < sParams.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("LEN") )
attack.fSecsRemaining = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("END") )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
attack.sModifiers = sBits[1];
if( end != -9999 )
{
attack.fSecsRemaining = end - attack.fStartSecond;
end = -9999;
}
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
}
}
}
void SMLoader::ProcessInstrumentTracks( Song &out, const RString &sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
}
bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
{
vector<RString> arrayBPMChangeExpressions;
split( sParam, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
bool bNotEmpty = false;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #BPMs value \"%s\" (must have exactly one '='), ignored.",
arrayBPMChangeExpressions[b].c_str() );
continue;
}
bNotEmpty = true;
const float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
return bNotEmpty;
}
void SMLoader::ProcessStops( TimingData &out, const RString sParam )
{
vector<RString> arrayFreezeExpressions;
split( sParam, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
}
}
void SMLoader::ProcessDelays( TimingData &out, const RString sParam )
{
vector<RString> arrayDelayExpressions;
split( sParam, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayDelayValues[0] );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
void SMLoader::ProcessTimeSignatures( TimingData &out, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), StringToInt( vs2[2] ));
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat );
continue;
}
if( seg.m_iNumerator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator );
continue;
}
if( seg.m_iDenominator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
}
void SMLoader::ProcessTickcounts( TimingData &out, const RString sParam )
{
vector<RString> arrayTickcountExpressions;
split( sParam, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] );
int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
}
}
void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
{
out.m_fBeat0OffsetInSeconds = 0;
@@ -123,255 +443,27 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
}
else if( sValueName=="BPMS" )
{
vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() );
continue;
}
const float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg;
new_seg.m_iStartRow = BeatToNoteRow(fBeat);
new_seg.SetBPM( fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
ProcessBPMs(out, sParams[1]);
}
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
}
ProcessStops(out, sParams[1]);
}
else if( sValueName=="DELAYS" )
{
vector<RString> arrayDelayExpressions;
split( sParams[1], ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = StringToFloat( arrayDelayValues[0] );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
ProcessDelays(out, sParams[1]);
}
else if( sValueName=="TIMESIGNATURES" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg;
seg.m_iStartRow = BeatToNoteRow(fBeat);
seg.m_iNumerator = StringToInt( vs2[1] );
seg.m_iDenominator = StringToInt( vs2[2] );
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat );
continue;
}
if( seg.m_iNumerator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator );
continue;
}
if( seg.m_iDenominator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
ProcessTimeSignatures(out, sParams[1]);
}
else if( sValueName=="TICKCOUNTS" )
{
vector<RString> arrayTickcountExpressions;
split( sParams[1], ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] );
int iTicks = StringToInt( arrayTickcountValues[1] );
// you're lazy, let SM do the work for you... -DaisuMaster
if( iTicks < 1) iTicks = 1;
if( iTicks > ROWS_PER_BEAT ) iTicks = ROWS_PER_BEAT;
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
if(iTicks >= 1 && iTicks <= ROWS_PER_BEAT ) // Constants
{
// LOG->Trace( "Adding a tickcount segment: beat: %f, ticks = %d", fTickcountBeat, iTicks );
//out.AddTickcountSegment( new_seg );
}
else
{
//LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid tickcount at beat %f, ticks %d.", fTickcountBeat, iTicks );
//LOG->UserLog( "Song file", "(UNKNOWN)", "Clamping tickcount value to %d at beat %f.", iTicks, fTickcountBeat);
//etc
}
}
ProcessTickcounts(out, sParams[1]);
}
// Ensure all of the warps are handled right.
sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end());
@@ -457,8 +549,8 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
return false;
}
out.m_Timing.m_sFile = sPath;
LoadTimingFromSMFile( msd, out.m_Timing );
out.m_SongTiming.m_sFile = sPath;
LoadTimingFromSMFile( msd, out.m_SongTiming );
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
@@ -512,19 +604,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName=="INSTRUMENTTRACK" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
ProcessInstrumentTracks( out, sParams[1] );
}
else if( sValueName=="MUSICLENGTH" )
@@ -617,27 +697,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParams[1], ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
ProcessBGChanges( out, sValueName, sPath, sParams[1]);
}
else if( sValueName=="FGCHANGES" )
@@ -661,47 +721,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
// Attacks loaded from file
else if( sValueName=="ATTACKS" )
{
// Build the RString vector here so we can write it to file again later
for( unsigned s=1; s < sParams.params.size(); ++s )
out.m_sAttackString.push_back( sParams[s] );
Attack attack;
float end = -9999;
for( unsigned j=1; j < sParams.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("LEN") )
attack.fSecsRemaining = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("END") )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
attack.sModifiers = sBits[1];
if( end != -9999 )
{
attack.fSecsRemaining = end - attack.fStartSecond;
end = -9999;
}
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
}
}
ProcessAttacks( out, sParams );
}
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -712,7 +732,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
continue;
}
Steps* pNewNotes = new Steps;
Steps* pNewNotes = out.CreateSteps();
LoadFromSMTokens(
sParams[1],
sParams[2],
@@ -733,7 +753,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
}
TidyUpData( out, bFromCache );
return true;
}
@@ -846,7 +866,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath
if( !bAddStepsToSong )
return true;
Steps* pNewNotes = new Steps;
Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMTokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
@@ -926,6 +946,7 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache )
bg.push_back( BackgroundChange(song.m_fLastBeat,song.m_sBackgroundFile) );
} while(0);
}
song.TidyUpData();
}
/*
+19 -1
View File
@@ -3,11 +3,18 @@
#include "GameConstantsAndTypes.h"
#include "BackgroundUtil.h"
#include "MsdFile.h" // we require the struct from here.
class MsdFile;
class Song;
class Steps;
class TimingData;
/**
* @brief The highest allowable speed before Warps come in.
*
* This was brought in from StepMania 4's recent betas. */
const float FAST_BPM_WARP = 9999999.f;
/** @brief Reads a Song from an .SM file. */
namespace SMLoader
{
@@ -25,6 +32,17 @@ namespace SMLoader
bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression );
bool ProcessBPMs( TimingData &, const RString );
void ProcessStops( TimingData &, const RString );
void ProcessDelays( TimingData &, const RString );
void ProcessTimeSignatures( TimingData &, const RString );
void ProcessTickcounts( TimingData &, const RString );
void ProcessBGChanges( Song &out, const RString &sValueName,
const RString &sPath, const RString &sParam );
void ProcessAttacks( Song &out, MsdFile::value_t sParams );
void ProcessInstrumentTracks( Song &out, const RString &sParam );
}
#endif
+484 -389
View File
@@ -12,12 +12,379 @@
#include "Song.h"
#include "SongManager.h"
#include "Steps.h"
#include "Attack.h"
/**
* @brief A custom .edit file can only be so big before we have to reject it.
*/
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60 KB
bool SMALoader::LoadFromBGChangesString( BackgroundChange &change,
const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString(change, sBGChangeExpression);
}
bool SMALoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SMA file. Only one SMA file is allowed per song." );
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSMAFile( sPath + aFileNames[0], out );
}
float SMALoader::RowToBeat( RString sLine, const int iRowsPerBeat )
{
if( sLine.find("R") || sLine.find("r") )
{
sLine = sLine.Left(sLine.size()-1);
return StringToFloat( sLine ) / iRowsPerBeat;
}
else
{
return StringToFloat( sLine );
}
}
bool SMALoader::ProcessBPMs( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayBPMChangeExpressions;
split( sParam, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
bool bNotEmpty = false;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #BPMs value \"%s\" (must have exactly one '='), ignored.",
arrayBPMChangeExpressions[b].c_str() );
continue;
}
bNotEmpty = true;
const float fBeat = RowToBeat( arrayBPMChangeValues[0], iRowsPerBeat );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
return bNotEmpty;
}
void SMALoader::ProcessStops( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayFreezeExpressions;
split( sParam, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = RowToBeat( arrayFreezeValues[0], iRowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
}
}
void SMALoader::ProcessDelays( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayDelayExpressions;
split( sParam, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = RowToBeat( arrayDelayValues[0], iRowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
void SMALoader::ProcessTickcounts( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayTickcountExpressions;
split( sParam, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = RowToBeat( arrayTickcountValues[0], iRowsPerBeat );
int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
}
}
void SMALoader::ProcessMultipliers( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayMultiplierExpressions;
split( sParam, ",", arrayMultiplierExpressions );
for( unsigned f=0; f<arrayMultiplierExpressions.size(); f++ )
{
vector<RString> arrayMultiplierValues;
split( arrayMultiplierExpressions[f], "=", arrayMultiplierValues );
if( arrayMultiplierValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #MULTIPLIER value \"%s\" (must have exactly one '='), ignored.",
arrayMultiplierExpressions[f].c_str() );
continue;
}
const float fComboBeat = RowToBeat( arrayMultiplierValues[0], iRowsPerBeat );
const int iCombos = StringToInt( arrayMultiplierValues[1] );
ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos );
out.AddComboSegment( new_seg );
}
}
void SMALoader::ProcessBeatsPerMeasure( TimingData &out, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid beats per measure change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), 4 );
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat );
continue;
}
if( seg.m_iNumerator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
}
float BeatToSeconds(float fromBeat, RString toSomething)
{
return 0;
}
void SMALoader::ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( RowToBeat(vs2[0], iRowsPerBeat) == 0 && vs2.size() == 2 ) // First one always seems to have 2.
{
vs2.push_back("0");
}
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = RowToBeat( vs2[0], iRowsPerBeat );
unsigned short tmp = ( (vs2[2].find("s") || vs2[2].find("S") )
? 1 : 0);
SpeedSegment seg( fBeat, StringToFloat( vs2[1] ), StringToFloat( vs2[2] ), tmp);
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f.", fBeat );
continue;
}
if( seg.m_fWait < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f, fWait %f.", fBeat, seg.m_fWait );
continue;
}
out.AddSpeedSegment( seg );
}
}
void SMALoader::ProcessFakes( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayFakeExpressions;
split( sParam, ",", arrayFakeExpressions );
for( unsigned b=0; b<arrayFakeExpressions.size(); b++ )
{
vector<RString> arrayFakeValues;
split( arrayFakeExpressions[b], "=", arrayFakeValues );
// XXX: Hard to tell which file caused this.
if( arrayFakeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #FAKES value \"%s\" (must have exactly one '='), ignored.",
arrayFakeExpressions[b].c_str() );
continue;
}
const float fBeat = RowToBeat( arrayFakeValues[0], iRowsPerBeat );
const float fNewBeat = StringToFloat( arrayFakeValues[1] );
if(fNewBeat > 0)
out.AddFakeSegment( FakeSegment(fBeat, fNewBeat) );
else
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Fake at beat %f, BPM %f.", fBeat, fNewBeat );
}
}
}
void SMALoader::LoadFromSMATokens(
RString sStepsType,
RString sDescription,
@@ -28,73 +395,9 @@ void SMALoader::LoadFromSMATokens(
Steps &out
)
{
// we're loading from disk, so this is by definition already saved:
out.SetSavedToDisk( true );
Trim( sStepsType );
Trim( sDescription );
Trim( sDifficulty );
Trim( sNoteData );
// LOG->Trace( "Steps::LoadFromSMTokens()" );
// insert stepstype hacks from GameManager.cpp here? -aj
out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType );
out.SetDescription( sDescription );
out.SetCredit( sDescription ); // this is often used for both.
out.SetDifficulty( StringToDifficulty(sDifficulty) );
sDescription.MakeLower();
// Handle hacks that originated back when StepMania didn't have
// Difficulty_Challenge. (At least v1.64, possibly v3.0 final...)
if( out.GetDifficulty() == Difficulty_Hard )
{
// HACK: SMANIAC used to be Difficulty_Hard with a special description.
if( sDescription == "smaniac" )
out.SetDifficulty( Difficulty_Challenge );
// HACK: CHALLENGE used to be Difficulty_Hard with a special description.
if( sDescription == "challenge" )
out.SetDifficulty( Difficulty_Challenge );
}
out.SetMeter( StringToInt(sMeter) );
vector<RString> saValues;
split( sRadarValues, ",", saValues, true );
int categories = NUM_RadarCategory - 1; // Fakes aren't counted in the radar values.
if( saValues.size() == (unsigned)categories * NUM_PLAYERS )
{
RadarValues v[NUM_PLAYERS];
FOREACH_PlayerNumber( pn )
{
// Can't use the foreach anymore due to flexible radar lines.
for( RadarCategory rc = (RadarCategory)0; rc < categories;
enum_add<RadarCategory>( rc, 1 ) )
{
v[pn][rc] = StringToFloat( saValues[pn*categories + rc] );
}
}
out.SetCachedRadarValues( v );
}
out.SetSMNoteData( sNoteData );
out.TidyUpData();
}
bool SMALoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SMA file. There can be only one!" );
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSMAFile( sPath + aFileNames[0], out );
SMLoader::LoadFromSMTokens( sStepsType, sDescription,
sDifficulty, sMeter, sRadarValues,
sNoteData, out );
}
void SMALoader::TidyUpData( Song &song, bool bFromCache )
@@ -113,8 +416,12 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
return false;
}
out.m_Timing.m_sFile = sPath;
LoadTimingFromSMAFile( msd, out.m_Timing );
out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing.
int state = SMA_GETTING_SONG_INFO;
Steps* pNewNotes = NULL;
TimingData stepsTiming;
int iRowsPerBeat = -1; // Start with an invalid value: needed for checking.
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
@@ -168,19 +475,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
else if( sValueName=="INSTRUMENTTRACK" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
SMLoader::ProcessInstrumentTracks( out, sParams[1] );
}
else if( sValueName=="MUSICLENGTH" )
@@ -202,7 +497,8 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
else if( sValueName=="LASTBEAT" )
{
; }
;
}
else if( sValueName=="SONGFILENAME" )
{
;
@@ -226,6 +522,11 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
//else if( sValueName=="SAMPLEPATH" )
//out.m_sMusicSamplePath = sParams[1];
else if( sValueName=="LISTSORT" )
{
;
}
else if( sValueName=="DISPLAYBPM" )
{
// #DISPLAYBPM:[xxx][xxx:xxx]|[*];
@@ -242,6 +543,41 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
}
else if( sValueName=="SMAVERSION" )
{
; // ignore it.
}
else if( sValueName=="ROWSPERBEAT" )
{
/* This value is used to help translate the timings
* the SMA format uses. Starting with the second
* appearance, it delimits NoteData. Right now, this
* value doesn't seem to be editable in SMA. When it
* becomes so, make adjustments to this code. */
if( iRowsPerBeat < 0 )
{
vector<RString> arrayBeatChangeExpressions;
split( sParams[1], ",", arrayBeatChangeExpressions );
vector<RString> arrayBeatChangeValues;
split( arrayBeatChangeExpressions[0], "=", arrayBeatChangeValues );
iRowsPerBeat = StringToInt(arrayBeatChangeValues[1]);
}
else
{
state = SMA_GETTING_STEP_INFO;
pNewNotes = new Steps;
}
}
else if( sValueName=="BEATSPERMEASURE" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBeatsPerMeasure( timing, sParams[1] );
}
else if( sValueName=="SELECTABLE" )
{
if(sParams[1].EqualsNoCase("YES"))
@@ -265,27 +601,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
{
BackgroundLayer iLayer = BACKGROUND_LAYER_1;
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParams[1], ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
SMLoader::ProcessBGChanges( out, sValueName, sPath, sParams[1]);
}
else if( sValueName=="FGCHANGES" )
@@ -301,6 +617,65 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
}
}
else if( sValueName=="OFFSET" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
timing.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="BPMS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBPMs( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessStops( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="DELAYS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessDelays( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="TICKCOUNT" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessTickcounts( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="SPEED" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessSpeeds( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="MULTIPLIER" )
{
ProcessMultipliers( pNewNotes->m_Timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="FAKES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessFakes( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="METERTYPE" )
{
; // We don't use this...yet.
}
else if( sValueName=="KEYSOUNDS" )
{
split( sParams[1], ",", out.m_vsKeysoundFile );
@@ -309,47 +684,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
// Attacks loaded from file
else if( sValueName=="ATTACKS" )
{
// Build the RString vector here so we can write it to file again later
for( unsigned s=1; s < sParams.params.size(); ++s )
out.m_sAttackString.push_back( sParams[s] );
Attack attack;
float end = -9999;
for( unsigned j=1; j < sParams.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("LEN") )
attack.fSecsRemaining = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("END") )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
attack.sModifiers = sBits[1];
if( end != -9999 )
{
attack.fSecsRemaining = end - attack.fStartSecond;
end = -9999;
}
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
}
}
SMLoader::ProcessAttacks( out, sParams );
}
else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -360,7 +695,6 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
continue;
}
Steps* pNewNotes = new Steps;
LoadFromSMATokens(
sParams[1],
sParams[2],
@@ -372,16 +706,13 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
out.AddSteps( pNewNotes );
}
/*
* We used to check for timing data in this section. That has
* since been moved to a dedicated function.
*/
else if( sValueName=="OFFSET" || sValueName=="BPMS" || sValueName=="STOPS" || sValueName=="FREEZES" || sValueName=="DELAYS" || sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" || sValueName=="TICKCOUNTS" )
else if( sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" )
;
else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
}
TidyUpData(out, false);
out.TidyUpData();
return true;
}
@@ -390,236 +721,6 @@ void SMALoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.sma"), out );
}
bool SMALoader::LoadTimingFromFile( const RString &fn, TimingData &out )
{
MsdFile msd;
if( !msd.ReadFile( fn, true ) ) // unescape
{
LOG->UserLog( "Song file", fn, "couldn't be loaded: %s", msd.GetError().c_str() );
return false;
}
out.m_sFile = fn;
LoadTimingFromSMAFile( msd, out );
return true;
}
void SMALoader::LoadTimingFromSMAFile( const MsdFile &msd, TimingData &out )
{
out.m_fBeat0OffsetInSeconds = 0;
out.m_BPMSegments.clear();
out.m_StopSegments.clear();
out.m_WarpSegments.clear();
out.m_vTimeSignatureSegments.clear();
vector<WarpSegment> arrayWarpsFromNegativeBPMs;
//vector<WarpSegment> arrayWarpsFromNegativeStops;
int rowsPerMeasure = 0;
bool encountered = false;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
if( sValueName=="ROWSPERBEAT")
{
if( encountered )
{
break;
}
encountered = true;
rowsPerMeasure = StringToInt( sParams[1] );
}
else if( sValueName=="BEATSPERMEASURE" )
{
TimeSignatureSegment new_seg;
new_seg.m_iStartRow = 0;
new_seg.m_iNumerator = StringToInt( sParams[1] );
new_seg.m_iDenominator = 4;
out.AddTimeSignatureSegment( new_seg );
}
else if( sValueName=="OFFSET" )
{
out.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="STOPS" )
{
vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions );
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayFreezeExpressions[f].c_str() );
continue;
}
float fFreezeBeat = 0;
RString beat = arrayFreezeValues[0];
if( beat.Right(0).MakeUpper() == "R" )
{
beat = beat.Left(beat.size()-1);
fFreezeBeat = StringToFloat( beat ) / rowsPerMeasure;
}
else
{
fFreezeBeat = StringToFloat(beat);
}
//float fFreezeBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
if(fFreezeSeconds > 0.0f)
{
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
out.AddStopSegment( new_seg );
}
else
{
// negative stops (hi JS!) -aj
if( PREFSMAN->m_bQuirksMode )
{
// LOG->Trace( "Adding a negative freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
out.AddStopSegment( new_seg );
}
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid stop at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
}
else if( sValueName=="BPMS" )
{
vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions );
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() );
continue;
}
float fBeat = 0;
RString beat = arrayBPMChangeValues[0];
if( beat.Right(0).MakeUpper() == "R" )
{
beat = beat.Left(beat.size()-1);
fBeat = StringToFloat( beat ) / rowsPerMeasure;
}
else
{
fBeat = StringToFloat(beat);
}
//float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
// XXX: Remove Negatives Bug?
BPMSegment new_seg;
new_seg.m_iStartRow = BeatToNoteRow(fBeat);
new_seg.SetBPM( fNewBPM );
// convert negative BPMs into Warp segments
if( fNewBPM < 0.0f )
{
vector<RString> arrayNextBPMChangeValues;
// get next bpm in sequence
if((b+1) < arrayBPMChangeExpressions.size())
{
split( arrayBPMChangeExpressions[b+1], "=", arrayNextBPMChangeValues );
const float fNextPositiveBeat = StringToFloat( arrayNextBPMChangeValues[0] );
const float fNextPositiveBPM = StringToFloat( arrayNextBPMChangeValues[1] );
// tJumpPos = (tPosBPS-abs(negBPS)) + (gPosBPMPosition - fNegPosition)
float fDeltaBeat = ((fNextPositiveBPM/60.0f)-abs(fNewBPM/60.0f)) + (fNextPositiveBeat-fBeat);
//float fWarpLengthBeats = fNextPositiveBeat + fDeltaBeat;
WarpSegment wsTemp(BeatToNoteRow(fBeat),fDeltaBeat);
arrayWarpsFromNegativeBPMs.push_back(wsTemp);
/*
LOG->Trace( ssprintf("==NotesLoSM negbpm==\nfnextposbeat = %f, fnextposbpm = %f,\nfdelta = %f, fwarpto = %f",
fNextPositiveBeat,
fNextPositiveBPM,
fDeltaBeat,
fWarpToBeat
) );
*/
/*
LOG->Trace( ssprintf("==Negative/Subtractive BPM in NotesLoader==\nNegBPM has noterow = %i, BPM = %f\nNextBPM @ noterow %i\nDelta value = %i noterows\nThis warp will have us end up at noterow %i",
BeatToNoteRow(fBeat), fNewBPM,
BeatToNoteRow(fNextPositiveBeat),
BeatToNoteRow(fDeltaBeat),
BeatToNoteRow(fWarpToBeat))
);
*/
//float fDeltaBeat = ((fNextPositiveBPM/60.0f)-abs(fNewBPM/60.0f)) + (fNextPositiveBeat-fBeat);
/*
LOG->Trace( ssprintf("==NotesLoader Delta as NoteRows==\nfDeltaBeat = %f (beat)\nfDeltaBeat = (NextBPMSeg %f - abs(fBPS %f)) + (nextStartRow %i - thisRow %i)",
fDeltaBeat,(fNextPositiveBPM/60.0f),abs(fNewBPM/60.0f),BeatToNoteRow(fNextPositiveBeat),BeatToNoteRow(fBeat))
);
*/
out.AddBPMSegment( new_seg );
continue;
}
else
{
// last BPM is a negative one? ugh. -aj (MAX_NOTE_ROW exists btw)
out.AddBPMSegment( new_seg );
}
}
if(fNewBPM > 0.0f)
out.AddBPMSegment( new_seg );
else
{
out.m_bHasNegativeBpms = true;
// only add Negative BPMs in quirks mode -aj
if( PREFSMAN->m_bQuirksMode )
out.AddBPMSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM );
}
}
}
// Note: Even though it is possible to have Negative BPMs and Stops in
// a song along with Warps, we should not support files that contain
// both styles of warp tricks (Negatives vs. #WARPS).
// If Warps have been populated from Negative BPMs, then go through that
// instead of using the data in the Warps tag. This should be above,
// but it breaks compiling so...
if(arrayWarpsFromNegativeBPMs.size() > 0)
{
// zomg we already have some warps...
for( unsigned j=0; j<arrayWarpsFromNegativeBPMs.size(); j++ )
{
out.AddWarpSegment( arrayWarpsFromNegativeBPMs[j] );
}
}
// warp sorting will need to take place.
//sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end());
}
}
bool SMALoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{
LOG->Trace( "SMALoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
@@ -702,7 +803,7 @@ bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
if( !bAddStepsToSong )
return true;
Steps* pNewNotes = new Steps;
Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMATokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes);
@@ -730,12 +831,6 @@ bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
return true;
}
bool SMALoader::LoadFromBGChangesString( BackgroundChange &change,
const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString(change, sBGChangeExpression);
}
/**
* @file
* @author Aldo Fregoso, Jason Felds (c) 2009-2011
+22 -2
View File
@@ -9,6 +9,16 @@ class Song;
class Steps;
class TimingData;
/**
* @brief The various states while parsing a .sma file.
*/
enum SMALoadingStates
{
SMA_GETTING_SONG_INFO, /**< Retrieving song information. */
SMA_GETTING_STEP_INFO, /**< Retrieving step information. */
NUM_SMALoadingStates /**< The number of states used. */
};
/** @brief Reads a Song from a .SMA file. */
namespace SMALoader
{
@@ -25,12 +35,22 @@ namespace SMALoader
bool LoadFromSMAFile( const RString &sPath, Song &out );
void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadTimingFromFile( const RString &fn, TimingData &out );
void LoadTimingFromSMAFile( const MsdFile &msd, TimingData &out );
bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression );
void ProcessBeatsPerMeasure( TimingData &out, const RString sParam );
bool ProcessBPMs( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessStops( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessDelays( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessTickcounts( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessMultipliers( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessFakes( TimingData &out, const int iRowsPerBeat, const RString sParam );
float RowToBeat( RString sLine, const int iRowsPerBeat );
};
#endif
+271 -535
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -16,8 +16,6 @@ enum SSCLoadingStates
{
GETTING_SONG_INFO, /**< Retrieving song information. */
GETTING_STEP_INFO, /**< Retrieving step information. */
GETTING_STEP_TIMING_INFO, /**< Retrieving a step's individual timing information. */
GETTING_NOTE_INFO, /**< Retrieving the specific notes. This state may be deprecated. */
NUM_SSCLoadingStates /**< The number of states used. */
};
@@ -25,6 +23,8 @@ enum SSCLoadingStates
const float VERSION_RADAR_FAKE = 0.53f;
/** @brief The version where WarpSegments started to be utilized. */
const float VERSION_WARP_SEGMENT = 0.56f;
/** @brief The version that formally introduced Split Timing. */
const float VERSION_SPLIT_TIMING = 0.7f;
/**
* @brief The SSCLoader handles all of the parsing needed for .ssc files.
@@ -75,6 +75,13 @@ namespace SSCLoader
* @param bFromCache a flag to determine if this song is loaded from a cache file.
*/
void TidyUpData( Song &song, bool bFromCache );
void ProcessWarps( TimingData &, const RString, const float );
void ProcessLabels( TimingData &, const RString );
void ProcessCombos( TimingData &, const RString );
void ProcessSpeeds( TimingData &, const RString );
void ProcessFakes( TimingData &, const RString );
}
#endif
/**
+11 -11
View File
@@ -350,10 +350,10 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
/* Write transliterations, if we have them, since DWI doesn't support UTF-8. */
f.PutLine( ssprintf("#TITLE:%s;", DwiEscape(out.GetTranslitFullTitle()).c_str()) );
f.PutLine( ssprintf("#ARTIST:%s;", DwiEscape(out.GetTranslitArtist()).c_str()) );
ASSERT( out.m_Timing.m_BPMSegments[0].m_iStartRow == 0 );
ASSERT( out.m_SongTiming.m_BPMSegments[0].m_iStartRow == 0 );
f.PutLine( ssprintf("#FILE:%s;", DwiEscape(out.m_sMusicFile).c_str()) );
f.PutLine( ssprintf("#BPM:%.3f;", out.m_Timing.m_BPMSegments[0].GetBPM()) );
f.PutLine( ssprintf("#GAP:%ld;", -lrintf( out.m_Timing.m_fBeat0OffsetInSeconds*1000 )) );
f.PutLine( ssprintf("#BPM:%.3f;", out.m_SongTiming.m_BPMSegments[0].GetBPM()) );
f.PutLine( ssprintf("#GAP:%ld;", -lrintf( out.m_SongTiming.m_fBeat0OffsetInSeconds*1000 )) );
f.PutLine( ssprintf("#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds) );
f.PutLine( ssprintf("#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds) );
if( out.m_sCDTitleFile.size() )
@@ -374,29 +374,29 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
break;
}
if( !out.m_Timing.m_StopSegments.empty() )
if( !out.m_SongTiming.m_StopSegments.empty() )
{
f.Write( "#FREEZE:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
f.Write( ssprintf("%.3f=%.3f", fs.m_iStartRow * 4.0f / ROWS_PER_BEAT,
roundf(fs.m_fStopSeconds*1000)) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
}
if( out.m_Timing.m_BPMSegments.size() > 1)
if( out.m_SongTiming.m_BPMSegments.size() > 1)
{
f.Write( "#CHANGEBPM:" );
for( unsigned i=1; i<out.m_Timing.m_BPMSegments.size(); i++ )
for( unsigned i=1; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.Write( ssprintf("%.3f=%.3f", bs.m_iStartRow * 4.0f / ROWS_PER_BEAT, bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
+2 -2
View File
@@ -137,7 +137,7 @@ bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWr
root["LyricsFile"] = out.m_sLyricsFile;
root["CDTitle"] = out.m_sCDTitleFile;
root["Music"] = out.m_sMusicFile;
root["Offset"] = out.m_Timing.m_fBeat0OffsetInSeconds;
root["Offset"] = out.m_SongTiming.m_fBeat0OffsetInSeconds;
root["SampleStart"] = out.m_fMusicSampleStartSeconds;
root["SampleLength"] = out.m_fMusicSampleLengthSeconds;
if( out.m_SelectionDisplay == Song::SHOW_ALWAYS )
@@ -161,7 +161,7 @@ bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWr
root["SpecifiedBpmMax"] = out.m_fSpecifiedBPMMax;
}
Serialize( out.m_Timing, root["TimingData"] );
Serialize( out.m_SongTiming, root["TimingData"] );
JsonUtil::SerializeVectorObjects( out.m_LyricSegments, Serialize, root["LyricSegments"] );
{
+38 -78
View File
@@ -48,7 +48,7 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
* @brief Write out the common tags for .SM files.
* @param f the file in question.
* @param out the Song in question. */
static void WriteGlobalTags( RageFile &f, const Song &out )
static void WriteGlobalTags( RageFile &f, Song &out )
{
f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) );
f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) );
@@ -63,20 +63,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) );
f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) );
f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) );
{
vector<RString> vs;
FOREACH_ENUM( InstrumentTrack, it )
if( out.HasInstrumentTrack(it) )
vs.push_back( InstrumentTrackToString(it) +
"=" + out.m_sInstrumentTrackFile[it] );
if( !vs.empty() )
{
RString s = join( ",", vs );
f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" );
}
}
f.PutLine( ssprintf( "#OFFSET:%.3f;", out.m_Timing.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#OFFSET:%.3f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds ) );
f.PutLine( ssprintf( "#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds ) );
if( out.m_fSpecifiedLastBeat > 0 )
@@ -111,82 +98,55 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
for( unsigned i=0; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
unsigned wSize = out.m_SongTiming.m_WarpSegments.size();
if( wSize > 0 )
{
for( unsigned i=0; i < wSize; i++ )
{
int iRow = out.m_SongTiming.m_WarpSegments[i].m_iStartRow;
float fBPS = 60 / out.m_SongTiming.GetBPMAtRow(iRow);
float fSkip = fBPS * out.m_SongTiming.m_WarpSegments[i].m_fLengthBeats;
StopSegment ss;
ss.m_iStartRow = iRow;
ss.m_fStopSeconds = -fSkip;
ss.m_bDelay = false; // Best to be sure.
out.m_SongTiming.AddStopSegment( ss );
}
}
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
int iRow = fs.m_iStartRow;
float fBeat = NoteRowToBeat(!fs.m_bDelay ? iRow : iRow - 1);
if(!fs.m_bDelay)
{
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.PutLine( ssprintf( "%.3f=%.3f", fBeat, fs.m_fStopSeconds ) );
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," );
if( fs.m_fStopSeconds < 0 )
{
out.m_SongTiming.m_StopSegments.erase(
out.m_SongTiming.m_StopSegments.begin()+i,
out.m_SongTiming.m_StopSegments.begin()+i+1 );
i--;
}
}
}
f.PutLine( ";" );
f.Write( "#ATTACKS:" );
for( unsigned j = 0; j < out.m_Attacks.size(); j++ )
{
const Attack &a = out.m_Attacks[j];
f.Write( ssprintf( "TIME=%.2f:LEN=%.2f:MODS=%s",
a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) );
if( j+1 < out.m_Attacks.size() )
f.Write( ":" );
f.PutLine( "" );
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_Timing.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.3f=%d=%d", NoteRowToBeat(iter->m_iStartRow),
iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_Timing.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_Timing.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.3f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_Timing.m_TickcountSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
FOREACH_BackgroundLayer( b )
{
if( b==0 )
@@ -277,8 +237,8 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
lines.push_back( ssprintf( " %d:", in.GetMeter() ) );
vector<RString> asRadarValues;
// SM files don't use fakes for radar data. Keep it that way.
int categories = NUM_RadarCategory - 1;
// OpenITG simfiles use 11 radar categories.
int categories = 11;
FOREACH_PlayerNumber( pn )
{
const RadarValues &rv = in.GetRadarValues( pn );
@@ -300,7 +260,7 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
return JoinLineList( lines );
}
bool NotesWriterSM::Write( RString sPath, const Song &out, const vector<Steps*>& vpStepsToSave )
bool NotesWriterSM::Write( RString sPath, Song &out, const vector<Steps*>& vpStepsToSave )
{
int flags = RageFile::WRITE;
+1 -1
View File
@@ -11,7 +11,7 @@ namespace NotesWriterSM
* @param sPath the path to write the file.
* @param out the Song to be written out.
* @return its success or failure. */
bool Write( RString sPath, const Song &out, const vector<Steps*>& vpStepsToSave );
bool Write( RString sPath, Song &out, const vector<Steps*>& vpStepsToSave );
/**
* @brief Get some contents about the edit file first.
* @param pSong the Song in question.
+130 -130
View File
@@ -42,7 +42,128 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
}
/**
* @brief Write out the common tags for .SM files.
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
// A utility class to write timing tags more easily!
struct TimingTagWriter {
vector<RString> *m_pvsLines;
RString m_sNext;
TimingTagWriter( vector<RString> *pvsLines ): m_pvsLines (pvsLines) { }
void Write( const int row, const char *value )
{
m_pvsLines->push_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) );
m_sNext = ",";
}
void Write( const int row, const float value ) { Write( row, ssprintf( "%.6f", value ) ); }
void Write( const int row, const int value ) { Write( row, ssprintf( "%d", value ) ); }
void Write( const int row, const int a, const int b ) { Write( row, ssprintf( "%d=%d", a, b ) ); }
void Write( const int row, const float a, const float b ) { Write( row, ssprintf( "%.6f=%.6f", a, b) ); }
void Write( const int row, const float a, const float b, const unsigned short c )
{ Write( row, ssprintf( "%.6f=%.6f=%hd", a, b, c) ); }
void Init( const RString sTag ) { m_sNext = "#" + sTag + ":"; }
void Finish( ) { m_pvsLines->push_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); }
};
static void GetTimingTags( vector<RString> &lines, TimingData timing, bool bIsSong = false )
{
TimingTagWriter w ( &lines );
timing.TidyUpData();
w.Init( "BPMS" );
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, bs )
w.Write( bs->m_iStartRow, bs->GetBPM() );
w.Finish();
w.Init( "STOPS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( !ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "DELAYS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "WARPS" );
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, ws )
w.Write( ws->m_iStartRow, ws->m_fLengthBeats );
w.Finish();
ASSERT( !timing.m_vTimeSignatureSegments.empty() );
w.Init( "TIMESIGNATURES" );
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
w.Write( iter->m_iStartRow, iter->m_iNumerator, iter->m_iDenominator );
w.Finish();
ASSERT( !timing.m_TickcountSegments.empty() );
w.Init( "TICKCOUNTS" );
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, ts )
w.Write( ts->m_iStartRow, ts->m_iTicks );
w.Finish();
ASSERT( !timing.m_ComboSegments.empty() );
w.Init( "COMBOS" );
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, cs )
w.Write( cs->m_iStartRow, cs->m_iCombo );
w.Finish();
// Song Timing should only have the initial value.
w.Init( "SPEEDS" );
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, ss )
w.Write( ss->m_iStartRow, ss->m_fPercent, ss->m_fWait, ss->m_usMode );
w.Finish();
if( !bIsSong )
{
w.Init( "FAKES" );
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, fs )
w.Write( fs->m_iStartRow, fs->m_fLengthBeats );
w.Finish();
}
w.Init( "LABELS" );
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, ls )
w.Write( ls->m_iStartRow, ls->m_sLabel.c_str() );
w.Finish();
}
static void WriteTimingTags( RageFile &f, const TimingData &timing, bool bIsSong = false )
{
vector<RString> lines;
GetTimingTags( lines, timing, bIsSong );
f.PutLine( JoinLineList( lines ) );
}
/**
* @brief Write out the common tags for .SSC files.
* @param f the file in question.
* @param out the Song in question. */
static void WriteGlobalTags( RageFile &f, const Song &out )
@@ -74,7 +195,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" );
}
}
f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_Timing.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) );
f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) );
if( out.m_fSpecifiedLastBeat > 0 )
@@ -106,105 +227,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
break;
}
f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if(!fs.m_bDelay)
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#WARPS:" );
for( unsigned i=0; i<out.m_Timing.m_WarpSegments.size(); i++ )
{
const WarpSegment &ws = out.m_Timing.m_WarpSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(ws.m_iStartRow), ws.m_fEndBeat ) );
if( i != out.m_Timing.m_WarpSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_Timing.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.6f=%d=%d", NoteRowToBeat(iter->m_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_Timing.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_Timing.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_Timing.m_TickcountSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_ComboSegments.empty() );
f.Write( "#COMBOS:" );
for( unsigned i=0; i<out.m_Timing.m_ComboSegments.size(); i++ )
{
const ComboSegment &cs = out.m_Timing.m_ComboSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(cs.m_iStartRow), cs.m_iCombo ) );
if( i != out.m_Timing.m_ComboSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#LABELS:" );
for( unsigned i=0; i<out.m_Timing.m_LabelSegments.size(); i++ )
{
const LabelSegment &ls = out.m_Timing.m_LabelSegments[i];
f.PutLine( ssprintf( "%.6f=%s", NoteRowToBeat(ls.m_iStartRow), ls.m_sLabel.c_str() ) );
if( i != out.m_Timing.m_LabelSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
WriteTimingTags( f, out.m_SongTiming, true );
FOREACH_BackgroundLayer( b )
{
@@ -258,23 +281,6 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ";" );
}
/**
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
/**
* @brief Retrieve the individual batches of NoteData.
* @param song the Song in question.
@@ -307,19 +313,12 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
/*
* TODO: Remove this block, transplant above code
* below for Split Timing. -Wolfman2000 */
lines.push_back( "#BPMS:;" );
lines.push_back( "#STOPS:;" );
lines.push_back( "#DELAYS:;" );
lines.push_back( "#WARPS:;" );
lines.push_back( "#LABELS:;" );
lines.push_back( "#TIMESIGNATURES:;" );
lines.push_back( "#TICKCOUNTS:;" );
GetTimingTags( lines, in.m_Timing );
// For now, attacks are NOT in use for the step.
lines.push_back( "#ATTACKS:;" );
lines.push_back( "#COMBOS:;" );
lines.push_back( ssprintf( "#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds ) );
RString sNoteData;
in.GetSMNoteData( sNoteData );
@@ -350,6 +349,7 @@ bool NotesWriterSSC::Write( RString sPath, const Song &out, const vector<Steps*>
}
WriteGlobalTags( f, out );
if( bSavingCache )
{
f.PutLine( ssprintf( "// cache tags:" ) );
+44 -33
View File
@@ -506,7 +506,7 @@ void Player::Load()
m_LastTapNoteScore = TNS_None;
// The editor can start playing in the middle of the song.
const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat );
const int iNoteRow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat );
m_iFirstUncrossedRow = iNoteRow - 1;
m_pJudgedRows->Reset( iNoteRow );
@@ -541,6 +541,8 @@ void Player::Load()
if( GAMESTATE->m_pCurGame->m_bAllowHopos )
NoteDataUtil::SetHopoPossibleFlags( pSong, m_NoteData );
m_Timing = &GAMESTATE->m_pCurSteps[pn]->m_Timing;
switch( GAMESTATE->m_PlayMode )
{
case PLAY_MODE_RAVE:
@@ -693,7 +695,7 @@ void Player::Update( float fDeltaTime )
return;
}
const float fSongBeat = GAMESTATE->m_fSongBeat;
const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat );
// Optimization: Don't spend time processing the things below that won't show
@@ -795,7 +797,7 @@ void Player::Update( float fDeltaTime )
// Check for a strum miss
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 &&
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < GAMESTATE->m_fMusicSeconds )
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds )
{
DoStrumMiss();
}
@@ -917,12 +919,12 @@ void Player::Update( float fDeltaTime )
/* We want to send the crossed row message exactly when we cross the row--not
* .5 before the row. Use a very slow song (around 2 BPM) as a test case: without
* rounding, autoplay steps early. -glenn */
const int iRowNow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat );
const int iRowNow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat );
if( iRowNow >= 0 )
{
if( GAMESTATE->IsPlayerEnabled(m_pPlayerState) )
{
if(GAMESTATE->m_bDelay)
if(m_pPlayerState->m_Position.m_bDelay)
{
if( !m_bDelay )
m_bDelay = true;
@@ -1528,7 +1530,7 @@ int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool
// Is this the row we want?
do {
const TapNote &tn = begin->second;
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( begin->first ) )
if( m_Timing->IsWarpAtRow( begin->first ) || m_Timing->IsFakeAtRow( begin->first ) )
break;
if( tn.type == TapNote::empty )
break;
@@ -1579,7 +1581,7 @@ int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool b
++iter;
continue;
}
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) )
if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
{
++iter;
continue;
@@ -1650,7 +1652,7 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
}
// Handle hammer-ons and pull-offs
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago();
int iHopoCol = -1;
bool bDoHopo =
m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 &&
@@ -1703,7 +1705,7 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
// Check if this fret breaks all active holds.
if( !bRelease )
{
const float fSongBeat = GAMESTATE->m_fSongBeat;
const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat );
int iMaxHoldCol = -1;
@@ -1745,7 +1747,7 @@ void Player::Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRel
DoStrumMiss();
}
m_pPlayerState->m_fLastStrumMusicSeconds = GAMESTATE->m_fMusicSeconds;
m_pPlayerState->m_fLastStrumMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds;
StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged );
}
@@ -1794,7 +1796,7 @@ void Player::ScoreAllActiveHoldsLetGo()
{
if( PENALIZE_TAP_SCORE_NONE )
{
const float fSongBeat = GAMESTATE->m_fSongBeat;
const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat );
// Score all active holds to NotHeld
@@ -1857,8 +1859,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
// Do everything that depends on a RageTimer here;
// set your breakpoints somewhere after this block.
const float fLastBeatUpdate = GAMESTATE->m_LastBeatUpdate.Ago();
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
const float fLastBeatUpdate = m_pPlayerState->m_Position.m_LastBeatUpdate.Ago();
const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago();
const float fTimeSinceStep = tm.Ago();
switch( pbt )
@@ -1873,7 +1875,16 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
break;
}
const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat;
float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
if( GAMESTATE->m_pCurSong )
{
fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
if( GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber] )
fSongBeat = m_Timing->GetBeatFromElapsedTime( fPositionSeconds );
}
const int iSongRow = row == -1 ? BeatToNoteRow( fSongBeat ) : row;
if( col != -1 && !bRelease )
@@ -2011,8 +2022,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
* "jack hammers." Hmm.
*/
const int iStepSearchRows = max(
BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds + StepSearchDistance ) ) - iSongRow,
iSongRow - BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds - StepSearchDistance ) )
BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds + StepSearchDistance ) ) - iSongRow,
iSongRow - BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds - StepSearchDistance ) )
) + ROWS_PER_BEAT;
int iRowOfOverlappingNoteOrRow = row;
if( row == -1 )
@@ -2039,7 +2050,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
float fNoteOffset = 0.0f;
// we need this later if we are autosyncing
const float fStepBeat = NoteRowToBeat( iRowOfOverlappingNoteOrRow );
const float fStepSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fStepBeat);
const float fStepSeconds = m_Timing->GetElapsedTimeFromBeat(fStepBeat);
if( row == -1 )
{
@@ -2048,7 +2059,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
/* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure
* out what the music time is as of now. */
const float fCurrentMusicSeconds = GAMESTATE->m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
const float fCurrentMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
// ... which means it happened at this point in the music:
const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
@@ -2089,7 +2100,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
// Stepped too close to mine?
if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) &&
fSecondsFromExact <= GetWindowSeconds(TW_Mine) &&
!GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iSongRow) )
!m_Timing->IsWarpAtRow(iSongRow) && !m_Timing->IsFakeAtRow(iSongRow))
score = TNS_HitMine;
break;
@@ -2557,14 +2568,14 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
{
//LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
int iMissIfOlderThanThisRow;
const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds;
const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds;
{
bool bFreeze, bDelay;
float fMissIfOlderThanThisBeat;
float fThrowAway;
int iWarpBeginRow;
float fWarpLength;
GAMESTATE->m_pCurSong->m_Timing.GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength );
m_Timing->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength );
iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat );
if( bFreeze || bDelay )
@@ -2586,8 +2597,8 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
if( !NeedsTapJudging(tn) )
continue;
// Ignore all notes that are skipped via WARPS.
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) )
// Ignore all notes in WarpSegments or FakeSegments.
if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
continue;
if( tn.type == TapNote::mine )
@@ -2610,7 +2621,7 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
void Player::UpdateJudgedRows()
{
const int iEndRow = BeatToNoteRow( GAMESTATE->m_fSongBeat );
const int iEndRow = BeatToNoteRow( m_pPlayerState->m_Position.m_fSongBeat );
bool bAllJudged = true;
const bool bSeparately = GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately;
@@ -2621,8 +2632,8 @@ void Player::UpdateJudgedRows()
{
int iRow = iter.Row();
// If row is within a warp section, ignore it. -aj
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iRow) )
// Do not judge arrows in WarpSegments or FakeSegments
if( m_Timing->IsWarpAtRow(iRow) || m_Timing->IsFakeAtRow(iRow) )
continue;
if( iLastSeenRow != iRow )
@@ -2843,13 +2854,13 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
int iCheckpointFrequencyRows = ROWS_PER_BEAT/2;
if( CHECKPOINTS_USE_TICKCOUNTS )
{
int tickCurrent = GAMESTATE->m_pCurSong->m_Timing.GetTickcountAtRow( iLastRowCrossed );
int tickCurrent = m_Timing->GetTickcountAtRow( iLastRowCrossed );
// There are some charts that don't want tickcounts involved at all.
iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0);
}
else if( CHECKPOINTS_USE_TIME_SIGNATURES )
{
TimeSignatureSegment tSignature = GAMESTATE->m_pCurSong->m_Timing.GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) );
TimeSignatureSegment tSignature = m_Timing->GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) );
// Most songs are in 4/4 time. The frequency for checking tick counts should reflect that.
iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature.m_iDenominator / (tSignature.m_iNumerator * 4);
@@ -2956,8 +2967,8 @@ void Player::HandleTapRowScore( unsigned row )
bNoCheating = false;
#endif
// Warp hackery. -aj
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( row ) )
// Do not score rows in WarpSegments or FakeSegments
if( m_Timing->IsWarpAtRow( row ) || m_Timing->IsFakeAtRow( row ) )
return;
if( GAMESTATE->m_bDemonstrationOrJukebox )
@@ -3060,8 +3071,8 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH
bNoCheating = false;
#endif
// More warp hackery. -aj
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iRow ) )
// WarpSegments and FakeSegments aren't judged in any way.
if( m_Timing->IsWarpAtRow( iRow ) || m_Timing->IsFakeAtRow( iRow ) )
return;
// don't accumulate combo if AutoPlay is on.
@@ -3257,7 +3268,7 @@ void Player::SetCombo( int iCombo, int iMisses )
}
else
{
bPastBeginning = GAMESTATE->m_fMusicSeconds > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO;
bPastBeginning = m_pPlayerState->m_Position.m_fMusicSeconds > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO;
}
if( m_bSendJudgmentAndComboMessages )
+2
View File
@@ -10,6 +10,7 @@
#include "ScreenMessage.h"
#include "ThemeMetric.h"
#include "InputEventPlus.h"
#include "TimingData.h"
class ScoreDisplay;
class LifeMeter;
@@ -147,6 +148,7 @@ protected:
PlayerState *m_pPlayerState;
/** @brief The player's present stage stats. */
PlayerStageStats *m_pPlayerStageStats;
TimingData *m_Timing;
float m_fNoteFieldHeight;
bool m_bPaused;
+1 -1
View File
@@ -680,7 +680,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const
bool PlayerOptions::IsEasierForSongAndSteps( Song* pSong, Steps* pSteps, PlayerNumber pn ) const
{
if( m_fTimeSpacing && pSong->HasSignificantBpmChangesOrStops() )
if( m_fTimeSpacing && pSteps->HasSignificantTimingChanges() )
return true;
const RadarValues &rv = pSteps->GetRadarValues( pn );
if( m_bTransforms[TRANSFORM_NOHOLDS] && rv[RadarCategory_Holds]>0 )
+2 -1
View File
@@ -35,8 +35,9 @@ public:
m_fPassmark(0), m_SpeedfPassmark(1.0f),
m_fRandomSpeed(0), m_SpeedfRandomSpeed(1.0f),
m_bMuteOnError(false), m_FailType(FAIL_IMMEDIATE),
m_ScoreDisplay(SCORING_ADD), m_sNoteSkin("")
m_ScoreDisplay(SCORING_ADD)
{
m_sNoteSkin = "";
ZERO( m_fAccels ); ONE( m_SpeedfAccels );
ZERO( m_fEffects ); ONE( m_SpeedfEffects );
ZERO( m_fAppearances ); ONE( m_SpeedfAppearances );
+9 -3
View File
@@ -69,8 +69,8 @@ void PlayerState::Update( float fDelta )
bool bCurrentlyEnabled =
attack.bGlobal ||
( attack.fStartSecond < GAMESTATE->m_fMusicSeconds &&
GAMESTATE->m_fMusicSeconds < attack.fStartSecond+attack.fSecsRemaining );
( attack.fStartSecond < m_Position.m_fMusicSeconds &&
m_Position.m_fMusicSeconds < attack.fStartSecond+attack.fSecsRemaining );
if( m_ActiveAttacks[s].bOn == bCurrentlyEnabled )
continue; // OK
@@ -116,7 +116,7 @@ void PlayerState::LaunchAttack( const Attack& a )
* so Player::Update knows to apply attack transforms correctly. (yuck) */
m_ModsToApply.push_back( attack );
if( attack.fStartSecond == -1 )
attack.fStartSecond = GAMESTATE->m_fMusicSeconds;
attack.fStartSecond = m_Position.m_fMusicSeconds;
m_ActiveAttacks.push_back( attack );
RebuildPlayerOptionsFromActiveAttacks();
@@ -197,6 +197,11 @@ class LunaPlayerState: public Luna<PlayerState>
{
public:
DEFINE_METHOD( GetPlayerNumber, m_PlayerNumber );
static int GetSongPosition( T* p, lua_State *L )
{
p->m_Position.PushSelf(L);
return 1;
}
DEFINE_METHOD( GetMultiPlayerNumber, m_mp );
DEFINE_METHOD( GetPlayerController, m_PlayerController );
static int SetPlayerOptions( T* p, lua_State *L )
@@ -246,6 +251,7 @@ public:
ADD_METHOD( GetPlayerOptionsArray );
ADD_METHOD( GetPlayerOptionsString );
ADD_METHOD( GetCurrentPlayerOptions );
ADD_METHOD( GetSongPosition );
ADD_METHOD( GetHealthState );
}
};
+4
View File
@@ -3,6 +3,7 @@
#ifndef PlayerState_H
#define PlayerState_H
#include "SongPosition.h"
#include "Attack.h"
#include "ModsGroup.h"
#include "PlayerNumber.h"
@@ -38,6 +39,9 @@ public:
*/
MultiPlayer m_mp;
// Music statistics:
SongPosition m_Position;
/**
* @brief Change the PlayerOptions to their default.
* @param l the level of mods to reset.
+2 -2
View File
@@ -443,7 +443,7 @@ void ScoreKeeperNormal::HandleComboInternal( int iNumHitContinueCombo, int iNumH
if( iNumBreakCombo == 0 )
{
TimingData td = GAMESTATE->m_pCurSong->m_Timing;
TimingData td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing;
int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo );
m_pPlayerStageStats->m_iCurCombo += iNumHitContinueCombo * multiplier;
}
@@ -463,7 +463,7 @@ void ScoreKeeperNormal::HandleRowComboInternal( TapNoteScore tns, int iNumTapsIn
if ( tns >= m_MinScoreToContinueCombo )
{
m_pPlayerStageStats->m_iCurMissCombo = 0;
TimingData td = GAMESTATE->m_pCurSong->m_Timing;
TimingData td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing;
int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo );
m_pPlayerStageStats->m_iCurCombo += iNumTapsInRow * multiplier;
}
+507 -289
View File
File diff suppressed because it is too large Load Diff
+43 -5
View File
@@ -86,6 +86,7 @@ enum EditButton
EDIT_BUTTON_SNAP_PREV,
EDIT_BUTTON_OPEN_EDIT_MENU,
EDIT_BUTTON_OPEN_TIMING_MENU,
EDIT_BUTTON_OPEN_AREA_MENU,
EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU,
EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU,
@@ -137,6 +138,8 @@ enum EditButton
EDIT_BUTTON_ADD_COURSE_MODS,
EDIT_BUTTON_SWITCH_PLAYERS, /**< Allow entering notes for a different Player. */
EDIT_BUTTON_SWITCH_TIMINGS, /**< Allow switching between Song and Step TimingData. */
NUM_EditButton, // leave this at the end
EditButton_Invalid
@@ -231,6 +234,9 @@ protected:
float GetMaximumBeatForMoving() const; // don't allow Down key to go past this beat.
void DoHelp();
/** @brief Display the TimingData menu for editing song and step timing. */
void DisplayTimingMenu();
EditState m_EditState;
@@ -275,14 +281,22 @@ protected:
/** @brief Has the NoteData been changed such that a user should be prompted to save? */
bool m_bDirty;
/** @brief The sound that is played when a note is added. */
RageSound m_soundAddNote;
/** @brief The sound that is played when a note is removed. */
RageSound m_soundRemoveNote;
RageSound m_soundChangeLine;
RageSound m_soundChangeSnap;
RageSound m_soundMarker;
RageSound m_soundValueIncrease;
RageSound m_soundValueDecrease;
/** @brief The sound that is played when switching players for Routine. */
RageSound m_soundSwitchPlayer;
/** @brief The sound that is played when switching song/step timing. */
RageSound m_soundSwitchTiming;
/** @brief The sound that is played when switching to a different chart. */
RageSound m_soundSwitchSteps;
/** @brief The sound that is played when the chart is saved. */
RageSound m_soundSave;
// used for reverting
@@ -459,7 +473,6 @@ public:
main_title_transliteration,
sub_title_transliteration,
artist_transliteration,
beat_0_offset,
last_beat_hint,
preview_start,
preview_length,
@@ -472,16 +485,21 @@ public:
enum TimingDataInformationChoice
{
beat_0_offset,
bpm,
stop,
delay,
// time_signature,
time_signature_numerator,
time_signature_denominator,
time_signature,
label,
tickcount,
combo,
label,
warp,
// speed,
speed_percent,
speed_wait,
speed_mode,
fake,
erase_step_timing,
NUM_TIMING_DATA_INFORMATION_CHOICES
};
@@ -514,7 +532,19 @@ public:
delete_change,
NUM_BGCHANGE_CHOICES
};
enum SpeedSegmentModes
{
SSMODE_Beats,
SSMODE_Seconds
};
/**
* @brief Take care of any background changes that the user wants.
*
* It is important that this is only called in Song Timing mode.
* @param c the Background Change style requested.
* @param iAnswers the other settings involving the change. */
void HandleBGChangeChoice( BGChangeChoice c, const vector<int> &iAnswers );
enum CourseAttackChoice
@@ -544,6 +574,14 @@ public:
void MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu );
void EditMiniMenu( const MenuDef* pDef, ScreenMessage SM_SendOnOK = SM_None, ScreenMessage SM_SendOnCancel = SM_None );
private:
/**
* @brief Retrieve the appropriate TimingData based on GAMESTATE.
* @return the proper TimingData. */
TimingData & GetAppropriateTiming() const;
void SetBeat(float fBeat);
float GetBeat();
};
#endif
+1 -1
View File
@@ -236,7 +236,7 @@ void ScreenEditMenu::MenuStart( const InputEventPlus &input )
case EditMenuAction_Create:
ASSERT( !pSteps );
{
pSteps = new Steps;
pSteps = pSong->CreateSteps();
switch( m_Selector.EDIT_MODE )
{
+17 -17
View File
@@ -1325,7 +1325,7 @@ void ScreenGameplay::StartPlayingSong( float fMinTimeToNotes, float fMinTimeToMu
{
const float fFirstBeat = GAMESTATE->m_pCurSong->m_fFirstBeat;
const float fFirstSecond = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( fFirstBeat );
const float fFirstSecond = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( fFirstBeat );
float fStartDelay = fMinTimeToNotes - fFirstSecond;
fStartDelay = max( fStartDelay, fMinTimeToMusic );
p.m_StartSecond = -fStartDelay;
@@ -1347,10 +1347,10 @@ void ScreenGameplay::StartPlayingSong( float fMinTimeToNotes, float fMinTimeToMu
m_pSoundMusic->Pause( true );
/* Make sure GAMESTATE->m_fMusicSeconds is set up. */
GAMESTATE->m_fMusicSeconds = -5000;
GAMESTATE->m_Position.m_fMusicSeconds = -5000;
UpdateSongPosition(0);
ASSERT( GAMESTATE->m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */
ASSERT( GAMESTATE->m_Position.m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */
}
@@ -1405,7 +1405,7 @@ void ScreenGameplay::PlayAnnouncer( RString type, float fSeconds )
if( m_DancingState != STATE_DANCING )
return;
if( GAMESTATE->m_pCurSong == NULL || // this will be true on ScreenDemonstration sometimes
GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat )
GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat )
return;
if( m_fTimeSinceLastDancingComment < fSeconds )
@@ -1423,7 +1423,7 @@ void ScreenGameplay::UpdateSongPosition( float fDeltaTime )
RageTimer tm;
const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm );
const float fAdjust = SOUND->GetFrameTimingAdjustment( fDeltaTime );
GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_Timing, tm+fAdjust );
GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_SongTiming, tm+fAdjust, true );
}
void ScreenGameplay::BeginScreen()
@@ -1480,7 +1480,7 @@ bool ScreenGameplay::AllAreFailing()
void ScreenGameplay::GetMusicEndTiming( float &fSecondsToStartFadingOutMusic, float &fSecondsToStartTransitioningOut )
{
float fLastStepSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
float fLastStepSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
fLastStepSeconds += Player::GetMaxStepDistanceSeconds();
float fTransitionLength;
@@ -1687,7 +1687,7 @@ void ScreenGameplay::Update( float fDeltaTime )
// update fGameplaySeconds
STATSMAN->m_CurStageStats.m_fGameplaySeconds += fUnscaledDeltaTime;
if( GAMESTATE->m_fSongBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat && GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
if( GAMESTATE->m_Position.m_fSongBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat && GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
{
STATSMAN->m_CurStageStats.m_fStepsSeconds += fUnscaledDeltaTime;
@@ -1707,7 +1707,7 @@ void ScreenGameplay::Update( float fDeltaTime )
if( bAllReallyFailed )
fSecondsToStartTransitioningOut += BEGIN_FAILED_DELAY;
if( GAMESTATE->m_fMusicSeconds >= fSecondsToStartTransitioningOut && !m_NextSong.IsTransitioning() )
if( GAMESTATE->m_Position.m_fMusicSeconds >= fSecondsToStartTransitioningOut && !m_NextSong.IsTransitioning() )
this->PostScreenMessage( SM_NotesEnded, 0 );
}
@@ -1843,8 +1843,8 @@ void ScreenGameplay::Update( float fDeltaTime )
float ScreenGameplay::GetHasteRate()
{
if( GAMESTATE->m_fMusicSeconds < GAMESTATE->m_fLastHasteUpdateMusicSeconds || // new song
GAMESTATE->m_fMusicSeconds > GAMESTATE->m_fLastHasteUpdateMusicSeconds + 4 )
if( GAMESTATE->m_Position.m_fMusicSeconds < GAMESTATE->m_fLastHasteUpdateMusicSeconds || // new song
GAMESTATE->m_Position.m_fMusicSeconds > GAMESTATE->m_fLastHasteUpdateMusicSeconds + 4 )
{
bool bAnyPlayerHitAllNotes = false;
FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi )
@@ -1865,7 +1865,7 @@ float ScreenGameplay::GetHasteRate()
GAMESTATE->m_fHasteRate += 0.1f;
CLAMP( GAMESTATE->m_fHasteRate, -1.0f, +1.0f );
GAMESTATE->m_fLastHasteUpdateMusicSeconds = GAMESTATE->m_fMusicSeconds;
GAMESTATE->m_fLastHasteUpdateMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
}
/* If the life meter is less than half full, push the haste rate down to let
@@ -1914,7 +1914,7 @@ void ScreenGameplay::UpdateLights()
ZERO( bBlinkGameButton );
bool bCrossedABeat = false;
{
const float fSongBeat = GAMESTATE->m_fLightSongBeat;
const float fSongBeat = GAMESTATE->m_Position.m_fLightSongBeat;
const int iSongRow = BeatToNoteRowNotRounded( fSongBeat );
static int iRowLastCrossed = 0;
@@ -1968,7 +1968,7 @@ void ScreenGameplay::UpdateLights()
}
// Before the first beat of the song, all cabinet lights solid on (except for menu buttons).
bool bOverrideCabinetBlink = (GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat);
bool bOverrideCabinetBlink = (GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat);
FOREACH_CabinetLight( cl )
bBlinkCabinetLight[cl] |= bOverrideCabinetBlink;
@@ -1994,8 +1994,8 @@ void ScreenGameplay::SendCrossedMessages()
{
static int iRowLastCrossed = 0;
float fPositionSeconds = GAMESTATE->m_fMusicSeconds;
float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds );
float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
float fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow );
@@ -2032,8 +2032,8 @@ void ScreenGameplay::SendCrossedMessages()
{
float fNoteWillCrossInSeconds = MESSAGE_SPACING_SECONDS * i;
float fPositionSeconds = GAMESTATE->m_fMusicSeconds + fNoteWillCrossInSeconds;
float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds );
float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds + fNoteWillCrossInSeconds;
float fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow );
+6 -6
View File
@@ -208,7 +208,7 @@ void ScreenHowToPlay::Step()
#define ST_JUMPUD (ST_UP | ST_DOWN)
int iStep = 0;
const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat + 0.6f );
const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat + 0.6f );
// if we want to miss from here on out, don't process steps.
if( m_iW2s < m_iNumW2s && m_NoteData.IsThereATapAtRow( iNoteRow ) )
{
@@ -229,10 +229,10 @@ void ScreenHowToPlay::Step()
m_pmCharacter->PlayAnimation( "Step-JUMPLR", 1.8f );
m_pmCharacter->StopTweening();
m_pmCharacter->BeginTweening( GAMESTATE->m_fCurBPS /8, TWEEN_LINEAR );
m_pmCharacter->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /8, TWEEN_LINEAR );
m_pmCharacter->SetRotationY( 90 );
m_pmCharacter->BeginTweening( (1/(GAMESTATE->m_fCurBPS * 2) ) ); //sleep between jump-frames
m_pmCharacter->BeginTweening( GAMESTATE->m_fCurBPS /6, TWEEN_LINEAR );
m_pmCharacter->BeginTweening( (1/(GAMESTATE->m_Position.m_fCurBPS * 2) ) ); //sleep between jump-frames
m_pmCharacter->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /6, TWEEN_LINEAR );
m_pmCharacter->SetRotationY( 0 );
break;
}
@@ -243,11 +243,11 @@ void ScreenHowToPlay::Update( float fDelta )
{
if( GAMESTATE->m_pCurSong != NULL )
{
GAMESTATE->UpdateSongPosition( m_fFakeSecondsIntoSong, GAMESTATE->m_pCurSong->m_Timing );
GAMESTATE->UpdateSongPosition( m_fFakeSecondsIntoSong, GAMESTATE->m_pCurSong->m_SongTiming );
m_fFakeSecondsIntoSong += fDelta;
static int iLastNoteRowCounted = 0;
int iCurNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat );
int iCurNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat );
if( iCurNoteRow != iLastNoteRowCounted &&m_NoteData.IsThereATapAtRow(iCurNoteRow) )
{
+2 -2
View File
@@ -61,7 +61,7 @@ void ScreenNameEntry::ScrollingText::Init( const RString &sName, const vector<fl
void ScreenNameEntry::ScrollingText::DrawPrimitives()
{
const float fFakeBeat = GAMESTATE->m_fSongBeat;
const float fFakeBeat = GAMESTATE->m_Position.m_fSongBeat;
const size_t iClosestIndex = lrintf( fFakeBeat ) % CHARS_CHOICES.size();
const float fClosestYOffset = GetClosestCharYOffset( fFakeBeat );
@@ -325,7 +325,7 @@ void ScreenNameEntry::Update( float fDelta )
SOUND->PlayOnceFromDir( ANNOUNCER->GetPathTo("name entry") );
m_fFakeBeat += fDelta * FAKE_BEATS_PER_SEC;
GAMESTATE->m_fSongBeat = m_fFakeBeat;
GAMESTATE->m_Position.m_fSongBeat = m_fFakeBeat;
ScreenWithMenuElements::Update(fDelta);
}
+1 -1
View File
@@ -1764,7 +1764,7 @@ void ScreenSelectMusic::AfterMusicChange()
case SampleMusicPreviewMode_LastSong: // fall through
// play the sample music
m_sSampleMusicToPlay = pSong->GetMusicPath();
m_pSampleMusicTimingData = &pSong->m_Timing;
m_pSampleMusicTimingData = &pSong->m_SongTiming;
m_fSampleStartSeconds = pSong->m_fMusicSampleStartSeconds;
m_fSampleLengthSeconds = pSong->m_fMusicSampleLengthSeconds;
break;
+2 -2
View File
@@ -221,7 +221,7 @@ bool ScreenSyncOverlay::OverlayInput( const InputEventPlus &input )
}
if( GAMESTATE->m_pCurSong != NULL )
{
BPMSegment& seg = GAMESTATE->m_pCurSong->GetBPMSegmentAtBeat( GAMESTATE->m_fSongBeat );
BPMSegment& seg = GAMESTATE->m_pCurSong->m_SongTiming.GetBPMSegmentAtBeat( GAMESTATE->m_Position.m_fSongBeat );
seg.m_fBPS += fDelta;
}
}
@@ -253,7 +253,7 @@ bool ScreenSyncOverlay::OverlayInput( const InputEventPlus &input )
case ChangeSongOffset:
if( GAMESTATE->m_pCurSong != NULL )
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += fDelta;
GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += fDelta;
break;
}
}
+37 -54
View File
@@ -41,7 +41,7 @@
* @brief The internal version of the cache for StepMania.
*
* Increment this value to invalidate the current cache. */
const int FILE_CACHE_VERSION = 171;
const int FILE_CACHE_VERSION = 174;
/** @brief How long does a song sample last by default? */
const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f;
@@ -145,6 +145,18 @@ void Song::AddLyricSegment( LyricSegment seg )
m_LyricSegments.push_back( seg );
}
Steps *Song::CreateSteps()
{
Steps *pSteps = new Steps;
InitSteps( pSteps );
return pSteps;
}
void Song::InitSteps(Steps *pSteps)
{
pSteps->m_Timing = m_SongTiming;
}
void Song::GetDisplayBpms( DisplayBpms &AddTo ) const
{
if( m_DisplayBPMType == DISPLAY_BPM_SPECIFIED )
@@ -155,7 +167,7 @@ void Song::GetDisplayBpms( DisplayBpms &AddTo ) const
else
{
float fMinBPM, fMaxBPM;
m_Timing.GetActualBPM( fMinBPM, fMaxBPM );
m_SongTiming.GetActualBPM( fMinBPM, fMaxBPM );
AddTo.Add( fMinBPM );
AddTo.Add( fMaxBPM );
}
@@ -460,6 +472,13 @@ void Song::TidyUpData()
m_fMusicLengthSeconds = 0;
}
m_SongTiming.TidyUpData();
FOREACH( Steps *, m_vpSteps, s )
{
(*s)->m_Timing.TidyUpData();
}
/* Generate these before we autogen notes, so the new notes can inherit
* their source's values. */
ReCalculateRadarValuesAndLastBeat();
@@ -476,29 +495,18 @@ void Song::TidyUpData()
m_sArtist = "Unknown artist";
TranslateTitles();
if( m_Timing.m_BPMSegments.empty() )
{
LOG->UserLog( "Song file", m_sSongDir + m_sSongFileName, "has no BPM segments, default provided." );
m_Timing.AddBPMSegment( BPMSegment(0, 60) );
}
// Make sure the first BPM segment starts at beat 0.
if( m_Timing.m_BPMSegments[0].m_iStartRow != 0 )
m_Timing.m_BPMSegments[0].m_iStartRow = 0;
if( m_fMusicSampleStartSeconds == -1 ||
m_fMusicSampleStartSeconds == 0 ||
m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
{
m_fMusicSampleStartSeconds = this->GetElapsedTimeFromBeat( 100 );
const TimingData &timing = this->m_SongTiming;
m_fMusicSampleStartSeconds = timing.GetElapsedTimeFromBeat( 100 );
if( m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
{
int iBeat = lrintf( m_fLastBeat/2 );
iBeat -= iBeat%4;
m_fMusicSampleStartSeconds = this->GetElapsedTimeFromBeat( (float)iBeat );
m_fMusicSampleStartSeconds = timing.GetElapsedTimeFromBeat( (float)iBeat );
}
}
@@ -777,38 +785,6 @@ void Song::TidyUpData()
m_sSongFileName += ".ssc";
} while(0);
}
// If no time signature specified, assume 4/4 time for the whole song.
if( m_Timing.m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg(0, 4, 4);
m_Timing.m_vTimeSignatureSegments.push_back( seg );
}
/*
* Likewise, if no tickcount signature is specified, assume 2 ticks
* per beat for the entire song. The default of 2 is chosen more
* for compatibility with the Pump Pro series than anything else.
*/
if( m_Timing.m_TickcountSegments.empty() )
{
TickcountSegment seg(0, 2);
m_Timing.m_TickcountSegments.push_back( seg );
}
// Have a default combo segment of one just in case.
if( m_Timing.m_ComboSegments.empty() )
{
ComboSegment seg(0, 1);
m_Timing.m_ComboSegments.push_back( seg );
}
// Have a default label segment just in case.
if( m_Timing.m_LabelSegments.empty() )
{
LabelSegment seg(0, "Song Start");
m_Timing.m_LabelSegments.push_back( seg );
}
}
void Song::TranslateTitles()
@@ -857,8 +833,8 @@ void Song::ReCalculateRadarValuesAndLastBeat()
if( tempNoteData.GetLastRow() == 0 )
continue;
fFirstBeat = min( fFirstBeat, tempNoteData.GetFirstBeat() );
fLastBeat = max( fLastBeat, tempNoteData.GetLastBeat() );
fFirstBeat = min( fFirstBeat, m_SongTiming.GetBeatFromElapsedTime(pSteps->m_Timing.GetElapsedTimeFromBeat(tempNoteData.GetFirstBeat())) );
fLastBeat = max( fLastBeat, m_SongTiming.GetBeatFromElapsedTime(pSteps->m_Timing.GetElapsedTimeFromBeat(tempNoteData.GetLastBeat())) );
}
m_fFirstBeat = fFirstBeat;
@@ -887,6 +863,12 @@ void Song::Save()
ReCalculateRadarValuesAndLastBeat();
TranslateTitles();
// TODO: Figure out a better way to save to Song's timing data.
if( m_vpSteps.size() == 1 )
{
m_SongTiming = m_vpSteps[0]->m_Timing;
}
// Save the new files. These calls make backups on their own.
if( !SaveToSSCFile(GetSongFilePath(), false) )
@@ -1453,7 +1435,7 @@ bool Song::IsEditAlreadyLoaded( Steps* pSteps ) const
bool Song::HasSignificantBpmChangesOrStops() const
{
if( m_Timing.HasStops() )
if( m_SongTiming.HasStops() )
return true;
// Don't consider BPM changes that only are only for maintaining sync as
@@ -1463,7 +1445,7 @@ bool Song::HasSignificantBpmChangesOrStops() const
if( m_fSpecifiedBPMMin != m_fSpecifiedBPMMax )
return true;
}
else if( m_Timing.HasBpmChanges() )
else if( m_SongTiming.HasBpmChanges() )
{
return true;
}
@@ -1473,7 +1455,8 @@ bool Song::HasSignificantBpmChangesOrStops() const
float Song::GetStepsSeconds() const
{
return GetElapsedTimeFromBeat( m_fLastBeat ) - GetElapsedTimeFromBeat( m_fFirstBeat );
const TimingData &timing = this->m_SongTiming;
return timing.GetElapsedTimeFromBeat( m_fLastBeat ) - timing.GetElapsedTimeFromBeat( m_fFirstBeat );
}
bool Song::IsLong() const
@@ -1556,7 +1539,7 @@ public:
}
static int GetTimingData( T* p, lua_State *L )
{
p->m_Timing.PushSelf(L);
p->m_SongTiming.PushSelf(L);
return 1;
}
// has functions
+37 -3
View File
@@ -8,16 +8,16 @@
#include "RageUtil_AutoPtr.h"
#include "RageUtil_CachedObject.h"
#include "RageTypes.h"
#include "Steps.h"
#include <set>
class Steps;
class Style;
class StepsID;
struct lua_State;
struct BackgroundChange;
/** @brief The version of the .ssc file format. */
const static float STEPFILE_VERSION_NUMBER = 0.59f;
const static float STEPFILE_VERSION_NUMBER = 0.7f;
/** @brief How many edits for this song can each profile have? */
const int MAX_EDITS_PER_SONG_PER_PROFILE = 5;
@@ -285,7 +285,10 @@ public:
bool Matches(RString sGroup, RString sSong) const;
/** @brief The Song's TimingData. */
TimingData m_Timing;
TimingData m_SongTiming;
/** @brief The initial offset of a song. */
float m_fBeat0OffsetInSeconds;
typedef vector<BackgroundChange> VBackgroundChange;
private:
@@ -317,9 +320,11 @@ public:
* This must be sorted before gameplay. */
vector<LyricSegment> m_LyricSegments;
/* [splittiming]
void AddBPMSegment( const BPMSegment &seg ) { m_Timing.AddBPMSegment( seg ); }
void AddStopSegment( const StopSegment &seg ) { m_Timing.AddStopSegment( seg ); }
void AddWarpSegment( const WarpSegment &seg ) { m_Timing.AddWarpSegment( seg ); }
*/
void AddBackgroundChange( BackgroundLayer blLayer, BackgroundChange seg );
void AddForegroundChange( BackgroundChange seg );
void AddLyricSegment( LyricSegment seg );
@@ -327,18 +332,47 @@ public:
void GetDisplayBpms( DisplayBpms &AddTo ) const;
const BackgroundChange &GetBackgroundAtBeat( BackgroundLayer iLayer, float fBeat ) const;
/* [splittiming]
float GetBPMAtBeat( float fBeat ) const { return m_Timing.GetBPMAtBeat( fBeat ); }
void SetBPMAtBeat( float fBeat, float fBPM ) { m_Timing.SetBPMAtBeat( fBeat, fBPM ); }
BPMSegment& GetBPMSegmentAtBeat( float fBeat ) { return m_Timing.GetBPMSegmentAtBeat( fBeat ); }
*/
Steps *CreateSteps();
void InitSteps(Steps *pSteps);
/**
* @brief Retrieve the beat based on the specified time.
* @param fElapsedTime the amount of time since the Song started.
* @return the appropriate beat. */
/* [splittiming]
float SongGetBeatFromElapsedTime( float fElapsedTime ) const
{
return m_SongTiming.GetBeatFromElapsedTime( fElapsedTime );
}
float StepsGetBeatFromElapsedTime( float fElapsedTime, const Steps &steps ) const
{
return steps.m_Timing.GetBeatFromElapsedTime( fElapsedTime );
}
float SongGetElapsedTimeFromBeat( float fBeat ) const
{
return m_SongTiming.GetElapsedTimeFromBeat( fBeat );
}
float StepsGetElapsedTimeFromBeat( float fBeat, const Steps &steps ) const
{
return steps.m_Timing.GetElapsedTimeFromBeat( fBeat );
}
*/
/* [splittiming]
float GetBeatFromElapsedTime( float fElapsedTime ) const
{
return m_Timing.GetBeatFromElapsedTime( fElapsedTime );
}
float GetElapsedTimeFromBeat( float fBeat ) const { return m_Timing.GetElapsedTimeFromBeat( fBeat ); }
*/
bool HasSignificantBpmChangesOrStops() const;
float GetStepsSeconds() const;
bool IsLong() const;
+135
View File
@@ -0,0 +1,135 @@
#include "global.h"
#include "SongPosition.h"
static Preference<float> g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f );
void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp )
{
if( !timestamp.IsZero() )
m_LastBeatUpdate = timestamp;
else
m_LastBeatUpdate.Touch();
// xxx testing: only do this on monotune survivor
/*
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
LOG->Trace( ssprintf("[GameState::UpdateSongPosition] cur BPS = %f, fPositionSeconds = %f",m_fCurBPS,fPositionSeconds) );
*/
timing.GetBeatAndBPSFromElapsedTime( fPositionSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze, m_bDelay, m_iWarpBeginRow, m_fWarpDestination );
// "Crash reason : -243478.890625 -48695.773438"
ASSERT_M( m_fSongBeat > -2000, ssprintf("Song beat %f at %f seconds is less than -2000!", m_fSongBeat, fPositionSeconds) );
m_fMusicSeconds = fPositionSeconds;
m_fLightSongBeat = timing.GetBeatFromElapsedTime( fPositionSeconds + g_fLightsAheadSeconds );
m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
/*
// xxx testing: only do this on monotune survivor
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
{
// and only do it in the known negative bpm region. HACKITY HACK
if(m_fSongBeat >= 445.490f && m_fSongBeat <= 453.72f)
{
LOG->Trace( ssprintf("fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
//LOG->Trace( ssprintf("Music seconds visible %f = fPositionSeconds %f - g_fVisualDelaySeconds %f", m_fMusicSecondsVisible,fPositionSeconds,g_fVisualDelaySeconds.Get()) );
}
else if(m_fSongBeat == 445.500f)
{
LOG->Trace( ssprintf("[beat 445.500] fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
}
}
*/
m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
float fThrowAway, fThrowAway2;
bool bThrowAway;
int iThrowAway;
timing.GetBeatAndBPSFromElapsedTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fThrowAway, bThrowAway, bThrowAway, iThrowAway, fThrowAway2 );
}
void SongPosition::Reset()
{
m_fMusicSecondsVisible = 0;
m_fSongBeatVisible = 0;
m_fMusicSeconds = 0; // MUSIC_SECONDS_INVALID;
// todo: move me to FOREACH_EnabledPlayer( p ) after [NUM_PLAYERS]ing
m_fSongBeat = 0;
m_fSongBeatNoOffset = 0;
m_fCurBPS = 10;
//m_bStop = false;
m_bFreeze = false;
m_bDelay = false;
m_iWarpBeginRow = -1; // Set to -1 because some song may want to warp to row 0. -aj
m_fWarpDestination = -1; // Set when a warp is encountered. also see above. -aj
}
//lua start
#include "LuaBinding.h"
class LunaSongPosition: public Luna<SongPosition>
{
public:
DEFINE_METHOD( GetMusicSecondsVisible, m_fMusicSecondsVisible );
DEFINE_METHOD( GetSongBeatVisible, m_fSongBeatVisible );
DEFINE_METHOD( GetMusicSeconds, m_fMusicSeconds );
DEFINE_METHOD( GetSongBeat, m_fSongBeat );
DEFINE_METHOD( GetSongBeatNoOffset, m_fSongBeatNoOffset );
DEFINE_METHOD( GetCurBPS, m_fCurBPS );
DEFINE_METHOD( GetFreeze, m_bFreeze );
DEFINE_METHOD( GetDelay, m_bDelay );
DEFINE_METHOD( GetWarpBeginRow, m_iWarpBeginRow );
DEFINE_METHOD( GetWarpDestination, m_fWarpDestination );
LunaSongPosition()
{
ADD_METHOD( GetMusicSecondsVisible );
ADD_METHOD( GetSongBeatVisible );
ADD_METHOD( GetMusicSeconds );
ADD_METHOD( GetSongBeat );
ADD_METHOD( GetSongBeatNoOffset );
ADD_METHOD( GetCurBPS );
ADD_METHOD( GetFreeze );
ADD_METHOD( GetDelay );
ADD_METHOD( GetWarpBeginRow );
ADD_METHOD( GetWarpDestination );
}
};
LUA_REGISTER_CLASS( SongPosition );
/**
* @file
* @author Thai Pangsakulyanont (c) 2011
* @section LICENSE
* 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.
*/
+72
View File
@@ -0,0 +1,72 @@
#ifndef SONGPOSITION_H
#define SONGPOSITION_H
#include "GameConstantsAndTypes.h"
#include "RageTimer.h"
#include "LightsManager.h"
#include "MessageManager.h"
#include "TimingData.h"
//XXX: where does this come from? might need another include
struct lua_State;
class SongPosition
{
public:
// Arcade - the current stage (one song).
// Oni/Endless - a single song in a course.
// Let a lot of classes access this info here so they don't have to keep their own copies.
float m_fMusicSeconds; // time into the current song, not scaled by music rate
float m_fSongBeat;
float m_fSongBeatNoOffset;
float m_fCurBPS;
float m_fLightSongBeat; // g_fLightsFalloffSeconds ahead
//bool m_bStop; // in the middle of a stop (freeze or delay)
/** @brief A flag to determine if we're in the middle of a freeze/stop. */
bool m_bFreeze;
/** @brief A flag to determine if we're in the middle of a delay (Pump style stop). */
bool m_bDelay;
/** @brief The row used to start a warp. */
int m_iWarpBeginRow;
/** @brief The beat to warp to afterwards. */
float m_fWarpDestination;
RageTimer m_LastBeatUpdate; // time of last m_fSongBeat, etc. update
float m_fMusicSecondsVisible;
float m_fSongBeatVisible;
void Reset();
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer );
// Lua
void PushSelf( lua_State *L );
};
#endif
/**
* @file
* @author Thai Pangsakulyanont (c) 2011
* @section LICENSE
* 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.
*/
+1 -1
View File
@@ -44,7 +44,7 @@ void SoundEffectControl::Update( float fDeltaTime )
if( LOCK_TO_HOLD )
{
int iRow = BeatToNoteRow( GAMESTATE->m_fSongBeat );
int iRow = BeatToNoteRow( GAMESTATE->m_Position.m_fSongBeat );
int iHoldsHeld, iHoldsLetGo;
HoldsBeingHeld( iRow, iHoldsHeld, iHoldsLetGo );
+8
View File
@@ -1444,6 +1444,14 @@
RelativePath="SongOptions.h"
>
</File>
<File
RelativePath=".\SongPosition.cpp"
>
</File>
<File
RelativePath=".\SongPosition.h"
>
</File>
<File
RelativePath="SongUtil.cpp"
>
+28
View File
@@ -339,6 +339,7 @@ void Steps::AutogenFrom( const Steps *parent_, StepsType ntTo )
{
parent = parent_;
m_StepsType = ntTo;
m_Timing = parent->m_Timing;
}
void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds ) // pSource does not have to be of the same StepsType
@@ -348,6 +349,7 @@ void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds
pSource->GetNoteData( noteData );
noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks );
parent = NULL;
m_Timing = pSource->m_Timing;
this->SetNoteData( noteData );
this->SetDescription( pSource->GetDescription() );
this->SetDifficulty( pSource->GetDifficulty() );
@@ -400,6 +402,21 @@ void Steps::SetMeter( int meter )
m_iMeter = meter;
}
bool Steps::HasSignificantTimingChanges() const
{
if( m_Timing.HasStops() )
return true;
/* TODO: Deal with DisplayBPM here...if possible?
* Song's version may still be useful. */
else if( m_Timing.HasBpmChanges() || m_Timing.HasWarps() || m_Timing.HasSpeedChanges() )
{
return true;
}
return false;
}
void Steps::SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] )
{
DeAutogen();
@@ -424,6 +441,8 @@ public:
DEFINE_METHOD( IsAnEdit, IsAnEdit() )
DEFINE_METHOD( IsAPlayerEdit, IsAPlayerEdit() )
static int HasSignificantTimingChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasSignificantTimingChanges()); return 1; }
static int GetRadarValues( T* p, lua_State *L )
{
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
@@ -432,6 +451,12 @@ public:
return 1;
}
static int GetTimingData( T* p, lua_State *L )
{
p->m_Timing.PushSelf(L);
return 1;
}
static int GetHash( T* p, lua_State *L ) { lua_pushnumber( L, p->GetHash() ); return 1; }
// untested
@@ -442,6 +467,7 @@ public:
lua_pushstring( L, out );
return 1;
}
LunaSteps()
{
@@ -452,7 +478,9 @@ public:
ADD_METHOD( GetFilename );
ADD_METHOD( GetHash );
ADD_METHOD( GetMeter );
ADD_METHOD( HasSignificantTimingChanges );
ADD_METHOD( GetRadarValues );
ADD_METHOD( GetTimingData );
//ADD_METHOD( GetSMNoteData );
ADD_METHOD( GetStepsType );
ADD_METHOD( IsAnEdit );
+10
View File
@@ -8,6 +8,8 @@
#include "Difficulty.h"
#include "RageUtil_AutoPtr.h"
#include "RageUtil_CachedObject.h"
#include "TimingData.h"
class Profile;
class NoteData;
struct lua_State;
@@ -116,6 +118,14 @@ public:
void TidyUpData();
void CalculateRadarValues( float fMusicLengthSeconds );
/** @brief Timing data */
TimingData m_Timing;
/**
* @brief Determine if the Steps have any major timing changes during gameplay.
* @return true if it does, or false otherwise. */
bool HasSignificantTimingChanges() const;
// Lua
void PushSelf( lua_State *L );
+354 -27
View File
@@ -8,10 +8,16 @@
#include <float.h>
TimingData::TimingData()
TimingData::TimingData() :
m_fBeat0OffsetInSeconds(0),
m_bHasNegativeBpms(false)
{
m_fBeat0OffsetInSeconds = 0;
m_bHasNegativeBpms = false;
}
TimingData::TimingData(float fOffset) :
m_fBeat0OffsetInSeconds(fOffset),
m_bHasNegativeBpms(false)
{
}
void TimingData::GetActualBPM( float &fMinBPMOut, float &fMaxBPMOut ) const
@@ -62,6 +68,16 @@ void TimingData::AddLabelSegment( const LabelSegment &seg )
m_LabelSegments.insert( upper_bound(m_LabelSegments.begin(), m_LabelSegments.end(), seg), seg );
}
void TimingData::AddSpeedSegment( const SpeedSegment &seg )
{
m_SpeedSegments.insert( upper_bound(m_SpeedSegments.begin(), m_SpeedSegments.end(), seg), seg );
}
void TimingData::AddFakeSegment( const FakeSegment &seg )
{
m_FakeSegments.insert( upper_bound(m_FakeSegments.begin(), m_FakeSegments.end(), seg), seg );
}
/* Change an existing BPM segment, merge identical segments together or insert a new one. */
void TimingData::SetBPMAtRow( int iNoteRow, float fBPM )
{
@@ -160,7 +176,7 @@ void TimingData::SetWarpAtRow( int iRow, float fNew )
for( i=0; i<m_WarpSegments.size(); i++ )
if( m_WarpSegments[i].m_iStartRow == iRow )
break;
bool valid = iRow > 0 && NoteRowToBeat(iRow) < fNew;
bool valid = iRow > 0 && fNew > 0;
if( i == m_WarpSegments.size() )
{
if( valid )
@@ -172,7 +188,7 @@ void TimingData::SetWarpAtRow( int iRow, float fNew )
{
if( valid )
{
m_WarpSegments[i].m_fEndBeat = fNew;
m_WarpSegments[i].m_fLengthBeats = fNew;
}
else
m_WarpSegments.erase( m_WarpSegments.begin()+i, m_WarpSegments.begin()+i+1 );
@@ -244,6 +260,86 @@ void TimingData::SetLabelAtRow( int iRow, const RString sLabel )
}
}
void TimingData::SetSpeedAtRow( int iRow, float fPercent, float fWait, unsigned short usMode )
{
unsigned i;
for( i = 0; i < m_SpeedSegments.size(); i++ )
{
if( m_SpeedSegments[i].m_iStartRow >= iRow)
break;
}
if ( i == m_SpeedSegments.size() || m_SpeedSegments[i].m_iStartRow != iRow )
{
// the core mod itself matters the most for comparisons.
if( i == 0 || m_SpeedSegments[i-1].m_fPercent != fPercent )
AddSpeedSegment( SpeedSegment(iRow, fPercent, fWait, usMode) );
}
else
{
// The others aren't compared: only the mod itself matters.
if( i > 0 && m_SpeedSegments[i-1].m_fPercent == fPercent )
m_SpeedSegments.erase( m_SpeedSegments.begin()+i,
m_SpeedSegments.begin()+i+1 );
else
{
m_SpeedSegments[i].m_fPercent = fPercent;
m_SpeedSegments[i].m_fWait = fWait;
m_SpeedSegments[i].m_usMode = usMode;
}
}
}
void TimingData::SetFakeAtRow( int iRow, float fNew )
{
unsigned i;
for( i=0; i<m_FakeSegments.size(); i++ )
if( m_FakeSegments[i].m_iStartRow == iRow )
break;
bool valid = iRow > 0 && fNew > 0;
if( i == m_FakeSegments.size() )
{
if( valid )
{
AddFakeSegment( FakeSegment(iRow, fNew) );
}
}
else
{
if( valid )
{
m_FakeSegments[i].m_fLengthBeats = fNew;
}
else
m_FakeSegments.erase( m_FakeSegments.begin()+i, m_FakeSegments.begin()+i+1 );
}
}
void TimingData::SetSpeedPercentAtRow( int iRow, float fPercent )
{
SetSpeedAtRow( iRow,
fPercent,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fWait,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_usMode);
}
void TimingData::SetSpeedWaitAtRow( int iRow, float fWait )
{
SetSpeedAtRow( iRow,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fPercent,
fWait,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_usMode);
}
void TimingData::SetSpeedModeAtRow( int iRow, unsigned short usMode )
{
SetSpeedAtRow( iRow,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fPercent,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fWait,
usMode );
}
float TimingData::GetStopAtRow( int iNoteRow, bool bDelay ) const
{
for( unsigned i=0; i<m_StopSegments.size(); i++ )
@@ -283,7 +379,34 @@ float TimingData::GetWarpAtRow( int iWarpRow ) const
{
if( m_WarpSegments[i].m_iStartRow == iWarpRow )
{
return m_WarpSegments[i].m_fEndBeat;
return m_WarpSegments[i].m_fLengthBeats;
}
}
return 0;
}
float TimingData::GetSpeedPercentAtRow( int iRow )
{
return GetSpeedSegmentAtRow( iRow ).m_fPercent;
}
float TimingData::GetSpeedWaitAtRow( int iRow )
{
return GetSpeedSegmentAtRow( iRow ).m_fWait;
}
unsigned short TimingData::GetSpeedModeAtRow( int iRow )
{
return GetSpeedSegmentAtRow( iRow ).m_usMode;
}
float TimingData::GetFakeAtRow( int iFakeRow ) const
{
for( unsigned i=0; i<m_FakeSegments.size(); i++ )
{
if( m_FakeSegments[i].m_iStartRow == iFakeRow )
{
return m_FakeSegments[i].m_fLengthBeats;
}
}
return 0;
@@ -371,6 +494,18 @@ int TimingData::GetWarpSegmentIndexAtRow( int iNoteRow ) const
return static_cast<int>(i);
}
int TimingData::GetFakeSegmentIndexAtRow( int iNoteRow ) const
{
unsigned i;
for( i=0; i<m_FakeSegments.size()-1; i++ )
{
const FakeSegment& s = m_FakeSegments[i+1];
if( s.m_iStartRow > iNoteRow )
break;
}
return static_cast<int>(i);
}
bool TimingData::IsWarpAtRow( int iNoteRow ) const
{
if( m_WarpSegments.empty() )
@@ -378,7 +513,7 @@ bool TimingData::IsWarpAtRow( int iNoteRow ) const
int i = GetWarpSegmentIndexAtRow( iNoteRow );
const WarpSegment& s = m_WarpSegments[i];
if( s.m_iStartRow <= iNoteRow && iNoteRow < BeatToNoteRow(s.m_fEndBeat) )
if( s.m_iStartRow <= iNoteRow && iNoteRow < (s.m_iStartRow + BeatToNoteRow(s.m_fLengthBeats) ) )
{
if( m_StopSegments.empty() )
{
@@ -393,6 +528,20 @@ bool TimingData::IsWarpAtRow( int iNoteRow ) const
return false;
}
bool TimingData::IsFakeAtRow( int iNoteRow ) const
{
if( m_FakeSegments.empty() )
return false;
int i = GetFakeSegmentIndexAtRow( iNoteRow );
const FakeSegment& s = m_FakeSegments[i];
if( s.m_iStartRow <= iNoteRow && iNoteRow < ( s.m_iStartRow + BeatToNoteRow(s.m_fLengthBeats) ) )
{
return true;
}
return false;
}
int TimingData::GetTimeSignatureSegmentIndexAtRow( int iRow ) const
{
unsigned i;
@@ -426,6 +575,15 @@ int TimingData::GetLabelSegmentIndexAtRow( int iRow ) const
return static_cast<int>(i);
}
int TimingData::GetSpeedSegmentIndexAtRow( int iRow ) const
{
unsigned i;
for (i=0; i < m_SpeedSegments.size() - 1; i++ )
if( m_SpeedSegments[i+1].m_iStartRow > iRow )
break;
return static_cast<int>(i);
}
BPMSegment& TimingData::GetBPMSegmentAtRow( int iNoteRow )
{
static BPMSegment empty;
@@ -445,6 +603,15 @@ TimeSignatureSegment& TimingData::GetTimeSignatureSegmentAtRow( int iRow )
return m_vTimeSignatureSegments[i];
}
SpeedSegment& TimingData::GetSpeedSegmentAtRow( int iRow )
{
unsigned i;
for( i=0; i<m_SpeedSegments.size()-1; i++ )
if( m_SpeedSegments[i+1].m_iStartRow > iRow )
break;
return m_SpeedSegments[i];
}
int TimingData::GetTimeSignatureNumeratorAtRow( int iRow )
{
return GetTimeSignatureSegmentAtRow( iRow ).m_iNumerator;
@@ -493,6 +660,16 @@ WarpSegment& TimingData::GetWarpSegmentAtRow( int iRow )
return m_WarpSegments[i];
}
FakeSegment& TimingData::GetFakeSegmentAtRow( int iRow )
{
static FakeSegment empty;
if( m_FakeSegments.empty() )
return empty;
int i = GetFakeSegmentIndexAtRow( iRow );
return m_FakeSegments[i];
}
int TimingData::GetTickcountSegmentIndexAtRow( int iRow ) const
{
unsigned i;
@@ -652,15 +829,18 @@ void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float
}
break;
case FOUND_WARP:
bIsWarping = true;
if( itWS->m_fEndBeat > fWarpDestination )
{
fWarpDestination = itWS->m_fEndBeat;
bIsWarping = true;
float fWarpSum = itWS->m_fLengthBeats + NoteRowToBeat( itWS->m_iStartRow );
if( fWarpSum > fWarpDestination )
{
fWarpDestination = fWarpSum;
}
iWarpBeginOut = iEventRow;
fWarpDestinationOut = fWarpDestination;
itWS ++;
break;
}
iWarpBeginOut = iEventRow;
fWarpDestinationOut = fWarpDestination;
itWS ++;
break;
}
iLastRow = iEventRow;
}
@@ -744,13 +924,16 @@ float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const
case FOUND_MARKER:
return fLastTime;
case FOUND_WARP:
bIsWarping = true;
if( itWS->m_fEndBeat > fWarpDestination )
{
fWarpDestination = itWS->m_fEndBeat;
bIsWarping = true;
float fWarpSum = itWS->m_fLengthBeats + NoteRowToBeat( itWS->m_iStartRow );
if( fWarpSum > fWarpDestination )
{
fWarpDestination = fWarpSum;
}
itWS ++;
break;
}
itWS ++;
break;
}
iLastRow = iEventRow;
}
@@ -790,13 +973,13 @@ void TimingData::ScaleRegion( float fScale, int iStartIndex, int iEndIndex, bool
for( unsigned i = 0; i < m_WarpSegments.size(); i++ )
{
const int iSegStartRow = m_WarpSegments[i].m_iStartRow;
const int iSegEndRow = BeatToNoteRow( m_WarpSegments[i].m_fEndBeat );
const int iSegEndRow = iSegStartRow + BeatToNoteRow( m_WarpSegments[i].m_fLengthBeats );
if( iSegEndRow >= iStartIndex )
{
if( iSegEndRow > iEndIndex )
m_WarpSegments[i].m_fEndBeat += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1)));
m_WarpSegments[i].m_fLengthBeats += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1)));
else
m_WarpSegments[i].m_fEndBeat = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale) + iStartIndex);
m_WarpSegments[i].m_fLengthBeats = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale));
}
if( iSegStartRow < iStartIndex )
continue;
@@ -839,6 +1022,36 @@ void TimingData::ScaleRegion( float fScale, int iStartIndex, int iEndIndex, bool
m_LabelSegments[i].m_iStartRow = lrintf( (iSegStart - iStartIndex) * fScale ) + iStartIndex;
}
for ( unsigned i = 0; i < m_SpeedSegments.size(); i++ )
{
const int iSegStart = m_SpeedSegments[i].m_iStartRow;
if( iSegStart < iStartIndex )
continue;
else if( iSegStart > iEndIndex )
m_SpeedSegments[i].m_iStartRow += lrintf( (iEndIndex - iStartIndex) * (fScale - 1) );
else
m_SpeedSegments[i].m_iStartRow = lrintf( (iSegStart - iStartIndex) * fScale ) + iStartIndex;
}
for( unsigned i = 0; i < m_FakeSegments.size(); i++ )
{
const int iSegStartRow = m_FakeSegments[i].m_iStartRow;
const int iSegEndRow = iSegStartRow + BeatToNoteRow( m_FakeSegments[i].m_fLengthBeats );
if( iSegEndRow >= iStartIndex )
{
if( iSegEndRow > iEndIndex )
m_FakeSegments[i].m_fLengthBeats += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1)));
else
m_FakeSegments[i].m_fLengthBeats = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale));
}
if( iSegStartRow < iStartIndex )
continue;
else if( iSegStartRow > iEndIndex )
m_FakeSegments[i].m_iStartRow += lrintf((iEndIndex - iStartIndex) * (fScale - 1));
else
m_FakeSegments[i].m_iStartRow = lrintf((iSegStartRow - iStartIndex) * fScale) + iStartIndex;
}
// adjust BPM changes to preserve timing
if( bAdjustBPM )
{
@@ -886,8 +1099,6 @@ void TimingData::InsertRows( int iStartRow, int iRowsToAdd )
for( unsigned i = 0; i < m_WarpSegments.size(); i++ )
{
WarpSegment &warp = m_WarpSegments[i];
if( BeatToNoteRow(warp.m_fEndBeat) >= iStartRow )
warp.m_fEndBeat += NoteRowToBeat(iRowsToAdd);
if( warp.m_iStartRow < iStartRow )
continue;
warp.m_iStartRow += iRowsToAdd;
@@ -923,6 +1134,22 @@ void TimingData::InsertRows( int iStartRow, int iRowsToAdd )
continue;
labl.m_iStartRow += iRowsToAdd;
}
for( unsigned i = 0; i < m_SpeedSegments.size(); i++ )
{
SpeedSegment &sped = m_SpeedSegments[i];
if( sped.m_iStartRow < iStartRow )
continue;
sped.m_iStartRow += iRowsToAdd;
}
for( unsigned i = 0; i < m_FakeSegments.size(); i++ )
{
FakeSegment &fake = m_FakeSegments[i];
if( fake.m_iStartRow < iStartRow )
continue;
fake.m_iStartRow += iRowsToAdd;
}
if( iStartRow == 0 )
{
@@ -985,9 +1212,6 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete )
{
WarpSegment &warp = m_WarpSegments[i];
if( BeatToNoteRow(warp.m_fEndBeat) >= iStartRow )
warp.m_fEndBeat = max( NoteRowToBeat(iStartRow), warp.m_fEndBeat - NoteRowToBeat(iRowsToDelete) );
if( warp.m_iStartRow < iStartRow )
continue;
@@ -1080,10 +1304,99 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete )
}
labl.m_iStartRow -= iRowsToDelete;
}
for( unsigned i = 0; i < m_SpeedSegments.size(); i++ )
{
SpeedSegment &sped = m_SpeedSegments[i];
if( sped.m_iStartRow < iStartRow )
continue;
if( sped.m_iStartRow < iStartRow+iRowsToDelete )
{
m_SpeedSegments.erase( m_SpeedSegments.begin()+i, m_SpeedSegments.begin()+i+1 );
--i;
continue;
}
sped.m_iStartRow -= iRowsToDelete;
}
for( unsigned i = 0; i < m_FakeSegments.size(); i++ )
{
FakeSegment &fake = m_FakeSegments[i];
if( fake.m_iStartRow < iStartRow )
continue;
if( fake.m_iStartRow < iStartRow+iRowsToDelete )
{
m_FakeSegments.erase( m_FakeSegments.begin()+i, m_FakeSegments.begin()+i+1 );
--i;
continue;
}
fake.m_iStartRow -= iRowsToDelete;
}
this->SetBPMAtRow( iStartRow, fNewBPM );
}
void TimingData::TidyUpData()
{
// If there are no BPM segments, provide a default.
if( m_BPMSegments.empty() )
{
LOG->UserLog( "Song file", m_sFile, "has no BPM segments, default provided." );
AddBPMSegment( BPMSegment(0, 60) );
}
// Make sure the first BPM segment starts at beat 0.
if( m_BPMSegments[0].m_iStartRow != 0 )
m_BPMSegments[0].m_iStartRow = 0;
// If no time signature specified, assume 4/4 time for the whole song.
if( m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg(0, 4, 4);
m_vTimeSignatureSegments.push_back( seg );
}
// Likewise, if no tickcount signature is specified, assume 2 ticks
//per beat for the entire song. The default of 2 is chosen more
//for compatibility with the Pump Pro series than anything else.
if( m_TickcountSegments.empty() )
{
TickcountSegment seg(0, 2);
m_TickcountSegments.push_back( seg );
}
// Have a default combo segment of one just in case.
if( m_ComboSegments.empty() )
{
ComboSegment seg(0, 1);
m_ComboSegments.push_back( seg );
}
// Have a default label segment just in case.
if( m_LabelSegments.empty() )
{
LabelSegment seg(0, "Song Start");
m_LabelSegments.push_back( seg );
}
// Always be sure there is a starting speed.
if( m_SpeedSegments.empty() )
{
SpeedSegment seg(0, 1, 0);
m_SpeedSegments.push_back( seg );
}
}
bool TimingData::HasBpmChanges() const
{
return m_BPMSegments.size()>1;
@@ -1099,6 +1412,16 @@ bool TimingData::HasWarps() const
return m_WarpSegments.size()>0;
}
bool TimingData::HasFakes() const
{
return m_FakeSegments.size()>0;
}
bool TimingData::HasSpeedChanges() const
{
return m_SpeedSegments.size()>1;
}
void TimingData::NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const
{
iMeasureIndexOut = 0;
@@ -1145,6 +1468,8 @@ public:
static int HasStops( T* p, lua_State *L ) { lua_pushboolean(L, p->HasStops()); return 1; }
static int HasBPMChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBpmChanges()); return 1; }
static int HasWarps( T* p, lua_State *L ) { lua_pushboolean(L, p->HasWarps()); return 1; }
static int HasFakes( T* p, lua_State *L ) { lua_pushboolean(L, p->HasFakes()); return 1; }
static int HasSpeedChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasSpeedChanges()); return 1; }
static int GetStops( T* p, lua_State *L )
{
vector<RString> vStops;
@@ -1232,6 +1557,8 @@ public:
ADD_METHOD( HasStops );
ADD_METHOD( HasBPMChanges );
ADD_METHOD( HasWarps );
ADD_METHOD( HasFakes );
ADD_METHOD( HasSpeedChanges );
ADD_METHOD( GetStops );
ADD_METHOD( GetDelays );
ADD_METHOD( GetBPMs );
+470 -18
View File
@@ -313,8 +313,8 @@ struct TimeSignatureSegment
*
* A warp segment is used to replicate the effects of Negative BPMs without
* abusing negative BPMs. Negative BPMs should be converted to warp segments.
* WarpAt=WarpTo is the format, where both are in beats. (Technically they're
* both rows though.) */
* WarpAt=WarpToRelative is the format, where both are in beats.
* (Technically they're both rows though.) */
struct WarpSegment
{
/**
@@ -322,45 +322,45 @@ struct WarpSegment
*
* It is best to override the values as soon as possible.
*/
WarpSegment() : m_iStartRow(-1), m_fEndBeat(-1) { }
WarpSegment() : m_iStartRow(-1), m_fLengthBeats(-1) { }
/**
* @brief Create a Warp Segment with the specified starting row and row to warp to.
* @param s the starting row of this segment.
* @param r the row to warp to.
* @param r the number of rows to jump ahead.
*/
WarpSegment( int s, int r ): m_iStartRow(max(0, (s < r ? s : r))),
m_fEndBeat(max(0, NoteRowToBeat((r > s ? r : s)))) {}
WarpSegment( int s, int r ): m_iStartRow(s),
m_fLengthBeats(NoteRowToBeat(r)) {}
/**
* @brief Creates a Warp Segment with the specified starting row and beat to warp to.
* @param s the starting row of this segment.
* @param b the beat to warp to.
* @param b the number of beats to jump ahead.
*/
WarpSegment( int s, float b ): m_iStartRow(max(0, s)),
m_fEndBeat(max(0, b)) {}
m_fLengthBeats(max(0, b)) {}
/**
* @brief Create a Warp Segment with the specified starting beat and row to warp to.
* @param s the starting beat in this segment.
* @param r the row to warp to.
* @param r the number of rows to jump ahead.
*/
WarpSegment( float s, int r ):
m_iStartRow(max(0, BeatToNoteRow(s))),
m_fEndBeat(max(0, NoteRowToBeat(r))) {}
m_fLengthBeats(max(0, NoteRowToBeat(r))) {}
/**
* @brief Creates a Warp Segment with the specified starting beat and beat to warp to.
* @param s the starting beat of this segment.
* @param b the beat to warp to.
* @param b the number of beats to jump ahead.
*/
WarpSegment( float s, float b ):
m_iStartRow(max(0, BeatToNoteRow((s < b ? s : b)))),
m_fEndBeat(max(0, (b > s ? b : s))) {}
m_iStartRow(BeatToNoteRow(s)),
m_fLengthBeats(b) {}
/**
* @brief The row in which the WarpSegment activates.
*/
int m_iStartRow;
/**
* @brief The beat to warp to.
* @brief The number of beats to warp ahead by.
*/
float m_fEndBeat;
float m_fLengthBeats;
/**
* @brief Compares two WarpSegments to see if they are equal to each other.
* @param other the other WarpSegment to compare to.
@@ -369,7 +369,7 @@ struct WarpSegment
bool operator==( const WarpSegment &other ) const
{
COMPARE( m_iStartRow );
COMPARE( m_fEndBeat );
COMPARE( m_fLengthBeats );
return true;
}
/**
@@ -386,7 +386,7 @@ struct WarpSegment
bool operator<( const WarpSegment &other ) const
{
return m_iStartRow < other.m_iStartRow ||
( m_iStartRow == other.m_iStartRow && m_fEndBeat < other.m_fEndBeat );
( m_iStartRow == other.m_iStartRow && m_fLengthBeats < other.m_fLengthBeats );
}
/**
* @brief Compares two WarpSegments to see if one is less than or equal to the other.
@@ -508,7 +508,7 @@ struct ComboSegment
* @param t the amount the combo increases on a succesful hit.
*/
ComboSegment( int s, int t ): m_iStartRow(max(0, s)),
m_iCombo(max(1,t)) {}
m_iCombo(max(0,t)) {}
/**
* @brief The row in which the ComboSegment activates.
*/
@@ -647,6 +647,243 @@ struct LabelSegment
bool operator>=( const LabelSegment &other ) const { return !operator<(other); }
};
/**
* @brief Identifies when the arrow scroll changes.
*
* SpeedSegments take a Player's scrolling BPM (Step's BPM * speed mod),
* and then multiplies it with the percentage value. No matter the player's
* speed mod, the ratio will be the same. Unlike forced attacks, these
* cannot be turned off at a set time: reset it by setting the precentage
* back to 1.
*
* These were inspired by the Pump It Up series. */
struct SpeedSegment
{
/** @brief Sets up the SpeedSegment with default values. */
SpeedSegment(): m_iStartRow(0),
m_fPercent(1), m_fWait(0), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param i The row this activates.
* @param p The percentage to use. */
SpeedSegment(int i, float p): m_iStartRow(0),
m_fPercent(p), m_fWait(0), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param r The beat this activates.
* @param p The percentage to use. */
SpeedSegment(float r, float p): m_iStartRow(BeatToNoteRow(r)),
m_fPercent(p), m_fWait(0), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param i The row this activates.
* @param p The percentage to use.
* @param w The number of beats to wait. */
SpeedSegment(int i, float p, float w): m_iStartRow(i),
m_fPercent(p), m_fWait(w), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param r The beat this activates.
* @param p The percentage to use.
* @param w The number of beats to wait. */
SpeedSegment(float r, float p, float w): m_iStartRow(BeatToNoteRow(r)),
m_fPercent(p), m_fWait(w), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param i The row this activates.
* @param p The percentage to use.
* @param w The number of beats/seconds to wait.
* @param k The mode used for the wait variable. */
SpeedSegment(int i, float p, float w, unsigned short k): m_iStartRow(i),
m_fPercent(p), m_fWait(w), m_usMode(k) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param r The beat this activates.
* @param p The percentage to use.
* @param w The number of beats/seconds to wait.
* @param k The mode used for the wait variable.*/
SpeedSegment(float r, float p, float w, unsigned short k): m_iStartRow(BeatToNoteRow(r)),
m_fPercent(p), m_fWait(w), m_usMode(k) {}
/** @brief The row in which the ComboSegment activates. */
int m_iStartRow;
/** @brief The percentage to use when multiplying the Player's BPM. */
float m_fPercent;
/**
* @brief The number of beats or seconds to wait for the change to take place.
*
* A value of 0 means this is immediate. */
float m_fWait;
/**
* @brief The mode that this segment uses for the math.
*
* 0: beats
* 1: seconds
* other
*/
unsigned short m_usMode;
/**
* @brief Compares two SpeedSegments to see if they are equal to each other.
* @param other the other SpeedSegment to compare to.
* @return the equality of the two segments.
*/
bool operator==( const SpeedSegment &other ) const
{
COMPARE( m_iStartRow );
COMPARE( m_fPercent );
COMPARE( m_usMode );
COMPARE( m_fWait );
return true;
}
/**
* @brief Compares two SpeedSegments to see if they are not equal to each other.
* @param other the other SpeedSegment to compare to.
* @return the inequality of the two segments.
*/
bool operator!=( const SpeedSegment &other ) const { return !operator==(other); }
/**
* @brief Compares two SpeedSegments to see if one is less than the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is less than the second.
*/
bool operator<( const SpeedSegment &other ) const { return m_iStartRow < other.m_iStartRow; }
/**
* @brief Compares two SpeedSegments to see if one is less than or equal to the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is less or equal to than the second.
*/
bool operator<=( const SpeedSegment &other ) const
{
return ( operator<(other) || operator==(other) );
}
/**
* @brief Compares two SpeedSegments to see if one is greater than the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is greater than the second.
*/
bool operator>( const SpeedSegment &other ) const { return !operator<=(other); }
/**
* @brief Compares two SpeedSegments to see if one is greater than or equal to the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is greater than or equal to the second.
*/
bool operator>=( const SpeedSegment &other ) const { return !operator<(other); }
};
/**
* @brief Identifies when a whole region of arrows is to be ignored.
*
* FakeSegments are similar to the Fake Tap Notes in that the contents
* inside are neither for nor against the player. They can be useful for
* mission modes, in conjunction with WarpSegments, or perhaps other
* uses not thought up at the time of this comment. Unlike the Warp
* Segments, these are not magically jumped over: instead, these are
* drawn normally.
*
* These were inspired by the Pump It Up series. */
struct FakeSegment
{
/**
* @brief Create a simple Fake Segment with default values.
*
* It is best to override the values as soon as possible.
*/
FakeSegment() : m_iStartRow(-1), m_fLengthBeats(-1) { }
/**
* @brief Create a Fake Segment with the specified values.
* @param s the starting row of this segment.
* @param r the number of rows this segment lasts.
*/
FakeSegment( int s, int r ): m_iStartRow(s),
m_fLengthBeats(NoteRowToBeat(r)) {}
/**
* @brief Creates a Fake Segment with the specified values.
* @param s the starting row of this segment.
* @param b the number of beats this segment lasts.
*/
FakeSegment( int s, float b ): m_iStartRow(max(0, s)),
m_fLengthBeats(max(0, b)) {}
/**
* @brief Create a Fake Segment with the specified values.
* @param s the starting beat in this segment.
* @param r the number of rows this segment lasts.
*/
FakeSegment( float s, int r ):
m_iStartRow(max(0, BeatToNoteRow(s))),
m_fLengthBeats(max(0, NoteRowToBeat(r))) {}
/**
* @brief Creates a Fake Segment with the specified values.
* @param s the starting beat of this segment.
* @param b the number of beats this segment lasts.
*/
FakeSegment( float s, float b ):
m_iStartRow(BeatToNoteRow(s)),
m_fLengthBeats(b) {}
/**
* @brief The row in which the FakeSegment activates.
*/
int m_iStartRow;
/**
* @brief The number of beats the FakeSegment is alive for.
*/
float m_fLengthBeats;
/**
* @brief Compares two FakeSegments to see if they are equal to each other.
* @param other the other FakeSegment to compare to.
* @return the equality of the two segments.
*/
bool operator==( const FakeSegment &other ) const
{
COMPARE( m_iStartRow );
COMPARE( m_fLengthBeats );
return true;
}
/**
* @brief Compares two FakeSegments to see if they are not equal to each other.
* @param other the other FakeSegment to compare to.
* @return the inequality of the two segments.
*/
bool operator!=( const FakeSegment &other ) const { return !operator==(other); }
/**
* @brief Compares two FakeSegments to see if one is less than the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is less than the second.
*/
bool operator<( const FakeSegment &other ) const
{
return m_iStartRow < other.m_iStartRow ||
( m_iStartRow == other.m_iStartRow && m_fLengthBeats < other.m_fLengthBeats );
}
/**
* @brief Compares two FakeSegments to see if one is less than or equal to the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is less or equal to than the second.
*/
bool operator<=( const FakeSegment &other ) const
{
return ( operator<(other) || operator==(other) );
}
/**
* @brief Compares two FakeSegments to see if one is greater than the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is greater than the second.
*/
bool operator>( const FakeSegment &other ) const { return !operator<=(other); }
/**
* @brief Compares two FakeSegments to see if one is greater than or equal to the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is greater than or equal to the second.
*/
bool operator>=( const FakeSegment &other ) const { return !operator<(other); }
};
/**
* @brief Holds data for translating beats<->seconds.
@@ -658,6 +895,10 @@ public:
* @brief Sets up initial timing data.
*/
TimingData();
/**
* @brief Sets up initial timing data with a defined offset.
* @param fOffset the offset from the 0th beat. */
TimingData(float fOffset);
/**
* @brief Gets the actual BPM of the song.
* @param fMinBPMOut the minimium specified BPM.
@@ -1234,6 +1475,193 @@ public:
*/
float GetNextLabelSegmentBeatAtBeat( float fBeat ) const { return GetNextLabelSegmentBeatAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the Speed's percent at the given row.
* @param iNoteRow the row in question.
* @return the percent.
*/
float GetSpeedPercentAtRow( int iNoteRow );
/**
* @brief Retrieve the Speed's percent at the given beat.
* @param fBeat the beat in question.
* @return the percent.
*/
float GetSpeedPercentAtBeat( float fBeat ) { return GetSpeedPercentAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the Speed's wait at the given row.
* @param iNoteRow the row in question.
* @return the wait.
*/
float GetSpeedWaitAtRow( int iNoteRow );
/**
* @brief Retrieve the Speed's wait at the given beat.
* @param fBeat the beat in question.
* @return the wait.
*/
float GetSpeedWaitAtBeat( float fBeat ) { return GetSpeedWaitAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the Speed's mode at the given row.
* @param iNoteRow the row in question.
* @return the mode.
*/
unsigned short GetSpeedModeAtRow( int iNoteRow );
/**
* @brief Retrieve the Speed's mode at the given beat.
* @param fBeat the beat in question.
* @return the mode.
*/
unsigned short GetSpeedModeAtBeat( float fBeat ) { return GetSpeedModeAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Set the row to have the new Speed.
* @param iNoteRow the row to have the new Speed.
* @param fPercent the percent.
* @param fWait the wait.
* @param usMode the mode.
*/
void SetSpeedAtRow( int iNoteRow, float fPercent, float fWait, unsigned short usMode );
/**
* @brief Set the beat to have the new Speed.
* @param fBeat the beat to have the new Speed.
* @param fPercent the percent.
* @param fWait the wait.
* @param usMode the mode.
*/
void SetSpeedAtBeat( float fBeat, float fPercent, float fWait, unsigned short usMode ) { SetSpeedAtRow( BeatToNoteRow(fBeat), fPercent, fWait, usMode ); }
/**
* @brief Set the row to have the new Speed percent.
* @param iNoteRow the row to have the new Speed percent.
* @param fPercent the percent.
*/
void SetSpeedPercentAtRow( int iNoteRow, float fPercent );
/**
* @brief Set the beat to have the new Speed percent.
* @param fBeat the beat to have the new Speed percent.
* @param fPercent the percent.
*/
void SetSpeedPercentAtBeat( float fBeat, float fPercent ) { SetSpeedPercentAtRow( BeatToNoteRow(fBeat), fPercent); }
/**
* @brief Set the row to have the new Speed wait.
* @param iNoteRow the row to have the new Speed wait.
* @param fWait the wait.
*/
void SetSpeedWaitAtRow( int iNoteRow, float fWait );
/**
* @brief Set the beat to have the new Speed wait.
* @param fBeat the beat to have the new Speed wait.
* @param fWait the wait.
*/
void SetSpeedWaitAtBeat( float fBeat, float fWait ) { SetSpeedWaitAtRow( BeatToNoteRow(fBeat), fWait); }
/**
* @brief Set the row to have the new Speed mode.
* @param iNoteRow the row to have the new Speed mode.
* @param usMode the mode.
*/
void SetSpeedModeAtRow( int iNoteRow, unsigned short usMode );
/**
* @brief Set the beat to have the new Speed mode.
* @param fBeat the beat to have the new Speed mode.
* @param usMode the mode.
*/
void SetSpeedModeAtBeat( float fBeat, unsigned short usMode ) { SetSpeedModeAtRow( BeatToNoteRow(fBeat), usMode); }
/**
* @brief Retrieve the SpeedSegment at the specified row.
* @param iNoteRow the row that has a SpeedSegment.
* @return the SpeedSegment in question.
*/
SpeedSegment& GetSpeedSegmentAtRow( int iNoteRow );
/**
* @brief Retrieve the SpeedSegment at the specified beat.
* @param fBeat the beat that has a SpeedSegment.
* @return the SpeedSegment in question.
*/
SpeedSegment& GetSpeedSegmentAtBeat( float fBeat ) { return GetSpeedSegmentAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the index of the SpeedSegments at the specified row.
* @param iNoteRow the row that has a SpeedSegment.
* @return the SpeedSegment's index in question.
*/
int GetSpeedSegmentIndexAtRow( int iNoteRow ) const;
/**
* @brief Retrieve the index of the SpeedSegments at the specified beat.
* @param fBeat the beat that has a SpeedSegment.
* @return the SpeedSegment's index in question.
*/
int GetSpeedSegmentIndexAtBeat( float fBeat ) const { return GetSpeedSegmentIndexAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Add the SpeedSegment to the TimingData.
* @param seg the new SpeedSegment.
*/
void AddSpeedSegment( const SpeedSegment &seg );
/**
* @brief Determine when the fakes end.
* @param iRow The row you start on.
* @return the time when the fakes end.
*/
float GetFakeAtRow( int iRow ) const;
/**
* @brief Determine when the fakes end.
* @param fBeat The beat you start on.
* @return the time when the fakes end.
*/
float GetFakeAtBeat( float fBeat ) const { return GetFakeAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Set the beat to indicate when the FakeSegment ends.
* @param iRow The row to start on.
* @param fNew The destination beat.
*/
void SetFakeAtRow( int iRow, float fNew );
/**
* @brief Set the beat to indicate when the FakeSegment ends.
* @param fBeat The beat to start on.
* @param fNew The destination beat.
*/
void SetFakeAtBeat( float fBeat, float fNew ) { SetFakeAtRow( BeatToNoteRow( fBeat ), fNew ); }
/**
* @brief Retrieve the FakeSegment at the specified row.
* @param iRow the row to focus on.
* @return the FakeSegment in question.
*/
FakeSegment& GetFakeSegmentAtRow( int iRow );
/**
* @brief Retrieve the FakeSegment at the specified beat.
* @param fBeat the beat to focus on.
* @return the FakeSegment in question.
*/
FakeSegment& GetFakeSegmentAtBeat( float fBeat ) { return GetFakeSegmentAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Retrieve the index of the FakeSegment at the specified row.
* @param iRow the row to focus on.
* @return the index in question.
*/
int GetFakeSegmentIndexAtRow( int iRow ) const;
/**
* @brief Retrieve the index of the FakeSegment at the specified beat.
* @param fBeat the beat to focus on.
* @return the index in question.
*/
int GetFakeSegmentIndexAtBeat( float fBeat ) const { return GetFakeSegmentIndexAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Checks if the row is inside a fake.
* @param iRow the row to focus on.
* @return true if the row is inside a fake, false otherwise.
*/
bool IsFakeAtRow( int iRow ) const;
/**
* @brief Checks if the beat is inside a fake.
* @param fBeat the beat to focus on.
* @return true if the row is inside a fake, false otherwise.
*/
bool IsFakeAtBeat( float fBeat ) const { return IsFakeAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Add the FakeSegment to the TimingData.
* @param seg the new FakeSegment.
*/
void AddFakeSegment( const FakeSegment &seg );
void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor );
void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const;
@@ -1276,6 +1704,14 @@ public:
* @return true if there is at least one warp, false otherwise.
*/
bool HasWarps() const;
/**
* @brief View the TimingData to see if there is at least one fake segment involved.
* @return true if there is at least one fake segment, false otherwise. */
bool HasFakes() const;
/**
* @brief View the TimingData to see if a song changes its speed scrolling at any point.
* @return true if there is at least one change, false otherwise. */
bool HasSpeedChanges() const;
/**
* @brief Compare two sets of timing data to see if they are equal.
* @param other the other TimingData.
@@ -1301,8 +1737,15 @@ public:
COMPARE( m_ComboSegments.size() );
for( unsigned i=0; i<m_ComboSegments.size(); i++ )
COMPARE( m_ComboSegments[i] );
COMPARE( m_LabelSegments.size() );
for( unsigned i=0; i<m_LabelSegments.size(); i++ )
COMPARE( m_LabelSegments[i] );
COMPARE( m_SpeedSegments.size() );
for( unsigned i=0; i<m_SpeedSegments.size(); i++ )
COMPARE( m_SpeedSegments[i] );
COMPARE( m_FakeSegments.size() );
for( unsigned i=0; i<m_FakeSegments.size(); i++ )
COMPARE( m_FakeSegments[i] );
COMPARE( m_fBeat0OffsetInSeconds );
return true;
}
@@ -1317,6 +1760,11 @@ public:
void InsertRows( int iStartRow, int iRowsToAdd );
void DeleteRows( int iStartRow, int iRowsToDelete );
/**
* @brief Tidy up the timing data, e.g. provide default BPMs, labels, tickcounts.
*/
void TidyUpData();
// Lua
void PushSelf( lua_State *L );
/**
@@ -1354,6 +1802,10 @@ public:
* @brief The collection of LabelSegments.
*/
vector<LabelSegment> m_LabelSegments;
/** @brief The collection of SpeedSegments. */
vector<SpeedSegment> m_SpeedSegments;
/** @brief The collection of FakeSegments. */
vector<FakeSegment> m_FakeSegments;
/**
* @brief The initial offset of a song.
*/