2001-11-03 10:52:42 +00:00
#include "stdafx.h"
2001-11-04 19:34:28 +00:00
/*
-----------------------------------------------------------------------------
File: Song.h
Desc: Holds metadata for a song and the song's step data.
Copyright (c) 2001 Chris Danford. All rights reserved.
-----------------------------------------------------------------------------
*/
2001-11-03 10:52:42 +00:00
2002-03-19 07:09:49 +00:00
#include "Pattern.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-03-30 20:00:13 +00:00
#include "RageHelper.h"
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 ;
2001-11-04 19:34:28 +00:00
}
2001-11-03 10:52:42 +00:00
2001-12-19 01:50:57 +00:00
void Song :: GetBeatAndBPSFromElapsedTime ( float fElapsedTime , float & fBeatOut , float & fBPSOut )
{
2002-03-30 20:00:13 +00:00
HELPER . Log ( "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
}
}
2001-11-03 10:52:42 +00:00
bool Song :: LoadFromSongDir ( CString sDir )
{
2002-03-30 20:00:13 +00:00
HELPER . Log ( "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 ;
// We are going to load from either a .song or .msd file.
// If no .song files exist, then look for an .msd.
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
2001-12-28 10:15:59 +00:00
if ( iNumDWIFiles > 1 )
2001-11-03 10:52:42 +00:00
{
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "There is more than one DWI file in '%s'. Which one should I use?" , sDir ) );
2001-11-03 10:52:42 +00:00
}
2001-12-19 01:50:57 +00:00
else if ( iNumDWIFiles == 1 )
2001-11-03 10:52:42 +00:00
{
2001-12-28 10:15:59 +00:00
LoadSongInfoFromDWIFile ( sDir + arrayDWIFileNames [ 0 ] );
}
else if ( iNumBMSFiles > 0 )
{
LoadSongInfoFromBMSDir ( sDir );
2001-11-03 10:52:42 +00:00
}
else
2001-12-28 10:15:59 +00:00
{
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Couldn't find any BMS or MSD files in '%s'" , sDir ) );
2001-12-28 10:15:59 +00:00
}
2001-11-03 10:52:42 +00:00
return TRUE ;
}
2001-12-28 10:15:59 +00:00
bool Song :: LoadSongInfoFromBMSDir ( CString sDir )
2001-11-03 10:52:42 +00:00
{
2002-03-30 20:00:13 +00:00
HELPER . Log ( "Song::LoadSongInfoFromBMSDir(%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-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "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-03-19 07:09:49 +00:00
// load the Pattern from the rest of the BMS files
2001-12-28 10:15:59 +00:00
for ( int i = 0 ; i < arrayBMSFileNames . GetSize (); i ++ )
{
2002-03-19 07:09:49 +00:00
m_arrayPatterns . SetSize ( m_arrayPatterns . GetSize () + 1 );
Pattern & new_steps = m_arrayPatterns [ m_arrayPatterns . GetSize () - 1 ];
new_steps . LoadFromBMSFile ( m_sSongDir + arrayBMSFileNames [ i ] );
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-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "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-01-24 08:01:24 +00:00
if ( - 1 != value_name . Find ( "#genre" ) )
{
m_sCreator = value_data ;
2001-11-04 19:34:28 +00:00
}
2002-01-24 08:01:24 +00:00
else if ( - 1 != value_name . Find ( "#title" ) )
{
m_sTitle = value_data ;
2002-03-19 07:09:49 +00:00
// strip Pattern type out of description leaving only song title (looks like 'B4U <BASIC>')
2002-01-24 08:01:24 +00:00
m_sTitle . Replace ( "(ANOTHER)" , "" );
m_sTitle . Replace ( "(BASIC)" , "" );
m_sTitle . Replace ( "(MANIAC)" , "" );
m_sTitle . Replace ( "<ANOTHER>" , "" );
m_sTitle . Replace ( "<BASIC>" , "" );
m_sTitle . Replace ( "<MANIAC>" , "" );
}
else if ( - 1 != value_name . Find ( "#artist" ) )
{
m_sArtist = value_data ;
}
else if ( - 1 != value_name . Find ( "#bpm" ) )
{
2001-11-27 22:47:30 +00:00
BPMSegment new_seg ;
2001-12-19 01:50:57 +00:00
new_seg . m_fStartBeat = 0 ;
2002-01-24 08:01:24 +00:00
new_seg . m_fBPM = ( float ) atof ( value_data );
2001-12-28 10:15:59 +00:00
// add, then sort
2001-12-19 01:50:57 +00:00
m_BPMSegments . Add ( new_seg );
2001-12-28 10:15:59 +00:00
SortBPMSegmentsArray ( m_BPMSegments );
2002-03-30 20:00:13 +00:00
HELPER . Log ( "Inserting new BPM change at beat %f, BPM %f" , new_seg . m_fStartBeat , new_seg . m_fBPM );
2001-11-27 22:47:30 +00:00
}
2002-01-24 08:01:24 +00:00
else if ( - 1 != value_name . Find ( "#backbmp" ) )
{
m_sBackgroundPath = m_sSongDir + value_data ;
}
else if ( - 1 != value_name . Find ( "#wav" ) )
{
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 ) );
2001-11-27 22:47:30 +00:00
int iTrackNum = atoi ( value_name . Mid ( 4 , 2 ) );
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-03-30 20:00:13 +00:00
//HELPER.Log( "%s:%s: iMeasureNo = %d, iTrackNum = %d, iNumNotesInThisMeasure = %d",
2001-11-27 22:47:30 +00:00
// valuename, sNoteData, iMeasureNo, iTrackNum, 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
2001-11-27 22:47:30 +00:00
switch ( iTrackNum )
2001-11-04 19:34:28 +00:00
{
2001-12-19 01:50:57 +00:00
case 01 : // background music track
float fBeatOffset ;
2002-03-19 07:09:49 +00:00
fBeatOffset = NoteIndexToBeat ( ( 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-03-30 20:00:13 +00:00
//HELPER.Log( "Found offset to be index %d, beat %f", iStepIndex, NoteIndexToBeat(iStepIndex) );
2001-11-27 22:47:30 +00:00
break ;
case 03 : // bpm
2001-12-19 01:50:57 +00:00
BPMSegment new_seg ;
2002-03-19 07:09:49 +00:00
new_seg . m_fStartBeat = NoteIndexToBeat ( ( float ) iStepIndex );
2001-12-19 01:50:57 +00:00
new_seg . m_fBPM = ( float ) arrayNotes [ j ];
2001-12-28 10:15:59 +00:00
2001-12-19 01:50:57 +00:00
m_BPMSegments . Add ( new_seg ); // add to back for now (we'll sort later)
SortBPMSegmentsArray ( m_BPMSegments );
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-03-30 20:00:13 +00:00
HELPER . Log ( "There is a BPM change at beat fd, 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-25 04:31:44 +00:00
TidyUpData ();
2001-11-03 10:52:42 +00:00
return TRUE ;
}
2001-12-19 01:50:57 +00:00
bool Song :: LoadSongInfoFromDWIFile ( CString sPath )
2001-11-03 10:52:42 +00:00
{
2002-03-30 20:00:13 +00:00
HELPER . Log ( "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 ();
if ( sDir . Find ( "dwi support" ) != - 1 ) // loading from DWI support
{
int iIndexOfFirstBackslash = sDir . Find ( '\\' );
int iIndexOfSecondBackslash = sDir . Find ( '\\' , iIndexOfFirstBackslash + 1 );
int iIndexOfThirdBackslash = sDir . Find ( '\\' , iIndexOfSecondBackslash + 1 );
m_sGroupName = sDir . Mid ( iIndexOfSecondBackslash + 1 , iIndexOfThirdBackslash - iIndexOfSecondBackslash - 1 );
}
else
{
// get group name
CStringArray sDirectoryParts ;
split ( m_sSongDir , " \\ " , sDirectoryParts , true );
m_sGroupName = sDirectoryParts [ 1 ];
}
// save probable image paths
m_sBackgroundPath = ssprintf ( ". \\ DWI Support \\ Backgrounds \\ %s \\ %s.avi" , m_sGroupName , sFName );
if ( ! DoesFileExist ( GetBackgroundPath () ) )
m_sBackgroundPath = ssprintf ( ". \\ DWI Support \\ Backgrounds \\ %s \\ %s.mpg" , m_sGroupName , sFName );
if ( ! DoesFileExist ( GetBackgroundPath () ) )
m_sBackgroundPath = ssprintf ( ". \\ DWI Support \\ Backgrounds \\ %s \\ %s.mpeg" , m_sGroupName , sFName );
if ( ! DoesFileExist ( GetBackgroundPath () ) )
m_sBackgroundPath = ssprintf ( ". \\ DWI Support \\ Backgrounds \\ %s \\ %s.png" , m_sGroupName , sFName );
m_sBannerPath = ssprintf ( ". \\ DWI Support \\ Banners \\ %s \\ %s.png" , m_sGroupName , sFName );
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-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Error opening DWI file '%s'." , GetSongFilePath ()) );
2001-12-28 10:15:59 +00:00
// MessageBox( NULL, sFName, sFName, MB_OK );
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 ();
// 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" )
2001-12-28 10:15:59 +00:00
m_sMusicPath = CString ( "DWI Support \\ " ) + arrayValueTokens [ 1 ];
2001-12-19 01:50:57 +00:00
else if ( sValueName == "#TITLE" )
2001-11-03 10:52:42 +00:00
m_sTitle = arrayValueTokens [ 1 ];
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
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-03-19 07:09:49 +00:00
float fFreezeBeat = NoteIndexToBeat ( 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
2001-12-28 10:15:59 +00:00
FreezeSegment new_seg ;
new_seg . m_fStartBeat = fFreezeBeat ;
new_seg . m_fFreezeSeconds = fFreezeSeconds ;
2001-12-19 14:56:22 +00:00
2002-03-30 20:00:13 +00:00
HELPER . Log ( "Adding a freeze segment: beat: %f, seconds = %f" , new_seg . m_fStartBeat , new_seg . m_fFreezeSeconds );
2001-12-28 10:15:59 +00:00
m_FreezeSegments . Add ( new_seg ); // add to back for now (we'll sort later)
SortFreezeSegmentsArray ( m_FreezeSegments );
2001-11-03 10:52:42 +00:00
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-03-19 07:09:49 +00:00
float fBeat = NoteIndexToBeat ( fIndex );
2002-01-16 19:46:22 +00:00
float fNewBPM = ( float ) atoi ( arrayBPMChangeValues [ 1 ] );
2001-12-19 01:50:57 +00:00
BPMSegment new_seg ;
new_seg . m_fStartBeat = fBeat ;
new_seg . m_fBPM = fNewBPM ;
2001-12-28 10:15:59 +00:00
// add and sort
m_BPMSegments . Add ( new_seg );
2001-12-19 01:50:57 +00:00
SortBPMSegmentsArray ( m_BPMSegments );
}
}
2001-11-03 10:52:42 +00:00
else if ( sValueName == "#SINGLE" || sValueName == "#DOUBLE" || sValueName == "#COUPLE" )
{
2002-03-19 07:09:49 +00:00
m_arrayPatterns . SetSize ( m_arrayPatterns . GetSize () + 1 , 1 );
Pattern & new_pattern = m_arrayPatterns [ m_arrayPatterns . GetSize () - 1 ];
2002-03-30 20:00:13 +00:00
new_pattern . LoadFromDWIValueTokens (
arrayValueTokens [ 0 ],
arrayValueTokens [ 1 ],
arrayValueTokens [ 2 ],
arrayValueTokens [ 3 ],
arrayValueTokens . GetSize () == 5 ? arrayValueTokens [ 4 ] : ""
);
2001-11-03 10:52:42 +00:00
}
else
// do nothing. We don't care about this value name
;
}
2001-12-19 01:50:57 +00:00
TidyUpData ();
2001-11-03 10:52:42 +00:00
return TRUE ;
}
2001-11-25 04:31:44 +00:00
void Song :: TidyUpData ()
2001-11-03 10:52:42 +00:00
{
if ( m_sTitle == "" ) m_sTitle = "Untitled song" ;
if ( m_sArtist == "" ) m_sArtist = "Unknown artist" ;
2001-12-28 10:15:59 +00:00
if ( m_sCreator == "" ) m_sCreator = "Unknown creator" ;
2001-11-27 22:47:30 +00:00
if ( m_BPMSegments . GetSize () == 0 )
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "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-03-30 20:00:13 +00:00
// HELPER.FatalError( 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
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-03-30 20:00:13 +00:00
//HELPER.FatalError( 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
}
}
2001-11-30 09:38:35 +00:00
2002-03-19 07:09:49 +00:00
void Song :: GetPatternsThatMatchStyle ( DanceStyle s , CArray < Pattern * , Pattern *>& arrayAddTo )
2001-11-30 09:38:35 +00:00
{
2002-03-19 07:09:49 +00:00
for ( int i = 0 ; i < m_arrayPatterns . GetSize (); i ++ ) // for each of the Song's Pattern
2001-11-30 09:38:35 +00:00
{
2002-03-19 07:09:49 +00:00
Pattern * pCurPattern = & m_arrayPatterns [ i ];
2002-01-24 08:01:24 +00:00
2002-03-19 07:09:49 +00:00
if ( s == pCurPattern -> m_DanceStyle // if the current GameModes matches the Pattern's GameMode
|| ( s == STYLE_VERSUS && pCurPattern -> m_DanceStyle == STYLE_SINGLE ) )
2001-11-30 09:38:35 +00:00
{
2002-03-19 07:09:49 +00:00
arrayAddTo . Add ( pCurPattern );
2001-11-30 09:38:35 +00:00
}
}
}
2002-03-19 07:09:49 +00:00
void Song :: GetNumFeet ( DanceStyle s , int & iDiffEasyOut , int & iDiffMediumOut , int & iDiffHardOut )
2001-11-30 09:38:35 +00:00
{
2001-11-30 19:08:44 +00:00
iDiffEasyOut = iDiffMediumOut = iDiffHardOut = - 1 ; // -1 means not found
2002-03-19 07:09:49 +00:00
CArray < Pattern * , Pattern *> arrayMatchingSteps ;
GetPatternsThatMatchStyle ( s , arrayMatchingSteps );
2001-11-30 09:38:35 +00:00
for ( int i = 0 ; i < arrayMatchingSteps . GetSize (); i ++ )
{
int iNumFeet = arrayMatchingSteps [ i ] -> m_iNumFeet ;
2002-02-02 05:11:12 +00:00
switch ( arrayMatchingSteps [ i ] -> m_DifficultyClass )
2001-11-30 09:38:35 +00:00
{
2002-03-19 07:09:49 +00:00
case Pattern :: CLASS_EASY :
2001-11-30 09:38:35 +00:00
iDiffEasyOut = iNumFeet ;
break ;
2002-03-19 07:09:49 +00:00
case Pattern :: CLASS_MEDIUM :
2001-11-30 09:38:35 +00:00
iDiffMediumOut = iNumFeet ;
break ;
2002-03-19 07:09:49 +00:00
case Pattern :: CLASS_HARD :
2001-11-30 09:38:35 +00:00
iDiffHardOut = iNumFeet ;
break ;
2002-03-19 07:09:49 +00:00
case Pattern :: CLASS_OTHER :
2001-11-30 09:38:35 +00:00
// should do something intelligent to fill in the missing spots...
if ( iDiffEasyOut < 0 && iNumFeet <= 4 )
iDiffEasyOut = iNumFeet ;
else if ( iDiffHardOut < 0 && iNumFeet >= 7 )
iDiffHardOut = iNumFeet ;
2001-11-30 19:08:44 +00:00
else if ( iDiffMediumOut < 0 )
2001-11-30 09:38:35 +00:00
iDiffMediumOut = iNumFeet ;
break ;
}
}
2001-12-28 10:15:59 +00:00
}
2002-01-16 10:01:32 +00:00
2002-02-11 04:46:31 +00:00
void Song :: SaveOffsetChangeToDisk ()
{
CString sDir , sFName , sExt ;
splitrelpath ( GetSongFilePath (), sDir , sFName , sExt );
sExt . MakeLower ();
2002-01-16 10:01:32 +00:00
2002-02-11 04:46:31 +00:00
if ( sExt == "bms" )
{
2002-03-19 07:09:49 +00:00
for ( int i = 0 ; i < m_arrayPatterns . GetSize (); i ++ ) // for each Pattern
2002-02-11 04:46:31 +00:00
{
2002-03-19 07:09:49 +00:00
CString sPatternFileName = m_arrayPatterns [ i ]. m_sPatternFilePath ;
2002-02-11 04:46:31 +00:00
CStringArray arrayLines ;
CStdioFile fileIn ;
2002-03-19 07:09:49 +00:00
if ( ! fileIn . Open ( sPatternFileName , CFile :: modeRead | CFile :: shareDenyNone ) )
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Failed to open '%s' for reading" , sPatternFileName ) );
2002-02-11 04:46:31 +00:00
// read the file into sFileContents
CString sLine ;
while ( fileIn . ReadString ( sLine ) )
{
arrayLines . Add ( sLine + " \n " );
}
fileIn . Close ();
// write the file back to disk with the changes
CStdioFile fileOut ;
2002-03-19 07:09:49 +00:00
if ( ! fileOut . Open ( sPatternFileName , CFile :: modeWrite | CFile :: shareDenyWrite ) )
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Failed to open '%s' for writing" , sPatternFileName ) );
2002-02-11 04:46:31 +00:00
for ( int j = 0 ; j < arrayLines . GetSize (); j ++ )
{
CString sLine = arrayLines [ j ];
if ( - 1 != sLine . Find ( "01:" ) )
{
float fBPM = m_BPMSegments [ 0 ]. m_fBPM ;
float fBPS = fBPM / 60 ;
float fOffsetInBeats = m_fOffsetInSeconds * fBPS ;
float fOffsetInMeasures = fOffsetInBeats / BEATS_PER_MEASURE ;
int iMeasure = ( int ) fOffsetInMeasures ;
float fPercentIntoMeasure = fmodf ( fOffsetInMeasures , 1 );
CString sBMSMeasureString ;
for ( int k = 0 ; k < 64 ; k ++ )
{
if ( k == int ( fPercentIntoMeasure * 64 ) )
sBMSMeasureString += "99" ;
else
sBMSMeasureString += "00" ;
}
fileOut . WriteString ( ssprintf ( "#%03d01:%s; \n " , iMeasure , sBMSMeasureString ) );
}
else
{
fileOut . WriteString ( sLine );
}
}
fileOut . Close ();
}
}
else if ( sExt == "dwi" )
{
CStringArray arrayLines ;
CStdioFile fileIn ;
if ( ! fileIn . Open ( GetSongFilePath (), CFile :: modeRead | CFile :: shareDenyNone ) )
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Failed to open '%s' for reading" , GetSongFilePath ()) );
2002-02-11 04:46:31 +00:00
// read the file into sFileContents
CString sLine ;
while ( fileIn . ReadString ( sLine ) )
{
arrayLines . Add ( sLine + " \n " );
}
fileIn . Close ();
// write the file back to disk with the changes
CStdioFile fileOut ;
if ( ! fileOut . Open ( GetSongFilePath (), CFile :: modeWrite | CFile :: shareDenyWrite ) )
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Failed to open '%s' for writing" , GetSongFilePath ()) );
2002-02-11 04:46:31 +00:00
for ( int i = 0 ; i < arrayLines . GetSize (); i ++ )
{
CString sLine = arrayLines [ i ];
if ( - 1 != sLine . Find ( "#GAP" ) )
{
2002-02-25 08:54:10 +00:00
// Discover whether the GAP line has a semicolon at the end.
// If it doesn't then the semicolon is probably on the next line
// and we don't want to write a second semicolon!
bool bHasSemiColon = - 1 != sLine . Find ( ";" );
2002-02-11 04:46:31 +00:00
// replace with new offset
2002-02-25 08:54:10 +00:00
fileOut . WriteString ( ssprintf ( "#GAP:%d%s \n " , roundf ( GetBeatOffsetInSeconds () *- 1 * 1000 ), bHasSemiColon ? ";" : "" ) );
2002-02-11 04:46:31 +00:00
}
else
{
fileOut . WriteString ( sLine );
}
}
fileOut . Close ();
}
else
{
2002-03-30 20:00:13 +00:00
HELPER . FatalError ( ssprintf ( "Unrecognized extension '%s' on song file '%s'" , sExt , GetSongFilePath ()) );
2002-02-11 04:46:31 +00:00
}
m_bChangedSinceSave = false ;
}
/////////////////////////////////////
// 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 ;
CString sTitle1 = pSong1 -> GetTitle ();
CString sTitle2 = pSong2 -> GetTitle ();
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 );
}