Added MeasureInfo calculations, and loader/writer for #MEASUREINFO cache
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user