diff --git a/.hgignore b/.hgignore index ea6889df61..aac27be6ce 100644 --- a/.hgignore +++ b/.hgignore @@ -72,6 +72,7 @@ Data/SpeedMods.txt Program/StepMania*.lib crashinfo.txt Utils/temp* +*.lnk syntax: regexp .*\#.*\#$ diff --git a/Docs/Changelog_SSCformat.txt b/Docs/Changelog_SSCformat.txt index c50c25737f..6bacef7c8d 100644 --- a/Docs/Changelog_SSCformat.txt +++ b/Docs/Changelog_SSCformat.txt @@ -9,6 +9,9 @@ change to JSON, but it is unsure if this will be done. Implement .ssc at your own risk. ________________________________________________________________________________ +[v0.66] - Wolfman2000 +* Implement #WARPS tag to replace negative bpm/stop gimmicks. + [v0.55] - Wolfman2000 * Add #COMBOS tag to the Song and Steps (does nothing here). diff --git a/Docs/Changelog_sm-ssc.txt b/Docs/Changelog_sm-ssc.txt index 70f3cb33d9..b72b37a36f 100644 --- a/Docs/Changelog_sm-ssc.txt +++ b/Docs/Changelog_sm-ssc.txt @@ -13,6 +13,13 @@ _____________________________________________________________________________ sm-ssc v1.2.4 | 2011032? -------------------------------------------------------------------------------- +20110326 +-------- +* [NotesLoaderSM, NotesWriterSSC] Officially add the #WARPS tag. The negative + tricks of the past are now officially standardized. Please tell us about + files which have broken sync or are otherwise unplayable/incorrect. + [AJ, Thai, Wolfman2000] + 20110320 -------- * [PlayerOptions] Added SetNoteSkin(string) Lua binding. [AJ] diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 4e465f8675..2b44eee848 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -843,6 +843,7 @@ Edit time signature (top)=Edit time signature (beats per measure) Edit time signature (bottom)=Edit time signature (single beat note value) Edit tickcount=Edit tickcount Edit combo=Edit combo +Edit warp=Edit warp Editor options=Options EditorShowBGChangesPlay=Show Backgrounds Effect=Effect @@ -1209,6 +1210,7 @@ Enter a new Time Signature numerator value.=Enter the number of beats per measur Enter a new Time Signature denominator value.=Enter the note value that represents one beat. Enter a new Tickcount value.=Enter a new Tickcount value. Enter a new Combo value.=Enter a new Combo value. +Enter a new Warp value.=Enter the beat you will warp to when you reach this point. Enter a new artist transliteration.=Enter a new artist transliteration. Enter a new artist.=Enter a new artist. Enter a new genre.=Enter a new genre. diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index c92fd40354..3cacf67bed 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -963,6 +963,7 @@ RoutineNoteSkinP2=RoutineSkinP2() BPMColor=color("1,0,0,1") StopColor=color("0.8,0.8,0,1") DelayColor=color("0,0.8,0.8,1") +WarpColor=color("1,0,0.5,1") TimeSignatureColor=color("1,0.55,0,1") TickcountColor=color("0,1,0,1") ComboColor=color("0.55,1,0,1") @@ -970,13 +971,15 @@ ComboColor=color("0.55,1,0,1") BPMIsLeftSide=true StopIsLeftSide=true DelayIsLeftSide=true +WarpIsLeftSide=false TimeSignatureIsLeftSide=true TickcountIsLeftSide=false ComboIsLeftSide=false # BPMOffsetX=60 -StopOffsetX=10 -DelayOffsetX=10 +StopOffsetX=50 +DelayOffsetX=120 +WarpOffsetX=60 TimeSignatureOffsetX=30 TickcountOffsetX=10 ComboOffsetX=30 diff --git a/src/GameState.cpp b/src/GameState.cpp index c1f5b0b0f4..1a3f2d0793 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -897,7 +897,7 @@ void GameState::ResetMusicStatistics() m_bFreeze = false; m_bDelay = false; m_iWarpBeginRow = -1; // Set to -1 because some song may want to warp to row 0. -aj - m_fWarpLength = -1; // Set when a warp is encountered. also see above. -aj + m_fWarpDestination = -1; // Set when a warp is encountered. also see above. -aj m_fMusicSecondsVisible = 0; m_fSongBeatVisible = 0; Actor::SetBGMTime( 0, 0, 0, 0 ); @@ -943,7 +943,7 @@ void GameState::ResetStageStatistics() } static Preference g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f ); -// todo: modify for warps -aj + void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp ) { if( !timestamp.IsZero() ) @@ -957,18 +957,10 @@ void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &ti 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_fWarpLength ); + 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) ); - //if( m_iWarpBeginRow != -1 || m_iWarpEndRow == -1 ) - if( m_iWarpBeginRow != -1 && m_fWarpLength > 0.0f ) - { - // There is a warp in this section. - LOG->Trace("warp at %i lasts for %f, jumps to %i",m_iWarpBeginRow,m_fWarpLength,m_iWarpBeginRow+BeatToNoteRow(m_fWarpLength)); - //fPositionSeconds += (m_fWarpLength * m_fCurBPS); - } - m_fMusicSeconds = fPositionSeconds; m_fLightSongBeat = timing.GetBeatFromElapsedTime( fPositionSeconds + g_fLightsAheadSeconds ); diff --git a/src/GameState.h b/src/GameState.h index d26fe5b308..406e4784b1 100644 --- a/src/GameState.h +++ b/src/GameState.h @@ -208,9 +208,10 @@ public: bool m_bFreeze; /** @brief A flag to determine if we're in the middle of a delay (Pump style stop). */ bool m_bDelay; - // used for warping: + /** @brief The row used to start a warp. */ int m_iWarpBeginRow; - float m_fWarpLength; + /** @brief The beat to warp to afterwards. */ + float m_fWarpDestination; RageTimer m_LastBeatUpdate; // time of last m_fSongBeat, etc. update BroadcastOnChange m_bGameplayLeadIn; diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index 65796fcbac..e61352d9c0 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -864,8 +864,8 @@ void LuaHelpers::ParseCommandList( Lua *L, const RString &sCommands, const RStri FOREACH_CONST( Command, cmds.v, c ) { const Command& cmd = (*c); - RString sName = cmd.GetName(); - s << "\tself:" << sName << "("; + RString local_sName = cmd.GetName(); + s << "\tself:" << local_sName << "("; for( unsigned i=1; i BPM_COLOR ( "NoteField", "BPMColor" ); static ThemeMetric STOP_COLOR ( "NoteField", "StopColor" ); static ThemeMetric DELAY_COLOR ( "NoteField", "DelayColor" ); +static ThemeMetric WARP_COLOR ( "NoteField", "WarpColor" ); static ThemeMetric TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignatureColor" ); static ThemeMetric TICKCOUNT_COLOR ( "NoteField", "TickcountColor" ); static ThemeMetric COMBO_COLOR ( "NoteField", "ComboColor" ); static ThemeMetric BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" ); static ThemeMetric STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" ); static ThemeMetric DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" ); +static ThemeMetric WARP_IS_LEFT_SIDE ( "NoteField", "WarpIsLeftSide" ); static ThemeMetric TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatureIsLeftSide" ); static ThemeMetric TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" ); static ThemeMetric COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" ); static ThemeMetric BPM_OFFSETX ( "NoteField", "BPMOffsetX" ); static ThemeMetric STOP_OFFSETX ( "NoteField", "StopOffsetX" ); static ThemeMetric DELAY_OFFSETX ( "NoteField", "DelayOffsetX" ); +static ThemeMetric WARP_OFFSETX ( "NoteField", "WarpOffsetX" ); static ThemeMetric TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOffsetX" ); static ThemeMetric TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" ); static ThemeMetric COMBO_OFFSETX ( "NoteField", "ComboOffsetX" ); @@ -481,6 +484,23 @@ void NoteField::DrawFreezeText( const float fBeat, const float fSecs, const floa m_textMeasureNumber.Draw(); } +void NoteField::DrawWarpText( 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 = WARP_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( WARP_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( WARP_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( ssprintf("%.3f", fNewBeat) ); + m_textMeasureNumber.SetXY( (WARP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + void NoteField::DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator ) { const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); @@ -770,6 +790,19 @@ void NoteField::DrawPrimitives() DrawFreezeText( fBeat, aStopSegments[i].m_fStopSeconds, aStopSegments[i].m_bDelay ); } } + + // Warp text + const vector &aWarpSegments = GAMESTATE->m_pCurSong->m_Timing.m_WarpSegments; + for( unsigned i=0; i= iFirstRowToDraw && + aWarpSegments[i].m_iStartRow <= iLastRowToDraw) + { + float fBeat = NoteRowToBeat(aWarpSegments[i].m_iStartRow); + if( IS_ON_SCREEN(fBeat) ) + DrawWarpText( fBeat, aWarpSegments[i].m_fEndBeat ); + } + } // Time Signature text const vector &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments; @@ -810,8 +843,6 @@ void NoteField::DrawPrimitives() } } - // todo: add warp text -aj - // Course mods text const Course *pCourse = GAMESTATE->m_pCurCourse; if( pCourse ) diff --git a/src/NoteField.h b/src/NoteField.h index 0c4f03c990..08a83e11aa 100644 --- a/src/NoteField.h +++ b/src/NoteField.h @@ -57,6 +57,7 @@ protected: void DrawAreaHighlight( int iStartBeat, int iEndBeat ); void DrawBPMText( const float fBeat, const float fBPM ); void DrawFreezeText( const float fBeat, const float fBPM, const float bDelay ); + void DrawWarpText( const float fBeat, const float fNewBeat ); void DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator ); void DrawTickcountText( const float fBeat, int iTicks ); void DrawComboText( const float fBeat, int iCombo ); diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index 8a280ec723..4937e37b52 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -12,7 +12,13 @@ #include "Steps.h" #include "PrefsManager.h" +/** @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, @@ -104,9 +110,6 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) out.m_WarpSegments.clear(); out.m_vTimeSignatureSegments.clear(); - vector arrayWarpsFromNegativeBPMs; - //vector arrayWarpsFromNegativeStops; - for( unsigned i=0; i 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 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 ); + } + } + } + } + } + else if( sValueName=="STOPS" || sValueName=="FREEZES" ) { vector arrayFreezeExpressions; split( sParams[1], ",", arrayFreezeExpressions ); + // Prepare variables for negative stop conversion. + float negBeat = -1; + float negPause = 0; + for( unsigned f=0; f arrayFreezeValues; @@ -136,29 +211,49 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) const float fFreezeBeat = StringToFloat( arrayFreezeValues[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) + + // Process the prior stop. + if( negPause > 0 ) { - // LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); - out.AddStopSegment( new_seg ); + 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; } - else + + if( fFreezeSeconds < 0.0f ) { - // 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 ); + 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 ); } } + else if( sValueName=="DELAYS" ) { vector arrayDelayExpressions; @@ -183,7 +278,7 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) // 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) @@ -193,95 +288,6 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) } } - else if( sValueName=="BPMS" ) - { - vector arrayBPMChangeExpressions; - split( sParams[1], ",", arrayBPMChangeExpressions ); - - for( unsigned b=0; b 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] ); - // 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 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 ); - } - } - } - else if( sValueName=="TIMESIGNATURES" ) { vector vs1; @@ -326,12 +332,12 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) out.AddTimeSignatureSegment( seg ); } } - + else if( sValueName=="TICKCOUNTS" ) { vector arrayTickcountExpressions; split( sParams[1], ",", arrayTickcountExpressions ); - + for( unsigned f=0; f arrayTickcountValues; @@ -343,7 +349,7 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) sValueName.c_str(), arrayTickcountExpressions[f].c_str() ); continue; } - + const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] ); int iTicks = atoi( arrayTickcountValues[1] ); // you're lazy, let SM do the work for you... -DaisuMaster @@ -366,61 +372,8 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) } } } - - // warps (replacement for Negative BPM and Negative Stops) - /* - else if( sValueName=="WARPS" ) - { - vector arrayWarpExpressions; - split( sParams[1], ",", arrayWarpExpressions ); - - for( unsigned f=0; f arrayWarpValues; - split( arrayWarpExpressions[f], "=", arrayWarpValues ); - if( arrayWarpValues.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(), arrayWarpExpressions[f].c_str() ); - continue; - } - - const float fWarpStart = StringToFloat( arrayWarpValues[0] ); - const float fWarpBeats = StringToFloat( arrayWarpValues[1] ); - - if( fWarpStart > 0.0f && fWarpBeats > 0.0f ) - { - WarpSegment new_seg( BeatToNoteRow(fWarpStart), fWarpBeats ); - out.AddWarpSegment( new_seg ); - } - else - { - // Currently disallow negative warps, to prevent the same - // kind of problem that happened when Negative/Subtractive - // BPMs arrived on the StepMania scene. -aj - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid warp at beat %f lasting %f beats.", fWarpStart, fWarpBeats ); - } - } - } - */ - - // 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 arrayWarpExpressions; + split( sParams[1], ",", arrayWarpExpressions ); + + for( unsigned b=0; b arrayWarpValues; + split( arrayWarpExpressions[b], "=", arrayWarpValues ); + // XXX: Hard to tell which file caused this. + if( arrayWarpValues.size() != 2 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayWarpExpressions[b].c_str() ); + continue; + } + + const float fBeat = StringToFloat( arrayWarpValues[0] ); + const float fNewBeat = StringToFloat( arrayWarpValues[1] ); + + if(fNewBeat > fBeat) + out.m_Timing.AddWarpSegment( WarpSegment(fBeat, fNewBeat) ); + else + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Warp at beat %f, BPM %f.", fBeat, fNewBeat ); + } + } + } else if( sValueName=="TIMESIGNATURES" ) { diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h index ae8f00bc9c..ff44a23cb5 100644 --- a/src/NotesLoaderSSC.h +++ b/src/NotesLoaderSSC.h @@ -21,7 +21,10 @@ enum SSCLoadingStates NUM_SSCLoadingStates /**< The number of states used. */ }; +/** @brief The version where fakes started to be used as a radar category. */ 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 SSCLoader handles all of the parsing needed for .ssc files. diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index c736104ae8..36c170b475 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -144,18 +144,18 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) } f.PutLine( ";" ); - /* + f.Write( "#WARPS:" ); for( unsigned i=0; i asWarpValues; + for( unsigned i=0; i asTimeSigValues; FOREACH_CONST( TimeSignatureSegment, in.m_Timing.m_vTimeSignatureSegments, iter ) diff --git a/src/Player.cpp b/src/Player.cpp index 2f505d391a..13bae07600 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1520,6 +1520,8 @@ 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 ) ) + break; if( tn.type == TapNote::empty ) break; if( !bAllowGraded && tn.result.tns != TNS_None ) @@ -1569,6 +1571,11 @@ int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool b ++iter; continue; } + if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) ) + { + ++iter; + continue; + } return iter.Row(); } } @@ -1966,7 +1973,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b * Either option would fundamentally change the grading of two quick notes * "jack hammers." Hmm. */ - const int iStepSearchRows = BeatToNoteRow( StepSearchDistance * GAMESTATE->m_fCurBPS * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate ); + const int iStepSearchRows = BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds + StepSearchDistance ) ) - iSongRow; int iRowOfOverlappingNoteOrRow = row; if( row == -1 ) { @@ -2040,7 +2047,9 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b { case TapNote::mine: // Stepped too close to mine? - if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) && fSecondsFromExact <= GetWindowSeconds(TW_Mine) ) + if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) && + fSecondsFromExact <= GetWindowSeconds(TW_Mine) && + !GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iSongRow) ) score = TNS_HitMine; break; @@ -2522,8 +2531,8 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds ) { //LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat ); int iMissIfOlderThanThisRow; + const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds; { - const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds; bool bFreeze, bDelay; float fMissIfOlderThanThisBeat; float fThrowAway; @@ -2550,9 +2559,9 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds ) if( !NeedsTapJudging(tn) ) continue; - - // warp hackery - if( iter.Row() >= GAMESTATE->m_iWarpBeginRow && iter.Row() <= (GAMESTATE->m_iWarpBeginRow + BeatToNoteRow(GAMESTATE->m_fWarpLength)) ) + + // Ignore all notes that are skipped via WARPS. + if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) ) continue; if( tn.type == TapNote::mine ) @@ -2568,11 +2577,6 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds ) } else { - // warp hackery: don't score notes within the warp region. - // (Only useful when QuirksMode is enabled.) -aj - if( iter.Row() >= GAMESTATE->m_iWarpBeginRow && iter.Row() <= (GAMESTATE->m_iWarpBeginRow + BeatToNoteRow(GAMESTATE->m_fWarpLength)) ) - continue; - tn.result.tns = TNS_Miss; } } @@ -2592,8 +2596,7 @@ void Player::UpdateJudgedRows() int iRow = iter.Row(); // if row is within a warp section, ignore it. -aj - if( iRow >= GAMESTATE->m_iWarpBeginRow && - iRow <= (GAMESTATE->m_iWarpBeginRow + BeatToNoteRow(GAMESTATE->m_fWarpLength)) ) + if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iRow) ) continue; if( iLastSeenRow != iRow ) @@ -2928,8 +2931,7 @@ void Player::HandleTapRowScore( unsigned row ) #endif // more warp hackery. -aj - if( row >= (unsigned)GAMESTATE->m_iWarpBeginRow && - row <= (unsigned)(GAMESTATE->m_iWarpBeginRow + BeatToNoteRow(GAMESTATE->m_fWarpLength)) ) + if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( row ) ) return; if( GAMESTATE->m_bDemonstrationOrJukebox ) @@ -3033,8 +3035,7 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH #endif // more warp hackery. -aj - if( iRow >= GAMESTATE->m_iWarpBeginRow && - iRow <= (GAMESTATE->m_iWarpBeginRow + BeatToNoteRow(GAMESTATE->m_fWarpLength)) ) + if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iRow ) ) return; // don't accumulate combo if AutoPlay is on. diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 213cc01028..8707da7293 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -80,6 +80,7 @@ AutoScreenMessage( SM_BackFromTimeSignatureNumeratorChange ); AutoScreenMessage( SM_BackFromTimeSignatureDenominatorChange ); AutoScreenMessage( SM_BackFromTickcountChange ); AutoScreenMessage( SM_BackFromComboChange ); +AutoScreenMessage( SM_BackFromWarpChange ); AutoScreenMessage( SM_DoSaveAndExit ); AutoScreenMessage( SM_DoExit ); AutoScreenMessage( SM_SaveSuccessful ); @@ -549,7 +550,8 @@ static MenuDef g_TimingDataInformation( MenuRowDef( ScreenEdit::time_signature_numerator, "Edit time signature (top)", true, EditMode_Full, true, true, 0, NULL ), MenuRowDef( ScreenEdit::time_signature_denominator, "Edit time signature (bottom)", true, EditMode_Full, true, true, 0, NULL ), MenuRowDef( ScreenEdit::tickcount, "Edit tickcount", true, EditMode_Full, true, true, 0, NULL ), - MenuRowDef( ScreenEdit::combo, "Edit combo", true, EditMode_Full, true, true, 0, NULL ) + MenuRowDef( ScreenEdit::combo, "Edit combo", true, EditMode_Full, true, true, 0, NULL ), + MenuRowDef( ScreenEdit::warp, "Edit warp", true, EditMode_Full, true, true, 0, NULL ) ); enum { song_bganimation, song_movie, song_bitmap, global_bganimation, global_movie, global_movie_song_group, global_movie_song_group_and_genre, dynamic_random, baked_random, none }; @@ -2639,6 +2641,13 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) } SetDirty( true ); } + else if ( SM == SM_BackFromWarpChange ) + { + float fWarp = StringToFloat( ScreenTextEntry::s_sLastAnswer ); + if( fWarp > 0 ) + m_pSong->m_Timing.SetWarpAtBeat( GAMESTATE->m_fSongBeat, fWarp ); + SetDirty( true ); + } else if( SM == SM_BackFromBGChange ) { HandleBGChangeChoice( (BGChangeChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers ); @@ -3176,6 +3185,7 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns g_TimingDataInformation.rows[time_signature_denominator].SetOneUnthemedChoice( ssprintf("%d", pTime.GetTimeSignatureDenominatorAtBeat( fBeat ) ) ); g_TimingDataInformation.rows[tickcount].SetOneUnthemedChoice( ssprintf("%d", pTime.GetTickcountAtBeat( fBeat ) ) ); g_TimingDataInformation.rows[combo].SetOneUnthemedChoice( ssprintf("%d", pTime.GetComboAtBeat( fBeat ) ) ); + g_TimingDataInformation.rows[warp].SetOneUnthemedChoice( ssprintf("%.5f", pTime.GetWarpAtBeat( fBeat ) ) ); EditMiniMenu( &g_TimingDataInformation, SM_BackFromTimingDataInformation ); } @@ -3493,7 +3503,7 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns // don't move the step from where it is, just move everything later NoteDataUtil::InsertRows( m_NoteDataEdit, BeatToNoteRow(GAMESTATE->m_fSongBeat) + 1, BeatToNoteRow(fStopBeats) ); - m_pSong->m_Timing.InsertRows( BeatToNoteRow(GAMESTATE->m_fSongBeat) + 1, BeatToNoteRow(fStopSeconds) ); + m_pSong->m_Timing.InsertRows( BeatToNoteRow(GAMESTATE->m_fSongBeat) + 1, BeatToNoteRow(fStopBeats) ); } break; case undo: @@ -3609,6 +3619,7 @@ static LocalizedString ENTER_TIME_SIGNATURE_NUMERATOR_VALUE ( "ScreenEdit", "Ent static LocalizedString ENTER_TIME_SIGNATURE_DENOMINATOR_VALUE ( "ScreenEdit", "Enter a new Time Signature denominator value." ); static LocalizedString ENTER_TICKCOUNT_VALUE ( "ScreenEdit", "Enter a new Tickcount value." ); static LocalizedString ENTER_COMBO_VALUE ( "ScreenEdit", "Enter a new Combo value." ); +static LocalizedString ENTER_WARP_VALUE ( "ScreenEdit", "Enter a new Warp value." ); void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice c, const vector &iAnswers ) { switch( c ) @@ -3673,6 +3684,15 @@ void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice 4 ); break; + case warp: + // todo: Call a screen with class ScreenTextEntry instead. -aj + ScreenTextEntry::TextEntry( + SM_BackFromWarpChange, + ENTER_WARP_VALUE, + ssprintf( "%.4f", m_pSong->m_Timing.GetWarpAtRow( BeatToNoteRow(GAMESTATE->m_fSongBeat) ) ), + 10 + ); + break; } } diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index 200a3979f0..1f79b78528 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -456,6 +456,7 @@ public: time_signature_denominator, tickcount, combo, + warp, NUM_TIMING_DATA_INFORMATION_CHOICES }; diff --git a/src/Song.cpp b/src/Song.cpp index ff686d6e25..b9d15c3ea8 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -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 = 166; +const int FILE_CACHE_VERSION = 167; /** @brief How long does a song sample last by default? */ const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; diff --git a/src/Song.h b/src/Song.h index 74d1159b73..d9b1d18c1f 100644 --- a/src/Song.h +++ b/src/Song.h @@ -17,7 +17,7 @@ struct lua_State; struct BackgroundChange; /** @brief The version of the .ssc file format. */ -const static float STEPFILE_VERSION_NUMBER = 0.55f; +const static float STEPFILE_VERSION_NUMBER = 0.56f; /** @brief How many edits for this song can each profile have? */ const int MAX_EDITS_PER_SONG_PER_PROFILE = 5; @@ -307,7 +307,7 @@ public: 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 AddWarpSegment( const WarpSegment &seg ) { m_Timing.AddWarpSegment( seg ); } void AddBackgroundChange( BackgroundLayer blLayer, BackgroundChange seg ); void AddForegroundChange( BackgroundChange seg ); void AddLyricSegment( LyricSegment seg ); diff --git a/src/TimingData.cpp b/src/TimingData.cpp index 9704ac00c3..1ddb2d6ad1 100644 --- a/src/TimingData.cpp +++ b/src/TimingData.cpp @@ -150,12 +150,30 @@ void TimingData::SetTimeSignatureDenominatorAtRow( int iRow, int iDenominator ) SetTimeSignatureAtRow( iRow, GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iRow ) ).m_iNumerator, iDenominator ); } -/* -void TimingData::SetWarpAtRow( int iRowAt, float fLengthBeats ) +void TimingData::SetWarpAtRow( int iRow, float fNew ) { - // todo: code this -aj + unsigned i; + for( i=0; i 0 && NoteRowToBeat(iRow) < fNew; + if( i == m_WarpSegments.size() ) + { + if( valid ) + { + AddWarpSegment( WarpSegment(iRow, fNew) ); + } + } + else + { + if( valid ) + { + m_WarpSegments[i].m_fEndBeat = fNew; + } + else + m_WarpSegments.erase( m_WarpSegments.begin()+i, m_WarpSegments.begin()+i+1 ); + } } -*/ /* Change an existing Tickcount segment, merge identical segments together or insert a new one. */ void TimingData::SetTickcountAtRow( int iRow, int iTicks ) @@ -229,13 +247,13 @@ int TimingData::GetComboAtRow( int iNoteRow ) const return m_ComboSegments[GetComboSegmentIndexAtRow( iNoteRow )].m_iCombo; } -int TimingData::GetWarpToRow( int iWarpBeginRow ) const +float TimingData::GetWarpAtRow( int iWarpRow ) const { for( unsigned i=0; i iNoteRow ) break; - return (int)i; + return static_cast(i); } int TimingData::GetStopSegmentIndexAtRow( int iNoteRow, bool bDelay ) const @@ -308,16 +326,72 @@ int TimingData::GetStopSegmentIndexAtRow( int iNoteRow, bool bDelay ) const if( s.m_iStartRow > iNoteRow && s.m_bDelay == bDelay ) break; } - return (int)i; + return static_cast(i); +} + +int TimingData::GetWarpSegmentIndexAtRow( int iNoteRow ) const +{ + unsigned i; + for( i=0; i iNoteRow ) + break; + } + return static_cast(i); +} + +bool TimingData::IsWarpAtRow( int iNoteRow ) const +{ + if( m_WarpSegments.empty() ) + return false; + + int i = GetWarpSegmentIndexAtRow( iNoteRow ); + const WarpSegment& s = m_WarpSegments[i]; + if( s.m_iStartRow <= iNoteRow && iNoteRow < BeatToNoteRow(s.m_fEndBeat) ) + { + if( m_StopSegments.empty() ) + { + return true; + } + if( GetStopAtRow(iNoteRow) != 0.0f ) + { + return false; + } + return true; + } + return false; } int TimingData::GetTimeSignatureSegmentIndexAtRow( int iRow ) const { - int i; - for (i=0; i < (int)(m_vTimeSignatureSegments.size()) - 1; i++ ) + unsigned i; + for (i=0; i < m_vTimeSignatureSegments.size() - 1; i++ ) if( m_vTimeSignatureSegments[i+1].m_iStartRow > iRow ) break; - return i; + return static_cast(i); +} + +int TimingData::GetComboSegmentIndexAtRow( int iRow ) const +{ + unsigned i; + for( i=0; i iRow ) + break; + } + return static_cast(i); +} + +BPMSegment& TimingData::GetBPMSegmentAtRow( int iNoteRow ) +{ + static BPMSegment empty; + if( m_BPMSegments.empty() ) + return empty; + + int i = GetBPMSegmentIndexAtRow( iNoteRow ); + return m_BPMSegments[i]; } TimeSignatureSegment& TimingData::GetTimeSignatureSegmentAtRow( int iRow ) @@ -339,28 +413,6 @@ int TimingData::GetTimeSignatureDenominatorAtRow( int iRow ) return GetTimeSignatureSegmentAtRow( iRow ).m_iDenominator; } -int TimingData::GetComboSegmentIndexAtRow( int iRow ) const -{ - unsigned i; - for( i=0; i iRow ) - break; - } - return (int)i; -} - -BPMSegment& TimingData::GetBPMSegmentAtRow( int iNoteRow ) -{ - static BPMSegment empty; - if( m_BPMSegments.empty() ) - return empty; - - int i = GetBPMSegmentIndexAtRow( iNoteRow ); - return m_BPMSegments[i]; -} - ComboSegment& TimingData::GetComboSegmentAtRow( int iRow ) { unsigned i; @@ -380,6 +432,16 @@ StopSegment& TimingData::GetStopSegmentAtRow( int iNoteRow, bool bDelay ) return m_StopSegments[i]; } +WarpSegment& TimingData::GetWarpSegmentAtRow( int iRow ) +{ + static WarpSegment empty; + if( m_WarpSegments.empty() ) + return empty; + + int i = GetWarpSegmentIndexAtRow( iRow ); + return m_WarpSegments[i]; +} + int TimingData::GetTickcountSegmentIndexAtRow( int iRow ) const { int i; @@ -411,150 +473,118 @@ void TimingData::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatO GetBeatAndBPSFromElapsedTimeNoOffset( fElapsedTime, fBeatOut, fBPSOut, bFreezeOut, bDelayOut, iWarpBeginOut, fWarpLengthOut ); } -void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpLengthOut ) const +enum { -// LOG->Trace( "GetBeatAndBPSFromElapsedTime( fElapsedTime = %f )", fElapsedTime ); - const float fTime = fElapsedTime; - fElapsedTime += m_fBeat0OffsetInSeconds; + FOUND_WARP, + FOUND_WARP_DESTINATION, + FOUND_BPM_CHANGE, + FOUND_STOP, + FOUND_MARKER, + NOT_FOUND +}; - for( unsigned i=0; i::const_iterator itBPMS = m_BPMSegments.begin(); + vector::const_iterator itWS = m_WarpSegments.begin(); + vector::const_iterator itSS = m_StopSegments.begin(); + + bFreezeOut = false; + bDelayOut = false; + + iWarpBeginOut = -1; + + int iLastRow = 0; + float fLastTime = -m_fBeat0OffsetInSeconds; + float fBPS = GetBPMAtRow(0) / 60.0; + + float bIsWarping = false; + float fWarpDestination = 0.0; + + for( ;; ) { - const int iStartRowThisSegment = m_BPMSegments[i].m_iStartRow; - const float fStartBeatThisSegment = NoteRowToBeat( iStartRowThisSegment ); - const bool bIsFirstBPMSegment = i==0; - const bool bIsLastBPMSegment = i==m_BPMSegments.size()-1; - const int iStartRowNextSegment = bIsLastBPMSegment ? MAX_NOTE_ROW : m_BPMSegments[i+1].m_iStartRow; - const float fStartBeatNextSegment = NoteRowToBeat( iStartRowNextSegment ); - const float fBPS = m_BPMSegments[i].m_fBPS; - - for( unsigned j=0; j= m_StopSegments[j].m_iStartRow ) - continue; - if( !bIsLastBPMSegment && m_StopSegments[j].m_iStartRow > iStartRowNextSegment ) - continue; - - // this freeze lies within this BPMSegment - const int iRowsBeatsSinceStartOfSegment = m_StopSegments[j].m_iStartRow - iStartRowThisSegment; - const float fBeatsSinceStartOfSegment = NoteRowToBeat(iRowsBeatsSinceStartOfSegment); - const float fFreezeStartSecond = fBeatsSinceStartOfSegment / fBPS; - - // modified for delays - if( !bIsDelay && fFreezeStartSecond >= fElapsedTime ) - break; - if( bIsDelay && fFreezeStartSecond > fElapsedTime ) - break; - - // the freeze segment is <= current time - fElapsedTime -= m_StopSegments[j].m_fStopSeconds; - - if( (fFreezeStartSecond >= fElapsedTime && !bIsDelay) || - (fFreezeStartSecond > fElapsedTime && bIsDelay) ) + iEventRow = BeatToNoteRow(fWarpDestination); + iEventType = FOUND_WARP_DESTINATION; + } + if( itBPMS != m_BPMSegments.end() && itBPMS->m_iStartRow < iEventRow ) + { + iEventRow = itBPMS->m_iStartRow; + iEventType = FOUND_BPM_CHANGE; + } + if( itSS != m_StopSegments.end() && itSS->m_iStartRow < iEventRow ) + { + iEventRow = itSS->m_iStartRow; + iEventType = FOUND_STOP; + } + if( itWS != m_WarpSegments.end() && itWS->m_iStartRow < iEventRow ) + { + iEventRow = itWS->m_iStartRow; + iEventType = FOUND_WARP; + } + if( iEventType == NOT_FOUND ) + { + break; + } + float fTimeToNextEvent = bIsWarping ? 0 : NoteRowToBeat( iEventRow - iLastRow ) / fBPS; + float fNextEventTime = fLastTime + fTimeToNextEvent; + if ( fElapsedTime < fNextEventTime ) + { + break; + } + fLastTime = fNextEventTime; + switch( iEventType ) + { + case FOUND_WARP_DESTINATION: + bIsWarping = false; + break; + case FOUND_BPM_CHANGE: + fBPS = itBPMS->m_fBPS; + itBPMS ++; + break; + case FOUND_STOP: // TODO: update for Delays. { - // The time lies within the stop. - fBeatOut = NoteRowToBeat(m_StopSegments[j].m_iStartRow); - fBPSOut = fBPS; - bFreezeOut = !bIsDelay; - bDelayOut = bIsDelay; - //iWarpBeginOut = -1; - //fWarpLengthOut = -1; - return; + fTimeToNextEvent = itSS->m_fStopSeconds; + fNextEventTime = fLastTime + fTimeToNextEvent; + const bool bIsDelay = itSS->m_bDelay; + if ( fElapsedTime < fNextEventTime ) + { + bFreezeOut = !bIsDelay; + bDelayOut = bIsDelay; + fBeatOut = NoteRowToBeat( itSS->m_iStartRow ); + fBPSOut = fBPS; + return; + } + fLastTime = fNextEventTime; + itSS ++; } - } - - // by this point we should have the warps in their own place. - for( unsigned j=0; j= m_WarpSegments[j].m_iStartRow ) - continue; - if( !bIsLastBPMSegment && m_WarpSegments[j].m_iStartRow > iStartRowNextSegment ) - continue; - - /* - const int iRowsBeatsSinceStartOfSegment = m_WarpSegments[j].m_iStartRow - iStartRowThisSegment; - const float fBeatsSinceStartOfSegment = NoteRowToBeat(iRowsBeatsSinceStartOfSegment); - const float fWarpStartSecond = fBeatsSinceStartOfSegment / fBPS; - */ - - // the freeze segment is <= current time - //fElapsedTime -= m_WarpSegments[j].m_fWarpBeats; - - // this warp lies within this BPMSegment. - /* - if( fWarpStartSecond >= fElapsedTime ) + break; + case FOUND_WARP: + bIsWarping = true; + if( itWS->m_fEndBeat > fWarpDestination ) { - // this WarpSegment IS the current segment. - // don't know how to properly handle beatout -aj - //fBeatOut = NoteRowToBeat(m_WarpSegments[j].m_iStartRow); - fBeatOut = fStartBeatThisSegment + fElapsedTime*fBPS; - fBPSOut = m_BPMSegments[i+1].m_fBPS; - bFreezeOut = false; - bDelayOut = false; - iWarpBeginOut = m_WarpSegments[j].m_iStartRow; - fWarpLengthOut = m_WarpSegments[j].m_fWarpBeats; - return; + fWarpDestination = itWS->m_fEndBeat; } - */ + iWarpBeginOut = iEventRow; + fWarpDestinationOut = fWarpDestination; + itWS ++; + break; } - - const float fBeatsInThisSegment = fStartBeatNextSegment - fStartBeatThisSegment; - const float fSecondsInThisSegment = fBeatsInThisSegment / fBPS; - //if(fBPS < 0.0f) - /* - if(fStartBeatThisSegment == 445.500f || fStartBeatThisSegment == 449.500) - { - LOG->Trace( ssprintf("segment (beat %f) beats: %f / seconds: %f / BPS: %f",fStartBeatThisSegment,fBeatsInThisSegment,fSecondsInThisSegment,fBPS) ); - } - */ - if( bIsLastBPMSegment || fElapsedTime <= fSecondsInThisSegment ) - { - // this BPMSegment IS the current segment. - fBeatOut = fStartBeatThisSegment + fElapsedTime*fBPS; - fBPSOut = fBPS; - bFreezeOut = false; - bDelayOut = false; - //iWarpBeginOut; - //fWarpLengthOut; - return; - } - - // this BPMSegment is NOT the current segment. - fElapsedTime -= fSecondsInThisSegment; - // xxx: negative testing [aj] - /* - //if(fBPS < 0.0f) - if( (fStartBeatNextSegment >= 445.490f && fStartBeatNextSegment <= 453.72f) || fBPS < 0.0f ) - { - //LOG->Trace( ssprintf("beat %f is %f BPS (%f BPM)",fBeatOut,fBPSOut,fBPSOut*60.0f) ); - //LOG->Trace( ssprintf("start beat %f + elapsed time %f",fStartBeatThisSegment,fElapsedTime) ); - //LOG->Trace( ssprintf("elapsed time is now %f",fElapsedTime) ); - } - */ + iLastRow = iEventRow; } - // If we get here, something has gone wrong. Is everything sorted? - vector vBPMS = m_BPMSegments; - vector vSS = m_StopSegments; - vector vWS = m_WarpSegments; - vector vTSS = m_vTimeSignatureSegments; - vector vTS = m_TickcountSegments; - vector vCS = m_ComboSegments; - sort( vBPMS.begin(), vBPMS.end() ); - sort( vSS.begin(), vSS.end() ); - sort( vWS.begin(), vWS.end() ); - sort( vTSS.begin(), vTSS.end() ); - sort( vTS.begin(), vTS.end() ); - sort( vCS.begin(), vCS.end() ); - ASSERT_M( vBPMS == m_BPMSegments, "The BPM segments were not sorted!" ); - ASSERT_M( vSS == m_StopSegments, "The Stop segments were not sorted!" ); - ASSERT_M( vWS == m_WarpSegments, "The Warp segments were not sorted!" ); - ASSERT_M( vTSS == m_vTimeSignatureSegments, "The Time Signature segments were not sorted!" ); - ASSERT_M( vTS == m_TickcountSegments, "The Tickcount segments were not sorted!" ); - ASSERT_M( vCS == m_ComboSegments, "The Combo segments were not sorted!" ); - FAIL_M( ssprintf("Failed to find the appropriate segment for elapsed time %f.", fTime) ); + + fBeatOut = NoteRowToBeat( iLastRow ) + (fElapsedTime - fLastTime) * fBPS; + fBPSOut = fBPS; + } + + float TimingData::GetElapsedTimeFromBeat( float fBeat ) const { return TimingData::GetElapsedTimeFromBeatNoOffset( fBeat ) - PREFSMAN->m_fGlobalOffsetSeconds; @@ -562,53 +592,86 @@ float TimingData::GetElapsedTimeFromBeat( float fBeat ) const float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const { - float fElapsedTime = 0; - fElapsedTime -= m_fBeat0OffsetInSeconds; - int iRow = BeatToNoteRow(fBeat); - for( unsigned j=0; j::const_iterator itBPMS = m_BPMSegments.begin(); + vector::const_iterator itWS = m_WarpSegments.begin(); + vector::const_iterator itSS = m_StopSegments.begin(); + + int iLastRow = 0; + float fLastTime = -m_fBeat0OffsetInSeconds; + float fBPS = GetBPMAtRow(0) / 60.0; + + float bIsWarping = false; + float fWarpDestination = 0.0; + + for( ;; ) { - /* A traditional stop has the beat happening before the stop. (>=) - * A Pump delay acts differently: the pause is before the beat. (>) - */ - if( ( m_StopSegments[j].m_iStartRow >= iRow && !m_StopSegments[j].m_bDelay ) || - ( m_StopSegments[j].m_iStartRow > iRow && m_StopSegments[j].m_bDelay ) ) + int iEventRow = INT_MAX; + int iEventType = NOT_FOUND; + if( bIsWarping && BeatToNoteRow(fWarpDestination) < iEventRow ) + { + iEventRow = BeatToNoteRow(fWarpDestination); + iEventType = FOUND_WARP_DESTINATION; + } + if( itBPMS != m_BPMSegments.end() && itBPMS->m_iStartRow < iEventRow ) + { + iEventRow = itBPMS->m_iStartRow; + iEventType = FOUND_BPM_CHANGE; + } + if( itSS != m_StopSegments.end() && itSS->m_bDelay && itSS->m_iStartRow < iEventRow ) // delays (come before marker) + { + iEventRow = itSS->m_iStartRow; + iEventType = FOUND_STOP; + } + if( BeatToNoteRow(fBeat) < iEventRow ) + { + iEventRow = BeatToNoteRow(fBeat); + iEventType = FOUND_MARKER; + } + if( itSS != m_StopSegments.end() && !itSS->m_bDelay && itSS->m_iStartRow < iEventRow ) // stops (come after marker) + { + iEventRow = itSS->m_iStartRow; + iEventType = FOUND_STOP; + } + if( itWS != m_WarpSegments.end() && itWS->m_iStartRow < iEventRow ) + { + iEventRow = itWS->m_iStartRow; + iEventType = FOUND_WARP; + } + float fTimeToNextEvent = bIsWarping ? 0 : NoteRowToBeat( iEventRow - iLastRow ) / fBPS; + float fNextEventTime = fLastTime + fTimeToNextEvent; + fLastTime = fNextEventTime; + switch( iEventType ) + { + case FOUND_WARP_DESTINATION: + bIsWarping = false; + break; + case FOUND_BPM_CHANGE: + fBPS = itBPMS->m_fBPS; + itBPMS ++; + break; + case FOUND_STOP: + fTimeToNextEvent = itSS->m_fStopSeconds; + fNextEventTime = fLastTime + fTimeToNextEvent; + fLastTime = fNextEventTime; + itSS ++; + break; + case FOUND_MARKER: + return fLastTime; + case FOUND_WARP: + bIsWarping = true; + if( itWS->m_fEndBeat > fWarpDestination ) + { + fWarpDestination = itWS->m_fEndBeat; + } + itWS ++; break; - fElapsedTime += m_StopSegments[j].m_fStopSeconds; - } - - for( unsigned i=0; i= iStartIndex ) + { + if( iSegEndRow > iEndIndex ) + m_WarpSegments[i].m_fEndBeat += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1))); + else + m_WarpSegments[i].m_fEndBeat = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale) + iStartIndex); + } + if( iSegStartRow < iStartIndex ) + continue; + else if( iSegStartRow > iEndIndex ) + m_WarpSegments[i].m_iStartRow += lrintf((iEndIndex - iStartIndex) * (fScale - 1)); + else + m_WarpSegments[i].m_iStartRow = lrintf((iSegStartRow - iStartIndex) * fScale) + iStartIndex; + } + // adjust BPM changes to preserve timing if( bAdjustBPM ) { @@ -683,6 +765,16 @@ void TimingData::InsertRows( int iStartRow, int iRowsToAdd ) stop.m_iStartRow += 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; + } + for( unsigned i = 0; i < m_vTimeSignatureSegments.size(); i++ ) { TimeSignatureSegment &time = m_vTimeSignatureSegments[i]; @@ -764,6 +856,26 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete ) stop.m_iStartRow -= iRowsToDelete; } + for( unsigned i = 0; i < m_WarpSegments.size(); i++ ) + { + 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; + + if( warp.m_iStartRow < iStartRow+iRowsToDelete ) + { + m_WarpSegments.erase( m_WarpSegments.begin()+i, m_WarpSegments.begin()+i+1 ); + --i; + continue; + } + + warp.m_iStartRow -= iRowsToDelete; + } + for( unsigned i = 0; i < m_vTimeSignatureSegments.size(); i++ ) { TimeSignatureSegment &time = m_vTimeSignatureSegments[i]; @@ -891,6 +1003,7 @@ class LunaTimingData: public Luna 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 GetStops( T* p, lua_State *L ) { vector vStops; @@ -965,6 +1078,7 @@ public: { ADD_METHOD( HasStops ); ADD_METHOD( HasBPMChanges ); + ADD_METHOD( HasWarps ); ADD_METHOD( GetStops ); ADD_METHOD( GetDelays ); ADD_METHOD( GetBPMs ); diff --git a/src/TimingData.h b/src/TimingData.h index 655a9087fb..f61afb6d9c 100644 --- a/src/TimingData.h +++ b/src/TimingData.h @@ -318,25 +318,41 @@ struct TimeSignatureSegment struct WarpSegment { /** - * @brief Creates a simple Warp Segment with default values. + * @brief Create a simple Warp Segment with default values. * * It is best to override the values as soon as possible. */ - WarpSegment() : m_iStartRow(-1), m_fWarpBeats(-1) { } + WarpSegment() : m_iStartRow(-1), m_fEndBeat(-1) { } /** - * @brief Creates a Warp Segment with the specified starting row and row to warp to. + * @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. */ - WarpSegment( int s, int r ): m_iStartRow(max(0, s)), - m_fWarpBeats(max(0, NoteRowToBeat(r))) {} + WarpSegment( int s, int r ): m_iStartRow(max(0, (s < r ? s : r))), + m_fEndBeat(max(0, NoteRowToBeat((r > s ? r : s)))) {} /** * @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. */ WarpSegment( int s, float b ): m_iStartRow(max(0, s)), - m_fWarpBeats(max(0, b)) {} + m_fEndBeat(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. + */ + WarpSegment( float s, int r ): + m_iStartRow(max(0, BeatToNoteRow(s))), + m_fEndBeat(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. + */ + WarpSegment( float s, float b ): + m_iStartRow(max(0, BeatToNoteRow((s < b ? s : b)))), + m_fEndBeat(max(0, (b > s ? b : s))) {} /** * @brief The row in which the WarpSegment activates. */ @@ -344,7 +360,7 @@ struct WarpSegment /** * @brief The beat to warp to. */ - float m_fWarpBeats; + float m_fEndBeat; /** * @brief Compares two WarpSegments to see if they are equal to each other. * @param other the other WarpSegment to compare to. @@ -353,7 +369,7 @@ struct WarpSegment bool operator==( const WarpSegment &other ) const { COMPARE( m_iStartRow ); - COMPARE( m_fWarpBeats ); + COMPARE( m_fEndBeat ); return true; } /** @@ -370,7 +386,7 @@ struct WarpSegment bool operator<( const WarpSegment &other ) const { return m_iStartRow < other.m_iStartRow || - ( m_iStartRow == other.m_iStartRow && m_fWarpBeats < other.m_fWarpBeats ); + ( m_iStartRow == other.m_iStartRow && m_fEndBeat < other.m_fEndBeat ); } /** * @brief Compares two WarpSegments to see if one is less than or equal to the other. @@ -875,19 +891,71 @@ public: * @param seg the new TimeSignatureSegment. */ void AddTimeSignatureSegment( const TimeSignatureSegment &seg ); - /** - * @brief Determine the row to warp to. - * @param iWarpBeginRow The row you start on. - * @return the row you warp to. + * @brief Determine the beat to warp to. + * @param iRow The row you start on. + * @return the beat you warp to. */ - int GetWarpToRow( int iWarpBeginRow ) const; + float GetWarpAtRow( int iRow ) const; + /** + * @brief Determine the beat to warp to. + * @param fBeat The beat you start on. + * @return the beat you warp to. + */ + float GetWarpAtBeat( float fBeat ) const { return GetWarpAtRow( BeatToNoteRow( fBeat ) ); } + /** + * @brief Set the beat to warp to given a starting row. + * @param iRow The row to start on. + * @param fNew The destination beat. + */ + void SetWarpAtRow( int iRow, float fNew ); + /** + * @brief Set the beat to warp to given a starting beat. + * @param fBeat The beat to start on. + * @param fNew The destination beat. + */ + void SetWarpAtBeat( float fBeat, float fNew ) { SetWarpAtRow( BeatToNoteRow( fBeat ), fNew ); } + /** + * @brief Retrieve the WarpSegment at the specified row. + * @param iRow the row to focus on. + * @return the WarpSegment in question. + */ + WarpSegment& GetWarpSegmentAtRow( int iRow ); + /** + * @brief Retrieve the WarpSegment at the specified beat. + * @param fBeat the beat to focus on. + * @return the WarpSegment in question. + */ + WarpSegment& GetWarpSegmentAtBeat( float fBeat ) { return GetWarpSegmentAtRow( BeatToNoteRow( fBeat ) ); } + /** + * @brief Retrieve the index of the WarpSegment at the specified row. + * @param iRow the row to focus on. + * @return the index in question. + */ + int GetWarpSegmentIndexAtRow( int iRow ) const; + /** + * @brief Retrieve the index of the WarpSegment at the specified beat. + * @param fBeat the beat to focus on. + * @return the index in question. + */ + int GetWarpSegmentIndexAtBeat( float fBeat ) const { return GetWarpSegmentIndexAtRow( BeatToNoteRow( fBeat ) ); } + /** + * @brief Checks if the row is inside a warp. + * @param iRow the row to focus on. + * @return true if the row is inside a warp, false otherwise. + */ + bool IsWarpAtRow( int iRow ) const; + /** + * @brief Checks if the beat is inside a warp. + * @param fBeat the beat to focus on. + * @return true if the row is inside a warp, false otherwise. + */ + bool IsWarpAtBeat( float fBeat ) const { return IsWarpAtRow( BeatToNoteRow( fBeat ) ); } /** * @brief Add the WarpSegment to the TimingData. * @param seg the new WarpSegment. */ void AddWarpSegment( const WarpSegment &seg ); - /** * @brief Retrieve the Tickcount at the given row. * @param iNoteRow the row in question. @@ -1011,7 +1079,7 @@ public: } float GetElapsedTimeFromBeat( float fBeat ) const; - void GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &iWarpLengthOut ) const; + void GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpDestinationOut ) const; float GetBeatFromElapsedTimeNoOffset( float fElapsedTime ) const // shortcut for places that care only about the beat { float fBeat, fThrowAway, fThrowAway2;