diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index b1c79de157..c3404ff6c7 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -412,21 +412,27 @@ void SetNpsPerMeasure(StepsTagInfo& info) { if (info.from_cache || info.for_load_edit) { - std::vector values; - split((*info.params)[1], ",", values, true); - std::size_t measures_per_player = values.size() / NUM_PlayerNumber; - std::vector> npsPerMeasure; - npsPerMeasure.resize(NUM_PLAYERS); - - FOREACH_PlayerNumber(pn) + + std::vector valuesPerPlayer; + split((*info.params)[1], "|", valuesPerPlayer, true); + + if(valuesPerPlayer.size() > NUM_PlayerNumber) { - npsPerMeasure[pn].resize(measures_per_player, 0); - for(std::size_t i= 0; i < measures_per_player; ++i) - { - npsPerMeasure[pn][i]= StringToFloat(values[pn * measures_per_player + i]); - } + LOG->Warn("#NPSPERMEASURE has more sections (%zu) than possible number of players (%d)!", valuesPerPlayer.size(), NUM_PlayerNumber); + } + + for(std::size_t pn = 0; pn < valuesPerPlayer.size() && pn < NUM_PlayerNumber; pn++) + { + std::vector values; + split(valuesPerPlayer[pn], ",", values, true); + std::vector npsPerMeasure; + npsPerMeasure.resize(values.size()); + for(std::size_t i = 0; i < values.size(); i++) + { + npsPerMeasure[i] = StringToFloat(values[i]); + } + info.steps->SetCachedNotesPerMeasure(npsPerMeasure, static_cast(pn)); } - info.steps->SetCachedNpsPerMeasure(npsPerMeasure); } else { @@ -439,21 +445,27 @@ void SetNotesPerMeasure(StepsTagInfo& info) { if (info.from_cache || info.for_load_edit) { - std::vector values; - split((*info.params)[1], ",", values, true); - std::size_t measures_per_player = values.size() / NUM_PlayerNumber; - std::vector> notesPerMeasure; - notesPerMeasure.resize(NUM_PLAYERS); - - FOREACH_PlayerNumber(pn) + std::vector valuesPerPlayer; + split((*info.params)[1], "|", valuesPerPlayer, true); + + if(valuesPerPlayer.size() > NUM_PlayerNumber) { - notesPerMeasure[pn].resize(measures_per_player, 0); - for(std::size_t i= 0; i < measures_per_player; ++i) - { - notesPerMeasure[pn][i]= StringToInt(values[pn * measures_per_player + i]); - } + LOG->Warn("#NOTESPERMEASURE has more sections (%zu) than possible number of players (%d)!", valuesPerPlayer.size(), NUM_PlayerNumber); + + } + + for(std::size_t pn = 0; pn < valuesPerPlayer.size() && pn < NUM_PlayerNumber; pn++) + { + std::vector values; + split(valuesPerPlayer[pn], ",", values, true); + std::vector notesPerMeasure; + notesPerMeasure.resize(values.size()); + for(std::size_t i = 0; i < values.size(); i++) + { + notesPerMeasure[i] = StringToInt(values[i]); + } + info.steps->SetCachedNotesPerMeasure(notesPerMeasure, static_cast(pn)); } - info.steps->SetCachedNotesPerMeasure(notesPerMeasure); } else { diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 5d46279a23..799f790afb 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -440,29 +440,30 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa } lines.push_back(ssprintf("#TECHCOUNTS:%s;", join(",", asTechCounts).c_str())); - std::vector asNpsPerMeasure; - FOREACH_PlayerNumber(pn) + // NpsPerMeasure and NotesPerMeasure are stored differently from Radar Values and Tech Counts, + // because the number of measures is variable. + // For charts that have different steps per player, each set of values is separated + // with pipes "|". + // The vast majority of charts don't, so there's no reason to store duplicated data. + const std::vector> &allNpsPerMeasures = in.GetAllNpsPerMeasures(); + std::vector npsPerMeasureStrings; + + for(std::vector npsPerMeasure : allNpsPerMeasures) { - const std::vector &npsPerMeasure = in.GetNpsPerMeasure(pn); - for(unsigned i = 0; i < npsPerMeasure.size(); i++) - { - asNpsPerMeasure.push_back(ssprintf("%.3f", npsPerMeasure[i])); - } + npsPerMeasureStrings.push_back(serialize(npsPerMeasure, ",", 3)); } - lines.push_back( ssprintf( "#NPSPERMEASURE:%s;", join(",",asNpsPerMeasure).c_str() ) ); + lines.push_back( ssprintf( "#NPSPERMEASURE:%s;", join("|",npsPerMeasureStrings).c_str() ) ); - std::vector asNotesPerMeasure; - FOREACH_PlayerNumber(pn) + const std::vector> &allNotesPerMeasures = in.GetAllNotesPerMeasures(); + std::vector notesPerMeasureStrings; + + for(std::vector notesPerMeasure : allNotesPerMeasures) { - const std::vector ¬esPerMeasure = in.GetNotesPerMeasure(pn); - for(unsigned i = 0; i < notesPerMeasure.size(); i++) - { - asNotesPerMeasure.push_back(ssprintf("%d", notesPerMeasure[i])); - } + notesPerMeasureStrings.push_back(serialize(notesPerMeasure, ",")); } - lines.push_back( ssprintf( "#NOTESPERMEASURE:%s;", join(",",asNotesPerMeasure).c_str() ) ); + lines.push_back( ssprintf( "#NOTESPERMEASURE:%s;", join("|",notesPerMeasureStrings).c_str() ) ); // NOTE(MV): #STEPFILENAME has to be at the end of the cache tags, // because it's used in SSCLoader::LoadFromSimfile to determine when diff --git a/src/RageUtil.cpp b/src/RageUtil.cpp index d985a1e3f8..e1bd5ab2fc 100644 --- a/src/RageUtil.cpp +++ b/src/RageUtil.cpp @@ -721,6 +721,28 @@ RString join( const RString &sDelimitor, std::vector::const_iterator be return sRet; } +RString serialize(const std::vector & sSource, const RString &sDelimitor, int precision) +{ + std::vector values; + RString precisionStr = ssprintf("%%.%df", precision); + for(float s : sSource) + { + values.push_back(ssprintf(precisionStr, s)); + } + return join(sDelimitor, values); +} + +RString serialize(const std::vector & sSource, const RString &sDelimitor) +{ + std::vector values; + for(int s : sSource) + { + values.push_back(ssprintf("%d", s)); + } + return join(sDelimitor, values); +} + + RString SmEscape( const RString &sUnescaped, const std::vector charsToEscape ) { return SmEscape(sUnescaped.c_str(), sUnescaped.size(), charsToEscape); diff --git a/src/RageUtil.h b/src/RageUtil.h index eb97cc5c5f..7da9dea1c9 100644 --- a/src/RageUtil.h +++ b/src/RageUtil.h @@ -435,6 +435,10 @@ void split( const std::wstring &sSource, const std::wstring &sDelimitor, int &iB RString join( const RString &sDelimitor, const std::vector& sSource ); RString join( const RString &sDelimitor, std::vector::const_iterator begin, std::vector::const_iterator end ); +// Joins a vector of numbers to a serialized string of numbers separated by Delimitor. +RString serialize(const std::vector & sSource, const RString &sDelimitor, int precision); +RString serialize(const std::vector & sSource, const RString &sDelimitor); + // These methods escapes a string for saving in a .sm or .crs file RString SmEscape(const RString &sUnescaped, const std::vector charsToEscape = {'\\', ':', ';'}); RString SmEscape( const char *cUnescaped, int len, const std::vector charsToEscape = {'\\', ':', ';'} ); diff --git a/src/Steps.cpp b/src/Steps.cpp index 03dc5627aa..d77be6ea75 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -62,13 +62,7 @@ Steps::Steps(Song *song): m_StepsType(StepsType_Invalid), m_pSong(song), m_AreCachedNpsPerMeasureJustLoaded(false), m_AreCachedNotesPerMeasureJustLoaded(false), m_sCredit(""), displayBPMType(DISPLAY_BPM_ACTUAL), - specifiedBPMMin(0), specifiedBPMMax(0) { - m_CachedNpsPerMeasure.resize(NUM_PLAYERS); - m_CachedNotesPerMeasure.resize(NUM_PLAYERS); - FOREACH_PlayerNumber(pn) { - m_PeakNps[pn] = 0; - } - } + specifiedBPMMin(0), specifiedBPMMax(0) { } Steps::~Steps() { @@ -427,46 +421,47 @@ void Steps::CalculateMeasureInfo() NoteData tempNoteData; this->GetNoteData( tempNoteData ); - MeasureInfo measureInfo[NUM_PLAYERS]; + std::vector measureInfoPerPlayer; - FOREACH_PlayerNumber(pn) - measureInfo[pn] - .Zero(); - GAMESTATE->SetProcessedTimingData(this->GetTimingData()); if( tempNoteData.IsComposite() ) { + measureInfoPerPlayer.resize(NUM_PLAYERS); std::vector vParts; NoteDataUtil::SplitCompositeNoteData( tempNoteData, vParts ); for( std::size_t pn = 0; pn < std::min(vParts.size(), std::size_t(NUM_PLAYERS)); ++pn ) { - MeasureInfo::CalculateMeasureInfo(vParts[pn], measureInfo[pn]); + MeasureInfo::CalculateMeasureInfo(vParts[pn], measureInfoPerPlayer[pn]); } } else if (GAMEMAN->GetStepsTypeInfo(this->m_StepsType).m_StepsTypeCategory == StepsTypeCategory_Couple) { + measureInfoPerPlayer.resize(NUM_PLAYERS); NoteData p1 = tempNoteData; // XXX: Assumption that couple will always have an even number of notes. const int tracks = tempNoteData.GetNumTracks() / 2; p1.SetNumTracks(tracks); - MeasureInfo::CalculateMeasureInfo(tempNoteData, measureInfo[PLAYER_1]); + MeasureInfo::CalculateMeasureInfo(tempNoteData, measureInfoPerPlayer[PLAYER_1]); NoteDataUtil::ShiftTracks(tempNoteData, tracks); tempNoteData.SetNumTracks(tracks); - MeasureInfo::CalculateMeasureInfo(tempNoteData, measureInfo[PLAYER_2]); + MeasureInfo::CalculateMeasureInfo(tempNoteData, measureInfoPerPlayer[PLAYER_2]); } else { - MeasureInfo::CalculateMeasureInfo(tempNoteData, measureInfo[0]); - std::fill_n( measureInfo + 1, NUM_PLAYERS-1, measureInfo[0] ); + measureInfoPerPlayer.resize(1); + MeasureInfo::CalculateMeasureInfo(tempNoteData, measureInfoPerPlayer[0]); } - FOREACH_PlayerNumber(pn) + m_CachedNotesPerMeasure.clear(); + m_CachedNpsPerMeasure.clear(); + m_PeakNps.clear(); + + for(MeasureInfo & mi : measureInfoPerPlayer) { - m_CachedNpsPerMeasure[pn].assign(measureInfo[pn].npsPerMeasure.begin(), measureInfo[pn].npsPerMeasure.end()); - - m_CachedNotesPerMeasure[pn].assign(measureInfo[pn].notesPerMeasure.begin(), measureInfo[pn].notesPerMeasure.end()); - m_PeakNps[pn] = measureInfo[pn].peakNps; + m_CachedNotesPerMeasure.push_back(mi.notesPerMeasure); + m_CachedNpsPerMeasure.push_back(mi.npsPerMeasure); + m_PeakNps.push_back(mi.peakNps); } GAMESTATE->SetProcessedTimingData(nullptr); @@ -762,39 +757,35 @@ void Steps::SetCachedTechCounts( const TechCounts ts[NUM_PLAYERS] ) m_bAreCachedTechCountsValuesJustLoaded = true; } -void Steps::SetCachedNpsPerMeasure(std::vector>& npsPerMeasure) +void Steps::SetCachedNpsPerMeasure(std::vector& npsPerMeasure, PlayerNumber pn) { DeAutogen(); - if(npsPerMeasure.size() != NUM_PLAYERS) + + if(m_CachedNpsPerMeasure.size() <= pn) { - return; + m_CachedNpsPerMeasure.resize(pn + 1); + m_PeakNps.resize(pn + 1); } - FOREACH_PlayerNumber(pn) + m_CachedNpsPerMeasure[pn].assign(npsPerMeasure.begin(), npsPerMeasure.end()); + std::vector::iterator peakNps = std::max_element(npsPerMeasure.begin(), npsPerMeasure.end()); + if(peakNps != npsPerMeasure.end()) { - m_CachedNpsPerMeasure[pn].assign(npsPerMeasure[pn].begin(), npsPerMeasure[pn].end()); - std::vector::iterator peakNps = std::max_element(npsPerMeasure[pn].begin(), npsPerMeasure[pn].end()); - if(peakNps != npsPerMeasure[pn].end()) - { - m_PeakNps[pn] = *peakNps; - } + m_PeakNps[pn] = *peakNps; } m_AreCachedNpsPerMeasureJustLoaded = true; } -void Steps::SetCachedNotesPerMeasure(std::vector>& notesPerMeasure) +void Steps::SetCachedNotesPerMeasure(std::vector& notesPerMeasure, PlayerNumber pn) { DeAutogen(); - if(notesPerMeasure.size() != NUM_PLAYERS) + if(m_CachedNotesPerMeasure.size() <= pn) { - return; + m_CachedNotesPerMeasure.resize(pn + 1); } - FOREACH_PlayerNumber(pn) - { - m_CachedNotesPerMeasure[pn].assign(notesPerMeasure[pn].begin(), notesPerMeasure[pn].end()); - } + m_CachedNotesPerMeasure[pn].assign(notesPerMeasure.begin(), notesPerMeasure.end()); m_AreCachedNotesPerMeasureJustLoaded = true; } @@ -883,6 +874,54 @@ std::vector Steps::GetColumnCues(float minDuration) } +const std::vector & Steps::GetNpsPerMeasure(PlayerNumber pn) const { + // CachedNpsPerMeasure will only have separate sets of values per-player if the + // steps type has different steps for each player (eg dance-couples, dance-routine). + // Otherwise, it will only store one copy of the values (which will be the case for like + // 99.9% of charts). + + static const std::vector EMPTY_VECTOR; + if(Real()->m_CachedNpsPerMeasure.size() == 0) { + return EMPTY_VECTOR; + } + else if(Real()->m_CachedNpsPerMeasure.size() <= pn) { + return Real()->m_CachedNpsPerMeasure[PLAYER_1]; + } + else { + return Real()->m_CachedNpsPerMeasure[pn]; + } +} + +const std::vector & Steps::GetNotesPerMeasure(PlayerNumber pn) const { + // CachedNotesPerMeasure will only have separate sets of values per-player if the + // steps type has different steps for each player (eg dance-couples, dance-routine). + // Otherwise, it will only have one copy of the values (which will be the case for like + // 99.9% of charts). + static const std::vector EMPTY_VECTOR; + if(Real()->m_CachedNotesPerMeasure.size() == 0) { + return EMPTY_VECTOR; + } + else if(Real()->m_CachedNotesPerMeasure.size() <= pn) { + return Real()->m_CachedNotesPerMeasure[PLAYER_1]; + } + else { + return Real()->m_CachedNotesPerMeasure[pn]; + } +} + +float Steps::GetPeakNps(PlayerNumber pn) const { + if(Real()->m_PeakNps.size() == 0) { + return 0; + } + else if(Real()->m_PeakNps.size() <= pn) { + return Real()->m_PeakNps[PLAYER_1]; + } + else { + return Real()->m_PeakNps[pn]; + } +} + + // lua start #include "LuaBinding.h" /** @brief Allow Lua to have access to the Steps. */ diff --git a/src/Steps.h b/src/Steps.h index e1ce4b572c..fc7534a1df 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -145,8 +145,8 @@ public: void SetMeter( int meter ); void SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] ); void SetCachedTechCounts(const TechCounts ts[NUM_PLAYERS]); - void SetCachedNpsPerMeasure(std::vector>& npsPerMeasure); - void SetCachedNotesPerMeasure(std::vector>& notesPerMeasure); + void SetCachedNpsPerMeasure(std::vector& npsPerMeasure, PlayerNumber pn); + void SetCachedNotesPerMeasure(std::vector& notesPerMeasure, PlayerNumber pn); float PredictMeter() const; unsigned GetHash() const; @@ -180,9 +180,12 @@ public: void CalculateMeasureInfo(); - const std::vector &GetNpsPerMeasure(PlayerNumber pn) const { return Real()->m_CachedNpsPerMeasure[pn]; } - const std::vector &GetNotesPerMeasure(PlayerNumber pn) const { return Real()->m_CachedNotesPerMeasure[pn]; } - float GetPeakNps(PlayerNumber pn) const { return Real()->m_PeakNps[pn]; } + const std::vector> & GetAllNpsPerMeasures() const { return Real()->m_CachedNpsPerMeasure; } + const std::vector &GetNpsPerMeasure(PlayerNumber pn) const; + const std::vector> & GetAllNotesPerMeasures() const { return Real()->m_CachedNotesPerMeasure; }; + const std::vector &GetNotesPerMeasure(PlayerNumber pn) const; + + float GetPeakNps(PlayerNumber pn) const; /** * @brief The TimingData used by the Steps. @@ -287,7 +290,7 @@ private: std::vector> m_CachedNotesPerMeasure; bool m_AreCachedNotesPerMeasureJustLoaded; - float m_PeakNps[NUM_PLAYERS]; + std::vector m_PeakNps;