Write timing tags for SSC files.
WARN: This does not work with ScreenEdit, yet!
This commit is contained in:
+108
-128
@@ -41,6 +41,111 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
|
|||||||
return s;
|
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.
|
* @brief Write out the common tags for .SM files.
|
||||||
* @param f the file in question.
|
* @param f the file in question.
|
||||||
@@ -106,105 +211,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Write( "#BPMS:" );
|
WriteTimingTags( f, out.m_SongTiming );
|
||||||
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( ";" );
|
|
||||||
|
|
||||||
FOREACH_BackgroundLayer( b )
|
FOREACH_BackgroundLayer( b )
|
||||||
{
|
{
|
||||||
@@ -258,23 +265,6 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
|
|||||||
f.PutLine( ";" );
|
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.
|
* @brief Retrieve the individual batches of NoteData.
|
||||||
* @param song the Song in question.
|
* @param song the Song in question.
|
||||||
@@ -307,18 +297,7 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
|
|||||||
|
|
||||||
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
|
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
|
||||||
|
|
||||||
/*
|
GetTimingTags( lines, in.m_Timing );
|
||||||
* 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:;" );
|
|
||||||
|
|
||||||
RString sNoteData;
|
RString sNoteData;
|
||||||
in.GetSMNoteData( sNoteData );
|
in.GetSMNoteData( sNoteData );
|
||||||
@@ -350,6 +329,7 @@ bool NotesWriterSSC::Write( RString sPath, const Song &out, const vector<Steps*>
|
|||||||
}
|
}
|
||||||
|
|
||||||
WriteGlobalTags( f, out );
|
WriteGlobalTags( f, out );
|
||||||
|
|
||||||
if( bSavingCache )
|
if( bSavingCache )
|
||||||
{
|
{
|
||||||
f.PutLine( ssprintf( "// cache tags:" ) );
|
f.PutLine( ssprintf( "// cache tags:" ) );
|
||||||
|
|||||||
+4
-41
@@ -488,18 +488,13 @@ void Song::TidyUpData()
|
|||||||
m_sArtist = "Unknown artist";
|
m_sArtist = "Unknown artist";
|
||||||
TranslateTitles();
|
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." );
|
(*s)->m_Timing.TidyUpData();
|
||||||
|
|
||||||
m_SongTiming.AddBPMSegment( BPMSegment(0, 60) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 ||
|
if( m_fMusicSampleStartSeconds == -1 ||
|
||||||
m_fMusicSampleStartSeconds == 0 ||
|
m_fMusicSampleStartSeconds == 0 ||
|
||||||
m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
|
m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
|
||||||
@@ -790,38 +785,6 @@ void Song::TidyUpData()
|
|||||||
m_sSongFileName += ".ssc";
|
m_sSongFileName += ".ssc";
|
||||||
} while(0);
|
} 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()
|
void Song::TranslateTitles()
|
||||||
|
|||||||
@@ -1090,6 +1090,56 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete )
|
|||||||
this->SetBPMAtRow( iStartRow, fNewBPM );
|
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
|
bool TimingData::HasBpmChanges() const
|
||||||
{
|
{
|
||||||
return m_BPMSegments.size()>1;
|
return m_BPMSegments.size()>1;
|
||||||
|
|||||||
@@ -1321,6 +1321,11 @@ public:
|
|||||||
void InsertRows( int iStartRow, int iRowsToAdd );
|
void InsertRows( int iStartRow, int iRowsToAdd );
|
||||||
void DeleteRows( int iStartRow, int iRowsToDelete );
|
void DeleteRows( int iStartRow, int iRowsToDelete );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tidy up the timing data, e.g. provide default BPMs, labels, tickcounts.
|
||||||
|
*/
|
||||||
|
void TidyUpData();
|
||||||
|
|
||||||
// Lua
|
// Lua
|
||||||
void PushSelf( lua_State *L );
|
void PushSelf( lua_State *L );
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user