diff --git a/stepmania/src/Notes.cpp b/stepmania/src/Notes.cpp index c93a23ea5d..2c817860d5 100644 --- a/stepmania/src/Notes.cpp +++ b/stepmania/src/Notes.cpp @@ -24,23 +24,6 @@ #include "GameManager.h" -typedef int DanceNote; -enum { - DANCE_NOTE_NONE = 0, - DANCE_NOTE_PAD1_LEFT, - DANCE_NOTE_PAD1_UPLEFT, - DANCE_NOTE_PAD1_DOWN, - DANCE_NOTE_PAD1_UP, - DANCE_NOTE_PAD1_UPRIGHT, - DANCE_NOTE_PAD1_RIGHT, - DANCE_NOTE_PAD2_LEFT, - DANCE_NOTE_PAD2_UPLEFT, - DANCE_NOTE_PAD2_DOWN, - DANCE_NOTE_PAD2_UP, - DANCE_NOTE_PAD2_UPRIGHT, - DANCE_NOTE_PAD2_RIGHT -}; - Notes::Notes() @@ -64,515 +47,6 @@ Notes::~Notes() { } - - -// BMS encoding: tap-hold -// 4&8panel: Player1 Player2 -// Left 11-51 21-61 -// Down 13-53 23-63 -// Up 15-55 25-65 -// Right 16-56 26-66 -// 6panel: Player1 -// Left 11-51 -// Left+Up 12-52 -// Down 13-53 -// Up 14-54 -// Up+Right 15-55 -// Right 16-56 -// -// Notice that 15 and 25 have double meanings! What were they thinking??? -// While reading in, use the 6 panel mapping. After reading in, detect if only 4 notes -// are used. If so, shift the Up+Right column back to the Up column -// -void mapBMSTrackToDanceNote( int iBMSTrack, int &iDanceColOut, char &cNoteCharOut ) -{ - if( iBMSTrack > 40 ) - { - cNoteCharOut = '2'; - iBMSTrack -= 40; - } - else - { - cNoteCharOut = '1'; - } - - switch( iBMSTrack ) - { - case 11: iDanceColOut = DANCE_NOTE_PAD1_LEFT; break; - case 12: iDanceColOut = DANCE_NOTE_PAD1_UPLEFT; break; - case 13: iDanceColOut = DANCE_NOTE_PAD1_DOWN; break; - case 14: iDanceColOut = DANCE_NOTE_PAD1_UP; break; - case 15: iDanceColOut = DANCE_NOTE_PAD1_UPRIGHT; break; - case 16: iDanceColOut = DANCE_NOTE_PAD1_RIGHT; break; - case 21: iDanceColOut = DANCE_NOTE_PAD2_LEFT; break; - case 22: iDanceColOut = DANCE_NOTE_PAD2_UPLEFT; break; - case 23: iDanceColOut = DANCE_NOTE_PAD2_DOWN; break; - case 24: iDanceColOut = DANCE_NOTE_PAD2_UP; break; - case 25: iDanceColOut = DANCE_NOTE_PAD2_UPRIGHT; break; - case 26: iDanceColOut = DANCE_NOTE_PAD2_RIGHT; break; - default: iDanceColOut = -1; break; - } -} - - -bool Notes::LoadFromBMSFile( const CString &sPath ) -{ - LOG->Trace( "Notes::LoadFromBMSFile( '%s' )", sPath ); - - NoteData* pNoteData = new NoteData; - pNoteData->m_iNumTracks = MAX_NOTE_TRACKS; - - CStdioFile file; - if( !file.Open( sPath, CFile::modeRead|CFile::shareDenyNone ) ) - { - throw RageException( "Failed to open %s.", sPath ); - 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(); - - if( -1 != value_name.Find("#player") ) - { - switch( atoi(value_data) ) - { - case 1: // 4 or 6 single - m_NotesType = NOTES_TYPE_DANCE_SINGLE; - // if the mode should be solo, then we'll update m_DanceStyle below when we read in step data - break; - case 2: // couple/battle - m_NotesType = NOTES_TYPE_DANCE_COUPLE; - break; - case 3: // double - m_NotesType = NOTES_TYPE_DANCE_DOUBLE; - break; - } - } - if( -1 != value_name.Find("#title") ) - { - m_sDescription = value_data; - - // extract the Notes description (looks like 'Music ') - int iPosOpenBracket = m_sDescription.Find( "<" ); - if( iPosOpenBracket == -1 ) - iPosOpenBracket = m_sDescription.Find( "(" ); - int iPosCloseBracket = m_sDescription.Find( ">" ); - if( iPosCloseBracket == -1 ) - iPosCloseBracket = m_sDescription.Find( ")" ); - - if( iPosOpenBracket != -1 && iPosCloseBracket != -1 ) - m_sDescription = m_sDescription.Mid( iPosOpenBracket+1, iPosCloseBracket-iPosOpenBracket-1 ); - m_sDescription.MakeLower(); - LOG->Trace( "Notes description found to be '%s'", m_sDescription ); - - // if there's a 6 in the description, it's probably part of "6panel" or "6-panel" - if( m_sDescription.Find("6") != -1 ) - m_NotesType = NOTES_TYPE_DANCE_SOLO; - - } - if( -1 != value_name.Find("#playlevel") ) - { - m_iMeter = atoi( 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; iTrace( "%s:%s: iMeasureNo = %d, iNoteNum = %d, iNumNotesInThisMeasure = %d", - // valuename, sNoteData, iMeasureNo, iNoteNum, iNumNotesInThisMeasure ); - for( int j=0; jm_TapNotes[iColumnNumber][iNoteIndex] = cNoteChar; - } - } - } - } - - if( m_NotesType == NOTES_TYPE_DANCE_SINGLE || - m_NotesType == NOTES_TYPE_DANCE_DOUBLE || - m_NotesType == NOTES_TYPE_DANCE_COUPLE ) // if there are 4 panels, then the Up+Right track really contains the notes for Up - { - for( int i=0; im_TapNotes[DANCE_NOTE_PAD1_UP], - pNoteData->m_TapNotes[DANCE_NOTE_PAD1_UPRIGHT], - MAX_TAP_NOTE_ROWS*sizeof(pNoteData->m_TapNotes[0][0]) - ); - memcpy( - pNoteData->m_TapNotes[DANCE_NOTE_PAD2_UP], - pNoteData->m_TapNotes[DANCE_NOTE_PAD2_UPRIGHT], - MAX_TAP_NOTE_ROWS*sizeof(pNoteData->m_TapNotes[0][0]) - ); - } - } - - // we're done reading in all of the BMS values - int iNumNewTracks = GameManager::NotesTypeToNumTracks( m_NotesType ); - int iTransformNewToOld[MAX_NOTE_TRACKS]; - - switch( m_NotesType ) - { - case NOTES_TYPE_DANCE_SINGLE: - iTransformNewToOld[0] = DANCE_NOTE_PAD1_LEFT; - iTransformNewToOld[1] = DANCE_NOTE_PAD1_DOWN; - iTransformNewToOld[2] = DANCE_NOTE_PAD1_UP; - iTransformNewToOld[3] = DANCE_NOTE_PAD1_RIGHT; - break; - case NOTES_TYPE_DANCE_DOUBLE: - case NOTES_TYPE_DANCE_COUPLE: - iTransformNewToOld[0] = DANCE_NOTE_PAD1_LEFT; - iTransformNewToOld[1] = DANCE_NOTE_PAD1_DOWN; - iTransformNewToOld[2] = DANCE_NOTE_PAD1_UP; - iTransformNewToOld[3] = DANCE_NOTE_PAD1_RIGHT; - iTransformNewToOld[4] = DANCE_NOTE_PAD2_LEFT; - iTransformNewToOld[5] = DANCE_NOTE_PAD2_DOWN; - iTransformNewToOld[6] = DANCE_NOTE_PAD2_UP; - iTransformNewToOld[7] = DANCE_NOTE_PAD2_RIGHT; - break; - case NOTES_TYPE_DANCE_SOLO: - iTransformNewToOld[0] = DANCE_NOTE_PAD1_LEFT; - iTransformNewToOld[1] = DANCE_NOTE_PAD1_UPLEFT; - iTransformNewToOld[2] = DANCE_NOTE_PAD1_DOWN; - iTransformNewToOld[3] = DANCE_NOTE_PAD1_UP; - iTransformNewToOld[4] = DANCE_NOTE_PAD1_UPRIGHT; - iTransformNewToOld[5] = DANCE_NOTE_PAD1_RIGHT; - break; - default: - throw RageException( "Invalid NotesType." ); - } - - NoteData* pNoteData2 = new NoteData; - pNoteData2->m_iNumTracks = iNumNewTracks; - pNoteData2->LoadTransformed( pNoteData, iNumNewTracks, iTransformNewToOld ); - - m_sSMNoteData = pNoteData2->GetSMNoteDataString(); - - delete pNoteData; - delete pNoteData2; - - TidyUpData(); - - return true; -} - - - - -void DWIcharToNote( char c, GameController i, DanceNote ¬e1Out, DanceNote ¬e2Out ) -{ - switch( c ) - { - case '0': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; - case '1': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_LEFT; break; - case '2': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_NONE; break; - case '3': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case '4': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_NONE; break; - case '5': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; - case '6': note1Out = DANCE_NOTE_PAD1_RIGHT; note2Out = DANCE_NOTE_NONE; break; - case '7': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_LEFT; break; - case '8': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_NONE; break; - case '9': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'A': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_DOWN; break; - case 'B': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'C': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_NONE; break; - case 'D': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_NONE; break; - case 'E': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPLEFT; break; - case 'F': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_DOWN; break; - case 'G': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UP; break; - case 'H': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'I': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - case 'J': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - case 'K': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - case 'L': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; - case 'M': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; - default: - LOG->Warn( "Encountered invalid DWI note characer '%c'", c ); - note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; - } - - switch( i ) - { - case GAME_CONTROLLER_1: - break; - case GAME_CONTROLLER_2: - if( note1Out != DANCE_NOTE_NONE ) - note1Out += 6; - if( note2Out != DANCE_NOTE_NONE ) - note2Out += 6; - break; - default: - ASSERT( false ); - } -} - - - -bool Notes::LoadFromDWITokens( - CString sMode, - CString sDescription, - CString sNumFeet, - CString sStepData1, - CString sStepData2 ) -{ - LOG->Trace( "Notes::LoadFromDWITokens()" ); - - sStepData1.Replace( "\n", "" ); - sStepData1.Replace( " ", "" ); - sStepData2.Replace( "\n", "" ); - sStepData2.Replace( " ", "" ); - - if( sMode == "SINGLE" ) m_NotesType = NOTES_TYPE_DANCE_SINGLE; - else if( sMode == "DOUBLE" ) m_NotesType = NOTES_TYPE_DANCE_DOUBLE; - else if( sMode == "COUPLE" ) m_NotesType = NOTES_TYPE_DANCE_COUPLE; - else if( sMode == "SOLO" ) m_NotesType = NOTES_TYPE_DANCE_SOLO; - else - { - ASSERT(0); // Unrecognized DWI notes format - m_NotesType = NOTES_TYPE_DANCE_SINGLE; - } - - - CMap mapDanceNoteToNoteDataColumn; - switch( m_NotesType ) - { - case NOTES_TYPE_DANCE_SINGLE: - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; - break; - case NOTES_TYPE_DANCE_DOUBLE: - case NOTES_TYPE_DANCE_COUPLE: - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; - break; - case NOTES_TYPE_DANCE_SOLO: - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; - mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; - break; - default: - ASSERT(0); - } - - m_sDescription = sDescription; - - m_iMeter = atoi( sNumFeet ); - - //m_DifficultyClass = DifficultyClassFromDescriptionAndMeter( m_sDescription, m_iMeter ); - - NoteData* pNoteData = new NoteData; - ASSERT( pNoteData ); - pNoteData->m_iNumTracks = mapDanceNoteToNoteDataColumn.GetCount(); - - for( int pad=0; pad<2; pad++ ) // foreach pad - { - CString sStepData; - switch( pad ) - { - case 0: - sStepData = sStepData1; - break; - case 1: - if( sStepData2 == "" ) // no data - continue; // skip - sStepData = sStepData2; - break; - default: - ASSERT( false ); - } - - double fCurrentBeat = 0; - double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; - - for( int i=0; i': - fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; - break; - - case ' ': - break; // do nothing! - - case '!': // hold start - { - // rewind and get the last step we inserted - double fLastStepBeat = fCurrentBeat - fCurrentIncrementer; - int iIndex = BeatToNoteRow( (float)fLastStepBeat ); - - char holdChar = sStepData[i++]; - - DanceNote note1, note2; - DWIcharToNote( holdChar, (GameController)pad, note1, note2 ); - - if( note1 != DANCE_NOTE_NONE ) - { - int iCol1 = mapDanceNoteToNoteDataColumn[note1]; - pNoteData->m_TapNotes[iCol1][iIndex] = '2'; - } - if( note2 != DANCE_NOTE_NONE ) - { - int iCol2 = mapDanceNoteToNoteDataColumn[note2]; - pNoteData->m_TapNotes[iCol2][iIndex] = '2'; - } - } - break; - default: // this is a note character - { - int iIndex = BeatToNoteRow( (float)fCurrentBeat ); - - DanceNote note1, note2; - DWIcharToNote( c, (GameController)pad, note1, note2 ); - - if( note1 != DANCE_NOTE_NONE ) - { - int iCol1 = mapDanceNoteToNoteDataColumn[note1]; - pNoteData->m_TapNotes[iCol1][iIndex] = '1'; - } - if( note2 != DANCE_NOTE_NONE ) - { - int iCol2 = mapDanceNoteToNoteDataColumn[note2]; - pNoteData->m_TapNotes[iCol2][iIndex] = '1'; - } - - fCurrentBeat += fCurrentIncrementer; - } - break; - } - } - } - - // this will expand the HoldNote begin markers we wrote into actual HoldNotes - pNoteData->Convert2sAnd3sToHoldNotes(); - - ASSERT( pNoteData->m_iNumTracks > 0 ); - - m_sSMNoteData = pNoteData->GetSMNoteDataString(); - - delete pNoteData; - - TidyUpData(); - - return true; -} - -void Notes::LoadFromSMTokens( - CString sNotesType, - CString sDescription, - CString sDifficultyClass, - CString sMeter, - CString sRadarValues, - CString sNoteData -) -{ - sNotesType.TrimLeft(); - sNotesType.TrimRight(); - sDescription.TrimLeft(); - sDescription.TrimRight(); - sDifficultyClass.TrimLeft(); - sDifficultyClass.TrimRight(); - - -// LOG->Trace( "Notes::LoadFromSMTokens()" ); - - m_NotesType = GameManager::StringToNotesType(sNotesType); - m_sDescription = sDescription; - m_DifficultyClass = StringToDifficultyClass( sDifficultyClass ); - m_iMeter = atoi(sMeter); - CStringArray saValues; - split( sRadarValues, ",", saValues, true ); - if( saValues.GetSize() == NUM_RADAR_VALUES ) - for( int r=0; rTrace( "Notes::LoadFromKSFFile( '%s' )", sPath ); - - MsdFile msd; - bool bResult = msd.ReadFile( sPath ); - if( !bResult ) - throw RageException( "Error opening file '%s'.", sPath ); - - int iTickCount = -1; // this is the value we read for TICKCOUNT - CString iStep; // this is the value we read for STEP - - for( int i=0; iWarn( "%s:\nTICKCOUNT not found; defaulting to %i", sPath, iTickCount ); - } - - NoteData notedata; // read it into here - - CStringArray asRows; - iStep.TrimLeft(); - split( iStep, "\n", asRows, true ); - - int iHoldStartRow[13]; - for( int t=0; t<13; t++ ) - iHoldStartRow[t] = -1; - - for( int r=0; rm_PlayMode ) { case PLAY_MODE_ARCADE: - GAMESTATE->m_pCurSong->LoadFromSMFile( GAMESTATE->m_pCurSong->GetCacheFilePath() ); + ld.LoadFromSMFile(GAMESTATE->m_pCurSong->GetCacheFilePath(), + *GAMESTATE->m_pCurSong); break; case PLAY_MODE_ONI: case PLAY_MODE_ENDLESS: @@ -935,7 +938,7 @@ void DontSaveChanges() for( int i=0; im_pCurCourse->m_iStages; i++ ) { Song* pSong = GAMESTATE->m_pCurCourse->m_apSongs[i]; - pSong->LoadFromSMFile( GAMESTATE->m_pCurSong->GetCacheFilePath() ); + ld.LoadFromSMFile( GAMESTATE->m_pCurSong->GetCacheFilePath(), *pSong ); } } break; diff --git a/stepmania/src/Song.cpp b/stepmania/src/Song.cpp index e6364a1c97..cc94edf92a 100644 --- a/stepmania/src/Song.cpp +++ b/stepmania/src/Song.cpp @@ -24,6 +24,10 @@ #include "GameManager.h" #include "PrefsManager.h" +#include "NotesLoaderSM.h" +#include "NotesLoaderDWI.h" +#include "NotesLoaderBMS.h" +#include "NotesLoaderKSF.h" const int FILE_CACHE_VERSION = 65; // increment this when Song or Notes changes to invalidate cache @@ -299,16 +303,29 @@ bool Song::LoadWithoutCache( CString sDir ) if( iNumSMFiles > 1 ) throw RageException( "There is more than one SM file in '%s'. There should be only one!", sDir ); - else if( iNumDWIFiles > 1 ) + if( iNumDWIFiles > 1 ) throw RageException( "There is more than one DWI file in '%s'. There should be only one!", sDir ); - else if( iNumSMFiles == 1 ) - LoadFromSMFile( sDir + arraySMFileNames[0] ); // do load note data + + if( iNumSMFiles == 1 ) + { + SMLoader ld; + ld.LoadFromSMFile( sDir + arraySMFileNames[0], *this ); + } else if( iNumDWIFiles == 1 ) - LoadFromDWIFile( sDir + arrayDWIFileNames[0] ); + { + DWILoader ld; + ld.LoadFromDWIFile( sDir + arrayDWIFileNames[0], *this ); + } else if( iNumBMSFiles > 0 ) - LoadFromBMSDir( sDir ); + { + BMSLoader ld; + ld.LoadFromBMSDir( sDir, *this ); + } else if( iNumKSFFiles > 0 ) - LoadFromKSFDir( sDir ); + { + KSFLoader ld; + ld.LoadFromKSFDir( sDir, *this ); + } else { LOG->Warn( "Couldn't find any SM, DWI, BMS, or KSF files in '%s'. This is not a valid song directory.", sDir ); @@ -348,7 +365,8 @@ bool Song::LoadFromSongDir( CString sDir ) DoesFileExist(GetCacheFilePath())) { LOG->Trace( "Loading '%s' from cache file '%s'.", m_sSongDir, GetCacheFilePath() ); - LoadFromSMFile( GetCacheFilePath() ); + SMLoader ld; + ld.LoadFromSMFile( GetCacheFilePath(), *this ); } else { @@ -372,700 +390,6 @@ bool Song::LoadFromSongDir( CString sDir ) } - -bool Song::LoadFromBMSDir( CString sDir ) -{ - LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir ); - - CStringArray arrayBMSFileNames; - GetDirListing( sDir + CString("*.bms"), arrayBMSFileNames ); - - if( arrayBMSFileNames.GetSize() == 0 ) - throw RageException( "Couldn't find any BMS files in '%s'", sDir ); - - // load the Notes from the rest of the BMS files - for( int i=0; iLoadFromBMSFile( m_sSongDir + arrayBMSFileNames[i] ); - m_apNotes.Add( pNewNotes ); - } - - CString sPath = m_sSongDir + arrayBMSFileNames[0]; - - CStdioFile file; - if( !file.Open( sPath, CFile::modeRead|CFile::shareDenyNone ) ) - { - throw RageException( "Failed to open %s.", sPath ); - 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(); - - - // handle the data - if( value_name == "#title" ) - { - // strip Notes type out of description leaving only song title - looks like 'B4U ' - int iIndex = value_data.ReverseFind('<'); - if( iIndex == -1 ) - iIndex = value_data.ReverseFind('('); - if( iIndex != -1 ) - { - value_data = value_data.Left( iIndex ); - GetMainAndSubTitlesFromFullTitle( value_data, m_sMainTitle, m_sSubTitle ); - } - else - m_sMainTitle = value_data; - } - else if( value_name == "#artist" ) - { - m_sArtist = value_data; - } - else if( value_name == "#bpm" ) - { - BPMSegment newSeg( 0, (float)atof(value_data) ); - AddBPMSegment( newSeg ); - - LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM ); - } - else if( value_name == "#backbmp" ) - { - m_sBackgroundFile = value_data; - } - else if( value_name == "#wav" ) - { - m_sMusicFile = 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 iBMSTrackNo = atoi( value_name.Mid(4,2) ); - - CString sNoteData = value_data; - CArray arrayNotes; - - for( int i=0; iTrace( "%s:%s: iMeasureNo = %d, iBMSTrackNo = %d, iNumNotesInThisMeasure = %d", - // valuename, sNoteData, iMeasureNo, iBMSTrackNo, iNumNotesInThisMeasure ); - for( int j=0; j 10 ) // some BPMs's play the music again at the end. Why? Who knows... - break; - float fBPS; - fBPS = m_BPMSegments[0].m_fBPM/60.0f; - m_fBeat0OffsetInSeconds = fBeatOffset / fBPS; - //LOG->Trace( "Found offset to be index %d, beat %f", iStepIndex, NoteRowToBeat(iStepIndex) ); - } - break; - case 3: // bpm change - { - BPMSegment newSeg( NoteRowToBeat(iStepIndex), (float)arrayNotes[j] ); - AddBPMSegment( newSeg ); - LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM ); - } - break; - - // 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( sPath, CFile::modeRead|CFile::shareDenyNone ) ) - { - throw RageException( "Failed to open %s.", sPath ); - 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( 0==stricmp(value_name, sTagToLookFor) ) - { - fBPM = (float)atof( value_data ); - break; - } - } - - if( fBPM == -1 ) // we didn't find the line we were looking for - { - LOG->Trace( "WARNING: Couldn't find tag '%s' in '%s'.", sTagToLookFor, sPath ); - } - else - { - BPMSegment newSeg( NoteRowToBeat(iStepIndex), fBPM ); - AddBPMSegment( newSeg ); - LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM ); - } - - file.Close(); - } - break; - case 9: // stop - { - // 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( sPath, CFile::modeRead|CFile::shareDenyNone ) ) - throw RageException( "Failed to open %s.", sPath ); - - 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( 0==stricmp(value_name, sTagToLookFor) ) - { - // find the BPM at the time of this freeze - float fBPM = -1; - for( int i=0; i 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->Trace( "WARNING: Couldn't find tag '%s' in '%s'.", sTagToLookFor, sPath ); - } - else - { - StopSegment newSeg( fFreezeStartBeat, fFreezeSecs ); - AddStopSegment( newSeg ); - LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds ); - } - - file.Close(); - } - break; - } - } - } - } - - for( i=0; iTrace( "There is a BPM change at beat %f, BPM %f, index %d", - m_BPMSegments[i].m_fStartBeat, m_BPMSegments[i].m_fBPM, i ); - - file.Close(); - - return TRUE; -} - - -bool Song::LoadFromDWIFile( CString sPath ) -{ - LOG->Trace( "Song::LoadFromDWIFile(%s)", sPath ); - - - MsdFile msd; - bool bResult = msd.ReadFile( sPath ); - if( !bResult ) - throw RageException( "Error opening file '%s'.", sPath ); - - for( int i=0; iLoadFromDWITokens( - sParams[0], - sParams[1], - sParams[2], - sParams[3], - (iNumParams==5) ? sParams[4] : "" - ); - m_apNotes.Add( pNewNotes ); - } - else - // do nothing. We don't care about this value name - ; - } - - return TRUE; -} - - -bool Song::LoadFromSMFile( CString sPath ) -{ - LOG->Trace( "Song::LoadFromSMDir(%s)", sPath ); - - m_BPMSegments.RemoveAll(); - m_StopSegments.RemoveAll(); - - int i; - - MsdFile msd; - bool bResult = msd.ReadFile( sPath ); - if( !bResult ) - throw RageException( "Error opening file '%s'.", sPath ); - - for( i=0; iWarn( "The song file '%s' has an unknown #SELECTABLE value, \"%s\"; ignored.", sPath, sParams[1]); - } - - else if( 0==stricmp(sValueName,"STOPS") || 0==stricmp(sValueName,"FREEZES") ) - { - CStringArray arrayFreezeExpressions; - split( sParams[1], ",", arrayFreezeExpressions ); - - for( int f=0; fLoadFromSMTokens( - sParams[1], - sParams[2], - sParams[3], - sParams[4], - sParams[5], - sParams[6] - ); - } - - else - LOG->Trace( "Unexpected value named '%s'", sValueName ); - } - - return TRUE; -} - - -bool Song::LoadFromKSFDir( CString sDir ) -{ - LOG->Trace( "Song::LoadFromKSFDir(%s)", sDir ); - - CStringArray arrayKSFFileNames; - GetDirListing( sDir + CString("*.ksf"), arrayKSFFileNames ); - - if( arrayKSFFileNames.GetSize() == 0 ) - throw RageException( "Couldn't find any KSF files in '%s'", sDir ); - - // load the Notes from the rest of the KSF files - for( int i=0; iLoadFromKSFFile( m_sSongDir + arrayKSFFileNames[i] ); - m_apNotes.Add( pNewNotes ); - } - - CString sPath = m_sSongDir + arrayKSFFileNames[0]; - - MsdFile msd; - bool bResult = msd.ReadFile( sPath ); - if( !bResult ) - throw RageException( "Error opening file '%s'.", sPath ); - - for( i=0; iTrace( "Unexpected value named '%s'", sValueName ); - } - - // search for music with song in the file name - CStringArray arrayPossibleMusic; - GetDirListing( m_sSongDir + CString("song.mp3"), arrayPossibleMusic ); - GetDirListing( m_sSongDir + CString("song.ogg"), arrayPossibleMusic ); - GetDirListing( m_sSongDir + CString("song.wav"), arrayPossibleMusic ); - - if( arrayPossibleMusic.GetSize() > 0 ) // we found a match - m_sMusicFile = arrayPossibleMusic[0]; - - return TRUE; -} - - void Song::TidyUpData() { m_sMainTitle.TrimRight(); diff --git a/stepmania/src/song.h b/stepmania/src/song.h index bcf89399ac..990622954e 100644 --- a/stepmania/src/song.h +++ b/stepmania/src/song.h @@ -45,23 +45,19 @@ struct BackgroundChange class Song { +public: /* Set when this song should be displayed in the music wheel: */ enum { SHOW_ALWAYS, /* all the time */ SHOW_ROULETTE, /* only when rouletting */ SHOW_NEVER } /* never (unless song hiding is turned off) */ m_SelectionDisplay; -public: + Song(); ~Song(); bool LoadWithoutCache( CString sDir ); - bool LoadFromSongDir( CString sDir ); // calls one of the loads below - - bool LoadFromDWIFile( CString sPath ); - bool LoadFromBMSDir( CString sDir ); - bool LoadFromSMFile( CString sPath ); - bool LoadFromKSFDir( CString sDir ); + bool LoadFromSongDir( CString sDir ); void TidyUpData(); // call after loading to clean up invalid data