Write timing tags for SSC files.

WARN: This does not work with ScreenEdit, yet!
This commit is contained in:
Thai Pangsakulyanont
2011-05-10 16:00:47 +07:00
parent 285f227851
commit b634639e8e
4 changed files with 168 additions and 170 deletions
+109 -129
View File
@@ -41,6 +41,111 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
return s;
}
/**
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
// A utility class to write timing tags more easily!
struct TimingTagWriter {
vector<RString> *m_pvsLines;
RString m_sNext;
TimingTagWriter( vector<RString> *pvsLines ): m_pvsLines (pvsLines) { }
void Write( const int row, const char *value )
{
m_pvsLines->push_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) );
m_sNext = ",";
}
void Write( const int row, const float value ) { Write( row, ssprintf( "%.6f", value ) ); }
void Write( const int row, const int value ) { Write( row, ssprintf( "%d", value ) ); }
void Write( const int row, const int a, const int b ) { Write( row, ssprintf( "%d=%d", a, b ) ); }
void Init( const RString sTag ) { m_sNext = "#" + sTag + ":"; }
void Finish( ) { m_pvsLines->push_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); }
};
static void GetTimingTags( vector<RString> &lines, TimingData timing )
{
TimingTagWriter w ( &lines );
timing.TidyUpData();
w.Init( "BPMS" );
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, bs )
w.Write( bs->m_iStartRow, bs->GetBPM() );
w.Finish();
w.Init( "STOPS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( !ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "DELAYS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "WARPS" );
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, ws )
w.Write( ws->m_iStartRow, ws->m_fEndBeat );
w.Finish();
ASSERT( !timing.m_vTimeSignatureSegments.empty() );
w.Init( "TIMESIGNATURES" );
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
w.Write( iter->m_iStartRow, iter->m_iNumerator, iter->m_iDenominator );
w.Finish();
ASSERT( !timing.m_TickcountSegments.empty() );
w.Init( "TICKCOUNTS" );
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, ts )
w.Write( ts->m_iStartRow, ts->m_iTicks );
w.Finish();
ASSERT( !timing.m_ComboSegments.empty() );
w.Init( "COMBOS" );
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, cs )
w.Write( cs->m_iStartRow, cs->m_iCombo );
w.Finish();
w.Init( "LABELS" );
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, ls )
w.Write( ls->m_iStartRow, ls->m_sLabel.c_str() );
w.Finish();
}
static void WriteTimingTags( RageFile &f, const TimingData &timing )
{
vector<RString> lines;
GetTimingTags( lines, timing );
f.PutLine( JoinLineList( lines ) );
}
/**
* @brief Write out the common tags for .SM files.
* @param f the file in question.
@@ -106,105 +211,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
break;
}
f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
if(!fs.m_bDelay)
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#WARPS:" );
for( unsigned i=0; i<out.m_SongTiming.m_WarpSegments.size(); i++ )
{
const WarpSegment &ws = out.m_SongTiming.m_WarpSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(ws.m_iStartRow), ws.m_fEndBeat ) );
if( i != out.m_SongTiming.m_WarpSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_SongTiming.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_SongTiming.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.6f=%d=%d", NoteRowToBeat(iter->m_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_SongTiming.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_SongTiming.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_SongTiming.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_SongTiming.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_SongTiming.m_TickcountSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_SongTiming.m_ComboSegments.empty() );
f.Write( "#COMBOS:" );
for( unsigned i=0; i<out.m_SongTiming.m_ComboSegments.size(); i++ )
{
const ComboSegment &cs = out.m_SongTiming.m_ComboSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(cs.m_iStartRow), cs.m_iCombo ) );
if( i != out.m_SongTiming.m_ComboSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#LABELS:" );
for( unsigned i=0; i<out.m_SongTiming.m_LabelSegments.size(); i++ )
{
const LabelSegment &ls = out.m_SongTiming.m_LabelSegments[i];
f.PutLine( ssprintf( "%.6f=%s", NoteRowToBeat(ls.m_iStartRow), ls.m_sLabel.c_str() ) );
if( i != out.m_SongTiming.m_LabelSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
WriteTimingTags( f, out.m_SongTiming );
FOREACH_BackgroundLayer( b )
{
@@ -258,23 +265,6 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ";" );
}
/**
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
/**
* @brief Retrieve the individual batches of NoteData.
* @param song the Song in question.
@@ -307,19 +297,8 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
/*
* TODO: Remove this block, transplant above code
* below for Split Timing. -Wolfman2000 */
lines.push_back( "#BPMS:;" );
lines.push_back( "#STOPS:;" );
lines.push_back( "#DELAYS:;" );
lines.push_back( "#WARPS:;" );
lines.push_back( "#LABELS:;" );
lines.push_back( "#TIMESIGNATURES:;" );
lines.push_back( "#TICKCOUNTS:;" );
lines.push_back( "#ATTACKS:;" );
lines.push_back( "#COMBOS:;" );
GetTimingTags( lines, in.m_Timing );
RString sNoteData;
in.GetSMNoteData( sNoteData );
@@ -350,6 +329,7 @@ bool NotesWriterSSC::Write( RString sPath, const Song &out, const vector<Steps*>
}
WriteGlobalTags( f, out );
if( bSavingCache )
{
f.PutLine( ssprintf( "// cache tags:" ) );
+4 -41
View File
@@ -488,18 +488,13 @@ void Song::TidyUpData()
m_sArtist = "Unknown artist";
TranslateTitles();
if( m_SongTiming.m_BPMSegments.empty() )
m_SongTiming.TidyUpData();
FOREACH( Steps *, m_vpSteps, s )
{
LOG->UserLog( "Song file", m_sSongDir + m_sSongFileName, "has no BPM segments, default provided." );
m_SongTiming.AddBPMSegment( BPMSegment(0, 60) );
(*s)->m_Timing.TidyUpData();
}
// Make sure the first BPM segment starts at beat 0.
if( m_SongTiming.m_BPMSegments[0].m_iStartRow != 0 )
m_SongTiming.m_BPMSegments[0].m_iStartRow = 0;
if( m_fMusicSampleStartSeconds == -1 ||
m_fMusicSampleStartSeconds == 0 ||
m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
@@ -790,38 +785,6 @@ void Song::TidyUpData()
m_sSongFileName += ".ssc";
} while(0);
}
// If no time signature specified, assume 4/4 time for the whole song.
if( m_SongTiming.m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg(0, 4, 4);
m_SongTiming.m_vTimeSignatureSegments.push_back( seg );
}
/*
* Likewise, if no tickcount signature is specified, assume 2 ticks
* per beat for the entire song. The default of 2 is chosen more
* for compatibility with the Pump Pro series than anything else.
*/
if( m_SongTiming.m_TickcountSegments.empty() )
{
TickcountSegment seg(0, 2);
m_SongTiming.m_TickcountSegments.push_back( seg );
}
// Have a default combo segment of one just in case.
if( m_SongTiming.m_ComboSegments.empty() )
{
ComboSegment seg(0, 1);
m_SongTiming.m_ComboSegments.push_back( seg );
}
// Have a default label segment just in case.
if( m_SongTiming.m_LabelSegments.empty() )
{
LabelSegment seg(0, "Song Start");
m_SongTiming.m_LabelSegments.push_back( seg );
}
}
void Song::TranslateTitles()
+50
View File
@@ -1090,6 +1090,56 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete )
this->SetBPMAtRow( iStartRow, fNewBPM );
}
void TimingData::TidyUpData()
{
// If there are no BPM segments, provide a default.
if( m_BPMSegments.empty() )
{
LOG->UserLog( "Song file", m_sFile, "has no BPM segments, default provided." );
AddBPMSegment( BPMSegment(0, 60) );
}
// Make sure the first BPM segment starts at beat 0.
if( m_BPMSegments[0].m_iStartRow != 0 )
m_BPMSegments[0].m_iStartRow = 0;
// If no time signature specified, assume 4/4 time for the whole song.
if( m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg(0, 4, 4);
m_vTimeSignatureSegments.push_back( seg );
}
// Likewise, if no tickcount signature is specified, assume 2 ticks
//per beat for the entire song. The default of 2 is chosen more
//for compatibility with the Pump Pro series than anything else.
if( m_TickcountSegments.empty() )
{
TickcountSegment seg(0, 2);
m_TickcountSegments.push_back( seg );
}
// Have a default combo segment of one just in case.
if( m_ComboSegments.empty() )
{
ComboSegment seg(0, 1);
m_ComboSegments.push_back( seg );
}
// Have a default label segment just in case.
if( m_LabelSegments.empty() )
{
LabelSegment seg(0, "Song Start");
m_LabelSegments.push_back( seg );
}
}
bool TimingData::HasBpmChanges() const
{
return m_BPMSegments.size()>1;
+5
View File
@@ -1321,6 +1321,11 @@ public:
void InsertRows( int iStartRow, int iRowsToAdd );
void DeleteRows( int iStartRow, int iRowsToDelete );
/**
* @brief Tidy up the timing data, e.g. provide default BPMs, labels, tickcounts.
*/
void TidyUpData();
// Lua
void PushSelf( lua_State *L );
/**