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-06-14 22:25:22 +00:00
2001-11-03 10:52:42 +00:00
2002-07-03 21:27:26 +00:00
const int FILE_CACHE_VERSION = 29 ; // increment this to force a cache reload (useful when the SM file format changes)
2001-12-28 10:15:59 +00:00
2001-12-19 01:50:57 +00:00
int CompareBPMSegments ( const void * arg1 , const void * arg2 )
{
// arg1 and arg2 are of type Step**
BPMSegment * seg1 = ( BPMSegment * ) arg1 ;
BPMSegment * seg2 = ( BPMSegment * ) arg2 ;
float score1 = seg1 -> m_fStartBeat ;
float score2 = seg2 -> m_fStartBeat ;
if ( score1 == score2 )
return 0 ;
else if ( score1 < score2 )
return - 1 ;
else
return 1 ;
}
void SortBPMSegmentsArray ( CArray < BPMSegment , BPMSegment &> & arrayBPMSegments )
{
qsort ( arrayBPMSegments . GetData (), arrayBPMSegments . GetSize (), sizeof ( BPMSegment ), CompareBPMSegments );
}
2001-12-28 10:15:59 +00:00
int CompareFreezeSegments ( const void * arg1 , const void * arg2 )
{
// arg1 and arg2 are of type Step**
FreezeSegment * seg1 = ( FreezeSegment * ) arg1 ;
FreezeSegment * seg2 = ( FreezeSegment * ) arg2 ;
float score1 = seg1 -> m_fStartBeat ;
float score2 = seg2 -> m_fStartBeat ;
if ( score1 == score2 )
return 0 ;
else if ( score1 < score2 )
return - 1 ;
else
return 1 ;
}
void SortFreezeSegmentsArray ( CArray < FreezeSegment , FreezeSegment &> & arrayFreezeSegments )
{
qsort ( arrayFreezeSegments . GetData (), arrayFreezeSegments . GetSize (), sizeof ( FreezeSegment ), CompareFreezeSegments );
}
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 ;
m_fMusicSampleLengthSeconds = 16 ; // 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 ;
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 ()
{
for ( int i = 0 ; i < m_arrayNotes . GetSize (); i ++ )
SAFE_DELETE ( m_arrayNotes [ i ] );
m_arrayNotes . RemoveAll ();
}
2001-12-19 01:50:57 +00:00
2002-06-24 22:04:31 +00:00
void Song :: AddBPMSegment ( BPMSegment seg )
{
m_BPMSegments . Add ( seg );
SortBPMSegmentsArray ( m_BPMSegments );
}
void Song :: AddFreezeSegment ( FreezeSegment seg )
{
m_FreezeSegments . Add ( seg );
SortFreezeSegmentsArray ( m_FreezeSegments );
}
2002-06-30 23:19:33 +00:00
void Song :: GetBeatAndBPSFromElapsedTime ( float fElapsedTime , float & fBeatOut , float & fBPSOut , bool & bFreezeOut )
2001-12-19 01:50:57 +00:00
{
2002-05-01 19:14:55 +00:00
// LOG->WriteLine( "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 ( int i = 0 ; i < m_BPMSegments . GetSize (); i ++ ) // foreach BPMSegment
{
BPMSegment & this_seg = m_BPMSegments [ i ];
2001-12-19 01:50:57 +00:00
float fStartBeatThisSegment = m_BPMSegments [ i ]. m_fStartBeat ;
bool bIsLastBPMSegment = i == m_BPMSegments . GetSize () - 1 ;
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 ;
2001-12-28 10:15:59 +00:00
for ( int j = 0 ; j < m_FreezeSegments . GetSize (); j ++ ) // foreach freeze
{
if ( fStartBeatThisSegment <= m_FreezeSegments [ j ]. m_fStartBeat && m_FreezeSegments [ j ]. m_fStartBeat < fStartBeatNextSegment )
{
// this freeze lies within this BPMSegment
fSecondsInThisSegment += m_FreezeSegments [ j ]. m_fFreezeSeconds ;
}
}
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 ;
2001-12-28 10:15:59 +00:00
}
else
{
// this BPMSegment IS the current segment
float fBeatEstimate = fStartBeatThisSegment + fElapsedTime * fBPS ;
for ( int j = 0 ; j < m_FreezeSegments . GetSize (); j ++ ) // foreach freeze
{
if ( fStartBeatThisSegment <= m_FreezeSegments [ j ]. m_fStartBeat && m_FreezeSegments [ j ]. m_fStartBeat <= fStartBeatNextSegment )
{
// this freeze lies within this BPMSegment
if ( m_FreezeSegments [ j ]. m_fStartBeat <= fBeatEstimate )
{
fElapsedTime -= m_FreezeSegments [ j ]. m_fFreezeSeconds ;
// re-estimate
fBeatEstimate = fStartBeatThisSegment + fElapsedTime * fBPS ;
if ( fBeatEstimate < m_FreezeSegments [ j ]. m_fStartBeat )
{
fBeatOut = m_FreezeSegments [ j ]. m_fStartBeat ;
fBPSOut = fBPS ;
2002-06-30 23:19:33 +00:00
bFreezeOut = true ;
2001-12-28 10:15:59 +00:00
return ;
}
}
else
break ;
}
}
fBeatOut = fBeatEstimate ;
2001-12-19 01:50:57 +00:00
fBPSOut = fBPS ;
2002-06-30 23:19:33 +00:00
bFreezeOut = false ;
2001-12-19 01:50:57 +00:00
return ;
}
2001-12-28 10:15:59 +00:00
2001-12-19 01:50:57 +00:00
}
}
2002-05-19 01:59:48 +00:00
// This is a super hack, but it's only called from ScreenEdit, so it's OK :-)
2002-04-16 17:31:00 +00:00
// Writing an inverse function of GetBeatAndBPSFromElapsedTime() is impossible,
// so do a binary search to get close to the correct elapsed time.
float Song :: GetElapsedTimeFromBeat ( float fBeat )
{
float fElapsedTimeBestGuess = 100 ; // seconds
float fSecondsToMove = fElapsedTimeBestGuess / 2 ; // seconds
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-05-01 19:14:55 +00:00
void Song :: GetMainAndSubTitlesFromFullTitle ( CString sFullTitle , CString & sMainTitleOut , CString & sSubTitleOut )
{
2002-06-24 22:04:31 +00:00
char sLeftSeps [] = { '-' , '~' , '(' , '[' };
char sRightSeps [] = { '-' , '~' , ')' , ']' };
ASSERT ( sizeof ( sLeftSeps ) == sizeof ( sRightSeps ) );
for ( int i = 0 ; i < sizeof ( sLeftSeps ); i ++ )
2002-05-01 19:14:55 +00:00
{
2002-06-24 22:04:31 +00:00
int iBeginIndex = sFullTitle . Find ( sLeftSeps [ i ] );
2002-05-01 19:14:55 +00:00
if ( iBeginIndex == - 1 )
continue ;
2002-06-24 22:04:31 +00:00
int iEndIndex = sFullTitle . Find ( sRightSeps [ i ], iBeginIndex + 1 );
2002-05-01 19:14:55 +00:00
if ( iEndIndex == - 1 )
continue ;
sMainTitleOut = sFullTitle . Left ( iBeginIndex - 1 );
sSubTitleOut = sFullTitle . Mid ( iBeginIndex , iEndIndex - iBeginIndex + 1 );
return ;
}
sMainTitleOut = sFullTitle ;
sSubTitleOut = "" ;
};
2002-04-16 17:31:00 +00:00
2002-05-19 01:59:48 +00:00
CString Song :: GetCacheFilePath ()
{
2002-06-30 23:19:33 +00:00
return ssprintf ( "Cache \\ %d" , GetHashForString ( m_sSongDir ) );
2002-05-19 01:59:48 +00:00
}
2002-07-02 00:27:58 +00:00
CString Song :: GetSongFilePath ()
{
CStringArray asFileNames ;
GetDirListing ( m_sSongDir + "*.sm" , asFileNames );
if ( asFileNames . GetSize () > 0 )
return m_sSongDir + asFileNames [ 0 ];
else
return m_sSongDir + GetFullTitle () + ".sm" ;
}
2001-11-03 10:52:42 +00:00
bool Song :: LoadFromSongDir ( CString sDir )
{
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "Song::LoadFromSongDir(%s)" , sDir );
2001-11-03 10:52:42 +00:00
// make sure there is a trailing '\\' at the end of sDir
if ( sDir . Right ( 1 ) != " \\ " )
sDir += " \\ " ;
// save song dir
m_sSongDir = sDir ;
2002-07-02 00:27:58 +00:00
// save group name
CStringArray sDirectoryParts ;
split ( m_sSongDir , " \\ " , sDirectoryParts , true );
m_sGroupName = sDirectoryParts [ 1 ];
2001-11-03 10:52:42 +00:00
2002-05-19 01:59:48 +00:00
//
// First look in the cache for this song (without loading NoteData)
//
2002-06-30 23:19:33 +00:00
IniFile ini ;
ini . SetPath ( "Cache \\ index.cache" );
if ( ! ini . ReadFile () )
goto load_without_cache ;
2002-05-19 01:59:48 +00:00
2002-06-30 23:19:33 +00:00
int iCacheVersion ;
ini . GetValueI ( "Cache" , "CacheVersion" , iCacheVersion );
if ( iCacheVersion != FILE_CACHE_VERSION )
{
2002-07-03 03:13:13 +00:00
LOG -> WriteLine ( "Cache format is out of date. Deleting all cache files." );
2002-06-30 23:19:33 +00:00
CStringArray asCacheFileNames ;
GetDirListing ( "Cache \\ *.*" , asCacheFileNames );
for ( int i = 0 ; i < asCacheFileNames . GetSize (); i ++ )
DeleteFile ( "Cache \\ " + asCacheFileNames [ i ] );
goto load_without_cache ;
}
2002-05-19 01:59:48 +00:00
2002-06-30 23:19:33 +00:00
int iDirHash ;
ini . GetValueI ( "Cache" , m_sSongDir , iDirHash );
if ( GetHashForDirectory ( m_sSongDir ) == iDirHash ) // this cache is up to date
{
if ( ! DoesFileExist ( GetCacheFilePath ()) )
goto load_without_cache ;
2002-07-03 03:13:13 +00:00
LoadFromSMFile ( GetCacheFilePath () ); // don't load NoteData
2002-07-02 00:27:58 +00:00
2002-06-30 23:19:33 +00:00
LOG -> WriteLine ( "Loading '%s' from cache file '%s'." , m_sSongDir , GetCacheFilePath () );
return true ;
}
load_without_cache :
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.
//
2001-11-03 10:52:42 +00:00
CStringArray arrayBMSFileNames ;
GetDirListing ( sDir + CString ( "*.bms" ), arrayBMSFileNames );
int iNumBMSFiles = arrayBMSFileNames . GetSize ();
2001-12-19 01:50:57 +00:00
CStringArray arrayDWIFileNames ;
GetDirListing ( sDir + CString ( "*.dwi" ), arrayDWIFileNames );
int iNumDWIFiles = arrayDWIFileNames . GetSize ();
2001-11-03 10:52:42 +00:00
2002-06-30 23:19:33 +00:00
CStringArray arraySMFileNames ;
GetDirListing ( sDir + CString ( "*.sm" ), arraySMFileNames );
int iNumSMFiles = arraySMFileNames . GetSize ();
2001-11-03 10:52:42 +00:00
2002-04-16 17:31:00 +00:00
2002-06-30 23:19:33 +00:00
if ( iNumSMFiles > 1 )
throw RageException ( "There is more than one SM file in '%s'. There should be only one!" , sDir );
2002-04-16 17:31:00 +00:00
else if ( iNumDWIFiles > 1 )
2002-06-14 22:25:22 +00:00
throw RageException ( "There is more than one DWI file in '%s'. There should be only one!" , sDir );
2002-06-30 23:19:33 +00:00
else if ( iNumSMFiles == 1 )
2002-07-03 03:13:13 +00:00
LoadFromSMFile ( sDir + arraySMFileNames [ 0 ] ); // do load note data
2001-12-19 01:50:57 +00:00
else if ( iNumDWIFiles == 1 )
2002-04-16 17:31:00 +00:00
LoadFromDWIFile ( sDir + arrayDWIFileNames [ 0 ] );
2001-12-28 10:15:59 +00:00
else if ( iNumBMSFiles > 0 )
2002-04-16 17:31:00 +00:00
LoadFromBMSDir ( sDir );
2001-11-03 10:52:42 +00:00
else
2002-06-30 23:19:33 +00:00
throw RageException ( "Couldn't find any SM, BMS, or DWI files in '%s'. This is not a valid song directory." , sDir );
2001-11-03 10:52:42 +00:00
2002-05-19 01:59:48 +00:00
TidyUpData ();
2002-07-03 03:13:13 +00:00
SaveToCacheFile (); // save a cahce file so we don't have to parse it all over again next time
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
2002-04-16 17:31:00 +00:00
bool Song :: LoadFromBMSDir ( CString sDir )
2001-11-03 10:52:42 +00:00
{
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "Song::LoadFromBMSDir(%s)" , sDir );
2001-11-03 10:52:42 +00:00
2001-12-28 10:15:59 +00:00
CStringArray arrayBMSFileNames ;
GetDirListing ( sDir + CString ( "*.bms" ), arrayBMSFileNames );
if ( arrayBMSFileNames . GetSize () == 0 )
2002-06-27 17:49:10 +00:00
throw RageException ( "Couldn't find any BMS files in '%s'" , sDir );
2001-12-28 10:15:59 +00:00
2002-05-19 01:59:48 +00:00
// load the Notes from the rest of the BMS files
2001-12-28 10:15:59 +00:00
for ( int i = 0 ; i < arrayBMSFileNames . GetSize (); i ++ )
{
2002-06-14 22:25:22 +00:00
Notes * pNewNotes = new Notes ;
pNewNotes -> LoadFromBMSFile ( m_sSongDir + arrayBMSFileNames [ i ] );
m_arrayNotes . Add ( pNewNotes );
2001-12-28 10:15:59 +00:00
}
2002-07-02 00:27:58 +00:00
CString sPath = m_sSongDir + arrayBMSFileNames [ 0 ];
2001-12-28 10:15:59 +00:00
2002-01-24 08:01:24 +00:00
CStdioFile file ;
2002-07-02 00:27:58 +00:00
if ( ! file . Open ( sPath , CFile :: modeRead | CFile :: shareDenyNone ) )
2001-11-03 10:52:42 +00:00
{
2002-07-02 00:27:58 +00:00
throw RageException ( "Failed to open %s." , sPath );
2002-01-24 08:01:24 +00:00
return false ;
}
CString line ;
while ( file . ReadString ( line ) ) // foreach line
{
CString value_name ; // fill these in
CString value_data ;
// BMS value names can be separated by a space or a colon.
int iIndexOfFirstColon = line . Find ( ":" );
int iIndexOfFirstSpace = line . Find ( " " );
if ( iIndexOfFirstColon == - 1 )
iIndexOfFirstColon = 10000 ;
if ( iIndexOfFirstSpace == - 1 )
iIndexOfFirstSpace = 10000 ;
int iIndexOfSeparator = min ( iIndexOfFirstSpace , iIndexOfFirstColon );
if ( iIndexOfSeparator != 10000 )
{
value_name = line . Mid ( 0 , iIndexOfSeparator );
value_data = line ; // the rest
value_data . Delete ( 0 , iIndexOfSeparator + 1 );
}
else // no separator
{
value_name = line ;
}
value_name . MakeLower ();
2001-11-03 10:52:42 +00:00
// handle the data
2002-06-27 17:49:10 +00:00
if ( value_name == "#title" )
2002-01-24 08:01:24 +00:00
{
2002-06-27 17:49:10 +00:00
// strip Notes type out of description leaving only song title - looks like 'B4U <BASIC>'
int iIndex = value_data . ReverseFind ( '<' );
if ( iIndex == - 1 )
iIndex = value_data . ReverseFind ( '(' );
if ( iIndex != - 1 )
{
2002-06-27 19:14:23 +00:00
value_data = value_data . Left ( iIndex );
2002-06-27 17:49:10 +00:00
GetMainAndSubTitlesFromFullTitle ( value_data , m_sMainTitle , m_sSubTitle );
}
else
m_sMainTitle = value_data ;
2002-01-24 08:01:24 +00:00
}
2002-06-27 17:49:10 +00:00
else if ( value_name == "#artist" )
2002-01-24 08:01:24 +00:00
{
m_sArtist = value_data ;
}
2002-06-27 17:49:10 +00:00
else if ( value_name == "#bpm" )
2002-01-24 08:01:24 +00:00
{
2002-06-27 17:49:10 +00:00
BPMSegment newSeg ( 0 , ( float ) atof ( value_data ) );
AddBPMSegment ( newSeg );
LOG -> WriteLine ( "Inserting new BPM change at beat %f, BPM %f" , newSeg . m_fStartBeat , newSeg . m_fBPM );
2001-11-27 22:47:30 +00:00
}
2002-06-27 17:49:10 +00:00
else if ( value_name == "#backbmp" )
2002-01-24 08:01:24 +00:00
{
2002-06-29 11:59:09 +00:00
m_sBackgroundFile = value_data ;
2002-01-24 08:01:24 +00:00
}
2002-06-27 17:49:10 +00:00
else if ( value_name == "#wav" )
2002-01-24 08:01:24 +00:00
{
2002-06-29 11:59:09 +00:00
m_sMusicFile = value_data ;
2002-01-24 08:01:24 +00:00
}
else if ( value_name . Left ( 1 ) == "#"
&& IsAnInt ( value_name . Mid ( 1 , 3 ) )
&& IsAnInt ( value_name . Mid ( 4 , 2 ) ) ) // this is step or offset data. Looks like "#00705"
2001-11-03 10:52:42 +00:00
{
2001-11-04 19:34:28 +00:00
int iMeasureNo = atoi ( value_name . Mid ( 1 , 3 ) );
2002-06-27 17:49:10 +00:00
int iBMSTrackNo = atoi ( value_name . Mid ( 4 , 2 ) );
2001-11-27 22:47:30 +00:00
2002-01-24 08:01:24 +00:00
CString sNoteData = value_data ;
2001-12-19 01:50:57 +00:00
CArray < int , int > arrayNotes ;
2001-11-03 10:52:42 +00:00
for ( int i = 0 ; i < sNoteData . GetLength (); i += 2 )
{
2001-12-19 01:50:57 +00:00
CString sNote = sNoteData . Mid ( i , 2 );
int iNote ;
sscanf ( sNote , "%x" , & iNote ); // data is in hexadecimal
arrayNotes . Add ( iNote );
2001-11-03 10:52:42 +00:00
}
const int iNumNotesInThisMeasure = arrayNotes . GetSize ();
2002-06-27 17:49:10 +00:00
//LOG->WriteLine( "%s:%s: iMeasureNo = %d, iBMSTrackNo = %d, iNumNotesInThisMeasure = %d",
// valuename, sNoteData, iMeasureNo, iBMSTrackNo, iNumNotesInThisMeasure );
2001-11-03 10:52:42 +00:00
for ( int j = 0 ; j < iNumNotesInThisMeasure ; j ++ )
{
2001-12-19 01:50:57 +00:00
if ( arrayNotes [ j ] != 0 )
2001-11-03 10:52:42 +00:00
{
2001-11-27 22:47:30 +00:00
float fPercentThroughMeasure = ( float ) j / ( float ) iNumNotesInThisMeasure ;
2001-11-03 10:52:42 +00:00
// index is in quarter beats starting at beat 0
int iStepIndex = ( int ) ( ( iMeasureNo + fPercentThroughMeasure )
2001-11-04 19:34:28 +00:00
* BEATS_PER_MEASURE * ELEMENTS_PER_BEAT );
2001-11-03 10:52:42 +00:00
2002-06-27 17:49:10 +00:00
switch ( iBMSTrackNo )
2001-11-04 19:34:28 +00:00
{
2002-06-27 17:49:10 +00:00
case 1 : // background music track
2002-07-03 21:27:26 +00:00
{
float fBeatOffset = fBeatOffset = NoteRowToBeat ( ( float ) iStepIndex );
if ( fBeatOffset > 10 ) // some BPMs's play the music again at the end. Why? Who knows...
break ;
float fBPS ;
fBPS = m_BPMSegments [ 0 ]. m_fBPM / 60.0f ;
m_fBeat0OffsetInSeconds = fBeatOffset / fBPS ;
//LOG->WriteLine( "Found offset to be index %d, beat %f", iStepIndex, NoteRowToBeat(iStepIndex) );
}
2001-11-27 22:47:30 +00:00
break ;
2002-06-27 17:49:10 +00:00
case 3 : // bpm change
{
BPMSegment newSeg ( NoteRowToBeat ( iStepIndex ), ( float ) arrayNotes [ j ] );
AddBPMSegment ( newSeg );
LOG -> WriteLine ( "Inserting new BPM change at beat %f, BPM %f" , newSeg . m_fStartBeat , newSeg . m_fBPM );
}
break ;
2001-12-28 10:15:59 +00:00
2002-06-27 17:49:10 +00:00
// Let me just take a moment to express how frustrated I am with the new,
// poorly-designed changes to the BMS format.
//
//
// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhhhhhhhhh!!!!!!!!!!!!!!!
//
// Thank you.
case 8 : // indirect bpm
{
// This is a very inefficient way to parse, but it doesn't matter much
// because this is only parsed on the first run after the song is installed.
CString sTagToLookFor = ssprintf ( "#BPM%02x" , arrayNotes [ j ] );
float fBPM = - 1 ;
// open the song file again and and look for this tag's value
CStdioFile file ;
2002-07-02 00:27:58 +00:00
if ( ! file . Open ( sPath , CFile :: modeRead | CFile :: shareDenyNone ) )
2002-06-27 17:49:10 +00:00
{
2002-07-02 00:27:58 +00:00
throw RageException ( "Failed to open %s." , sPath );
2002-06-27 17:49:10 +00:00
return false ;
}
CString line ;
while ( file . ReadString ( line ) ) // foreach line
{
CString value_name ; // fill these in
CString value_data ;
// BMS value names can be separated by a space or a colon.
int iIndexOfFirstColon = line . Find ( ":" );
int iIndexOfFirstSpace = line . Find ( " " );
if ( iIndexOfFirstColon == - 1 )
iIndexOfFirstColon = 10000 ;
if ( iIndexOfFirstSpace == - 1 )
iIndexOfFirstSpace = 10000 ;
int iIndexOfSeparator = min ( iIndexOfFirstSpace , iIndexOfFirstColon );
if ( iIndexOfSeparator != 10000 )
{
value_name = line . Mid ( 0 , iIndexOfSeparator );
value_data = line ; // the rest
value_data . Delete ( 0 , iIndexOfSeparator + 1 );
}
else // no separator
{
value_name = line ;
}
if ( stricmp ( value_name , sTagToLookFor ) == 0 )
{
fBPM = ( float ) atof ( value_data );
break ;
}
}
if ( fBPM == - 1 ) // we didn't find the line we were looking for
{
2002-07-02 00:27:58 +00:00
LOG -> WriteLine ( "WARNING: Couldn't find tag '%s' in '%s'." , sTagToLookFor , sPath );
2002-06-27 17:49:10 +00:00
}
else
{
BPMSegment newSeg ( NoteRowToBeat ( iStepIndex ), fBPM );
AddBPMSegment ( newSeg );
LOG -> WriteLine ( "Inserting new BPM change at beat %f, BPM %f" , newSeg . m_fStartBeat , newSeg . m_fBPM );
}
2002-07-02 00:27:58 +00:00
file . Close ();
2002-06-27 17:49:10 +00:00
}
break ;
case 9 : // freeze
{
// This is a very inefficient way to parse, but it doesn't
// matter much because this is only parsed on the first run after the song is installed.
CString sTagToLookFor = ssprintf ( "#STOP%02x" , arrayNotes [ j ] );
float fFreezeStartBeat = NoteRowToBeat ( iStepIndex );
float fFreezeSecs = - 1 ;
// open the song file again and and look for this tag's value
CStdioFile file ;
2002-07-02 00:27:58 +00:00
if ( ! file . Open ( sPath , CFile :: modeRead | CFile :: shareDenyNone ) )
throw RageException ( "Failed to open %s." , sPath );
2002-06-27 17:49:10 +00:00
CString line ;
while ( file . ReadString ( line ) ) // foreach line
{
CString value_name ; // fill these in
CString value_data ;
// BMS value names can be separated by a space or a colon.
int iIndexOfFirstColon = line . Find ( ":" );
int iIndexOfFirstSpace = line . Find ( " " );
if ( iIndexOfFirstColon == - 1 )
iIndexOfFirstColon = 10000 ;
if ( iIndexOfFirstSpace == - 1 )
iIndexOfFirstSpace = 10000 ;
int iIndexOfSeparator = min ( iIndexOfFirstSpace , iIndexOfFirstColon );
if ( iIndexOfSeparator != 10000 )
{
value_name = line . Mid ( 0 , iIndexOfSeparator );
value_data = line ; // the rest
value_data . Delete ( 0 , iIndexOfSeparator + 1 );
}
else // no separator
{
value_name = line ;
}
if ( stricmp ( value_name , sTagToLookFor ) == 0 )
{
// find the BPM at the time of this freeze
float fBPM = - 1 ;
for ( int i = 0 ; i < m_BPMSegments . GetSize () - 1 ; i ++ )
{
if ( m_BPMSegments [ i ]. m_fStartBeat <= fFreezeStartBeat &&
m_BPMSegments [ i + 1 ]. m_fStartBeat > fFreezeStartBeat )
{
fBPM = m_BPMSegments [ i ]. m_fBPM ;
break ;
}
}
// the BPM segment of this beat is the last BPM segment
if ( fBPM == - 1 )
fBPM = m_BPMSegments [ m_BPMSegments . GetSize () - 1 ]. m_fBPM ;
fFreezeSecs = ( float ) atof ( value_data ) / ( fBPM * 0.81f ); // I have no idea what units these are in, so I experimented until finding this factor.
break ;
}
}
if ( fFreezeSecs == - 1 ) // we didn't find the line we were looking for
{
2002-07-02 00:27:58 +00:00
LOG -> WriteLine ( "WARNING: Couldn't find tag '%s' in '%s'." , sTagToLookFor , sPath );
2002-06-27 17:49:10 +00:00
}
else
{
FreezeSegment newSeg ( fFreezeStartBeat , fFreezeSecs );
AddFreezeSegment ( newSeg );
LOG -> WriteLine ( "Inserting new Freeze at beat %f, secs %f" , newSeg . m_fStartBeat , newSeg . m_fFreezeSeconds );
}
2002-07-02 00:27:58 +00:00
file . Close ();
2002-06-27 17:49:10 +00:00
}
2001-11-04 19:34:28 +00:00
break ;
}
2001-11-03 10:52:42 +00:00
}
}
}
}
2001-12-28 10:15:59 +00:00
for ( i = 0 ; i < m_BPMSegments . GetSize (); i ++ )
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "There is a BPM change at beat %f, BPM %f, index %d" ,
2001-12-19 01:50:57 +00:00
m_BPMSegments [ i ]. m_fStartBeat , m_BPMSegments [ i ]. m_fBPM , i );
2002-07-02 00:27:58 +00:00
file . Close ();
2001-12-19 01:50:57 +00:00
2001-11-03 10:52:42 +00:00
return TRUE ;
}
2002-04-16 17:31:00 +00:00
bool Song :: LoadFromDWIFile ( CString sPath )
2001-11-03 10:52:42 +00:00
{
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "Song::LoadFromDWIFile(%s)" , sPath );
2001-12-28 10:15:59 +00:00
2001-11-03 10:52:42 +00:00
CStdioFile file ;
2002-07-02 00:27:58 +00:00
if ( ! file . Open ( sPath , CFile :: modeRead | CFile :: shareDenyNone ) )
throw RageException ( "Error opening DWI file '%s'." , sPath );
2001-12-28 10:15:59 +00:00
2001-11-03 10:52:42 +00:00
// read the whole file into a sFileText
CString sFileText ;
CString buffer ;
while ( file . ReadString ( buffer ) )
2002-07-02 00:27:58 +00:00
sFileText += buffer + " \n " ;
2001-11-03 10:52:42 +00:00
file . Close ();
2002-06-24 22:04:31 +00:00
// strip comments out of sFileText
while ( sFileText . Find ( "//" ) != - 1 )
{
int iIndexCommentStart = sFileText . Find ( "//" );
int iIndexCommentEnd = sFileText . Find ( " \n " , iIndexCommentStart );
if ( iIndexCommentEnd == - 1 ) // comment doesn't have an end?
sFileText . Delete ( iIndexCommentStart , 2 );
else
sFileText . Delete ( iIndexCommentStart , iIndexCommentEnd - iIndexCommentStart );
}
2001-11-03 10:52:42 +00:00
// split sFileText into strings containing each value expression
CStringArray arrayValueStrings ;
split ( sFileText , ";" , arrayValueStrings );
// for each value expression string, parse it into a value name and data
for ( int i = 0 ; i < arrayValueStrings . GetSize (); i ++ )
{
CString sValueString = arrayValueStrings [ i ];
// split the value string into tokens
CStringArray arrayValueTokens ;
split ( sValueString , ":" , arrayValueTokens );
2002-07-02 00:27:58 +00:00
for ( int j = 0 ; j < arrayValueTokens . GetSize (); j ++ )
{
arrayValueTokens [ j ]. TrimLeft ();
arrayValueTokens [ j ]. TrimRight ();
}
2002-02-12 06:55:04 +00:00
if ( arrayValueTokens . GetSize () == 0 )
continue ;
2001-11-03 10:52:42 +00:00
CString sValueName = arrayValueTokens . GetAt ( 0 );
2001-12-19 01:50:57 +00:00
2001-11-03 10:52:42 +00:00
// handle the data
2001-12-19 01:50:57 +00:00
if ( sValueName == "#FILE" )
2002-06-29 11:59:09 +00:00
m_sMusicFile = arrayValueTokens [ 1 ];
2001-12-19 01:50:57 +00:00
else if ( sValueName == "#TITLE" )
2002-04-28 20:42:32 +00:00
GetMainAndSubTitlesFromFullTitle ( arrayValueTokens [ 1 ], m_sMainTitle , m_sSubTitle );
2002-05-28 20:01:22 +00:00
2001-11-03 10:52:42 +00:00
else if ( sValueName == "#ARTIST" )
m_sArtist = arrayValueTokens [ 1 ];
else if ( sValueName == "#BPM" )
2001-12-19 01:50:57 +00:00
{
BPMSegment new_seg ;
new_seg . m_fStartBeat = 0 ;
2001-12-28 10:15:59 +00:00
new_seg . m_fBPM = ( float ) atof ( arrayValueTokens [ 1 ] );
m_BPMSegments . Add ( new_seg );
SortBPMSegmentsArray ( m_BPMSegments );
2001-12-19 01:50:57 +00:00
}
2001-11-03 10:52:42 +00:00
else if ( sValueName == "#GAP" )
2001-12-19 01:50:57 +00:00
// the units of GAP is 1/1000 second
2002-07-03 21:27:26 +00:00
m_fBeat0OffsetInSeconds = - atoi ( arrayValueTokens [ 1 ] ) / 1000.0f ;
2001-11-03 10:52:42 +00:00
2002-04-28 20:42:32 +00:00
else if ( sValueName == "#SAMPLESTART" )
m_fMusicSampleStartSeconds = TimeToSeconds ( arrayValueTokens [ 1 ] );
else if ( sValueName == "#SAMPLELENGTH" )
m_fMusicSampleLengthSeconds = TimeToSeconds ( arrayValueTokens [ 1 ] );
2001-12-19 01:50:57 +00:00
else if ( sValueName == "#FREEZE" )
{
CStringArray arrayFreezeExpressions ;
split ( arrayValueTokens [ 1 ], "," , arrayFreezeExpressions );
2001-11-03 10:52:42 +00:00
2001-12-19 01:50:57 +00:00
for ( int f = 0 ; f < arrayFreezeExpressions . GetSize (); f ++ )
{
CStringArray arrayFreezeValues ;
split ( arrayFreezeExpressions [ f ], "=" , arrayFreezeValues );
2002-02-28 19:40:40 +00:00
float fIndex = atoi ( arrayFreezeValues [ 0 ] ) * ELEMENTS_PER_BEAT / 4.0f ;
2002-05-19 01:59:48 +00:00
float fFreezeBeat = NoteRowToBeat ( fIndex );
2002-01-16 19:46:22 +00:00
float fFreezeSeconds = ( float ) atof ( arrayFreezeValues [ 1 ] ) / 1000.0f ;
2001-12-19 01:50:57 +00:00
2002-06-24 22:04:31 +00:00
AddFreezeSegment ( FreezeSegment ( fFreezeBeat , fFreezeSeconds ) );
LOG -> WriteLine ( "Adding a freeze segment: beat: %f, seconds = %f" , fFreezeBeat , fFreezeSeconds );
2001-12-19 01:50:57 +00:00
}
}
else if ( sValueName == "#CHANGEBPM" || sValueName == "#BPMCHANGE" )
{
CStringArray arrayBPMChangeExpressions ;
split ( arrayValueTokens [ 1 ], "," , arrayBPMChangeExpressions );
for ( int b = 0 ; b < arrayBPMChangeExpressions . GetSize (); b ++ )
{
CStringArray arrayBPMChangeValues ;
split ( arrayBPMChangeExpressions [ b ], "=" , arrayBPMChangeValues );
2002-02-28 19:40:40 +00:00
float fIndex = atoi ( arrayBPMChangeValues [ 0 ] ) * ELEMENTS_PER_BEAT / 4.0f ;
2002-05-19 01:59:48 +00:00
float fBeat = NoteRowToBeat ( fIndex );
2002-01-16 19:46:22 +00:00
float fNewBPM = ( float ) atoi ( arrayBPMChangeValues [ 1 ] );
2001-12-19 01:50:57 +00:00
2002-06-24 22:04:31 +00:00
AddBPMSegment ( BPMSegment ( fBeat , fNewBPM ) );
2001-12-19 01:50:57 +00:00
}
}
2001-11-03 10:52:42 +00:00
else if ( sValueName == "#SINGLE" || sValueName == "#DOUBLE" || sValueName == "#COUPLE" )
{
2002-06-14 22:25:22 +00:00
Notes * pNewNotes = new Notes ;
pNewNotes -> LoadFromDWITokens (
2002-03-30 20:00:13 +00:00
arrayValueTokens [ 0 ],
arrayValueTokens [ 1 ],
2002-06-24 22:04:31 +00:00
atoi ( arrayValueTokens [ 2 ]),
2002-03-30 20:00:13 +00:00
arrayValueTokens [ 3 ],
arrayValueTokens . GetSize () == 5 ? arrayValueTokens [ 4 ] : ""
);
2002-06-14 22:25:22 +00:00
m_arrayNotes . Add ( pNewNotes );
2001-11-03 10:52:42 +00:00
}
else
// do nothing. We don't care about this value name
;
}
return TRUE ;
}
2002-07-03 03:13:13 +00:00
bool Song :: LoadFromSMFile ( CString sPath )
2002-04-16 17:31:00 +00:00
{
2002-07-03 03:13:13 +00:00
LOG -> WriteLine ( "Song::LoadFromSMDir(%s)" , sPath );
2002-04-16 17:31:00 +00:00
2002-07-02 00:27:58 +00:00
m_BPMSegments . RemoveAll ();
m_FreezeSegments . RemoveAll ();
2002-04-16 17:31:00 +00:00
int i ;
// get group name
CStringArray sDirectoryParts ;
split ( m_sSongDir , " \\ " , sDirectoryParts , true );
m_sGroupName = sDirectoryParts [ 1 ];
CStdioFile file ;
2002-07-02 00:27:58 +00:00
if ( ! file . Open ( sPath , CFile :: modeRead | CFile :: shareDenyNone ) )
throw RageException ( "Error opening SM file '%s'." , sPath );
2002-04-16 17:31:00 +00:00
// read the whole file into a sFileText
CString sFileText ;
CString buffer ;
while ( file . ReadString ( buffer ) )
2002-07-02 00:27:58 +00:00
sFileText += buffer + " \n " ;
2002-04-16 17:31:00 +00:00
file . Close ();
2002-06-24 22:04:31 +00:00
// strip comments out of sFileText
while ( sFileText . Find ( "//" ) != - 1 )
{
int iIndexCommentStart = sFileText . Find ( "//" );
int iIndexCommentEnd = sFileText . Find ( " \n " , iIndexCommentStart );
if ( iIndexCommentEnd == - 1 ) // comment doesn't have an end?
sFileText . Delete ( iIndexCommentStart , 2 );
else
sFileText . Delete ( iIndexCommentStart , iIndexCommentEnd - iIndexCommentStart );
}
2002-04-16 17:31:00 +00:00
// split sFileText into strings containing each value expression
CStringArray arrayValueStrings ;
split ( sFileText , ";" , arrayValueStrings , false );
// for each value expression string, parse it into a value name and data
for ( i = 0 ; i < arrayValueStrings . GetSize (); i ++ )
{
CString sValueString = arrayValueStrings [ i ];
// split the value string into tokens
CStringArray arrayValueTokens ;
split ( sValueString , ":" , arrayValueTokens , false );
2002-07-02 00:27:58 +00:00
for ( int j = 0 ; j < arrayValueTokens . GetSize (); j ++ )
{
arrayValueTokens [ j ]. TrimLeft ();
arrayValueTokens [ j ]. TrimRight ();
}
2002-04-16 17:31:00 +00:00
if ( arrayValueTokens . GetSize () == 0 )
continue ;
CString sValueName = arrayValueTokens . GetAt ( 0 );
// handle the data
if ( sValueName == "#TITLE" )
2002-04-28 20:42:32 +00:00
GetMainAndSubTitlesFromFullTitle ( arrayValueTokens [ 1 ], m_sMainTitle , m_sSubTitle );
2002-07-02 00:27:58 +00:00
else if ( sValueName == "#SUBTITLE" )
2002-04-28 20:42:32 +00:00
m_sSubTitle = arrayValueTokens [ 1 ];
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#ARTIST" )
m_sArtist = arrayValueTokens [ 1 ];
else if ( sValueName == "#CREDIT" )
m_sCredit = arrayValueTokens [ 1 ];
else if ( sValueName == "#BANNER" )
2002-06-29 11:59:09 +00:00
m_sBannerFile = arrayValueTokens [ 1 ];
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#BACKGROUND" )
2002-06-29 11:59:09 +00:00
m_sBackgroundFile = arrayValueTokens [ 1 ];
2002-04-16 17:31:00 +00:00
2002-07-03 21:27:26 +00:00
// else if( sValueName == "#BACKGROUNDMOVIE" )
// m_sBackgroundMovieFile = arrayValueTokens[1];
2002-06-29 11:59:09 +00:00
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#CDTITLE" )
2002-06-29 11:59:09 +00:00
m_sCDTitleFile = arrayValueTokens [ 1 ];
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#MUSIC" )
2002-06-29 11:59:09 +00:00
m_sMusicFile = arrayValueTokens [ 1 ];
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#MUSICBYTES" )
2002-05-19 01:59:48 +00:00
m_iMusicBytes = atoi ( arrayValueTokens [ 1 ] );
2002-04-16 17:31:00 +00:00
2002-07-03 21:27:26 +00:00
else if ( sValueName == "#MUSICLENGTH" )
m_fMusicLengthSeconds = ( float ) atof ( arrayValueTokens [ 1 ] );
2002-04-28 20:42:32 +00:00
else if ( sValueName == "#SAMPLESTART" )
m_fMusicSampleStartSeconds = TimeToSeconds ( arrayValueTokens [ 1 ] );
else if ( sValueName == "#SAMPLELENGTH" )
m_fMusicSampleLengthSeconds = TimeToSeconds ( arrayValueTokens [ 1 ] );
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#OFFSET" )
2002-07-03 21:27:26 +00:00
m_fBeat0OffsetInSeconds = ( float ) atof ( arrayValueTokens [ 1 ] );
2002-04-16 17:31:00 +00:00
else if ( sValueName == "#FREEZES" )
{
CStringArray arrayFreezeExpressions ;
split ( arrayValueTokens [ 1 ], "," , arrayFreezeExpressions );
for ( int f = 0 ; f < arrayFreezeExpressions . GetSize (); f ++ )
{
CStringArray arrayFreezeValues ;
split ( arrayFreezeExpressions [ f ], "=" , arrayFreezeValues );
float fFreezeBeat = ( float ) atof ( arrayFreezeValues [ 0 ] );
float fFreezeSeconds = ( float ) atof ( arrayFreezeValues [ 1 ] );
FreezeSegment new_seg ;
new_seg . m_fStartBeat = fFreezeBeat ;
new_seg . m_fFreezeSeconds = fFreezeSeconds ;
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "Adding a freeze segment: beat: %f, seconds = %f" , new_seg . m_fStartBeat , new_seg . m_fFreezeSeconds );
2002-04-16 17:31:00 +00:00
2002-07-02 00:27:58 +00:00
AddFreezeSegment ( new_seg );
2002-04-16 17:31:00 +00:00
}
}
else if ( sValueName == "#BPMS" )
{
CStringArray arrayBPMChangeExpressions ;
split ( arrayValueTokens [ 1 ], "," , arrayBPMChangeExpressions );
for ( int b = 0 ; b < arrayBPMChangeExpressions . GetSize (); b ++ )
{
CStringArray arrayBPMChangeValues ;
split ( arrayBPMChangeExpressions [ b ], "=" , arrayBPMChangeValues );
float fBeat = ( float ) atof ( arrayBPMChangeValues [ 0 ] );
float fNewBPM = ( float ) atoi ( arrayBPMChangeValues [ 1 ] );
BPMSegment new_seg ;
new_seg . m_fStartBeat = fBeat ;
new_seg . m_fBPM = fNewBPM ;
2002-07-02 00:27:58 +00:00
AddBPMSegment ( new_seg );
2002-04-16 17:31:00 +00:00
}
}
2002-06-30 23:19:33 +00:00
else if ( sValueName == "#NOTES" )
{
2002-07-02 00:27:58 +00:00
Notes * pNewNotes = NULL ;
// Check to see if this notes already has been allocated
for ( int j = 0 ; j < m_arrayNotes . GetSize (); j ++ )
if ( m_arrayNotes [ j ] -> m_NotesType == StringToNotesType ( arrayValueTokens [ 1 ]) &&
m_arrayNotes [ j ] -> m_sDescription == arrayValueTokens [ 2 ] )
pNewNotes = m_arrayNotes [ j ];
if ( pNewNotes == NULL ) // we didn't find a match that was already loaded
{
pNewNotes = new Notes ;
m_arrayNotes . Add ( pNewNotes );
}
2002-07-03 21:27:26 +00:00
if ( arrayValueTokens . GetSize () != 7 )
throw RageException ( "The song file '%s' is has %d fields in a #NOTES tag, but should have %d." , sPath , arrayValueTokens . GetSize (), 7 );
2002-06-30 23:19:33 +00:00
pNewNotes -> LoadFromSMTokens (
arrayValueTokens [ 1 ],
arrayValueTokens [ 2 ],
arrayValueTokens [ 3 ],
arrayValueTokens [ 4 ],
arrayValueTokens [ 5 ],
2002-07-03 21:27:26 +00:00
arrayValueTokens [ 6 ]
2002-07-03 03:13:13 +00:00
);
2002-06-30 23:19:33 +00:00
}
2002-04-16 17:31:00 +00:00
else
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "Unexpected value named '%s'" , sValueName );
2002-04-16 17:31:00 +00:00
}
return TRUE ;
}
2001-11-25 04:31:44 +00:00
void Song :: TidyUpData ()
2001-11-03 10:52:42 +00:00
{
2002-04-28 20:42:32 +00:00
m_sMainTitle . TrimRight ();
if ( m_sMainTitle == "" ) m_sMainTitle = "Untitled song" ;
m_sSubTitle . TrimRight ();
if ( m_sArtist == "" ) m_sArtist = "Unknown artist" ;
2001-11-27 22:47:30 +00:00
if ( m_BPMSegments . GetSize () == 0 )
2002-06-14 22:25:22 +00:00
throw RageException ( "No #BPM specified in '%s.'" , GetSongFilePath () );
2001-11-03 10:52:42 +00:00
2002-06-29 11:59:09 +00:00
if ( m_sMusicFile == "" || ! DoesFileExist ( GetMusicPath ()) )
2001-11-03 10:52:42 +00:00
{
2001-11-25 04:31:44 +00:00
CStringArray arrayPossibleMusic ;
GetDirListing ( m_sSongDir + CString ( "*.mp3" ), arrayPossibleMusic );
2001-12-19 14:56:22 +00:00
GetDirListing ( m_sSongDir + CString ( "*.ogg" ), arrayPossibleMusic );
2001-11-25 04:31:44 +00:00
GetDirListing ( m_sSongDir + CString ( "*.wav" ), arrayPossibleMusic );
2001-11-03 10:52:42 +00:00
2001-11-25 04:31:44 +00:00
if ( arrayPossibleMusic . GetSize () != 0 ) // we found a match
2002-06-29 11:59:09 +00:00
m_sMusicFile = arrayPossibleMusic [ 0 ];
2001-11-25 04:31:44 +00:00
else
2002-06-29 11:59:09 +00:00
throw RageException ( "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 );
2001-11-03 10:52:42 +00:00
}
2001-12-28 10:15:59 +00:00
2002-05-19 01:59:48 +00:00
// Save length of music
if ( GetMusicPath () != "" )
{
RageSoundStream sound ;
sound . Load ( GetMusicPath () );
2002-06-29 11:59:09 +00:00
m_fMusicLengthSeconds = sound . GetLengthSeconds ();
2002-05-19 01:59:48 +00:00
}
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
// find the smallest image in the directory
2001-11-25 04:31:44 +00:00
CStringArray arrayPossibleBanners ;
GetDirListing ( m_sSongDir + CString ( "*.png" ), arrayPossibleBanners );
2001-12-19 14:56:22 +00:00
GetDirListing ( m_sSongDir + CString ( "*.jpg" ), arrayPossibleBanners );
2001-11-25 04:31:44 +00:00
GetDirListing ( m_sSongDir + CString ( "*.bmp" ), arrayPossibleBanners );
2001-11-03 10:52:42 +00:00
2001-12-20 12:37:38 +00:00
DWORD dwSmallestFileSoFar = 0xFFFFFFFF ;
2001-12-28 10:15:59 +00:00
CString sSmallestFileSoFar = "" ;
2001-12-19 14:56:22 +00:00
for ( int i = 0 ; i < arrayPossibleBanners . GetSize (); i ++ )
{
2001-12-20 12:37:38 +00:00
DWORD this_size = GetFileSizeInBytes ( m_sSongDir + arrayPossibleBanners [ i ] );
if ( this_size < dwSmallestFileSoFar ) // we have a new leader!
2001-12-19 14:56:22 +00:00
{
2001-12-20 12:37:38 +00:00
dwSmallestFileSoFar = this_size ;
2002-06-29 11:59:09 +00:00
sSmallestFileSoFar = arrayPossibleBanners [ i ];
2001-12-19 14:56:22 +00:00
}
}
2001-12-28 10:15:59 +00:00
if ( sSmallestFileSoFar != "" ) // we found a match
2002-06-29 11:59:09 +00:00
m_sBannerFile = sSmallestFileSoFar ;
2001-11-25 04:31:44 +00:00
else
2002-06-29 11:59:09 +00:00
m_sBannerFile = "" ;
2001-12-28 10:15:59 +00:00
2002-06-14 22:25:22 +00:00
//throw RageException( ssprintf("Banner could not be found. Please check the Song file '%s' and verify the specified #BANNER exists.", GetSongFilePath()) );
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-12-19 14:56:22 +00:00
// find the largest image in the directory
2001-11-25 04:31:44 +00:00
2001-12-19 14:56:22 +00:00
CStringArray arrayPossibleBackgrounds ;
GetDirListing ( m_sSongDir + CString ( "*.png" ), arrayPossibleBackgrounds );
GetDirListing ( m_sSongDir + CString ( "*.jpg" ), arrayPossibleBackgrounds );
GetDirListing ( m_sSongDir + CString ( "*.bmp" ), arrayPossibleBackgrounds );
2001-12-20 12:37:38 +00:00
DWORD dwLargestFileSoFar = 0 ;
2001-12-28 10:15:59 +00:00
CString sLargestFileSoFar = "" ;
2001-12-19 14:56:22 +00:00
for ( int i = 0 ; i < arrayPossibleBackgrounds . GetSize (); i ++ )
{
2001-12-20 12:37:38 +00:00
DWORD this_size = GetFileSizeInBytes ( m_sSongDir + arrayPossibleBackgrounds [ i ] );
if ( this_size > dwLargestFileSoFar ) // we have a new leader!
2001-12-19 14:56:22 +00:00
{
2001-12-20 12:37:38 +00:00
dwLargestFileSoFar = this_size ;
2002-06-29 11:59:09 +00:00
sLargestFileSoFar = arrayPossibleBackgrounds [ i ];
2001-12-19 14:56:22 +00:00
}
}
2002-01-16 10:01:32 +00:00
2001-12-28 10:15:59 +00:00
if ( sLargestFileSoFar != "" ) // we found a match
2002-06-29 11:59:09 +00:00
m_sBackgroundFile = sLargestFileSoFar ;
2002-01-16 10:01:32 +00:00
}
2002-07-03 21:27:26 +00:00
// if( !DoesFileExist(GetBackgroundMoviePath()) )
// {
// m_sBackgroundMovieFile = "";
//
// CStringArray arrayPossibleBackgroundMovies;
// GetDirListing( m_sSongDir + CString("*.avi"), arrayPossibleBackgroundMovies );
// GetDirListing( m_sSongDir + CString("*.mpg"), arrayPossibleBackgroundMovies );
// GetDirListing( m_sSongDir + CString("*.mpeg"), arrayPossibleBackgroundMovies );
//
// if( arrayPossibleBackgroundMovies.GetSize() > 0 ) // we found a match
// m_sBackgroundMovieFile = arrayPossibleBackgroundMovies[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-04-16 17:31:00 +00:00
}
2002-05-19 01:59:48 +00:00
for ( int i = 0 ; i < m_arrayNotes . GetSize (); i ++ )
{
2002-07-02 00:27:58 +00:00
Notes * pNotes = m_arrayNotes [ 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-07-03 03:13:13 +00:00
pNotes -> m_fRadarValues [ r ] = tempNoteData . GetRadarValue ( ( RadarCategory ) r , m_fMusicLengthSeconds );
2002-05-19 01:59:48 +00:00
}
2001-11-03 10:52:42 +00:00
}
2001-11-30 09:38:35 +00:00
2002-05-27 08:23:27 +00:00
void Song :: GetNotesThatMatch ( NotesType nt , CArray < Notes * , Notes *>& arrayAddTo )
2001-11-30 09:38:35 +00:00
{
2002-05-19 01:59:48 +00:00
for ( int i = 0 ; i < m_arrayNotes . GetSize (); i ++ ) // for each of the Song's Notes
2002-06-14 22:25:22 +00:00
if ( m_arrayNotes [ i ] -> m_NotesType == nt )
arrayAddTo . Add ( m_arrayNotes [ i ] );
2001-11-30 09:38:35 +00:00
}
2002-05-19 01:59:48 +00:00
void Song :: SaveToCacheFile ()
2002-02-11 04:46:31 +00:00
{
2002-05-19 01:59:48 +00:00
LOG -> WriteLine ( "Song::SaveToCacheFile()" );
2002-04-16 17:31:00 +00:00
2002-06-30 23:19:33 +00:00
//
// First look in the cache for this song (without loading NoteData)
//
IniFile ini ;
ini . SetPath ( "Cache \\ index.cache" );
ini . ReadFile (); // don't care if this fails
2002-04-16 17:31:00 +00:00
2002-06-30 23:19:33 +00:00
ini . SetValueI ( "Cache" , "CacheVersion" , FILE_CACHE_VERSION );
ini . SetValueI ( "Cache" , m_sSongDir , GetHashForDirectory ( m_sSongDir ) );
ini . WriteFile ();
SaveToSMFile ( GetCacheFilePath () );
2002-05-19 01:59:48 +00:00
}
2002-06-30 23:19:33 +00:00
void Song :: SaveToSMFile ( CString sPath )
2002-05-19 01:59:48 +00:00
{
2002-06-30 23:19:33 +00:00
if ( sPath == "" )
2002-07-02 00:27:58 +00:00
{
2002-06-30 23:19:33 +00:00
sPath = GetSongFilePath ();
2002-07-02 00:27:58 +00:00
//
// rename all old files to avoid confusion
//
CStringArray arrayOldFileNames ;
GetDirListing ( m_sSongDir + "*.bms" , arrayOldFileNames );
GetDirListing ( m_sSongDir + "*.dwi" , arrayOldFileNames );
for ( int i = 0 ; i < arrayOldFileNames . GetSize (); i ++ )
{
CString sOldPath = m_sSongDir + arrayOldFileNames [ i ];
CString sNewPath = sOldPath + ".old" ;
MoveFile ( sOldPath , sNewPath );
}
}
2002-06-30 23:19:33 +00:00
LOG -> WriteLine ( "Song::SaveToSMDir('%s')" , sPath );
2002-05-19 01:59:48 +00:00
int i ;
2002-04-16 17:31:00 +00:00
2002-06-30 23:19:33 +00:00
FILE * fp = fopen ( sPath , "w" );
if ( fp == NULL )
throw RageException ( "Error opening song file '%s' for writing." , sPath );
2002-04-16 17:31:00 +00:00
2002-06-30 23:19:33 +00:00
fprintf ( fp , "#TITLE:%s; \n " , m_sMainTitle );
fprintf ( fp , "#SUBTITLE:%s; \n " , m_sSubTitle );
fprintf ( fp , "#ARTIST:%s; \n " , m_sArtist );
fprintf ( fp , "#CREDIT:%s; \n " , m_sCredit );
fprintf ( fp , "#BANNER:%s; \n " , m_sBannerFile );
fprintf ( fp , "#BACKGROUND:%s; \n " , m_sBackgroundFile );
2002-07-03 21:27:26 +00:00
// fprintf( fp, "#BACKGROUNDMOVIE:%s;\n", m_sBackgroundMovieFile );
2002-06-30 23:19:33 +00:00
fprintf ( fp , "#CDTITLE:%s; \n " , m_sCDTitleFile );
fprintf ( fp , "#MUSIC:%s; \n " , m_sMusicFile );
fprintf ( fp , "#MUSICBYTES:%u; \n " , m_iMusicBytes );
2002-07-03 21:27:26 +00:00
fprintf ( fp , "#MUSICLENGTH:%.2f; \n " , m_fMusicLengthSeconds );
fprintf ( fp , "#OFFSET:%.2f; \n " , m_fBeat0OffsetInSeconds );
2002-06-30 23:19:33 +00:00
fprintf ( fp , "#SAMPLESTART:%.2f; \n " , m_fMusicSampleStartSeconds );
fprintf ( fp , "#SAMPLELENGTH:%.2f; \n " , m_fMusicSampleLengthSeconds );
2002-04-28 20:42:32 +00:00
2002-06-30 23:19:33 +00:00
fprintf ( fp , "#FREEZES:" );
2002-04-16 17:31:00 +00:00
for ( i = 0 ; i < m_FreezeSegments . GetSize (); i ++ )
{
FreezeSegment & fs = m_FreezeSegments [ i ];
2002-06-30 23:19:33 +00:00
fprintf ( fp , "%.2f=%.2f" , fs . m_fStartBeat , fs . m_fFreezeSeconds );
2002-04-16 17:31:00 +00:00
if ( i != m_FreezeSegments . GetSize () - 1 )
2002-06-30 23:19:33 +00:00
fprintf ( fp , "," );
2002-04-16 17:31:00 +00:00
}
2002-06-30 23:19:33 +00:00
fprintf ( fp , "; \n " );
2002-04-16 17:31:00 +00:00
2002-06-30 23:19:33 +00:00
fprintf ( fp , "#BPMS:" );
2002-04-16 17:31:00 +00:00
for ( i = 0 ; i < m_BPMSegments . GetSize (); i ++ )
{
BPMSegment & bs = m_BPMSegments [ i ];
2002-06-30 23:19:33 +00:00
fprintf ( fp , "%.2f=%.2f" , bs . m_fStartBeat , bs . m_fBPM );
2002-04-16 17:31:00 +00:00
if ( i != m_BPMSegments . GetSize () - 1 )
2002-06-30 23:19:33 +00:00
fprintf ( fp , "," );
2002-04-16 17:31:00 +00:00
}
2002-06-30 23:19:33 +00:00
fprintf ( fp , "; \n " );
2002-04-16 17:31:00 +00:00
//
2002-06-24 22:04:31 +00:00
// Save all Notes for this file
2002-04-16 17:31:00 +00:00
//
2002-05-19 01:59:48 +00:00
for ( i = 0 ; i < m_arrayNotes . GetSize (); i ++ )
2002-06-30 23:19:33 +00:00
m_arrayNotes [ i ] -> WriteSMNotesTag ( fp );
2002-02-11 04:46:31 +00:00
2002-06-30 23:19:33 +00:00
fclose ( fp );
2002-02-11 04:46:31 +00:00
}
2002-05-27 08:23:27 +00:00
Grade Song :: GetGradeForDifficultyClass ( NotesType nt , DifficultyClass dc )
2002-04-16 17:31:00 +00:00
{
2002-06-14 22:25:22 +00:00
CArray < Notes * , Notes *> aNotes ;
this -> GetNotesThatMatch ( nt , aNotes );
SortNotesArrayByDifficultyClass ( aNotes );
2002-02-11 04:46:31 +00:00
2002-06-14 22:25:22 +00:00
for ( int i = 0 ; i < aNotes . GetSize (); i ++ )
2002-04-16 17:31:00 +00:00
{
2002-06-14 22:25:22 +00:00
Notes * pNotes = aNotes [ i ];
2002-05-19 01:59:48 +00:00
if ( pNotes -> m_DifficultyClass == dc )
return pNotes -> m_TopGrade ;
2002-04-16 17:31:00 +00:00
}
return GRADE_NO_DATA ;
}
2002-02-11 04:46:31 +00:00
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
int CompareSongPointersByTitle ( const void * arg1 , const void * arg2 )
{
Song * pSong1 = * ( Song ** ) arg1 ;
Song * pSong2 = * ( Song ** ) arg2 ;
2002-06-29 11:59:09 +00:00
CString sTitle1 = pSong1 -> GetFullTitle ();
CString sTitle2 = pSong2 -> GetFullTitle ();
2002-01-16 10:01:32 +00:00
CString sFilePath1 = pSong1 -> GetSongFilePath (); // this is unique among songs
CString sFilePath2 = pSong2 -> GetSongFilePath ();
CString sCompareString1 = sTitle1 + sFilePath1 ;
CString sCompareString2 = sTitle2 + sFilePath2 ;
2002-03-30 20:00:13 +00:00
return CompareCStringsAsc ( ( void * ) & sCompareString1 , ( void * ) & sCompareString2 );
2002-01-16 10:01:32 +00:00
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByTitle ( CArray < Song * , Song *> & arraySongPointers )
2002-01-16 10:01:32 +00:00
{
qsort ( arraySongPointers . GetData (), arraySongPointers . GetSize (), sizeof ( Song * ), CompareSongPointersByTitle );
}
int CompareSongPointersByBPM ( const void * arg1 , const void * arg2 )
{
Song * pSong1 = * ( Song ** ) arg1 ;
Song * pSong2 = * ( Song ** ) arg2 ;
float fMinBPM1 , fMaxBPM1 , fMinBPM2 , fMaxBPM2 ;
pSong1 -> GetMinMaxBPM ( fMinBPM1 , fMaxBPM1 );
pSong2 -> GetMinMaxBPM ( fMinBPM2 , fMaxBPM2 );
CString sFilePath1 = pSong1 -> GetSongFilePath (); // this is unique among songs
CString sFilePath2 = pSong2 -> GetSongFilePath ();
if ( fMaxBPM1 < fMaxBPM2 )
return - 1 ;
else if ( fMaxBPM1 == fMaxBPM2 )
2002-03-30 20:00:13 +00:00
return CompareCStringsAsc ( ( void * ) & sFilePath1 , ( void * ) & sFilePath2 );
2002-01-16 10:01:32 +00:00
else
return 1 ;
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByBPM ( CArray < Song * , Song *> & arraySongPointers )
2002-01-16 10:01:32 +00:00
{
qsort ( arraySongPointers . GetData (), arraySongPointers . GetSize (), sizeof ( Song * ), CompareSongPointersByBPM );
}
int CompareSongPointersByArtist ( const void * arg1 , const void * arg2 )
{
Song * pSong1 = * ( Song ** ) arg1 ;
Song * pSong2 = * ( Song ** ) arg2 ;
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 )
return - 1 ;
else if ( sArtist1 == sArtist2 )
return CompareSongPointersByTitle ( arg1 , arg2 );
else
return 1 ;
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
{
qsort ( arraySongPointers . GetData (), arraySongPointers . GetSize (), sizeof ( Song * ), CompareSongPointersByArtist );
}
int CompareSongPointersByGroup ( const void * arg1 , const void * arg2 )
{
Song * pSong1 = * ( Song ** ) arg1 ;
Song * pSong2 = * ( Song ** ) arg2 ;
2002-06-29 11:59:09 +00:00
CString sGroup1 = pSong1 -> m_sGroupName ;
CString sGroup2 = pSong2 -> m_sGroupName ;
2002-01-16 10:01:32 +00:00
2002-02-24 01:43:11 +00:00
if ( sGroup1 < sGroup2 )
return - 1 ;
else if ( sGroup1 == sGroup2 )
return CompareSongPointersByTitle ( arg1 , arg2 );
else
return 1 ;
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
{
qsort ( arraySongPointers . GetData (), arraySongPointers . GetSize (), sizeof ( Song * ), CompareSongPointersByGroup );
}
int CompareSongPointersByMostPlayed ( const void * arg1 , const void * arg2 )
{
Song * pSong1 = * ( Song ** ) arg1 ;
Song * pSong2 = * ( Song ** ) arg2 ;
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-02-24 01:43:11 +00:00
if ( iNumTimesPlayed1 < iNumTimesPlayed2 )
2002-01-16 10:01:32 +00:00
return - 1 ;
else if ( iNumTimesPlayed1 == iNumTimesPlayed2 )
2002-02-24 01:43:11 +00:00
return CompareSongPointersByTitle ( arg1 , arg2 );
2002-01-16 10:01:32 +00:00
else
return 1 ;
}
2002-02-11 04:46:31 +00:00
void SortSongPointerArrayByMostPlayed ( CArray < Song * , Song *> & arraySongPointers )
2002-01-16 10:01:32 +00:00
{
qsort ( arraySongPointers . GetData (), arraySongPointers . GetSize (), sizeof ( Song * ), CompareSongPointersByMostPlayed );
}