diff --git a/src/AdjustSync.cpp b/src/AdjustSync.cpp index 7d410dad42..fb88d39475 100644 --- a/src/AdjustSync.cpp +++ b/src/AdjustSync.cpp @@ -216,6 +216,10 @@ void AdjustSync::AutosyncOffset() const vector& vpSteps = GAMESTATE->m_pCurSong->GetAllSteps(); FOREACH( Steps*, const_cast&>(vpSteps), s ) { + // Empty TimingData means it's inherited + // from the song and is already changed. + if( (*s)->m_Timing.empty() ) + continue; (*s)->m_Timing.m_fBeat0OffsetInSeconds += mean; } break; diff --git a/src/ArrowEffects.cpp b/src/ArrowEffects.cpp index ad1ff53648..e0513eb8ba 100644 --- a/src/ArrowEffects.cpp +++ b/src/ArrowEffects.cpp @@ -262,7 +262,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fBeatsUntilStep = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat); float fYOffsetBeatSpacing = fBeatsUntilStep; float fSpeedMultiplier = bShowEffects ? - pCurSteps->m_Timing.GetDisplayedSpeedPercent( + pCurSteps->GetTimingData()->GetDisplayedSpeedPercent( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible ) : 1.0f; fYOffset += fSpeedMultiplier * fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing); @@ -271,7 +271,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 0.0f ) { float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible; - float fNoteSeconds = pCurSteps->m_Timing.GetElapsedTimeFromBeat(fNoteBeat); + float fNoteSeconds = pCurSteps->GetTimingData()->GetElapsedTimeFromBeat(fNoteBeat); float fSecondsUntilStep = fNoteSeconds - fSongSeconds; float fBPM = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollBPM; float fBPS = fBPM/60.f; diff --git a/src/AutoKeysounds.cpp b/src/AutoKeysounds.cpp index 97968228da..e7fcf31f2b 100644 --- a/src/AutoKeysounds.cpp +++ b/src/AutoKeysounds.cpp @@ -103,7 +103,7 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain ) if( tn[pn].iKeysoundIndex >= 0 ) { RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex]; - float fSeconds = GAMESTATE->m_pCurSteps[pn]->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency(); + float fSeconds = GAMESTATE->m_pCurSteps[pn]->GetTimingData()->GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency(); float fPan = 0; if( !bSoundIsGlobal ) diff --git a/src/BPMDisplay.cpp b/src/BPMDisplay.cpp index f6e3cae00c..c2e29c99d8 100644 --- a/src/BPMDisplay.cpp +++ b/src/BPMDisplay.cpp @@ -204,7 +204,7 @@ void BPMDisplay::SetBpmFromSteps( const Steps* pSteps ) ASSERT( pSteps != NULL ); DisplayBpms bpms; float fMinBPM, fMaxBPM; - pSteps->m_Timing.GetActualBPM( fMinBPM, fMaxBPM ); + pSteps->GetTimingData()->GetActualBPM( fMinBPM, fMaxBPM ); bpms.Add( fMinBPM ); bpms.Add( fMaxBPM ); m_fCycleTime = 1.0f; diff --git a/src/GameState.cpp b/src/GameState.cpp index 4b966aa990..7da1e2dd0a 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -985,7 +985,7 @@ void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &ti { if( m_pCurSteps[pn] ) { - m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, m_pCurSteps[pn]->m_Timing, timestamp ); + m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, *m_pCurSteps[pn]->GetTimingData(), timestamp ); Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset ); } } diff --git a/src/GameplayAssist.cpp b/src/GameplayAssist.cpp index 66fe7398c9..9334470812 100644 --- a/src/GameplayAssist.cpp +++ b/src/GameplayAssist.cpp @@ -34,7 +34,7 @@ void GameplayAssist::PlayTicks( const NoteData &nd, const PlayerState *ps ) //float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds; fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f; - const TimingData &timing = GAMESTATE->m_pCurSteps[ps->m_PlayerNumber]->m_Timing; + const TimingData &timing = *GAMESTATE->m_pCurSteps[ps->m_PlayerNumber]->GetTimingData(); const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds ); const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) ); diff --git a/src/JsonUtil.h b/src/JsonUtil.h index 22c33ae4ec..3cb1bdf673 100644 --- a/src/JsonUtil.h +++ b/src/JsonUtil.h @@ -181,6 +181,20 @@ namespace JsonUtil } } + /* For classes with one-parameter constructors, such as Steps */ + template + static void DeserializeVectorPointersParam(vector &v, void fn(T &, const Json::Value &), const Json::Value &root, const P param) + { + for(unsigned i=0; i static void DeserializeArrayValues(vector &v, const Json::Value &root) { diff --git a/src/NotesLoaderBMS.cpp b/src/NotesLoaderBMS.cpp index 4fd1ccc054..ef49a5130c 100644 --- a/src/NotesLoaderBMS.cpp +++ b/src/NotesLoaderBMS.cpp @@ -1002,7 +1002,7 @@ bool BMSChartReader::ReadNoteData() delete holdStart; delete lastNote; - td.TidyUpData(); + td.TidyUpData( false ); out->SetNoteData(nd); out->m_Timing = td; out->TidyUpData(); @@ -1243,7 +1243,7 @@ void BMSSongLoader::AddToSong() bool BMSLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps & out ) { - Song *pSong = SONGMAN->GetSongFromSteps( &out ); + Song *pSong = out.m_pSong; // before doing anything else, load the chart first! BMSChart chart; diff --git a/src/NotesLoaderJson.cpp b/src/NotesLoaderJson.cpp index 1195ea413f..b09d564454 100644 --- a/src/NotesLoaderJson.cpp +++ b/src/NotesLoaderJson.cpp @@ -218,7 +218,7 @@ static void Deserialize( Song &out, const Json::Value &root ) { vector vpSteps; - JsonUtil::DeserializeVectorPointers( vpSteps, Deserialize, root["Charts"] ); + JsonUtil::DeserializeVectorPointersParam( vpSteps, Deserialize, root["Charts"], &out ); FOREACH( Steps*, vpSteps, iter ) out.AddSteps( *iter ); } diff --git a/src/NotesLoaderSMA.cpp b/src/NotesLoaderSMA.cpp index b8852822a1..43d5d08b50 100644 --- a/src/NotesLoaderSMA.cpp +++ b/src/NotesLoaderSMA.cpp @@ -163,7 +163,6 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach 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(song.m_SongTiming) != in.m_Timing) + // If the Steps TimingData is not empty, then they have their own + // timing. Write out the corresponding tags. + if( !in.m_Timing.empty() ) { - lines.push_back( ssprintf( "#OFFSET:%.f;", in.m_Timing.m_fBeat0OffsetInSeconds ) ); + lines.push_back( ssprintf( "#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds ) ); GetTimingTags( lines, in.m_Timing ); } diff --git a/src/Player.cpp b/src/Player.cpp index 4d3b7f157f..b158a19ee8 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -659,7 +659,7 @@ void Player::Load() const Song* pSong = GAMESTATE->m_pCurSong; - m_Timing = &GAMESTATE->m_pCurSteps[pn]->m_Timing; + m_Timing = GAMESTATE->m_pCurSteps[pn]->GetTimingData(); // Generate some cache data structure. GenerateCacheDataStructure(m_pPlayerState, m_NoteData); diff --git a/src/PlayerState.cpp b/src/PlayerState.cpp index 68016ebb34..23dee44b62 100644 --- a/src/PlayerState.cpp +++ b/src/PlayerState.cpp @@ -199,9 +199,10 @@ const SongPosition &PlayerState::GetDisplayedPosition() const const TimingData &PlayerState::GetDisplayedTiming() const { - if( GAMESTATE->m_bIsUsingStepTiming && GAMESTATE->m_pCurSteps[m_PlayerNumber] != NULL ) - return GAMESTATE->m_pCurSteps[m_PlayerNumber]->m_Timing; - return GAMESTATE->m_pCurSong->m_SongTiming; + Steps *steps = GAMESTATE->m_pCurSteps[m_PlayerNumber]; + if( steps == NULL ) + return GAMESTATE->m_pCurSong->m_SongTiming; + return *steps->GetTimingData(); } diff --git a/src/ScoreKeeperNormal.cpp b/src/ScoreKeeperNormal.cpp index 62743275db..3fe433e76c 100644 --- a/src/ScoreKeeperNormal.cpp +++ b/src/ScoreKeeperNormal.cpp @@ -122,7 +122,7 @@ void ScoreKeeperNormal::Load( * forced and not chosen by the user. */ NoteDataUtil::TransformNoteData( nd, aa, pSteps->m_StepsType, pSong ); RadarValues rvPre; - GAMESTATE->SetProcessedTimingData(&pSteps->m_Timing); + GAMESTATE->SetProcessedTimingData(pSteps->GetTimingData()); NoteDataUtil::CalculateRadarValues( nd, pSong->m_fMusicLengthSeconds, rvPre ); /* Apply user transforms to find out how the notes will really look. @@ -283,7 +283,7 @@ void ScoreKeeperNormal::HandleTapNoteScoreInternal( TapNoteScore tns, TapNoteSco m_pPlayerStageStats->m_iActualDancePoints += TapNoteScoreToDancePoints( tns ); // update judged row totals. Respect Combo segments here. - TimingData &td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing; + TimingData &td = *GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->GetTimingData(); ComboSegment *cs = td.GetComboSegmentAtRow(row); if (tns == TNS_CheckpointHit || tns >= m_MinScoreToContinueCombo) { @@ -316,7 +316,7 @@ void ScoreKeeperNormal::HandleComboInternal( int iNumHitContinueCombo, int iNumH { m_pPlayerStageStats->m_iCurMissCombo = 0; } - TimingData &td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing; + TimingData &td = *GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->GetTimingData(); if( iNumBreakCombo == 0 ) { int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow )->GetCombo() ); @@ -336,7 +336,7 @@ void ScoreKeeperNormal::HandleRowComboInternal( TapNoteScore tns, int iNumTapsIn { iNumTapsInRow = min( iNumTapsInRow, 1); } - TimingData &td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing; + TimingData &td = *GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->GetTimingData(); if ( tns >= m_MinScoreToContinueCombo ) { m_pPlayerStageStats->m_iCurMissCombo = 0; diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index fd2993b437..8a31f4e610 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -86,6 +86,7 @@ AutoScreenMessage( SM_DoRevertToLastSave ); AutoScreenMessage( SM_DoRevertFromDisk ); AutoScreenMessage( SM_BackFromTimingDataInformation ); AutoScreenMessage( SM_BackFromDifficultyMeterChange ); +AutoScreenMessage( SM_BackFromBeat0Change ); AutoScreenMessage( SM_BackFromBPMChange ); AutoScreenMessage( SM_BackFromStopChange ); AutoScreenMessage( SM_BackFromDelayChange ); @@ -1374,7 +1375,7 @@ void ScreenEdit::Update( float fDeltaTime ) continue; float fStartedHoldingSeconds = m_pSoundMusic->GetPositionSeconds() - fSecsHeld; - float fStartBeat = max( fStartPlayingAtBeat, m_pSteps->m_Timing.GetBeatFromElapsedTime(fStartedHoldingSeconds) ); + float fStartBeat = max( fStartPlayingAtBeat, m_pSteps->GetTimingData()->GetBeatFromElapsedTime(fStartedHoldingSeconds) ); float fEndBeat = max( fStartBeat, GetBeat() ); fEndBeat = min( fEndBeat, fStopPlayingAtBeat ); @@ -1419,11 +1420,11 @@ void ScreenEdit::Update( float fDeltaTime ) float fLastBeat = NoteRowToBeat(m_iStopPlayingAt); if( bButtonIsBeingPressed && m_EditState == STATE_RECORDING ) { - float fSeconds = m_pSteps->m_Timing.GetElapsedTimeFromBeat( fLastBeat ); - fLastBeat = m_pSteps->m_Timing.GetBeatFromElapsedTime( fSeconds + 0.5f ); + float fSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( fLastBeat ); + fLastBeat = m_pSteps->GetTimingData()->GetBeatFromElapsedTime( fSeconds + 0.5f ); } - float fStopAtSeconds = m_pSteps->m_Timing.GetElapsedTimeFromBeat( NoteRowToBeat(m_iStopPlayingAt) ) + 1; + float fStopAtSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( NoteRowToBeat(m_iStopPlayingAt) ) + 1; if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fMusicSeconds > fStopAtSeconds ) { TransitionEditState( ( LOOP_ON_CHART_END ? STATE_PLAYING : STATE_EDITING ) ); @@ -1600,7 +1601,7 @@ void ScreenEdit::UpdateTextInfo() sText += ssprintf("Attack here?: %s\n", FindAttackAtTime(attacks, beat) > -1 ? "YES" : "NO"); } - GAMESTATE->SetProcessedTimingData(&m_pSteps->m_Timing); + GAMESTATE->SetProcessedTimingData(m_pSteps->GetTimingData()); const StepsTypeCategory &cat = GAMEMAN->GetStepsTypeInfo(m_pSteps->m_StepsType).m_StepsTypeCategory; if (cat == StepsTypeCategory_Couple || cat == StepsTypeCategory_Routine) { @@ -1785,7 +1786,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) // does it mean in this case? return false; } - TimingData &sTiming = GetAppropriateTiming(); + const TimingData &sTiming = GetAppropriateTiming(); float playerBeat = GetAppropriatePosition().m_fSongBeat; int beatsPerMeasure = sTiming.GetTimeSignatureSegmentAtBeat( playerBeat )->GetNum(); @@ -2008,7 +2009,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) case EDIT_BUTTON_SEGMENT_NEXT: { // TODO: Work around Stops and Delays. We MAY have to separate them. - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); ScrollTo(timing.GetNextSegmentBeatAtBeat(this->currentCycleSegment, GetBeat())); } @@ -2016,7 +2017,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) case EDIT_BUTTON_SEGMENT_PREV: { // TODO: Work around Stops and Delays. We MAY have to separate them. - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); ScrollTo(timing.GetPreviousSegmentBeatAtBeat(this->currentCycleSegment, GetBeat())); } @@ -2189,7 +2190,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) } float fNewBPM = fBPM + fDelta; - GetAppropriateTiming().SetBPMAtBeat( GetBeat(), fNewBPM ); + GetAppropriateTimingForUpdate().SetBPMAtBeat( GetBeat(), fNewBPM ); (fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play(); SetDirty( true ); } @@ -2217,7 +2218,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) } // is there a StopSegment on the current row? - TimingData & timing = GetAppropriateTiming(); + TimingData & timing = GetAppropriateTimingForUpdate(); StopSegment *seg = timing.GetStopSegmentAtRow( GetRow() ); int i = timing.GetSegmentIndexAtRow(SEGMENT_STOP, GetRow()); if (i == -1 || seg->GetRow() != GetRow()) // invalid @@ -2263,7 +2264,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) } // is there a StopSegment on the current row? - TimingData & timing = GetAppropriateTiming(); + TimingData & timing = GetAppropriateTimingForUpdate(); DelaySegment *seg = timing.GetDelaySegmentAtRow( GetRow() ); int i = timing.GetSegmentIndexAtRow(SEGMENT_DELAY, GetRow()); if (i == -1 || seg->GetRow() != GetRow()) // invalid @@ -2306,7 +2307,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) else fDelta *= 40; } - GetAppropriateTiming().m_fBeat0OffsetInSeconds += fDelta; + GetAppropriateTimingForUpdate().m_fBeat0OffsetInSeconds += fDelta; (fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play(); if (GAMESTATE->m_bIsUsingStepTiming) { @@ -2511,7 +2512,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) if( pCourse == NULL ) return false; CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex]; - float fStartTime = m_pSteps->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat ); + float fStartTime = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat ); int iAttack = FindAttackAtTime( ce.attacks, fStartTime ); if( iAttack >= 0 ) @@ -2547,7 +2548,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) } else { - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); start = timing.GetElapsedTimeFromBeat(NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker)); AttackArray &attacks = (GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks); @@ -2593,7 +2594,8 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) else { // TODO: Give Song/Step Timing switches/functions here? - fStart = m_pSteps->m_Timing.GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) ); + TimingData *timing = m_pSteps->GetTimingData(); + fStart = timing->GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) ); int iAttack = FindAttackAtTime( ce.attacks, fStart ); if( iAttack >= 0 ) @@ -2602,7 +2604,7 @@ bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB ) if( m_NoteFieldEdit.m_iEndMarker == -1 ) fEnd = m_pSong->m_fMusicLengthSeconds; else - fEnd = m_pSteps->m_Timing.GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) ); + fEnd = timing->GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) ); } g_fLastInsertAttackPositionSeconds = fStart; g_fLastInsertAttackDurationSeconds = fEnd - fStart; @@ -2928,7 +2930,7 @@ bool ScreenEdit::InputPlay( const InputEventPlus &input, EditButton EditB ) fOffsetDelta *= 40; } - GetAppropriateTiming().m_fBeat0OffsetInSeconds += fOffsetDelta; + GetAppropriateTimingForUpdate().m_fBeat0OffsetInSeconds += fOffsetDelta; if (!GAMESTATE->m_bIsUsingStepTiming) { GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += fOffsetDelta; @@ -3048,8 +3050,10 @@ void ScreenEdit::TransitionEditState( EditState em ) if (!GAMESTATE->m_bIsUsingStepTiming) { + // Substitute the song timing for the step timing during + // previuw if we're in song mode backupStepTiming = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing; - GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = GAMESTATE->m_pCurSong->m_SongTiming; + GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing.Clear(); } /* Reset the note skin, in case preferences have changed. */ @@ -3312,12 +3316,33 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(i); SetDirty( true ); } + else if( SM == SM_BackFromBeat0Change && !ScreenTextEntry::s_bCancelledLast ) + { + float fBeat0 = StringToFloat( ScreenTextEntry::s_sLastAnswer ); + + TimingData &timing = GetAppropriateTimingForUpdate(); + float old = timing.m_fBeat0OffsetInSeconds; + timing.m_fBeat0OffsetInSeconds = fBeat0; + float delta = timing.m_fBeat0OffsetInSeconds - old; + + if (GAMESTATE->m_bIsUsingStepTiming) + { + GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.UpdateStartTimes(delta); + } + else + { + GAMESTATE->m_pCurSong->m_Attacks.UpdateStartTimes(delta); + GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += delta; + } + + SetDirty( true ); + } else if( SM == SM_BackFromBPMChange && !ScreenTextEntry::s_bCancelledLast ) { float fBPM = StringToFloat( ScreenTextEntry::s_sLastAnswer ); if( fBPM > 0 ) - GetAppropriateTiming().AddSegment( BPMSegment(GetRow(), fBPM) ); + GetAppropriateTimingForUpdate().AddSegment( BPMSegment(GetRow(), fBPM) ); SetDirty( true ); } @@ -3326,7 +3351,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) float fStop = StringToFloat( ScreenTextEntry::s_sLastAnswer ); if( fStop >= 0 ) - GetAppropriateTiming().AddSegment( StopSegment(GetRow(), fStop) ); + GetAppropriateTimingForUpdate().AddSegment( StopSegment(GetRow(), fStop) ); SetDirty( true ); } @@ -3335,7 +3360,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) float fDelay = StringToFloat( ScreenTextEntry::s_sLastAnswer ); if( fDelay >= 0 ) - GetAppropriateTiming().AddSegment( DelaySegment(GetRow(), fDelay) ); + GetAppropriateTimingForUpdate().AddSegment( DelaySegment(GetRow(), fDelay) ); SetDirty( true ); } @@ -3344,7 +3369,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) int iNum, iDen; if( sscanf( ScreenTextEntry::s_sLastAnswer.c_str(), " %d / %d ", &iNum, &iDen ) == 2 ) - GetAppropriateTiming().AddSegment( TimeSignatureSegment(GetRow(), iNum, iDen) ); + GetAppropriateTimingForUpdate().AddSegment( TimeSignatureSegment(GetRow(), iNum, iDen) ); SetDirty( true ); } @@ -3353,7 +3378,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) int iTick = StringToInt( ScreenTextEntry::s_sLastAnswer ); if ( iTick >= 0 && iTick <= ROWS_PER_BEAT ) - GetAppropriateTiming().AddSegment( TickcountSegment( GetRow(), iTick) ); + GetAppropriateTimingForUpdate().AddSegment( TickcountSegment( GetRow(), iTick) ); SetDirty( true ); } @@ -3362,7 +3387,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) int iCombo, iMiss; if (sscanf(ScreenTextEntry::s_sLastAnswer.c_str(), " %d / %d ", &iCombo, &iMiss) == 2) - GetAppropriateTiming().AddSegment( ComboSegment(GetRow(), iCombo, iMiss) ); + GetAppropriateTimingForUpdate().AddSegment( ComboSegment(GetRow(), iCombo, iMiss) ); SetDirty( true ); } @@ -3375,7 +3400,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) // XXX: these should be in the NotesWriters where they're needed. sLabel.Replace("=", "_"); sLabel.Replace(",", "_"); - GetAppropriateTiming().AddSegment( LabelSegment(GetRow(), sLabel) ); + GetAppropriateTimingForUpdate().AddSegment( LabelSegment(GetRow(), sLabel) ); SetDirty( true ); } } @@ -3384,14 +3409,14 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) float fWarp = StringToFloat( ScreenTextEntry::s_sLastAnswer ); if( fWarp >= 0 ) // allow 0 to kill a warp. { - GetAppropriateTiming().SetWarpAtBeat( GetBeat(), fWarp ); + GetAppropriateTimingForUpdate().SetWarpAtBeat( GetBeat(), fWarp ); SetDirty( true ); } } else if( SM == SM_BackFromSpeedPercentChange && !ScreenTextEntry::s_bCancelledLast ) { float fNum = StringToFloat( ScreenTextEntry::s_sLastAnswer ); - GetAppropriateTiming().SetSpeedPercentAtBeat( GetBeat(), fNum ); + GetAppropriateTimingForUpdate().SetSpeedPercentAtBeat( GetBeat(), fNum ); SetDirty( true ); } else if ( SM == SM_BackFromSpeedWaitChange && !ScreenTextEntry::s_bCancelledLast ) @@ -3399,7 +3424,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) float fDen = StringToFloat( ScreenTextEntry::s_sLastAnswer ); if( fDen >= 0) { - GetAppropriateTiming().SetSpeedWaitAtBeat( GetBeat(), fDen ); + GetAppropriateTimingForUpdate().SetSpeedWaitAtBeat( GetBeat(), fDen ); } SetDirty( true ); } @@ -3407,11 +3432,11 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) { if( ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "b" || ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "B" ) { - GetAppropriateTiming().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_BEATS ); + GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_BEATS ); } else if( ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "s" || ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "S" ) { - GetAppropriateTiming().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_SECONDS ); + GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_SECONDS ); } else { @@ -3420,14 +3445,14 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) SpeedSegment::BaseUnit unit = (tmp == 0 ) ? SpeedSegment::UNIT_BEATS : SpeedSegment::UNIT_SECONDS; - GetAppropriateTiming().SetSpeedModeAtBeat( GetBeat(), unit ); + GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), unit ); } SetDirty( true ); } else if( SM == SM_BackFromScrollChange && !ScreenTextEntry::s_bCancelledLast ) { float fNum = StringToFloat( ScreenTextEntry::s_sLastAnswer ); - GetAppropriateTiming().SetScrollAtBeat( GetBeat(), fNum ); + GetAppropriateTimingForUpdate().SetScrollAtBeat( GetBeat(), fNum ); SetDirty( true ); } else if ( SM == SM_BackFromFakeChange && !ScreenTextEntry::s_bCancelledLast ) @@ -3435,7 +3460,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) float fFake = StringToFloat( ScreenTextEntry::s_sLastAnswer ); if( fFake >= 0 ) // allow 0 to kill a fake. { - GetAppropriateTiming().AddSegment( FakeSegment(GetRow(), fFake) ); + GetAppropriateTimingForUpdate().AddSegment( FakeSegment(GetRow(), fFake) ); SetDirty( true ); } } @@ -3708,7 +3733,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) { int attackChoice = ScreenMiniMenu::s_iLastRowCode; int attackDecision = ScreenMiniMenu::s_viLastAnswers[attackChoice]; - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); float startTime = timing.GetElapsedTimeFromBeat(GetBeat()); AttackArray &attacks = (GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks); @@ -3784,7 +3809,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) else if (SM == SM_BackFromInsertStepAttack) { int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0]; - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); g_fLastInsertAttackPositionSeconds = timing.GetElapsedTimeFromBeat( GetBeat() ); g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertStepAttack.rows[0].choices[iDurationChoice] ); AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks; @@ -3817,7 +3842,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) // TODO: Handle Song/Step Timing functions/switches here? - g_fLastInsertAttackPositionSeconds = m_pSteps->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_Position.m_fSongBeat ); + g_fLastInsertAttackPositionSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( GAMESTATE->m_Position.m_fSongBeat ); g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertCourseAttack.rows[0].choices[iDurationChoice] ); iAttack = FindAttackAtTime( ce.attacks, g_fLastInsertAttackPositionSeconds ); @@ -3913,7 +3938,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) if( ScreenPrompt::s_LastAnswer == ANSWER_YES ) { SaveUndo(); - m_pSteps->m_Timing = m_pSong->m_SongTiming; + m_pSteps->m_Timing.Clear(); SetDirty( true ); } } @@ -4116,26 +4141,6 @@ static void ChangeArtistTranslit( const RString &sNew ) pSong->m_sArtistTranslit = sNew; } -static void ChangeBeat0Offset( const RString &sNew ) -{ - TimingData &timing = (GAMESTATE->m_bIsUsingStepTiming ? - GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing : - GAMESTATE->m_pCurSong->m_SongTiming); - float old = timing.m_fBeat0OffsetInSeconds; - timing.m_fBeat0OffsetInSeconds = StringToFloat(sNew); - float delta = timing.m_fBeat0OffsetInSeconds - old; - - if (GAMESTATE->m_bIsUsingStepTiming) - { - GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.UpdateStartTimes(delta); - } - else - { - GAMESTATE->m_pCurSong->m_Attacks.UpdateStartTimes(delta); - GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += delta; - } -} - static void ChangeLastSecondHint( const RString &sNew ) { Song &s = *GAMESTATE->m_pCurSong; @@ -4174,10 +4179,22 @@ static void ChangeStepsMaxBPM(const RString &sNew) step->SetMaxBPM(StringToFloat(sNew)); } -TimingData & ScreenEdit::GetAppropriateTiming() const +const TimingData & ScreenEdit::GetAppropriateTiming() const { if( GAMESTATE->m_bIsUsingStepTiming ) { + return *m_pSteps->GetTimingData(); + } + return m_pSong->m_SongTiming; +} + +TimingData & ScreenEdit::GetAppropriateTimingForUpdate() +{ + if( GAMESTATE->m_bIsUsingStepTiming ) + { + // Copy from song if there is no step timing + if( m_pSteps->m_Timing.empty() ) + m_pSteps->m_Timing = m_pSong->m_SongTiming; return m_pSteps->m_Timing; } return m_pSong->m_SongTiming; @@ -4197,12 +4214,12 @@ inline void ScreenEdit::SetBeat(float fBeat) if( !GAMESTATE->m_bIsUsingStepTiming ) { GAMESTATE->m_Position.m_fSongBeat = fBeat; - GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = m_pSteps->m_Timing.GetBeatFromElapsedTime(m_pSong->m_SongTiming.GetElapsedTimeFromBeat(fBeat)); + GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = m_pSteps->GetTimingData()->GetBeatFromElapsedTime(m_pSong->m_SongTiming.GetElapsedTimeFromBeat(fBeat)); } else { GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = fBeat; - GAMESTATE->m_Position.m_fSongBeat = m_pSong->m_SongTiming.GetBeatFromElapsedTime(m_pSteps->m_Timing.GetElapsedTimeFromBeat(fBeat)); + GAMESTATE->m_Position.m_fSongBeat = m_pSong->m_SongTiming.GetBeatFromElapsedTime(m_pSteps->GetTimingData()->GetElapsedTimeFromBeat(fBeat)); } } @@ -4223,7 +4240,7 @@ inline int ScreenEdit::GetRow() void ScreenEdit::DisplayTimingMenu() { int row = GetRow(); - TimingData &pTime = GetAppropriateTiming(); + const TimingData &pTime = GetAppropriateTiming(); bool bHasSpeedOnThisRow = pTime.GetSpeedSegmentAtRow( row )->GetRow() == row; // bool bIsSelecting = ( (m_NoteFieldEdit.m_iEndMarker != -1) && (m_NoteFieldEdit.m_iBeginMarker != -1) ); @@ -4271,7 +4288,7 @@ static LocalizedString SAVE_CHANGES_BEFORE_EXITING ( "ScreenEdit", "Do you want void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAnswers ) { - GAMESTATE->SetProcessedTimingData(&m_pSteps->m_Timing); + GAMESTATE->SetProcessedTimingData(m_pSteps->GetTimingData()); switch( c ) { DEFAULT_FAIL( c ); @@ -4713,7 +4730,7 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &iAn NoteDataUtil::ScaleRegion( m_NoteDataEdit, fScale, iStartIndex, iEndIndex ); // scale timing data - GetAppropriateTiming().ScaleRegion(fScale, + GetAppropriateTimingForUpdate().ScaleRegion(fScale, m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker, true ); @@ -4755,9 +4772,9 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &iAn m_NoteFieldEdit.m_iBeginMarker + 1, m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker ); - GetAppropriateTiming().DeleteRows( m_NoteFieldEdit.m_iBeginMarker + 1, + GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker + 1, m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker ); - GetAppropriateTiming().SetStopAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength ); + GetAppropriateTimingForUpdate().SetStopAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength ); m_NoteFieldEdit.m_iBeginMarker = -1; m_NoteFieldEdit.m_iEndMarker = -1; break; @@ -4775,9 +4792,9 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &iAn m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker ); - GetAppropriateTiming().DeleteRows( m_NoteFieldEdit.m_iBeginMarker, + GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker ); - GetAppropriateTiming().SetDelayAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength ); + GetAppropriateTimingForUpdate().SetDelayAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength ); m_NoteFieldEdit.m_iBeginMarker = -1; m_NoteFieldEdit.m_iEndMarker = -1; break; @@ -4786,7 +4803,7 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &iAn { float startBeat = NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker); float lengthBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) - startBeat; - GetAppropriateTiming().SetWarpAtBeat(startBeat,lengthBeat); + GetAppropriateTimingForUpdate().SetWarpAtBeat(startBeat,lengthBeat); SetDirty(true); break; } @@ -4794,7 +4811,7 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &iAn { float startBeat = NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker); float endBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker); - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); float &start = g_fLastInsertAttackPositionSeconds; float &length = g_fLastInsertAttackDurationSeconds; start = timing.GetElapsedTimeFromBeat(startBeat); @@ -4819,7 +4836,7 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &iAn { int startRow = m_NoteFieldEdit.m_iBeginMarker; float lengthBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) - NoteRowToBeat(startRow); - GetAppropriateTiming().AddSegment( FakeSegment(startRow,lengthBeat) ); + GetAppropriateTimingForUpdate().AddSegment( FakeSegment(startRow,lengthBeat) ); SetDirty(true); break; } @@ -4962,12 +4979,12 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns NoteTypeToRow((NoteType)iAnswers[c]) : 48); break; case shift_pauses_forward: - GetAppropriateTiming().InsertRows( GetRow(), + GetAppropriateTimingForUpdate().InsertRows( GetRow(), iAnswers.size() > 0 ? NoteTypeToRow((NoteType)iAnswers[c]) : 48); break; case shift_pauses_backward: - GetAppropriateTiming().DeleteRows( GetRow() + 1, + GetAppropriateTimingForUpdate().DeleteRows( GetRow() + 1, iAnswers.size() > 0 ? NoteTypeToRow((NoteType)iAnswers[c]) : 48); break; @@ -4975,18 +4992,18 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns case convert_pause_to_beat: { float fStopSeconds = GetAppropriateTiming().GetStopAtRow(GetRow()); - GetAppropriateTiming().SetStopAtBeat( GetBeat() , 0 ); + GetAppropriateTimingForUpdate().SetStopAtBeat( GetBeat() , 0 ); float fStopBeats = fStopSeconds * GetAppropriateTiming().GetBPMAtBeat( GetBeat() ) / 60; // don't move the step from where it is, just move everything later NoteDataUtil::InsertRows( m_NoteDataEdit, GetRow() + 1, BeatToNoteRow(fStopBeats) ); - GetAppropriateTiming().InsertRows( GetRow() + 1, BeatToNoteRow(fStopBeats) ); + GetAppropriateTimingForUpdate().InsertRows( GetRow() + 1, BeatToNoteRow(fStopBeats) ); } break; case convert_delay_to_beat: { - TimingData &timing = GetAppropriateTiming(); + TimingData &timing = GetAppropriateTimingForUpdate(); float pause = timing.GetDelayAtRow(GetRow()); timing.SetDelayAtRow(GetRow(), 0); @@ -4998,7 +5015,7 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns } case last_second_at_beat: { - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); Song &s = *GAMESTATE->m_pCurSong; s.SetSpecifiedLastSecond(timing.GetElapsedTimeFromBeat(GetBeat())); break; @@ -5219,11 +5236,14 @@ void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice { DEFAULT_FAIL( c ); case beat_0_offset: - ScreenTextEntry::TextEntry( SM_None, ENTER_BEAT_0_OFFSET, - FloatToString(GetAppropriateTiming().m_fBeat0OffsetInSeconds), 20, - ScreenTextEntry::FloatValidate, ChangeBeat0Offset, NULL ); + ScreenTextEntry::TextEntry( + SM_BackFromBeat0Change, + ENTER_BEAT_0_OFFSET, + FloatToString(GetAppropriateTiming().m_fBeat0OffsetInSeconds), + 20 + ); break; - case bpm: + case bpm: ScreenTextEntry::TextEntry( SM_BackFromBPMChange, ENTER_BPM_VALUE, @@ -5778,7 +5798,7 @@ static RString GetDeviceButtonsLocalized( const vector &veb, const M void ScreenEdit::DoStepAttackMenu() { - TimingData &timing = GetAppropriateTiming(); + const TimingData &timing = GetAppropriateTiming(); float startTime = timing.GetElapsedTimeFromBeat(GetBeat()); AttackArray &attacks = (GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks); diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index a7a0b2b01a..f73679fbbd 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -682,7 +682,10 @@ private: /** * @brief Retrieve the appropriate TimingData based on GAMESTATE. * @return the proper TimingData. */ - TimingData & GetAppropriateTiming() const; + const TimingData & GetAppropriateTiming() const; + /** + * @brief Retrieve the appropriate TimingData to use for updating. */ + TimingData & GetAppropriateTimingForUpdate(); /** * @brief Retrieve the appropriate SongPosition data based on GAMESTATE. * @return the proper SongPosition. */ diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp index 2db7247365..71a7035b32 100644 --- a/src/ScreenGameplay.cpp +++ b/src/ScreenGameplay.cpp @@ -2340,7 +2340,7 @@ void ScreenGameplay::SaveStats() const NoteData &nd = pi->m_pPlayer->GetNoteData(); PlayerNumber pn = pi->m_pn; - GAMESTATE->SetProcessedTimingData(&GAMESTATE->m_pCurSteps[pn]->m_Timing); + GAMESTATE->SetProcessedTimingData(GAMESTATE->m_pCurSteps[pn]->GetTimingData()); NoteDataUtil::CalculateRadarValues( nd, fMusicLen, rv ); pss.m_radarPossible += rv; NoteDataWithScoring::GetActualRadarValues( nd, pss, fMusicLen, rv ); diff --git a/src/ScreenHowToPlay.cpp b/src/ScreenHowToPlay.cpp index 16d5ae3f62..2daf79084e 100644 --- a/src/ScreenHowToPlay.cpp +++ b/src/ScreenHowToPlay.cpp @@ -151,7 +151,8 @@ void ScreenHowToPlay::Init() Steps *pSteps = SongUtil::GetClosestNotes( &m_Song, pStyle->m_StepsType, Difficulty_Beginner ); ASSERT_M( pSteps != NULL, ssprintf("No playable steps of StepsType '%s' for ScreenHowToPlay", StringConversion::ToString(pStyle->m_StepsType).c_str()) ); - pSteps->m_Timing.TidyUpData(); + m_Song.m_SongTiming.TidyUpData( false ); + pSteps->m_Timing.TidyUpData( true ); NoteData tempNoteData; pSteps->GetNoteData( tempNoteData ); pStyle->GetTransformedNoteDataForStyle( PLAYER_1, tempNoteData, m_NoteData ); diff --git a/src/ScreenOptionsManageEditSteps.cpp b/src/ScreenOptionsManageEditSteps.cpp index f009ff6cbb..3a6b646bcb 100644 --- a/src/ScreenOptionsManageEditSteps.cpp +++ b/src/ScreenOptionsManageEditSteps.cpp @@ -87,7 +87,7 @@ void ScreenOptionsManageEditSteps::BeginScreen() vHands.push_back( OptionRowHandlerUtil::MakeNull() ); OptionRowDefinition &def = vHands.back()->m_Def; - Song *pSong = SONGMAN->GetSongFromSteps( *s ); + Song *pSong = (*s)->m_pSong; def.m_sName = pSong->GetTranslitFullTitle() + " - " + (*s)->GetDescription(); def.m_bAllowThemeTitle = false; // not themable @@ -153,7 +153,7 @@ void ScreenOptionsManageEditSteps::HandleScreenMessage( const ScreenMessage SM ) ASSERT( ScreenTextEntry::s_sLastAnswer != "" ); // validate should have assured this Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - Song *pSong = SONGMAN->GetSongFromSteps( pSteps ); + Song *pSong = pSteps->m_pSong; RString sOldDescription = pSteps->GetDescription(); pSteps->SetDescription( ScreenTextEntry::s_sLastAnswer ); @@ -190,7 +190,7 @@ void ScreenOptionsManageEditSteps::HandleScreenMessage( const ScreenMessage SM ) case StepsEditAction_Edit: { Steps *pSteps = GetStepsWithFocus(); - Song *pSong = SONGMAN->GetSongFromSteps( pSteps ); + Song *pSong = pSteps->m_pSong; GAMESTATE->m_pCurSong.Set( pSong ); GAMESTATE->m_pCurSteps[PLAYER_1].Set( pSteps ); @@ -230,7 +230,7 @@ void ScreenOptionsManageEditSteps::HandleScreenMessage( const ScreenMessage SM ) void ScreenOptionsManageEditSteps::AfterChangeRow( PlayerNumber pn ) { Steps *pSteps = GetStepsWithFocus(); - Song *pSong = pSteps ? SONGMAN->GetSongFromSteps( pSteps ) : NULL; + Song *pSong = pSteps ? pSteps->m_pSong : NULL; GAMESTATE->m_pCurSong.Set( pSong ); GAMESTATE->m_pCurSteps[PLAYER_1].Set( pSteps ); diff --git a/src/ScreenSelectMusic.cpp b/src/ScreenSelectMusic.cpp index c5eaa06bda..c79f889da4 100644 --- a/src/ScreenSelectMusic.cpp +++ b/src/ScreenSelectMusic.cpp @@ -1779,7 +1779,7 @@ void ScreenSelectMusic::AfterMusicChange() if(SAMPLE_MUSIC_PREVIEW_MODE == SampleMusicPreviewMode_LastSong) { m_sSampleMusicToPlay = pSong->GetMusicPath(); - m_pSampleMusicTimingData = &pSong->m_Timing; + m_pSampleMusicTimingData = &pSong->m_SongTiming; m_fSampleStartSeconds = pSong->m_fMusicSampleStartSeconds; m_fSampleLengthSeconds = pSong->m_fMusicSampleLengthSeconds; } diff --git a/src/ScreenSyncOverlay.cpp b/src/ScreenSyncOverlay.cpp index bd556266d0..b0ba604e4b 100644 --- a/src/ScreenSyncOverlay.cpp +++ b/src/ScreenSyncOverlay.cpp @@ -233,6 +233,10 @@ bool ScreenSyncOverlay::Input( const InputEventPlus &input ) FOREACH( Steps*, const_cast&>(vpSteps), s ) { TimingData &pTiming = (*s)->m_Timing; + // Empty means it inherits song timing, + // which has already been updated. + if( pTiming.empty() ) + continue; float second = sTiming.GetElapsedTimeFromBeat(GAMESTATE->m_Position.m_fSongBeat); seg = pTiming.GetBPMSegmentAtBeat(pTiming.GetBeatFromElapsedTime(second)); seg->SetBPS( seg->GetBPS() + fDelta ); @@ -278,6 +282,10 @@ bool ScreenSyncOverlay::Input( const InputEventPlus &input ) const vector& vpSteps = GAMESTATE->m_pCurSong->GetAllSteps(); FOREACH( Steps*, const_cast&>(vpSteps), s ) { + // Empty means it inherits song timing, + // which has already been updated. + if( (*s)->m_Timing.empty() ) + continue; (*s)->m_Timing.m_fBeat0OffsetInSeconds += fDelta; } } diff --git a/src/Song.cpp b/src/Song.cpp index c5da1cb5f0..5d0d16eac9 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -191,14 +191,14 @@ void Song::AddLyricSegment( LyricSegment seg ) Steps *Song::CreateSteps() { - Steps *pSteps = new Steps; + Steps *pSteps = new Steps(this); InitSteps( pSteps ); return pSteps; } void Song::InitSteps(Steps *pSteps) { - pSteps->m_Timing = this->m_SongTiming; + // TimingData is initially empty (i.e. defaults to song timing) pSteps->m_sAttackString = this->m_sAttackString; pSteps->m_Attacks = this->m_Attacks; pSteps->SetDisplayBPM(this->m_DisplayBPMType); @@ -415,7 +415,7 @@ bool Song::ReloadFromSongDir( RString sDir ) // The leftovers in the map are steps that didn't exist before we reverted for( map::const_iterator it = mNewSteps.begin(); it != mNewSteps.end(); ++it ) { - Steps *NewSteps = new Steps(); + Steps *NewSteps = new Steps(this); *NewSteps = *(it->second); AddSteps( NewSteps ); } @@ -537,11 +537,11 @@ void Song::TidyUpData( bool fromCache, bool /* duringCache */ ) m_fMusicLengthSeconds = 0; } - m_SongTiming.TidyUpData(); + m_SongTiming.TidyUpData( false ); FOREACH( Steps *, m_vpSteps, s ) { - (*s)->m_Timing.TidyUpData(); + (*s)->m_Timing.TidyUpData( true ); } /* Generate these before we autogen notes, so the new notes can inherit @@ -889,9 +889,9 @@ void Song::ReCalculateRadarValuesAndLastSecond(bool fromCache, bool duringCache) if( tempNoteData.GetLastRow() != 0 ) { localFirst = min(localFirst, - pSteps->m_Timing.GetElapsedTimeFromBeat(tempNoteData.GetFirstBeat())); + pSteps->GetTimingData()->GetElapsedTimeFromBeat(tempNoteData.GetFirstBeat())); localLast = max(localLast, - pSteps->m_Timing.GetElapsedTimeFromBeat(tempNoteData.GetLastBeat())); + pSteps->GetTimingData()->GetElapsedTimeFromBeat(tempNoteData.GetLastBeat())); } wipe_notedata: if (duringCache) @@ -1136,7 +1136,7 @@ void Song::AutoGen( StepsType ntTo, StepsType ntFrom ) const Steps* pOriginalNotes = m_vpSteps[j]; if( pOriginalNotes->m_StepsType == ntFrom ) { - Steps* pNewNotes = new Steps; + Steps* pNewNotes = new Steps(this); pNewNotes->AutogenFrom( pOriginalNotes, ntTo ); this->AddSteps( pNewNotes ); } @@ -1587,7 +1587,8 @@ bool Song::IsEditAlreadyLoaded( Steps* pSteps ) const bool Song::IsStepsUsingDifferentTiming(Steps *pSteps) const { - return pSteps->m_Timing != this->m_SongTiming; + // XXX This no longer depends on Song at all + return !pSteps->m_Timing.empty(); } bool Song::HasSignificantBpmChangesOrStops() const diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 0558824d46..4450f1db85 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -1127,30 +1127,9 @@ void SongManager::GetStepsLoadedFromProfile( vector &AddTo, ProfileSlot } } -Song *SongManager::GetSongFromSteps( Steps *pSteps ) const -{ - ASSERT( pSteps != NULL ); - const vector &vSongs = GetAllSongs(); - FOREACH_CONST( Song*, vSongs, song ) - { - vector vSteps; - SongUtil::GetSteps( *song, vSteps ); - - FOREACH_CONST( Steps*, vSteps, steps ) - { - if( *steps == pSteps ) - { - return *song; - } - } - } - FAIL_M("No song found for steps"); -} - void SongManager::DeleteSteps( Steps *pSteps ) { - Song *pSong = GetSongFromSteps( pSteps ); - pSong->DeleteSteps( pSteps ); + pSteps->m_pSong->DeleteSteps( pSteps ); } bool SongManager::WasLoadedFromAdditionalSongs( const Song *pSong ) const @@ -1942,11 +1921,12 @@ public: static int GetNumCourses( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumCourses() ); return 1; } static int GetNumAdditionalCourses( T* p, lua_State *L ){ lua_pushnumber( L, p->GetNumAdditionalCourses() ); return 1; } static int GetNumCourseGroups( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumCourseGroups() ); return 1; } + /* Note: this could now be implemented as Luna::GetSong */ static int GetSongFromSteps( T* p, lua_State *L ) { Song *pSong = NULL; - if( lua_isnil(L,1) ) { pSong = p->GetSongFromSteps( NULL ); } - else { Steps *pSteps = Luna::check(L,1); pSong = p->GetSongFromSteps( pSteps ); } + if( lua_isnil(L,1) ) { pSong = NULL; } + else { Steps *pSteps = Luna::check(L,1); pSong = pSteps->m_pSong; } if(pSong) pSong->PushSelf(L); else lua_pushnil(L); return 1; diff --git a/src/SongManager.h b/src/SongManager.h index 2ad3ff6ace..0ddb5bd69a 100644 --- a/src/SongManager.h +++ b/src/SongManager.h @@ -144,7 +144,6 @@ public: int GetSongRank(Song* pSong); void GetStepsLoadedFromProfile( vector &AddTo, ProfileSlot slot ) const; - Song *GetSongFromSteps( Steps *pSteps ) const; void DeleteSteps( Steps *pSteps ); // transfers ownership of pSteps bool WasLoadedFromAdditionalSongs( const Song *pSong ) const; bool WasLoadedFromAdditionalCourses( const Course *pCourse ) const; diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index 8b03b31233..eb57e74288 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -795,7 +795,7 @@ static LocalizedString EDIT_NAME_CANNOT_CONTAIN ( "SongUtil", "The edit name can bool SongUtil::ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ) { Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - Song *pSong = SONGMAN->GetSongFromSteps( pSteps ); + Song *pSong = pSteps->m_pSong; ASSERT( pSteps->IsAnEdit() ); diff --git a/src/Steps.cpp b/src/Steps.cpp index 70c6edc168..40c9258665 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -42,7 +42,7 @@ static const char *DisplayBPMNames[] = XToString( DisplayBPM ); LuaXType( DisplayBPM ); -Steps::Steps(): m_StepsType(StepsType_Invalid), +Steps::Steps(Song *song): m_StepsType(StepsType_Invalid), m_pSong(song), parent(NULL), m_pNoteData(new NoteData), m_bNoteDataIsFilled(false), m_sNoteDataCompressed(""), m_sFilename(""), m_bSavedToDisk(false), m_LoadedFromProfile(ProfileSlot_Invalid), m_iHash(0), @@ -66,7 +66,7 @@ void Steps::GetDisplayBpms( DisplayBpms &AddTo ) const else { float fMinBPM, fMaxBPM; - this->m_Timing.GetActualBPM( fMinBPM, fMaxBPM ); + this->GetTimingData()->GetActualBPM( fMinBPM, fMaxBPM ); AddTo.Add( fMinBPM ); AddTo.Add( fMaxBPM ); } @@ -291,7 +291,7 @@ void Steps::CalculateRadarValues( float fMusicLengthSeconds ) FOREACH_PlayerNumber( pn ) m_CachedRadarValues[pn].Zero(); - GAMESTATE->SetProcessedTimingData(&this->m_Timing); + GAMESTATE->SetProcessedTimingData(this->GetTimingData()); if( tempNoteData.IsComposite() ) { vector vParts; @@ -465,6 +465,7 @@ void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks ); parent = NULL; m_Timing = pSource->m_Timing; + this->m_pSong = pSource->m_pSong; this->m_Attacks = pSource->m_Attacks; this->m_sAttackString = pSource->m_sAttackString; this->SetNoteData( noteData ); @@ -519,13 +520,19 @@ void Steps::SetMeter( int meter ) m_iMeter = meter; } +const TimingData *Steps::GetTimingData() const +{ + return m_Timing.empty() ? &m_pSong->m_SongTiming : &m_Timing; +} + bool Steps::HasSignificantTimingChanges() const { - if( m_Timing.HasStops() || m_Timing.HasDelays() || m_Timing.HasWarps() || - m_Timing.HasSpeedChanges() || m_Timing.HasScrollChanges() ) + const TimingData *timing = GetTimingData(); + if( timing->HasStops() || timing->HasDelays() || timing->HasWarps() || + timing->HasSpeedChanges() || timing->HasScrollChanges() ) return true; - if( m_Timing.HasBpmChanges() ) + if( timing->HasBpmChanges() ) { // check to see if these changes are significant. if( (GetMaxBPM() - GetMinBPM()) > 3.000f ) @@ -578,7 +585,7 @@ public: } static int GetTimingData( T* p, lua_State *L ) { - p->m_Timing.PushSelf(L); + p->GetTimingData()->PushSelf(L); return 1; } static int GetHash( T* p, lua_State *L ) { lua_pushnumber( L, p->GetHash() ); return 1; } diff --git a/src/Steps.h b/src/Steps.h index e81e754269..178a0ab725 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -42,7 +42,7 @@ class Steps { public: /** @brief Set up the Steps with initial values. */ - Steps(); + Steps( Song* song ); /** @brief Destroy the Steps that are no longer needed. */ ~Steps(); @@ -157,6 +157,12 @@ public: * This is required to allow Split Timing. */ TimingData m_Timing; + /** + * @brief Retrieves the appropriate timing data for the Steps. Falls + * back on the Song if needed. */ + const TimingData *GetTimingData() const; + TimingData *GetTimingData() { return const_cast( static_cast( this )->GetTimingData() ); }; + /** * @brief Determine if the Steps have any major timing changes during gameplay. * @return true if it does, or false otherwise. */ @@ -171,6 +177,8 @@ public: void PushSelf( lua_State *L ); StepsType m_StepsType; + /** @brief The Song these Steps are associated with */ + Song *m_pSong; CachedObject m_CachedObject; diff --git a/src/TimingData.cpp b/src/TimingData.cpp index 9fb9668cce..02653f81b8 100644 --- a/src/TimingData.cpp +++ b/src/TimingData.cpp @@ -863,8 +863,13 @@ float TimingData::GetDisplayedSpeedPercent( float fSongBeat, float fMusicSeconds } -void TimingData::TidyUpData() +void TimingData::TidyUpData(bool allowEmpty) { + // Empty TimingData is used to implement steps with no timing of their + // own. Don't override this. + if( allowEmpty && empty() ) + return; + // If there are no BPM segments, provide a default. vector *segs = m_avpTimingSegments; if( segs[SEGMENT_BPM].empty() ) diff --git a/src/TimingData.h b/src/TimingData.h index ee644b311c..7fd1acdcd1 100644 --- a/src/TimingData.h +++ b/src/TimingData.h @@ -220,11 +220,11 @@ public: void SetLabelAtBeat( float fBeat, const RString sLabel ) { SetLabelAtRow( BeatToNoteRow( fBeat ), sLabel ); } bool DoesLabelExist( const RString& sLabel ) const; - float GetSpeedPercentAtRow( int iNoteRow ) { return GetSpeedSegmentAtRow(iNoteRow)->GetRatio(); } - float GetSpeedPercentAtBeat( float fBeat ) { return GetSpeedPercentAtRow( BeatToNoteRow(fBeat) ); } + float GetSpeedPercentAtRow( int iNoteRow ) const { return GetSpeedSegmentAtRow(iNoteRow)->GetRatio(); } + float GetSpeedPercentAtBeat( float fBeat ) const { return GetSpeedPercentAtRow( BeatToNoteRow(fBeat) ); } - float GetSpeedWaitAtRow( int iNoteRow ) { return GetSpeedSegmentAtRow(iNoteRow)->GetDelay(); } - float GetSpeedWaitAtBeat( float fBeat ) { return GetSpeedWaitAtRow( BeatToNoteRow(fBeat) ); } + float GetSpeedWaitAtRow( int iNoteRow ) const { return GetSpeedSegmentAtRow(iNoteRow)->GetDelay(); } + float GetSpeedWaitAtBeat( float fBeat ) const { return GetSpeedWaitAtRow( BeatToNoteRow(fBeat) ); } // XXX: is there any point to having specific unit types? SpeedSegment::BaseUnit GetSpeedModeAtRow( int iNoteRow ) const { return GetSpeedSegmentAtRow(iNoteRow)->GetUnit(); } @@ -378,8 +378,10 @@ public: /** * @brief Tidy up the timing data, e.g. provide default BPMs, labels, tickcounts. + * @param allowEmpty true if completely empty TimingData should be left + * alone, false if it should be changed */ - void TidyUpData(); + void TidyUpData(bool allowEmpty); // Lua void PushSelf( lua_State *L ); diff --git a/src/Trail.cpp b/src/Trail.cpp index f1e334e686..d2c9d8febe 100644 --- a/src/Trail.cpp +++ b/src/Trail.cpp @@ -115,7 +115,7 @@ const RadarValues &Trail::GetRadarValues() const NoteData nd; pSteps->GetNoteData( nd ); RadarValues rv_orig; - GAMESTATE->SetProcessedTimingData(const_cast(&pSteps->m_Timing)); + GAMESTATE->SetProcessedTimingData(const_cast(pSteps->GetTimingData())); NoteDataUtil::CalculateRadarValues( nd, e->pSong->m_fMusicLengthSeconds, rv_orig ); PlayerOptions po; po.FromString( e->Modifiers );