Files
itgmania212121/stepmania/src/Song.cpp
T

1683 lines
46 KiB
C++
Raw Normal View History

2003-02-16 04:01:45 +00:00
#include "global.h"
/*
-----------------------------------------------------------------------------
2002-06-24 22:04:31 +00:00
Class: Song
Desc: Holds metadata for a song and the song's step data.
2002-05-19 01:59:48 +00:00
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
2002-06-24 22:04:31 +00:00
Chris Danford
2003-01-16 20:21:31 +00:00
Glenn Maynard
-----------------------------------------------------------------------------
*/
2001-11-03 10:52:42 +00:00
#include "song.h"
2003-08-03 00:13:55 +00:00
#include "Steps.h"
2001-11-03 10:52:42 +00:00
#include "RageUtil.h"
2002-05-01 19:14:55 +00:00
#include "RageLog.h"
2002-06-30 23:19:33 +00:00
#include "IniFile.h"
2002-07-03 03:13:13 +00:00
#include "NoteData.h"
2002-12-13 23:41:46 +00:00
#include "RageSound.h"
2002-07-27 19:29:51 +00:00
#include "RageException.h"
2003-12-21 02:54:23 +00:00
#include "SongManager.h"
#include "SongCacheIndex.h"
#include "GameManager.h"
2002-08-30 04:28:12 +00:00
#include "PrefsManager.h"
2002-09-07 11:45:15 +00:00
#include "StyleDef.h"
#include "GameState.h"
2003-01-16 20:21:31 +00:00
#include "FontCharAliases.h"
2003-02-10 22:59:24 +00:00
#include "TitleSubstitution.h"
2003-06-04 22:42:43 +00:00
#include "BannerCache.h"
#include "Sprite.h"
2003-07-22 07:47:27 +00:00
#include "arch/arch.h"
#include "RageFile.h"
2003-12-21 07:44:41 +00:00
#include "RageFileManager.h"
2003-08-07 06:36:34 +00:00
#include "NoteDataUtil.h"
2003-12-04 20:43:22 +00:00
#include "SDL_utils.h"
2002-06-14 22:25:22 +00:00
2002-09-06 23:36:04 +00:00
#include "NotesLoaderSM.h"
#include "NotesLoaderDWI.h"
#include "NotesLoaderBMS.h"
#include "NotesLoaderKSF.h"
#include "NotesWriterDWI.h"
2002-12-14 00:21:22 +00:00
#include "NotesWriterSM.h"
2001-11-03 10:52:42 +00:00
#include "LyricsLoader.h"
#include "SDL.h"
2003-07-28 08:23:29 +00:00
#include <set>
2003-12-10 09:02:55 +00:00
#define CACHE_DIR "Cache/"
2003-12-30 03:40:29 +00:00
const int FILE_CACHE_VERSION = 133; // increment this when Song or Steps changes to invalidate cache
2003-02-19 23:49:57 +00:00
const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f;
2001-12-28 10:15:59 +00:00
2001-12-19 01:50:57 +00:00
2002-07-11 19:02:26 +00:00
2002-10-24 07:56:20 +00:00
int CompareBackgroundChanges(const BackgroundChange &seg1, const BackgroundChange &seg2)
2002-07-11 19:02:26 +00:00
{
2002-10-24 07:56:20 +00:00
return seg1.m_fStartBeat < seg2.m_fStartBeat;
2002-07-11 19:02:26 +00:00
}
2003-01-03 05:56:28 +00:00
void SortBackgroundChangesArray( vector<BackgroundChange> &arrayBackgroundChanges )
2002-07-11 19:02:26 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arrayBackgroundChanges.begin(), arrayBackgroundChanges.end(), CompareBackgroundChanges );
2001-12-28 10:15:59 +00:00
}
2001-12-19 01:50:57 +00:00
2001-11-03 10:52:42 +00:00
//////////////////////////////
// Song
//////////////////////////////
Song::Song()
{
m_bChangedSinceSave = false;
m_fMusicSampleStartSeconds = -1;
m_fMusicSampleLengthSeconds = DEFAULT_MUSIC_SAMPLE_LENGTH;
2002-06-29 11:59:09 +00:00
m_fMusicLengthSeconds = 0;
2002-07-11 19:02:26 +00:00
m_fFirstBeat = -1;
m_fLastBeat = -1;
2002-08-30 04:28:12 +00:00
m_SelectionDisplay = SHOW_ALWAYS;
m_DisplayBPMType = DISPLAY_ACTUAL;
2003-06-16 17:28:58 +00:00
m_fSpecifiedBPMMin = 0;
m_fSpecifiedBPMMax = 0;
m_bIsSymLink = false;
2003-12-30 03:40:29 +00:00
m_bHasMusic = false;
m_bHasBanner = false;
}
2001-11-03 10:52:42 +00:00
2002-06-14 22:25:22 +00:00
Song::~Song()
{
for( unsigned i=0; i<m_apNotes.size(); i++ )
2002-07-11 19:02:26 +00:00
SAFE_DELETE( m_apNotes[i] );
2002-06-14 22:25:22 +00:00
2002-10-24 20:15:24 +00:00
m_apNotes.clear();
2002-06-14 22:25:22 +00:00
}
2003-06-24 20:04:35 +00:00
/* Reset to an empty song. */
void Song::Reset()
{
for( unsigned i=0; i<m_apNotes.size(); i++ )
SAFE_DELETE( m_apNotes[i] );
m_apNotes.clear();
Song empty;
*this = empty;
2003-12-21 02:54:23 +00:00
/* Courses cache Notes* pointers. On the off chance that this isn't the last
* thing this screen does, clear that cache. */
SONGMAN->Cleanup();
2003-06-24 20:04:35 +00:00
}
2001-12-19 01:50:57 +00:00
void Song::AddBackgroundChange( BackgroundChange seg )
2002-06-24 22:04:31 +00:00
{
2002-10-31 04:23:39 +00:00
m_BackgroundChanges.push_back( seg );
SortBackgroundChangesArray( m_BackgroundChanges );
2002-06-24 22:04:31 +00:00
}
void Song::AddLyricSegment( LyricSegment seg )
{
m_LyricSegments.push_back( seg );
}
2003-12-18 04:48:26 +00:00
void Song::GetDisplayBPM( float &fMinBPMOut, float &fMaxBPMOut ) const
2002-08-18 23:20:18 +00:00
{
2003-12-18 04:48:26 +00:00
if( m_DisplayBPMType == DISPLAY_SPECIFIED )
2001-12-28 10:15:59 +00:00
{
2003-12-18 04:48:26 +00:00
fMinBPMOut = m_fSpecifiedBPMMin;
fMaxBPMOut = m_fSpecifiedBPMMax;
}
2003-12-18 04:48:26 +00:00
else
{
2003-12-18 04:48:26 +00:00
m_Timing.GetActualBPM( fMinBPMOut, fMaxBPMOut );
}
2003-12-18 04:48:26 +00:00
}
2003-12-18 04:48:26 +00:00
CString Song::GetBackgroundAtBeat( float fBeat ) const
{
unsigned i;
for( i=0; i<m_BackgroundChanges.size()-1; i++ )
if( m_BackgroundChanges[i+1].m_fStartBeat > fBeat )
2003-02-09 03:32:04 +00:00
break;
2003-12-18 04:48:26 +00:00
return m_BackgroundChanges[i].m_sBGName;
2002-04-16 17:31:00 +00:00
}
2003-12-18 04:48:26 +00:00
CString Song::GetCacheFilePath() const
2002-05-19 01:59:48 +00:00
{
2003-12-10 09:26:05 +00:00
return ssprintf( CACHE_DIR "Songs/%u", GetHashForString(m_sSongDir) );
2002-05-19 01:59:48 +00:00
}
/* Get a path to the SM containing data for this song. It might
* be a cache file. */
const CString &Song::GetSongFilePath() const
2002-07-02 00:27:58 +00:00
{
ASSERT ( m_sSongFileName.GetLength() != 0 );
return m_sSongFileName;
2002-07-02 00:27:58 +00:00
}
2002-09-11 05:15:46 +00:00
NotesLoader *Song::MakeLoader( CString sDir ) const
2001-11-03 10:52:42 +00:00
{
2002-09-11 05:15:46 +00:00
NotesLoader *ret;
/* Actually, none of these have any persistant data, so we
* could optimize this, but since they don't have any data,
* there's no real point ... */
ret = new SMLoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
ret = new DWILoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2002-09-11 05:15:46 +00:00
ret = new BMSLoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
ret = new KSFLoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
return NULL;
}
2002-04-16 17:31:00 +00:00
2003-07-28 08:23:29 +00:00
/* Hack: This should be a parameter to TidyUpData, but I don't want to
* pull in <set> into Song.h, which is heavily used. */
static set<istring> BlacklistedImages;
2003-07-28 08:23:29 +00:00
2003-12-30 04:26:39 +00:00
/*
2003-12-31 09:32:21 +00:00
* If PREFSMAN->m_bFastLoad is true, always load from cache if possible. Don't read
* the contents of sDir if we can avoid it. That means we can't call HasMusic(),
2003-12-30 04:26:39 +00:00
* HasBanner() or GetHashForDirectory().
*
* If true, check the directory hash and reload the song from scratch if it's changed.
*/
bool Song::LoadFromSongDir( CString sDir )
{
2003-04-25 00:27:30 +00:00
// LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.c_str() );
2003-06-24 20:04:35 +00:00
ASSERT( sDir != "" );
// make sure there is a trailing slash at the end of sDir
2003-12-10 09:26:05 +00:00
if( sDir.Right(1) != "/" )
sDir += "/";
// save song dir
m_sSongDir = sDir;
// save group name
CStringArray sDirectoryParts;
2003-12-10 09:26:05 +00:00
split( m_sSongDir, "/", sDirectoryParts, false );
ASSERT( sDirectoryParts.size() >= 4 ); /* Songs/Slow/Taps/ */
m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item
ASSERT( m_sGroupName != "" );
//
// First look in the cache for this song (without loading NoteData)
//
2002-09-07 10:22:49 +00:00
unsigned uDirHash = SONGINDEX->GetCacheHash(m_sSongDir);
2003-12-30 04:26:39 +00:00
bool bUseCache = true;
if( !DoesFileExist(GetCacheFilePath()) )
bUseCache = false;
2003-12-31 09:32:21 +00:00
if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sSongDir) != uDirHash )
2003-12-30 04:26:39 +00:00
bUseCache = false; // this cache is out of date
if( bUseCache )
{
2003-04-25 00:27:30 +00:00
// LOG->Trace( "Loading '%s' from cache file '%s'.", m_sSongDir.c_str(), GetCacheFilePath().c_str() );
2002-09-06 23:36:04 +00:00
SMLoader ld;
ld.LoadFromSMFile( GetCacheFilePath(), *this, true );
}
else
{
//
// There was no entry in the cache for this song, or it was out of date.
// Let's load it from a file, then write a cache entry.
//
NotesLoader *ld = MakeLoader( sDir );
if(!ld)
{
LOG->Warn( "Couldn't find any SM, DWI, BMS, or KSF files in '%s'. This is not a valid song directory.", sDir.c_str() );
return false;
}
bool success = ld->LoadFromDir( sDir, *this );
BlacklistedImages = ld->GetBlacklistedImages();
delete ld;
if(!success)
return false;
TidyUpData();
// save a cache file so we don't have to parse it all over again next time
SaveToCacheFile();
}
2003-06-15 01:53:51 +00:00
/* Load the cached banners, if it's not loaded already. */
2003-12-30 03:40:29 +00:00
if( m_bHasBanner )
2003-06-15 00:57:05 +00:00
BANNERCACHE->LoadBanner( GetBannerPath() );
2003-01-02 22:10:51 +00:00
/* Add AutoGen pointers. (These aren't cached.) */
AddAutoGenNotes();
2003-12-30 03:40:29 +00:00
if( !m_bHasMusic )
return false; // don't load this song
else
return true; // do load this song
2001-11-03 10:52:42 +00:00
}
2003-06-24 20:04:35 +00:00
void Song::RevertFromDisk()
{
// Ugly: When we re-load the song, the Steps* will change.
// Fix the GAMESTATE->m_CurNotes after reloading.
StepsType st[NUM_PLAYERS];
Difficulty dc[NUM_PLAYERS];
for( int p = 0; p < NUM_PLAYERS; ++p )
{
if( GAMESTATE->m_pCurNotes[p] == NULL )
{
st[p] = STEPS_TYPE_INVALID;
dc[p] = DIFFICULTY_INVALID;
}
else
{
st[p] = GAMESTATE->m_pCurNotes[p]->m_StepsType;
dc[p] = GAMESTATE->m_pCurNotes[p]->GetDifficulty();
}
}
2003-07-02 01:38:00 +00:00
2003-06-24 20:04:35 +00:00
const CString dir = GetSongDir();
/* Erase all existing data. */
Reset();
LoadFromSongDir( dir );
2003-07-02 01:38:00 +00:00
2003-07-02 01:38:00 +00:00
if( GAMESTATE->m_pCurSong == this )
{
for( int p = 0; p < NUM_PLAYERS; ++p )
2003-07-02 01:38:00 +00:00
{
if( st[p] == STEPS_TYPE_INVALID || dc[p] == DIFFICULTY_INVALID )
continue; // skip
GAMESTATE->m_pCurNotes[p] = this->GetStepsByDifficulty( st[p], dc[p], false );
ASSERT( GAMESTATE->m_pCurNotes[p] ); // we had something selected before reloading, so it better still be there after!
2003-07-02 01:38:00 +00:00
}
}
2003-06-24 20:04:35 +00:00
}
2001-12-28 10:15:59 +00:00
2003-06-22 20:59:57 +00:00
static void GetImageDirListing( CString sPath, CStringArray &AddTo, bool bReturnPathToo=false )
{
GetDirListing( sPath + ".png", AddTo, false, bReturnPathToo );
GetDirListing( sPath + ".jpg", AddTo, false, bReturnPathToo );
GetDirListing( sPath + ".bmp", AddTo, false, bReturnPathToo );
GetDirListing( sPath + ".gif", AddTo, false, bReturnPathToo );
}
static CString RemoveInitialWhitespace( CString s )
{
unsigned i = s.find_first_not_of(" \t\r\n");
if( i != s.npos )
s.erase( 0, i );
return s;
}
/* This is called within TidyUpData, before autogen notes are added. */
static void DeleteDuplicateSteps( Song *song, vector<Steps*> &vSteps )
{
/* vSteps have the same StepsType and Difficulty. Delete them if they have the
* same m_sDescription, m_iMeter and SMNoteData. */
CHECKPOINT;
for( unsigned i=0; i<vSteps.size(); i++ )
{
CHECKPOINT;
const Steps *s1 = vSteps[i];
2003-10-04 00:47:57 +00:00
for( unsigned j=i+1; j<vSteps.size(); j++ )
{
CHECKPOINT;
const Steps *s2 = vSteps[j];
2003-10-04 00:47:57 +00:00
if( s1->GetDescription() != s2->GetDescription() )
continue;
if( s1->GetMeter() != s2->GetMeter() )
continue;
/* Compare, ignoring whitespace. */
2003-11-17 03:38:24 +00:00
CString sSMNoteData1, sSMAttackData1;
s1->GetSMNoteData( sSMNoteData1, sSMAttackData1 );
CString sSMNoteData2, sSMAttackData2;
s2->GetSMNoteData( sSMNoteData2, sSMAttackData2 );
if( RemoveInitialWhitespace(sSMNoteData1) != RemoveInitialWhitespace(sSMNoteData2) )
continue;
LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\" and meter \"%i\"",
s2, song->GetSongDir().c_str(), s1->GetDescription().c_str(), s1->GetMeter() );
/* Don't use RemoveNotes; autogen notes havn't yet been created and it'll
* create them. */
for( int k=song->m_apNotes.size()-1; k>=0; k-- )
{
if( song->m_apNotes[k] == s2 )
{
delete song->m_apNotes[k];
song->m_apNotes.erase( song->m_apNotes.begin()+k );
break;
}
}
vSteps.erase(vSteps.begin()+j);
--j;
}
}
}
static bool ImageIsLoadable( const CString &sPath )
{
2003-12-04 20:43:22 +00:00
SDL_Surface *img = SDL_LoadImage( sPath );
if( !img )
{
LOG->Warn( "Error loading song image \"%s\": %s", sPath.c_str(), SDL_GetError() );
return false;
}
SDL_FreeSurface( img );
return true;
}
2003-12-11 07:26:59 +00:00
/* Fix up song paths. If there's a leading "./", be sure to keep it: it's
* a signal that the path is from the root directory, not the song directory. */
void FixupPath( CString &path )
{
/* Replace backslashes with slashes in all paths. */
FixSlashesInPlace( path );
bool StartsWithDot = false;
if( path.Left(2) == "./" )
StartsWithDot = true;
CollapsePath( path );
/* Many imported files contain erroneous whitespace before or after
* filenames. Paths usually don't actually start or end with spaces,
* so let's just remove it. */
TrimLeft( path );
TrimRight( path );
if( StartsWithDot )
path = "./" + path;
}
2003-07-28 08:23:29 +00:00
/* Songs in BlacklistImages will never be autodetected as song images. */
2001-11-25 04:31:44 +00:00
void Song::TidyUpData()
2001-11-03 10:52:42 +00:00
{
if( !HasMusic() )
{
CStringArray arrayPossibleMusic;
GetDirListing( m_sSongDir + CString("*.mp3"), arrayPossibleMusic );
GetDirListing( m_sSongDir + CString("*.ogg"), arrayPossibleMusic );
GetDirListing( m_sSongDir + CString("*.wav"), arrayPossibleMusic );
if( !arrayPossibleMusic.empty() )
{
int idx = 0;
/* If the first song is "intro", and we have more than one available,
* don't use it--it's probably a KSF intro music file, which we don't support. */
if( arrayPossibleMusic.size() > 1 &&
!arrayPossibleMusic[0].Left(5).CompareNoCase("intro") )
++idx;
// we found a match
m_sMusicFile = arrayPossibleMusic[idx];
}
}
/* This must be done before radar calculation. */
if( HasMusic() )
{
RageSound sound;
sound.Load( GetMusicPath(), false ); /* don't pre-cache */
2003-01-02 07:58:34 +00:00
m_fMusicLengthSeconds = sound.GetLengthSeconds();
2003-02-10 21:21:48 +00:00
if(m_fMusicLengthSeconds == -1)
{
2003-02-10 21:21:48 +00:00
/* It failed; bad file or something. It's already logged a warning,
* so just set the file to 0 seconds. */
m_fMusicLengthSeconds = 0;
} else if(m_fMusicLengthSeconds == 0) {
2003-04-25 00:27:30 +00:00
LOG->Warn("File %s is empty?", GetMusicPath().c_str());
}
}
else // ! HasMusic()
{
m_fMusicLengthSeconds = 100; // guess
2003-09-19 01:06:20 +00:00
LOG->Warn("Song \"%s\" has no music file; guessing at %f seconds", this->GetSongDir().c_str(), m_fMusicLengthSeconds);
}
2003-02-10 21:21:48 +00:00
if(m_fMusicLengthSeconds < 0)
{
2003-09-19 01:06:20 +00:00
LOG->Warn( "File %s has negative length? (%f)", GetMusicPath().c_str(), m_fMusicLengthSeconds );
2003-02-10 21:21:48 +00:00
m_fMusicLengthSeconds = 0;
}
/* Generate these before we autogen notes, so the new notes can inherit
* their source's values. */
ReCalculateRadarValuesAndLastBeat();
2002-10-31 08:05:13 +00:00
TrimRight(m_sMainTitle);
2003-03-04 06:25:11 +00:00
if( m_sMainTitle == "" )
{
/* Use the song directory name. */
CString SongDir = this->GetSongDir();
vector<CString> parts;
2003-12-10 09:26:05 +00:00
split(SongDir, "/", parts);
2003-03-04 06:25:11 +00:00
ASSERT(parts.size() > 0);
NotesLoader::GetMainAndSubTitlesFromFullTitle( parts[parts.size()-1], m_sMainTitle, m_sSubTitle );
}
2002-10-31 08:05:13 +00:00
TrimRight(m_sSubTitle);
2002-04-28 20:42:32 +00:00
if( m_sArtist == "" ) m_sArtist = "Unknown artist";
TranslateTitles();
2003-01-12 04:59:40 +00:00
2003-12-18 04:48:26 +00:00
if( m_Timing.m_BPMSegments.empty() )
2003-03-04 05:59:56 +00:00
{
/* XXX: Once we have a way to display warnings that the user actually
* cares about (unlike most warnings), this should be one of them. */
LOG->Warn( "No BPM segments specified in '%s%s', default provided.",
2003-04-25 00:27:30 +00:00
m_sSongDir.c_str(), m_sSongFileName.c_str() );
2003-03-04 05:59:56 +00:00
2003-12-18 04:48:26 +00:00
m_Timing.AddBPMSegment( BPMSegment(0, 60) );
2003-03-04 05:59:56 +00:00
}
2001-11-03 10:52:42 +00:00
/* Only automatically set the sample time if there was no sample length
* (m_fMusicSampleStartSeconds == -1). */
/* We don't want to test if m_fMusicSampleStartSeconds == 0, since some
* people really do want the sample to start at the very beginning of the song. */
/* Having a sample start of 0 sounds terrible for most songs because because
* of the silence at the beginning. Many of my files have a 0 for the
* sample start that was not manually set, and I assume other people have the same.
* If there are complaints atou manually-set sample start at 0 is being ignored,
* then change this back. -Chris */
if( m_fMusicSampleStartSeconds == -1 ||
m_fMusicSampleStartSeconds == 0 ||
m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
2003-02-19 23:49:57 +00:00
{
2002-09-11 06:01:07 +00:00
m_fMusicSampleStartSeconds = this->GetElapsedTimeFromBeat( 100 );
2003-02-19 23:49:57 +00:00
if( m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
{
// fix for BAG and other slow songs
2003-02-19 23:49:57 +00:00
int iBeat = (int)(m_fLastBeat/2);
/* Er. I see that this truncates the beat down to a multiple
* of 10, but what's the logic behind doing that? (It'd make
* sense to use a multiple of 4, so we try to line up to a
* measure ...) -glenn */
2003-02-19 23:49:57 +00:00
iBeat = iBeat - iBeat%10;
m_fMusicSampleStartSeconds = this->GetElapsedTimeFromBeat( (float)iBeat );
2003-02-19 23:49:57 +00:00
}
}
2002-09-11 06:01:07 +00:00
2003-01-12 04:24:38 +00:00
/* Some DWIs have lengths in ms when they meant seconds, eg. #SAMPLELENGTH:10;.
* If the sample length is way too short, change it. */
2003-02-19 23:49:57 +00:00
if( m_fMusicSampleLengthSeconds < 3 || m_fMusicSampleLengthSeconds > 30 )
m_fMusicSampleLengthSeconds = DEFAULT_MUSIC_SAMPLE_LENGTH;
2003-01-12 04:24:38 +00:00
2002-07-11 19:02:26 +00:00
//
// Here's the problem: We have a directory full of images. We want to determine which
// image is the banner, which is the background, and which is the CDTitle.
//
CHECKPOINT_M( "Looking for images..." );
2003-12-11 07:26:59 +00:00
FixupPath( m_sSongDir );
FixupPath( m_sMusicFile );
FixupPath( m_sBannerFile );
FixupPath( m_sLyricsFile );
FixupPath( m_sBackgroundFile );
FixupPath( m_sCDTitleFile );
2003-04-20 23:45:01 +00:00
2002-07-11 19:02:26 +00:00
//
// First, check the file name for hints.
//
2002-07-03 21:27:26 +00:00
if( !HasBanner() )
2001-11-03 10:52:42 +00:00
{
2003-12-30 03:40:29 +00:00
/* If a nonexistant banner file is specified, and we can't find a replacement,
* don't wipe out the old value. */
// m_sBannerFile = "";
2001-12-19 14:56:22 +00:00
2002-07-11 19:02:26 +00:00
// find an image with "banner" in the file name
2001-11-25 04:31:44 +00:00
CStringArray arrayPossibleBanners;
2003-06-22 20:59:57 +00:00
GetImageDirListing( m_sSongDir + "*banner*", arrayPossibleBanners );
/* Some people do things differently for the sake of being different. Don't
* match eg. abnormal, numbness. */
GetImageDirListing( m_sSongDir + "* BN", arrayPossibleBanners );
if( !arrayPossibleBanners.empty() )
2002-07-11 19:02:26 +00:00
m_sBannerFile = arrayPossibleBanners[0];
}
2001-11-25 04:31:44 +00:00
2002-07-11 19:02:26 +00:00
if( !HasBackground() )
{
// m_sBackgroundFile = "";
2001-12-19 14:56:22 +00:00
2002-07-11 19:02:26 +00:00
// find an image with "bg" or "background" in the file name
CStringArray arrayPossibleBGs;
2003-06-22 20:59:57 +00:00
GetImageDirListing( m_sSongDir + "*bg*", arrayPossibleBGs );
GetImageDirListing( m_sSongDir + "*background*", arrayPossibleBGs );
if( !arrayPossibleBGs.empty() )
2002-07-11 19:02:26 +00:00
m_sBackgroundFile = arrayPossibleBGs[0];
}
2001-12-19 14:56:22 +00:00
2002-07-11 19:02:26 +00:00
if( !HasCDTitle() )
{
// find an image with "cdtitle" in the file name
CStringArray arrayPossibleCDTitles;
2003-06-22 20:59:57 +00:00
GetImageDirListing( m_sSongDir + "*cdtitle*", arrayPossibleCDTitles );
if( !arrayPossibleCDTitles.empty() )
2002-07-11 19:02:26 +00:00
m_sCDTitleFile = arrayPossibleCDTitles[0];
2001-11-03 10:52:42 +00:00
}
2002-01-16 10:01:32 +00:00
2003-03-19 20:01:33 +00:00
if( !HasLyrics() )
{
2003-03-19 20:01:33 +00:00
// Check if there is a lyric file in here
CStringArray arrayLyricFiles;
GetDirListing(m_sSongDir + CString("*.lrc"), arrayLyricFiles );
if( !arrayLyricFiles.empty() )
2003-03-19 20:01:33 +00:00
m_sLyricsFile = arrayLyricFiles[0];
}
/* For any images we have now, make sure they're loadable, so we don't throw with
* "can't load image" later. Do this before the image search below, to avoid redundant
* image decodes. XXX: Images in BGA scripts? */
if( HasBanner() && !ImageIsLoadable( GetBannerPath() ) )
m_sBannerFile = "";
if( HasBackground() && !ImageIsLoadable( GetBackgroundPath() ) )
m_sBackgroundFile = "";
if( HasCDTitle() && !ImageIsLoadable( GetCDTitlePath() ) )
m_sCDTitleFile = "";
2002-07-11 19:02:26 +00:00
//
// Now, For the images we still haven't found, look at the image dimensions of the remaining unclassified images.
//
CStringArray arrayImages;
2003-06-22 20:59:57 +00:00
GetImageDirListing( m_sSongDir + "*", arrayImages );
unsigned i;
for( i=0; i<arrayImages.size(); i++ ) // foreach image
2001-11-03 10:52:42 +00:00
{
if( m_sBannerFile != "" && m_sCDTitleFile != "" && m_sBackgroundFile != "" )
break; /* done */
// ignore DWI "-char" graphics
if( BlacklistedImages.find( arrayImages[i].c_str() ) != BlacklistedImages.end() )
continue; // skip
2002-07-11 19:02:26 +00:00
// Skip any image that we've already classified
2002-07-23 01:41:40 +00:00
if( HasBanner() && stricmp(m_sBannerFile, arrayImages[i])==0 )
2002-07-11 19:02:26 +00:00
continue; // skip
2001-12-19 14:56:22 +00:00
2002-07-23 01:41:40 +00:00
if( HasBackground() && stricmp(m_sBackgroundFile, arrayImages[i])==0 )
2002-07-11 19:02:26 +00:00
continue; // skip
2001-12-19 14:56:22 +00:00
2002-07-23 01:41:40 +00:00
if( HasCDTitle() && stricmp(m_sCDTitleFile, arrayImages[i])==0 )
2002-07-11 19:02:26 +00:00
continue; // skip
2001-11-25 04:31:44 +00:00
2003-07-22 07:47:27 +00:00
CString sPath = m_sSongDir + arrayImages[i];
2003-12-04 20:43:22 +00:00
SDL_Surface *img = SDL_LoadImage( sPath );
2003-03-05 09:51:32 +00:00
if( !img )
2001-12-19 14:56:22 +00:00
{
2003-07-28 08:23:29 +00:00
LOG->Trace("Couldn't load '%s': %s", sPath.c_str(), SDL_GetError());
2003-03-05 09:51:32 +00:00
continue;
}
2003-03-05 09:51:32 +00:00
const int width = img->w;
const int height = img->h;
SDL_FreeSurface( img );
if( !HasBackground() && width >= 320 && height >= 240 )
{
m_sBackgroundFile = arrayImages[i];
continue;
}
if( !HasBanner() && Sprite::IsDiagonalBanner(width, height) )
{
m_sBannerFile = arrayImages[i];
continue;
}
2003-03-05 09:51:32 +00:00
if( !HasBanner() && 100<=width && width<=320 && 50<=height && height<=240 )
{
m_sBannerFile = arrayImages[i];
continue;
}
2003-06-16 08:03:20 +00:00
/* Some songs have overlarge banners. Check if the ratio is reasonable (over
* 2:1; usually over 3:1), and large (not a cdtitle). */
if( !HasBanner() && width > 200 && float(width) / height > 2.0f )
{
m_sBannerFile = arrayImages[i];
continue;
}
/* Agh. DWI's inline title images are triggering this, resulting in kanji,
* etc., being used as a CDTitle for songs with none. Some sample data
2003-06-04 22:42:43 +00:00
* from random incarnations:
* 42x50 35x50 50x50 144x49
* It looks like ~50 height is what people use to align to DWI's font.
*
* My tallest CDTitle is 44. Let's cut off in the middle and hope for
* the best. */
if( !HasCDTitle() && width<=100 && height<=48 )
2003-03-05 09:51:32 +00:00
{
m_sCDTitleFile = arrayImages[i];
continue;
2001-12-19 14:56:22 +00:00
}
2001-11-03 10:52:42 +00:00
}
2003-12-30 03:40:29 +00:00
/* These will be written to cache, for Song::LoadFromSongDir to use later. */
m_bHasMusic = HasMusic();
m_bHasBanner = HasBanner();
2003-06-04 22:42:43 +00:00
if( HasBanner() )
2003-06-15 00:57:05 +00:00
BANNERCACHE->CacheBanner( GetBannerPath() );
2003-06-04 22:42:43 +00:00
2002-07-03 21:27:26 +00:00
// If no BGChanges are specified and there are movies in the song directory, then assume
// they are DWI style where the movie begins at beat 0.
if( !HasBGChanges() )
2001-11-30 09:38:35 +00:00
{
2002-07-11 19:02:26 +00:00
CStringArray arrayPossibleMovies;
GetDirListing( m_sSongDir + CString("*movie*.avi"), arrayPossibleMovies );
GetDirListing( m_sSongDir + CString("*movie*.mpg"), arrayPossibleMovies );
GetDirListing( m_sSongDir + CString("*movie*.mpeg"), arrayPossibleMovies );
GetDirListing( m_sSongDir + CString("*.avi"), arrayPossibleMovies );
GetDirListing( m_sSongDir + CString("*.mpg"), arrayPossibleMovies );
GetDirListing( m_sSongDir + CString("*.mpeg"), arrayPossibleMovies );
/* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the
* music starts. */
if( arrayPossibleMovies.size() == 1 )
this->AddBackgroundChange( BackgroundChange(0,arrayPossibleMovies[0],1.f,true,true,false) );
2001-11-30 09:38:35 +00:00
}
2002-05-19 01:59:48 +00:00
/* Don't allow multiple Steps of the same StepsType and Diffiuclty.
* This happens a lot reading BMS files because they we have to guess
* the Difficulty from the meter. */
for( i=0; i<NUM_STEPS_TYPES; i++ )
{
StepsType st = (StepsType)i;
2003-09-02 20:28:56 +00:00
for( unsigned j=0; j<NUM_DIFFICULTIES; j++ )
{
Difficulty dc = (Difficulty)j;
vector<Steps*> vSteps;
this->GetSteps( vSteps, st, dc );
/* Delete steps that are completely identical. This happened due to a
* bug in an earlier version. */
DeleteDuplicateSteps( this, vSteps );
2003-09-07 03:37:31 +00:00
CHECKPOINT;
SortNotesArrayByDifficulty( vSteps );
2003-09-07 03:37:31 +00:00
CHECKPOINT;
for( unsigned k=1; k<vSteps.size(); k++ )
{
Steps* pSteps = vSteps[k];
Difficulty dc2 = min( (Difficulty)(dc+1), DIFFICULTY_CHALLENGE );
pSteps->SetDifficulty( dc2 );
}
}
}
2003-12-30 02:46:47 +00:00
{
/* Generated filename; this doesn't always point to a loadable file,
* but instead points to the file we should write changed files to,
* and will always be an .SM.
*
* This is a little tricky. We can't always use the song title directly,
* since it might contain characters we can't store in filenames. Two
* easy options: we could manually filter out invalid characters, or we
* could use the name of the directory, which is always a valid filename
* and should always be the same as the song. The former might not catch
* everything--filename restrictions are platform-specific; we might even
* be on an 8.3 filesystem, so let's do the latter.
*
* We can't rely on searching for other data filenames; it works for DWIs,
* but not KSFs and BMSs.
*
* So, let's do this (by priority):
* 1. If there's an .SM file, use that filename. No reason to use anything
* else; it's the filename in use.
* 2. If there's a .DWI, use it with a changed extension.
* 3. Otherwise, use the name of the directory, since it's definitely a valid
* filename, and should always be the title of the song (unlike KSFs).
*/
m_sSongFileName = m_sSongDir;
CStringArray asFileNames;
GetDirListing( m_sSongDir+"*.sm", asFileNames );
if( !asFileNames.empty() )
m_sSongFileName += asFileNames[0];
else {
GetDirListing( m_sSongDir+"*.dwi", asFileNames );
if( !asFileNames.empty() ) {
m_sSongFileName += SetExtension( asFileNames[0], "sm" );
} else {
m_sSongFileName += Basename(m_sSongDir);
m_sSongFileName += ".sm";
}
}
}
// Compress all Steps
2002-12-18 23:07:58 +00:00
for( i=0; i<m_apNotes.size(); i++ )
{
2002-12-18 23:07:58 +00:00
m_apNotes[i]->Compress();
}
}
void Song::TranslateTitles()
{
static TitleSubst tsub("songs");
2003-01-13 06:04:44 +00:00
2003-02-10 22:59:24 +00:00
tsub.Subst(m_sMainTitle, m_sSubTitle, m_sArtist,
m_sMainTitleTranslit, m_sSubTitleTranslit, m_sArtistTranslit);
2001-11-30 09:38:35 +00:00
}
void Song::ReCalculateRadarValuesAndLastBeat()
{
for( unsigned i=0; i<m_apNotes.size(); i++ )
{
2003-08-03 00:13:55 +00:00
Steps* pNotes = m_apNotes[i];
2003-09-21 23:16:44 +00:00
/* If it's autogen, radar vals and first/last beat will come from the parent. */
2003-09-21 20:57:49 +00:00
if( pNotes->IsAutogen() )
continue;
2003-09-21 23:16:44 +00:00
//
// calculate radar values
//
NoteData tempNoteData;
pNotes->GetNoteData( &tempNoteData );
2003-01-30 07:18:33 +00:00
for( int r=0; r<NUM_RADAR_CATEGORIES; r++ )
{
2003-09-21 23:16:44 +00:00
float fVal = NoteDataUtil::GetRadarValue( tempNoteData, (RadarCategory)r, m_fMusicLengthSeconds );
pNotes->SetRadarValue(r, fVal);
}
2003-09-21 23:16:44 +00:00
2003-12-18 04:48:26 +00:00
/* Calculate first/last beat.
*
* Many songs have stray, empty song patterns. Ignore them, so
2003-11-02 04:17:28 +00:00
* they don't force the first beat of the whole song to 0. */
2003-02-01 03:26:19 +00:00
if(tempNoteData.GetLastRow() == 0)
continue;
float fFirstBeat = tempNoteData.GetFirstBeat();
float fLastBeat = tempNoteData.GetLastBeat();
if( m_fFirstBeat == -1 )
m_fFirstBeat = fFirstBeat;
else
m_fFirstBeat = min( m_fFirstBeat, fFirstBeat );
if( m_fLastBeat == -1 )
m_fLastBeat = fLastBeat;
else
m_fLastBeat = max( m_fLastBeat, fLastBeat );
}
}
2003-12-21 02:21:30 +00:00
void Song::GetSteps( vector<Steps*>& arrayAddTo, StepsType nt, Difficulty dc, int iMeterLow, int iMeterHigh, const CString &sDescription, bool bIncludeAutoGen, int Max ) const
2002-04-16 17:31:00 +00:00
{
2003-08-03 00:13:55 +00:00
for( unsigned i=0; i<m_apNotes.size(); i++ ) // for each of the Song's Steps
2003-04-20 01:31:13 +00:00
{
2003-12-21 02:21:30 +00:00
if( !Max )
break;
2003-08-07 06:36:34 +00:00
if( m_apNotes[i]->m_StepsType != nt ) continue;
2003-04-20 01:31:13 +00:00
if( dc != DIFFICULTY_INVALID && dc != m_apNotes[i]->GetDifficulty() )
continue;
if( iMeterLow != -1 && iMeterLow > m_apNotes[i]->GetMeter() )
continue;
if( iMeterHigh != -1 && iMeterHigh < m_apNotes[i]->GetMeter() )
continue;
if( sDescription != "" && sDescription != m_apNotes[i]->GetDescription() )
continue;
if( !bIncludeAutoGen && m_apNotes[i]->IsAutogen() )
continue;
2003-12-21 02:21:30 +00:00
if( Max != -1 )
--Max;
2003-04-20 01:31:13 +00:00
arrayAddTo.push_back( m_apNotes[i] );
}
2003-01-30 07:18:33 +00:00
}
Steps* Song::GetStepsByDifficulty( StepsType nt, Difficulty dc, bool bIncludeAutoGen ) const
2003-01-30 07:18:33 +00:00
{
2003-08-03 00:13:55 +00:00
vector<Steps*> vNotes;
2003-12-21 02:21:30 +00:00
GetSteps( vNotes, nt, dc, -1, -1, "", bIncludeAutoGen, 1 );
if( vNotes.size() == 0 )
return NULL;
else
return vNotes[0];
}
Steps* Song::GetStepsByMeter( StepsType nt, int iMeterLow, int iMeterHigh, bool bIncludeAutoGen ) const
{
2003-08-03 00:13:55 +00:00
vector<Steps*> vNotes;
2003-12-21 02:21:30 +00:00
GetSteps( vNotes, nt, DIFFICULTY_INVALID, iMeterLow, iMeterHigh, "", bIncludeAutoGen, 1 );
if( vNotes.size() == 0 )
return NULL;
else
return vNotes[0];
}
Steps* Song::GetStepsByDescription( StepsType nt, CString sDescription, bool bIncludeAutoGen ) const
{
2003-08-03 00:13:55 +00:00
vector<Steps*> vNotes;
GetSteps( vNotes, nt, DIFFICULTY_INVALID, -1, -1, sDescription, bIncludeAutoGen );
if( vNotes.size() == 0 )
return NULL;
else
return vNotes[0];
}
2003-08-07 06:16:17 +00:00
Steps* Song::GetClosestNotes( StepsType nt, Difficulty dc, bool bIncludeAutoGen ) const
{
Difficulty newDC = dc;
2003-08-03 00:13:55 +00:00
Steps* pNotes;
pNotes = GetStepsByDifficulty( nt, newDC, bIncludeAutoGen );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc-1);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetStepsByDifficulty( nt, newDC, bIncludeAutoGen );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc+1);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetStepsByDifficulty( nt, newDC, bIncludeAutoGen );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc-2);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetStepsByDifficulty( nt, newDC, bIncludeAutoGen );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc+2);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetStepsByDifficulty( nt, newDC, bIncludeAutoGen );
return pNotes;
2001-12-28 10:15:59 +00:00
}
2003-08-07 06:16:17 +00:00
void Song::GetEdits( vector<Steps*>& arrayAddTo, StepsType nt, bool bIncludeAutoGen ) const
{
}
/* Return whether the song is playable in the given style. */
bool Song::SongCompleteForStyle( const StyleDef *st ) const
{
2003-08-07 06:36:34 +00:00
if(!SongHasNotesType(st->m_StepsType))
return false;
return true;
}
2003-08-07 06:16:17 +00:00
bool Song::SongHasNotesType( StepsType nt ) const
{
2003-08-03 00:13:55 +00:00
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Steps
2003-08-07 06:36:34 +00:00
if( m_apNotes[i]->m_StepsType == nt )
return true;
return false;
}
2003-08-07 06:16:17 +00:00
bool Song::SongHasNotesTypeAndDifficulty( StepsType nt, Difficulty dc ) const
{
2003-08-03 00:13:55 +00:00
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Steps
2003-08-07 06:36:34 +00:00
if( m_apNotes[i]->m_StepsType == nt && m_apNotes[i]->GetDifficulty() == dc )
return true;
return false;
}
2002-09-11 04:49:07 +00:00
void Song::Save()
2002-04-16 17:31:00 +00:00
{
LOG->Trace( "Song::SaveToSongFile()" );
2003-12-21 07:44:41 +00:00
ReCalculateRadarValuesAndLastBeat();
TranslateTitles();
SaveToSMFile( GetSongFilePath(), false );
SaveToDWIFile();
SaveToCacheFile();
2003-12-21 07:44:41 +00:00
/* We've safely written our files and created backups. Rename non-SM and non-DWI
* files to avoid confusion.
*
2003-12-21 07:44:41 +00:00
* Don't rename anything before writing. If we do, and we crash before we actually get
* the file written, we'll be in a state where we can't automatically find the
* data in the future and the user will have to correct it himself. Also, don't
* rename SM or DWI: we just wrote those. */
CStringArray arrayOldFileNames;
GetDirListing( m_sSongDir + "*.bms", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.ksf", arrayOldFileNames );
2003-12-21 07:44:41 +00:00
// GetDirListing( m_sSongDir + "*.dwi", arrayOldFileNames );
// GetDirListing( m_sSongDir + "*.sm", arrayOldFileNames );
2003-12-21 07:44:41 +00:00
/* We copy files instead of renaming them, since it's more reliable: if we crash
* before the data below is written, we'll be left */
for( unsigned i=0; i<arrayOldFileNames.size(); i++ )
2002-07-02 00:27:58 +00:00
{
2003-12-21 07:44:41 +00:00
const CString sOldPath = m_sSongDir + arrayOldFileNames[i];
const CString sNewPath = sOldPath + ".old";
2002-07-02 00:27:58 +00:00
2003-12-21 07:44:41 +00:00
if( !FileCopy( sOldPath, sNewPath ) )
{
LOG->Warn( "Backup of \"%s\" failed", sOldPath.c_str() );
/* Don't remove. */
} else
FILEMAN->Remove( sOldPath );
}
}
2002-07-02 00:27:58 +00:00
void Song::SaveToSMFile( CString sPath, bool bSavingCache )
{
2003-04-25 00:27:30 +00:00
LOG->Trace( "Song::SaveToSMFile('%s')", sPath.c_str() );
2002-04-16 17:31:00 +00:00
2003-12-21 07:44:41 +00:00
/* If the file exists, make a backup. */
if( !bSavingCache && IsAFile(sPath) )
FileCopy( sPath, sPath + ".old" );
2002-12-14 00:21:22 +00:00
NotesWriterSM wr;
wr.Write(sPath, *this, bSavingCache);
2002-02-11 04:46:31 +00:00
}
2003-12-21 07:44:41 +00:00
void Song::SaveToCacheFile()
{
SONGINDEX->AddCacheIndex(m_sSongDir, GetHashForDirectory(m_sSongDir));
SaveToSMFile( GetCacheFilePath(), true );
}
2002-09-11 04:49:07 +00:00
void Song::SaveToDWIFile()
2002-08-17 06:44:04 +00:00
{
2003-11-01 22:16:42 +00:00
const CString sPath = SetExtension( GetSongFilePath(), "dwi" );
LOG->Trace( "Song::SaveToDWIFile(%s)", sPath.c_str() );
2002-08-17 06:44:04 +00:00
2003-12-21 07:44:41 +00:00
/* If the file exists, make a backup. */
if( IsAFile(sPath) )
FileCopy( sPath, sPath + ".old" );
NotesWriterDWI wr;
wr.Write(sPath, *this);
2002-08-17 06:44:04 +00:00
}
void Song::AddAutoGenNotes()
{
bool HasNotes[NUM_STEPS_TYPES];
memset( HasNotes, 0, sizeof(HasNotes) );
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Steps
{
if( m_apNotes[i]->IsAutogen() )
continue;
StepsType nt = m_apNotes[i]->m_StepsType;
HasNotes[nt] = true;
}
for( StepsType ntMissing=(StepsType)0; ntMissing<NUM_STEPS_TYPES; ntMissing=(StepsType)(ntMissing+1) )
{
if( HasNotes[ntMissing] )
continue;
2003-08-03 00:13:55 +00:00
// missing Steps of this type
int iNumTracksOfMissing = GAMEMAN->NotesTypeToNumTracks(ntMissing);
// look for closest match
2003-08-07 06:16:17 +00:00
StepsType ntBestMatch = (StepsType)-1;
int iBestTrackDifference = 10000; // inf
2003-08-07 06:38:18 +00:00
for( StepsType nt=(StepsType)0; nt<NUM_STEPS_TYPES; nt=(StepsType)(nt+1) )
{
if( !HasNotes[nt] )
2003-12-29 23:02:27 +00:00
continue;
2003-01-02 22:10:51 +00:00
/* has (non-autogen) Steps of this type */
2003-12-29 23:02:27 +00:00
const int iNumTracks = GAMEMAN->NotesTypeToNumTracks(nt);
const int iTrackDifference = abs(iNumTracks-iNumTracksOfMissing);
2003-01-02 22:10:51 +00:00
if( iTrackDifference < iBestTrackDifference )
{
ntBestMatch = nt;
iBestTrackDifference = iTrackDifference;
}
}
if( ntBestMatch != -1 )
AutoGen( ntMissing, ntBestMatch );
}
}
2002-10-11 18:25:15 +00:00
2003-08-07 06:16:17 +00:00
void Song::AutoGen( StepsType ntTo, StepsType ntFrom )
{
2003-01-02 22:10:51 +00:00
// int iNumTracksOfTo = GAMEMAN->NotesTypeToNumTracks(ntTo);
2002-10-11 18:25:15 +00:00
for( unsigned int j=0; j<m_apNotes.size(); j++ )
{
2003-12-29 23:02:27 +00:00
const Steps* pOriginalNotes = m_apNotes[j];
2003-08-07 06:36:34 +00:00
if( pOriginalNotes->m_StepsType == ntFrom )
{
2003-08-03 00:13:55 +00:00
Steps* pNewNotes = new Steps;
2003-01-02 22:10:51 +00:00
pNewNotes->AutogenFrom(pOriginalNotes, ntTo);
2002-10-31 04:23:39 +00:00
this->m_apNotes.push_back( pNewNotes );
}
}
}
2003-01-30 07:18:33 +00:00
void Song::RemoveAutoGenNotes()
{
for( int j=m_apNotes.size()-1; j>=0; j-- )
{
if( m_apNotes[j]->IsAutogen() )
{
delete m_apNotes[j];
m_apNotes.erase( m_apNotes.begin()+j );
}
}
}
2003-12-18 03:41:47 +00:00
Steps::MemCardData::HighScore GetHighScoreForDifficulty( const Song *s, const StyleDef *st, MemoryCard card, Difficulty dc )
2002-04-16 17:31:00 +00:00
{
// return max grade of notes in difficulty class
2003-08-03 00:13:55 +00:00
vector<Steps*> aNotes;
2003-12-18 03:41:47 +00:00
s->GetSteps( aNotes, st->m_StepsType );
2002-08-01 13:42:56 +00:00
SortNotesArrayByDifficulty( aNotes );
2002-02-11 04:46:31 +00:00
2003-12-18 03:41:47 +00:00
Steps* pSteps = s->GetStepsByDifficulty( st->m_StepsType, dc );
if( pSteps )
return pSteps->GetTopScore(card);
else
return Steps::MemCardData::HighScore();
2002-04-16 17:31:00 +00:00
}
2002-01-16 10:01:32 +00:00
2002-05-19 01:59:48 +00:00
bool Song::IsNew() const
2002-08-20 21:00:56 +00:00
{
2003-11-14 21:52:05 +00:00
return GetNumTimesPlayed(MEMORY_CARD_MACHINE) == 0;
2002-08-20 21:00:56 +00:00
}
2003-08-07 06:16:17 +00:00
bool Song::IsEasy( StepsType nt ) const
2002-08-20 21:00:56 +00:00
{
const Steps* pHardNotes = GetStepsByDifficulty( nt, DIFFICULTY_HARD );
2003-02-11 02:20:38 +00:00
// HACK: Looks bizarre to see the easy mark by Legend of MAX.
2003-02-15 04:35:18 +00:00
if( pHardNotes && pHardNotes->GetMeter() > 9 )
2003-02-11 02:20:38 +00:00
return false;
/* The easy marker indicates which songs a beginner, having selected "beginner",
* can play and actually get a very easy song: if there are actual beginner
* steps, or if the light steps are 1- or 2-foot. */
const Steps* pBeginnerNotes = GetStepsByDifficulty( nt, DIFFICULTY_BEGINNER );
2003-07-02 01:38:00 +00:00
if( pBeginnerNotes )
return true;
const Steps* pEasyNotes = GetStepsByDifficulty( nt, DIFFICULTY_EASY );
if( pEasyNotes && pEasyNotes->GetMeter() == 1 )
return true;
return false;
2002-08-20 21:00:56 +00:00
}
2002-05-19 01:59:48 +00:00
2003-08-07 06:16:17 +00:00
bool Song::HasEdits( StepsType nt ) const
{
2003-08-03 00:13:55 +00:00
vector<Steps*> vpNotes;
this->GetEdits( vpNotes, nt );
return vpNotes.size() > 0;
}
2002-02-11 04:46:31 +00:00
/////////////////////////////////////
// Sorting
/////////////////////////////////////
2002-01-16 10:01:32 +00:00
2003-06-16 20:23:07 +00:00
CString MakeSortString( CString s )
{
s.MakeUpper();
// Make sure that non-alphanumeric characters are placed at the very end
if( s.size() > 0 )
2003-06-16 20:23:07 +00:00
{
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;
2003-06-16 20:23:07 +00:00
}
return s;
}
2003-02-22 21:25:09 +00:00
bool CompareSongPointersByTitle(const Song *pSong1, const Song *pSong2)
{
2003-02-24 03:46:04 +00:00
// Prefer transliterations to full titles
2003-06-16 20:23:07 +00:00
CString s1 = pSong1->GetTranslitMainTitle();
CString s2 = pSong2->GetTranslitMainTitle();
if( s1 == s2 )
{
s1 = pSong1->GetTranslitSubTitle();
s2 = pSong2->GetTranslitSubTitle();
}
s1 = MakeSortString(s1);
s2 = MakeSortString(s2);
2003-06-16 20:23:07 +00:00
int ret = s1.CompareNoCase( s2 );
2002-10-24 07:56:20 +00:00
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. */
2003-01-03 05:29:45 +00:00
return pSong1->GetSongFilePath().CompareNoCase(pSong2->GetSongFilePath()) < 0;
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByTitle( vector<Song*> &arraySongPointers )
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByTitle );
}
static int GetSongSortDifficulty(const Song *pSong)
{
2003-08-03 00:13:55 +00:00
vector<Steps*> aNotes;
pSong->GetSteps( aNotes, GAMESTATE->GetCurrentStyleDef()->m_StepsType );
/* Sort by the first difficulty found in the following order: */
const Difficulty d[] = { DIFFICULTY_EASY, DIFFICULTY_MEDIUM, DIFFICULTY_HARD,
DIFFICULTY_CHALLENGE, DIFFICULTY_INVALID };
for(int i = 0; d[i] != DIFFICULTY_INVALID; ++i)
{
for( unsigned j = 0; j < aNotes.size(); ++j)
{
if(aNotes[j]->GetDifficulty() != d[i])
continue;
return aNotes[j]->GetMeter();
}
}
return 1;
}
int CompareSongPointersByDifficulty(const Song *pSong1, const Song *pSong2)
{
int iEasiestMeter1 = GetSongSortDifficulty(pSong1);
int iEasiestMeter2 = GetSongSortDifficulty(pSong2);
if( iEasiestMeter1 < iEasiestMeter2 )
2002-10-24 07:56:20 +00:00
return true;
if( iEasiestMeter1 > iEasiestMeter2 )
return false;
return CompareSongPointersByTitle( pSong1, pSong2 );
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByDifficulty( vector<Song*> &arraySongPointers )
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByDifficulty );
}
2002-10-24 07:56:20 +00:00
bool CompareSongPointersByBPM(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
float fMinBPM1, fMaxBPM1, fMinBPM2, fMaxBPM2;
2003-06-23 06:45:47 +00:00
pSong1->GetDisplayBPM( fMinBPM1, fMaxBPM1 );
pSong2->GetDisplayBPM( fMinBPM2, fMaxBPM2 );
2002-01-16 10:01:32 +00:00
if( fMaxBPM1 < fMaxBPM2 )
2002-10-24 07:56:20 +00:00
return true;
if( fMaxBPM1 > fMaxBPM2 )
return false;
return CompareCStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() );
2002-01-16 10:01:32 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByBPM( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByBPM );
2002-01-16 10:01:32 +00:00
}
2003-12-12 04:40:35 +00:00
void AppendOctal( int n, int digits, CString &out )
2003-06-16 20:23:07 +00:00
{
2003-12-12 04:40:35 +00:00
for( int p = digits; p >= 0; --p )
2003-06-16 20:23:07 +00:00
{
2003-12-12 04:40:35 +00:00
const int shift = p*3;
int n2 = (n >> shift) & 0x7;
2003-12-12 10:06:30 +00:00
out.insert( out.begin(), (char) n2 );
2003-06-16 20:23:07 +00:00
}
}
2003-12-12 04:40:35 +00:00
bool CompDescending( const pair<Song *, CString> &a, const pair<Song *, CString> &b )
{ return a.second > b.second; }
2003-06-16 20:23:07 +00:00
void SortSongPointerArrayByGrade( vector<Song*> &arraySongPointers )
{
2003-12-12 04:40:35 +00:00
/* 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;
vals.reserve( arraySongPointers.size() );
2003-12-12 03:05:37 +00:00
2003-12-12 10:06:30 +00:00
unsigned i;
for( i = 0; i < arraySongPointers.size(); ++i )
2003-12-12 04:40:35 +00:00
{
2003-12-12 03:05:37 +00:00
Song *pSong = arraySongPointers[i];
2003-12-12 04:40:35 +00:00
CString foo;
foo.reserve(256);
2003-12-12 03:05:37 +00:00
for( int g=NUM_GRADES-1; g>GRADE_NO_DATA; g-- )
2003-12-12 04:40:35 +00:00
{
int n = pSong->GetNumNotesWithGrade( (Grade)g );
AppendOctal( n, 3, foo );
}
vals.push_back( val(pSong, foo) );
2003-12-12 03:05:37 +00:00
}
2003-12-12 04:40:35 +00:00
sort( vals.begin(), vals.end(), CompDescending );
2003-12-12 10:06:30 +00:00
for( i = 0; i < arraySongPointers.size(); ++i )
2003-12-12 04:40:35 +00:00
arraySongPointers[i] = vals[i].first;
2003-06-16 20:23:07 +00:00
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByArtist(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
2003-06-19 05:30:01 +00:00
CString s1 = pSong1->GetTranslitArtist();
CString s2 = pSong2->GetTranslitArtist();
2002-01-16 10:01:32 +00:00
2003-06-16 20:23:07 +00:00
s1 = MakeSortString(s1);
s2 = MakeSortString(s2);
if( s1 < s2 )
2002-10-24 07:56:20 +00:00
return true;
2003-06-16 20:23:07 +00:00
if( s1 > s2 )
2002-10-24 07:56:20 +00:00
return false;
return CompareSongPointersByTitle( pSong1, pSong2 );
2002-01-16 10:01:32 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByArtist( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByArtist );
2002-01-16 10:01:32 +00:00
}
int CompareSongPointersByGroupAndDifficulty(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
const CString &sGroup1 = pSong1->m_sGroupName;
const CString &sGroup2 = pSong2->m_sGroupName;
2002-01-16 10:01:32 +00:00
2002-02-24 01:43:11 +00:00
if( sGroup1 < sGroup2 )
2002-10-24 07:56:20 +00:00
return true;
if( sGroup1 > sGroup2 )
return false;
/* Same group; compare by difficulty. */
2002-10-24 07:56:20 +00:00
return CompareSongPointersByDifficulty( pSong1, pSong2 );
2002-01-16 10:01:32 +00:00
}
void SortSongPointerArrayByGroupAndDifficulty( vector<Song*> &arraySongPointers )
{
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByGroupAndDifficulty );
}
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 );
}
void SortSongPointerArrayByGroupAndTitle( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByGroupAndTitle );
2002-01-16 10:01:32 +00:00
}
2003-11-17 04:34:18 +00:00
/* Just calculating GetNumTimesPlayed within the sort is pretty slow, so let's precompute
* it. (This could be generalized with a template.) */
2003-06-19 06:23:16 +00:00
map<const Song*, CString> song_sort_val;
2003-02-22 21:25:09 +00:00
2003-12-09 07:12:28 +00:00
bool CompareSongPointersBySortValueAscending(const Song *pSong1, const Song *pSong2)
2003-02-22 21:25:09 +00:00
{
return song_sort_val[pSong1] < song_sort_val[pSong2];
2002-01-16 10:01:32 +00:00
}
2003-12-09 07:12:28 +00:00
bool CompareSongPointersBySortValueDescending(const Song *pSong1, const Song *pSong2)
{
return song_sort_val[pSong1] > song_sort_val[pSong2];
}
2003-11-14 21:52:05 +00:00
void SortSongPointerArrayByMostPlayed( vector<Song*> &arraySongPointers, MemoryCard card )
2002-01-16 10:01:32 +00:00
{
2003-02-22 21:25:09 +00:00
for(unsigned i = 0; i < arraySongPointers.size(); ++i)
2003-11-14 21:52:05 +00:00
song_sort_val[arraySongPointers[i]] = ssprintf("%9i", arraySongPointers[i]->GetNumTimesPlayed(card));
2003-12-09 07:12:28 +00:00
stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueDescending );
song_sort_val.clear();
}
CString GetSectionNameFromSongAndSort( const Song* pSong, SongSortOrder so )
{
if( pSong == NULL )
return "";
switch( so )
{
case SORT_PREFERRED:
return "";
case SORT_GROUP:
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 "";
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);
}
case SORT_BPM:
{
const int iBPMGroupSize = 20;
float fMinBPM, fMaxBPM;
pSong->GetDisplayBPM( fMinBPM, fMaxBPM );
int iMaxBPM = (int)fMaxBPM;
iMaxBPM += iBPMGroupSize - (iMaxBPM%iBPMGroupSize) - 1;
return ssprintf("%03d-%03d",iMaxBPM-(iBPMGroupSize-1), iMaxBPM);
}
case SORT_MOST_PLAYED:
return "";
case SORT_GRADE:
{
for( int i=NUM_GRADES; i>GRADE_NO_DATA; i-- )
{
Grade g = (Grade)i;
int iCount = pSong->GetNumNotesWithGrade( g );
if( iCount > 0 )
return ssprintf( "%4s x %d", GradeToString(g).c_str(), iCount );
}
return "NO DATA";
}
case SORT_EASY_METER:
{
Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_EASY);
if( pNotes )
return ssprintf("%02d", pNotes->GetMeter() );
return "N/A";
}
case SORT_MEDIUM_METER:
{
Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_MEDIUM);
if( pNotes )
return ssprintf("%02d", pNotes->GetMeter() );
return "N/A";
}
case SORT_HARD_METER:
{
Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_HARD);
if( pNotes )
return ssprintf("%02d", pNotes->GetMeter() );
return "N/A";
}
case SORT_MENU:
return "";
case SORT_ALL_COURSES:
case SORT_NONSTOP_COURSES:
case SORT_ONI_COURSES:
case SORT_ENDLESS_COURSES:
default:
ASSERT(0);
return "";
}
}
void SortSongPointerArrayBySectionName( vector<Song*> &arraySongPointers, SongSortOrder so )
{
for(unsigned i = 0; i < arraySongPointers.size(); ++i)
{
CString val = GetSectionNameFromSongAndSort( arraySongPointers[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" + val;
song_sort_val[arraySongPointers[i]] = val;
}
stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending );
2003-02-22 21:25:09 +00:00
song_sort_val.clear();
2002-01-16 10:01:32 +00:00
}
2002-08-30 04:28:12 +00:00
struct CompareSongByMeter
2003-06-27 08:06:22 +00:00
{
Difficulty dc;
2003-06-27 08:06:22 +00:00
CompareSongByMeter(Difficulty d): dc(d) { }
bool operator() (const Song* pSong1, const Song* pSong2)
{
Steps* pNotes1 = pSong1->GetStepsByDifficulty( GAMESTATE->GetCurrentStyleDef()->m_StepsType, dc );
Steps* pNotes2 = pSong2->GetStepsByDifficulty( GAMESTATE->GetCurrentStyleDef()->m_StepsType, dc );
2003-06-27 08:06:22 +00:00
const int iMeter1 = pNotes1 ? pNotes1->GetMeter() : 0;
const int iMeter2 = pNotes2 ? pNotes2->GetMeter() : 0;
2003-06-27 08:06:22 +00:00
if( iMeter1 < iMeter2 )
return true;
if( iMeter1 > iMeter2 )
return false;
return CompareSongPointersByTitle( pSong1, pSong2 );
}
};
2003-06-27 08:06:22 +00:00
void SortSongPointerArrayByMeter( vector<Song*> &arraySongPointers, Difficulty dc )
{
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongByMeter(dc) );
2003-06-27 08:06:22 +00:00
}
2002-08-30 04:28:12 +00:00
bool Song::NormallyDisplayed() const
{
if(!PREFSMAN->m_bHiddenSongs) return true;
return m_SelectionDisplay == SHOW_ALWAYS;
}
bool Song::RouletteDisplayed() const
{
if(!PREFSMAN->m_bHiddenSongs) return true;
return m_SelectionDisplay != SHOW_NEVER;
}
2002-09-07 11:45:15 +00:00
bool Song::HasMusic() const {return m_sMusicFile != "" && IsAFile(GetMusicPath()); }
bool Song::HasBanner() const {return m_sBannerFile != "" && IsAFile(GetBannerPath()); }
bool Song::HasLyrics() const {return m_sLyricsFile != "" && IsAFile(GetLyricsPath()); }
bool Song::HasBackground() const {return m_sBackgroundFile != "" && IsAFile(GetBackgroundPath()); }
bool Song::HasCDTitle() const {return m_sCDTitleFile != "" && IsAFile(GetCDTitlePath()); }
bool Song::HasBGChanges() const {return !m_BackgroundChanges.empty(); }
2002-09-07 11:45:15 +00:00
2003-11-14 21:52:05 +00:00
int Song::GetNumTimesPlayed( MemoryCard card ) const
2002-09-07 11:45:15 +00:00
{
int iTotalNumTimesPlayed = 0;
for( unsigned i=0; i<m_apNotes.size(); i++ )
2003-11-14 21:52:05 +00:00
iTotalNumTimesPlayed += m_apNotes[i]->GetNumTimesPlayed( card );
2002-09-07 11:45:15 +00:00
return iTotalNumTimesPlayed;
}
2002-09-10 08:39:58 +00:00
2003-12-05 04:38:22 +00:00
/* Note that supplying a path relative to the top-level directory is only for compatibility
* with DWI. We prefer paths relative to the song directory. */
2002-09-10 08:39:58 +00:00
CString Song::GetMusicPath() const
{
/* If there's no path in the music file, the file is in the same directory
* as the song. (This is the preferred configuration.) */
2003-12-10 09:26:05 +00:00
if( m_sMusicFile.Find('/') == -1)
return m_sSongDir+m_sMusicFile;
/* Otherwise, it's relative to the top of the SM directory (the CWD), so
* return it directly. */
return m_sMusicFile;
2002-09-10 08:39:58 +00:00
}
CString Song::GetBannerPath() const
{
if( m_sBannerFile == "" )
return "";
2002-09-10 08:39:58 +00:00
return m_sSongDir+m_sBannerFile;
}
CString Song::GetLyricsPath() const
{
if( m_sLyricsFile == "" )
return "";
2003-03-19 20:01:33 +00:00
return m_sSongDir+m_sLyricsFile;
}
2002-09-10 08:39:58 +00:00
CString Song::GetCDTitlePath() const
{
2003-12-11 07:26:59 +00:00
if( m_sCDTitleFile.Find('/') == -1 )
return m_sSongDir+m_sCDTitleFile;
return m_sCDTitleFile;
2002-09-10 08:39:58 +00:00
}
CString Song::GetBackgroundPath() const
2002-09-10 08:39:58 +00:00
{
return m_sSongDir+m_sBackgroundFile;
2002-09-10 08:39:58 +00:00
}
2002-10-24 07:56:20 +00:00
2003-02-11 23:52:18 +00:00
CString Song::GetDisplayMainTitle() const
2003-01-09 09:01:20 +00:00
{
2003-05-20 04:41:47 +00:00
if(!PREFSMAN->m_bShowNative) return GetTranslitMainTitle();
2003-02-11 23:52:18 +00:00
return m_sMainTitle;
}
2003-01-09 09:01:20 +00:00
2003-02-11 23:52:18 +00:00
CString Song::GetDisplaySubTitle() const
{
2003-05-20 04:41:47 +00:00
if(!PREFSMAN->m_bShowNative) return GetTranslitSubTitle();
2003-02-11 23:52:18 +00:00
return m_sSubTitle;
}
2003-01-09 09:01:20 +00:00
2003-02-11 23:52:18 +00:00
CString Song::GetDisplayArtist() const
{
2003-05-20 04:41:47 +00:00
if(!PREFSMAN->m_bShowNative) return GetTranslitArtist();
2003-02-11 23:52:18 +00:00
return m_sArtist;
}
CString Song::GetFullDisplayTitle() const
{
CString Title = GetDisplayMainTitle();
CString SubTitle = GetDisplaySubTitle();
if(!SubTitle.empty()) Title += " " + SubTitle;
return Title;
}
CString Song::GetFullTranslitTitle() const
{
CString Title = GetTranslitMainTitle();
CString SubTitle = GetTranslitSubTitle();
2003-01-09 09:01:20 +00:00
2003-02-11 23:52:18 +00:00
if(!SubTitle.empty()) Title += " " + SubTitle;
2003-01-09 09:01:20 +00:00
return Title;
}
2003-08-03 00:13:55 +00:00
void Song::AddNotes( Steps* pNotes )
2003-01-30 07:18:33 +00:00
{
m_apNotes.push_back( pNotes );
}
2003-09-02 20:39:29 +00:00
void Song::RemoveNotes( const Steps* pNotes )
2003-01-30 07:18:33 +00:00
{
2003-08-03 00:13:55 +00:00
// Avoid any stale Note::parent pointers by removing all AutoGen'd Steps,
2003-01-30 07:18:33 +00:00
// then adding them again.
RemoveAutoGenNotes();
2003-06-30 05:19:50 +00:00
for( int j=m_apNotes.size()-1; j>=0; j-- )
2003-01-30 07:18:33 +00:00
{
if( m_apNotes[j] == pNotes )
{
delete m_apNotes[j];
m_apNotes.erase( m_apNotes.begin()+j );
break;
}
}
AddAutoGenNotes();
}
2003-06-16 20:23:07 +00:00
int Song::GetNumNotesWithGrade( Grade g ) const
{
int iCount = 0;
2003-08-03 00:13:55 +00:00
vector<Steps*> vNotes;
this->GetSteps( vNotes, GAMESTATE->GetCurrentStyleDef()->m_StepsType );
2003-06-16 20:23:07 +00:00
for( unsigned j=0; j<vNotes.size(); j++ )
{
Steps* pSteps = vNotes[j];
if( pSteps->GetTopScore(MEMORY_CARD_MACHINE).grade == g )
2003-06-16 20:23:07 +00:00
iCount++;
}
2003-06-16 20:23:07 +00:00
return iCount;
}
2003-07-09 04:09:35 +00:00
bool Song::Matches(CString sGroup, CString sSong) const
{
if( sGroup.size() && sGroup.CompareNoCase(this->m_sGroupName) != 0)
return false;
CString sDir = this->GetSongDir();
sDir.Replace("\\","/");
CStringArray bits;
split( sDir, "/", bits );
ASSERT(bits.size() >= 2); /* should always have at least two parts */
2003-08-05 01:50:44 +00:00
const CString &sLastBit = bits[bits.size()-1];
2003-07-09 04:09:35 +00:00
// match on song dir or title (ala DWI)
if( !sSong.CompareNoCase(sLastBit) )
return true;
if( !sSong.CompareNoCase(this->GetFullTranslitTitle()) )
return true;
return false;
}