Files
itgmania212121/stepmania/src/Song.cpp
T

1210 lines
37 KiB
C++
Raw Normal View History

2001-11-03 10:52:42 +00:00
#include "stdafx.h"
/*
-----------------------------------------------------------------------------
2002-06-24 22:04:31 +00:00
Class: Song
Desc: Holds metadata for a song and the song's step data.
2002-05-19 01:59:48 +00:00
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
2002-06-24 22:04:31 +00:00
Chris Danford
2003-01-16 20:21:31 +00:00
Glenn Maynard
-----------------------------------------------------------------------------
*/
2001-11-03 10:52:42 +00:00
2002-05-19 01:59:48 +00:00
#include "Notes.h"
2001-11-03 10:52:42 +00:00
#include "RageUtil.h"
2002-02-28 19:40:40 +00:00
#include <math.h> // for fmod
2002-05-01 19:14:55 +00:00
#include "RageLog.h"
2002-06-30 23:19:33 +00:00
#include "IniFile.h"
#include "Song.h"
2002-07-03 03:13:13 +00:00
#include "NoteData.h"
2002-07-23 01:41:40 +00:00
#include "MsdFile.h"
2002-12-13 23:41:46 +00:00
#include "RageSound.h"
2002-07-27 19:29:51 +00:00
#include "RageException.h"
#include "SongCacheIndex.h"
#include "GameManager.h"
2002-08-30 04:28:12 +00:00
#include "PrefsManager.h"
2002-09-07 11:45:15 +00:00
#include "StyleDef.h"
#include "Notes.h"
#include "GameState.h"
2003-01-16 20:21:31 +00:00
#include "FontCharAliases.h"
2002-06-14 22:25:22 +00:00
2002-09-06 23:36:04 +00:00
#include "NotesLoaderSM.h"
#include "NotesLoaderDWI.h"
#include "NotesLoaderBMS.h"
#include "NotesLoaderKSF.h"
#include "NotesWriterDWI.h"
2002-12-14 00:21:22 +00:00
#include "NotesWriterSM.h"
2001-11-03 10:52:42 +00:00
#include "SDL.h"
#include "SDL_image.h"
2003-01-16 20:21:31 +00:00
const int FILE_CACHE_VERSION = 104; // 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
//////////////////////////////
Song::Song()
{
m_bChangedSinceSave = false;
2002-07-03 21:27:26 +00:00
m_fBeat0OffsetInSeconds = 0;
2002-06-29 11:59:09 +00:00
m_fMusicSampleStartSeconds = 0;
m_fMusicSampleLengthSeconds = 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-03 10:52:42 +00:00
2002-06-14 22:25:22 +00:00
Song::~Song()
{
for( unsigned i=0; i<m_apNotes.size(); i++ )
2002-07-11 19:02:26 +00:00
SAFE_DELETE( m_apNotes[i] );
2002-06-14 22:25:22 +00:00
2002-10-24 20:15:24 +00:00
m_apNotes.clear();
2002-06-14 22:25:22 +00:00
}
2001-12-19 01:50:57 +00:00
2002-06-24 22:04:31 +00:00
void Song::AddBPMSegment( BPMSegment seg )
{
2002-10-31 04:23:39 +00:00
m_BPMSegments.push_back( seg );
2002-06-24 22:04:31 +00:00
SortBPMSegmentsArray( m_BPMSegments );
}
2002-07-11 19:02:26 +00:00
void Song::AddStopSegment( StopSegment seg )
{
2002-10-31 04:23:39 +00:00
m_StopSegments.push_back( seg );
2002-07-11 19:02:26 +00:00
SortStopSegmentsArray( m_StopSegments );
}
void Song::AddBackgroundChange( BackgroundChange seg )
2002-06-24 22:04:31 +00:00
{
2002-10-31 04:23:39 +00:00
m_BackgroundChanges.push_back( seg );
SortBackgroundChangesArray( m_BackgroundChanges );
2002-06-24 22:04:31 +00:00
}
float Song::GetMusicStartBeat() const
2002-08-18 23:20:18 +00:00
{
float fBPS = m_BPMSegments[0].m_fBPM / 60.0f;
return -m_fBeat0OffsetInSeconds*fBPS;
};
2002-06-24 22:04:31 +00:00
void Song::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const
2001-12-19 01:50:57 +00:00
{
// LOG->Trace( "GetBeatAndBPSFromElapsedTime( fElapsedTime = %f )", fElapsedTime );
2002-02-28 19:40:40 +00:00
// This function is a nightmare. Don't even try to understand it. :-)
2002-07-03 21:27:26 +00:00
fElapsedTime += m_fBeat0OffsetInSeconds;
2001-12-19 01:50:57 +00:00
2001-12-28 10:15:59 +00:00
for( unsigned i=0; i<m_BPMSegments.size(); i++ ) // foreach BPMSegment
2001-12-28 10:15:59 +00:00
{
2001-12-19 01:50:57 +00:00
float fStartBeatThisSegment = m_BPMSegments[i].m_fStartBeat;
bool bIsLastBPMSegment = i==m_BPMSegments.size()-1;
2001-12-19 01:50:57 +00:00
float fStartBeatNextSegment = bIsLastBPMSegment ? 40000/*inf*/ : m_BPMSegments[i+1].m_fStartBeat;
float fBeatsInThisSegment = fStartBeatNextSegment - fStartBeatThisSegment;
float fBPM = m_BPMSegments[i].m_fBPM;
float fBPS = fBPM / 60.0f;
2001-12-28 10:15:59 +00:00
// calculate the number of seconds in this segment
2001-12-19 01:50:57 +00:00
float fSecondsInThisSegment = fBeatsInThisSegment / fBPS;
unsigned j;
for( j=0; j<m_StopSegments.size(); j++ ) // foreach freeze
2001-12-28 10:15:59 +00:00
{
2002-07-11 19:02:26 +00:00
if( fStartBeatThisSegment <= m_StopSegments[j].m_fStartBeat && m_StopSegments[j].m_fStartBeat < fStartBeatNextSegment )
2001-12-28 10:15:59 +00:00
{
// this freeze lies within this BPMSegment
2002-07-11 19:02:26 +00:00
fSecondsInThisSegment += m_StopSegments[j].m_fStopSeconds;
2001-12-28 10:15:59 +00:00
}
}
2001-12-19 01:50:57 +00:00
if( fElapsedTime > fSecondsInThisSegment )
2001-12-28 10:15:59 +00:00
{
2002-06-30 23:19:33 +00:00
// this BPMSegement is NOT the current segment
2001-12-19 01:50:57 +00:00
fElapsedTime -= fSecondsInThisSegment;
continue;
2001-12-28 10:15:59 +00:00
}
// this BPMSegment IS the current segment
float fBeatEstimate = fStartBeatThisSegment + fElapsedTime*fBPS;
for( j=0; j<m_StopSegments.size(); j++ ) // foreach freeze
2001-12-28 10:15:59 +00:00
{
if( fStartBeatThisSegment > m_StopSegments[j].m_fStartBeat ||
m_StopSegments[j].m_fStartBeat > fStartBeatNextSegment )
continue;
2001-12-28 10:15:59 +00:00
// this freeze lies within this BPMSegment
2001-12-28 10:15:59 +00:00
if( m_StopSegments[j].m_fStartBeat > fBeatEstimate )
break;
fElapsedTime -= m_StopSegments[j].m_fStopSeconds;
// re-estimate
fBeatEstimate = fStartBeatThisSegment + fElapsedTime*fBPS;
if( fBeatEstimate < m_StopSegments[j].m_fStartBeat )
2001-12-28 10:15:59 +00:00
{
fBeatOut = m_StopSegments[j].m_fStartBeat;
fBPSOut = fBPS;
bFreezeOut = true;
return;
2001-12-28 10:15:59 +00:00
}
2001-12-19 01:50:57 +00:00
}
2001-12-28 10:15:59 +00:00
fBeatOut = fBeatEstimate;
fBPSOut = fBPS;
bFreezeOut = false;
return;
2001-12-19 01:50:57 +00:00
}
}
// This is a super hack, but it's only called from ScreenEdit, so it's OK.
// Writing an inverse function of GetBeatAndBPSFromElapsedTime() uber difficult,
2002-04-16 17:31:00 +00:00
// so do a binary search to get close to the correct elapsed time.
float Song::GetElapsedTimeFromBeat( float fBeat ) const
2002-04-16 17:31:00 +00:00
{
float fElapsedTimeBestGuess = this->m_fMusicLengthSeconds/2; // seconds
2002-07-11 19:02:26 +00:00
float fSecondsToMove = fElapsedTimeBestGuess; // seconds
2002-04-16 17:31:00 +00:00
float fBeatOut, fBPSOut;
2002-06-30 23:19:33 +00:00
bool bFreezeOut;
2002-04-16 17:31:00 +00:00
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;
}
CString Song::GetCacheFilePath() const
2002-05-19 01:59:48 +00:00
{
return ssprintf( "Cache\\%u", GetHashForString(m_sSongDir) );
2002-05-19 01:59:48 +00:00
}
/* Get a path to the SM containing data for this song. It might
* be a cache file. */
const CString &Song::GetSongFilePath() const
2002-07-02 00:27:58 +00:00
{
ASSERT ( m_sSongFileName.GetLength() != 0 );
return m_sSongFileName;
2002-07-02 00:27:58 +00:00
}
2002-09-11 05:15:46 +00:00
NotesLoader *Song::MakeLoader( CString sDir ) const
2001-11-03 10:52:42 +00:00
{
2002-09-11 05:15:46 +00:00
NotesLoader *ret;
/* Actually, none of these have any persistant data, so we
* could optimize this, but since they don't have any data,
* there's no real point ... */
ret = new SMLoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
ret = new DWILoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2002-09-11 05:15:46 +00:00
ret = new BMSLoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
ret = new KSFLoader;
if(ret->Loadable( sDir )) return ret;
delete ret;
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
return NULL;
}
2002-04-16 17:31:00 +00:00
2002-09-11 05:15:46 +00:00
bool Song::LoadWithoutCache( CString sDir )
{
//
// There was no entry in the cache for this song.
// Let's load it from a file, then write a cache entry.
//
2002-09-06 23:36:04 +00:00
2002-09-11 05:15:46 +00:00
NotesLoader *ld = MakeLoader( sDir );
if(!ld)
{
LOG->Warn( "Couldn't find any SM, DWI, BMS, or KSF files in '%s'. This is not a valid song directory.", sDir.GetString() );
return false;
}
2001-11-03 10:52:42 +00:00
2002-09-11 05:15:46 +00:00
bool success = ld->LoadFromDir( sDir, *this );
delete ld;
if(!success)
return false;
2002-05-19 01:59:48 +00:00
TidyUpData();
2003-01-02 22:10:51 +00:00
// save a cache file so we don't have to parse it all over again next time
SaveToCacheFile();
return true;
}
bool Song::LoadFromSongDir( CString sDir )
{
LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.GetString() );
// 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 );
m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item
//
// First look in the cache for this song (without loading NoteData)
//
2002-09-07 10:22:49 +00:00
unsigned uDirHash = SONGINDEX->GetCacheHash(m_sSongDir);
2002-09-07 10:22:06 +00:00
if( GetHashForDirectory(m_sSongDir) == uDirHash && // this cache is up to date
DoesFileExist(GetCacheFilePath()))
{
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 );
}
else
{
2002-09-10 08:39:58 +00:00
if(!LoadWithoutCache(m_sSongDir))
return false;
}
{
/* Generated filename; this doesn't always point to a loadable file,
* but instead points to the file we should write changed files to,
* and will always be an .SM.
*
* This is a little tricky. We can't always use the song title directly,
* since it might contain characters we can't store in filenames. Two
* easy options: we could manually filter out invalid characters, or we
* could use the name of the directory, which is always a valid filename
* and should always be the same as the song. The former might not catch
* everything--filename restrictions are platform-specific; we might even
* be on an 8.3 filesystem, so let's do the latter.
*
* We can't rely on searching for other data filenames; it works for DWIs,
* but not KSFs and BMSs.
*
* So, let's do this (by priority):
* 1. If there's an .SM file, use that filename. No reason to use anything
* else; it's the filename in use.
* 2. If there's a .DWI, use it with a changed extension.
* 3. Otherwise, use the name of the directory, since it's definitely a valid
* filename, and should always be the title of the song (unlike KSFs).
*/
m_sSongFileName = m_sSongDir;
CStringArray asFileNames;
GetDirListing( m_sSongDir+"*.sm", asFileNames );
if( !asFileNames.empty() )
m_sSongFileName += asFileNames[0];
else {
GetDirListing( m_sSongDir+"*.dwi", asFileNames );
if( !asFileNames.empty() ) {
m_sSongFileName += asFileNames[0];
/* XXX: This would mess up "vote.for.dwight.d.eisenhower.dwi". */
m_sSongFileName.Replace( ".dwi", ".sm" );
} else {
m_sSongFileName += sDirectoryParts[sDirectoryParts.size()-2]; // last item
m_sSongFileName += ".sm";
}
}
}
2002-05-19 01:59:48 +00:00
2003-01-02 22:10:51 +00:00
/* Add AutoGen pointers. (These aren't cached.) */
AddAutoGenNotes();
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
{
if( !HasMusic() )
{
CStringArray arrayPossibleMusic;
GetDirListing( m_sSongDir + CString("*.mp3"), arrayPossibleMusic );
GetDirListing( m_sSongDir + CString("*.ogg"), arrayPossibleMusic );
GetDirListing( m_sSongDir + CString("*.wav"), arrayPossibleMusic );
if( !arrayPossibleMusic.empty() ) // we found a match
m_sMusicFile = arrayPossibleMusic[0];
// Don't throw on missing music. -Chris
// else
2002-12-21 18:38:29 +00:00
// RageException::Throw( "The song in '%s' is missing a music file. You must place a music file in the song folder or remove the song", m_sSongDir.GetString() );
}
/* This must be done before radar calculation. */
if( HasMusic() )
{
RageSound sound;
sound.Load( GetMusicPath(), false ); /* don't pre-cache */
2003-01-02 07:58:34 +00:00
m_fMusicLengthSeconds = sound.GetLengthSeconds();
/* XXX: if(m_fMusicLengthSeconds == -1), warn and throw out the song */
if(m_fMusicLengthSeconds == 0)
{
LOG->Warn("File %s is empty?", GetMusicPath().GetString());
} else if(m_fMusicLengthSeconds == -1) {
LOG->Warn("File %s: error getting length", GetMusicPath().GetString());
}
}
else // ! HasMusic()
{
m_fMusicLengthSeconds = 100; // guess
LOG->Warn("File %s has no music; guessing at %i seconds", m_fMusicLengthSeconds);
}
/* 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";
2003-01-12 04:59:40 +00:00
/* XXX don't throw due to broken songs */
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.
//
//
// 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 );
if( !arrayPossibleBanners.empty() )
2002-07-11 19:02:26 +00:00
m_sBannerFile = arrayPossibleBanners[0];
}
2001-11-25 04:31:44 +00:00
2002-07-11 19:02:26 +00:00
if( !HasBackground() )
{
m_sBackgroundFile = "";
2001-12-19 14:56:22 +00:00
2002-07-11 19:02:26 +00:00
// find an image with "bg" or "background" in the file name
CStringArray arrayPossibleBGs;
GetDirListing( m_sSongDir + CString("*bg*.png"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*bg*.jpg"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*bg*.bmp"), arrayPossibleBGs );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*bg*.gif"), arrayPossibleBGs );
2002-07-11 19:02:26 +00:00
GetDirListing( m_sSongDir + CString("*background*.png"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*background*.jpg"), arrayPossibleBGs );
GetDirListing( m_sSongDir + CString("*background*.bmp"), arrayPossibleBGs );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*background*.gif"), arrayPossibleBGs );
if( !arrayPossibleBGs.empty() )
2002-07-11 19:02:26 +00:00
m_sBackgroundFile = arrayPossibleBGs[0];
}
2001-12-19 14:56:22 +00:00
2002-07-11 19:02:26 +00:00
if( !HasCDTitle() )
{
m_sCDTitleFile = "";
2001-12-28 10:15:59 +00:00
2002-07-11 19:02:26 +00:00
// find an image with "cdtitle" in the file name
CStringArray arrayPossibleCDTitles;
GetDirListing( m_sSongDir + CString("*cdtitle*.png"), arrayPossibleCDTitles );
GetDirListing( m_sSongDir + CString("*cdtitle*.jpg"), arrayPossibleCDTitles );
GetDirListing( m_sSongDir + CString("*cdtitle*.bmp"), arrayPossibleCDTitles );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*cdtitle*.gif"), arrayPossibleCDTitles );
if( !arrayPossibleCDTitles.empty() )
2002-07-11 19:02:26 +00:00
m_sCDTitleFile = arrayPossibleCDTitles[0];
2001-11-03 10:52:42 +00:00
}
2002-01-16 10:01:32 +00:00
2002-07-11 19:02:26 +00:00
//
// Now, For the images we still haven't found, look at the image dimensions of the remaining unclassified images.
//
CStringArray arrayImages;
GetDirListing( m_sSongDir + CString("*.png"), arrayImages );
GetDirListing( m_sSongDir + CString("*.jpg"), arrayImages );
GetDirListing( m_sSongDir + CString("*.bmp"), arrayImages );
2002-07-27 19:29:51 +00:00
GetDirListing( m_sSongDir + CString("*.gif"), arrayImages );
unsigned i;
for( i=0; i<arrayImages.size(); i++ ) // foreach image
2001-11-03 10:52:42 +00:00
{
2002-07-11 19:02:26 +00:00
// Skip any image that we've already classified
2002-07-23 01:41:40 +00:00
if( HasBanner() && stricmp(m_sBannerFile, arrayImages[i])==0 )
2002-07-11 19:02:26 +00:00
continue; // skip
2001-12-19 14:56:22 +00:00
2002-07-23 01:41:40 +00:00
if( HasBackground() && stricmp(m_sBackgroundFile, arrayImages[i])==0 )
2002-07-11 19:02:26 +00:00
continue; // skip
2001-12-19 14:56:22 +00:00
2002-07-23 01:41:40 +00:00
if( HasCDTitle() && stricmp(m_sCDTitleFile, arrayImages[i])==0 )
2002-07-11 19:02:26 +00:00
continue; // skip
2001-11-25 04:31:44 +00:00
SDL_Surface *img = IMG_Load( m_sSongDir + arrayImages[i] );
if( img )
2001-12-19 14:56:22 +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;
}
if( !HasBanner() && 100<width && width<320 && 50<height && height<240 )
2002-07-11 19:02:26 +00:00
{
m_sBannerFile = arrayImages[i];
continue;
}
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
// 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 );
if( arrayPossibleMovies.size() == 1 )
{
CString sBGMovieFile = arrayPossibleMovies[0];
// calculate start beat of music
float fMusicStartBeat, fBPS;
bool bFreeze;
this->GetBeatAndBPSFromElapsedTime( -this->m_fBeat0OffsetInSeconds, fMusicStartBeat, fBPS, bFreeze );
this->AddBackgroundChange( BackgroundChange(fMusicStartBeat,sBGMovieFile) );
}
2001-11-30 09:38:35 +00:00
}
2002-05-19 01:59:48 +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) )
{
2003-01-03 05:56:28 +00:00
vector<Notes*> apNotes;
GetNotesThatMatch( nt, apNotes );
if( apNotes.size() == 1 )
{
2003-01-02 22:10:51 +00:00
if( 0 == apNotes[0]->GetDescription().CompareNoCase("smaniac") )
{
2003-01-02 22:10:51 +00:00
apNotes[0]->SetDescription("Challenge");
apNotes[0]->SetDifficulty(DIFFICULTY_HARD);
}
}
}
2002-12-18 23:07:58 +00:00
for( i=0; i<m_apNotes.size(); i++ )
m_apNotes[i]->Compress();
}
2003-01-13 06:04:44 +00:00
struct TitleTrans
{
CString TitleFrom, SubFrom, ArtistFrom, /* regex */
TitleTo, SubTo, ArtistTo; /* plain text */
TitleTrans(CString tf, CString sf, CString af, CString tt, CString st, CString at):
TitleFrom(tf), SubFrom(sf), ArtistFrom(af),
TitleTo(tt), SubTo(st), ArtistTo(at) { }
bool Matches(CString title, CString sub, CString artist)
{
if(!TitleFrom.empty() && !regex(TitleFrom, title)) return false; /* no match */
if(!SubFrom.empty() && !regex(SubFrom, sub)) return false; /* no match */
if(!ArtistFrom.empty() && !regex(ArtistFrom, artist)) return false; /* no match */
return true;
}
};
void Song::ReCalulateRadarValuesAndLastBeat()
{
2002-07-11 19:02:26 +00:00
//
// calculate radar values and first/last beat
//
2003-01-13 06:04:44 +00:00
unsigned int i;
for( i=0; i<m_apNotes.size(); i++ )
2002-05-19 01:59:48 +00:00
{
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++ )
{
2003-01-02 22:10:51 +00:00
/* If it's autogen, radar vals come from the parent. */
if(pNotes->IsAutogen())
continue;
2003-01-02 22:10:51 +00:00
pNotes->SetRadarValue(r, NoteDataUtil::GetRadarValue( tempNoteData, (RadarCategory)r, m_fMusicLengthSeconds ));
}
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
/* XXX make theme metrics for these or something. Candy makes it
* a little annoying ...*/
2003-01-13 06:04:44 +00:00
static vector<TitleTrans> ttab;
/* These REs aren't completely tested. XXX */
if(ttab.empty())
{
2003-01-16 20:21:31 +00:00
/* Ambiguous, so check artist. Do this early; Riyu will be replaced later: */
2003-01-13 06:04:44 +00:00
ttab.push_back(TitleTrans("^Candy$", "", "^Luv.*", "Candy &whitestar;", "", "") );
2003-01-18 03:14:40 +00:00
ttab.push_back(TitleTrans("^Candy$", "", ".*Riyu.*", "Candy &whiteheart;", "", "") );
2003-01-13 06:04:44 +00:00
2003-01-16 20:21:31 +00:00
/* Make sure this appears after the above "Riyu" match, so it doesn't
* break it. I've seen both "Kosaku" and "Kosaka"; I think Kosaka is
* correct, but handle both. */
ttab.push_back(TitleTrans("", "", "Riyu Kosak[au]", "", "", "&kosaka1;&kosaka2;&hri;&hyu;") );
2003-01-18 03:14:40 +00:00
ttab.push_back(TitleTrans("", "", "Kosak[au] Riyu", "", "", "&kosaka1;&kosaka2;&hri;&hyu;") );
2003-01-16 20:21:31 +00:00
/* This is fuzzy, so check the artist, too. */
2003-01-13 06:04:44 +00:00
ttab.push_back(TitleTrans(".*Japan.*", "", "(Re-Venge)|(RevenG)", "&matsuri; Japan", "", "") );
2003-01-16 20:21:31 +00:00
/* Fix up hacked titles: */
ttab.push_back(TitleTrans("^Max 300$", "", "%", "", "", "&omega;") );
ttab.push_back(TitleTrans("^Bre=kdown$", "", "", "Bre&flipped-a;kdown", "", "") );
2003-01-13 06:04:44 +00:00
ttab.push_back(TitleTrans("^Candy #$", "", "", "Candy &whitestar;", "", "") );
ttab.push_back(TitleTrans("^Candy \\$$", "", "", "Candy &whiteheart;", "", "") );
2003-01-16 20:21:31 +00:00
ttab.push_back(TitleTrans("^} JAPAN$", "", "", "&matsuri; Japan", "", "") );
ttab.push_back(TitleTrans("^\\+\\{$", "", "", "&kakumei1;&kakumei2;", "", "") );
ttab.push_back(TitleTrans("^Sweet Sweet \\$ Magic$", "", "", "Sweet Sweet &whiteheart; Magic", "", "") );
2003-01-13 06:04:44 +00:00
2003-01-16 20:21:31 +00:00
/* Special stuff is done. Titles: */
ttab.push_back(TitleTrans("^Matsuri Japan$", "", "", "&matsuri; Japan", "", "") );
ttab.push_back(TitleTrans("^Kakumei$", "", "", "&kakumei1;&kakumei2;", "", "") );
2003-01-19 22:50:40 +00:00
ttab.push_back(TitleTrans("^Sweet Sweet (Love )?Magic$", "", "", "Sweet Sweet &whiteheart; Magic", "", "") );
2003-01-16 20:21:31 +00:00
ttab.push_back(TitleTrans("^Breakdown$", "", "", "Bre&flipped-a;kdown", "", "") );
2003-01-19 22:43:16 +00:00
/* サナ・モレッテ・ネ・エンテ
* People can't decide how they want to spell this, so cope with
* both l or r, and one or two l/r and t. */
ttab.push_back(TitleTrans("^Sana Mo((ll?)|(rr?))et(t?)e Ne Ente", "", "",
2003-01-16 20:21:31 +00:00
"&ksa;&kna;&kdot;&kmo;&kre;&kq;&kte;&kdot;&kne;&kdot;&ke;&kn;&kte;", "", ""));
ttab.push_back(TitleTrans("^Sobakasu$", "", "", "&hso;&hba;&hka;&hsu;", "", "") );
2003-01-13 06:04:44 +00:00
2003-01-16 20:21:31 +00:00
/* 夜空ノムコウ */
ttab.push_back(TitleTrans("^Yozora no Muko$", "", "", "&yozora1;&yozora2;&hno;&kmu;&kko;", "", "") );
2003-01-13 06:04:44 +00:00
2003-01-19 22:43:16 +00:00
/* 17才 */
ttab.push_back(TitleTrans("^17 ?(Sai)?$", "", "", "17&sai;", "", "") );
2003-01-16 20:21:31 +00:00
/* XXX blackstar or whitestar? Is this supposed to be capped? */
ttab.push_back(TitleTrans("^Mobo Moga$", "", "", "Mobo&whitestar;Moga", "", "") );
/* XXX whiteheart or blackheart? Is this supposed to be capped? */
ttab.push_back(TitleTrans("^Love Shine$", "", "", "Love &whiteheart; Shine", "", "") );
/* ロマンスの神様 */
ttab.push_back(TitleTrans("^God of Romance$", "", "", "&kro;&kma;&kn;&ksu;&hno;&kami;&sama;", "", "") );
/* ダンシン・オール・アローン */
ttab.push_back(TitleTrans("^Dancing Pompokolin$", "", "", "&kda;&kn;&ksi;&kn;&kdot;&ko;&kdash;&kru;&kdot;&ka;&kro;&kdash;&kn;", "", "") );
2003-01-18 03:14:40 +00:00
/* メキシコ民謡 */
ttab.push_back(TitleTrans("", "", "^Spanish Folk Music$", "", "", "&kme;&kki;&ksi;&kko;&minyou1;&minyou2;") );
2003-01-16 20:21:31 +00:00
/* 青い振動 XXX verify this title */
ttab.push_back(TitleTrans("^Blue Impulse$", "", "", "&aoi;&hi;&shoudou1;&shoudou2;", "", "") );
/* XXX 大見解 verify this title (Night Line or Nightline or am I completely wrong?) */
ttab.push_back(TitleTrans("^Night ?Line$", "", "", "&ookii;&kenkai1;&kenkai2;", "", "") );
2003-01-18 03:14:40 +00:00
/* ♡LOVE²シュガ→♡ */
ttab.push_back(TitleTrans("^Love Love Sugar$", "", "", "&whiteheart;LOVE&squared; &ksi;&kyus;&kga;&rightarrow;&whiteheart;", "", "") );
/* 三毛猫ロック */
ttab.push_back(TitleTrans("^Mikeneko Rock$", "", "", "&num-san;&hair;&neko;&kro;&kq;&kku;", "", "") );
/* XXX: "door of magic" (tobira no mahou) -> 魔法の扉 */
/* XXX スペース★マコのテーマ (space? special? * "mako"?'s team) (title or subtitle, not sure) */
2003-01-16 20:21:31 +00:00
/* Subtitles: */
/* XXX それぞれの明日 (every tomorrow?) (subtitle) (title is Graduation) */
2003-01-18 03:14:40 +00:00
2003-01-16 20:21:31 +00:00
/* Artists: */
ttab.push_back(TitleTrans("", "", "Omega", "", "", "&omega;") );
2003-01-18 03:14:40 +00:00
/* 亜熱帯マジ-SKA爆弾 (serious tropical ska bomb? ruh roh) */
ttab.push_back(TitleTrans("", "", "^Anettai Maji.*Ska (Bakudan|Bukuden)", "", "", "&anettai1;&anettai2;&anettai3;&kma;&kji;-SKA&bakudan1;&bakudan2;") );
/* 新谷さなえ (Sanae or incorrect Sana) */
ttab.push_back(TitleTrans("", "", "Sanae? Shintani", "", "", "&shintani1;&shintani2;&hsa;&hna;&he;") );
2003-01-16 20:21:31 +00:00
ttab.push_back(TitleTrans("", "", "dj TAKA feat. ?Noria", "", "", "dj TAKA feat. &hno;&hri;&ha;") );
2003-01-18 03:14:40 +00:00
/* くにたけみゆき */
ttab.push_back(TitleTrans("", "", "Miyuki Kunitake", "", "", "&hku;&hni;&hta;&hke;&hmi;&hyu;&hki;") );
ttab.push_back(TitleTrans("", "", "Kunitake Miyuki", "", "", "&hku;&hni;&hta;&hke;&hmi;&hyu;&hki;") );
// ttab.push_back(TitleTrans(".*Legend.*", "", "ZZ", "", "", "&zz;") );
2003-01-16 20:21:31 +00:00
}
2003-01-13 06:04:44 +00:00
for(i = 0; i < ttab.size(); ++i)
{
if(!ttab[i].Matches(m_sMainTitle, m_sSubTitle, m_sArtist))
continue;
/* The song matches. Replace whichever strings aren't empty: */
if(!ttab[i].TitleTo.empty())
{
m_sMainTitleTranslit = m_sMainTitle;
2003-01-13 06:04:44 +00:00
m_sMainTitle = ttab[i].TitleTo;
2003-01-16 20:21:31 +00:00
FontCharAliases::ReplaceMarkers( m_sMainTitle );
2003-01-13 06:04:44 +00:00
}
if(!ttab[i].SubTo.empty())
{
m_sSubTitleTranslit = m_sSubTitle;
m_sSubTitle = ttab[i].SubTo;
2003-01-16 20:21:31 +00:00
FontCharAliases::ReplaceMarkers( m_sSubTitle );
2003-01-13 06:04:44 +00:00
}
if(!ttab[i].ArtistTo.empty())
{
m_sArtistTranslit = m_sArtist;
m_sArtist = ttab[i].ArtistTo;
2003-01-16 20:21:31 +00:00
FontCharAliases::ReplaceMarkers( m_sArtist );
}
}
2001-11-30 09:38:35 +00:00
}
2003-01-03 05:56:28 +00:00
void Song::GetNotesThatMatch( NotesType nt, vector<Notes*>& arrayAddTo ) const
2002-04-16 17:31:00 +00:00
{
for( unsigned i=0; i<m_apNotes.size(); i++ ) // for each of the Song's Notes
2002-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-12-28 10:15:59 +00:00
}
/* Return whether the song is playable in the given style. */
bool Song::SongCompleteForStyle( const StyleDef *st ) const
{
if(!SongHasNotesType(st->m_NotesType))
return false;
return true;
}
bool Song::SongHasNotesType( NotesType nt ) const
{
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Notes
if( m_apNotes[i]->m_NotesType == nt )
return true;
return false;
}
2002-09-29 05:06:18 +00:00
bool Song::SongHasNotesTypeAndDifficulty( NotesType nt, Difficulty dc ) const
{
for( unsigned i=0; i < m_apNotes.size(); i++ ) // foreach Notes
2003-01-02 22:10:51 +00:00
if( m_apNotes[i]->m_NotesType == nt && m_apNotes[i]->GetDifficulty() == dc )
return true;
return false;
}
2002-05-19 01:59:48 +00:00
void Song::SaveToCacheFile()
2002-02-11 04:46:31 +00:00
{
LOG->Trace( "Song::SaveToCacheFile()" );
2002-02-11 04:46:31 +00:00
SONGINDEX->AddCacheIndex(m_sSongDir, GetHashForDirectory(m_sSongDir));
SaveToSMFile( GetCacheFilePath(), true );
2002-05-19 01:59:48 +00:00
}
2002-02-11 04:46:31 +00:00
2002-09-11 04:49:07 +00:00
void Song::Save()
2002-04-16 17:31:00 +00:00
{
LOG->Trace( "Song::SaveToSongFile()" );
/* rename all old files to avoid confusion.
*
* This also serves as a backup, so rename .sm's, too. If we crash when
* saving the .sm, we don't want to lose what we had. But, what we really
* should be doing is saving to another file (eg. foo.sm.new), then once we
* know we havn't crashed, move the old .sm to .sm.old and the new one to
* the real filename. That way, if we crash, we don't leave the song in an
* unplayable state where the user has to manually un-rename stuff. XXX -glenn
*/
CStringArray arrayOldFileNames;
GetDirListing( m_sSongDir + "*.bms", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.dwi", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.ksf", arrayOldFileNames );
GetDirListing( m_sSongDir + "*.sm", arrayOldFileNames );
for( unsigned i=0; i<arrayOldFileNames.size(); i++ )
2002-07-02 00:27:58 +00:00
{
CString sOldPath = m_sSongDir + arrayOldFileNames[i];
CString sNewPath = sOldPath + ".old";
MoveFile( sOldPath, sNewPath );
2002-07-02 00:27:58 +00:00
}
ReCalulateRadarValuesAndLastBeat();
SaveToSMFile( GetSongFilePath(), false );
2002-09-11 04:49:07 +00:00
SaveToDWIFile();
}
2002-07-02 00:27:58 +00:00
void Song::SaveToSMFile( CString sPath, bool bSavingCache )
{
2002-12-14 00:21:22 +00:00
LOG->Trace( "Song::SaveToSMFile('%s')", sPath.GetString() );
2002-04-16 17:31:00 +00:00
2002-12-14 00:21:22 +00:00
NotesWriterSM wr;
wr.Write(sPath, *this, bSavingCache);
2002-02-11 04:46:31 +00:00
}
2002-09-11 04:49:07 +00:00
void Song::SaveToDWIFile()
2002-08-17 06:44:04 +00:00
{
LOG->Trace( "Song::SaveToSongFileAndDWI()" );
2002-08-17 06:44:04 +00:00
CString sPath = GetSongFilePath();
sPath.Replace( ".sm", ".dwi" );
NotesWriterDWI wr;
wr.Write(sPath, *this);
2002-08-17 06:44:04 +00:00
}
void Song::AddAutoGenNotes()
{
for( NotesType ntMissing=(NotesType)0; ntMissing<NUM_NOTES_TYPES; ntMissing=(NotesType)(ntMissing+1) )
{
if( SongHasNotesType(ntMissing) )
continue;
// missing Notes of this type
int iNumTracksOfMissing = GAMEMAN->NotesTypeToNumTracks(ntMissing);
// look for closest match
NotesType ntBestMatch = (NotesType)-1;
int iBestTrackDifference = 10000; // inf
for( NotesType nt=(NotesType)0; nt<NUM_NOTES_TYPES; nt=(NotesType)(nt+1) )
{
2003-01-03 05:56:28 +00:00
vector<Notes*> apNotes;
this->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 )
{
ntBestMatch = nt;
iBestTrackDifference = iTrackDifference;
}
}
if( ntBestMatch != -1 )
AutoGen( ntMissing, ntBestMatch );
}
}
2002-10-11 18:25:15 +00:00
void Song::AutoGen( NotesType ntTo, NotesType ntFrom )
{
2003-01-02 22:10:51 +00:00
// int iNumTracksOfTo = GAMEMAN->NotesTypeToNumTracks(ntTo);
2002-10-11 18:25:15 +00:00
for( unsigned int j=0; j<m_apNotes.size(); j++ )
{
Notes* pOriginalNotes = m_apNotes[j];
if( pOriginalNotes->m_NotesType == ntFrom )
{
2002-10-11 18:25:15 +00:00
Notes* pNewNotes = new Notes;
2003-01-02 22:10:51 +00:00
pNewNotes->AutogenFrom(pOriginalNotes, ntTo);
2002-10-31 04:23:39 +00:00
this->m_apNotes.push_back( pNewNotes );
}
}
}
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
{
// return max grade of notes in difficulty class
2003-01-03 05:56:28 +00:00
vector<Notes*> aNotes;
this->GetNotesThatMatch( st->m_NotesType, aNotes );
2002-08-01 13:42:56 +00:00
SortNotesArrayByDifficulty( aNotes );
2002-02-11 04:46:31 +00:00
Grade grade = GRADE_NO_DATA;
for( unsigned i=0; i<aNotes.size(); i++ )
2002-04-16 17:31:00 +00:00
{
2002-09-06 10:15:00 +00:00
const Notes* pNotes = aNotes[i];
2003-01-02 22:10:51 +00:00
if( pNotes->GetDifficulty() == dc )
grade = max( grade, pNotes->m_TopGrade );
2002-04-16 17:31:00 +00:00
}
return grade;
2002-04-16 17:31:00 +00:00
}
2002-01-16 10:01:32 +00:00
2002-05-19 01:59:48 +00:00
bool Song::IsNew() const
2002-08-20 21:00:56 +00:00
{
return GetNumTimesPlayed()==0;
}
bool Song::IsEasy( NotesType nt ) const
2002-08-20 21:00:56 +00:00
{
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)
{
//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;
/* The titles are the same. Ensure we get a consistent ordering
* by comparing the unique SongFilePaths. */
2003-01-03 05:29:45 +00:00
return pSong1->GetSongFilePath().CompareNoCase(pSong2->GetSongFilePath()) < 0;
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByTitle( vector<Song*> &arraySongPointers )
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByTitle );
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByDifficulty(const Song *pSong1, const Song *pSong2)
{
2003-01-03 05:56:28 +00:00
vector<Notes*> aNotes1;
vector<Notes*> aNotes2;
pSong1->GetNotesThatMatch( GAMESTATE->GetCurrentStyleDef()->m_NotesType, aNotes1 );
pSong2->GetNotesThatMatch( GAMESTATE->GetCurrentStyleDef()->m_NotesType, aNotes2 );
int iEasiestMeter1 = 1000; // infinity
int iEasiestMeter2 = 1000; // infinity
unsigned i;
for( i=0; i<aNotes1.size(); i++ )
2003-01-02 22:10:51 +00:00
iEasiestMeter1 = min( iEasiestMeter1, aNotes1[i]->GetMeter() );
for( i=0; i<aNotes2.size(); i++ )
2003-01-02 22:10:51 +00:00
iEasiestMeter2 = min( iEasiestMeter2, aNotes2[i]->GetMeter() );
if( iEasiestMeter1 < iEasiestMeter2 )
2002-10-24 07:56:20 +00:00
return true;
if( iEasiestMeter1 > iEasiestMeter2 )
return false;
return CompareSongPointersByTitle( pSong1, pSong2 );
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByDifficulty( vector<Song*> &arraySongPointers )
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByDifficulty );
}
2002-10-24 07:56:20 +00:00
bool CompareSongPointersByBPM(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
float fMinBPM1, fMaxBPM1, fMinBPM2, fMaxBPM2;
pSong1->GetMinMaxBPM( fMinBPM1, fMaxBPM1 );
pSong2->GetMinMaxBPM( fMinBPM2, fMaxBPM2 );
if( fMaxBPM1 < fMaxBPM2 )
2002-10-24 07:56:20 +00:00
return true;
if( fMaxBPM1 > fMaxBPM2 )
return false;
return CompareCStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() );
2002-01-16 10:01:32 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByBPM( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByBPM );
2002-01-16 10:01:32 +00:00
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByArtist(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
2002-06-29 11:59:09 +00:00
CString sArtist1 = pSong1->m_sArtist;
CString sArtist2 = pSong2->m_sArtist;
2002-01-16 10:01:32 +00:00
2002-02-24 01:43:11 +00:00
if( sArtist1 < sArtist2 )
2002-10-24 07:56:20 +00:00
return true;
if( sArtist1 > sArtist2 )
return false;
return CompareSongPointersByTitle( pSong1, pSong2 );
2002-01-16 10:01:32 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByArtist( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByArtist );
2002-01-16 10:01:32 +00:00
}
2002-10-24 07:56:20 +00:00
int CompareSongPointersByGroup(const Song *pSong1, const Song *pSong2)
2002-01-16 10:01:32 +00:00
{
const CString &sGroup1 = pSong1->m_sGroupName;
const CString &sGroup2 = pSong2->m_sGroupName;
2002-01-16 10:01:32 +00:00
2002-02-24 01:43:11 +00:00
if( sGroup1 < sGroup2 )
2002-10-24 07:56:20 +00:00
return true;
if( sGroup1 > sGroup2 )
return false;
/* Same group; compare by difficulty. */
2002-10-24 07:56:20 +00:00
return CompareSongPointersByDifficulty( pSong1, pSong2 );
2002-01-16 10:01:32 +00:00
}
2003-01-03 05:56:28 +00:00
void SortSongPointerArrayByGroup( vector<Song*> &arraySongPointers )
2002-01-16 10:01:32 +00:00
{
2002-10-24 08:40:27 +00:00
sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByGroup );
2002-01-16 10:01:32 +00:00
}
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
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;
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
/* Search semantics for all song files:
*
* If the path doesn't have any directory separators, it's a filename in the
* song directory.
*
* If it does, it's relative to the top directory of the tree it was loaded
* from, eg ".\music\stuff\song.ogg". Most of the time, that's the SM tree,
* and that's the PWD, so just return it directly. If the file was originally
* loaded from the DWIPath, it's relative to that, so prepend it.
*
* Some DWI's do have relative paths that don't start with ".\".
*/
/* We only follow this for song files and cdtitles; it's for compatibility
* with DWI. We prefer paths relative to the song directory; only support
* that for all other paths. */
/* Note: Prepending the dwipath is ugly. The first impression might be to
* add a Song::TopFilePath, but don't do that--we have too much stuff in there
* already. We don't really need it; this is the only place it'd be used. What
* we *should* be doing is prepending this path when we first load the song.
* However, dwipaths are usually like "c:\games\dwi", and we can't store colons
* in SM's; they'll get interpreted as delimiters. So:
* XXX: Add some kind of escape character to SM's.
*
* -glenn */
2002-09-10 08:39:58 +00:00
CString Song::GetMusicPath() const
{
/* If there's no path in the music file, the file is in the same directory
* as the song. (This is the preferred configuration.) */
if( m_sMusicFile.Find('\\') == -1)
return m_sSongDir+m_sMusicFile;
/* The file has a path. If it was loaded from the m_DWIPath, it's relative
* to that. */
if( PREFSMAN->m_DWIPath!="" && m_sSongDir.Left(PREFSMAN->m_DWIPath.GetLength()) == PREFSMAN->m_DWIPath )
return PREFSMAN->m_DWIPath+"\\"+m_sMusicFile;
/* Otherwise, it's relative to the top of the SM directory (the CWD), so
* return it directly. */
return m_sMusicFile;
2002-09-10 08:39:58 +00:00
}
CString Song::GetBannerPath() const
{
return m_sSongDir+m_sBannerFile;
}
CString Song::GetCDTitlePath() const
{
if( m_sCDTitleFile.Find('\\') == -1)
return m_sSongDir+m_sCDTitleFile;
2002-09-10 20:55:06 +00:00
if( PREFSMAN->m_DWIPath!="" && m_sSongDir.Left(PREFSMAN->m_DWIPath.GetLength()) == PREFSMAN->m_DWIPath )
return PREFSMAN->m_DWIPath+"\\"+m_sCDTitleFile;
return m_sCDTitleFile;
2002-09-10 08:39:58 +00:00
}
CString Song::GetBackgroundPath() const
2002-09-10 08:39:58 +00:00
{
return m_sSongDir+m_sBackgroundFile;
2002-09-10 08:39:58 +00:00
}
2002-10-24 07:56:20 +00:00
2003-01-09 09:01:20 +00:00
CString Song::GetSortTitle() const
{
CString Title = m_sMainTitleTranslit.empty()?
m_sMainTitle: m_sMainTitleTranslit;
CString SubTitle = m_sSubTitleTranslit.empty()?
m_sSubTitle:m_sSubTitleTranslit;
if(!SubTitle.empty())
{
Title += " " + SubTitle;
}
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