#include "global.h" #include "NotesLoaderBMS.h" #include "NoteData.h" #include "GameConstantsAndTypes.h" #include "RageLog.h" #include "GameManager.h" #include "RageException.h" #include "RageFile.h" #include "StepsUtil.h" #include "song.h" #include "Steps.h" #include "RageUtil_CharConversions.h" /* 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 Hey, folks, BMSes are used for things BESIDES DDR steps, and so we're borking up BMSes that are for pnm/bm/etc. pnm-nine: 11-15,22-25 pnm-five: 13-15,21-22 bm-single5: 11-16 bm-double5: 11-16,21-26 bm-single7: 11-16,18-19 bm-double7: 11-16,18-19,21-26,28-29 So the magics for these are: pnm-nine: nothing >5, with 12, 14, 22 and/or 24 pnm-five: nothing >5, with 14 and/or 22 bm-*: can't tell difference between bm-single and dance-solo 18/19 marks bm-single7, 28/29 marks bm-double7 bm-double uses 21-26. */ enum BmsTrack { BMS_P1_KEY1 = 0, BMS_P1_KEY2, BMS_P1_KEY3, BMS_P1_KEY4, BMS_P1_KEY5, BMS_P1_TURN, BMS_P1_KEY6, BMS_P1_KEY7, BMS_P2_KEY1, BMS_P2_KEY2, BMS_P2_KEY3, BMS_P2_KEY4, BMS_P2_KEY5, BMS_P2_TURN, BMS_P2_KEY6, BMS_P2_KEY7, // max 4 simultaneous auto keysounds BMS_AUTO_KEYSOUND_1, BMS_AUTO_KEYSOUND_2, BMS_AUTO_KEYSOUND_3, BMS_AUTO_KEYSOUND_4, BMS_AUTO_KEYSOUND_5, BMS_AUTO_KEYSOUND_6, BMS_AUTO_KEYSOUND_7, BMS_AUTO_KEYSOUND_LAST, NUM_BMS_TRACKS, }; const int NUM_NON_AUTO_KEYSOUND_TRACKS = BMS_AUTO_KEYSOUND_1; const int NUM_AUTO_KEYSOUND_TRACKS = NUM_BMS_TRACKS - NUM_NON_AUTO_KEYSOUND_TRACKS; static bool ConvertRawTrackToTapNote( int iRawTrack, BmsTrack &bmsTrackOut, bool &bIsHoldOut ) { if( iRawTrack > 40 ) { bIsHoldOut = true; iRawTrack -= 40; } else { bIsHoldOut = false; } switch( iRawTrack ) { case 1: bmsTrackOut = BMS_AUTO_KEYSOUND_1; break; case 11: bmsTrackOut = BMS_P1_KEY1; break; case 12: bmsTrackOut = BMS_P1_KEY2; break; case 13: bmsTrackOut = BMS_P1_KEY3; break; case 14: bmsTrackOut = BMS_P1_KEY4; break; case 15: bmsTrackOut = BMS_P1_KEY5; break; case 16: bmsTrackOut = BMS_P1_TURN; break; case 18: bmsTrackOut = BMS_P1_KEY6; break; case 19: bmsTrackOut = BMS_P1_KEY7; break; case 21: bmsTrackOut = BMS_P2_KEY1; break; case 22: bmsTrackOut = BMS_P2_KEY2; break; case 23: bmsTrackOut = BMS_P2_KEY3; break; case 24: bmsTrackOut = BMS_P2_KEY4; break; case 25: bmsTrackOut = BMS_P2_KEY5; break; case 26: bmsTrackOut = BMS_P2_TURN; break; case 28: bmsTrackOut = BMS_P2_KEY6; break; case 29: bmsTrackOut = BMS_P2_KEY7; break; default: // unknown track return false; } return true; } static StepsType DetermineStepsType( int iPlayer, const NoteData &nd ) { ASSERT( NUM_BMS_TRACKS == nd.GetNumTracks() ); bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS]; ZERO( bTrackHasNote ); int iLastRow = nd.GetLastRow(); for( int t=0; tWarn( "Invalid #PLAYER value %d", iPlayer ); return STEPS_TYPE_INVALID; } } float BMSLoader::GetBeatsPerMeasure( const MeasureToTimeSig_t &sigs, int iMeasure ) { map::const_iterator time_sig = sigs.find( iMeasure ); if( time_sig != sigs.end() ) return 4.0f * time_sig->second; return 4.0f; } int BMSLoader::GetMeasureStartRow( const MeasureToTimeSig_t &sigs, int iMeasureNo ) { int iRowNo = 0; for( int i = 0; i < iMeasureNo; ++i ) iRowNo += BeatToNoteRow( GetBeatsPerMeasure(sigs, i) ); return iRowNo; } void BMSLoader::SearchForDifficulty( CString sTag, Steps *pOut ) { sTag.ToLower(); /* Only match "Light" in parentheses. */ if( sTag.find( "(light" ) != sTag.npos ) pOut->SetDifficulty( DIFFICULTY_EASY ); else if( sTag.find( "another" ) != sTag.npos ) pOut->SetDifficulty( DIFFICULTY_HARD ); if( sTag.find( "(solo)" ) != sTag.npos ) { pOut->SetDescription( "Edit" ); pOut->SetDifficulty( DIFFICULTY_EDIT ); } LOG->Trace( "Tag \"%s\" is %s", sTag.c_str(), DifficultyToString(pOut->GetDifficulty()).c_str() ); } bool BMSLoader::LoadFromBMSFile( const CString &sPath, const NameToData_t &mapNameToData, Steps &out ) { LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() ); out.m_StepsType = STEPS_TYPE_INVALID; // BMS player code. Fill in below and use to determine StepsType. int iPlayer = -1; CString sData; if( GetTagFromMap( mapNameToData, "#player", sData ) ) iPlayer = atoi(sData); if( GetTagFromMap( mapNameToData, "#playlevel", sData ) ) out.SetMeter( atoi(sData) ); if( GetTagFromMap( mapNameToData, "#title", sData ) ) { sData.MakeLower(); // extract the Steps description (looks like 'Music ') int iPosOpenBracket = sData.Find( "<" ); if( iPosOpenBracket == -1 ) iPosOpenBracket = sData.Find( "(" ); int iPosCloseBracket = sData.Find( ">" ); if( iPosCloseBracket == -1 ) iPosCloseBracket = sData.Find( ")" ); if( iPosOpenBracket != -1 && iPosCloseBracket != -1 ) sData = sData.substr( iPosOpenBracket+1, iPosCloseBracket-iPosOpenBracket-1 ); LOG->Trace( "Steps description found to be '%s'", sData.c_str() ); out.SetDescription(sData); // if there's a 6 in the description, it's probably part of "6panel" or "6-panel" if( sData.Find("6") != -1 ) out.m_StepsType = STEPS_TYPE_DANCE_SOLO; } NoteData ndNotes; ndNotes.SetNumTracks( NUM_BMS_TRACKS ); /* Read time signatures. Note that these can differ across files in the same * song. */ MeasureToTimeSig_t mapMeasureToTimeSig; ReadTimeSigs( mapNameToData, mapMeasureToTimeSig ); NameToData_t::const_iterator it; for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it ) { const CString &sName = it->first; if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) ) continue; // this is step or offset data. Looks like "#00705" int iMeasureNo = atoi( sName.substr(1,3).c_str() ); int iRawTrackNum = atoi( sName.substr(4,2).c_str() ); int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo ); float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo ); const CString &sNoteData = it->second; vector vTapNotes; for( int i=0; i+1::const_iterator it = m_mapWavIdToKeysoundIndex.find(sNoteId); if( it != m_mapWavIdToKeysoundIndex.end() ) { tn.bKeysound = true; tn.iKeysoundIndex = it->second; } vTapNotes.push_back( tn ); } else { vTapNotes.push_back( TAP_EMPTY ); } } const unsigned uNumNotesInThisMeasure = vTapNotes.size(); for( unsigned j=0; j= BMS_AUTO_KEYSOUND_1 ) { bmsTrack = (BmsTrack)iLastEmptyTrack; } else { // no room for this note. Drop it. continue; } } else if( bIsHold ) { tn.type = TapNote::hold_head; } else { tn.type = TapNote::tap; } ndNotes.SetTapNote( bmsTrack, row, tn ); } } } } // we're done reading in all of the BMS values out.m_StepsType = DetermineStepsType( iPlayer, ndNotes ); if( out.m_StepsType == STEPS_TYPE_INVALID ) { LOG->Warn( "Couldn't determine note type of file '%s'", sPath.c_str() ); return false; } // shift all of the autokeysound tracks onto the main tracks for( int t=BMS_AUTO_KEYSOUND_1; tWarn( "No room to shift." ); } } } int iNumNewTracks = GameManager::StepsTypeToNumTracks( out.m_StepsType ); vector iTransformNewToOld; iTransformNewToOld.resize( iNumNewTracks, -1 ); switch( out.m_StepsType ) { case STEPS_TYPE_DANCE_SINGLE: iTransformNewToOld[0] = BMS_P1_KEY1; iTransformNewToOld[1] = BMS_P1_KEY3; iTransformNewToOld[2] = BMS_P1_KEY5; iTransformNewToOld[3] = BMS_P1_TURN; break; case STEPS_TYPE_DANCE_DOUBLE: case STEPS_TYPE_DANCE_COUPLE: iTransformNewToOld[0] = BMS_P1_KEY1; iTransformNewToOld[1] = BMS_P1_KEY3; iTransformNewToOld[2] = BMS_P1_KEY5; iTransformNewToOld[3] = BMS_P1_TURN; iTransformNewToOld[4] = BMS_P2_KEY1; iTransformNewToOld[5] = BMS_P2_KEY3; iTransformNewToOld[6] = BMS_P2_KEY5; iTransformNewToOld[7] = BMS_P2_TURN; break; case STEPS_TYPE_DANCE_SOLO: case STEPS_TYPE_BM_SINGLE5: // Hey! Why are these exactly the same? :-) iTransformNewToOld[0] = BMS_P1_KEY1; iTransformNewToOld[1] = BMS_P1_KEY2; iTransformNewToOld[2] = BMS_P1_KEY3; iTransformNewToOld[3] = BMS_P1_KEY4; iTransformNewToOld[4] = BMS_P1_KEY5; iTransformNewToOld[5] = BMS_P1_TURN; break; case STEPS_TYPE_PNM_FIVE: iTransformNewToOld[0] = BMS_P1_KEY3; iTransformNewToOld[1] = BMS_P1_KEY4; iTransformNewToOld[2] = BMS_P1_KEY5; // fix these columns! iTransformNewToOld[3] = BMS_P2_KEY2; iTransformNewToOld[4] = BMS_P2_KEY3; break; case STEPS_TYPE_PNM_NINE: iTransformNewToOld[0] = BMS_P1_KEY1; // lwhite iTransformNewToOld[1] = BMS_P1_KEY2; // lyellow iTransformNewToOld[2] = BMS_P1_KEY3; // lgreen iTransformNewToOld[3] = BMS_P1_KEY4; // lblue iTransformNewToOld[4] = BMS_P1_KEY5; // red // fix these columns! iTransformNewToOld[5] = BMS_P2_KEY2; // rblue iTransformNewToOld[6] = BMS_P2_KEY3; // rgreen iTransformNewToOld[7] = BMS_P2_KEY4; // ryellow iTransformNewToOld[8] = BMS_P2_KEY5; // rwhite break; case STEPS_TYPE_BM_DOUBLE5: iTransformNewToOld[0] = BMS_P1_KEY1; iTransformNewToOld[1] = BMS_P1_KEY2; iTransformNewToOld[2] = BMS_P1_KEY3; iTransformNewToOld[3] = BMS_P1_KEY4; iTransformNewToOld[4] = BMS_P1_KEY5; iTransformNewToOld[5] = BMS_P1_TURN; iTransformNewToOld[6] = BMS_P2_KEY1; iTransformNewToOld[7] = BMS_P2_KEY2; iTransformNewToOld[8] = BMS_P2_KEY3; iTransformNewToOld[9] = BMS_P2_KEY4; iTransformNewToOld[10] = BMS_P2_KEY5; iTransformNewToOld[11] = BMS_P2_TURN; break; case STEPS_TYPE_BM_SINGLE7: iTransformNewToOld[0] = BMS_P1_KEY1; iTransformNewToOld[1] = BMS_P1_KEY2; iTransformNewToOld[2] = BMS_P1_KEY3; iTransformNewToOld[3] = BMS_P1_KEY4; iTransformNewToOld[4] = BMS_P1_KEY5; iTransformNewToOld[5] = BMS_P1_KEY6; iTransformNewToOld[6] = BMS_P1_KEY7; iTransformNewToOld[7] = BMS_P1_TURN; break; case STEPS_TYPE_BM_DOUBLE7: iTransformNewToOld[0] = BMS_P1_KEY1; iTransformNewToOld[1] = BMS_P1_KEY2; iTransformNewToOld[2] = BMS_P1_KEY3; iTransformNewToOld[3] = BMS_P1_KEY4; iTransformNewToOld[4] = BMS_P1_KEY5; iTransformNewToOld[5] = BMS_P1_KEY6; iTransformNewToOld[6] = BMS_P1_KEY7; iTransformNewToOld[7] = BMS_P1_TURN; iTransformNewToOld[8] = BMS_P2_KEY1; iTransformNewToOld[9] = BMS_P2_KEY2; iTransformNewToOld[10] = BMS_P2_KEY3; iTransformNewToOld[11] = BMS_P2_KEY4; iTransformNewToOld[12] = BMS_P2_KEY5; iTransformNewToOld[13] = BMS_P2_KEY6; iTransformNewToOld[14] = BMS_P2_KEY7; iTransformNewToOld[15] = BMS_P2_TURN; break; default: ASSERT(0); } NoteData noteData2; noteData2.SetNumTracks( iNumNewTracks ); noteData2.LoadTransformed( ndNotes, iNumNewTracks, &*iTransformNewToOld.begin() ); out.SetNoteData( noteData2 ); out.TidyUpData(); return true; } void BMSLoader::GetApplicableFiles( CString sPath, CStringArray &out ) { GetDirListing( sPath + CString("*.bms"), out ); GetDirListing( sPath + CString("*.bme"), out ); } bool BMSLoader::ReadBMSFile( const CString &sPath, NameToData_t &mapNameToData ) { RageFile file; if( !file.Open(sPath) ) RageException::Throw( "Failed to open \"%s\" for reading: %s", sPath.c_str(), file.GetError().c_str() ); while( !file.AtEOF() ) { CString line; if( file.GetLine( line ) == -1 ) { LOG->Warn( "Error reading \"%s\": %s", sPath.c_str(), file.GetError().c_str() ); return false; } StripCrnl(line); // BMS value names can be separated by a space or a colon. size_t iIndexOfSeparator = line.find_first_of( ": " ); CString value_name = line.substr( 0, iIndexOfSeparator ); CString value_data; if( iIndexOfSeparator != line.npos ) value_data = line.substr( iIndexOfSeparator+1 ); value_name.MakeLower(); mapNameToData.insert( pair(value_name, value_data) ); } return true; } bool BMSLoader::GetTagFromMap( const NameToData_t &mapNameToData, const CString &sName, CString &sOut ) { NameToData_t::const_iterator it; it = mapNameToData.find( sName ); if( it == mapNameToData.end() ) return false; sOut = it->second; return true; } enum { BMS_TRACK_TIME_SIG = 2, BMS_TRACK_BPM = 3, BMS_TRACK_BPM_REF = 8, BMS_TRACK_STOP = 9 }; void BMSLoader::ReadGlobalTags( const NameToData_t &mapNameToData, Song &out ) { CString sData; if( GetTagFromMap( mapNameToData, "#title", sData ) ) { // strip Steps type out of description leaving only song title - looks like 'B4U ' size_t iIndex = sData.find_last_of('<'); if( iIndex == sData.npos ) iIndex = sData.find_last_of('('); if( iIndex != sData.npos ) { sData = sData.Left( iIndex ); GetMainAndSubTitlesFromFullTitle( sData, out.m_sMainTitle, out.m_sSubTitle ); } else out.m_sMainTitle = sData; } GetTagFromMap( mapNameToData, "#artist", out.m_sArtist ); GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile ); GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile ); if( GetTagFromMap( mapNameToData, "#bpm", sData ) ) { BPMSegment newSeg( 0, strtof(sData, NULL) ); out.AddBPMSegment( newSeg ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM ); } NameToData_t::const_iterator it; for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it ) { const CString &sName = it->first; if( sName.size() != 6 || sName.Left(4) != "#wav" ) continue; // this is keysound file name. Looks like "#WAV1A" CString sData = it->second; CString sWavID = sName.Right(2); /* Due to bugs in some programs, many BMS files have a "WAV" extension * on files in the BMS for files that actually have some other extension. * Do a search. Don't do a wildcard search; if sData is "song.wav", * we might also have "song.png", which we shouldn't match. */ if( !IsAFile(out.GetSongDir()+sData) ) { const char *exts[] = { "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere for( unsigned i = 0; exts[i] != NULL; ++i ) { CString fn = SetExtension( sData, exts[i] ); if( IsAFile(out.GetSongDir()+fn) ) { sData = fn; break; } } } if( !IsAFile(out.GetSongDir()+sData) ) LOG->Trace( "Song \"%s\" references key \"%s\" that can't be found", m_sDir.c_str(), sData.c_str() ); sWavID.MakeUpper(); // HACK: undo the MakeLower() out.m_vsKeysoundFile.push_back( sData ); m_mapWavIdToKeysoundIndex[ sWavID ] = out.m_vsKeysoundFile.size()-1; LOG->Trace( "Inserting keysound index %lu '%s'", out.m_vsKeysoundFile.size()-1, sWavID.c_str() ); } /* Time signature tags affect all other global timing tags, so read them first. */ MeasureToTimeSig_t mapMeasureToTimeSig; ReadTimeSigs( mapNameToData, mapMeasureToTimeSig ); for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it ) { const CString &sName = it->first; if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) ) continue; // this is step or offset data. Looks like "#00705" int iMeasureNo = atoi( sName.substr(1,3).c_str() ); int iBMSTrackNo = atoi( sName.substr(4,2).c_str() ); int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo ); float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo ); int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure ); CString sData = it->second; int totalPairs = sData.size() / 2; for( int i = 0; i < totalPairs; ++i ) { CString sPair = sData.substr(i*2,2); int iVal = 0; if( sscanf( sPair, "%x", &iVal ) == 0 || iVal == 0 ) continue; int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs; float fBeat = NoteRowToBeat( iRow ); switch( iBMSTrackNo ) { case BMS_TRACK_BPM: out.SetBPMAtBeat( fBeat, (float) iVal ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal ); break; case BMS_TRACK_BPM_REF: { CString sTagToLookFor = ssprintf( "#bpm%02x", iVal ); CString sBPM; if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) ) { float fBPM = strtof( sBPM, NULL ); BPMSegment newSeg( fBeat, fBPM ); out.AddBPMSegment( newSeg ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM ); } else LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() ); break; } case BMS_TRACK_STOP: { CString sTagToLookFor = ssprintf( "#stop%02x", iVal ); CString sBeats; if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) ) { // find the BPM at the time of this freeze float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f; float fBeats = strtof(sBeats,NULL) / 48.0f; float fFreezeSecs = fBeats / fBPS; StopSegment newSeg( fBeat, fFreezeSecs ); out.AddStopSegment( newSeg ); LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds ); } else LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() ); break; } } } switch( iBMSTrackNo ) { case BMS_TRACK_BPM_REF: { // XXX: offset int iBPMNo; sscanf( sData, "%x", &iBPMNo ); // data is in hexadecimal CString sBPM; CString sTagToLookFor = ssprintf( "#bpm%02x", iBPMNo ); if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) ) { float fBPM = strtof( sBPM, NULL ); BPMSegment newSeg( NoteRowToBeat(iStepIndex), fBPM ); out.AddBPMSegment( newSeg ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM ); } else LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() ); break; } } } } void BMSLoader::ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t &out ) { NameToData_t::const_iterator it; for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it ) { const CString &sName = it->first; if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) ) continue; // this is step or offset data. Looks like "#00705" const CString &sData = it->second; int iMeasureNo = atoi( sName.substr(1,3).c_str() ); int iBMSTrackNo = atoi( sName.substr(4,2).c_str() ); if( iBMSTrackNo == BMS_TRACK_TIME_SIG ) out[iMeasureNo] = (float) atof(sData); } } bool BMSLoader::LoadFromDir( CString sDir, Song &out ) { LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() ); ASSERT( out.m_vsKeysoundFile.empty() ); m_sDir = sDir; CStringArray arrayBMSFileNames; GetApplicableFiles( sDir, arrayBMSFileNames ); /* We should have at least one; if we had none, we shouldn't have been * called to begin with. */ ASSERT( arrayBMSFileNames.size() ); /* Read all BMS files. */ vector aBMSData; for( unsigned i=0; i apSteps; for( unsigned i=0; iSetDifficulty( DIFFICULTY_MEDIUM ); CString sTag; if( GetTagFromMap( aBMSData[i], "#title", sTag ) ) SearchForDifficulty( sTag, pSteps ); if( GetTagFromMap( aBMSData[i], "#genre", sTag ) ) SearchForDifficulty( sTag, pSteps ); } /* Prefer to read global tags from a DIFFICULTY_MEDIUM file. These tend to * have the least cruft in the #TITLE tag, so it's more likely to get a clean * title. */ int iMainDataIndex = 0; for( unsigned i=1; iGetDifficulty() == DIFFICULTY_MEDIUM ) iMainDataIndex = i; ReadGlobalTags( aBMSData[iMainDataIndex], out ); // Now that we've parsed the keysound data, load the Steps from the rest // of the .bms files. for( unsigned i=0; i vSteps; p.GetSteps( vSteps, st, dc ); StepsUtil::SortNotesArrayByDifficulty( vSteps ); for( unsigned k=1; kSetDifficulty( dc2 ); } } } } /* * (c) 2001-2004 Chris Danford, Glenn Maynard * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */