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"
#include "Song.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-14 22:25:22 +00:00
2001-11-03 10:52:42 +00:00
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 ;
m_fOffsetInSeconds = 0 ;
2002-04-28 20:42:32 +00:00
m_fMusicSampleStartSeconds = m_fMusicSampleLengthSeconds = - 1 ;
2002-05-19 01:59:48 +00:00
m_iMusicBytes = 0 ;
m_fMusicLength = 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 );
}
2001-12-19 01:50:57 +00:00
void Song :: GetBeatAndBPSFromElapsedTime ( float fElapsedTime , float & fBeatOut , float & fBPSOut )
{
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. :-)
2001-12-19 01:50:57 +00:00
fElapsedTime += m_fOffsetInSeconds ;
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
{
// 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 ;
return ;
}
}
else
break ;
}
}
fBeatOut = fBeatEstimate ;
2001-12-19 01:50:57 +00:00
fBPSOut = fBPS ;
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 ;
while ( fSecondsToMove > 0.1f )
{
GetBeatAndBPSFromElapsedTime ( fElapsedTimeBestGuess , fBeatOut , fBPSOut );
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 ()
{
ULONG hash = GetHashForString ( m_sSongDir );
2002-05-27 08:23:27 +00:00
return ssprintf ( "Cache \\ %u.song" , hash );
2002-05-19 01:59:48 +00:00
}
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-05-19 01:59:48 +00:00
//
// First look in the cache for this song (without loading NoteData)
//
2002-05-28 20:01:22 +00:00
//if( LoadFromCacheFile(false) )
// return true;
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-04-16 17:31:00 +00:00
CStringArray arraySongFileNames ;
GetDirListing ( sDir + CString ( "*.song" ), arraySongFileNames );
int iNumSongFiles = arraySongFileNames . GetSize ();
2001-11-03 10:52:42 +00:00
2002-04-16 17:31:00 +00:00
if ( iNumSongFiles > 1 )
2002-06-14 22:25:22 +00:00
throw RageException ( "There is more than one .song 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-04-16 17:31:00 +00:00
else if ( iNumSongFiles == 1 )
LoadFromSMDir ( sDir );
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-14 22:25:22 +00:00
throw RageException ( "Couldn't find any .song, 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 ();
//
// In order to save memory, we're going to save the file back to the cache,
// then unload all the large NoteData.
//
2002-05-28 20:01:22 +00:00
//SaveToCacheFile();
//LoadFromCacheFile( false );
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
// make sure there is a trailing '\\' at the end of sDir
if ( sDir . Right ( 1 ) != " \\ " )
sDir += " \\ " ;
2001-12-19 01:50:57 +00:00
2001-12-28 10:15:59 +00:00
// save song dir
m_sSongDir = sDir ;
// get group name
CStringArray sDirectoryParts ;
split ( m_sSongDir , " \\ " , sDirectoryParts , true );
m_sGroupName = sDirectoryParts [ 1 ];
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
// Load the Song info from the first BMS file. Silly BMS duplicates the song info in every
2002-01-24 08:01:24 +00:00
// file. So, we read the song data from only the first BMS file and assume that the info
// is identical for every BMS file in the directory.
2002-02-11 04:46:31 +00:00
m_sSongFilePath = m_sSongDir + arrayBMSFileNames [ 0 ];
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-01-24 08:01:24 +00:00
CStdioFile file ;
2002-02-11 04:46:31 +00:00
if ( ! file . Open ( m_sSongFilePath , CFile :: modeRead | CFile :: shareDenyNone ) )
2001-11-03 10:52:42 +00:00
{
2002-06-27 17:49:10 +00:00
throw RageException ( "Failed to open %s." , m_sSongFilePath );
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 )
{
value_data . Delete ( iIndex , 10000 );
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
{
m_sBackgroundPath = m_sSongDir + value_data ;
}
2002-06-27 17:49:10 +00:00
else if ( value_name == "#wav" )
2002-01-24 08:01:24 +00:00
{
m_sMusicPath = m_sSongDir + value_data ;
}
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
2001-12-19 01:50:57 +00:00
float fBeatOffset ;
2002-05-19 01:59:48 +00:00
fBeatOffset = NoteRowToBeat ( ( float ) iStepIndex );
2001-12-19 01:50:57 +00:00
float fBPS ;
fBPS = m_BPMSegments [ 0 ]. m_fBPM / 60.0f ;
2001-11-27 22:47:30 +00:00
m_fOffsetInSeconds = fBeatOffset / fBPS ;
2002-05-19 01:59:48 +00:00
//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 ;
if ( ! file . Open ( m_sSongFilePath , CFile :: modeRead | CFile :: shareDenyNone ) )
{
throw RageException ( "Failed to open %s." , m_sSongFilePath );
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
{
LOG -> WriteLine ( "WARNING: Couldn't find tag '%s' in '%s'." , sTagToLookFor , m_sSongFilePath );
}
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 );
}
}
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 ;
if ( ! file . Open ( m_sSongFilePath , CFile :: modeRead | CFile :: shareDenyNone ) )
{
throw RageException ( "Failed to open %s." , m_sSongFilePath );
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 )
{
// 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
{
LOG -> WriteLine ( "WARNING: Couldn't find tag '%s' in '%s'." , sTagToLookFor , m_sSongFilePath );
}
else
{
FreezeSegment newSeg ( fFreezeStartBeat , fFreezeSecs );
AddFreezeSegment ( newSeg );
LOG -> WriteLine ( "Inserting new Freeze at beat %f, secs %f" , newSeg . m_fStartBeat , newSeg . m_fFreezeSeconds );
}
}
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 );
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
// save song file path
m_sSongFilePath = sPath ;
// save song dir
CString sDir , sFName , sExt ;
splitrelpath ( sPath , sDir , sFName , sExt );
m_sSongDir = sDir ;
// get group name
sDir . MakeLower ();
2002-05-28 20:01:22 +00:00
CStringArray sDirectoryParts ;
split ( m_sSongDir , " \\ " , sDirectoryParts , true );
m_sGroupName = sDirectoryParts [ 1 ];
2001-12-28 10:15:59 +00:00
2001-11-03 10:52:42 +00:00
CStdioFile file ;
2002-01-24 08:01:24 +00:00
if ( ! file . Open ( GetSongFilePath (), CFile :: modeRead | CFile :: shareDenyNone ) )
2002-06-24 22:04:31 +00:00
throw RageException ( "Error opening DWI file '%s'." , GetSongFilePath () );
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 ) )
sFileText += buffer ;
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-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 );
2002-02-09 18:53:47 +00:00
sValueName . TrimLeft ();
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-05-28 20:01:22 +00:00
m_sMusicPath = 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
m_fOffsetInSeconds = - 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-04-16 17:31:00 +00:00
bool Song :: LoadFromSMDir ( CString sDir )
{
2002-05-01 19:14:55 +00:00
LOG -> WriteLine ( "Song::LoadFromSMDir(%s)" , sDir );
2002-04-16 17:31:00 +00:00
int i ;
// make sure there is a trailing '\\' at the end of sDir
if ( sDir . Right ( 1 ) != " \\ " )
sDir += " \\ " ;
// save song dir
m_sSongDir = sDir ;
// get group name
CStringArray sDirectoryParts ;
split ( m_sSongDir , " \\ " , sDirectoryParts , true );
m_sGroupName = sDirectoryParts [ 1 ];
CStringArray arraySongFileNames ;
GetDirListing ( sDir + "*.song" , arraySongFileNames );
if ( arraySongFileNames . GetSize () == 0 )
2002-06-14 22:25:22 +00:00
throw RageException ( "Couldn't find any SM Song files in '%s'" , sDir );
2002-04-16 17:31:00 +00:00
// Load the Song info from the first BMS file. Silly BMS duplicates the song info in every
// file. So, we read the song data from only the first BMS file and assume that the info
// is identical for every BMS file in the directory.
m_sSongFilePath = m_sSongDir + arraySongFileNames [ 0 ];
2002-05-19 01:59:48 +00:00
CStringArray arrayNotesFileNames ;
GetDirListing ( sDir + CString ( "*.notes" ), arrayNotesFileNames );
2002-04-16 17:31:00 +00:00
2002-05-19 01:59:48 +00:00
// load the Notes from the rest of the BMS files
for ( i = 0 ; i < arrayNotesFileNames . GetSize (); i ++ )
2002-04-16 17:31:00 +00:00
{
2002-06-14 22:25:22 +00:00
Notes * pNewNotes = new Notes ;
pNewNotes -> LoadFromNotesFile ( m_sSongDir + arrayNotesFileNames [ i ] );
m_arrayNotes . Add ( pNewNotes );
2002-04-16 17:31:00 +00:00
}
CStdioFile file ;
if ( ! file . Open ( GetSongFilePath (), CFile :: modeRead | CFile :: shareDenyNone ) )
2002-06-14 22:25:22 +00:00
throw RageException ( "Error opening DWI file '%s'." , GetSongFilePath () );
2002-04-16 17:31:00 +00:00
// read the whole file into a sFileText
CString sFileText ;
CString buffer ;
while ( file . ReadString ( buffer ) )
sFileText += buffer ;
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 );
if ( arrayValueTokens . GetSize () == 0 )
continue ;
CString sValueName = arrayValueTokens . GetAt ( 0 );
sValueName . TrimLeft ();
// handle the data
if ( sValueName == "#TITLE" )
2002-04-28 20:42:32 +00:00
GetMainAndSubTitlesFromFullTitle ( arrayValueTokens [ 1 ], m_sMainTitle , m_sSubTitle );
if ( sValueName == "#MAINTITLE" )
m_sMainTitle = arrayValueTokens [ 1 ];
if ( sValueName == "#SUBTITLE" )
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" )
m_sBannerPath = sDir + " \\ " + arrayValueTokens [ 1 ];
else if ( sValueName == "#BACKGROUND" )
m_sBackgroundPath = sDir + " \\ " + arrayValueTokens [ 1 ];
else if ( sValueName == "#BACKGROUNDMOVIE" )
{
if ( arrayValueTokens [ 1 ] != "" )
m_sBackgroundMoviePath = sDir + " \\ " + arrayValueTokens [ 1 ];
}
else if ( sValueName == "#CDTITLE" )
{
if ( arrayValueTokens [ 1 ] != "" )
m_sCDTitlePath = sDir + " \\ " + arrayValueTokens [ 1 ];
}
else if ( sValueName == "#MUSIC" )
m_sMusicPath = sDir + " \\ " + arrayValueTokens [ 1 ];
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-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" )
m_fOffsetInSeconds = ( float ) atof ( arrayValueTokens [ 1 ] );
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
m_FreezeSegments . Add ( new_seg ); // add to back for now (we'll sort later)
SortFreezeSegmentsArray ( m_FreezeSegments );
}
}
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 ;
// add and sort
m_BPMSegments . Add ( new_seg );
SortBPMSegmentsArray ( m_BPMSegments );
}
}
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
2001-12-28 10:15:59 +00:00
if ( m_sMusicPath == "" || ! 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
2001-12-28 10:15:59 +00:00
m_sMusicPath = m_sSongDir + arrayPossibleMusic [ 0 ];
2001-11-25 04:31:44 +00:00
else
2001-12-28 10:15:59 +00:00
m_sMusicPath = "" ;
2002-06-14 22:25:22 +00:00
// throw RageException( ssprintf("Music could not be found. Please check the Song file '%s' and verify the specified #MUSIC exists.", GetSongFilePath()) );
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 () );
m_fMusicLength = sound . GetLengthSeconds ();
}
2001-12-28 10:15:59 +00:00
if ( ! DoesFileExist ( GetBannerPath ()) )
2001-11-03 10:52:42 +00:00
{
2002-01-16 10:01:32 +00:00
m_sBannerPath = "" ;
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 ;
2001-12-28 10:15:59 +00:00
sSmallestFileSoFar = m_sSongDir + arrayPossibleBanners [ i ];
2001-12-19 14:56:22 +00:00
}
}
2001-12-28 10:15:59 +00:00
if ( sSmallestFileSoFar != "" ) // we found a match
m_sBannerPath = sSmallestFileSoFar ;
2001-11-25 04:31:44 +00:00
else
2001-12-28 10:15:59 +00:00
m_sBannerPath = "" ;
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
2001-12-28 10:15:59 +00:00
if ( ! DoesFileExist ( GetBackgroundPath ()) )
2001-11-03 10:52:42 +00:00
{
2002-01-16 10:01:32 +00:00
m_sBackgroundPath = "" ;
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 ;
2001-12-28 10:15:59 +00:00
sLargestFileSoFar = m_sSongDir + 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
m_sBackgroundPath = sLargestFileSoFar ;
2002-01-16 10:01:32 +00:00
}
if ( ! DoesFileExist ( GetBackgroundMoviePath ()) )
{
m_sBackgroundMoviePath = "" ;
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_sBackgroundMoviePath = arrayPossibleBackgroundMovies [ 0 ];
2001-11-03 10:52:42 +00:00
}
2002-04-16 17:31:00 +00:00
if ( ! DoesFileExist ( GetCDTitlePath ()) )
{
m_sCDTitlePath = "" ;
}
2002-05-19 01:59:48 +00:00
for ( int i = 0 ; i < m_arrayNotes . GetSize (); i ++ )
{
2002-06-14 22:25:22 +00:00
Notes * pNM = m_arrayNotes [ i ];
2002-05-19 01:59:48 +00:00
float fMusicLength = m_fMusicLength ;
if ( fMusicLength == 0 )
fMusicLength = 100 ;
pNM -> m_fRadarValues [ RADAR_STREAM ] = pNM -> GetNoteData () -> GetStreamRadarValue ( fMusicLength );
pNM -> m_fRadarValues [ RADAR_VOLTAGE ] = pNM -> GetNoteData () -> GetVoltageRadarValue ( fMusicLength );
pNM -> m_fRadarValues [ RADAR_AIR ] = pNM -> GetNoteData () -> GetAirRadarValue ( fMusicLength );
2002-06-23 11:43:53 +00:00
pNM -> m_fRadarValues [ RADAR_FREEZE ] = pNM -> GetNoteData () -> GetFreezeRadarValue ( fMusicLength );
2002-06-24 22:04:31 +00:00
pNM -> m_fRadarValues [ RADAR_CHAOS ] = pNM -> GetNoteData () -> GetChaosRadarValue ( fMusicLength );
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-01-16 10:01:32 +00:00
2002-05-27 08:23:27 +00:00
const int FILE_CACHE_VERSION = 10 ; // increment this when the cache file format changes
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
int i ;
2002-05-19 01:59:48 +00:00
CString sCacheFilePath = GetCacheFilePath ();
2002-04-16 17:31:00 +00:00
2002-05-19 01:59:48 +00:00
FILE * file = fopen ( sCacheFilePath , "w" );
ASSERT ( file != NULL );
if ( file == NULL )
2002-04-16 17:31:00 +00:00
return ;
2002-05-19 01:59:48 +00:00
fprintf ( file , "%d \n " , FILE_CACHE_VERSION );
fprintf ( file , "%u \n " , GetHashForDirectory ( m_sSongDir ) );
WriteStringToFile ( file , m_sSongDir );
WriteStringToFile ( file , m_sSongFilePath );
WriteStringToFile ( file , m_sGroupName );
WriteStringToFile ( file , m_sMainTitle );
WriteStringToFile ( file , m_sSubTitle );
WriteStringToFile ( file , m_sArtist );
WriteStringToFile ( file , m_sCredit );
fprintf ( file , "%f \n " , m_fOffsetInSeconds );
WriteStringToFile ( file , m_sMusicPath );
fprintf ( file , "%d \n " , m_iMusicBytes );
fprintf ( file , "%f \n " , m_fMusicLength );
fprintf ( file , "%f \n " , m_fMusicSampleStartSeconds );
fprintf ( file , "%f \n " , m_fMusicSampleLengthSeconds );
WriteStringToFile ( file , m_sBannerPath );
WriteStringToFile ( file , m_sBackgroundPath );
WriteStringToFile ( file , m_sBackgroundMoviePath );
WriteStringToFile ( file , m_sCDTitlePath );
fprintf ( file , "%d \n " , m_BPMSegments . GetSize () );
for ( i = 0 ; i < m_BPMSegments . GetSize (); i ++ )
fprintf ( file , "%f,%f \n " , m_BPMSegments [ i ]. m_fStartBeat , m_BPMSegments [ i ]. m_fBPM );
fprintf ( file , "%d \n " , m_FreezeSegments . GetSize () );
for ( i = 0 ; i < m_FreezeSegments . GetSize (); i ++ )
fprintf ( file , "%f,%f \n " , m_FreezeSegments [ i ]. m_fStartBeat , m_FreezeSegments [ i ]. m_fFreezeSeconds );
fprintf ( file , "%d \n " , m_arrayNotes . GetSize () );
for ( i = 0 ; i < m_arrayNotes . GetSize (); i ++ )
2002-06-14 22:25:22 +00:00
m_arrayNotes [ i ] -> WriteToCacheFile ( file );
2002-05-19 01:59:48 +00:00
fclose ( file );
}
bool Song :: LoadFromCacheFile ( bool bLoadNoteData )
{
LOG -> WriteLine ( "Song::LoadFromCacheFile( %i )" , bLoadNoteData );
int i ;
CString sCacheFilePath = GetCacheFilePath ();
LOG -> WriteLine ( "cache file is '%s'." , sCacheFilePath );
FILE * file = fopen ( sCacheFilePath , "r" );
if ( file == NULL )
return false ;
int iCacheVersion ;
fscanf ( file , "%d \n " , & iCacheVersion );
if ( iCacheVersion != FILE_CACHE_VERSION )
{
LOG -> WriteLine ( "Cache file versions don't match '%s'." , sCacheFilePath );
fclose ( file );
DeleteCacheFile ();
return false ;
2002-04-16 17:31:00 +00:00
}
2002-05-19 01:59:48 +00:00
ULONG hash ;
fscanf ( file , "%u \n " , & hash );
if ( hash != GetHashForDirectory ( m_sSongDir ) )
{
LOG -> WriteLine ( "Cache file is out of date." , sCacheFilePath );
fclose ( file );
return false ;
}
ReadStringFromFile ( file , m_sSongDir );
ReadStringFromFile ( file , m_sSongFilePath );
ReadStringFromFile ( file , m_sGroupName );
ReadStringFromFile ( file , m_sMainTitle );
ReadStringFromFile ( file , m_sSubTitle );
ReadStringFromFile ( file , m_sArtist );
ReadStringFromFile ( file , m_sCredit );
fscanf ( file , "%f \n " , & m_fOffsetInSeconds );
ReadStringFromFile ( file , m_sMusicPath );
fscanf ( file , "%d \n " , & m_iMusicBytes );
fscanf ( file , "%f \n " , & m_fMusicLength );
fscanf ( file , "%f \n " , & m_fMusicSampleStartSeconds );
fscanf ( file , "%f \n " , & m_fMusicSampleLengthSeconds );
ReadStringFromFile ( file , m_sBannerPath );
ReadStringFromFile ( file , m_sBackgroundPath );
ReadStringFromFile ( file , m_sBackgroundMoviePath );
ReadStringFromFile ( file , m_sCDTitlePath );
int iNumBPMSegments ;
fscanf ( file , "%d \n " , & iNumBPMSegments );
m_BPMSegments . SetSize ( iNumBPMSegments );
for ( i = 0 ; i < iNumBPMSegments ; i ++ )
fscanf ( file , "%f,%f \n " , & m_BPMSegments [ i ]. m_fStartBeat , & m_BPMSegments [ i ]. m_fBPM );
int iNumFreezeSegments ;
fscanf ( file , "%d \n " , & iNumFreezeSegments );
m_FreezeSegments . SetSize ( iNumFreezeSegments );
for ( i = 0 ; i < iNumFreezeSegments ; i ++ )
fscanf ( file , "%f,%f \n " , & m_FreezeSegments [ i ]. m_fStartBeat , & m_FreezeSegments [ i ]. m_fFreezeSeconds );
int iNumNotes ;
fscanf ( file , "%d \n " , & iNumNotes );
m_arrayNotes . SetSize ( iNumNotes );
for ( i = 0 ; i < iNumNotes ; i ++ )
2002-06-14 22:25:22 +00:00
m_arrayNotes [ i ] -> ReadFromCacheFile ( file , bLoadNoteData );
2002-05-19 01:59:48 +00:00
fclose ( file );
return true ;
}
void Song :: DeleteCacheFile ()
{
DeleteFile ( GetCacheFilePath () );
}
void Song :: SaveToSMDir ()
{
LOG -> WriteLine ( "Song::SaveToSMDir()" );
int i ;
2002-04-16 17:31:00 +00:00
//
// rename all old files to avoid confusion
//
CStringArray arrayOldFileNames ;
GetDirListing ( m_sSongDir + CString ( "*.bms" ), arrayOldFileNames );
GetDirListing ( m_sSongDir + CString ( "*.dwi" ), arrayOldFileNames );
for ( i = 0 ; i < arrayOldFileNames . GetSize (); i ++ )
{
CString sOldPath = m_sSongDir + arrayOldFileNames [ i ];
CString sNewPath = sOldPath + ".old" ;
MoveFile ( sOldPath , sNewPath );
}
CString sDir , sFName , sExt ;
splitrelpath ( GetSongFilePath (), sDir , sFName , sExt );
m_sSongFilePath = m_sSongDir + sFName + ".song" ;
CStdioFile file ;
if ( ! file . Open ( m_sSongFilePath , CFile :: modeWrite | CFile :: modeCreate ) )
2002-06-14 22:25:22 +00:00
throw RageException ( "Error opening song file '%s' for writing." , m_sSongFilePath );
2002-04-16 17:31:00 +00:00
2002-04-28 20:42:32 +00:00
file . WriteString ( ssprintf ( "#MAINTITLE:%s; \n " , m_sMainTitle ) );
file . WriteString ( ssprintf ( "#SUBTITLE:%s; \n " , m_sSubTitle ) );
2002-04-16 17:31:00 +00:00
file . WriteString ( ssprintf ( "#ARTIST:%s; \n " , m_sArtist ) );
file . WriteString ( ssprintf ( "#CREDIT:%s; \n " , m_sCredit ) );
if ( m_sBannerPath != "" )
{
splitrelpath ( m_sBannerPath , sDir , sFName , sExt );
file . WriteString ( ssprintf ( "#BANNER:%s; \n " , sFName + "." + sExt ) );
}
else
file . WriteString ( "#BANNER:; \n " );
if ( m_sBackgroundPath != "" )
{
splitrelpath ( m_sBackgroundPath , sDir , sFName , sExt );
file . WriteString ( ssprintf ( "#BACKGROUND:%s; \n " , sFName + "." + sExt ) );
}
else
file . WriteString ( "#BACKGROUND:; \n " );
if ( m_sBackgroundMoviePath != "" )
{
splitrelpath ( m_sBackgroundMoviePath , sDir , sFName , sExt );
file . WriteString ( ssprintf ( "#BACKGROUNDMOVIE:%s; \n " , sFName + "." + sExt ) );
}
else
file . WriteString ( "#BACKGROUNDMOVIE:; \n " );
if ( m_sCDTitlePath != "" )
{
splitrelpath ( m_sCDTitlePath , sDir , sFName , sExt );
file . WriteString ( ssprintf ( "#CDTITLE:%s; \n " , sFName + "." + sExt ) );
}
else
file . WriteString ( "#CDTITLE:; \n " );
if ( m_sMusicPath != "" )
{
splitrelpath ( m_sMusicPath , sDir , sFName , sExt );
file . WriteString ( ssprintf ( "#MUSIC:%s; \n " , sFName + "." + sExt ) );
}
else
file . WriteString ( "#MUSIC:; \n " );
2002-05-19 01:59:48 +00:00
file . WriteString ( ssprintf ( "#MUSICBYTES:%u; \n " , m_iMusicBytes ) );
2002-04-16 17:31:00 +00:00
file . WriteString ( ssprintf ( "#OFFSET:%f; \n " , m_fOffsetInSeconds ) );
2002-04-28 20:42:32 +00:00
file . WriteString ( ssprintf ( "#SAMPLESTART:%f; \n " , m_fMusicSampleStartSeconds ) );
file . WriteString ( ssprintf ( "#SAMPLELENGTH:%f; \n " , m_fMusicSampleLengthSeconds ) );
2002-04-16 17:31:00 +00:00
file . WriteString ( "#FREEZES:" );
for ( i = 0 ; i < m_FreezeSegments . GetSize (); i ++ )
{
FreezeSegment & fs = m_FreezeSegments [ i ];
file . WriteString ( ssprintf ( "%f=%f" , fs . m_fStartBeat , fs . m_fFreezeSeconds ) );
if ( i != m_FreezeSegments . GetSize () - 1 )
file . WriteString ( "," );
}
file . WriteString ( "; \n " );
file . WriteString ( "#BPMS:" );
for ( i = 0 ; i < m_BPMSegments . GetSize (); i ++ )
{
BPMSegment & bs = m_BPMSegments [ i ];
file . WriteString ( ssprintf ( "%f=%f" , bs . m_fStartBeat , bs . m_fBPM ) );
if ( i != m_BPMSegments . GetSize () - 1 )
file . WriteString ( "," );
}
file . WriteString ( "; \n " );
file . Close ();
//
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-04-16 17:31:00 +00:00
{
2002-06-14 22:25:22 +00:00
m_arrayNotes [ i ] -> SaveToSMDir ( m_sSongDir );
2002-04-16 17:31:00 +00:00
}
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-04-28 20:42:32 +00:00
CString sTitle1 = pSong1 -> GetMainTitle ();
CString sTitle2 = pSong2 -> GetMainTitle ();
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 ;
CString sArtist1 = pSong1 -> GetArtist ();
CString sArtist2 = pSong2 -> GetArtist ();
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 ;
CString sGroup1 = pSong1 -> GetGroupName ();
CString sGroup2 = pSong2 -> GetGroupName ();
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 );
}
2002-04-16 17:31:00 +00:00