Files
itgmania212121/stepmania/src/Song.cpp
T

1326 lines
38 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
2002-05-19 01:59:48 +00:00
#include "Notes.h"
2001-11-03 10:52:42 +00:00
#include "RageUtil.h"
2002-02-28 19:40:40 +00:00
#include <math.h> // for fmod
2002-05-01 19:14:55 +00:00
#include "RageLog.h"
2002-06-30 23:19:33 +00:00
#include "IniFile.h"
2003-02-16 04:28:17 +00:00
#include "song.h"
2002-07-03 03:13:13 +00:00
#include "NoteData.h"
2002-07-23 01:41:40 +00:00
#include "MsdFile.h"
2002-12-13 23:41:46 +00:00
#include "RageSound.h"
2002-07-27 19:29:51 +00:00
#include "RageException.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"
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"
#include "SDL_image.h"
2003-03-06 23:03:42 +00:00
const int FILE_CACHE_VERSION = 113; // increment this when Song or Notes 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-10-24 07:56:20 +00:00
static int CompareBPMSegments(const BPMSegment &seg1, const BPMSegment &seg2)
2001-12-19 01:50:57 +00:00
{
2002-10-24 07:56:20 +00:00
return seg1.m_fStartBeat < seg2.m_fStartBeat;
2001-12-19 01:50:57 +00:00
}
2003-01-03 05:56:28 +00:00
void SortBPMSegmentsArray( vector<BPMSegment> &arrayBPMSegments )
2001-12-19 01:50:57 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arrayBPMSegments.begin(), arrayBPMSegments.end(), CompareBPMSegments );
2001-12-19 01:50:57 +00:00
}
2002-10-24 07:56:20 +00:00
static int CompareStopSegments(const StopSegment &seg1, const StopSegment &seg2)
2001-12-28 10:15:59 +00:00
{
2002-10-24 07:56:20 +00:00
return seg1.m_fStartBeat < seg2.m_fStartBeat;
2001-12-28 10:15:59 +00:00
}
2003-01-03 05:56:28 +00:00
void SortStopSegmentsArray( vector<StopSegment> &arrayStopSegments )
2001-12-28 10:15:59 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arrayStopSegments.begin(), arrayStopSegments.end(), CompareStopSegments );
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
}
static int CompareLyricSegments(const LyricSegment &seg1, const LyricSegment &seg2)
{
return seg1.m_fStartTime < seg2.m_fStartTime;
}
void SortLyricSegmentsArray( vector<LyricSegment> &arrayLyricSegments )
{
sort( arrayLyricSegments.begin(), arrayLyricSegments.end(), CompareLyricSegments );
}
2001-12-19 01:50:57 +00:00
2001-11-03 10:52:42 +00:00
//////////////////////////////
// Song
//////////////////////////////
Song::Song()
{
m_bChangedSinceSave = false;
2002-07-03 21:27:26 +00:00
m_fBeat0OffsetInSeconds = 0;
2002-06-29 11:59:09 +00:00
m_fMusicSampleStartSeconds = 0;
m_fMusicSampleLengthSeconds = 0;
2002-05-19 01:59:48 +00:00
m_iMusicBytes = 0;
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;
}
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
}
2001-12-19 01:50:57 +00:00
2002-06-24 22:04:31 +00:00
void Song::AddBPMSegment( BPMSegment seg )
{
2002-10-31 04:23:39 +00:00
m_BPMSegments.push_back( seg );
2002-06-24 22:04:31 +00:00
SortBPMSegmentsArray( m_BPMSegments );
}
2002-07-11 19:02:26 +00:00
void Song::AddStopSegment( StopSegment seg )
{
2002-10-31 04:23:39 +00:00
m_StopSegments.push_back( seg );
2002-07-11 19:02:26 +00:00
SortStopSegmentsArray( m_StopSegments );
}
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 );
SortLyricSegmentsArray( m_LyricSegments );
}
float Song::GetMusicStartBeat() const
2002-08-18 23:20:18 +00:00
{
float fBPS = m_BPMSegments[0].m_fBPM / 60.0f;
return -m_fBeat0OffsetInSeconds*fBPS;
};
2002-06-24 22:04:31 +00:00
void Song::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const
2001-12-19 01:50:57 +00:00
{
// LOG->Trace( "GetBeatAndBPSFromElapsedTime( fElapsedTime = %f )", fElapsedTime );
2002-02-28 19:40:40 +00:00
// This function is a nightmare. Don't even try to understand it. :-)
2002-07-03 21:27:26 +00:00
fElapsedTime += m_fBeat0OffsetInSeconds;
2001-12-19 01:50:57 +00:00
2001-12-28 10:15:59 +00:00
for( unsigned i=0; i<m_BPMSegments.size(); i++ ) // foreach BPMSegment
2001-12-28 10:15:59 +00:00
{
2001-12-19 01:50:57 +00:00
float fStartBeatThisSegment = m_BPMSegments[i].m_fStartBeat;
bool bIsLastBPMSegment = i==m_BPMSegments.size()-1;
2001-12-19 01:50:57 +00:00
float fStartBeatNextSegment = bIsLastBPMSegment ? 40000/*inf*/ : m_BPMSegments[i+1].m_fStartBeat;
float fBeatsInThisSegment = fStartBeatNextSegment - fStartBeatThisSegment;
float fBPM = m_BPMSegments[i].m_fBPM;
float fBPS = fBPM / 60.0f;
2001-12-28 10:15:59 +00:00
// calculate the number of seconds in this segment
2001-12-19 01:50:57 +00:00
float fSecondsInThisSegment = fBeatsInThisSegment / fBPS;
unsigned j;
for( j=0; j<m_StopSegments.size(); j++ ) // foreach freeze
2001-12-28 10:15:59 +00:00
{
2002-07-11 19:02:26 +00:00
if( fStartBeatThisSegment <= m_StopSegments[j].m_fStartBeat && m_StopSegments[j].m_fStartBeat < fStartBeatNextSegment )
2001-12-28 10:15:59 +00:00
{
// this freeze lies within this BPMSegment
2002-07-11 19:02:26 +00:00
fSecondsInThisSegment += m_StopSegments[j].m_fStopSeconds;
2001-12-28 10:15:59 +00:00
}
}
2001-12-19 01:50:57 +00:00
if( fElapsedTime > fSecondsInThisSegment )
2001-12-28 10:15:59 +00:00
{
2002-06-30 23:19:33 +00:00
// this BPMSegement is NOT the current segment
2001-12-19 01:50:57 +00:00
fElapsedTime -= fSecondsInThisSegment;
continue;
2001-12-28 10:15:59 +00:00
}
// this BPMSegment IS the current segment
float fBeatEstimate = fStartBeatThisSegment + fElapsedTime*fBPS;
for( j=0; j<m_StopSegments.size(); j++ ) // foreach freeze
2001-12-28 10:15:59 +00:00
{
if( fStartBeatThisSegment > m_StopSegments[j].m_fStartBeat ||
m_StopSegments[j].m_fStartBeat > fStartBeatNextSegment )
continue;
2001-12-28 10:15:59 +00:00
// this freeze lies within this BPMSegment
2001-12-28 10:15:59 +00:00
if( m_StopSegments[j].m_fStartBeat > fBeatEstimate )
break;
fElapsedTime -= m_StopSegments[j].m_fStopSeconds;
// re-estimate
fBeatEstimate = fStartBeatThisSegment + fElapsedTime*fBPS;
if( fBeatEstimate < m_StopSegments[j].m_fStartBeat )
2001-12-28 10:15:59 +00:00
{
fBeatOut = m_StopSegments[j].m_fStartBeat;
fBPSOut = fBPS;
bFreezeOut = true;
return;
2001-12-28 10:15:59 +00:00
}
2001-12-19 01:50:57 +00:00
}
2001-12-28 10:15:59 +00:00
fBeatOut = fBeatEstimate;
fBPSOut = fBPS;
bFreezeOut = false;
return;
2001-12-19 01:50:57 +00:00
}
}
// This is a super hack, but it's only called from ScreenEdit, so it's OK.
// Writing an inverse function of GetBeatAndBPSFromElapsedTime() uber difficult,
2002-04-16 17:31:00 +00:00
// so do a binary search to get close to the correct elapsed time.
float Song::GetElapsedTimeFromBeat( float fBeat ) const
2002-04-16 17:31:00 +00:00
{
float fElapsedTimeBestGuess = this->m_fMusicLengthSeconds/2; // seconds
2002-07-11 19:02:26 +00:00
float fSecondsToMove = fElapsedTimeBestGuess; // seconds
2002-04-16 17:31:00 +00:00
float fBeatOut, fBPSOut;
2002-06-30 23:19:33 +00:00
bool bFreezeOut;
2002-04-16 17:31:00 +00:00
2003-02-09 03:32:04 +00:00
/* 0.001 gives higher precision and *takes about 7 more iterations than
* 0.100. A 90-second song took about 9 iterations; now it takes about
* 16. -glenn */
while( fSecondsToMove > 0.001f )
2002-04-16 17:31:00 +00:00
{
2002-06-30 23:19:33 +00:00
GetBeatAndBPSFromElapsedTime( fElapsedTimeBestGuess, fBeatOut, fBPSOut, bFreezeOut );
2003-02-09 03:32:04 +00:00
/* If this is an exact match, we're done. However, if we're on a
* freeze segment, keep moving backwards until we're off it, so we
* return the time associated with the beginning of the freeze segment
* and not some random place in its middle. */
if( fBeatOut == fBeat && !bFreezeOut)
break;
if( fBeatOut >= fBeat )
2002-04-16 17:31:00 +00:00
fElapsedTimeBestGuess -= fSecondsToMove;
else
fElapsedTimeBestGuess += fSecondsToMove;
fSecondsToMove /= 2;
}
return fElapsedTimeBestGuess;
}
CString Song::GetCacheFilePath() const
2002-05-19 01:59:48 +00:00
{
return ssprintf( "Cache/%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
2002-09-11 05:15:46 +00:00
bool Song::LoadWithoutCache( CString sDir )
{
//
// There was no entry in the cache for this song.
// Let's load it from a file, then write a cache entry.
//
2002-09-06 23:36:04 +00:00
2002-09-11 05:15:46 +00:00
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.GetString() );
return false;
}
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
bool success = ld->LoadFromDir( sDir, *this );
delete ld;
LOG->Trace("\n\n\n SONG NAME:: %s", this->GetDisplayMainTitle().GetBuffer() );
if( this->GetDisplayMainTitle().GetBuffer() == "Future Girls" )
{
LOG->Trace("AAA");
}
if( HasLyrics() )
{
LOG->Trace("\n\n\n LOAD LYRICS HERE!! \n\n\n");
}
2002-09-11 05:15:46 +00:00
if(!success)
return false;
2002-05-19 01:59:48 +00:00
TidyUpData();
2003-01-02 22:10:51 +00:00
// save a cache file so we don't have to parse it all over again next time
SaveToCacheFile();
return true;
}
bool Song::LoadFromSongDir( CString sDir )
{
2003-01-24 02:19:01 +00:00
// LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.GetString() );
2003-01-23 04:42:51 +00:00
sDir.Replace("\\", "/");
// make sure there is a trailing slash at the end of sDir
2003-01-23 04:42:51 +00:00
if( sDir.Right(1) != "/" )
sDir += "/";
// save song dir
m_sSongDir = sDir;
// save group name
CStringArray sDirectoryParts;
2003-01-23 04:42:51 +00:00
split( m_sSongDir, "/", sDirectoryParts, false );
m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item
//
// 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);
2002-09-07 10:22:06 +00:00
if( GetHashForDirectory(m_sSongDir) == uDirHash && // this cache is up to date
DoesFileExist(GetCacheFilePath()))
{
2003-01-24 02:19:01 +00:00
// LOG->Trace( "Loading '%s' from cache file '%s'.", m_sSongDir.GetString(), GetCacheFilePath().GetString() );
2002-09-06 23:36:04 +00:00
SMLoader ld;
ld.LoadFromSMFile( GetCacheFilePath(), *this, true );
}
else
{
2002-09-10 08:39:58 +00:00
if(!LoadWithoutCache(m_sSongDir))
return false;
}
{
/* 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 += asFileNames[0];
/* XXX: This would mess up "vote.for.dwight.d.eisenhower.dwi". */
m_sSongFileName.Replace( ".dwi", ".sm" );
} else {
m_sSongFileName += sDirectoryParts[sDirectoryParts.size()-2]; // last item
m_sSongFileName += ".sm";
}
}
}
2002-05-19 01:59:48 +00:00
2003-01-02 22:10:51 +00:00
/* Add AutoGen pointers. (These aren't cached.) */
AddAutoGenNotes();
if( !HasMusic() )
return false; // don't load this song
else
return true; // do load this song
2001-11-03 10:52:42 +00:00
}
2001-12-28 10:15:59 +00:00
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() ) // we found a match
m_sMusicFile = arrayPossibleMusic[0];
// Don't throw on missing music. -Chris
// else
2002-12-21 18:38:29 +00:00
// RageException::Throw( "The song in '%s' is missing a music file. You must place a music file in the song folder or remove the song", m_sSongDir.GetString() );
}
/* 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) {
LOG->Warn("File %s is empty?", GetMusicPath().GetString());
} else if(m_fMusicLengthSeconds == -1) {
LOG->Warn("File %s: error getting length", GetMusicPath().GetString());
}
}
else // ! HasMusic()
{
m_fMusicLengthSeconds = 100; // guess
LOG->Warn("This song has no music file; guessing at %f seconds", m_fMusicLengthSeconds);
}
2003-02-10 21:21:48 +00:00
if(m_fMusicLengthSeconds < 0)
{
LOG->Warn("File %i has negative length? (%i)", GetMusicPath().GetString(), m_fMusicLengthSeconds);
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();
SongDir.Replace("\\", "/");
vector<CString> parts;
split(SongDir, "/", parts);
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
if( 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.",
m_sSongDir.GetString(), m_sSongFileName.GetString() );
AddBPMSegment( BPMSegment(0, 60) );
}
2001-11-03 10:52:42 +00:00
/* Only automatically set the sample time if there was no sample length
* (m_fMusicSampleLengthSeconds == 0). 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. (Of course,
* this assumes nobody wants to have a song with no sample, but I can't
* think of any reason to do that ...) */
2002-09-11 06:01:07 +00:00
// We're going to try and do something intelligent here...
// The MusicSampleStart always seems to be about 100-120 beats into
// the song regardless of BPM. Let's take a shot-in-the dark guess.
if( m_fMusicSampleLengthSeconds == 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 );
m_fMusicSampleLengthSeconds = DEFAULT_MUSIC_SAMPLE_LENGTH;
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.
//
LOG->Trace("Looking for images...");
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
{
// 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;
2002-07-11 19:02:26 +00:00
GetDirListing( m_sSongDir + CString("*banner*.png"), arrayPossibleBanners );
GetDirListing( m_sSongDir + CString("*banner*.jpg"), arrayPossibleBanners );
GetDirListing( m_sSongDir + CString("*banner*.bmp"), arrayPossibleBanners );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*banner*.gif"), 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;
GetDirListing( m_sSongDir + CString("*bg*.png"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*bg*.jpg"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*bg*.bmp"), arrayPossibleBGs );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*bg*.gif"), arrayPossibleBGs );
2002-07-11 19:02:26 +00:00
GetDirListing( m_sSongDir + CString("*background*.png"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*background*.jpg"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*background*.bmp"), arrayPossibleBGs );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*background*.gif"), 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() )
{
// m_sCDTitleFile = "";
2001-12-28 10:15:59 +00:00
2002-07-11 19:02:26 +00:00
// find an image with "cdtitle" in the file name
CStringArray arrayPossibleCDTitles;
GetDirListing( m_sSongDir + CString("*cdtitle*.png"), arrayPossibleCDTitles );
GetDirListing( m_sSongDir + CString("*cdtitle*.jpg"), arrayPossibleCDTitles );
GetDirListing( m_sSongDir + CString("*cdtitle*.bmp"), arrayPossibleCDTitles );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*cdtitle*.gif"), 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
2002-07-11 19:02:26 +00:00
LOG->Trace("Looking for lyrics..");
//if( HasLyrics() )
//{
//Check if there is a lyric file in here
CStringArray arrayLyricFiles;
GetDirListing(m_sSongDir + CString("*.lrc"), arrayLyricFiles );
if( !arrayLyricFiles.empty() )
{
m_sLyricsFile = m_sSongDir+arrayLyricFiles[0];
LyricsLoader ll;
ll.LoadFromLRCFile(m_sLyricsFile.GetBuffer(), *GAMESTATE->m_pCurSong);
}
//}
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;
GetDirListing( m_sSongDir + CString("*.png"), arrayImages );
GetDirListing( m_sSongDir + CString("*.jpg"), arrayImages );
GetDirListing( m_sSongDir + CString("*.bmp"), arrayImages );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*.gif"), arrayImages );
unsigned i;
for( i=0; i<arrayImages.size(); i++ ) // foreach image
2001-11-03 10:52:42 +00:00
{
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
SDL_Surface *img = IMG_Load( m_sSongDir + arrayImages[i] );
2003-03-05 09:51:32 +00:00
if( !img )
2001-12-19 14:56:22 +00:00
{
2003-03-05 09:51:32 +00:00
LOG->Trace("Couldn't load %s%s: %s",
m_sSongDir.GetString(), arrayImages[i].GetString(), SDL_GetError());
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() && 100<=width && width<=320 && 50<=height && height<=240 )
{
m_sBannerFile = arrayImages[i];
continue;
}
if( !HasCDTitle() && width<=100 && height<=50 )
{
m_sCDTitleFile = arrayImages[i];
continue;
2001-12-19 14:56:22 +00:00
}
2001-11-03 10:52:42 +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]) );
2001-11-30 09:38:35 +00:00
}
2002-05-19 01:59:48 +00:00
2002-12-18 23:07:58 +00:00
for( i=0; i<m_apNotes.size(); i++ )
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()
{
//
// calculate radar values and first/last beat
//
for( unsigned i=0; i<m_apNotes.size(); i++ )
{
Notes* pNotes = m_apNotes[i];
NoteData tempNoteData;
pNotes->GetNoteData( &tempNoteData );
2003-01-30 07:18:33 +00:00
for( int r=0; r<NUM_RADAR_CATEGORIES; r++ )
{
/* If it's autogen, radar vals come from the parent. */
if(pNotes->IsAutogen())
continue;
pNotes->SetRadarValue(r, NoteDataUtil::GetRadarValue( tempNoteData, (RadarCategory)r, m_fMusicLengthSeconds ));
}
2003-02-01 03:26:19 +00:00
/* Many songs have stray, empty song patterns. Ignore them, so
* they don't force the first beat of the whole song to 0. XXX Should
* we just delete them, now that new patterns can be created quickly
* in the editor? */
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 );
}
}
void Song::GetNotes( vector<Notes*>& arrayAddTo, NotesType nt, Difficulty dc, int iMeterLow, int iMeterHigh, CString sDescription, bool bIncludeAutoGen ) const
2002-04-16 17:31:00 +00:00
{
for( unsigned i=0; i<m_apNotes.size(); i++ ) // for each of the Song's Notes
2002-10-05 20:03:14 +00:00
if( m_apNotes[i]->m_NotesType == nt )
if( dc==DIFFICULTY_INVALID || dc==m_apNotes[i]->GetDifficulty() )
if( iMeterLow==-1 || iMeterLow<=m_apNotes[i]->GetMeter() )
if( iMeterHigh==-1 || iMeterHigh>=m_apNotes[i]->GetMeter() )
if( sDescription=="" || sDescription==m_apNotes[i]->GetDescription() )
if( bIncludeAutoGen || !m_apNotes[i]->IsAutogen() )
arrayAddTo.push_back( m_apNotes[i] );
2003-01-30 07:18:33 +00:00
}
Notes* Song::GetNotes( NotesType nt, Difficulty dc, bool bIncludeAutoGen ) const
2003-01-30 07:18:33 +00:00
{
vector<Notes*> vNotes;
GetNotes( vNotes, nt, dc, -1, -1, "", bIncludeAutoGen );
if( vNotes.size() == 0 )
return NULL;
else
return vNotes[0];
}
Notes* Song::GetNotes( NotesType nt, int iMeterLow, int iMeterHigh ) const
{
vector<Notes*> vNotes;
GetNotes( vNotes, nt, DIFFICULTY_INVALID, iMeterLow, iMeterHigh );
if( vNotes.size() == 0 )
return NULL;
else
return vNotes[0];
}
Notes* Song::GetNotes( NotesType nt, CString sDescription ) const
{
vector<Notes*> vNotes;
GetNotes( vNotes, nt, DIFFICULTY_INVALID, -1, -1, sDescription );
if( vNotes.size() == 0 )
return NULL;
else
return vNotes[0];
}
Notes* Song::GetClosestNotes( NotesType nt, Difficulty dc ) const
{
Difficulty newDC = dc;
Notes* pNotes;
pNotes = GetNotes( nt, newDC );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc-1);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetNotes( nt, newDC );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc+1);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetNotes( nt, newDC );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc-2);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetNotes( nt, newDC );
if( pNotes )
return pNotes;
newDC = (Difficulty)(dc+2);
CLAMP( (int&)newDC, 0, NUM_DIFFICULTIES-1 );
pNotes = GetNotes( nt, newDC );
return pNotes;
2001-12-28 10:15:59 +00:00
}
void Song::GetEdits( vector<Notes*>& arrayAddTo, NotesType nt, bool bIncludeAutoGen ) const
{
}
/* Return whether the song is playable in the given style. */
bool Song::SongCompleteForStyle( const StyleDef *st ) const
{
if(!SongHasNotesType(st->m_NotesType))
return false;
return true;
}
bool Song::SongHasNotesType( NotesType nt ) const
{
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Notes
if( m_apNotes[i]->m_NotesType == nt )
return true;
return false;
}
2002-09-29 05:06:18 +00:00
bool Song::SongHasNotesTypeAndDifficulty( NotesType nt, Difficulty dc ) const
{
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Notes
2003-01-02 22:10:51 +00:00
if( m_apNotes[i]->m_NotesType == nt && m_apNotes[i]->GetDifficulty() == dc )
return true;
return false;
}
2002-05-19 01:59:48 +00:00
void Song::SaveToCacheFile()
2002-02-11 04:46:31 +00:00
{
LOG->Trace( "Song::SaveToCacheFile()" );
2002-02-11 04:46:31 +00:00
SONGINDEX->AddCacheIndex(m_sSongDir, GetHashForDirectory(m_sSongDir));
SaveToSMFile( GetCacheFilePath(), true );
2002-05-19 01:59:48 +00:00
}
2002-02-11 04:46:31 +00:00
2002-09-11 04:49:07 +00:00
void Song::Save()
2002-04-16 17:31:00 +00:00
{
LOG->Trace( "Song::SaveToSongFile()" );
/* rename all old files to avoid confusion.
*
* This also serves as a backup, so rename .sm's, too. If we crash when
* saving the .sm, we don't want to lose what we had. But, what we really
* should be doing is saving to another file (eg. foo.sm.new), then once we
* know we havn't crashed, move the old .sm to .sm.old and the new one to
* the real filename. That way, if we crash, we don't leave the song in an
* unplayable state where the user has to manually un-rename stuff. XXX -glenn
*/
CStringArray arrayOldFileNames;
GetDirListing( m_sSongDir + "*.bms", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.dwi", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.ksf", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.sm", arrayOldFileNames );
for( unsigned i=0; i<arrayOldFileNames.size(); i++ )
2002-07-02 00:27:58 +00:00
{
CString sOldPath = m_sSongDir + arrayOldFileNames[i];
CString sNewPath = sOldPath + ".old";
2003-02-14 06:59:56 +00:00
rename( sOldPath, sNewPath );
2002-07-02 00:27:58 +00:00
}
ReCalculateRadarValuesAndLastBeat();
TranslateTitles();
SaveToSMFile( GetSongFilePath(), false );
2002-09-11 04:49:07 +00:00
SaveToDWIFile();
}
2002-07-02 00:27:58 +00:00
void Song::SaveToSMFile( CString sPath, bool bSavingCache )
{
2002-12-14 00:21:22 +00:00
LOG->Trace( "Song::SaveToSMFile('%s')", sPath.GetString() );
2002-04-16 17:31:00 +00:00
2002-12-14 00:21:22 +00:00
NotesWriterSM wr;
wr.Write(sPath, *this, bSavingCache);
2002-02-11 04:46:31 +00:00
}
2002-09-11 04:49:07 +00:00
void Song::SaveToDWIFile()
2002-08-17 06:44:04 +00:00
{
LOG->Trace( "Song::SaveToSongFileAndDWI()" );
2002-08-17 06:44:04 +00:00
CString sPath = GetSongFilePath();
sPath.Replace( ".sm", ".dwi" );
NotesWriterDWI wr;
wr.Write(sPath, *this);
2002-08-17 06:44:04 +00:00
}
void Song::AddAutoGenNotes()
{
for( NotesType ntMissing=(NotesType)0; ntMissing<NUM_NOTES_TYPES; ntMissing=(NotesType)(ntMissing+1) )
{
if( SongHasNotesType(ntMissing) )
continue;
// missing Notes of this type
int iNumTracksOfMissing = GAMEMAN->NotesTypeToNumTracks(ntMissing);
// look for closest match
NotesType ntBestMatch = (NotesType)-1;
int iBestTrackDifference = 10000; // inf
for( NotesType nt=(NotesType)0; nt<NUM_NOTES_TYPES; nt=(NotesType)(nt+1) )
{
2003-01-03 05:56:28 +00:00
vector<Notes*> apNotes;
this->GetNotes( apNotes, nt );
2003-01-02 22:10:51 +00:00
if(apNotes.empty() || apNotes[0]->IsAutogen())
continue; /* can't autogen from other autogen */
int iNumTracks = GAMEMAN->NotesTypeToNumTracks(nt);
int iTrackDifference = abs(iNumTracks-iNumTracksOfMissing);
if( iTrackDifference < iBestTrackDifference )
{
ntBestMatch = nt;
iBestTrackDifference = iTrackDifference;
}
}
if( ntBestMatch != -1 )
AutoGen( ntMissing, ntBestMatch );
}
}
2002-10-11 18:25:15 +00:00
void Song::AutoGen( NotesType ntTo, NotesType 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++ )
{
Notes* pOriginalNotes = m_apNotes[j];
if( pOriginalNotes->m_NotesType == ntFrom )
{
2002-10-11 18:25:15 +00:00
Notes* pNewNotes = new Notes;
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-01-22 05:29:27 +00:00
Grade Song::GetGradeForDifficulty( const StyleDef *st, PlayerNumber pn, Difficulty dc ) const
2002-04-16 17:31:00 +00:00
{
// return max grade of notes in difficulty class
2003-01-03 05:56:28 +00:00
vector<Notes*> aNotes;
this->GetNotes( aNotes, st->m_NotesType );
2002-08-01 13:42:56 +00:00
SortNotesArrayByDifficulty( aNotes );
2002-02-11 04:46:31 +00:00
Grade grade = GRADE_NO_DATA;
for( unsigned i=0; i<aNotes.size(); i++ )
2002-04-16 17:31:00 +00:00
{
2002-09-06 10:15:00 +00:00
const Notes* pNotes = aNotes[i];
2003-01-02 22:10:51 +00:00
if( pNotes->GetDifficulty() == dc )
2003-01-22 05:29:27 +00:00
grade = max( grade, pNotes->m_MemCardScores[pn].grade );
2002-04-16 17:31:00 +00:00
}
return grade;
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
{
return GetNumTimesPlayed()==0;
}
bool Song::IsEasy( NotesType nt ) const
2002-08-20 21:00:56 +00:00
{
2003-02-11 02:20:38 +00:00
Notes* pBeginnerNotes = GetNotes( nt, DIFFICULTY_BEGINNER );
Notes* pEasyNotes = GetNotes( nt, DIFFICULTY_EASY );
Notes* pHardNotes = GetNotes( nt, DIFFICULTY_HARD );
// 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;
if( pBeginnerNotes && pBeginnerNotes->GetMeter()==1 )
return true;
else if( pEasyNotes && pEasyNotes->GetMeter()==1 )
return true;
else
return false;
2002-08-20 21:00:56 +00:00
}
2002-05-19 01:59:48 +00:00
bool Song::HasEdits( NotesType nt ) const
{
vector<Notes*> 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-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
int ret = pSong1->GetTranslitMainTitle().CompareNoCase(pSong2->GetTranslitMainTitle());
2003-02-22 21:25:09 +00:00
if(ret < 0) return true;
if(ret > 0) return false;
2003-02-22 21:25:09 +00:00
ret = pSong1->GetTranslitSubTitle().CompareNoCase(pSong2->GetTranslitSubTitle());
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 );
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByDifficulty(const Song *pSong1, const Song *pSong2)
{
2003-01-03 05:56:28 +00:00
vector<Notes*> aNotes1;
vector<Notes*> aNotes2;
pSong1->GetNotes( aNotes1, GAMESTATE->GetCurrentStyleDef()->m_NotesType );
pSong2->GetNotes( aNotes2, GAMESTATE->GetCurrentStyleDef()->m_NotesType );
int iEasiestMeter1 = 1000; // infinity
int iEasiestMeter2 = 1000; // infinity
unsigned i;
for( i=0; i<aNotes1.size(); i++ )
if(aNotes1[i]->GetDifficulty() != DIFFICULTY_BEGINNER) //sort by anything but beginner
iEasiestMeter1 = min( iEasiestMeter1, aNotes1[i]->GetMeter() );
for( i=0; i<aNotes2.size(); i++ )
if(aNotes2[i]->GetDifficulty() != DIFFICULTY_BEGINNER)
iEasiestMeter2 = min( iEasiestMeter2, aNotes2[i]->GetMeter() );
// odd case where there are only beginner steps... what to do? should probably
// act just like it was a 1-footer, even if the beginner steps aren't
if( iEasiestMeter1 == 1000 )
iEasiestMeter1 = 1;
if( iEasiestMeter2 == 1000 )
iEasiestMeter2 = 1;
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;
pSong1->GetMinMaxBPM( fMinBPM1, fMaxBPM1 );
pSong2->GetMinMaxBPM( fMinBPM2, fMaxBPM2 );
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
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByArtist(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
2002-06-29 11:59:09 +00:00
CString sArtist1 = pSong1->m_sArtist;
CString sArtist2 = pSong2->m_sArtist;
2002-01-16 10:01:32 +00:00
2002-02-24 01:43:11 +00:00
if( sArtist1 < sArtist2 )
2002-10-24 07:56:20 +00:00
return true;
if( sArtist1 > sArtist2 )
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
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByGroup(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
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByGroup( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByGroup );
2002-01-16 10:01:32 +00:00
}
2003-02-22 21:25:09 +00:00
bool CompareSongPointersByMostPlayed(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
2003-02-22 21:25:09 +00:00
return pSong1->GetNumTimesPlayed() < pSong2->GetNumTimesPlayed();
/*
Comparing titles is slow, and this makes course selection choppy. Turning this
off means we don't get consistent orderings of songs that have been played
the same amount, but that shouldn't be a big problem; the songs should stay
in the same original order during a single load of the game anyway (and will
probably stay in the same general order even across loads). The number of
plays is more likely to change.
This is mostly a problem with new games, where there are a large number of songs
that have all been played 0 times.
2002-02-02 05:11:12 +00:00
int iNumTimesPlayed1 = pSong1->GetNumTimesPlayed();
int iNumTimesPlayed2 = pSong2->GetNumTimesPlayed();
2003-02-22 21:25:09 +00:00
2002-08-20 21:00:56 +00:00
if( iNumTimesPlayed1 > iNumTimesPlayed2 )
2002-10-24 07:56:20 +00:00
return true;
if( iNumTimesPlayed1 < iNumTimesPlayed2 )
return false;
return CompareSongPointersByTitle( pSong1, pSong2 );
2003-02-22 21:25:09 +00:00
*/
}
/* Actually, just calculating GetNumTimesPlayed within the sort is pretty
* slow, so let's precompute it. (This could be generalized with a template.) */
map<const Song*, int> song_sort_val;
bool CompareSongPointersBySortVal(const Song *pSong1, const Song *pSong2)
{
return song_sort_val[pSong1] < song_sort_val[pSong2];
2002-01-16 10:01:32 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByMostPlayed( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2003-02-22 21:25:09 +00:00
for(unsigned i = 0; i < arraySongPointers.size(); ++i)
song_sort_val[arraySongPointers[i]] = arraySongPointers[i]->GetNumTimesPlayed();
stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortVal );
song_sort_val.clear();
2002-01-16 10:01:32 +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
int Song::GetNumTimesPlayed() const
{
int iTotalNumTimesPlayed = 0;
for( unsigned i=0; i<m_apNotes.size(); i++ )
2002-09-07 11:45:15 +00:00
{
iTotalNumTimesPlayed += m_apNotes[i]->m_MemCardScores[MEMORY_CARD_MACHINE].iNumTimesPlayed;
2002-09-07 11:45:15 +00:00
}
return iTotalNumTimesPlayed;
}
2002-09-10 08:39:58 +00:00
/* Search semantics for all song files:
*
* If the path doesn't have any directory separators, it's a filename in the
* song directory.
*
* If it does, it's relative to the top directory of the tree it was loaded
* from, eg ".\music\stuff\song.ogg". Most of the time, that's the SM tree,
* and that's the PWD, so just return it directly. If the file was originally
* loaded from the DWIPath, it's relative to that, so prepend it.
*
* Some DWI's do have relative paths that don't start with ".\".
*/
/* We only follow this for song files and cdtitles; it's for compatibility
* with DWI. We prefer paths relative to the song directory; only support
* that for all other paths. */
/* Note: Prepending the dwipath is ugly. The first impression might be to
* add a Song::TopFilePath, but don't do that--we have too much stuff in there
* already. We don't really need it; this is the only place it'd be used. What
* we *should* be doing is prepending this path when we first load the song.
* However, dwipaths are usually like "c:\games\dwi", and we can't store colons
* in SM's; they'll get interpreted as delimiters. So:
* XXX: Add some kind of escape character to SM's.
*
* -glenn */
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.) */
if( m_sMusicFile.Find('/') == -1)
return m_sSongDir+m_sMusicFile;
/* The file has a path. If it was loaded from the m_DWIPath, it's relative
* to that. */
if( PREFSMAN->m_DWIPath!="" && m_sSongDir.Left(PREFSMAN->m_DWIPath.GetLength()) == PREFSMAN->m_DWIPath )
return PREFSMAN->m_DWIPath+"/"+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
{
return m_sSongDir+m_sBannerFile;
}
CString Song::GetLyricsPath() const
{
LOG->Trace("\n\n\n TRYING TO GET LYRICS FROM:: %s", m_sSongDir+m_sBannerFile);
return m_sSongDir+m_sLyricsFile;
}
2002-09-10 08:39:58 +00:00
CString Song::GetCDTitlePath() const
{
if( m_sCDTitleFile.Find('/') == -1)
return m_sSongDir+m_sCDTitleFile;
2002-09-10 20:55:06 +00:00
if( PREFSMAN->m_DWIPath!="" && m_sSongDir.Left(PREFSMAN->m_DWIPath.GetLength()) == PREFSMAN->m_DWIPath )
return PREFSMAN->m_DWIPath+"/"+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-02-11 23:52:18 +00:00
if(!PREFSMAN->m_bShowTranslations) return GetTranslitMainTitle();
return m_sMainTitle;
}
2003-01-09 09:01:20 +00:00
2003-02-11 23:52:18 +00:00
CString Song::GetDisplaySubTitle() const
{
if(!PREFSMAN->m_bShowTranslations) return GetTranslitSubTitle();
return m_sSubTitle;
}
2003-01-09 09:01:20 +00:00
2003-02-11 23:52:18 +00:00
CString Song::GetDisplayArtist() const
{
if(!PREFSMAN->m_bShowTranslations) return GetTranslitArtist();
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-01-30 07:18:33 +00:00
void Song::AddNotes( Notes* pNotes )
{
m_apNotes.push_back( pNotes );
}
void Song::RemoveNotes( Notes* pNotes )
{
// Avoid any stale Note::parent pointers by removing all AutoGen'd Notes,
// then adding them again.
RemoveAutoGenNotes();
for( unsigned j=m_apNotes.size()-1; j>=0; j-- )
{
if( m_apNotes[j] == pNotes )
{
delete m_apNotes[j];
m_apNotes.erase( m_apNotes.begin()+j );
break;
}
}
AddAutoGenNotes();
}