Added MeasureInfo calculations, and loader/writer for #MEASUREINFO cache

This commit is contained in:
Michael Votaw
2025-02-11 19:39:03 -08:00
committed by teejusb
parent 200a44d8a1
commit 429fab2fbc
6 changed files with 313 additions and 2 deletions
+133
View File
@@ -0,0 +1,133 @@
#include "global.h"
#include "MeasureInfo.h"
#include "NoteData.h"
#include "RageLog.h"
#include "LocalizedString.h"
#include "LuaBinding.h"
#include "TimingData.h"
#include "GameState.h"
RString MeasureInfo::ToString() const
{
std::vector<RString> asMeasureInfo;
for (unsigned i = 0; i < npsPerMeasure.size(); i++)
{
asMeasureInfo.push_back(ssprintf("%.6f", npsPerMeasure[i]));
}
for (unsigned i = 0; i < notesPerMeasure.size(); i++)
{
asMeasureInfo.push_back(ssprintf("%d", notesPerMeasure[i]));
}
return join(",", asMeasureInfo);
}
void MeasureInfo::FromString(RString sValues)
{
std::vector<RString> asValues;
split( sValues, ",", asValues, true );
int half_size = static_cast<int>(asValues.size()) / 2;
float peak_nps = 0;
for (int i = 0; i < half_size; i++)
{
float nps = StringToFloat(asValues[i]);
this->npsPerMeasure.push_back(nps);
if(nps > peak_nps)
{
peak_nps = nps;
}
}
for (unsigned i = half_size; i < asValues.size(); i++)
{
this->notesPerMeasure.push_back(StringToInt(asValues[i]));
}
this->measureCount = half_size;
this->peakNps = peak_nps;
}
void MeasureInfo::CalculateMeasureInfo(const NoteData &in, MeasureInfo &out)
{
int lastRow = in.GetLastRow();
int lastRowMeasureIndex = 0;
int lastRowBeatIndex = 0;
int lastRowRemainder = 0;
TimingData *timing = GAMESTATE->GetProcessedTimingData();
timing->NoteRowToMeasureAndBeat(lastRow, lastRowMeasureIndex, lastRowBeatIndex, lastRowRemainder);
int totalMeasureCount = lastRowMeasureIndex + 1;
out.notesPerMeasure.clear();
out.npsPerMeasure.clear();
out.notesPerMeasure.resize(totalMeasureCount, 0);
out.npsPerMeasure.resize(totalMeasureCount, 0);
NoteData::all_tracks_const_iterator curr_note = in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW);
int iMeasureIndexOut = 0;
int iBeatIndexOut = 0;
int iRowsRemainder = 0;
float peak_nps = 0;
int curr_row = -1;
int notes_this_row = 0;
while (!curr_note.IsAtEnd())
{
if(curr_note.Row() != curr_row)
{
// Before moving on to a new row, update the row count for the "current" measure
// Note that we're only add
out.notesPerMeasure[iMeasureIndexOut] += notes_this_row;
// Update iMeasureIndex for the current row
timing->NoteRowToMeasureAndBeat(curr_note.Row(), iMeasureIndexOut, iBeatIndexOut, iRowsRemainder);
curr_row = curr_note.Row();
notes_this_row = 0;
}
// Update tap and mine count for the current measure
// Regardless of how many notes are on this row, it's only considered 1 "note" when we want to
// calculate nps. So jumps/brackets don't inflate the nps value. Maybe we should call it
// "steps per second" or something like that?
if (curr_note->type == TapNoteType_Tap || curr_note->type == TapNoteType_HoldHead)
{
notes_this_row = 1;
}
++curr_note;
}
// And handle the final note...
out.notesPerMeasure[iMeasureIndexOut] += notes_this_row;
// Now that all of the notes have been parsed, calculate nps for each measure
for (int m = 0; m < totalMeasureCount; m++)
{
if(out.notesPerMeasure[m] == 0)
{
out.npsPerMeasure[m] = 0;
continue;
}
float measureDuration = timing->GetElapsedTimeFromBeat(4 * (m+1)) - timing->GetElapsedTimeFromBeat(4 * m);
float nps = out.notesPerMeasure[m] / measureDuration;
if(measureDuration < 0.12)
{
out.npsPerMeasure[m] = 0;
}
else
{
out.npsPerMeasure[m] = nps;
}
if(nps > peak_nps)
{
peak_nps = nps;
}
}
out.peakNps = peak_nps;
out.measureCount = totalMeasureCount;
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef MEASURE_INFO_H
#define MEASURE_INFO_H
#include "GameConstantsAndTypes.h"
class NoteData;
/** This is a container for per-measure stats of a stepchart.
This data is calculated and saved to the song cache files as the #MEASUREINFO tag.
This data is provided to the theme via lua functions in Steps.cpp and Trail.cpp,
*/
struct MeasureInfo
{
int measureCount;
float peakNps;
std::vector<float> npsPerMeasure;
std::vector<int> notesPerMeasure;
MeasureInfo()
{
Zero();
}
void Zero()
{
measureCount = 0;
peakNps = 0;
npsPerMeasure.clear();
notesPerMeasure.clear();
}
RString ToString() const;
void FromString( RString sValues );
static void CalculateMeasureInfo(const NoteData &in, MeasureInfo &out);
};
#endif
+26
View File
@@ -383,6 +383,30 @@ void SetRadarValues(StepsTagInfo& info)
}
info.ssc_format= true;
}
void SetMeasureInfo(StepsTagInfo& info)
{
if (info.from_cache || info.for_load_edit)
{
std::vector<RString> values;
split((*info.params)[1], "|", values, true);
MeasureInfo v[NUM_PLAYERS];
FOREACH_PlayerNumber(pn)
{
v[pn].FromString(values[pn]);
}
info.steps->SetCachedMeasureInfo(v);
}
else
{
// just recalc at time.
}
info.ssc_format= true;
}
void SetCredit(StepsTagInfo& info)
{
info.steps->SetCredit((*info.params)[1]);
@@ -623,6 +647,8 @@ struct ssc_parser_helper_t
steps_tag_handlers["SCROLLS"]= &SetStepsScrolls;
steps_tag_handlers["FAKES"]= &SetStepsFakes;
steps_tag_handlers["LABELS"]= &SetStepsLabels;
steps_tag_handlers["MEASUREINFO"] = &SetMeasureInfo;
/* If this is called, the chart does not use the same attacks
* as the Song's timing. No other changes are required. */
steps_tag_handlers["ATTACKS"]= &SetStepsAttacks;
+8
View File
@@ -428,6 +428,14 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
}
if (bSavingCache)
{
std::vector<RString> asMeasureInfo;
FOREACH_PlayerNumber( pn )
{
const MeasureInfo &ms = in.GetMeasureInfo(pn);
asMeasureInfo.push_back(ms.ToString());
}
lines.push_back(ssprintf("#MEASUREINFO:%s;", join("|", asMeasureInfo).c_str()));
lines.push_back(ssprintf("#STEPFILENAME:%s;", in.GetFilename().c_str()));
}
else
+99 -1
View File
@@ -55,6 +55,7 @@ Steps::Steps(Song *song): m_StepsType(StepsType_Invalid), m_pSong(song),
m_sDescription(""), m_sChartStyle(""),
m_Difficulty(Difficulty_Invalid), m_iMeter(0),
m_bAreCachedRadarValuesJustLoaded(false),
m_bAreCachedMeasureInfoJustLoaded(false),
m_sCredit(""), displayBPMType(DISPLAY_BPM_ACTUAL),
specifiedBPMMin(0), specifiedBPMMax(0) {}
@@ -354,6 +355,58 @@ void Steps::CalculateRadarValues( float fMusicLengthSeconds )
GAMESTATE->SetProcessedTimingData(nullptr);
}
void Steps::CalculateMeasureInfo()
{
if(parent != nullptr)
{
return;
}
if( m_bAreCachedMeasureInfoJustLoaded )
{
m_bAreCachedMeasureInfoJustLoaded = false;
return;
}
NoteData tempNoteData;
this->GetNoteData( tempNoteData );
FOREACH_PlayerNumber(pn)
m_CachedMeasureInfo[pn]
.Zero();
GAMESTATE->SetProcessedTimingData(this->GetTimingData());
if( tempNoteData.IsComposite() )
{
std::vector<NoteData> 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], m_CachedMeasureInfo[pn]);
}
}
else if (GAMEMAN->GetStepsTypeInfo(this->m_StepsType).m_StepsTypeCategory == StepsTypeCategory_Couple)
{
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, m_CachedMeasureInfo[PLAYER_1]);
NoteDataUtil::ShiftTracks(tempNoteData, tracks);
tempNoteData.SetNumTracks(tracks);
MeasureInfo::CalculateMeasureInfo(tempNoteData, m_CachedMeasureInfo[PLAYER_2]);
}
else
{
MeasureInfo::CalculateMeasureInfo(tempNoteData, m_CachedMeasureInfo[0]);
std::fill_n( m_CachedMeasureInfo + 1, NUM_PLAYERS-1, m_CachedMeasureInfo[0] );
}
GAMESTATE->SetProcessedTimingData(nullptr);
}
void Steps::ChangeFilenamesForCustomSong()
{
m_sFilename= custom_songify_path(m_sFilename);
@@ -503,6 +556,7 @@ void Steps::DeAutogen( bool bCopyNoteData )
m_Difficulty = Real()->m_Difficulty;
m_iMeter = Real()->m_iMeter;
std::copy( Real()->m_CachedRadarValues, Real()->m_CachedRadarValues + NUM_PLAYERS, m_CachedRadarValues );
std::copy( Real()->m_CachedMeasureInfo, Real()->m_CachedMeasureInfo + NUM_PLAYERS, m_CachedMeasureInfo );
m_sCredit = Real()->m_sCredit;
parent = nullptr;
@@ -631,6 +685,13 @@ void Steps::SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] )
m_bAreCachedRadarValuesJustLoaded = true;
}
void Steps::SetCachedMeasureInfo(const MeasureInfo ms[NUM_PLAYERS])
{
DeAutogen();
std::copy(ms, ms + NUM_PLAYERS, m_CachedMeasureInfo);
m_bAreCachedMeasureInfoJustLoaded = true;
}
RString Steps::GenerateChartKey()
{
ChartKey = this->GenerateChartKey(*m_pNoteData, this->GetTimingData());
@@ -813,6 +874,40 @@ public:
return 1;
}
static int GetNPSPerMeasure(T *p, lua_State *L)
{
PlayerNumber pn = PLAYER_1;
if (!lua_isnil(L, 1)) {
pn = Enum::Check<PlayerNumber>(L, 1);
}
MeasureInfo &ts = const_cast<MeasureInfo &>(p->GetMeasureInfo(pn));
LuaHelpers::CreateTableFromArray(ts.npsPerMeasure, L);
return 1;
}
static int GetNotesPerMeasure(T *p, lua_State * L)
{
PlayerNumber pn = PLAYER_1;
if (!lua_isnil(L, 1)) {
pn = Enum::Check<PlayerNumber>(L, 1);
}
MeasureInfo &ts = const_cast<MeasureInfo &>(p->GetMeasureInfo(pn));
LuaHelpers::CreateTableFromArray(ts.notesPerMeasure, L);
return 1;
}
static int GetPeakNPS(T *p, lua_State *L)
{
PlayerNumber pn = PLAYER_1;
if (!lua_isnil(L, 1)) {
pn = Enum::Check<PlayerNumber>(L, 1);
}
MeasureInfo &ts = const_cast<MeasureInfo &>(p->GetMeasureInfo(pn));
lua_pushnumber(L, ts.peakNps);
return 1;
}
static int GetColumnCues(T *p, lua_State*L)
{
float minDuration = 1.5;
@@ -856,7 +951,7 @@ public:
}
return 1;
}
LunaSteps()
{
ADD_METHOD( GetAuthorCredit );
@@ -883,6 +978,9 @@ public:
ADD_METHOD( PredictMeter );
ADD_METHOD( GetDisplayBPMType );
ADD_METHOD( GetColumnCues );
ADD_METHOD( GetNPSPerMeasure );
ADD_METHOD( GetNotesPerMeasure );
ADD_METHOD( GetPeakNPS );
}
};
+10 -1
View File
@@ -10,6 +10,7 @@
#include "RageUtil_AutoPtr.h"
#include "TimingData.h"
#include "ColumnCues.h"
#include "MeasureInfo.h"
#include <vector>
@@ -140,6 +141,7 @@ public:
void SetLoadedFromProfile( ProfileSlot slot ) { m_LoadedFromProfile = slot; }
void SetMeter( int meter );
void SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] );
void SetCachedMeasureInfo(const MeasureInfo ms[NUM_PLAYERS]);
float PredictMeter() const;
unsigned GetHash() const;
@@ -164,6 +166,9 @@ public:
void TidyUpData();
void CalculateRadarValues( float fMusicLengthSeconds );
void CalculateMeasureInfo();
const MeasureInfo &GetMeasureInfo(PlayerNumber pn) const { return Real()->m_CachedMeasureInfo[pn]; }
/**
* @brief The TimingData used by the Steps.
*
@@ -211,7 +216,7 @@ public:
{
return join(":", this->m_sAttackString);
}
std::vector<ColumnCue> GetColumnCues(float minDuration);
private:
@@ -256,6 +261,10 @@ private:
/** @brief The radar values used for each player. */
RadarValues m_CachedRadarValues[NUM_PLAYERS];
bool m_bAreCachedRadarValuesJustLoaded;
mutable MeasureInfo m_CachedMeasureInfo[NUM_PLAYERS];
bool m_bAreCachedMeasureInfoJustLoaded;
/** @brief The name of the person who created the Steps. */
RString m_sCredit;
/** @brief The name of the chart. */