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.
*/