Files
itgmania212121/stepmania/src/SongUtil.cpp
T

455 lines
14 KiB
C++
Raw Normal View History

#include "global.h"
#include "SongUtil.h"
#include "song.h"
#include "Steps.h"
#include "GameState.h"
2004-06-28 07:26:00 +00:00
#include "Style.h"
#include "ProfileManager.h"
2005-07-01 05:07:22 +00:00
#include "Profile.h"
#include "PrefsManager.h"
#include "SongManager.h"
2004-05-22 01:37:00 +00:00
#include "XmlFile.h"
2005-04-25 22:44:32 +00:00
#include "Foreach.h"
2005-05-08 11:55:56 +00:00
#include "UnlockManager.h"
/////////////////////////////////////
// Sorting
/////////////////////////////////////
/* Just calculating GetNumTimesPlayed within the sort is pretty slow, so let's precompute
* it. (This could be generalized with a template.) */
map<const Song*, CString> song_sort_val;
bool CompareSongPointersBySortValueAscending(const Song *pSong1, const Song *pSong2)
{
return song_sort_val[pSong1] < song_sort_val[pSong2];
}
bool CompareSongPointersBySortValueDescending(const Song *pSong1, const Song *pSong2)
{
return song_sort_val[pSong1] > song_sort_val[pSong2];
}
CString SongUtil::MakeSortString( CString s )
{
s.MakeUpper();
// Make sure that non-alphanumeric strings are placed at the very end.
if( s.size() > 0 )
{
if( s[0] == '.' ) // ".59"
s.erase(s.begin());
if( (s[0] < 'A' || s[0] > 'Z') && (s[0] < '0' || s[0] > '9') )
s = char(126) + s;
}
return s;
}
bool CompareSongPointersByTitle(const Song *pSong1, const Song *pSong2)
{
// Prefer transliterations to full titles
CString s1 = pSong1->GetTranslitMainTitle();
CString s2 = pSong2->GetTranslitMainTitle();
if( s1 == s2 )
{
s1 = pSong1->GetTranslitSubTitle();
s2 = pSong2->GetTranslitSubTitle();
}
s1 = SongUtil::MakeSortString(s1);
s2 = SongUtil::MakeSortString(s2);
int ret = strcmp( s1, s2 );
if(ret < 0) return true;
if(ret > 0) return false;
/* The titles are the same. Ensure we get a consistent ordering
* by comparing the unique SongFilePaths. */
return pSong1->GetSongFilePath().CompareNoCase(pSong2->GetSongFilePath()) < 0;
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByTitle( vector<Song*> &vpSongsInOut )
{
2005-04-25 22:44:32 +00:00
sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByTitle );
}
bool CompareSongPointersByBPM(const Song *pSong1, const Song *pSong2)
{
DisplayBpms bpms1, bpms2;
pSong1->GetDisplayBpms( bpms1 );
pSong2->GetDisplayBpms( bpms2 );
if( bpms1.GetMax() < bpms2.GetMax() )
return true;
if( bpms1.GetMax() > bpms2.GetMax() )
return false;
return CompareCStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() );
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByBPM( vector<Song*> &vpSongsInOut )
{
2005-04-25 22:44:32 +00:00
sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByBPM );
}
void AppendOctal( int n, int digits, CString &out )
{
for( int p = digits-1; p >= 0; --p )
{
const int shift = p*3;
int n2 = (n >> shift) & 0x7;
out.insert( out.end(), (char) (n2+'0') );
}
}
bool CompDescending( const pair<Song *, CString> &a, const pair<Song *, CString> &b )
{ return a.second > b.second; }
2005-06-28 08:11:30 +00:00
bool CompAscending( const pair<Song *, CString> &a, const pair<Song *, CString> &b )
{ return a.second < b.second; }
2005-06-28 08:11:30 +00:00
void SongUtil::SortSongPointerArrayByGrades( vector<Song*> &vpSongsInOut, bool bDescending )
{
/* Optimize by pre-writing a string to compare, since doing GetNumNotesWithGrade
* inside the sort is too slow. */
typedef pair< Song *, CString > val;
vector<val> vals;
2005-04-25 22:44:32 +00:00
vals.reserve( vpSongsInOut.size() );
2005-04-25 22:44:32 +00:00
for( unsigned i = 0; i < vpSongsInOut.size(); ++i )
{
2005-04-25 22:44:32 +00:00
Song *pSong = vpSongsInOut[i];
int iCounts[NUM_Grade];
2005-08-03 03:23:21 +00:00
const Profile *pProfile = PROFILEMAN->GetMachineProfile();
const Style *pStyle = GAMESTATE->GetCurrentStyle();
StepsType st = pStyle->m_StepsType;
pProfile->GetGrades( pSong, st, iCounts );
CString foo;
foo.reserve(256);
for( int g=Grade_Tier01; g<=Grade_NoData; ++g )
AppendOctal( iCounts[g], 3, foo );
vals.push_back( val(pSong, foo) );
}
2005-06-28 08:11:30 +00:00
sort( vals.begin(), vals.end(), bDescending ? CompDescending : CompAscending );
2005-04-25 22:44:32 +00:00
for( unsigned i = 0; i < vpSongsInOut.size(); ++i )
vpSongsInOut[i] = vals[i].first;
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByArtist( vector<Song*> &vpSongsInOut )
{
2005-04-25 22:44:32 +00:00
for( unsigned i = 0; i < vpSongsInOut.size(); ++i )
song_sort_val[vpSongsInOut[i]] = MakeSortString( vpSongsInOut[i]->GetTranslitArtist() );
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending );
}
2004-05-25 04:08:21 +00:00
/* This is for internal use, not display; sorting by Unicode codepoints isn't very
* interesting for display. */
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByDisplayArtist( vector<Song*> &vpSongsInOut )
2004-05-25 04:08:21 +00:00
{
2005-04-25 22:44:32 +00:00
for( unsigned i = 0; i < vpSongsInOut.size(); ++i )
song_sort_val[vpSongsInOut[i]] = MakeSortString( vpSongsInOut[i]->GetDisplayArtist() );
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending );
2004-05-25 04:08:21 +00:00
}
2005-03-02 01:48:38 +00:00
static int CompareSongPointersByGenre(const Song *pSong1, const Song *pSong2)
{
return pSong1->m_sGenre < pSong2->m_sGenre;
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByGenre( vector<Song*> &vpSongsInOut )
2005-03-02 01:48:38 +00:00
{
2005-04-25 22:44:32 +00:00
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByGenre );
2005-03-02 01:48:38 +00:00
}
2005-05-09 17:33:22 +00:00
int SongUtil::CompareSongPointersByGroup(const Song *pSong1, const Song *pSong2)
{
return pSong1->m_sGroupName < pSong2->m_sGroupName;
}
int CompareSongPointersByGroupAndTitle(const Song *pSong1, const Song *pSong2)
{
const CString &sGroup1 = pSong1->m_sGroupName;
const CString &sGroup2 = pSong2->m_sGroupName;
if( sGroup1 < sGroup2 )
return true;
if( sGroup1 > sGroup2 )
return false;
/* Same group; compare by name. */
return CompareSongPointersByTitle( pSong1, pSong2 );
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByGroupAndTitle( vector<Song*> &vpSongsInOut )
{
2005-04-25 22:44:32 +00:00
sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByGroupAndTitle );
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByNumPlays( vector<Song*> &vpSongsInOut, ProfileSlot slot, bool bDescending )
{
if( !PROFILEMAN->IsPersistentProfile(slot) )
return; // nothing to do since we don't have data
Profile* pProfile = PROFILEMAN->GetProfile(slot);
2005-04-25 22:44:32 +00:00
SortSongPointerArrayByNumPlays( vpSongsInOut, pProfile, bDescending );
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByNumPlays( vector<Song*> &vpSongsInOut, const Profile* pProfile, bool bDescending )
{
ASSERT( pProfile );
2005-04-25 22:44:32 +00:00
for(unsigned i = 0; i < vpSongsInOut.size(); ++i)
song_sort_val[vpSongsInOut[i]] = ssprintf("%9i", pProfile->GetSongNumTimesPlayed(vpSongsInOut[i]));
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), bDescending ? CompareSongPointersBySortValueDescending : CompareSongPointersBySortValueAscending );
song_sort_val.clear();
}
CString SongUtil::GetSectionNameFromSongAndSort( const Song* pSong, SortOrder so )
{
if( pSong == NULL )
return CString();
switch( so )
{
case SORT_PREFERRED:
return CString();
2005-03-02 01:48:38 +00:00
case SORT_GROUP:
// guaranteed not empty
return pSong->m_sGroupName;
case SORT_TITLE:
case SORT_ARTIST:
{
CString s;
switch( so )
{
case SORT_TITLE: s = pSong->GetTranslitMainTitle(); break;
case SORT_ARTIST: s = pSong->GetTranslitArtist(); break;
default: ASSERT(0);
}
s = MakeSortString(s); // resulting string will be uppercase
if( s.empty() )
return CString();
else if( s[0] >= '0' && s[0] <= '9' )
return "NUM";
else if( s[0] < 'A' || s[0] > 'Z')
return "OTHER";
else
return s.Left(1);
}
2005-03-02 01:48:38 +00:00
case SORT_GENRE:
if( !pSong->m_sGenre.empty() )
return pSong->m_sGenre;
return "N/A";
case SORT_BPM:
{
const int iBPMGroupSize = 20;
DisplayBpms bpms;
pSong->GetDisplayBpms( bpms );
int iMaxBPM = (int)bpms.GetMax();
iMaxBPM += iBPMGroupSize - (iMaxBPM%iBPMGroupSize) - 1;
return ssprintf("%03d-%03d",iMaxBPM-(iBPMGroupSize-1), iMaxBPM);
}
2005-04-15 07:18:40 +00:00
case SORT_POPULARITY:
return CString();
2005-04-15 07:18:40 +00:00
case SORT_TOP_GRADES:
{
int iCounts[NUM_Grade];
2004-06-28 07:26:00 +00:00
PROFILEMAN->GetMachineProfile()->GetGrades( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType, iCounts );
for( int i=Grade_Tier01; i<NUM_Grade; ++i )
{
Grade g = (Grade)i;
if( iCounts[i] > 0 )
return ssprintf( "%4s x %d", GradeToThemedString(g).c_str(), iCounts[i] );
}
return GradeToThemedString( Grade_NoData );
}
case SORT_EASY_METER:
{
2004-06-28 07:26:00 +00:00
Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_EASY);
2005-05-08 11:55:56 +00:00
if( pSteps && !UNLOCKMAN->StepsIsLocked(pSong,pSteps) )
2004-05-24 03:41:39 +00:00
return ssprintf("%02d", pSteps->GetMeter() );
return "N/A";
}
case SORT_MEDIUM_METER:
{
2004-06-28 07:26:00 +00:00
Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_MEDIUM);
2005-05-08 11:55:56 +00:00
if( pSteps && !UNLOCKMAN->StepsIsLocked(pSong,pSteps) )
2004-05-24 03:41:39 +00:00
return ssprintf("%02d", pSteps->GetMeter() );
return "N/A";
}
case SORT_HARD_METER:
{
2004-06-28 07:26:00 +00:00
Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_HARD);
2005-05-08 11:55:56 +00:00
if( pSteps && !UNLOCKMAN->StepsIsLocked(pSong,pSteps) )
2004-05-24 03:41:39 +00:00
return ssprintf("%02d", pSteps->GetMeter() );
return "N/A";
}
case SORT_CHALLENGE_METER:
{
2004-06-28 07:26:00 +00:00
Steps* pSteps = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyle()->m_StepsType,DIFFICULTY_CHALLENGE);
2005-05-08 11:55:56 +00:00
if( pSteps && !UNLOCKMAN->StepsIsLocked(pSong,pSteps) )
2004-05-24 03:41:39 +00:00
return ssprintf("%02d", pSteps->GetMeter() );
return "N/A";
}
case SORT_MODE_MENU:
return CString();
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
default:
ASSERT(0);
return CString();
}
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayBySectionName( vector<Song*> &vpSongsInOut, SortOrder so )
{
2005-04-25 22:44:32 +00:00
for(unsigned i = 0; i < vpSongsInOut.size(); ++i)
{
2005-04-25 22:44:32 +00:00
CString val = GetSectionNameFromSongAndSort( vpSongsInOut[i], so );
/* Make sure NUM comes first and OTHER comes last. */
if( val == "NUM" ) val = "0";
else if( val == "OTHER" ) val = "2";
else val = "1" + MakeSortString(val);
2005-04-25 22:44:32 +00:00
song_sort_val[vpSongsInOut[i]] = val;
}
2005-04-25 22:44:32 +00:00
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending );
song_sort_val.clear();
}
2005-04-25 22:44:32 +00:00
void SongUtil::SortSongPointerArrayByMeter( vector<Song*> &vpSongsInOut, Difficulty dc )
{
song_sort_val.clear();
2005-04-25 22:44:32 +00:00
for(unsigned i = 0; i < vpSongsInOut.size(); ++i)
{
2005-05-08 11:55:56 +00:00
/* Ignore locked steps. */
const Steps* pSteps = vpSongsInOut[i]->GetClosestNotes( GAMESTATE->GetCurrentStyle()->m_StepsType, dc, true );
2005-04-25 22:44:32 +00:00
CString &s = song_sort_val[vpSongsInOut[i]];
2004-06-02 07:07:12 +00:00
s = ssprintf("%03d", pSteps ? pSteps->GetMeter() : 0);
2005-03-12 07:24:47 +00:00
/* Hack: always put tutorial songs first. */
2005-04-25 22:44:32 +00:00
s += ssprintf( "%c", vpSongsInOut[i]->IsTutorial()? '0':'1' );
2005-03-12 07:24:47 +00:00
2005-04-05 01:59:03 +00:00
/*
* pSteps may not be exactly the difficulty we want; for example, we may
* be sorting by Hard difficulty and a song may have no Hard steps.
*
* In this case, we can end up with unintuitive ties; for example, pSteps
* may be Medium with a meter of 5, which will sort it among the 5-meter
* Hard songs. Break the tie, by adding the difficulty to the sort as
* well. That way, we'll always put Medium 5s before Hard 5s. If all
* songs are using the preferred difficulty (dc), this will be a no-op.
*/
s += ssprintf( "%c", (pSteps? pSteps->GetDifficulty():0) + '0' );
2004-06-02 07:07:12 +00:00
if( PREFSMAN->m_bSubSortByNumSteps )
2004-07-11 07:21:33 +00:00
s += ssprintf("%06.0f",pSteps ? pSteps->GetRadarValues()[RADAR_NUM_TAPS_AND_HOLDS] : 0);
}
2005-04-25 22:44:32 +00:00
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending );
}
void SongUtil::SortByMostRecentlyPlayedForMachine( vector<Song*> &vpSongsInOut )
{
Profile *pProfile = PROFILEMAN->GetMachineProfile();
FOREACH_CONST( Song*, vpSongsInOut, s )
{
int iNumTimesPlayed = pProfile->GetSongNumTimesPlayed( *s );
2005-05-02 03:32:14 +00:00
CString val = iNumTimesPlayed ? pProfile->GetSongLastPlayedDateTime(*s).GetString() : "0";
2005-04-25 22:44:32 +00:00
song_sort_val[*s] = val;
}
2005-05-02 03:32:14 +00:00
stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueDescending );
2005-04-25 22:44:32 +00:00
song_sort_val.clear();
}
2005-03-05 20:48:27 +00:00
//////////////////////////////////
// SongID
//////////////////////////////////
void SongID::FromSong( const Song *p )
{
if( p )
sDir = p->GetSongDir();
else
sDir = "";
// HACK for backwards compatibility:
// Strip off leading "/". 2005/05/21 file layer changes added a leading slash.
if( sDir.Left(1) == "/" )
sDir.erase( sDir.begin() );
}
Song *SongID::ToSong() const
{
// HACK for backwards compatibility:
// Re-add the leading "/". 2005/05/21 file layer changes added a leading slash.
CString sDir2 = sDir;
if( sDir2.Left(1) != "/" )
sDir2 = "/" + sDir2;
return SONGMAN->GetSongFromDir( sDir2 );
}
XNode* SongID::CreateNode() const
{
XNode* pNode = new XNode;
2005-01-07 14:28:00 +00:00
pNode->m_sName = "Song";
pNode->AppendAttr( "Dir", sDir );
return pNode;
}
void SongID::LoadFromNode( const XNode* pNode )
{
2005-01-07 14:28:00 +00:00
ASSERT( pNode->m_sName == "Song" );
pNode->GetAttrValue("Dir", sDir);
}
2004-05-08 10:12:10 +00:00
CString SongID::ToString() const
{
return sDir;
}
bool SongID::IsValid() const
{
return !sDir.empty();
}
2004-05-31 21:35:31 +00:00
/*
* (c) 2001-2004 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/