diff --git a/Docs/Changelog_SSCformat.txt b/Docs/Changelog_SSCformat.txt index 205aac7c06..4aa7024de4 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.57] - Wolfman2000 +* Implement #LABELS tag for easier Editor work/Rock Band section mimicry. + [v0.56] - Wolfman2000 * Implement #WARPS tag to replace negative bpm/stop gimmicks. diff --git a/Docs/Changelog_sm-ssc.txt b/Docs/Changelog_sm-ssc.txt index 9b7c9fd1a0..f480c29717 100644 --- a/Docs/Changelog_sm-ssc.txt +++ b/Docs/Changelog_sm-ssc.txt @@ -9,6 +9,16 @@ Not all changes are documented, for various reasons. supported but exist anyways.) _____________________________________________________________________________ +================================================================================ +sm-ssc $SM5VERSION | 2011???? +-------------------------------------------------------------------------------- + +20110405 +-------- +* [NotesLoaderSSC, NotesWriterSSC] Add the #LABELS tag. This is meant for step + editors to label different parts of their stepcharts. Use Control + , or . to + jump to different labels made. [Wolfman2000] + ================================================================================ sm-ssc $NEXTVERSION | 2011???? -------------------------------------------------------------------------------- diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 334204ea30..beda92ad34 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1391,6 +1391,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index cf44a947be..b82e145a03 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3241,6 +3241,9 @@ Returns a table of the Delays and the times they happen as strings with the format "beat=stop seconds". + + Returns a table of the Labels and the times they happen as strings with the format "beat=label name." + Returns true if the TimingData contains BPM changes. diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 2b44eee848..a140e09d77 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 label=Edit label Edit warp=Edit warp Editor options=Options EditorShowBGChangesPlay=Show Backgrounds @@ -1210,6 +1211,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 Label value.=Enter a name for this section of the chart. 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. diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 524bda0550..ab4339e527 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -981,6 +981,7 @@ 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") +LabelColor=color("1,0,0,1") # BPMIsLeftSide=true StopIsLeftSide=true @@ -989,6 +990,7 @@ WarpIsLeftSide=false TimeSignatureIsLeftSide=true TickcountIsLeftSide=false ComboIsLeftSide=false +LabelIsLeftSide=false # BPMOffsetX=60 StopOffsetX=50 @@ -997,6 +999,7 @@ WarpOffsetX=60 TimeSignatureOffsetX=30 TickcountOffsetX=10 ComboOffsetX=30 +LabelOffsetX=80 [PlayerStageStats] # Original CVS Grading diff --git a/src/NoteField.cpp b/src/NoteField.cpp index 958866b0a5..4d1a7ef0b2 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -426,6 +426,7 @@ 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 LABEL_COLOR ( "NoteField", "LabelColor" ); static ThemeMetric BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" ); static ThemeMetric STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" ); static ThemeMetric DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" ); @@ -433,6 +434,7 @@ 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 LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" ); static ThemeMetric BPM_OFFSETX ( "NoteField", "BPMOffsetX" ); static ThemeMetric STOP_OFFSETX ( "NoteField", "StopOffsetX" ); static ThemeMetric DELAY_OFFSETX ( "NoteField", "DelayOffsetX" ); @@ -440,6 +442,7 @@ 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" ); +static ThemeMetric LABEL_OFFSETX ( "NoteField", "LabelOffsetX" ); void NoteField::DrawBPMText( const float fBeat, const float fBPM ) { @@ -552,6 +555,23 @@ void NoteField::DrawComboText( const float fBeat, int iCombo ) m_textMeasureNumber.Draw(); } +void NoteField::DrawLabelText( const float fBeat, RString sLabel ) +{ + 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 = LABEL_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( LABEL_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( LABEL_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( sLabel.c_str() ); + m_textMeasureNumber.SetXY( (LABEL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + void NoteField::DrawAttackText( const float fBeat, const Attack &attack ) { const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); @@ -842,6 +862,19 @@ void NoteField::DrawPrimitives() DrawComboText( fBeat, tComboSegments[i].m_iCombo ); } } + + // Label text + const vector &lLabelSegments = GAMESTATE->m_pCurSong->m_Timing.m_LabelSegments; + for( unsigned i=0; i= iFirstRowToDraw && + lLabelSegments[i].m_iStartRow <= iLastRowToDraw) + { + float fBeat = NoteRowToBeat(lLabelSegments[i].m_iStartRow); + if( IS_ON_SCREEN(fBeat) ) + DrawLabelText( fBeat, lLabelSegments[i].m_sLabel ); + } + } // Course mods text const Course *pCourse = GAMESTATE->m_pCurCourse; diff --git a/src/NoteField.h b/src/NoteField.h index 08a83e11aa..23502eca00 100644 --- a/src/NoteField.h +++ b/src/NoteField.h @@ -61,6 +61,7 @@ protected: void DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator ); void DrawTickcountText( const float fBeat, int iTicks ); void DrawComboText( const float fBeat, int iCombo ); + void DrawLabelText( const float fBeat, RString sLabel ); void DrawAttackText( const float fBeat, const Attack &attack ); void DrawBGChangeText( const float fBeat, const RString sNewBGName ); float GetWidth() const; diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 1868dfd19d..00c2503f2b 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -514,6 +514,35 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach } } } + + else if( sValueName=="LABELS" ) + { + vector arrayLabelExpressions; + split( sParams[1], ",", arrayLabelExpressions ); + + for( unsigned b=0; b arrayLabelValues; + split( arrayLabelExpressions[b], "=", arrayLabelValues ); + if( arrayLabelValues.size() != 2 ) + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", + sValueName.c_str(), arrayLabelExpressions[b].c_str() ); + continue; + } + + const float fBeat = StringToFloat( arrayLabelValues[0] ); + RString sLabel = arrayLabelValues[1]; + TrimRight(sLabel); + if( fBeat >= 0.0f ) + out.m_Timing.AddLabelSegment( LabelSegment(fBeat, sLabel) ); + else + { + LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Label at beat %f called %s.", fBeat, sLabel.c_str() ); + } + + } + } else if( sValueName=="TIMESIGNATURES" ) { @@ -763,180 +792,28 @@ bool SSCLoader::LoadFromSSCFile( const RString &sPath, Song &out, bool bFromCach { if( sValueName=="STOPS" ) { - /* - vector arrayFreezeExpressions; - split( sParams[1], ",", arrayFreezeExpressions ); - - for( unsigned f=0; f arrayFreezeValues; - split( arrayFreezeExpressions[f], "=", arrayFreezeValues ); - if( arrayFreezeValues.size() != 2 ) - { - // XXX: Hard to tell which file caused this. - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", - sValueName.c_str(), arrayFreezeExpressions[f].c_str() ); - continue; - } - - const float fFreezeBeat = StringToFloat( arrayFreezeValues[0] ); - const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ); - StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds ); - - if(fFreezeSeconds > 0.0f) - { - // LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); - pNewNotes->m_Timing.AddStopSegment( new_seg ); - } - else - { - // negative stops (hi JS!) -aj - if( PREFSMAN->m_bQuirksMode ) - { - // LOG->Trace( "Adding a negative freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); - pNewNotes->m_Timing.AddStopSegment( new_seg ); - } - else - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid stop at beat %f, length %f.", fFreezeBeat, fFreezeSeconds ); - } - } - */ + // copy from above when it's time. } else if( sValueName=="DELAYS" ) { - /* - vector arrayDelayExpressions; - split( sParams[1], ",", arrayDelayExpressions ); - - for( unsigned f=0; f arrayDelayValues; - split( arrayDelayExpressions[f], "=", arrayDelayValues ); - if( arrayDelayValues.size() != 2 ) - { - // XXX: Hard to tell which file caused this. - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", - sValueName.c_str(), arrayDelayExpressions[f].c_str() ); - continue; - } - - const float fFreezeBeat = StringToFloat( arrayDelayValues[0] ); - const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] ); - - StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true ); - - // LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds ); - - if(fFreezeSeconds > 0.0f) - pNewNotes->m_Timing.AddStopSegment( new_seg ); - else - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds ); - } - */ + // copy from above when it's time. } else if( sValueName=="TIMESIGNATURES" ) { - /* - vector vs1; - split( sParams[1], ",", vs1 ); - - FOREACH_CONST( RString, vs1, s1 ) - { - vector vs2; - split( *s1, "=", vs2 ); - - if( vs2.size() < 3 ) - { - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() ); - continue; - } - - const float fBeat = StringToFloat( vs2[0] ); - - TimeSignatureSegment seg; - seg.m_iStartRow = BeatToNoteRow(fBeat); - seg.m_iNumerator = atoi( vs2[1] ); - seg.m_iDenominator = atoi( vs2[2] ); - - if( fBeat < 0 ) - { - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat ); - continue; - } - - if( seg.m_iNumerator < 1 ) - { - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator ); - continue; - } - - if( seg.m_iDenominator < 1 ) - { - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator ); - continue; - } - - pNewNotes->m_Timing.AddTimeSignatureSegment( seg ); - } - */ + // copy from above when it's time. } else if( sValueName=="TICKCOUNTS" ) { - /* - vector arrayTickcountExpressions; - split( sParams[1], ",", arrayTickcountExpressions ); - - for( unsigned f=0; f arrayTickcountValues; - split( arrayTickcountExpressions[f], "=", arrayTickcountValues ); - if( arrayTickcountValues.size() != 2 ) - { - // XXX: Hard to tell which file caused this. - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", - sValueName.c_str(), arrayTickcountExpressions[f].c_str() ); - continue; - } - - const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] ); - const int iTicks = atoi( arrayTickcountValues[1] ); - TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks ); - - if(iTicks >= 1 && iTicks <= ROWS_PER_BEAT ) // Constants - { - // LOG->Trace( "Adding a tickcount segment: beat: %f, ticks = %d", fTickcountBeat, iTicks ); - pNewNotes->m_Timing.AddTickcountSegment( new_seg ); - } - else - { - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid tickcount at beat %f, ticks %d.", fTickcountBeat, iTicks ); - } - } - */ + // copy from above when it's time. } else if( sValueName=="COMBOS" ) { - /* - vector arrayComboExpressions; - split( sParams[1], ",", arrayComboExpressions ); - - for( unsigned f=0; f arrayComboValues; - split( arrayComboExpressions[f], "=", arrayComboValues ); - if( arrayComboValues.size() != 2 ) - { - LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", - sValueName.c_str(), arrayComboExpressions[f].c_str() ); - continue; - } - const float fComboBeat = StringToFloat( arrayComboValues[0] ); - const int iCombos = atoi( arrayComboValues[1] ); - ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos ); - pNewNotes->m_Timing.AddComboSegment( new_seg ); - } - */ + // copy from above when it's time. + } + else if( sValueName=="WARPS" || sValueName=="LABELS" ) + { + // copy from above when it's time. } else if( sValueName=="ATTACKS" ) { diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 36c170b475..71e57ef042 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -194,6 +194,17 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) } f.PutLine( ";" ); + f.Write( "#LABELS:" ); + for( unsigned i=0; i asBPMValues; - for( unsigned i=0; i asStopValues; - for( unsigned i=0; i asDelayValues; - for( unsigned i=0; i asWarpValues; - for( unsigned i=0; i asTimeSigValues; - FOREACH_CONST( TimeSignatureSegment, in.m_Timing.m_vTimeSignatureSegments, iter ) - { - asTimeSigValues.push_back( ssprintf( "%.6f=%d=%d", NoteRowToBeat(iter->m_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) ); - vector::const_iterator iter2 = iter; - iter2++; - } - lines.push_back( ssprintf( "#TIMESIGNATURES:%s;", join("\n,", asTimeSigValues).c_str() ) ); - - ASSERT( !in.m_Timing.m_TickcountSegments.empty() ); - vector asTickValues; - for( unsigned i=0; i asComboValues; - for( unsigned i=0; im_pCurSong->m_Timing. + GetNextLabelSegmentBeatAtBeat( GAMESTATE->m_fSongBeat ) ); + } + break; + case EDIT_BUTTON_LABEL_PREV: + { + ScrollTo( GAMESTATE->m_pCurSong->m_Timing. + GetPreviousLabelSegmentBeatAtBeat( GAMESTATE->m_fSongBeat ) ); + } + break; case EDIT_BUTTON_SNAP_NEXT: if( m_SnapDisplay.PrevSnapMode() ) OnSnapModeChange(); @@ -2644,6 +2666,17 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) } SetDirty( true ); } + else if ( SM == SM_BackFromLabelChange ) + { + RString sLabel = ScreenTextEntry::s_sLastAnswer; + if ( !m_pSong->m_Timing.DoesLabelExist(sLabel) ) + { + sLabel.Replace("=", "_"); + sLabel.Replace(",", "_"); + m_pSong->m_Timing.SetLabelAtBeat( GAMESTATE->m_fSongBeat, sLabel ); + SetDirty( true ); + } + } else if ( SM == SM_BackFromWarpChange ) { float fWarp = StringToFloat( ScreenTextEntry::s_sLastAnswer ); @@ -3187,6 +3220,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[label].SetOneUnthemedChoice( pTime.GetLabelAtBeat( fBeat ).c_str() ); g_TimingDataInformation.rows[warp].SetOneUnthemedChoice( ssprintf("%.5f", pTime.GetWarpAtBeat( fBeat ) ) ); EditMiniMenu( &g_TimingDataInformation, SM_BackFromTimingDataInformation ); @@ -3618,6 +3652,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_LABEL_VALUE ( "ScreenEdit", "Enter a new Label value." ); static LocalizedString ENTER_WARP_VALUE ( "ScreenEdit", "Enter a new Warp value." ); void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice c, const vector &iAnswers ) { @@ -3680,6 +3715,14 @@ void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice 4 ); break; + case label: + ScreenTextEntry::TextEntry( + SM_BackFromLabelChange, + ENTER_LABEL_VALUE, + ssprintf( "%s", m_pSong->m_Timing.GetLabelAtBeat( GAMESTATE->m_fSongBeat ).c_str() ), + 64 + ); + break; case warp: ScreenTextEntry::TextEntry( SM_BackFromWarpChange, diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index 1f79b78528..f64772cc27 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -70,6 +70,9 @@ enum EditButton EDIT_BUTTON_SCROLL_NEXT, EDIT_BUTTON_SCROLL_PREV, + EDIT_BUTTON_LABEL_NEXT, + EDIT_BUTTON_LABEL_PREV, + // These are modifiers to EDIT_BUTTON_SCROLL_*. EDIT_BUTTON_SCROLL_SELECT, @@ -456,6 +459,7 @@ public: time_signature_denominator, tickcount, combo, + label, warp, NUM_TIMING_DATA_INFORMATION_CHOICES }; diff --git a/src/Song.cpp b/src/Song.cpp index 50537a8b97..5e76729eac 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 = 167; +const int FILE_CACHE_VERSION = 168; /** @brief How long does a song sample last by default? */ const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; @@ -781,10 +781,7 @@ void Song::TidyUpData() // If no time signature specified, assume 4/4 time for the whole song. if( m_Timing.m_vTimeSignatureSegments.empty() ) { - TimeSignatureSegment seg; - seg.m_iStartRow = 0; - seg.m_iNumerator = 4; - seg.m_iDenominator = 4; + TimeSignatureSegment seg(0, 4, 4); m_Timing.m_vTimeSignatureSegments.push_back( seg ); } @@ -795,9 +792,7 @@ void Song::TidyUpData() */ if( m_Timing.m_TickcountSegments.empty() ) { - TickcountSegment seg; - seg.m_iStartRow = 0; - seg.m_iTicks = 2; + TickcountSegment seg(0, 2); m_Timing.m_TickcountSegments.push_back( seg ); } @@ -809,6 +804,13 @@ void Song::TidyUpData() seg.m_iCombo = 1; m_Timing.m_ComboSegments.push_back( seg ); } + + // Have a default label segment just in case. + if( m_Timing.m_LabelSegments.empty() ) + { + LabelSegment seg(0, "Song Start"); + m_Timing.m_LabelSegments.push_back( seg ); + } } void Song::TranslateTitles() diff --git a/src/Song.h b/src/Song.h index d9b1d18c1f..5774879483 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.56f; +const static float STEPFILE_VERSION_NUMBER = 0.57f; /** @brief How many edits for this song can each profile have? */ const int MAX_EDITS_PER_SONG_PER_PROFILE = 5; diff --git a/src/TimingData.cpp b/src/TimingData.cpp index 0fa5d50889..26991f3d67 100644 --- a/src/TimingData.cpp +++ b/src/TimingData.cpp @@ -57,6 +57,11 @@ void TimingData::AddComboSegment( const ComboSegment &seg ) m_ComboSegments.insert( upper_bound(m_ComboSegments.begin(), m_ComboSegments.end(), seg), seg ); } +void TimingData::AddLabelSegment( const LabelSegment &seg ) +{ + m_LabelSegments.insert( upper_bound(m_LabelSegments.begin(), m_LabelSegments.end(), seg), seg ); +} + /* Change an existing BPM segment, merge identical segments together or insert a new one. */ void TimingData::SetBPMAtRow( int iNoteRow, float fBPM ) { @@ -219,6 +224,27 @@ void TimingData::SetComboAtRow( int iRow, int iCombo ) } } +void TimingData::SetLabelAtRow( int iRow, const RString sLabel ) +{ + unsigned i; + for( i=0; i= iRow ) + break; + + if( i == m_LabelSegments.size() || m_LabelSegments[i].m_iStartRow != iRow ) + { + if( i == 0 || m_LabelSegments[i-1].m_sLabel != sLabel ) + AddLabelSegment( LabelSegment(iRow, sLabel ) ); + } + else + { + if( i > 0 && ( m_LabelSegments[i-1].m_sLabel == sLabel || sLabel == "" ) ) + m_LabelSegments.erase( m_LabelSegments.begin()+i, m_LabelSegments.begin()+i+1 ); + else + m_LabelSegments[i].m_sLabel = sLabel; + } +} + float TimingData::GetStopAtRow( int iNoteRow, bool bDelay ) const { for( unsigned i=0; i(i); } +int TimingData::GetLabelSegmentIndexAtRow( int iRow ) const +{ + unsigned i; + for( i=0; i iRow ) + break; + } + return static_cast(i); +} + BPMSegment& TimingData::GetBPMSegmentAtRow( int iNoteRow ) { static BPMSegment empty; @@ -422,6 +465,15 @@ ComboSegment& TimingData::GetComboSegmentAtRow( int iRow ) return m_ComboSegments[i]; } +LabelSegment& TimingData::GetLabelSegmentAtRow( int iRow ) +{ + unsigned i; + for( i=0; i iRow ) + break; + return m_LabelSegments[i]; +} + StopSegment& TimingData::GetStopSegmentAtRow( int iNoteRow, bool bDelay ) { static StopSegment empty; @@ -444,11 +496,11 @@ WarpSegment& TimingData::GetWarpSegmentAtRow( int iRow ) int TimingData::GetTickcountSegmentIndexAtRow( int iRow ) const { - int i; - for (i=0; i < (int)(m_TickcountSegments.size()) - 1; i++ ) + unsigned i; + for (i=0; i < m_TickcountSegments.size() - 1; i++ ) if( m_TickcountSegments[i+1].m_iStartRow > iRow ) break; - return i; + return static_cast(i); } TickcountSegment& TimingData::GetTickcountSegmentAtRow( int iRow ) @@ -466,6 +518,43 @@ int TimingData::GetTickcountAtRow( int iRow ) const return m_TickcountSegments[GetTickcountSegmentIndexAtRow( iRow )].m_iTicks; } +float TimingData::GetPreviousLabelSegmentBeatAtRow( int iRow ) const +{ + float backup = -1; + for (unsigned i = 0; i < m_LabelSegments.size(); i++ ) + { + if( m_LabelSegments[i].m_iStartRow >= iRow ) + { + break; + } + backup = NoteRowToBeat(m_LabelSegments[i].m_iStartRow); + } + return (backup > -1) ? backup : NoteRowToBeat(iRow); +} + +float TimingData::GetNextLabelSegmentBeatAtRow( int iRow ) const +{ + for (unsigned i = 0; i < m_LabelSegments.size(); i++ ) + { + if( m_LabelSegments[i].m_iStartRow <= iRow ) + { + continue; + } + return NoteRowToBeat(m_LabelSegments[i].m_iStartRow); + } + return NoteRowToBeat(iRow); +} + +bool TimingData::DoesLabelExist( RString sLabel ) const +{ + FOREACH_CONST( LabelSegment, m_LabelSegments, seg ) + { + if( seg->m_sLabel == sLabel ) + return true; + } + return false; +} + void TimingData::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut, bool &bDelayOut, int &iWarpBeginOut, float &fWarpLengthOut ) const { fElapsedTime += PREFSMAN->m_fGlobalOffsetSeconds; @@ -582,9 +671,6 @@ void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float } - - - float TimingData::GetElapsedTimeFromBeat( float fBeat ) const { return TimingData::GetElapsedTimeFromBeatNoOffset( fBeat ) - PREFSMAN->m_fGlobalOffsetSeconds; @@ -798,6 +884,13 @@ void TimingData::InsertRows( int iStartRow, int iRowsToAdd ) continue; comb.m_iStartRow += iRowsToAdd; } + for( unsigned i = 0; i < m_LabelSegments.size(); i++ ) + { + LabelSegment &labl = m_LabelSegments[i]; + if( labl.m_iStartRow < iStartRow ) + continue; + labl.m_iStartRow += iRowsToAdd; + } if( iStartRow == 0 ) { @@ -939,6 +1032,22 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete ) // After deleted region: comb.m_iStartRow -= iRowsToDelete; } + + for( unsigned i = 0; i < m_LabelSegments.size(); i++ ) + { + LabelSegment &labl = m_LabelSegments[i]; + + if( labl.m_iStartRow < iStartRow ) + continue; + + if( labl.m_iStartRow < iStartRow+iRowsToDelete ) + { + m_LabelSegments.erase( m_LabelSegments.begin()+i, m_LabelSegments.begin()+i+1 ); + --i; + continue; + } + labl.m_iStartRow -= iRowsToDelete; + } this->SetBPMAtRow( iStartRow, fNewBPM ); } @@ -1044,6 +1153,18 @@ public: LuaHelpers::CreateTableFromArray(vBPMs, L); return 1; } + static int GetLabels( T* p, lua_State *L ) + { + vector vLabels; + FOREACH_CONST( LabelSegment, p->m_LabelSegments, seg ) + { + const float fStartRow = NoteRowToBeat(seg->m_iStartRow); + const RString sLabel = seg->m_sLabel; + vLabels.push_back( ssprintf("%f=%s", fStartRow, sLabel.c_str()) ); + } + LuaHelpers::CreateTableFromArray(vLabels, L); + return 1; + } static int GetBPMsAndTimes( T* p, lua_State *L ) { vector vBPMs; @@ -1082,6 +1203,7 @@ public: ADD_METHOD( GetStops ); ADD_METHOD( GetDelays ); ADD_METHOD( GetBPMs ); + ADD_METHOD( GetLabels ); ADD_METHOD( GetBPMsAndTimes ); ADD_METHOD( GetActualBPM ); ADD_METHOD( HasNegativeBPMs ); diff --git a/src/TimingData.h b/src/TimingData.h index f61afb6d9c..5464a8748e 100644 --- a/src/TimingData.h +++ b/src/TimingData.h @@ -564,6 +564,90 @@ struct ComboSegment bool operator>=( const ComboSegment &other ) const { return !operator<(other); } }; +/** + * @brief Identifies when a chart is entering a different section. + * + * This is meant for helping to identify different sections of a chart + * versus relying on measures and beats alone. + */ +struct LabelSegment +{ + /** + * @brief Creates a simple Label Segment with default values. + * + * It is best to override the values as soon as possible. + */ + LabelSegment() : m_iStartRow(-1), m_sLabel("") { } + /** + * @brief Creates a Label Segment with the specified starting row and label. + * @param s the starting row of this segment. + * @param l the label for this section. + */ + LabelSegment( int s, RString l ): m_iStartRow(max(0, s)), + m_sLabel(l) {} + /** + * @brief Creates a Label Segment with the specified starting beat and label. + * @param s the starting beat of this segment. + * @param l the label for this section. + */ + LabelSegment( float s, RString l ): + m_iStartRow(max(0, BeatToNoteRow(s))), m_sLabel(l) {} + /** + * @brief The row in which the ComboSegment activates. + */ + int m_iStartRow; + /** + * @brief The label/section name for this point. + */ + RString m_sLabel; + + /** + * @brief Compares two LabelSegments to see if they are equal to each other. + * @param other the other LabelSegment to compare to. + * @return the equality of the two segments. + */ + bool operator==( const LabelSegment &other ) const + { + COMPARE( m_iStartRow ); + COMPARE( m_sLabel ); + return true; + } + /** + * @brief Compares two LabelSegments to see if they are not equal to each other. + * @param other the other LabelSegment to compare to. + * @return the inequality of the two segments. + */ + bool operator!=( const LabelSegment &other ) const { return !operator==(other); } + /** + * @brief Compares two LabelSegments to see if one is less than the other. + * @param other the other LabelSegment to compare to. + * @return the truth/falsehood of if the first is less than the second. + */ + bool operator<( const LabelSegment &other ) const { return m_iStartRow < other.m_iStartRow; } + /** + * @brief Compares two LabelSegments to see if one is less than or equal to the other. + * @param other the other LabelSegment to compare to. + * @return the truth/falsehood of if the first is less or equal to than the second. + */ + bool operator<=( const LabelSegment &other ) const + { + return ( operator<(other) || operator==(other) ); + } + /** + * @brief Compares two LabelSegments to see if one is greater than the other. + * @param other the other LabelSegment to compare to. + * @return the truth/falsehood of if the first is greater than the second. + */ + bool operator>( const LabelSegment &other ) const { return !operator<=(other); } + /** + * @brief Compares two LabelSegments to see if one is greater than or equal to the other. + * @param other the other LabelSegment to compare to. + * @return the truth/falsehood of if the first is greater than or equal to the second. + */ + bool operator>=( const LabelSegment &other ) const { return !operator<(other); } +}; + + /** * @brief Holds data for translating beats<->seconds. */ @@ -1064,6 +1148,92 @@ public: */ void AddComboSegment( const ComboSegment &seg ); + /** + * @brief Retrieve the Label at the given row. + * @param iNoteRow the row in question. + * @return the Label. + */ + RString GetLabelAtRow( int iNoteRow ) const; + /** + * @brief Retrieve the Label at the given beat. + * @param fBeat the beat in question. + * @return the Label. + */ + RString GetLabelAtBeat( float fBeat ) const { return GetLabelAtRow( BeatToNoteRow(fBeat) ); } + /** + * @brief Set the row to have the new Label. + * @param iNoteRow the row to have the new Label. + * @param sLabel the Label. + */ + void SetLabelAtRow( int iNoteRow, const RString sLabel ); + /** + * @brief Set the beat to have the new Label. + * @param fBeat the beat to have the new Label. + * @param sLabel the Label. + */ + void SetLabelAtBeat( float fBeat, const RString sLabel ) { SetLabelAtRow( BeatToNoteRow( fBeat ), sLabel ); } + /** + * @brief Retrieve the LabelSegment at the specified row. + * @param iNoteRow the row that has a LabelSegment. + * @return the LabelSegment in question. + */ + LabelSegment& GetLabelSegmentAtRow( int iNoteRow ); + /** + * @brief Retrieve the LabelSegment at the specified beat. + * @param fBeat the beat that has a LabelSegment. + * @return the LabelSegment in question. + */ + LabelSegment& GetLabelSegmentAtBeat( float fBeat ) { return GetLabelSegmentAtRow( BeatToNoteRow(fBeat) ); } + /** + * @brief Retrieve the index of the LabelSegments at the specified row. + * @param iNoteRow the row that has a LabelSegment. + * @return the LabelSegment's index in question. + */ + int GetLabelSegmentIndexAtRow( int iNoteRow ) const; + /** + * @brief Retrieve the index of the LabelSegments at the specified beat. + * @param fBeat the beat that has a LabelSegment. + * @return the LabelSegment's index in question. + */ + int GetLabelSegmentIndexAtBeat( float fBeat ) const { return GetLabelSegmentIndexAtRow( BeatToNoteRow(fBeat) ); } + /** + * @brief Add the LabelSegment to the TimingData. + * @param seg the new LabelSegment. + */ + void AddLabelSegment( const LabelSegment &seg ); + + /** + * @brief Retrieve the previous beat that contains a LabelSegment. + * @param iRow the present row. + * @return the previous beat with a LabelSegment, or fBeat if there is none prior. + */ + float GetPreviousLabelSegmentBeatAtRow( int iRow ) const; + /** + * @brief Retrieve the previous beat that contains a LabelSegment. + * @param fBeat the present beat. + * @return the previous beat with a LabelSegment, or fBeat if there is none prior. + */ + float GetPreviousLabelSegmentBeatAtBeat( float fBeat ) const { return GetPreviousLabelSegmentBeatAtRow( BeatToNoteRow(fBeat) ); } + + /** + * @brief Determine if the requisite label already exists. + * @param sLabel the label to check. + * @return true if it exists, false otherwise. */ + bool DoesLabelExist( RString sLabel ) const; + + /** + * @brief Retrieve the next beat that contains a LabelSegment. + * @param iRow the present row. + * @return the next beat with a LabelSegment, or fBeat if there is none ahead. + */ + float GetNextLabelSegmentBeatAtRow( int iRow ) const; + /** + * @brief Retrieve the previous beat that contains a LabelSegment. + * @param fBeat the present beat. + * @return the next beat with a LabelSegment, or fBeat if there is none ahead. + */ + float GetNextLabelSegmentBeatAtBeat( float fBeat ) const { return GetNextLabelSegmentBeatAtRow( BeatToNoteRow(fBeat) ); } + void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor ); void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const; @@ -1131,6 +1301,8 @@ public: COMPARE( m_ComboSegments.size() ); for( unsigned i=0; i m_ComboSegments; + /** + * @brief The collection of LabelSegments. + */ + vector m_LabelSegments; /** * @brief The initial offset of a song. */