diff --git a/Docs/Changelog_SSCformat.txt b/Docs/Changelog_SSCformat.txt index 721673f31d..75c9fe55ec 100644 --- a/Docs/Changelog_SSCformat.txt +++ b/Docs/Changelog_SSCformat.txt @@ -9,6 +9,10 @@ change to JSON, but it is unsure if this will be done. Implement .ssc at your own risk. ________________________________________________________________________________ +[v0.77] - Wolfman2000 +* Add the cache tag #STEPFILENAME to gain access to the proper #NOTES when + required. + [v0.76] - Wolfman2000 * Expand the #COMBOS tag to allow for Miss Combos. Anyone that is crazy enough to put in a 51 miss combo in their tags should be...oh wait: it's already diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 7f632cfb55..6887ae8504 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -8,6 +8,13 @@ ________________________________________________________________________________ StepMania 5.0 ????????? | 20110??? -------------------------------------------------------------------------------- +2011/07/17 +---------- +* [Cache] No longer store #NOTES in the cache. This should speed things up + on the first load if there is not much to write. (General note: this was + taken from another branch, but results at that point were great.) + [Wolfman2000] + 2011/07/13 ---------- * [Difficulty] Allow ALL StepsTypes to use CustomDifficulty properly. diff --git a/Docs/SimfileFormats/ssc_msd5.txt b/Docs/SimfileFormats/ssc_msd5.txt index 07ae7177ed..b8e5a2aea3 100644 --- a/Docs/SimfileFormats/ssc_msd5.txt +++ b/Docs/SimfileFormats/ssc_msd5.txt @@ -25,15 +25,15 @@ #SAMPLELENGTH:; #DISPLAYBPM:[xxx][xxx:xxx]|[*]; #SELECTABLE:; -#LASTBEATHINT:; +#LASTSECONDHINT:; #BGCHANGES:; #FGCHANGES:; #KEYSOUNDS:; #ATTACKS:; // stored in cache -#FIRSTBEAT:; // calculated -#LASTBEAT:; // calculated +#FIRSTSECOND:; // calculated +#LASTSECOND:; // calculated #SONGFILENAME:; #MUSICBYTES:; // ignored by loader #HASMUSIC:; @@ -66,5 +66,8 @@ #ATTACKS:; #DISPLAYBPM:; -// actual step data +// only in cache files +#STEPFILENAME:; + +// actual step data (not in cache files) #NOTES:; diff --git a/src/NotesLoaderBMS.cpp b/src/NotesLoaderBMS.cpp index b8edfde57e..fdaf8ea92c 100644 --- a/src/NotesLoaderBMS.cpp +++ b/src/NotesLoaderBMS.cpp @@ -459,13 +459,16 @@ static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map< * Do a search. Don't do a wildcard search; if sData is "song.wav", * we might also have "song.png", which we shouldn't match. */ RString nData = nDataOriginal; - if( !IsAFile(out.GetSongDir()+nData) ) + RString dir = out.GetSongDir(); + if (dir.empty()) + dir = Dirname(sPath); + if( !IsAFile(dir+nData) ) { const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere for( unsigned i = 0; exts[i] != NULL; ++i ) { RString fn = SetExtension( nData, exts[i] ); - if( IsAFile(out.GetSongDir()+fn) ) + if( IsAFile(dir+fn) ) { nData = fn; break; @@ -473,9 +476,9 @@ static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map< } } - if( !IsAFile(out.GetSongDir()+nData) ) + if( !IsAFile(dir+nData) ) { - LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() ); + LOG->UserLog( "Song file", dir, "references key \"%s\" that can't be found", nData.c_str() ); return false; } @@ -1067,6 +1070,75 @@ void BMSLoader::GetApplicableFiles( const RString &sPath, vector &out ) GetDirListing( sPath + RString("*.bml"), out ); } +bool BMSLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps & out ) +{ + Song dummy; + // TODO: Simplify this copy/paste from LoadFromDir. + + vector BMSData; + BMSData.push_back(NameToData_t()); + ReadBMSFile(cachePath, BMSData.back()); + + RString commonSubstring; + GetCommonTagFromMapList( BMSData, "#title", commonSubstring ); + + Steps *copy = dummy.CreateSteps(); + + copy->SetDifficulty( Difficulty_Medium ); + RString sTag; + if( GetTagFromMap( BMSData[0], "#title", sTag ) && sTag.size() != commonSubstring.size() ) + { + sTag = sTag.substr( commonSubstring.size(), sTag.size() - commonSubstring.size() ); + sTag.MakeLower(); + + if( sTag.find('l') != sTag.npos ) + { + unsigned lPos = sTag.find('l'); + if( lPos > 2 && sTag.substr(lPos-2,4) == "solo" ) + { + copy->SetDifficulty( Difficulty_Edit ); + } + else + { + copy->SetDifficulty( Difficulty_Easy ); + } + } + else if( sTag.find('a') != sTag.npos ) + copy->SetDifficulty( Difficulty_Hard ); + else if( sTag.find('b') != sTag.npos ) + copy->SetDifficulty( Difficulty_Beginner ); + } + if( commonSubstring == "" ) + { + copy->SetDifficulty(Difficulty_Medium); + RString sTag; + if (GetTagFromMap(BMSData[0], "#title#", sTag)) + SearchForDifficulty(sTag, copy); + } + ReadGlobalTags( BMSData[0], dummy ); + if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' ) + { + switch( commonSubstring[commonSubstring.size() - 1] ) + { + case '[': + case '(': + case '<': + commonSubstring = commonSubstring.substr(0, commonSubstring.size() - 2); + default: + break; + } + } + map mapFilenameToKeysoundIndex; + + + const bool ok = LoadFromBMSFile( cachePath, BMSData[0], *copy, dummy, mapFilenameToKeysoundIndex ); + if( ok ) + { + out.SetNoteData(copy->GetNoteData()); + } + return ok; +} + bool BMSLoader::LoadFromDir( const RString &sDir, Song &out ) { LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() ); @@ -1171,6 +1243,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out ) iMainDataIndex = i; ReadGlobalTags( aBMSData[iMainDataIndex], out ); + out.m_sSongFileName = out.GetSongDir() + arrayBMSFileNames[iMainDataIndex]; // The brackets before the difficulty are in common substring, so remove them if it's found. if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' ) @@ -1203,6 +1276,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out ) if( i == static_cast(iMainDataIndex) ) out.m_SongTiming = pNewNotes->m_Timing; + pNewNotes->SetFilename(out.GetSongDir() + arrayBMSFileNames[i]); out.AddSteps( pNewNotes ); } else diff --git a/src/NotesLoaderBMS.h b/src/NotesLoaderBMS.h index caf8372d64..caf124f07b 100644 --- a/src/NotesLoaderBMS.h +++ b/src/NotesLoaderBMS.h @@ -2,11 +2,13 @@ #define NOTES_LOADER_BMS_H class Song; +class Steps; /** @brief Reads a Song from a set of .BMS files. */ namespace BMSLoader { void GetApplicableFiles( const RString &sPath, vector &out ); bool LoadFromDir( const RString &sDir, Song &out ); + bool LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ); } #endif diff --git a/src/NotesLoaderDWI.cpp b/src/NotesLoaderDWI.cpp index 9d1f2fc9b6..242108bbe3 100644 --- a/src/NotesLoaderDWI.cpp +++ b/src/NotesLoaderDWI.cpp @@ -170,6 +170,238 @@ Difficulty DwiCompatibleStringToDifficulty( const RString& sDC ) else return Difficulty_Invalid; } +static StepsType GetTypeFromMode(const RString &mode) +{ + if( mode == "SINGLE" ) + return StepsType_dance_single; + else if( mode == "DOUBLE" ) + return StepsType_dance_double; + else if( mode == "COUPLE" ) + return StepsType_dance_couple; + else if( mode == "SOLO" ) + return StepsType_dance_solo; + ASSERT_M(0, "Unrecognized DWI notes format " + mode + "!"); + return StepsType_Invalid; // just in case. +} + +static NoteData ParseNoteData(RString &step1, RString &step2, + Steps &out, const RString &path) +{ + g_mapDanceNoteToNoteDataColumn.clear(); + switch( out.m_StepsType ) + { + case StepsType_dance_single: + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; + break; + case StepsType_dance_double: + case StepsType_dance_couple: + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; + break; + case StepsType_dance_solo: + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; + g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; + break; + DEFAULT_FAIL( out.m_StepsType ); + } + + NoteData newNoteData; + newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() ); + + for( int pad=0; pad<2; pad++ ) // foreach pad + { + RString sStepData; + switch( pad ) + { + case 0: + sStepData = step1; + break; + case 1: + if( step2 == "" ) // no data + continue; // skip + sStepData = step2; + break; + DEFAULT_FAIL( pad ); + } + + sStepData.Replace("\n", ""); + sStepData.Replace("\r", ""); + sStepData.Replace("\t", ""); + sStepData.Replace(" ", ""); + + double fCurrentBeat = 0; + double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; + + for( size_t i=0; i': + fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; + break; + + default: // this is a note character + { + if( c == '!' ) + { + LOG->UserLog( + "Song file", + path, + "has an unexpected character: '!'." ); + continue; + } + + bool jump = false; + if( c == '<' ) + { + /* Arr. Is this a jump or a 1/192 marker? */ + if( Is192( sStepData, i ) ) + { + fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; + break; + } + + /* It's a jump. + * We need to keep reading notes until we hit a >. */ + jump = true; + i++; + } + + const int iIndex = BeatToNoteRow( (float)fCurrentBeat ); + i--; + do { + c = sStepData[i++]; + + if( jump && c == '>' ) + break; + + int iCol1, iCol2; + DWIcharToNoteCol( + c, + (GameController)pad, + iCol1, + iCol2, + path ); + + if( iCol1 != -1 ) + newNoteData.SetTapNote(iCol1, + iIndex, + TAP_ORIGINAL_TAP); + if( iCol2 != -1 ) + newNoteData.SetTapNote(iCol2, + iIndex, + TAP_ORIGINAL_TAP); + + if(i>=sStepData.length()) + { + break; + //we ran out of data + //while looking for the ending > mark + } + + if( sStepData[i] == '!' ) + { + i++; + const char holdChar = sStepData[i++]; + + DWIcharToNoteCol(holdChar, + (GameController)pad, + iCol1, + iCol2, + path ); + + if( iCol1 != -1 ) + newNoteData.SetTapNote(iCol1, + iIndex, + TAP_ORIGINAL_HOLD_HEAD); + if( iCol2 != -1 ) + newNoteData.SetTapNote(iCol2, + iIndex, + TAP_ORIGINAL_HOLD_HEAD); + } + } + while( jump ); + fCurrentBeat += fCurrentIncrementer; + } + break; + } + } + } + + /* Fill in iDuration. */ + for( int t=0; tUserLog("Song file", + path, + "failed to close a hold note in \"%s\" on track %i", + DifficultyToString(out.GetDifficulty()).c_str(), + t); + + newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY ); + } + } + } + + ASSERT( newNoteData.GetNumTracks() > 0 ); + return newNoteData; +} + /** * @brief Look through the notes tag to extract the data. * @param sMode the steps type. @@ -192,216 +424,17 @@ static bool LoadFromDWITokens( { CHECKPOINT_M( "DWILoader::LoadFromDWITokens()" ); - out.m_StepsType = StepsType_Invalid; + out.m_StepsType = GetTypeFromMode(sMode); - if( sMode == "SINGLE" ) out.m_StepsType = StepsType_dance_single; - else if( sMode == "DOUBLE" ) out.m_StepsType = StepsType_dance_double; - else if( sMode == "COUPLE" ) out.m_StepsType = StepsType_dance_couple; - else if( sMode == "SOLO" ) out.m_StepsType = StepsType_dance_solo; - else - { - ASSERT_M(0, "Unrecognized DWI notes format " + sMode + "!"); - out.m_StepsType = StepsType_dance_single; - } + out.SetMeter(StringToInt(sNumFeet)); - - g_mapDanceNoteToNoteDataColumn.clear(); - switch( out.m_StepsType ) - { - case StepsType_dance_single: - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; - break; - case StepsType_dance_double: - case StepsType_dance_couple: - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; - break; - case StepsType_dance_solo: - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; - g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; - break; - DEFAULT_FAIL( out.m_StepsType ); - } - - int iNumFeet = StringToInt(sNumFeet); - // out.SetDescription(sDescription); // Don't put garbage in the description. - out.SetMeter(iNumFeet); out.SetDifficulty( DwiCompatibleStringToDifficulty(sDescription) ); - - NoteData newNoteData; - newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() ); - - for( int pad=0; pad<2; pad++ ) // foreach pad - { - RString sStepData; - switch( pad ) - { - case 0: - sStepData = sStepData1; - break; - case 1: - if( sStepData2 == "" ) // no data - continue; // skip - sStepData = sStepData2; - break; - DEFAULT_FAIL( pad ); - } - - sStepData.Replace("\n", ""); - sStepData.Replace("\r", ""); - sStepData.Replace("\t", ""); - sStepData.Replace(" ", ""); - - double fCurrentBeat = 0; - double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; - - for( size_t i=0; i': - fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; - break; - - default: // this is a note character - { - if( c == '!' ) - { - LOG->UserLog( "Song file", sPath, "has an unexpected character: '!'." ); - continue; - } - - bool jump = false; - if( c == '<' ) - { - /* Arr. Is this a jump or a 1/192 marker? */ - if( Is192( sStepData, i ) ) - { - fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; - break; - } - - /* It's a jump. We need to keep reading notes until we hit a >. */ - jump = true; - i++; - } - - const int iIndex = BeatToNoteRow( (float)fCurrentBeat ); - i--; - do { - c = sStepData[i++]; - - if( jump && c == '>' ) - break; - - int iCol1, iCol2; - DWIcharToNoteCol( c, (GameController)pad, iCol1, iCol2, sPath ); - - if( iCol1 != -1 ) - newNoteData.SetTapNote(iCol1, iIndex, TAP_ORIGINAL_TAP); - if( iCol2 != -1 ) - newNoteData.SetTapNote(iCol2, iIndex, TAP_ORIGINAL_TAP); - - if(i>=sStepData.length()) { - break;//we ran out of data while looking for the ending > mark - } - - if( sStepData[i] == '!' ) - { - i++; - const char holdChar = sStepData[i++]; - - DWIcharToNoteCol( holdChar, (GameController)pad, iCol1, iCol2, sPath ); - - if( iCol1 != -1 ) - newNoteData.SetTapNote(iCol1, iIndex, TAP_ORIGINAL_HOLD_HEAD); - if( iCol2 != -1 ) - newNoteData.SetTapNote(iCol2, iIndex, TAP_ORIGINAL_HOLD_HEAD); - } - } - while( jump ); - fCurrentBeat += fCurrentIncrementer; - } - break; - } - } - } - - /* Fill in iDuration. */ - for( int t=0; tUserLog( "Song file", sPath, "failed to close a hold note in \"%s\" on track %i", - sDescription.c_str(), t ); - - newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY ); - } - } - } - - ASSERT( newNoteData.GetNumTracks() > 0 ); - - out.SetNoteData( newNoteData ); + + out.SetNoteData( ParseNoteData(sStepData1, sStepData2, out, sPath) ); out.TidyUpData(); out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved - return true; } @@ -444,17 +477,55 @@ void DWILoader::GetApplicableFiles( const RString &sPath, vector &out ) GetDirListing( sPath + RString("*.dwi"), out ); } +bool DWILoader::LoadNoteDataFromSimfile( const RString &path, Steps &out ) +{ + MsdFile msd; + if( !msd.ReadFile( path, false ) ) // don't unescape + { + LOG->UserLog("Song file", + path, + "couldn't be opened: %s", + msd.GetError().c_str() ); + return false; + } + + for( unsigned i=0; i &BlacklistedImages ) { vector aFileNames; GetApplicableFiles( sPath_, aFileNames ); - + if( aFileNames.size() > 1 ) { LOG->UserLog( "Song", sPath_, "has more than one DWI file. There should be only one!" ); return false; } - + /* We should have exactly one; if we had none, we shouldn't have been called to begin with. */ ASSERT( aFileNames.size() == 1 ); const RString sPath = sPath_ + aFileNames[0]; @@ -468,6 +539,8 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set &Bla return false; } + out.m_sSongFileName = sPath; + for( unsigned i=0; i &Bla sPath ); if( pNewNotes->m_StepsType != StepsType_Invalid ) + { + pNewNotes->SetFilename( sPath ); out.AddSteps( pNewNotes ); + } else delete pNewNotes; } @@ -664,7 +740,6 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set &Bla // do nothing. We don't care about this value name } } - out.TidyUpData(); return true; } diff --git a/src/NotesLoaderDWI.h b/src/NotesLoaderDWI.h index 1aa9e729c4..c121d0dc28 100644 --- a/src/NotesLoaderDWI.h +++ b/src/NotesLoaderDWI.h @@ -6,6 +6,7 @@ #include class Song; +class Steps; /** @brief The DWILoader handles parsing the .dwi file. */ namespace DWILoader @@ -24,6 +25,8 @@ namespace DWILoader * @return its success or failure. */ bool LoadFromDir( const RString &sPath, Song &out, set &BlacklistedImages ); + + bool LoadNoteDataFromSimfile( const RString &path, Steps &out ); } #endif diff --git a/src/NotesLoaderKSF.cpp b/src/NotesLoaderKSF.cpp index 0e078bb7ad..fdd93670c5 100644 --- a/src/NotesLoaderKSF.cpp +++ b/src/NotesLoaderKSF.cpp @@ -195,28 +195,43 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, out.SetDifficulty( Difficulty_Edit ); if( !out.GetMeter() ) out.SetMeter( 25 ); } - else if( sFName.find("wild") != string::npos || sFName.find("wd") != string::npos || sFName.find("crazy+") != string::npos || sFName.find("cz+") != string::npos || sFName.find("hardcore") != string::npos ) + else if(sFName.find("wild") != string::npos || + sFName.find("wd") != string::npos || + sFName.find("crazy+") != string::npos || + sFName.find("cz+") != string::npos || + sFName.find("hardcore") != string::npos ) { out.SetDifficulty( Difficulty_Challenge ); if( !out.GetMeter() ) out.SetMeter( 20 ); } - else if( sFName.find("crazy") != string::npos || sFName.find("cz") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("nm") != string::npos || sFName.find("crazydouble") != string::npos ) + else if(sFName.find("crazy") != string::npos || + sFName.find("cz") != string::npos || + sFName.find("nightmare") != string::npos || + sFName.find("nm") != string::npos || + sFName.find("crazydouble") != string::npos ) { out.SetDifficulty( Difficulty_Hard ); if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR. } - else if( sFName.find("hard") != string::npos || sFName.find("hd") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("fs") != string::npos || sFName.find("double") != string::npos ) + else if(sFName.find("hard") != string::npos || + sFName.find("hd") != string::npos || + sFName.find("freestyle") != string::npos || + sFName.find("fs") != string::npos || + sFName.find("double") != string::npos ) { out.SetDifficulty( Difficulty_Medium ); if( !out.GetMeter() ) out.SetMeter( 8 ); } - else if( sFName.find("easy") != string::npos || sFName.find("ez") != string::npos || sFName.find("normal") != string::npos ) + else if(sFName.find("easy") != string::npos || + sFName.find("ez") != string::npos || + sFName.find("normal") != string::npos ) { // I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster out.SetDifficulty( Difficulty_Easy ); if( !out.GetMeter() ) out.SetMeter( 4 ); } - else if( sFName.find("beginner") != string::npos || sFName.find("practice") != string::npos || sFName.find("pr") != string::npos ) + else if(sFName.find("beginner") != string::npos || + sFName.find("practice") != string::npos || sFName.find("pr") != string::npos ) { out.SetDifficulty( Difficulty_Beginner ); if( !out.GetMeter() ) out.SetMeter( 4 ); @@ -230,10 +245,18 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, out.m_StepsType = StepsType_pump_single; // Check for "halfdouble" before "double". - if( sFName.find("halfdouble") != string::npos || sFName.find("half-double") != string::npos || sFName.find("h_double") != string::npos || sFName.find("hdb") != string::npos ) + if(sFName.find("halfdouble") != string::npos || + sFName.find("half-double") != string::npos || + sFName.find("h_double") != string::npos || + sFName.find("hdb") != string::npos ) out.m_StepsType = StepsType_pump_halfdouble; // Handle bDoublesChart from above as well. -aj - else if( sFName.find("double") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("db") != string::npos || sFName.find("nm") != string::npos || sFName.find("fs") != string::npos || bDoublesChart ) + else if(sFName.find("double") != string::npos || + sFName.find("nightmare") != string::npos || + sFName.find("freestyle") != string::npos || + sFName.find("db") != string::npos || + sFName.find("nm") != string::npos || + sFName.find("fs") != string::npos || bDoublesChart ) out.m_StepsType = StepsType_pump_double; else if( sFName.find("_1") != string::npos ) out.m_StepsType = StepsType_pump_single; @@ -280,7 +303,10 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) ) notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); else - notedata.AddHoldNote( t, iHoldStartRow[t], BeatToNoteRow(prevBeat) , TAP_ORIGINAL_HOLD_HEAD ); + notedata.AddHoldNote(t, + iHoldStartRow[t], + BeatToNoteRow(prevBeat), + TAP_ORIGINAL_HOLD_HEAD ); } } break; @@ -307,53 +333,6 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, { // duh iTickCount = static_cast(numTemp); - //if( iTickCount > ROWS_PER_BEAT ) - - /* adapt tickcounts // - // valid tickcounts for SM: 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 64, ROWS_PER_BEAT - // put this inside the tickcount handling condition yes/no - - if( iTickCount > ROWS_PER_BEAT ) // beyond 48 - { - // clamp/scale/whatever and use scroll segments - } - else if( iTickCount > 32 || iTickCount < ROWS_PER_BEAT ) // ranging from 33 to 48, approximate to 32 - { - fScrollRatio = 32; - iTickCount = 32; - } - else if( iTickCount > 24 || iTickCount < 32 ) // ranging from 25 to 31, approximate to 24 - { - fScrollRatio = 24; - iTickCount = 24; - } - else if( iTickCount > 16 || iTickCount < 24 ) // ranging from 17 to 23, approximate to 16 - { - fScrollRatio = 16; - iTickCount = 16; - } - else if( iTickCount > 12 || iTickCount < 16 ) // ranging from 13 to 15, approximate to 12 - { - fScrollRatio = 12; - iTickCount = 12; - } - else if( iTickCount > 8 || iTickCount < 12 ) // ranging from 9 to 11, approximate to 8 - { - fScrollRatio = 8; - iTickCount = 8; - } - else if( iTickCount > 6 || iTickCount < 8 ) // 7, approximate to 6 - { - fScrollRatio = 6 / iTickCountt; - iTickCount = 6; - } - else if( iTickCount > 4 || iTickCount < 6 ) // 5, approximate to 4 - { - fScrollRatio = iTickCount / 4; - iTickCount = 4; - } - //*/ - stepsTiming.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) ); } else if (BeginsWith(sRowString, "|B")) @@ -516,7 +495,21 @@ static void LoadTags( const RString &str, Song &out ) out.m_sArtist = artist; } -static bool LoadGlobalData( const RString &sPath, Song &out, bool bKIUCompliant ) +static void ProcessTickcounts( const RString & value, int & ticks, TimingData & timing ) +{ + /* TICKCOUNT will be used below if there are DM compliant BPM changes + * and stops. It will be called again in LoadFromKSFFile for the + * actual steps. */ + ticks = StringToInt( value ); + ticks = ticks > 0 ? ticks : 4; + // add a tickcount for those using the [Player] + // CheckpointsUseTimeSignatures metric. -aj + // It's not with timesigs now -DaisuMaster + TickcountSegment * tcs = new TickcountSegment(0, ticks > ROWS_PER_BEAT ? ROWS_PER_BEAT : ticks); + timing.AddSegment( SEGMENT_TICKCOUNT, tcs ); +} + +static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant ) { MsdFile msd; if( !msd.ReadFile( sPath, false ) ) // don't unescape @@ -594,17 +587,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool bKIUCompliant } else if ( sValueName=="TICKCOUNT" ) { - /* TICKCOUNT will be used below if there are DM compliant BPM changes - * and stops. It will be called again in LoadFromKSFFile for the - * actual steps. */ - iTickCount = StringToInt( sParams[1] ); - iTickCount = iTickCount > 0 ? iTickCount : 4; - // add a tickcount for those using the [Player] - // CheckpointsUseTimeSignatures metric. -aj - // It's not with timesigs now -DaisuMaster - TickcountSegment * tcs = new TickcountSegment(0); - tcs->SetTicks(iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount); - out.m_SongTiming.AddSegment( SEGMENT_TICKCOUNT, tcs ); + ProcessTickcounts(sParams[1], iTickCount, out.m_SongTiming); } else if ( sValueName=="STEP" ) { @@ -689,7 +672,6 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool bKIUCompliant } // This is where the DMRequired test will take place. - //if (BeginsWith(NoteRowString, "|T") || BeginsWith(NoteRowString, "|B") || BeginsWith(NoteRowString, "|D") || BeginsWith(NoteRowString, "|E") ) if ( BeginsWith( NoteRowString, "|" ) ) { // have a static timing for everything @@ -723,6 +705,22 @@ void KSFLoader::GetApplicableFiles( const RString &sPath, vector &out ) GetDirListing( sPath + RString("*.ksf"), out ); } +bool KSFLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) +{ + bool KIUCompliant = false; + Song dummy; + if (!LoadGlobalData(cachePath, dummy, KIUCompliant)) + return false; + Steps *notes = dummy.CreateSteps(); + if (LoadFromKSFFile(cachePath, *notes, dummy, KIUCompliant)) + { + KIUCompliant = true; // yeah, reusing a variable. + out.SetNoteData(notes->GetNoteData()); + } + delete notes; + return KIUCompliant; +} + bool KSFLoader::LoadFromDir( const RString &sDir, Song &out ) { LOG->Trace( "KSFLoader::LoadFromDir(%s)", sDir.c_str() ); @@ -733,7 +731,7 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out ) // We shouldn't have been called to begin with if there were no KSFs. ASSERT( arrayKSFFileNames.size() ); - //bool bKIUCompliant = false; + bool bKIUCompliant = false; /* With Split Timing, there has to be a backup Song Timing in case * anything goes wrong. As these files are kept in alphabetical * order (hopefully), it is best to use the LAST file for timing @@ -751,22 +749,24 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out ) // for directmove though, and we're just gathering basic info anyway, and // most of the time all the KSF files have the same info in the #TITLE:; section unsigned files = arrayKSFFileNames.size(); - if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[0], out, false) ) + RString dir = out.GetSongDir(); + if( !LoadGlobalData(dir + arrayKSFFileNames[files - 1], out, bKIUCompliant) ) return false; + out.m_sSongFileName = dir + arrayKSFFileNames[files - 1]; // load the Steps from the rest of the KSF files for( unsigned i=0; iSetFilename(dir + arrayKSFFileNames[i]); out.AddSteps( pNewNotes ); } - out.TidyUpData(); + out.TidyUpData(false, true); return true; } diff --git a/src/NotesLoaderKSF.h b/src/NotesLoaderKSF.h index b18e94a1e9..650f7aa44f 100644 --- a/src/NotesLoaderKSF.h +++ b/src/NotesLoaderKSF.h @@ -2,11 +2,13 @@ #define NOTES_LOADER_KSF_H class Song; +class Steps; /** @brief Reads a Song from a set of .KSF files. */ namespace KSFLoader { void GetApplicableFiles( const RString &sPath, vector &out ); bool LoadFromDir( const RString &sDir, Song &out ); + bool LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ); } #endif diff --git a/src/NotesLoaderMidi.cpp b/src/NotesLoaderMidi.cpp index d34e5434a6..e1cdabe15d 100644 --- a/src/NotesLoaderMidi.cpp +++ b/src/NotesLoaderMidi.cpp @@ -956,7 +956,7 @@ bool MidiLoader::LoadFromDir( const RString &sDir, Song &out ) if( !LoadFromMidi(sDir+vsFiles[0], out) ) return false; - out.TidyUpData(); + out.TidyUpData(false, true); return true; } diff --git a/src/NotesLoaderPMS.cpp b/src/NotesLoaderPMS.cpp index 301b843379..ca4c672091 100644 --- a/src/NotesLoaderPMS.cpp +++ b/src/NotesLoaderPMS.cpp @@ -573,7 +573,7 @@ static bool LoadFromPMSFile( const RString &sPath, const NameToData_t &mapNameTo return true; } -static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map &idToKeySoundIndexOut ) +static void ReadGlobalTags( const RString &sPath, const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map &idToKeySoundIndexOut ) { RString sData; if( GetTagFromMap(mapNameToData, "#title", sData) ) @@ -611,26 +611,29 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur // this is keysound file name. Looks like "#WAV1A" RString nData = it->second; RString sWavID = sName.Right(2); + RString dir = out.GetSongDir(); + if (dir.empty()) + dir = Dirname(sPath); /* Due to bugs in some programs, many PMS files have a "WAV" extension * on files in the PMS for files that actually have some other extension. * Do a search. Don't do a wildcard search; if sData is "song.wav", * we might also have "song.png", which we shouldn't match. */ - if( !IsAFile(out.GetSongDir()+nData) ) + if( !IsAFile(dir+nData) ) { const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere for( unsigned i = 0; exts[i] != NULL; ++i ) { RString fn = SetExtension( nData, exts[i] ); - if( IsAFile(out.GetSongDir()+fn) ) + if( IsAFile(dir+fn) ) { nData = fn; break; } } } - if( !IsAFile(out.GetSongDir()+nData) ) - LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() ); + if( !IsAFile(dir+nData) ) + LOG->UserLog( "Song file", dir, "references key \"%s\" that can't be found", nData.c_str() ); sWavID.MakeUpper(); // HACK: undo the MakeLower() out.m_vsKeysoundFile.push_back( nData ); @@ -810,6 +813,64 @@ void PMSLoader::GetApplicableFiles( const RString &sPath, vector &out ) GetDirListing( sPath + RString("*.pms"), out ); } +bool PMSLoader::LoadNoteDataFromSimfile(const RString &cachePath, Steps &out) +{ + Song dummy; + // TODO: Simplify this copy/paste from LoadFromDir. + + vector BMSData; + BMSData.push_back(NameToData_t()); + ReadPMSFile(cachePath, BMSData.back()); + + RString commonSubstring; + GetCommonTagFromMapList( BMSData, "#title", commonSubstring ); + + Steps *copy = dummy.CreateSteps(); + + copy->SetDifficulty( Difficulty_Medium ); + RString sTag; + if( GetTagFromMap( BMSData[0], "#title", sTag ) && sTag.size() != commonSubstring.size() ) + { + sTag = sTag.substr( commonSubstring.size(), sTag.size() - commonSubstring.size() ); + sTag.MakeLower(); + + if( sTag.find('l') != sTag.npos ) + { + unsigned lPos = sTag.find('l'); + if( lPos > 2 && sTag.substr(lPos-2,4) == "solo" ) + { + copy->SetDifficulty( Difficulty_Edit ); + } + else + { + copy->SetDifficulty( Difficulty_Easy ); + } + } + else if( sTag.find('a') != sTag.npos ) + copy->SetDifficulty( Difficulty_Hard ); + else if( sTag.find('b') != sTag.npos ) + copy->SetDifficulty( Difficulty_Beginner ); + } + if( commonSubstring == "" ) + { + copy->SetDifficulty(Difficulty_Medium); + RString unused; + if (GetTagFromMap(BMSData[0], "#title#", sTag)) + SearchForDifficulty(unused, copy); + } + MeasureToTimeSig_t sigAdjustments; + map idToKeysoundIndex; + ReadGlobalTags( cachePath, BMSData[0], dummy, sigAdjustments, idToKeysoundIndex ); + + const bool ok = LoadFromPMSFile( cachePath, BMSData[0], *copy, sigAdjustments, idToKeysoundIndex ); + if( ok ) + { + out.SetNoteData(copy->GetNoteData()); + } + return ok; + +} + bool PMSLoader::LoadFromDir( const RString &sDir, Song &out ) { LOG->Trace( "Song::LoadFromPMSDir(%s)", sDir.c_str() ); @@ -915,7 +976,8 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out ) MeasureToTimeSig_t sigAdjustments; map idToKeysoundIndex; - ReadGlobalTags( aPMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex ); + ReadGlobalTags( sDir, aPMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex ); + out.m_sSongFileName = out.GetSongDir() + arrayPMSFileNames[iMainDataIndex]; // Override what that global tag said about the title if we have a good substring. // Prevents clobbering and catches "MySong (7keys)" / "MySong (Another) (7keys)" @@ -930,7 +992,10 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out ) Steps* pNewNotes = apSteps[i]; const bool ok = LoadFromPMSFile( out.GetSongDir() + arrayPMSFileNames[i], aPMSData[i], *pNewNotes, sigAdjustments, idToKeysoundIndex ); if( ok ) + { + pNewNotes->SetFilename(out.GetSongDir() + arrayPMSFileNames[i]); out.AddSteps( pNewNotes ); + } else delete pNewNotes; } @@ -941,7 +1006,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out ) ConvertString( out.m_sArtist, "utf-8,japanese" ); ConvertString( out.m_sGenre, "utf-8,japanese" ); - out.TidyUpData(); + out.TidyUpData(false, true); return true; } diff --git a/src/NotesLoaderPMS.h b/src/NotesLoaderPMS.h index 0cdc0d398d..65ec7f4ecb 100644 --- a/src/NotesLoaderPMS.h +++ b/src/NotesLoaderPMS.h @@ -2,11 +2,13 @@ #define NOTES_LOADER_PMS_H class Song; +class Steps; /** @brief Reads a Song from a set of .PMS files. */ namespace PMSLoader { void GetApplicableFiles( const RString &sPath, vector &out ); bool LoadFromDir( const RString &sDir, Song &out ); + bool LoadNoteDataFromSimfile(const RString & cachePath, Steps & out); } #endif diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index ba3ea24e50..9a5f276105 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -10,6 +10,7 @@ #include "Song.h" #include "SongManager.h" #include "Steps.h" +#include "Attack.h" #include "PrefsManager.h" void SMLoader::SetSongTitle(const RString & title) @@ -95,23 +96,6 @@ void SMLoader::LoadFromTokens( } out.SetMeter( StringToInt(sMeter) ); - vector saValues; - split( sRadarValues, ",", saValues, true ); - int categories = NUM_RadarCategory - 1; // Fakes aren't counted in the radar values. - if( saValues.size() == (unsigned)categories * NUM_PLAYERS ) - { - RadarValues v[NUM_PLAYERS]; - FOREACH_PlayerNumber( pn ) - { - // Can't use the foreach anymore due to flexible radar lines. - for( RadarCategory rc = (RadarCategory)0; rc < categories; - enum_add( rc, 1 ) ) - { - v[pn][rc] = StringToFloat( saValues[pn*categories + rc] ); - } - } - out.SetCachedRadarValues( v ); - } out.SetSMNoteData( sNoteData ); @@ -645,9 +629,8 @@ bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString return aBGChangeValues.size() >= 2; } -bool SMLoader::LoadNotedataFromSimfile( const RString &path, Steps &out ) +bool SMLoader::LoadNoteDataFromSimfile( const RString &path, Steps &out ) { - // stub: do this later. MsdFile msd; if( !msd.ReadFile( path, true ) ) // unescape { @@ -698,7 +681,7 @@ bool SMLoader::LoadNotedataFromSimfile( const RString &path, Steps &out ) RString noteData = sParams[6]; Trim( noteData ); out.SetSMNoteData( noteData ); - + out.TidyUpData(); return true; } } @@ -717,6 +700,7 @@ bool SMLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache } out.m_SongTiming.m_sFile = sPath; + out.m_sSongFileName = sPath; for( unsigned i=0; iSetFilename(sPath); out.AddSteps( pNewNotes ); } // XXX: Does anyone know what LEADTRACK is for? -Wolfman2000 @@ -1110,7 +1095,10 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache ) bg.push_back( BackgroundChange(lastBeat,song.m_sBackgroundFile) ); } while(0); } - song.TidyUpData( bFromCache ); + if (bFromCache) + { + song.TidyUpData( bFromCache, true ); + } } /* diff --git a/src/NotesLoaderSM.h b/src/NotesLoaderSM.h index 6654c5bc62..6ba22589d6 100644 --- a/src/NotesLoaderSM.h +++ b/src/NotesLoaderSM.h @@ -46,7 +46,7 @@ struct SMLoader * @brief Retrieve the relevant notedata from the simfile. * @param path the path where the simfile lives. * @param out the Steps we are loading the data into. */ - virtual bool LoadNotedataFromSimfile(const RString &path, Steps &out ); + virtual bool LoadNoteDataFromSimfile(const RString &path, Steps &out ); /** * @brief Attempt to load the specified sm file. diff --git a/src/NotesLoaderSMA.cpp b/src/NotesLoaderSMA.cpp index 2a6198e1ff..1a3cf4ef00 100644 --- a/src/NotesLoaderSMA.cpp +++ b/src/NotesLoaderSMA.cpp @@ -164,6 +164,7 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach } out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing. + out.m_sSongFileName = sPath; int state = SMA_GETTING_SONG_INFO; Steps* pNewNotes = NULL; @@ -450,7 +451,7 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach sParams[5], sParams[6], *pNewNotes ); - + pNewNotes->SetFilename(sPath); out.AddSteps( pNewNotes ); } else if( sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" ) @@ -462,7 +463,7 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach sValueName.c_str() ); } TidyUpData(out, false); - out.TidyUpData(); + out.TidyUpData(false, true); return true; } diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 2e5fc1cabf..4475916620 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -146,6 +146,95 @@ void SSCLoader::ProcessScrolls( TimingData &out, const RString sParam ) } } +bool SSCLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) +{ + LOG->Trace( "Loading notes from %s", cachePath.c_str() ); + + MsdFile msd; + if (!msd.ReadFile(cachePath, true)) + { + LOG->UserLog("Unable to load any notes from", + cachePath, + "for this reason: %s", + msd.GetError().c_str()); + return false; + } + + bool tryingSteps = false; + float storedVersion; + const unsigned values = msd.GetNumValues(); + + for (unsigned i = 0; i < values; i++) + { + const MsdFile::value_t ¶ms = msd.GetValue(i); + RString valueName = params[0]; + valueName.MakeUpper(); + RString matcher = params[1]; // mainly for debugging. + Trim(matcher); + + if (valueName=="VERSION") + { + storedVersion = StringToFloat(matcher); + } + if (tryingSteps) + { + if( valueName=="STEPSTYPE" ) + { + if (out.m_StepsType != GAMEMAN->StringToStepsType(matcher)) + tryingSteps = false; + } + else if( valueName=="CHARTNAME") + { + if (storedVersion >= VERSION_CHART_NAME_TAG && out.GetChartName() != matcher) + tryingSteps = false; + } + else if( valueName=="DESCRIPTION" ) + { + if (storedVersion < VERSION_CHART_NAME_TAG) + { + if (out.GetChartName() != matcher) + tryingSteps = false; + } + else if (out.GetDescription() != matcher) + tryingSteps = false; + } + + else if( valueName=="DIFFICULTY" ) + { + if (out.GetDifficulty() != StringToDifficulty(matcher)) + tryingSteps = false; + } + + else if( valueName=="METER" ) + { + if (out.GetMeter() != StringToInt(matcher)) + tryingSteps = false; + } + + else if( valueName=="CREDIT" ) + { + if (out.GetCredit() != matcher) + tryingSteps = false; + } + + else if( valueName=="NOTES" || valueName=="NOTES2" ) + { + out.SetSMNoteData(matcher); + out.TidyUpData(); + return true; + } + } + else + { + if(valueName == "NOTEDATA") + { + tryingSteps = true; + } + } + } + return false; +} + bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache ) { LOG->Trace( "Song::LoadFromSSCFile(%s)", sPath.c_str() ); @@ -158,6 +247,7 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach } out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing. + out.m_sSongFileName = sPath; int state = GETTING_SONG_INFO; const unsigned values = msd.GetNumValues(); @@ -484,27 +574,35 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach else if( sValueName=="RADARVALUES" ) { - vector saValues; - split( sParams[1], ",", saValues, true ); - - int categories = NUM_RadarCategory; - if( out.m_fVersion < VERSION_RADAR_FAKE ) - categories -= 1; - - if( saValues.size() == (unsigned)categories * NUM_PLAYERS ) + if (bFromCache) { - RadarValues v[NUM_PLAYERS]; - FOREACH_PlayerNumber( pn ) + vector saValues; + split( sParams[1], ",", saValues, true ); + + int categories = NUM_RadarCategory; + if( out.m_fVersion < VERSION_RADAR_FAKE ) + categories -= 1; + + if( saValues.size() == (unsigned)categories * NUM_PLAYERS ) { - // Can't use the foreach anymore due to flexible radar lines. - for( RadarCategory rc = (RadarCategory)0; rc < categories; - enum_add( rc, +1 ) ) + RadarValues v[NUM_PLAYERS]; + FOREACH_PlayerNumber( pn ) { - v[pn][rc] = StringToFloat( saValues[pn*categories + rc] ); + // Can't use the foreach anymore due to flexible radar lines. + for( RadarCategory rc = (RadarCategory)0; rc < categories; + enum_add( rc, +1 ) ) + { + v[pn][rc] = StringToFloat( saValues[pn*categories + rc] ); + } } + pNewNotes->SetCachedRadarValues( v ); } - pNewNotes->SetCachedRadarValues( v ); } + else + { + // just recalc at time. + } + } else if( sValueName=="CREDIT" ) @@ -519,6 +617,7 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach pNewNotes->m_Timing = stepsTiming; pNewNotes->SetSMNoteData( sParams[1] ); pNewNotes->TidyUpData(); + pNewNotes->SetFilename(sPath); out.AddSteps( pNewNotes ); } @@ -605,6 +704,14 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach pNewNotes->SetMaxBPM(StringToFloat(sParams[2])); } } + else if( sValueName=="STEPFILENAME" ) + { + state = GETTING_SONG_INFO; + if( bHasOwnTiming ) + pNewNotes->m_Timing = stepsTiming; + pNewNotes->SetFilename(sParams[1]); + out.AddSteps( pNewNotes ); + } break; } } diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h index 5b35cc5c4c..3d8f8ca634 100644 --- a/src/NotesLoaderSSC.h +++ b/src/NotesLoaderSSC.h @@ -30,6 +30,8 @@ const float VERSION_SPLIT_TIMING = 0.7f; const float VERSION_OFFSET_BEFORE_ATTACK = 0.72f; /** @brief The version that introduced the Chart Name tag. */ const float VERSION_CHART_NAME_TAG = 0.74f; +/** @brief The version that introduced the cache switch tag. */ +const float VERSION_CACHE_SWITCH_TAG = 0.77f; /** * @brief The SSCLoader handles all of the parsing needed for .ssc files. @@ -65,6 +67,12 @@ struct SSCLoader : public SMLoader */ bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ); + /** + * @brief Retrieve the specific NoteData from the file. + * @param cachePath the path to the cache file. + * @param out the Steps to receive just the particular notedata. + * @return true if successful, false otherwise. */ + virtual bool LoadNoteDataFromSimfile( const RString &cachePath, Steps &out ); void ProcessWarps( TimingData &, const RString, const float ); void ProcessLabels( TimingData &, const RString ); diff --git a/src/NotesWriterSM.cpp b/src/NotesWriterSM.cpp index 4be321e651..81eca70991 100644 --- a/src/NotesWriterSM.cpp +++ b/src/NotesWriterSM.cpp @@ -160,16 +160,7 @@ static void WriteGlobalTags( RageFile &f, Song &out ) } f.PutLine( ";" ); - f.Write( "#ATTACKS:" ); - for( unsigned a=0; a < out.m_sAttackString.size(); a++ ) - { - RString sData = out.m_sAttackString[a]; - f.Write( ssprintf( "%s", sData.c_str() ) ); - - if( a != (out.m_sAttackString.size() - 1) ) - f.Write( ":" ); // Not the end, so write a divider ':' - } - f.PutLine( ";" ); + f.PutLine( ssprintf("#ATTACKS:%s;", out.GetAttackString().c_str()) ); } /** diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 8ca8140231..566905d759 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -180,12 +180,29 @@ static void GetTimingTags( vector &lines, TimingData timing, bool bIsSo static void WriteTimingTags( RageFile &f, const TimingData &timing, bool bIsSong = false ) { - - vector lines; - - GetTimingTags( lines, timing, bIsSong ); - - f.PutLine( JoinLineList( lines ) ); + f.PutLine(ssprintf("#BPMS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_BPM)).c_str())); + f.PutLine(ssprintf("#STOPS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_STOP_DELAY, false)).c_str())); + f.PutLine(ssprintf("#DELAYS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_STOP_DELAY, true)).c_str())); + f.PutLine(ssprintf("#WARPS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_WARP)).c_str())); + f.PutLine(ssprintf("#TIMESIGNATURES:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_TIME_SIG)).c_str())); + f.PutLine(ssprintf("#TICKCOUNTS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_TICKCOUNT)).c_str())); + f.PutLine(ssprintf("#COMBOS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_COMBO)).c_str())); + f.PutLine(ssprintf("#SPEEDS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_SPEED)).c_str())); + f.PutLine(ssprintf("#SCROLLS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_SCROLL)).c_str())); + if (!bIsSong) + f.PutLine(ssprintf("#FAKES:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_FAKE)).c_str())); + f.PutLine(ssprintf("#LABELS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_LABEL)).c_str())); } @@ -212,10 +229,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) ); { - vector vs; - FOREACH_ENUM( InstrumentTrack, it ) - if( out.HasInstrumentTrack(it) ) - vs.push_back( InstrumentTrackToString(it) + "=" + out.m_sInstrumentTrackFile[it] ); + vector vs = out.GetInstrumentTracksToVectorString(); if( !vs.empty() ) { RString s = join( ",", vs ); @@ -229,7 +243,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) f.Write( "#SELECTABLE:" ); switch(out.m_SelectionDisplay) { - default: ASSERT(0); // fall through + default: ASSERT_M(0, "An invalid selectable value was found for this song!"); // fall through case Song::SHOW_ALWAYS: f.Write( "YES" ); break; //case Song::SHOW_NONSTOP: f.Write( "NONSTOP" ); break; case Song::SHOW_NEVER: f.Write( "NO" ); break; @@ -297,16 +311,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) } f.PutLine( ";" ); - f.Write( "#ATTACKS:" ); - for( unsigned a=0; a < out.m_sAttackString.size(); a++ ) - { - RString sData = out.m_sAttackString[a]; - f.Write( ssprintf( "%s", sData.c_str() ) ); - - if( a != (out.m_sAttackString.size() - 1) ) - f.Write( ":" ); // Not the end, so write a divider ':' - } - f.PutLine( ";" ); + f.PutLine( ssprintf("#ATTACKS:%s;", out.GetAttackString().c_str()) ); } /** @@ -345,17 +350,7 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa GetTimingTags( lines, in.m_Timing ); - RString attacks = ""; - for( unsigned a=0; a < in.m_sAttackString.size(); a++ ) - { - RString sData = in.m_sAttackString[a]; - attacks += sData; - - if( a != (in.m_sAttackString.size() - 1) ) - attacks += ":\r\n"; // Not the end, so write a divider ':' - } - Trim(attacks, ":"); // just in case something screwy happens. - lines.push_back( ssprintf( "#ATTACKS:%s;", attacks.c_str())); + lines.push_back( ssprintf("#ATTACKS:%s;", in.GetAttackString().c_str())); switch( in.GetDisplayBPM() ) { @@ -376,16 +371,21 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa lines.push_back( ssprintf( "#DISPLAYBPM:*;" ) ); break; } - - RString sNoteData; - in.GetSMNoteData( sNoteData ); + if (bSavingCache) + { + lines.push_back(ssprintf("#STEPFILENAME:%s;", in.GetFilename().c_str())); + } + else + { + RString sNoteData; + in.GetSMNoteData( sNoteData ); - lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" ); - - TrimLeft(sNoteData); - split( sNoteData, "\n", lines, true ); - lines.push_back( ";" ); + lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" ); + TrimLeft(sNoteData); + split( sNoteData, "\n", lines, true ); + lines.push_back( ";" ); + } return JoinLineList( lines ); } diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp index 650c9c3a3f..d0ac5a1cfd 100644 --- a/src/ScreenGameplay.cpp +++ b/src/ScreenGameplay.cpp @@ -393,9 +393,33 @@ void ScreenGameplay::Init() /* Called once per stage (single song or single course). */ GAMESTATE->BeginStage(); + int player = 1; + FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) + { + unsigned int count = pi->m_vpStepsQueue.size(); + + for (unsigned int i = 0; i < count; i++) + { + Steps *curSteps = pi->m_vpStepsQueue[i]; + if (curSteps->IsNoteDataEmpty()) + { + if (curSteps->GetNoteDataFromSimfile()) + { + LOG->Trace("Notes should be loaded for player %d", player); + } + else + { + LOG->Trace("Error loading notes for player %d", player); + } + } + } + player++; + } + if(!GAMESTATE->IsCourseMode() && !GAMESTATE->m_bDemonstrationOrJukebox) { // fill in difficulty of CPU players with that of the first human player + // this should not need to worry about step content. FOREACH_PotentialCpuPlayer(p) GAMESTATE->m_pCurSteps[p].Set( GAMESTATE->m_pCurSteps[ GAMESTATE->GetFirstHumanPlayer() ] ); diff --git a/src/Song.cpp b/src/Song.cpp index 38d93f7f96..afdddd4c4e 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 = 191; +const int FILE_CACHE_VERSION = 192; /** @brief How long does a song sample last by default? */ const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; @@ -860,7 +860,9 @@ void Song::ReCalculateRadarValuesAndLastSecond(bool fromCache, bool duringCache) wipe_notedata: if (duringCache) { - // update this when at that point. + NoteData dummy; + dummy.SetNumTracks(tempNoteData.GetNumTracks()); + pSteps->SetNoteData(dummy); } } diff --git a/src/Song.h b/src/Song.h index 29a53ff942..5f17ad8969 100644 --- a/src/Song.h +++ b/src/Song.h @@ -20,7 +20,7 @@ void FixupPath( RString &path, const RString &sSongPath ); RString GetSongAssetPath( RString sPath, const RString &sSongPath ); /** @brief The version of the .ssc file format. */ -const static float STEPFILE_VERSION_NUMBER = 0.76f; +const static float STEPFILE_VERSION_NUMBER = 0.77f; /** @brief How many edits for this song can each profile have? */ const int MAX_EDITS_PER_SONG_PER_PROFILE = 15; @@ -100,7 +100,9 @@ public: /** * @brief Get the new radar values, and determine the last second at the same time. * - * This is called by TidyUpData, after saving the Song. */ + * This is called by TidyUpData, after saving the Song. + * @param fromCache was this data loaded from the cache file? + * @param duringCache was this data loaded during the cache process? */ void ReCalculateRadarValuesAndLastSecond(bool fromCache = false, bool duringCache = false); /** * @brief Translate any titles that aren't in english. diff --git a/src/Steps.cpp b/src/Steps.cpp index 7e9e659c4a..9700de9a1b 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -88,6 +88,45 @@ bool Steps::IsNoteDataEmpty() const return this->m_sNoteDataCompressed.empty(); } +bool Steps::GetNoteDataFromSimfile() +{ + // Replace the line below with the Steps' cache file. + RString stepFile = this->GetFilename(); + RString extension = GetExtension(stepFile); + if (extension.empty() || extension == "ssc") // remember cache files. + { + SSCLoader loader; + return loader.LoadNoteDataFromSimfile(stepFile, *this); + } + else if (extension == "sm") + { + SMLoader loader; + return loader.LoadNoteDataFromSimfile(stepFile, *this); + } + else if (extension == "sma") + { + SMALoader loader; + return loader.LoadNoteDataFromSimfile(stepFile, *this); + } + else if (extension == "dwi") + { + return DWILoader::LoadNoteDataFromSimfile(stepFile, *this); + } + else if (extension == "ksf") + { + return KSFLoader::LoadNoteDataFromSimfile(stepFile, *this); + } + else if (extension == "bms" || extension == "bml" || extension == "bme") + { + return BMSLoader::LoadNoteDataFromSimfile(stepFile, *this); + } + else if (extension == "pms") + { + return PMSLoader::LoadNoteDataFromSimfile(stepFile, *this); + } + return false; +} + void Steps::SetNoteData( const NoteData& noteDataNew ) { ASSERT( noteDataNew.GetNumTracks() == GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks ); @@ -130,7 +169,6 @@ void Steps::SetSMNoteData( const RString ¬es_comp_ ) m_sNoteDataCompressed = notes_comp_; m_iHash = 0; - m_sFilename = RString(); // We can no longer read from the file because it has changed in memory. } /* XXX: this function should pull data from m_sFilename, like Decompress() */ @@ -293,38 +331,14 @@ void Steps::Decompress() if( !m_sFilename.empty() && m_sNoteDataCompressed.empty() ) { - // We have data on disk and not in memory. Load it. - Song s; - SSCLoader loaderSSC; - bool bLoadedFromSSC = loaderSSC.LoadFromSimfile(m_sFilename, s, true); - if( !bLoadedFromSSC ) + // We have NoteData on disk and not in memory. Load it. + if (!this->GetNoteDataFromSimfile()) { - // try reading from .sm instead - SMLoader loaderSM; - if( !loaderSM.LoadFromSimfile(m_sFilename, s, true) ) - { - LOG->Warn( "Couldn't load \"%s\"", m_sFilename.c_str() ); - return; - } - } - - /* Find the steps. */ - StepsID ID; - ID.FromSteps( this ); - - /* We're using a StepsID to search in a different copy of a Song than - * the one it was created with. Clear the cache before doing this, - * or search results will come from cache and point to the original - * copy. */ - CachedObject::ClearCacheAll(); - Steps *pSteps = ID.ToSteps( &s, true ); - if( pSteps == NULL ) - { - LOG->Warn( "Couldn't find %s in \"%s\"", ID.ToString().c_str(), m_sFilename.c_str() ); + LOG->Warn("Couldn't load \"%s\"", m_sFilename.c_str()); return; } - pSteps->GetSMNoteData( m_sNoteDataCompressed ); + this->GetSMNoteData( m_sNoteDataCompressed ); } if( m_sNoteDataCompressed.empty() ) diff --git a/src/TimingData.cpp b/src/TimingData.cpp index fd3db33b47..6097c69489 100644 --- a/src/TimingData.cpp +++ b/src/TimingData.cpp @@ -1299,6 +1299,24 @@ void TimingData::NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, i return; } +vector TimingData::ToVectorString(TimingSegmentType tst, + bool isDelay, int dec) const +{ + const vector segs = this->allTimingSegments[tst]; + vector ret; + + for (unsigned i = 0; i < segs.size(); i++) + { + if (tst == SEGMENT_STOP_DELAY) + { + StopSegment *seg = static_cast(segs[i]); + if (seg->GetDelay() == isDelay) + continue; + } + ret.push_back(segs[i]->ToString(dec)); + } + return ret; +} // lua start #include "LuaBinding.h" @@ -1315,136 +1333,47 @@ public: static int HasScrollChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasScrollChanges()); return 1; } static int GetWarps( T* p, lua_State *L ) { - vector vWarps; - vector &warps = p->allTimingSegments[SEGMENT_WARP]; - for (unsigned i = 0; i < warps.size(); i++) - { - WarpSegment *seg = static_cast(warps[i]); - const float length = seg->GetLength(); - const float beat = seg->GetBeat(); - vWarps.push_back( ssprintf("%f=%f", beat, length) ); - } - LuaHelpers::CreateTableFromArray(vWarps, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_WARP), L); return 1; } static int GetFakes( T* p, lua_State *L ) { - vector vFakes; - vector &fakes = p->allTimingSegments[SEGMENT_FAKE]; - for (unsigned i = 0; i < fakes.size(); i++) - { - FakeSegment *seg = static_cast(fakes[i]); - const float length = seg->GetLength(); - const float beat = seg->GetBeat(); - vFakes.push_back( ssprintf("%f=%f", beat, length) ); - } - LuaHelpers::CreateTableFromArray(vFakes, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_FAKE), L); return 1; } static int GetScrolls( T* p, lua_State *L ) { - vector vScrolls; - vector &scrolls = p->allTimingSegments[SEGMENT_SCROLL]; - for (unsigned i = 0; i < scrolls.size(); i++) - { - ScrollSegment *seg = static_cast(scrolls[i]); - const float ratio = seg->GetRatio(); - const float beat = seg->GetBeat(); - vScrolls.push_back( ssprintf("%f=%f", beat, ratio) ); - } - LuaHelpers::CreateTableFromArray(vScrolls, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_SCROLL), L); return 1; } static int GetSpeeds( T* p, lua_State *L ) { - vector vSpeeds; - vector &speeds = p->allTimingSegments[SEGMENT_SPEED]; - for (unsigned i = 0; i < speeds.size(); i++) - { - SpeedSegment *seg = static_cast(speeds[i]); - const float length = seg->GetLength(); - const float ratio = seg->GetRatio(); - const unsigned short unit = seg->GetUnit(); - const float beat = seg->GetBeat(); - vSpeeds.push_back( ssprintf("%f=%f=%f=%uh", beat, ratio, length, unit) ); - } - LuaHelpers::CreateTableFromArray(vSpeeds, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_SPEED), L); return 1; } static int GetTimeSignatures( T* p, lua_State *L ) { - vector vTimes; - vector &tSigs = p->allTimingSegments[SEGMENT_TIME_SIG]; - for (unsigned i = 0; i < tSigs.size(); i++) - { - TimeSignatureSegment *seg = static_cast(tSigs[i]); - const int numerator = seg->GetNum(); - const int denominator = seg->GetDen(); - const float beat = seg->GetBeat(); - vTimes.push_back( ssprintf("%f=%d=%d", beat, numerator, denominator) ); - } - LuaHelpers::CreateTableFromArray(vTimes, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_TIME_SIG), L); return 1; } static int GetCombos( T* p, lua_State *L ) { - vector vCombos; - vector &combos = p->allTimingSegments[SEGMENT_COMBO]; - for (unsigned i = 0; i < combos.size(); i++) - { - ComboSegment *seg = static_cast(combos[i]); - const int combo = seg->GetCombo(); - const int miss = seg->GetMissCombo(); - const float beat = seg->GetBeat(); - vCombos.push_back( ssprintf("%f=%d=%d", beat, combo, miss) ); - } - LuaHelpers::CreateTableFromArray(vCombos, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_COMBO), L); return 1; } static int GetTickcounts( T* p, lua_State *L ) { - vector vTicks; - vector &ticks = p->allTimingSegments[SEGMENT_TICKCOUNT]; - for (unsigned i = 0; i < ticks.size(); i++) - { - TickcountSegment *seg = static_cast(ticks[i]); - const int tick = seg->GetTicks(); - const float beat = seg->GetBeat(); - vTicks.push_back( ssprintf("%f=%d", beat, tick) ); - } - LuaHelpers::CreateTableFromArray(vTicks, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_TICKCOUNT), L); return 1; } static int GetStops( T* p, lua_State *L ) { - vector vStops; - vector &stops = p->allTimingSegments[SEGMENT_STOP_DELAY]; - for (unsigned i = 0; i < stops.size(); i++) - { - StopSegment *seg = static_cast(stops[i]); - const float fStartBeat = seg->GetBeat(); - const float fStopLength = seg->GetPause(); - if(!seg->GetDelay()) - vStops.push_back( ssprintf("%f=%f", fStartBeat, fStopLength) ); - } - - LuaHelpers::CreateTableFromArray(vStops, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_STOP_DELAY, false), L); return 1; } static int GetDelays( T* p, lua_State *L ) { - vector vDelays; - vector &stops = p->allTimingSegments[SEGMENT_STOP_DELAY]; - for (unsigned i = 0; i < stops.size(); i++) - { - StopSegment *seg = static_cast(stops[i]); - const float fStartBeat = seg->GetBeat(); - const float fStopLength = seg->GetPause(); - if(seg->GetDelay()) - vDelays.push_back( ssprintf("%f=%f", fStartBeat, fStopLength) ); - } - - LuaHelpers::CreateTableFromArray(vDelays, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_STOP_DELAY, true), L); return 1; } static int GetBPMs( T* p, lua_State *L ) @@ -1463,31 +1392,12 @@ public: } static int GetLabels( T* p, lua_State *L ) { - vector vLabels; - vector &labels = p->allTimingSegments[SEGMENT_LABEL]; - for (unsigned i = 0; i < labels.size(); i++) - { - LabelSegment *seg = static_cast(labels[i]); - const float fStartRow = seg->GetBeat(); - const RString sLabel = seg->GetLabel(); - vLabels.push_back( ssprintf("%f=%s", fStartRow, sLabel.c_str()) ); - } - LuaHelpers::CreateTableFromArray(vLabels, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_LABEL), L); return 1; } static int GetBPMsAndTimes( T* p, lua_State *L ) { - vector vBPMs; - vector &bpms = p->allTimingSegments[SEGMENT_BPM]; - for (unsigned i = 0; i < bpms.size(); i++) - { - BPMSegment *seg = static_cast(bpms[i]); - const float fStartRow = seg->GetBeat(); - const float fBPM = seg->GetBPM(); - vBPMs.push_back( ssprintf("%f=%f", fStartRow, fBPM) ); - } - - LuaHelpers::CreateTableFromArray(vBPMs, L); + LuaHelpers::CreateTableFromArray(p->ToVectorString(SEGMENT_BPM), L); return 1; } static int GetActualBPM( T* p, lua_State *L ) diff --git a/src/TimingData.h b/src/TimingData.h index 5249559dcf..2486ff5701 100644 --- a/src/TimingData.h +++ b/src/TimingData.h @@ -825,6 +825,8 @@ public: * @brief The initial offset of a song. */ float m_fBeat0OffsetInSeconds; + + vector ToVectorString(TimingSegmentType tst, bool isDelay = false, int dec = 6) const; }; #undef COMPARE diff --git a/src/TimingSegments.h b/src/TimingSegments.h index 09ea2782f1..2b0eed4d3d 100644 --- a/src/TimingSegments.h +++ b/src/TimingSegments.h @@ -81,6 +81,11 @@ struct TimingSegment { return TimingSegmentType_Invalid; } + // TODO: Remove isDelay optional param and split Stops and Delays. + virtual RString ToString(int dec) const + { + return FloatToString(this->GetBeat()); + } /** * @brief Compares two DrivedSegments to see if one is less than the other. @@ -206,6 +211,13 @@ struct FakeSegment : public TimingSegment TimingSegmentType GetType() const { return SEGMENT_FAKE; } + virtual RString ToString(int dec) const + { + RString str = "%.0" + IntToString(dec) + + "f=%.0" + IntToString(dec) + "f"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetLength()); + } + private: /** * @brief The number of beats the FakeSegment is alive for. @@ -260,6 +272,13 @@ struct WarpSegment : public TimingSegment void Scale( int start, int length, int newLength ); + virtual RString ToString(int dec) const + { + RString str = "%.0" + IntToString(dec) + + "f=%.0" + IntToString(dec) + "f"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetLength()); + } + /* * @brief Compares two WarpSegments to see if one is less than the other. * @param other the other WarpSegment to compare to. @@ -326,6 +345,12 @@ struct TickcountSegment : public TimingSegment * @param i the tickcount. */ void SetTicks(const int i); + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + "f=%i"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetTicks()); + } + /** * @brief Compares two TickcountSegments to see if one is less than the other. * @param other the other TickcountSegment to compare to. @@ -404,6 +429,17 @@ struct ComboSegment : public TimingSegment * @param i the miss combo. */ void SetMissCombo(const int i); + virtual RString ToString(int dec) const + { + RString str = "%.0" + IntToString(dec) + "f=%i"; + if (this->GetCombo() == this->GetMissCombo()) + { + return ssprintf(str.c_str(), this->GetBeat(), this->GetCombo()); + } + str += "=%i"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetCombo(), this->GetMissCombo()); + } + /** * @brief Compares two ComboSegments to see if one is less than the other. * @param other the other ComboSegment to compare to. @@ -462,6 +498,12 @@ struct LabelSegment : public TimingSegment * @param l the label. */ void SetLabel(const RString l); + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + "f=%s"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetLabel().c_str()); + } + /** * @brief Compares two LabelSegments to see if one is less than the other. * @param other the other LabelSegment to compare to. @@ -526,6 +568,13 @@ struct BPMSegment : public TimingSegment * @param l the label. */ void SetBPS(const float newBPS); + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + + "f=%.0" + IntToString(dec) + "f"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetBPM()); + } + /** * @brief Compares two LabelSegments to see if one is less than the other. * @param other the other LabelSegment to compare to. @@ -604,6 +653,12 @@ struct TimeSignatureSegment : public TimingSegment * @param i the denominator. */ void SetDen(const int i); + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + "f=%i=%i"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetNum(), this->GetDen()); + } + /** * @brief Retrieve the number of note rows per measure within the TimeSignatureSegment. * @@ -727,6 +782,15 @@ struct SpeedSegment : public TimingSegment void SetUnit(const int i); void Scale( int start, int length, int newLength ); + + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + + "f=%.0" + IntToString(dec) + "f=%.0" + + IntToString(dec) + "f=%uh"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetRatio(), + this->GetLength(), this->GetUnit()); + } /** * @brief Compares two SpeedSegments to see if one is less than the other. @@ -792,6 +856,13 @@ struct ScrollSegment : public TimingSegment * @param i the ratio. */ void SetRatio(const float i); + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + + "f=%.0" + IntToString(dec) + "f"; + return ssprintf(str.c_str(), this->GetBeat(), this->GetRatio()); + } + /** * @brief Compares two ScrollSegment to see if one is less than the other. * @param other the other ScrollSegment to compare to. @@ -867,6 +938,13 @@ struct StopSegment : public TimingSegment * @brief Set the behavior in this StopSegment. * @param i the behavior. */ void SetDelay(const bool i); + + virtual RString ToString(int dec) const + { + const RString str = "%.0" + IntToString(dec) + + "f=%.0" + IntToString(dec) + "f"; + return ssprintf(str.c_str(), this->GetPause()); + } /** * @brief Compares two StopSegments to see if one is less than the other.