2003-02-16 04:01:45 +00:00
# include "global.h"
2001-11-04 19:34:28 +00:00
/*
-----------------------------------------------------------------------------
2002-06-24 22:04:31 +00:00
Class: Song
2001-11-04 19:34:28 +00:00
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-04 19:34:28 +00:00
-----------------------------------------------------------------------------
*/
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"
2002-08-21 02:13:08 +00:00
# include "SongCacheIndex.h"
2002-08-25 19:00:12 +00:00
# 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 "Notes.h"
2002-09-29 23:29:01 +00:00
# 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"
2002-09-09 00:22:02 +00:00
# include "NotesWriterDWI.h"
2002-12-14 00:21:22 +00:00
# include "NotesWriterSM.h"
2001-11-03 10:52:42 +00:00
2002-11-13 05:17:15 +00:00
# include "SDL.h"
# include "SDL_image.h"
2002-10-28 05:30:45 +00:00
2003-02-14 05:50:19 +00:00
const int FILE_CACHE_VERSION = 109 ; // increment this when Song or Notes changes to invalidate cache
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
}
2001-12-19 01:50:57 +00:00
2001-11-03 10:52:42 +00:00
//////////////////////////////
// Song
//////////////////////////////
2001-11-04 19:34:28 +00:00
Song : : Song ( )
{
2001-11-27 22:47:30 +00:00
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 ;
2002-09-11 05:32:15 +00:00
m_fMusicSampleLengthSeconds = 12.0f ; // start fading out at m_fMusicSampleLengthSeconds-1 seconds
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-04 19:34:28 +00:00
}
2001-11-03 10:52:42 +00:00
2002-06-14 22:25:22 +00:00
Song : : ~ Song ( )
{
2002-10-31 02:31:15 +00:00
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 ) ;
}
2002-08-23 20:18:29 +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 ) ;
2002-08-23 20:18:29 +00:00
SortBackgroundChangesArray ( m_BackgroundChanges ) ;
2002-06-24 22:04:31 +00:00
}
2002-08-21 00:47:29 +00:00
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
2002-08-21 00:47:29 +00:00
void Song : : GetBeatAndBPSFromElapsedTime ( float fElapsedTime , float & fBeatOut , float & fBPSOut , bool & bFreezeOut ) const
2001-12-19 01:50:57 +00:00
{
2002-07-31 19:40:40 +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
2002-10-31 02:31:15 +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 ;
2002-10-31 02:31:15 +00:00
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 ;
2002-10-31 02:31:15 +00:00
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 ;
2002-08-20 07:20:59 +00:00
continue ;
2001-12-28 10:15:59 +00:00
}
2002-08-20 07:20:59 +00:00
// this BPMSegment IS the current segment
float fBeatEstimate = fStartBeatThisSegment + fElapsedTime * fBPS ;
2002-10-31 02:31:15 +00:00
for ( j = 0 ; j < m_StopSegments . size ( ) ; j + + ) // foreach freeze
2001-12-28 10:15:59 +00:00
{
2002-08-20 07:20: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
2002-08-20 07:20:59 +00:00
// this freeze lies within this BPMSegment
2001-12-28 10:15:59 +00:00
2002-08-20 07:20: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
{
2002-08-20 07:20: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
2002-08-20 07:20:59 +00:00
fBeatOut = fBeatEstimate ;
fBPSOut = fBPS ;
bFreezeOut = false ;
return ;
2001-12-19 01:50:57 +00:00
}
}
2002-08-25 19:00:12 +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.
2002-08-21 00:47:29 +00:00
float Song : : GetElapsedTimeFromBeat ( float fBeat ) const
2002-04-16 17:31:00 +00:00
{
2002-10-09 18:14:29 +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 ;
}
2002-08-21 00:47:29 +00:00
CString Song : : GetCacheFilePath ( ) const
2002-05-19 01:59:48 +00:00
{
2003-02-14 08:15:15 +00:00
return ssprintf ( " Cache/%u " , GetHashForString ( m_sSongDir ) ) ;
2002-05-19 01:59:48 +00:00
}
2002-08-22 21:59:47 +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
{
2002-08-24 07:57:28 +00:00
ASSERT ( m_sSongFileName . GetLength ( ) ! = 0 ) ;
2002-08-22 21:59:47 +00:00
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-07-31 19:40:40 +00:00
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 )
2002-07-31 19:40:40 +00:00
{
2002-10-29 07:58:44 +00:00
LOG - > Warn ( " Couldn't find any SM, DWI, BMS, or KSF files in '%s'. This is not a valid song directory. " , sDir . GetString ( ) ) ;
2002-07-31 19:40:40 +00:00
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 ;
if ( ! success )
return false ;
2002-05-19 01:59:48 +00:00
TidyUpData ( ) ;
2003-01-02 22:10:51 +00:00
2002-08-22 21:59:47 +00:00
// save a cache file so we don't have to parse it all over again next time
SaveToCacheFile ( ) ;
2002-08-24 07:57:28 +00:00
return true ;
}
bool Song : : LoadFromSongDir ( CString sDir )
{
2003-01-24 02:19:01 +00:00
// LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.GetString() );
2002-08-24 07:57:28 +00:00
2003-01-23 04:42:51 +00:00
sDir . Replace ( " \\ " , " / " ) ;
2003-02-14 08:15:15 +00:00
// 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 + = " / " ;
2002-08-24 07:57:28 +00:00
// save song dir
m_sSongDir = sDir ;
// save group name
CStringArray sDirectoryParts ;
2003-01-23 04:42:51 +00:00
split ( m_sSongDir , " / " , sDirectoryParts , false ) ;
2002-10-31 02:31:15 +00:00
m_sGroupName = sDirectoryParts [ sDirectoryParts . size ( ) - 3 ] ; // second from last item
2002-08-24 07:57:28 +00:00
//
// 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
2002-08-24 07:57:28 +00:00
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 ;
2003-01-25 08:10:51 +00:00
ld . LoadFromSMFile ( GetCacheFilePath ( ) , * this , true ) ;
2002-08-24 07:57:28 +00:00
}
else
{
2002-09-10 08:39:58 +00:00
if ( ! LoadWithoutCache ( m_sSongDir ) )
2002-08-24 07:57:28 +00:00
return false ;
}
{
/* Generated filename; this doesn't always point to a loadable file,
2002-10-17 20:43:47 +00:00
* 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 ;
2002-08-24 07:57:28 +00:00
CStringArray asFileNames ;
GetDirListing ( m_sSongDir + " *.sm " , asFileNames ) ;
2002-10-31 02:31:15 +00:00
if ( ! asFileNames . empty ( ) )
2002-10-17 20:43:47 +00:00
m_sSongFileName + = asFileNames [ 0 ] ;
else {
GetDirListing ( m_sSongDir + " *.dwi " , asFileNames ) ;
2002-10-31 02:31:15 +00:00
if ( ! asFileNames . empty ( ) ) {
2002-10-17 20:43:47 +00:00
m_sSongFileName + = asFileNames [ 0 ] ;
/* XXX: This would mess up "vote.for.dwight.d.eisenhower.dwi". */
m_sSongFileName . Replace ( " .dwi " , " .sm " ) ;
} else {
2002-10-31 02:31:15 +00:00
m_sSongFileName + = sDirectoryParts [ sDirectoryParts . size ( ) - 2 ] ; // last item
2002-10-17 20:43:47 +00:00
m_sSongFileName + = " .sm " ;
}
}
2002-08-24 07:57:28 +00:00
}
2002-05-19 01:59:48 +00:00
2003-01-02 22:10:51 +00:00
/* Add AutoGen pointers. (These aren't cached.) */
AddAutoGenNotes ( ) ;
2003-02-16 10:12:03 +00:00
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
{
2002-12-14 21:29:44 +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() );
2002-12-14 21:29:44 +00:00
}
2002-12-14 06:22:33 +00:00
/* 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
2002-12-14 06:22:33 +00:00
m_fMusicLengthSeconds = sound . GetLengthSeconds ( ) ;
2003-02-10 21:21:48 +00:00
if ( m_fMusicLengthSeconds = = - 1 )
2002-12-14 21:29:44 +00:00
{
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 ) {
2002-12-14 21:29:44 +00:00
LOG - > Warn ( " File %s is empty? " , GetMusicPath ( ) . GetString ( ) ) ;
} else if ( m_fMusicLengthSeconds = = - 1 ) {
LOG - > Warn ( " File %s: error getting length " , GetMusicPath ( ) . GetString ( ) ) ;
}
2002-12-14 06:22:33 +00:00
}
else // ! HasMusic()
{
m_fMusicLengthSeconds = 100 ; // guess
2003-02-10 07:38:12 +00:00
LOG - > Warn ( " This song has no music file; guessing at %f seconds " , m_fMusicLengthSeconds ) ;
2002-12-14 06:22:33 +00:00
}
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 ;
}
2002-12-14 06:22:33 +00:00
/* Generate these before we autogen notes, so the new notes can inherit
* their source's values. */
2003-01-22 02:07:27 +00:00
ReCalculateRadarValuesAndLastBeat ( ) ;
2002-12-14 06:22:33 +00:00
2002-10-31 08:05:13 +00:00
TrimRight ( m_sMainTitle ) ;
2002-04-28 20:42:32 +00:00
if ( m_sMainTitle = = " " ) m_sMainTitle = " Untitled song " ;
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 " ;
2003-01-22 02:07:27 +00:00
TranslateTitles ( ) ;
2003-01-12 04:59:40 +00:00
/* XXX don't throw due to broken songs */
2002-10-31 02:31:15 +00:00
if ( m_BPMSegments . empty ( ) )
2002-12-21 18:38:29 +00:00
RageException : : Throw ( " No #BPM specified in '%s%s.' " , m_sSongDir . GetString ( ) , m_sSongFileName . GetString ( ) ) ;
2001-11-03 10:52:42 +00:00
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_fMusicSampleStartSeconds = = 0 )
m_fMusicSampleStartSeconds = this - > GetElapsedTimeFromBeat ( 100 ) ;
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. */
if ( m_fMusicSampleLengthSeconds < 3 )
m_fMusicSampleLengthSeconds = 10 ;
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.
//
2003-02-10 07:38:12 +00:00
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
{
2002-06-29 11:59:09 +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 ) ;
2002-10-31 02:31:15 +00:00
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 ) ;
2002-10-31 02:31:15 +00:00
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 ) ;
2002-10-31 02:31:15 +00:00
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
//
// 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 ) ;
2002-10-31 02:31:15 +00:00
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
2002-11-13 05:17:15 +00:00
SDL_Surface * img = IMG_Load ( m_sSongDir + arrayImages [ i ] ) ;
if ( img )
2001-12-19 14:56:22 +00:00
{
2002-11-13 05:17:15 +00:00
int width = img - > w ;
int height = img - > h ;
SDL_FreeSurface ( img ) ;
if ( ! HasBackground ( ) & & width > = 320 & & height > = 240 )
2001-12-19 14:56:22 +00:00
{
2002-07-11 19:02:26 +00:00
m_sBackgroundFile = arrayImages [ i ] ;
continue ;
}
2002-11-13 05:17:15 +00:00
if ( ! HasBanner ( ) & & 100 < width & & width < 320 & & 50 < height & & height < 240 )
2002-07-11 19:02:26 +00:00
{
m_sBannerFile = arrayImages [ i ] ;
continue ;
}
2002-11-13 05:17:15 +00:00
if ( ! HasCDTitle ( ) & & width < = 100 & & height < = 50 )
2002-07-11 19:02:26 +00:00
{
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
2002-10-06 20:17:38 +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 ) ;
2003-02-07 23:35:36 +00:00
/* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the
* music starts. */
2002-10-31 02:31:15 +00:00
if ( arrayPossibleMovies . size ( ) = = 1 )
2003-02-07 23:35:36 +00:00
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 ( ) ;
2002-11-13 05:17:15 +00:00
}
2003-01-22 02:07:27 +00:00
void Song : : TranslateTitles ( )
2002-11-13 05:17:15 +00:00
{
2003-02-10 22:59:24 +00:00
static TitleSubst tsub ;
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
}
2003-01-22 02:07:27 +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 + + )
2003-01-22 02:07:27 +00:00
{
/* 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 ;
2003-01-22 02:07:27 +00:00
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-02-16 10:12:03 +00:00
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
{
2002-10-31 02:31:15 +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 )
2003-02-16 10:12:03 +00:00
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
}
2003-02-16 10:12:03 +00:00
Notes * Song : : GetNotes ( NotesType nt , Difficulty dc ) const
2003-01-30 07:18:33 +00:00
{
2003-02-16 10:12:03 +00:00
vector < Notes * > vNotes ;
GetNotes ( vNotes , nt , dc ) ;
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
}
2003-02-10 05:30:12 +00:00
void Song : : GetEdits ( vector < Notes * > & arrayAddTo , NotesType nt , bool bIncludeAutoGen ) const
{
}
2002-09-06 08:56:28 +00:00
/* Return whether the song is playable in the given style. */
bool Song : : SongCompleteForStyle ( const StyleDef * st ) const
{
2002-10-14 00:28:27 +00:00
if ( ! SongHasNotesType ( st - > m_NotesType ) )
return false ;
2002-09-06 08:56:28 +00:00
return true ;
}
2002-08-25 19:00:12 +00:00
bool Song : : SongHasNotesType ( NotesType nt ) const
2002-08-20 06:47:30 +00:00
{
2002-10-31 02:31:15 +00:00
for ( unsigned i = 0 ; i < m_apNotes . size ( ) ; i + + ) // foreach Notes
2002-08-20 06:47:30 +00:00
if ( m_apNotes [ i ] - > m_NotesType = = nt )
return true ;
2002-08-25 19:00:12 +00:00
return false ;
}
2002-08-20 06:47:30 +00:00
2002-09-29 05:06:18 +00:00
bool Song : : SongHasNotesTypeAndDifficulty ( NotesType nt , Difficulty dc ) const
2002-08-25 19:00:12 +00:00
{
2002-10-31 02:31:15 +00:00
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 )
2002-08-25 19:00:12 +00:00
return true ;
2002-08-20 06:47:30 +00:00
return false ;
}
2002-05-19 01:59:48 +00:00
void Song : : SaveToCacheFile ( )
2002-02-11 04:46:31 +00:00
{
2002-07-31 19:40:40 +00:00
LOG - > Trace ( " Song::SaveToCacheFile() " ) ;
2002-02-11 04:46:31 +00:00
2002-08-21 02:13:08 +00:00
SONGINDEX - > AddCacheIndex ( m_sSongDir , GetHashForDirectory ( m_sSongDir ) ) ;
2002-08-25 19:00:12 +00:00
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
{
2002-08-25 19:00:12 +00:00
LOG - > Trace ( " Song::SaveToSongFile() " ) ;
2002-10-17 20:52:32 +00:00
/* 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
*/
2002-08-25 19:00:12 +00:00
CStringArray arrayOldFileNames ;
GetDirListing ( m_sSongDir + " *.bms " , arrayOldFileNames ) ;
GetDirListing ( m_sSongDir + " *.dwi " , arrayOldFileNames ) ;
GetDirListing ( m_sSongDir + " *.ksf " , arrayOldFileNames ) ;
2002-10-17 20:52:32 +00:00
GetDirListing ( m_sSongDir + " *.sm " , arrayOldFileNames ) ;
2002-08-25 19:00:12 +00:00
2002-10-31 02:31:15 +00:00
for ( unsigned i = 0 ; i < arrayOldFileNames . size ( ) ; i + + )
2002-07-02 00:27:58 +00:00
{
2002-08-25 19:00:12 +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
}
2003-01-22 02:07:27 +00:00
ReCalculateRadarValuesAndLastBeat ( ) ;
TranslateTitles ( ) ;
2002-11-13 05:17:15 +00:00
2002-08-25 19:00:12 +00:00
SaveToSMFile ( GetSongFilePath ( ) , false ) ;
2002-09-11 04:49:07 +00:00
SaveToDWIFile ( ) ;
2002-08-25 19:00:12 +00:00
}
2002-07-02 00:27:58 +00:00
2002-08-25 19:00:12 +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
{
2002-08-25 19:00:12 +00:00
LOG - > Trace ( " Song::SaveToSongFileAndDWI() " ) ;
2002-08-17 06:44:04 +00:00
CString sPath = GetSongFilePath ( ) ;
sPath . Replace ( " .sm " , " .dwi " ) ;
2002-09-09 00:22:02 +00:00
NotesWriterDWI wr ;
wr . Write ( sPath , * this ) ;
2002-08-17 06:44:04 +00:00
}
2002-08-25 19:00:12 +00:00
void Song : : AddAutoGenNotes ( )
{
2002-10-24 01:51:01 +00:00
for ( NotesType ntMissing = ( NotesType ) 0 ; ntMissing < NUM_NOTES_TYPES ; ntMissing = ( NotesType ) ( ntMissing + 1 ) )
2002-08-25 19:00:12 +00:00
{
2002-10-24 01:51:01 +00:00
if ( SongHasNotesType ( ntMissing ) )
continue ;
2002-10-11 18:23:05 +00:00
// missing Notes of this type
int iNumTracksOfMissing = GAMEMAN - > NotesTypeToNumTracks ( ntMissing ) ;
2002-10-05 14:47:03 +00:00
2002-10-11 18:23:05 +00:00
// look for closest match
NotesType ntBestMatch = ( NotesType ) - 1 ;
int iBestTrackDifference = 10000 ; // inf
2002-10-05 14:47:03 +00:00
2002-10-11 18:23:05 +00:00
for ( NotesType nt = ( NotesType ) 0 ; nt < NUM_NOTES_TYPES ; nt = ( NotesType ) ( nt + 1 ) )
{
2003-01-03 05:56:28 +00:00
vector < Notes * > apNotes ;
2003-02-10 05:30:12 +00:00
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 )
2002-10-05 14:47:03 +00:00
{
2002-10-11 18:23:05 +00:00
ntBestMatch = nt ;
iBestTrackDifference = iTrackDifference ;
2002-10-05 14:47:03 +00:00
}
2002-10-11 18:23:05 +00:00
}
2002-10-05 14:47:03 +00:00
2002-11-13 05:17:15 +00:00
if ( ntBestMatch ! = - 1 )
AutoGen ( ntMissing , ntBestMatch ) ;
}
}
2002-10-11 18:25:15 +00:00
2002-11-13 05:17: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
2002-11-13 05:17: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 ) ;
2002-08-25 19:00:12 +00:00
}
}
}
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
{
2002-10-07 05:45:59 +00:00
// return max grade of notes in difficulty class
2003-01-03 05:56:28 +00:00
vector < Notes * > aNotes ;
2003-02-10 05:30:12 +00:00
this - > GetNotes ( aNotes , st - > m_NotesType ) ;
2002-08-01 13:42:56 +00:00
SortNotesArrayByDifficulty ( aNotes ) ;
2002-02-11 04:46:31 +00:00
2002-10-07 05:45:59 +00:00
Grade grade = GRADE_NO_DATA ;
2002-10-31 02:31:15 +00:00
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
}
2002-10-07 05:45:59 +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
2002-08-21 00:47:29 +00:00
bool Song : : IsNew ( ) const
2002-08-20 21:00:56 +00:00
{
return GetNumTimesPlayed ( ) = = 0 ;
}
2002-08-21 00:47:29 +00:00
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 ;
2003-02-11 02:44:26 +00:00
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
2003-02-10 05:30:12 +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
2002-10-24 07:56:20 +00:00
int CompareSongPointersByTitle ( const Song * pSong1 , const Song * pSong2 )
2002-10-02 01:32:43 +00:00
{
//Prefer transliterations to full titles
2003-02-11 23:52:18 +00:00
CString sTitle1 = pSong1 - > GetFullTranslitTitle ( ) ;
CString sTitle2 = pSong2 - > GetFullTranslitTitle ( ) ;
2002-10-02 01:32:43 +00:00
int ret = sTitle1 . CompareNoCase ( sTitle2 ) ;
2002-10-24 07:56:20 +00:00
if ( ret < 0 ) return true ;
if ( ret > 0 ) return false ;
2002-10-02 01:32:43 +00:00
/* 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 ;
2002-10-02 01:32:43 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByTitle ( vector < Song * > & arraySongPointers )
2002-10-02 01:32:43 +00:00
{
2002-10-24 08:40:27 +00:00
sort ( arraySongPointers . begin ( ) , arraySongPointers . end ( ) , CompareSongPointersByTitle ) ;
2002-10-02 01:32:43 +00:00
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByDifficulty ( const Song * pSong1 , const Song * pSong2 )
2002-09-29 23:29:01 +00:00
{
2003-01-03 05:56:28 +00:00
vector < Notes * > aNotes1 ;
vector < Notes * > aNotes2 ;
2002-09-29 23:29:01 +00:00
2003-02-10 05:30:12 +00:00
pSong1 - > GetNotes ( aNotes1 , GAMESTATE - > GetCurrentStyleDef ( ) - > m_NotesType ) ;
pSong2 - > GetNotes ( aNotes2 , GAMESTATE - > GetCurrentStyleDef ( ) - > m_NotesType ) ;
2002-09-29 23:29:01 +00:00
int iEasiestMeter1 = 1000 ; // infinity
int iEasiestMeter2 = 1000 ; // infinity
2002-10-31 02:31:15 +00:00
unsigned i ;
for ( i = 0 ; i < aNotes1 . size ( ) ; i + + )
2003-02-04 01:56:24 +00:00
if ( aNotes1 [ i ] - > GetDifficulty ( ) ! = DIFFICULTY_BEGINNER ) //sort by anything but beginner
iEasiestMeter1 = min ( iEasiestMeter1 , aNotes1 [ i ] - > GetMeter ( ) ) ;
2002-10-31 02:31:15 +00:00
for ( i = 0 ; i < aNotes2 . size ( ) ; i + + )
2003-02-04 01:56:24 +00:00
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 ;
2002-09-29 23:29:01 +00:00
if ( iEasiestMeter1 < iEasiestMeter2 )
2002-10-24 07:56:20 +00:00
return true ;
if ( iEasiestMeter1 > iEasiestMeter2 )
return false ;
return CompareSongPointersByTitle ( pSong1 , pSong2 ) ;
2002-09-29 23:29:01 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByDifficulty ( vector < Song * > & arraySongPointers )
2002-09-29 23:29:01 +00:00
{
2002-10-24 08:40:27 +00:00
sort ( arraySongPointers . begin ( ) , arraySongPointers . end ( ) , CompareSongPointersByDifficulty ) ;
2002-09-29 23:29:01 +00:00
}
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
{
2002-08-22 21:59:47 +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 ;
2002-08-22 21:59:47 +00:00
2002-09-29 23:29:01 +00:00
/* 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
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByMostPlayed ( const Song * pSong1 , const Song * pSong2 )
2002-01-16 10:01:32 +00:00
{
2002-02-02 05:11:12 +00:00
int iNumTimesPlayed1 = pSong1 - > GetNumTimesPlayed ( ) ;
int iNumTimesPlayed2 = pSong2 - > GetNumTimesPlayed ( ) ;
2002-01-16 10:01:32 +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 ) ;
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
{
2002-10-24 08:40:27 +00:00
sort ( arraySongPointers . begin ( ) , arraySongPointers . end ( ) , CompareSongPointersByMostPlayed ) ;
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
2002-10-31 02:31:15 +00:00
bool Song : : HasMusic ( ) const { return m_sMusicFile ! = " " & & IsAFile ( GetMusicPath ( ) ) ; }
bool Song : : HasBanner ( ) const { return m_sBannerFile ! = " " & & IsAFile ( GetBannerPath ( ) ) ; }
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 ;
2002-10-31 02:31:15 +00:00
for ( unsigned i = 0 ; i < m_apNotes . size ( ) ; i + + )
2002-09-07 11:45:15 +00:00
{
2003-02-14 21:42:44 +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
2002-09-15 02:58:33 +00:00
/* Search semantics for all song files:
2002-09-10 09:03:08 +00:00
*
2002-09-15 02:58:33 +00:00
* If the path doesn't have any directory separators, it's a filename in the
* song directory.
2002-09-10 09:03:08 +00:00
*
2002-09-15 02:58:33 +00:00
* 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.
2002-09-10 09:03:08 +00:00
*
2002-09-15 02:58:33 +00:00
* Some DWI's do have relative paths that don't start with ".\".
2002-09-10 09:03:08 +00:00
*/
2002-09-15 02:58:33 +00:00
/* 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 09:03:08 +00:00
2002-09-10 08:39:58 +00:00
CString Song : : GetMusicPath ( ) const
{
2002-09-15 02:58:33 +00:00
/* 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-02-14 08:15:15 +00:00
if ( m_sMusicFile . Find ( ' / ' ) = = - 1 )
2002-09-15 02:58:33 +00:00
return m_sSongDir + m_sMusicFile ;
/* The file has a path. If it was loaded from the m_DWIPath, it's relative
* to that. */
2002-09-10 16:31:35 +00:00
if ( PREFSMAN - > m_DWIPath ! = " " & & m_sSongDir . Left ( PREFSMAN - > m_DWIPath . GetLength ( ) ) = = PREFSMAN - > m_DWIPath )
2003-02-14 08:15:15 +00:00
return PREFSMAN - > m_DWIPath + " / " + m_sMusicFile ;
2002-09-10 09:03:08 +00:00
2002-09-15 02:58:33 +00:00
/* 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 : : GetCDTitlePath ( ) const
{
2003-02-14 08:15:15 +00:00
if ( m_sCDTitleFile . Find ( ' / ' ) = = - 1 )
2002-09-15 02:58:33 +00:00
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 )
2003-02-14 08:15:15 +00:00
return PREFSMAN - > m_DWIPath + " / " + m_sCDTitleFile ;
2002-09-10 09:03:08 +00:00
2002-09-15 02:58:33 +00:00
return m_sCDTitleFile ;
2002-09-10 08:39:58 +00:00
}
2002-09-15 02:58:33 +00:00
2002-10-06 20:17:38 +00:00
CString Song : : GetBackgroundPath ( ) const
2002-09-10 08:39:58 +00:00
{
2002-10-06 20:17:38 +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 ;
}
2002-10-24 07:56:20 +00:00
/* Get the first/last beat of any currently active note pattern. If two
* players are active, they often have the same start beat, but they don't
* have to.
*
* This is currently slow (notedata can't cache the return, and getnotedata
* is slow). */
#if 0 /* XXX not finished/tested/used yet -glenn */
float Song::GetFirstBeat() const
{
float first = MAX_BEATS;
for( int pn = 0; pn < NUM_PLAYERS; ++pn) {
if(!GAMESTATE->IsPlayerEnabled(pn)) continue;
NoteData tempNoteData;
GAMESTATE->m_pCurNotes[pn]->GetNoteData( &tempNoteData );
first = min(first, tempNoteData.GetFirstBeat());
}
return first;
}
float Song::GetLastBeat() const
{
float last = MAX_BEATS;
for( int pn = 0; pn < NUM_PLAYERS; ++pn) {
if(!GAMESTATE->IsPlayerEnabled(pn)) continue;
NoteData tempNoteData;
GAMESTATE->m_pCurNotes[pn]->GetNoteData( &tempNoteData );
last = max(last, tempNoteData.GetLastBeat());
}
return last;
}
#endif
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 ( ) ;
}