2001-11-03 10:52:42 +00:00
#include "stdafx.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
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"
#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"
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-01-02 22:15:54 +00:00
const int FILE_CACHE_VERSION = 103 ; // 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
}
void SortBPMSegmentsArray ( CArray < BPMSegment , BPMSegment &> & arrayBPMSegments )
{
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
}
2002-07-11 19:02:26 +00:00
void SortStopSegmentsArray ( CArray < StopSegment , 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
}
2002-08-23 20:18:29 +00:00
void SortBackgroundChangesArray ( CArray < BackgroundChange , 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-06-24 22:04:31 +00:00
{
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-07-11 19:02:26 +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
while ( fSecondsToMove > 0.1f )
{
2002-06-30 23:19:33 +00:00
GetBeatAndBPSFromElapsedTime ( fElapsedTimeBestGuess , fBeatOut , fBPSOut , bFreezeOut );
2002-04-16 17:31:00 +00:00
if ( fBeatOut > fBeat )
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
{
2002-08-25 19:00:12 +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
{
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 ;
ret = new DWILoader ;
if ( ret -> Loadable ( sDir )) return ret ;
delete ret ;
ret = new BMSLoader ;
if ( ret -> Loadable ( sDir )) return ret ;
delete ret ;
ret = new KSFLoader ;
if ( ret -> Loadable ( sDir )) return ret ;
delete ret ;
return NULL ;
}
2002-08-24 07:57:28 +00:00
bool Song :: LoadWithoutCache ( CString sDir )
2001-11-03 10:52:42 +00:00
{
2002-05-19 01:59:48 +00:00
//
// 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 )
{
2002-10-29 07:58:44 +00:00
LOG -> Trace ( "Song::LoadFromSongDir(%s)" , sDir . GetString () );
2002-08-24 07:57:28 +00:00
// make sure there is a trailing '\\' at the end of sDir
if ( sDir . Right ( 1 ) != " \\ " )
sDir += " \\ " ;
// save song dir
m_sSongDir = sDir ;
// save group name
CStringArray sDirectoryParts ;
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 ()))
{
2002-10-29 07:58:44 +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 );
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 ();
2002-05-19 01:59:48 +00:00
return true ;
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 ();
/* XXX: if(m_fMusicLengthSeconds == -1), warn and throw out the song */
2002-12-14 21:29:44 +00:00
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 ());
}
2002-12-14 06:22:33 +00:00
}
else // ! HasMusic()
{
m_fMusicLengthSeconds = 100 ; // guess
2002-12-14 21:29:44 +00:00
LOG -> Warn ( "File %s has no music; guessing at %i seconds" , m_fMusicLengthSeconds );
2002-12-14 06:22:33 +00:00
}
/* Generate these before we autogen notes, so the new notes can inherit
* their source's values. */
ReCalulateRadarValuesAndLastBeat ();
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" ;
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 );
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.
//
//
// 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-03 10:52:42 +00:00
}
2002-01-16 10:01:32 +00:00
2002-07-03 21:27:26 +00:00
if ( ! HasBackground () )
2001-11-03 10:52:42 +00:00
{
2002-06-29 11:59:09 +00:00
m_sBackgroundFile = "" ;
2001-11-25 04:31:44 +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 ];
2002-01-16 10:01:32 +00:00
}
2002-07-03 21:27:26 +00:00
if ( ! HasCDTitle () )
2002-04-16 17:31:00 +00:00
{
2002-06-29 11:59:09 +00:00
m_sCDTitleFile = "" ;
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 ];
2002-04-16 17:31:00 +00:00
}
2002-05-19 01:59:48 +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
2002-05-19 01:59:48 +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
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
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
2002-11-13 05:17:15 +00:00
SDL_Surface * img = IMG_Load ( m_sSongDir + arrayImages [ i ] );
if ( img )
2002-07-11 19:02:26 +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 )
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 ;
}
}
}
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 () )
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 );
2002-10-31 02:31:15 +00:00
if ( arrayPossibleMovies . size () == 1 )
2002-10-06 20:17:38 +00:00
{
CString sBGMovieFile = arrayPossibleMovies [ 0 ];
// calculate start beat of music
float fMusicStartBeat , fBPS ;
bool bFreeze ;
this -> GetBeatAndBPSFromElapsedTime ( - this -> m_fBeat0OffsetInSeconds , fMusicStartBeat , fBPS , bFreeze );
2002-10-25 07:35:47 +00:00
this -> AddBackgroundChange ( BackgroundChange ( fMusicStartBeat , sBGMovieFile ) );
2002-10-06 20:17:38 +00:00
}
2002-07-11 19:02:26 +00:00
}
2002-11-13 05:17:15 +00:00
// challenge notes are encoded as smaniac. If there is only one Notes for
// a NotesType and it's "smaniac", then convert it to "Challenge"
for ( NotesType nt = ( NotesType ) 0 ; nt < NUM_NOTES_TYPES ; nt = ( NotesType )( nt + 1 ) )
{
CArray < Notes * , Notes *> apNotes ;
GetNotesThatMatch ( nt , apNotes );
if ( apNotes . size () == 1 )
{
2003-01-02 22:10:51 +00:00
if ( 0 == apNotes [ 0 ] -> GetDescription (). CompareNoCase ( "smaniac" ) )
2002-11-13 05:17:15 +00:00
{
2003-01-02 22:10:51 +00:00
apNotes [ 0 ] -> SetDescription ( "Challenge" );
apNotes [ 0 ] -> SetDifficulty ( DIFFICULTY_HARD );
2002-11-13 05:17:15 +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
}
void Song :: ReCalulateRadarValuesAndLastBeat ()
{
2002-07-11 19:02:26 +00:00
//
// calculate radar values and first/last beat
//
2002-11-13 05:17:15 +00:00
for ( unsigned int i = 0 ; i < m_apNotes . size (); i ++ )
2002-07-11 19:02:26 +00:00
{
Notes * pNotes = m_apNotes [ i ];
2002-07-03 03:13:13 +00:00
NoteData tempNoteData ;
pNotes -> GetNoteData ( & tempNoteData );
2002-05-19 01:59:48 +00:00
2002-07-02 00:27:58 +00:00
for ( int r = 0 ; r < NUM_RADAR_VALUES ; r ++ )
2002-12-14 06:22:33 +00:00
{
2003-01-02 22:10:51 +00:00
/* If it's autogen, radar vals come from the parent. */
if ( pNotes -> IsAutogen ())
2002-12-14 06:22:33 +00:00
continue ;
2003-01-02 22:10:51 +00:00
pNotes -> SetRadarValue ( r , NoteDataUtil :: GetRadarValue ( tempNoteData , ( RadarCategory ) r , m_fMusicLengthSeconds ));
2002-12-14 06:22:33 +00:00
}
2002-07-11 19:02:26 +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 );
2002-05-19 01:59:48 +00:00
}
2002-10-06 16:56:58 +00:00
2001-11-03 10:52:42 +00:00
}
2002-10-05 20:03:14 +00:00
void Song :: GetNotesThatMatch ( NotesType nt , CArray < Notes * , Notes *>& arrayAddTo ) const
2001-11-30 09:38:35 +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-09-06 10:15:00 +00:00
{
2002-10-05 20:03:14 +00:00
if ( m_apNotes [ i ] -> m_NotesType == nt )
2002-10-31 04:23:39 +00:00
arrayAddTo . push_back ( m_apNotes [ i ] );
2002-09-06 10:15:00 +00:00
}
2001-11-30 09:38:35 +00:00
}
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-04-16 17:31:00 +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-09-11 04:49:07 +00:00
void Song :: Save ()
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-08-25 19:00:12 +00:00
{
CString sOldPath = m_sSongDir + arrayOldFileNames [ i ];
CString sNewPath = sOldPath + ".old" ;
MoveFile ( sOldPath , sNewPath );
}
2002-11-13 05:17:15 +00:00
ReCalulateRadarValuesAndLastBeat ();
2002-08-25 19:00:12 +00:00
SaveToSMFile ( GetSongFilePath (), false );
2002-09-11 04:49:07 +00:00
SaveToDWIFile ();
2002-05-19 01:59:48 +00:00
}
2002-08-25 19:00:12 +00:00
void Song :: SaveToSMFile ( CString sPath , bool bSavingCache )
2002-05-19 01:59:48 +00:00
{
2002-12-14 00:21:22 +00:00
LOG -> Trace ( "Song::SaveToSMFile('%s')" , sPath . GetString () );
2002-05-19 01:59:48 +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 );
// 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 ) )
2002-08-25 19:00:12 +00:00
{
2002-10-11 18:23:05 +00:00
CArray < Notes * , Notes *> apNotes ;
this -> GetNotesThatMatch ( nt , apNotes );
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-08-25 19:00:12 +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-08-25 19:00:12 +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-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:23:05 +00:00
{
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
}
}
}
2002-09-29 05:06:18 +00:00
Grade Song :: GetGradeForDifficulty ( const StyleDef * st , int p , 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
2002-06-14 22:25:22 +00:00
CArray < Notes * , Notes *> aNotes ;
2002-10-14 00:28:27 +00:00
this -> GetNotesThatMatch ( st -> m_NotesType , aNotes );
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 )
2002-10-07 05:45:59 +00:00
grade = max ( grade , pNotes -> m_TopGrade );
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-02-11 04:46:31 +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
{
2002-10-31 02:31:15 +00:00
for ( unsigned i = 0 ; i < m_apNotes . size (); i ++ )
2002-08-20 21:00:56 +00:00
{
Notes * pNotes = m_apNotes [ i ];
if ( pNotes -> m_NotesType != nt )
continue ;
2003-01-02 22:10:51 +00:00
if ( pNotes -> GetMeter () <= 2 )
2002-08-20 21:00:56 +00:00
return true ;
}
return false ;
}
2002-05-19 01:59:48 +00:00
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
CString sTitle1 = pSong1 -> GetSortTitle ();
CString sTitle2 = pSong2 -> GetSortTitle ();
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. */
return pSong1 -> GetSongFilePath (). CompareNoCase ( pSong2 -> GetSongFilePath ());
}
void SortSongPointerArrayByTitle ( CArray < Song * , Song *> & arraySongPointers )
{
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
{
CArray < Notes * , Notes *> aNotes1 ;
CArray < Notes * , Notes *> aNotes2 ;
2002-10-14 00:28:27 +00:00
pSong1 -> GetNotesThatMatch ( GAMESTATE -> GetCurrentStyleDef () -> m_NotesType , aNotes1 );
pSong2 -> GetNotesThatMatch ( GAMESTATE -> GetCurrentStyleDef () -> m_NotesType , aNotes2 );
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-01-02 22:10:51 +00:00
iEasiestMeter1 = min ( iEasiestMeter1 , aNotes1 [ i ] -> GetMeter () );
2002-10-31 02:31:15 +00:00
for ( i = 0 ; i < aNotes2 . size (); i ++ )
2003-01-02 22:10:51 +00:00
iEasiestMeter2 = min ( iEasiestMeter2 , aNotes2 [ i ] -> GetMeter () );
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
}
void SortSongPointerArrayByDifficulty ( CArray < Song * , Song *> & arraySongPointers )
{
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
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByBPM ( CArray < Song * , 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
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByArtist ( CArray < Song * , 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
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByGroup ( CArray < Song * , 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
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByMostPlayed ( CArray < Song * , 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
{
iTotalNumTimesPlayed += m_apNotes [ i ] -> m_iNumTimesPlayed ;
}
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.) */
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. */
2002-09-10 16:31:35 +00:00
if ( PREFSMAN -> m_DWIPath != "" && m_sSongDir . Left ( PREFSMAN -> m_DWIPath . GetLength ()) == PREFSMAN -> m_DWIPath )
2002-09-10 09:03:08 +00:00
return PREFSMAN -> m_DWIPath + " \\ " + m_sMusicFile ;
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
{
2002-09-15 02:58:33 +00:00
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 )
2002-09-10 09:03:08 +00:00
return PREFSMAN -> m_DWIPath + " \\ " + m_sCDTitleFile ;
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
/* 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