#include "stdafx.h" /* ----------------------------------------------------------------------------- File: Song.h Desc: Holds metadata for a song and the song's step data. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. ----------------------------------------------------------------------------- */ #include "Notes.h" #include "RageUtil.h" #include "Song.h" #include // for fmod #include "RageLog.h" #include "ErrorCatcher/ErrorCatcher.h" 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 &arrayBPMSegments ) { qsort( arrayBPMSegments.GetData(), arrayBPMSegments.GetSize(), sizeof(BPMSegment), CompareBPMSegments ); } 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 &arrayFreezeSegments ) { qsort( arrayFreezeSegments.GetData(), arrayFreezeSegments.GetSize(), sizeof(FreezeSegment), CompareFreezeSegments ); } ////////////////////////////// // Song ////////////////////////////// Song::Song() { m_bChangedSinceSave = false; m_fOffsetInSeconds = 0; m_fMusicSampleStartSeconds = m_fMusicSampleLengthSeconds = -1; m_iMusicBytes = 0; m_fMusicLength = 0; } void Song::GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut ) { // LOG->WriteLine( "GetBeatAndBPSFromElapsedTime( fElapsedTime = %f )", fElapsedTime ); // This function is a nightmare. Don't even try to understand it. :-) fElapsedTime += m_fOffsetInSeconds; for( int i=0; i fSecondsInThisSegment ) { // this BPMSegement is not the current segment fElapsedTime -= fSecondsInThisSegment; } else { // this BPMSegment IS the current segment float fBeatEstimate = fStartBeatThisSegment + fElapsedTime*fBPS; for( int j=0; j 0.1f ) { GetBeatAndBPSFromElapsedTime( fElapsedTimeBestGuess, fBeatOut, fBPSOut ); if( fBeatOut > fBeat ) fElapsedTimeBestGuess -= fSecondsToMove; else fElapsedTimeBestGuess += fSecondsToMove; fSecondsToMove /= 2; } return fElapsedTimeBestGuess; } void Song::GetMainAndSubTitlesFromFullTitle( CString sFullTitle, CString &sMainTitleOut, CString &sSubTitleOut ) { char szSeps[] = { '-', '~' }; for( int i=0; iWriteLine( "Song::LoadFromSongDir(%s)", sDir ); // make sure there is a trailing '\\' at the end of sDir if( sDir.Right(1) != "\\" ) sDir += "\\"; // save song dir m_sSongDir = sDir; // // First look in the cache for this song (without loading NoteData) // if( LoadFromCacheFile(false) ) return true; // // There was no entry in the cache for this song. // Let's load it from a file, then write a cache entry. // CStringArray arrayBMSFileNames; GetDirListing( sDir + CString("*.bms"), arrayBMSFileNames ); int iNumBMSFiles = arrayBMSFileNames.GetSize(); CStringArray arrayDWIFileNames; GetDirListing( sDir + CString("*.dwi"), arrayDWIFileNames ); int iNumDWIFiles = arrayDWIFileNames.GetSize(); CStringArray arraySongFileNames; GetDirListing( sDir + CString("*.song"), arraySongFileNames ); int iNumSongFiles = arraySongFileNames.GetSize(); if( iNumSongFiles > 1 ) FatalError( "There is more than one .song file in '%s'. There should be only one!", sDir ); else if( iNumDWIFiles > 1 ) FatalError( "There is more than one DWI file in '%s'. There should be only one!", sDir ); else if( iNumSongFiles == 1 ) LoadFromSMDir( sDir ); else if( iNumDWIFiles == 1 ) LoadFromDWIFile( sDir + arrayDWIFileNames[0] ); else if( iNumBMSFiles > 0 ) LoadFromBMSDir( sDir ); else FatalError( "Couldn't find any .song, BMS, or DWI files in '%s'. This is not a valid song directory.", sDir ); TidyUpData(); // // In order to save memory, we're going to save the file back to the cache, // then unload all the large NoteData. // SaveToCacheFile(); LoadFromCacheFile( false ); return true; } bool Song::LoadFromBMSDir( CString sDir ) { LOG->WriteLine( "Song::LoadFromBMSDir(%s)", sDir ); // 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 arrayBMSFileNames; GetDirListing( sDir + CString("*.bms"), arrayBMSFileNames ); if( arrayBMSFileNames.GetSize() == 0 ) FatalError( ssprintf("Couldn't find any BMS files in '%s'", sDir) ); // 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 + arrayBMSFileNames[0]; // load the Notes from the rest of the BMS files for( int i=0; i') value_data.Replace( "(ANOTHER)", "" ); value_data.Replace( "(BASIC)", "" ); value_data.Replace( "(MANIAC)", "" ); value_data.Replace( "", "" ); value_data.Replace( "", "" ); value_data.Replace( "", "" ); GetMainAndSubTitlesFromFullTitle( value_data, m_sMainTitle, m_sSubTitle ); } else if( -1 != value_name.Find("#artist") ) { m_sArtist = value_data; } else if( -1 != value_name.Find("#bpm") ) { BPMSegment new_seg; new_seg.m_fStartBeat = 0; new_seg.m_fBPM = (float)atof( value_data ); // add, then sort m_BPMSegments.Add( new_seg ); SortBPMSegmentsArray( m_BPMSegments ); LOG->WriteLine( "Inserting new BPM change at beat %f, BPM %f", new_seg.m_fStartBeat, new_seg.m_fBPM ); } 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" { int iMeasureNo = atoi( value_name.Mid(1,3) ); int iTrackNum = atoi( value_name.Mid(4,2) ); CString sNoteData = value_data; CArray arrayNotes; for( int i=0; iWriteLine( "%s:%s: iMeasureNo = %d, iTrackNum = %d, iNumNotesInThisMeasure = %d", // valuename, sNoteData, iMeasureNo, iTrackNum, iNumNotesInThisMeasure ); for( int j=0; jWriteLine( "Found offset to be index %d, beat %f", iStepIndex, NoteRowToBeat(iStepIndex) ); break; case 03: // bpm BPMSegment new_seg; new_seg.m_fStartBeat = NoteRowToBeat( (float)iStepIndex ); new_seg.m_fBPM = (float)arrayNotes[j]; m_BPMSegments.Add( new_seg ); // add to back for now (we'll sort later) SortBPMSegmentsArray( m_BPMSegments ); break; } } } } } for( i=0; iWriteLine( "There is a BPM change at beat %f, BPM %f, index %d", m_BPMSegments[i].m_fStartBeat, m_BPMSegments[i].m_fBPM, i ); return TRUE; } bool Song::LoadFromDWIFile( CString sPath ) { LOG->WriteLine( "Song::LoadFromDWIFile(%s)", sPath ); // 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); CStdioFile file; if( !file.Open( GetSongFilePath(), CFile::modeRead|CFile::shareDenyNone ) ) FatalError( ssprintf("Error opening DWI file '%s'.", GetSongFilePath()) ); // MessageBox( NULL, sFName, sFName, MB_OK ); // 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 ); if( arrayValueTokens.GetSize() == 0 ) continue; CString sValueName = arrayValueTokens.GetAt( 0 ); sValueName.TrimLeft(); // handle the data if( sValueName == "#FILE" ) m_sMusicPath = CString("DWI Support\\") + arrayValueTokens[1]; else if( sValueName == "#TITLE" ) { GetMainAndSubTitlesFromFullTitle( arrayValueTokens[1], m_sMainTitle, m_sSubTitle ); } else if( sValueName == "#ARTIST" ) m_sArtist = arrayValueTokens[1]; else if( sValueName == "#BPM" ) { BPMSegment new_seg; new_seg.m_fStartBeat = 0; new_seg.m_fBPM = (float)atof( arrayValueTokens[1] ); m_BPMSegments.Add( new_seg ); SortBPMSegmentsArray( m_BPMSegments ); } else if( sValueName == "#GAP" ) // the units of GAP is 1/1000 second m_fOffsetInSeconds = -atoi( arrayValueTokens[1] ) / 1000.0f; else if( sValueName == "#SAMPLESTART" ) m_fMusicSampleStartSeconds = TimeToSeconds( arrayValueTokens[1] ); else if( sValueName == "#SAMPLELENGTH" ) m_fMusicSampleLengthSeconds = TimeToSeconds( arrayValueTokens[1] ); else if( sValueName == "#FREEZE" ) { CStringArray arrayFreezeExpressions; split( arrayValueTokens[1], ",", arrayFreezeExpressions ); for( int f=0; fWriteLine( "Song::LoadFromSMDir(%s)", sDir ); 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 ) FatalError( "Couldn't find any SM Song files in '%s'", sDir ); // 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]; CStringArray arrayNotesFileNames; GetDirListing( sDir + CString("*.notes"), arrayNotesFileNames ); // load the Notes from the rest of the BMS files for( i=0; i dwLargestFileSoFar ) // we have a new leader! { dwLargestFileSoFar = this_size; sLargestFileSoFar = m_sSongDir + arrayPossibleBackgrounds[i]; } } if( sLargestFileSoFar != "" ) // we found a match m_sBackgroundPath = sLargestFileSoFar; } 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]; } if( !DoesFileExist(GetCDTitlePath()) ) { m_sCDTitlePath = ""; } for( int i=0; im_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 ); pNM->m_fRadarValues[RADAR_CHAOS] = pNM->GetNoteData()->GetChaosRadarValue( fMusicLength ); pNM->m_fRadarValues[RADAR_FREEZE] = pNM->GetNoteData()->GetFreezeRadarValue( fMusicLength ); } } void Song::GetNotesThatMatch( NotesType nt, CArray& arrayAddTo ) { for( int i=0; iWriteLine( "Song::SaveToCacheFile()" ); int i; CString sCacheFilePath = GetCacheFilePath(); FILE* file = fopen( sCacheFilePath, "w" ); ASSERT( file != NULL ); if( file == NULL ) return; 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; iWriteLine( "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; } 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; iWriteLine( "Song::SaveToSMDir()" ); int i; // // 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 arrayNotess; this->GetNotesThatMatch( nt, arrayNotess ); SortNotesArrayByDifficultyClass( arrayNotess ); for( int i=0; im_DifficultyClass == dc ) return pNotes->m_TopGrade; } return GRADE_NO_DATA; } ///////////////////////////////////// // Sorting ///////////////////////////////////// int CompareSongPointersByTitle(const void *arg1, const void *arg2) { Song* pSong1 = *(Song**)arg1; Song* pSong2 = *(Song**)arg2; CString sTitle1 = pSong1->GetMainTitle(); CString sTitle2 = pSong2->GetMainTitle(); CString sFilePath1 = pSong1->GetSongFilePath(); // this is unique among songs CString sFilePath2 = pSong2->GetSongFilePath(); CString sCompareString1 = sTitle1 + sFilePath1; CString sCompareString2 = sTitle2 + sFilePath2; return CompareCStringsAsc( (void*)&sCompareString1, (void*)&sCompareString2 ); } void SortSongPointerArrayByTitle( CArray &arraySongPointers ) { 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 ) return CompareCStringsAsc( (void*)&sFilePath1, (void*)&sFilePath2 ); else return 1; } void SortSongPointerArrayByBPM( CArray &arraySongPointers ) { 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(); if( sArtist1 < sArtist2 ) return -1; else if( sArtist1 == sArtist2 ) return CompareSongPointersByTitle( arg1, arg2 ); else return 1; } void SortSongPointerArrayByArtist( CArray &arraySongPointers ) { 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(); if( sGroup1 < sGroup2 ) return -1; else if( sGroup1 == sGroup2 ) return CompareSongPointersByTitle( arg1, arg2 ); else return 1; } void SortSongPointerArrayByGroup( CArray &arraySongPointers ) { qsort( arraySongPointers.GetData(), arraySongPointers.GetSize(), sizeof(Song*), CompareSongPointersByGroup ); } int CompareSongPointersByMostPlayed(const void *arg1, const void *arg2) { Song* pSong1 = *(Song**)arg1; Song* pSong2 = *(Song**)arg2; int iNumTimesPlayed1 = pSong1->GetNumTimesPlayed(); int iNumTimesPlayed2 = pSong2->GetNumTimesPlayed(); if( iNumTimesPlayed1 < iNumTimesPlayed2 ) return -1; else if( iNumTimesPlayed1 == iNumTimesPlayed2 ) return CompareSongPointersByTitle( arg1, arg2 ); else return 1; } void SortSongPointerArrayByMostPlayed( CArray &arraySongPointers ) { qsort( arraySongPointers.GetData(), arraySongPointers.GetSize(), sizeof(Song*), CompareSongPointersByMostPlayed ); }