fix saving and copying of tap attack notes

This commit is contained in:
Chris Danford
2003-11-17 03:38:24 +00:00
parent 929c8108c8
commit 3b8718c1e1
11 changed files with 257 additions and 127 deletions
+14 -2
View File
@@ -92,7 +92,18 @@ void NoteData::CopyRange( const NoteData* pFrom, int iFromIndexBegin, int iFromI
while( f<=iFromIndexEnd ) while( f<=iFromIndexEnd )
{ {
for( int c=0; c<m_iNumTracks; c++ ) for( int c=0; c<m_iNumTracks; c++ )
To.SetTapNote(c, t, From.GetTapNote(c, f)); {
TapNote tn = From.GetTapNote(c, f);
if( IsTapAttack(tn) )
{
Attack attack = From.GetAttackAt(c,f);
To.SetTapAttackNote( c, t, attack );
}
else
{
To.SetTapNote(c, t, tn);
}
}
f++; f++;
t++; t++;
} }
@@ -113,6 +124,7 @@ void NoteData::CopyAll( const NoteData* pFrom )
for( int c=0; c<m_iNumTracks; c++ ) for( int c=0; c<m_iNumTracks; c++ )
m_TapNotes[c] = pFrom->m_TapNotes[c]; m_TapNotes[c] = pFrom->m_TapNotes[c];
m_HoldNotes = pFrom->m_HoldNotes; m_HoldNotes = pFrom->m_HoldNotes;
m_AttackMap = pFrom->m_AttackMap;
} }
void NoteData::AddHoldNote( HoldNote add ) void NoteData::AddHoldNote( HoldNote add )
@@ -174,7 +186,7 @@ void NoteData::RemoveHoldNote( int iHoldIndex )
m_HoldNotes.erase(m_HoldNotes.begin()+iHoldIndex, m_HoldNotes.begin()+iHoldIndex+1); m_HoldNotes.erase(m_HoldNotes.begin()+iHoldIndex, m_HoldNotes.begin()+iHoldIndex+1);
} }
void NoteData::AddAttackNote( int track, int row, Attack attack ) void NoteData::SetTapAttackNote( int track, int row, Attack attack )
{ {
PruneUnusedAttacksFromMap(); PruneUnusedAttacksFromMap();
+6 -1
View File
@@ -35,6 +35,7 @@ class NoteData
void LoadOverlapped( const NoteData* pOriginal, int iNewNumTracks ); void LoadOverlapped( const NoteData* pOriginal, int iNewNumTracks );
public: public:
/* Set up to hold the data in From; same number of tracks, same /* Set up to hold the data in From; same number of tracks, same
* divisor. Doesn't allocate or copy anything. */ * divisor. Doesn't allocate or copy anything. */
void Config( const NoteData &From ); void Config( const NoteData &From );
@@ -46,6 +47,10 @@ public:
int GetNumTracks() const; int GetNumTracks() const;
void SetNumTracks( int iNewNumTracks ); void SetNumTracks( int iNewNumTracks );
// TODO: Think of better accessors
const map<TapNote,Attack>& GetAttackMap() const { return m_AttackMap; }
map<TapNote,Attack>& GetAttackMap() { return m_AttackMap; }
/* Return the note at the given track and row. Row may be out of /* Return the note at the given track and row. Row may be out of
* range; pretend the song goes on with TAP_EMPTYs indefinitely. */ * range; pretend the song goes on with TAP_EMPTYs indefinitely. */
inline TapNote GetTapNote(unsigned track, int row) const inline TapNote GetTapNote(unsigned track, int row) const
@@ -144,7 +149,7 @@ public:
HoldNote &GetHoldNote( int index ) { return m_HoldNotes[index]; } HoldNote &GetHoldNote( int index ) { return m_HoldNotes[index]; }
const HoldNote &GetHoldNote( int index ) const { return m_HoldNotes[index]; } const HoldNote &GetHoldNote( int index ) const { return m_HoldNotes[index]; }
void AddAttackNote( int track, int row, Attack attack ); void SetTapAttackNote( int track, int row, Attack attack );
void PruneUnusedAttacksFromMap(); // slow void PruneUnusedAttacksFromMap(); // slow
const Attack& GetAttackAt( int track, int row ); const Attack& GetAttackAt( int track, int row );
// remove Attacks with SetTapNote(TAP_EMPTY) // remove Attacks with SetTapNote(TAP_EMPTY)
+183 -97
View File
@@ -52,132 +52,218 @@ NoteType NoteDataUtil::GetSmallestNoteTypeForMeasure( const NoteData &n, int iMe
return nt; return nt;
} }
void NoteDataUtil::LoadFromSMNoteDataString( NoteData &out, CString sSMNoteData ) void NoteDataUtil::LoadFromSMNoteDataString( NoteData &out, CString sSMNoteData, CString sSMAttackData )
{ {
/* Clear notes, but keep the same number of tracks. */
int iNumTracks = out.GetNumTracks();
out.Init();
out.SetNumTracks( iNumTracks );
// strip comments out of sSMNoteData
while( sSMNoteData.Find("//") != -1 )
{ {
int iIndexCommentStart = sSMNoteData.Find("//"); //
int iIndexCommentEnd = sSMNoteData.Find("\n", iIndexCommentStart); // Load note data
if( iIndexCommentEnd == -1 ) // comment doesn't have an end? //
sSMNoteData.erase( iIndexCommentStart, 2 );
else
sSMNoteData.erase( iIndexCommentStart, iIndexCommentEnd-iIndexCommentStart );
}
CStringArray asMeasures; /* Clear notes, but keep the same number of tracks. */
split( sSMNoteData, ",", asMeasures, true ); // ignore empty is important int iNumTracks = out.GetNumTracks();
for( unsigned m=0; m<asMeasures.size(); m++ ) // foreach measure out.Init();
{ out.SetNumTracks( iNumTracks );
CString &sMeasureString = asMeasures[m];
TrimLeft(sMeasureString);
TrimRight(sMeasureString);
CStringArray asMeasureLines; // strip comments out of sSMNoteData
split( sMeasureString, "\n", asMeasureLines, true ); // ignore empty is important while( sSMNoteData.Find("//") != -1 )
//ASSERT( asMeasureLines.size() == 4 ||
// asMeasureLines.size() == 8 ||
// asMeasureLines.size() == 12 ||
// asMeasureLines.size() == 16 );
for( unsigned l=0; l<asMeasureLines.size(); l++ )
{ {
CString &sMeasureLine = asMeasureLines[l]; int iIndexCommentStart = sSMNoteData.Find("//");
TrimLeft(sMeasureLine); int iIndexCommentEnd = sSMNoteData.Find("\n", iIndexCommentStart);
TrimRight(sMeasureLine); if( iIndexCommentEnd == -1 ) // comment doesn't have an end?
sSMNoteData.erase( iIndexCommentStart, 2 );
else
sSMNoteData.erase( iIndexCommentStart, iIndexCommentEnd-iIndexCommentStart );
}
const float fPercentIntoMeasure = l/(float)asMeasureLines.size(); CStringArray asMeasures;
const float fBeat = (m + fPercentIntoMeasure) * BEATS_PER_MEASURE; split( sSMNoteData, ",", asMeasures, true ); // ignore empty is important
const int iIndex = BeatToNoteRow( fBeat ); for( unsigned m=0; m<asMeasures.size(); m++ ) // foreach measure
{
CString &sMeasureString = asMeasures[m];
TrimLeft(sMeasureString);
TrimRight(sMeasureString);
// if( m_iNumTracks != sMeasureLine.GetLength() ) CStringArray asMeasureLines;
// RageException::Throw( "Actual number of note columns (%d) is different from the StepsType (%d).", m_iNumTracks, sMeasureLine.GetLength() ); split( sMeasureString, "\n", asMeasureLines, true ); // ignore empty is important
for( int c=0; c<min(sMeasureLine.GetLength(),out.GetNumTracks()); c++ ) //ASSERT( asMeasureLines.size() == 4 ||
// asMeasureLines.size() == 8 ||
// asMeasureLines.size() == 12 ||
// asMeasureLines.size() == 16 );
for( unsigned l=0; l<asMeasureLines.size(); l++ )
{ {
TapNote t; CString &sMeasureLine = asMeasureLines[l];
switch(sMeasureLine[c]) TrimLeft(sMeasureLine);
{ TrimRight(sMeasureLine);
case '0': t = TAP_EMPTY; break;
case '1': t = TAP_TAP; break;
case '2': t = TAP_HOLD_HEAD; break;
case '3': t = TAP_HOLD_TAIL; break;
// case 'm':
// Don't be loose with the definition. Use only 'M' since
// that's what we've been writing to disk. -Chris
case 'M': t = TAP_MINE; break;
default: const float fPercentIntoMeasure = l/(float)asMeasureLines.size();
/* Invalid data. We don't want to assert, since there might const float fBeat = (m + fPercentIntoMeasure) * BEATS_PER_MEASURE;
* simply be invalid data in an .SM, and we don't want to die const int iIndex = BeatToNoteRow( fBeat );
* due to invalid data. We should probably check for this when
* we load SM data for the first time ... */ // if( m_iNumTracks != sMeasureLine.GetLength() )
// ASSERT(0); // RageException::Throw( "Actual number of note columns (%d) is different from the StepsType (%d).", m_iNumTracks, sMeasureLine.GetLength() );
t = TAP_EMPTY; break;
for( int c=0; c<min(sMeasureLine.GetLength(),out.GetNumTracks()); c++ )
{
TapNote t;
switch(sMeasureLine[c])
{
case '0': t = TAP_EMPTY; break;
case '1': t = TAP_TAP; break;
case '2': t = TAP_HOLD_HEAD; break;
case '3': t = TAP_HOLD_TAIL; break;
// case 'm':
// Don't be loose with the definition. Use only 'M' since
// that's what we've been writing to disk. -Chris
case 'M': t = TAP_MINE; break;
default:
if( sMeasureLine[c] >= 'a' && sMeasureLine[c] <= 'z' )
{
t = sMeasureLine[c];
}
else
{
/* Invalid data. We don't want to assert, since there might
* simply be invalid data in an .SM, and we don't want to die
* due to invalid data. We should probably check for this when
* we load SM data for the first time ... */
// ASSERT(0);
t = TAP_EMPTY;
}
break;
}
out.SetTapNote(c, iIndex, t);
} }
out.SetTapNote(c, iIndex, t);
} }
} }
out.Convert2sAnd3sToHoldNotes();
}
{
//
// Load attack data
//
CStringArray asLines;
split( sSMAttackData, ",", asLines, true );
for( int i=0; i<asLines.size(); i++ )
{
CString& sLine = asLines[i];
TrimLeft( sLine );
TrimRight( sLine );
if( sLine.empty() )
continue; // skip
CStringArray asBits;
split( sLine, "=", asBits, true );
if( asBits.size() < 3 )
continue;
if( asBits[0].empty() )
continue;
TapNote tn = asBits[0][0];
Attack attack;
attack.level = ATTACK_LEVEL_1;
attack.sModifier = asBits[1];
attack.sModifier.Replace( '.', ',' ); // we couldn't use comma here because the map item separator is a comma
attack.fSecsRemaining = atof( asBits[2] );
out.GetAttackMap()[tn] = attack;
}
} }
out.Convert2sAnd3sToHoldNotes();
} }
void NoteDataUtil::GetSMNoteDataString( const NoteData &in_, CString &out ) void NoteDataUtil::GetSMNoteDataString( const NoteData &in_, CString &notes_out, CString &attacks_out )
{ {
NoteData in;
in.To2sAnd3s( in_ );
float fLastBeat = in.GetLastBeat();
int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE );
CString &sRet = out;
sRet = "\n"; /* data begins on a new line when written to disk */
sRet.reserve( 1024*32 );
for( int m=0; m<=iLastMeasure; m++ ) // foreach measure
{ {
NoteType nt = GetSmallestNoteTypeForMeasure( in, m ); //
int iRowSpacing; // Get note data
if( nt == NOTE_TYPE_INVALID ) //
iRowSpacing = 1; NoteData in;
else in.To2sAnd3s( in_ );
iRowSpacing = int(roundf( NoteTypeToBeat(nt) * ROWS_PER_BEAT ));
sRet += ssprintf(" // measure %d\n", m+1); float fLastBeat = in.GetLastBeat();
int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE );
const int iMeasureStartRow = m * ROWS_PER_MEASURE; CString &sRet = notes_out;
const int iMeasureLastRow = (m+1) * ROWS_PER_MEASURE - 1;
for( int r=iMeasureStartRow; r<=iMeasureLastRow; r+=iRowSpacing ) sRet = "\n"; /* data begins on a new line when written to disk */
sRet.reserve( 1024*32 );
for( int m=0; m<=iLastMeasure; m++ ) // foreach measure
{ {
for( int t=0; t<in.GetNumTracks(); t++ ) NoteType nt = GetSmallestNoteTypeForMeasure( in, m );
int iRowSpacing;
if( nt == NOTE_TYPE_INVALID )
iRowSpacing = 1;
else
iRowSpacing = int(roundf( NoteTypeToBeat(nt) * ROWS_PER_BEAT ));
sRet += ssprintf(" // measure %d\n", m+1);
const int iMeasureStartRow = m * ROWS_PER_MEASURE;
const int iMeasureLastRow = (m+1) * ROWS_PER_MEASURE - 1;
for( int r=iMeasureStartRow; r<=iMeasureLastRow; r+=iRowSpacing )
{ {
TapNote tn = in.GetTapNote(t, r); for( int t=0; t<in.GetNumTracks(); t++ )
char c;
switch( tn )
{ {
case TAP_EMPTY: c = '0'; break; TapNote tn = in.GetTapNote(t, r);
case TAP_TAP: c = '1'; break; char c;
case TAP_HOLD_HEAD: c = '2'; break; switch( tn )
case TAP_HOLD_TAIL: c = '3'; break; {
case TAP_MINE: c = 'M'; break; case TAP_EMPTY: c = '0'; break;
default: ASSERT(0); c = '0'; break; case TAP_TAP: c = '1'; break;
case TAP_HOLD_HEAD: c = '2'; break;
case TAP_HOLD_TAIL: c = '3'; break;
case TAP_MINE: c = 'M'; break;
default:
if( IsTapAttack(tn) )
{
c = tn;
}
else
{
ASSERT(0);
c = '0';
}
break;
}
sRet.append(1, c);
} }
sRet.append(1, c);
sRet.append(1, '\n');
} }
sRet.append(1, '\n'); sRet.append(1, ',');
}
}
{
//
// Get attack data
//
CStringArray asLines;
for( map<TapNote,Attack>::const_iterator iter = in_.GetAttackMap().begin();
iter != in_.GetAttackMap().end();
iter++ )
{
TapNote tn = iter->first;
Attack attack = iter->second;
attack.sModifier.Replace( ',', '.' ); // comma is the map item separator
asLines.push_back( ssprintf("%c=%s=%f,\n", tn, attack.sModifier.c_str(), attack.fSecsRemaining) );
} }
sRet.append(1, ','); attacks_out = join( ",", asLines );
} }
} }
+2 -2
View File
@@ -27,8 +27,8 @@ struct PlayerOptions;
namespace NoteDataUtil namespace NoteDataUtil
{ {
NoteType GetSmallestNoteTypeForMeasure( const NoteData &n, int iMeasureIndex ); NoteType GetSmallestNoteTypeForMeasure( const NoteData &n, int iMeasureIndex );
void LoadFromSMNoteDataString( NoteData &out, CString sSMNoteData ); void LoadFromSMNoteDataString( NoteData &out, CString sSMNoteData, CString sSMAttackData );
void GetSMNoteDataString( const NoteData &in, CString &out ); void GetSMNoteDataString( const NoteData &in, CString &notes_out, CString &attacks_out );
float GetStreamRadarValue( const NoteData &in, float fSongSeconds ); float GetStreamRadarValue( const NoteData &in, float fSongSeconds );
float GetVoltageRadarValue( const NoteData &in, float fSongSeconds ); float GetVoltageRadarValue( const NoteData &in, float fSongSeconds );
+5 -4
View File
@@ -14,6 +14,7 @@ void SMLoader::LoadFromSMTokens(
CString sMeter, CString sMeter,
CString sRadarValues, CString sRadarValues,
CString sNoteData, CString sNoteData,
CString sAttackData,
Steps &out Steps &out
) )
{ {
@@ -47,7 +48,7 @@ void SMLoader::LoadFromSMTokens(
for( int r=0; r<NUM_RADAR_CATEGORIES; r++ ) for( int r=0; r<NUM_RADAR_CATEGORIES; r++ )
out.SetRadarValue(r, (float)atof(saValues[r])); out.SetRadarValue(r, (float)atof(saValues[r]));
out.SetSMNoteData(sNoteData); out.SetSMNoteData(sNoteData, sAttackData);
out.TidyUpData(); out.TidyUpData();
} }
@@ -277,14 +278,14 @@ bool SMLoader::LoadFromSMFile( CString sPath, Song &out )
ASSERT( pNewNotes ); ASSERT( pNewNotes );
out.m_apNotes.push_back( pNewNotes ); out.m_apNotes.push_back( pNewNotes );
if( iNumParams != 7 ) if( iNumParams < 7 )
{ {
LOG->Trace( "The song file '%s' is has %d fields in a #NOTES tag, but should have %d.", sPath.c_str(), iNumParams, 7 ); LOG->Trace( "The song file '%s' is has %d fields in a #NOTES tag, but should have at least %d.", sPath.c_str(), iNumParams, 7 );
continue; continue;
} }
LoadFromSMTokens( LoadFromSMTokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], (iNumParams>=8)?sParams[7]:"",
*pNewNotes); *pNewNotes);
} }
else else
+1
View File
@@ -13,6 +13,7 @@ class SMLoader: public NotesLoader {
CString sMeter, CString sMeter,
CString sRadarValues, CString sRadarValues,
CString sNoteData, CString sNoteData,
CString sAttackData,
Steps &out); Steps &out);
bool FromCache; bool FromCache;
+10 -1
View File
@@ -109,7 +109,16 @@ void NotesWriterSM::WriteSMNotesTag( const Steps &in, FILE* fp )
* newline and they'll accumulate. */ * newline and they'll accumulate. */
fprintf( fp, " %s:", join(",",asRadarValues).c_str() ); fprintf( fp, " %s:", join(",",asRadarValues).c_str() );
fprintf( fp, "%s;\n", in.GetSMNoteData().c_str() ); CString sNoteData;
CString sAttackData;
in.GetSMNoteData( sNoteData, sAttackData );
fprintf( fp, "%s", sNoteData.c_str() );
if( sAttackData.empty() )
fprintf( fp, ";\n" );
else
fprintf( fp, ":\n%s;\n", sAttackData.c_str() );
} }
bool NotesWriterSM::Write(CString sPath, const Song &out, bool bSavingCache) bool NotesWriterSM::Write(CString sPath, const Song &out, bool bSavingCache)
+1 -1
View File
@@ -1282,7 +1282,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM )
attack.fSecsRemaining = g_fLastInsertAttackDurationSeconds; attack.fSecsRemaining = g_fLastInsertAttackDurationSeconds;
attack.sModifier = sMods; attack.sModifier = sMods;
m_NoteFieldEdit.AddAttackNote( g_iLastInsertAttackTrack, iSongIndex, attack ); m_NoteFieldEdit.SetTapAttackNote( g_iLastInsertAttackTrack, iSongIndex, attack );
GAMESTATE->RestoreSelectedOptions(); // restore the edit and playback options GAMESTATE->RestoreSelectedOptions(); // restore the edit and playback options
} }
break; break;
+5 -1
View File
@@ -550,7 +550,11 @@ static void DeleteDuplicateSteps( Song *song, vector<Steps*> &vSteps )
if( s1->GetMeter() != s2->GetMeter() ) if( s1->GetMeter() != s2->GetMeter() )
continue; continue;
/* Compare, ignoring whitespace. */ /* Compare, ignoring whitespace. */
if( RemoveInitialWhitespace(s1->GetSMNoteData()) != RemoveInitialWhitespace(s2->GetSMNoteData()) ) CString sSMNoteData1, sSMAttackData1;
s1->GetSMNoteData( sSMNoteData1, sSMAttackData1 );
CString sSMNoteData2, sSMAttackData2;
s2->GetSMNoteData( sSMNoteData2, sSMAttackData2 );
if( RemoveInitialWhitespace(sSMNoteData1) != RemoveInitialWhitespace(sSMNoteData2) )
continue; continue;
LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\" and meter \"%i\"", LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\" and meter \"%i\"",
+19 -11
View File
@@ -79,27 +79,35 @@ void Steps::GetNoteData( NoteData* pNoteDataOut ) const
} }
} }
void Steps::SetSMNoteData( const CString &out ) void Steps::SetSMNoteData( const CString &notes_comp_, const CString &attacks_comp_ )
{ {
delete notes; delete notes;
notes = NULL; notes = NULL;
if(!notes_comp) if(!notes_comp)
notes_comp = new CString; notes_comp = new CompressedNoteData;
*notes_comp = out; notes_comp->notes = notes_comp_;
notes_comp->attacks = attacks_comp_;
} }
CString Steps::GetSMNoteData() const void Steps::GetSMNoteData( CString &notes_comp_out, CString &attacks_comp_out ) const
{ {
if(!notes_comp) if(!notes_comp)
{ {
if(!notes) return ""; /* no data is no data */ if(!notes)
notes_comp = new CString; {
NoteDataUtil::GetSMNoteDataString( *notes, *notes_comp ); /* no data is no data */
notes_comp_out = attacks_comp_out = "";
return;
}
notes_comp = new CompressedNoteData;
NoteDataUtil::GetSMNoteDataString( *notes, notes_comp->notes, notes_comp->attacks );
} }
return *notes_comp; notes_comp_out = notes_comp->notes;
attacks_comp_out = notes_comp->attacks;
} }
void Steps::TidyUpData() void Steps::TidyUpData()
@@ -177,7 +185,7 @@ void Steps::Decompress() const
notes = new NoteData; notes = new NoteData;
notes->SetNumTracks( GameManager::NotesTypeToNumTracks(m_StepsType) ); notes->SetNumTracks( GameManager::NotesTypeToNumTracks(m_StepsType) );
NoteDataUtil::LoadFromSMNoteDataString(*notes, *notes_comp); NoteDataUtil::LoadFromSMNoteDataString(*notes, notes_comp->notes, notes_comp->attacks );
} }
} }
@@ -186,8 +194,8 @@ void Steps::Compress() const
if(!notes_comp) if(!notes_comp)
{ {
if(!notes) return; /* no data is no data */ if(!notes) return; /* no data is no data */
notes_comp = new CString; notes_comp = new CompressedNoteData;
NoteDataUtil::GetSMNoteDataString( *notes, *notes_comp ); NoteDataUtil::GetSMNoteDataString( *notes, notes_comp->notes, notes_comp->attacks );
} }
delete notes; delete notes;
+10 -6
View File
@@ -45,12 +45,12 @@ public:
bool IsAutogen() const; // Was created by autogen? bool IsAutogen() const; // Was created by autogen?
StepsType m_StepsType; StepsType m_StepsType;
void GetNoteData( NoteData* pNoteDataOut ) const; void GetNoteData( NoteData* pNoteDataOut ) const;
void SetNoteData( const NoteData* pNewNoteData ); void SetNoteData( const NoteData* pNewNoteData );
void SetSMNoteData( const CString &out ); void SetSMNoteData( const CString &notes_comp, const CString &attacks_comp );
CString GetSMNoteData() const; void GetSMNoteData( CString &notes_comp_out, CString &attacks_comp_out ) const;
struct MemCardData struct MemCardData
@@ -115,7 +115,11 @@ protected:
* Call Compress() to force us to only have notes_comp; otherwise, creation of * Call Compress() to force us to only have notes_comp; otherwise, creation of
* these is transparent. */ * these is transparent. */
mutable NoteData *notes; mutable NoteData *notes;
mutable CString *notes_comp; struct CompressedNoteData
{
CString notes, attacks;
};
mutable CompressedNoteData *notes_comp;
const Steps *Real() const; const Steps *Real() const;