#include "global.h" #include "NotesLoaderKSF.h" #include "RageException.h" #include "RageUtil_CharConversions.h" #include "MsdFile.h" #include "RageLog.h" #include "RageUtil.h" #include "NoteData.h" #include "NoteTypes.h" #if 0 void KSFLoader::RemoveHoles( NoteData &out, const Song &song ) { /* Start at the second BPM segment; the first one is already aligned. */ for( unsigned seg = 1; seg < song.m_BPMSegments.size(); ++seg ) { // const float FromBeat = song.m_BPMSegments[seg].m_fStartBeat; const float FromBeat = song.m_BPMSegments[seg].m_fStartBeat * song.m_BPMSegments[seg].m_fBPM / song.m_BPMSegments[0].m_fBPM; const int FromRow = (int) BeatToNoteRow(FromBeat); const int ToRow = (int) BeatToNoteRow(song.m_BPMSegments[seg].m_fStartBeat); LOG->Trace("from %f (%i) to (%i)", FromBeat, FromRow, ToRow); // const int ToRow = (int) roundf(FromRow * song.m_BPMSegments[0].m_fBPM / song.m_BPMSegments[seg].m_fBPM); // const int Rows = out.GetLastRow() - FromRow + 1; // int LastRow; // if(seg+1 < song.m_BPMSegments().size()) // LastRow = (int) NoteRowToBeat( song.m_BPMSegments[seg+1].m_fStartBeat ) - 1; // else // LastRow = out.GetLastRow(); NoteData tmp; tmp.SetNumTracks(out.GetNumTracks()); tmp.CopyRange( &out, FromRow, out.GetLastRow() ); out.ClearRange( FromRow, out.GetLastRow() ); out.CopyRange( &tmp, 0, tmp.GetLastRow(), ToRow ); } /* out.ConvertHoldNotesTo2sAnd3s(); for( t = 0; t < notedata.GetNumTracks(); ++t ) { const float CurBPM = song.GetBPMAtBeat( NoteRowToBeat(row) ); song.m_BPMSegments.size() for( int row = 0; row <= notedata.GetLastRow(); ++row ) { TapNote tn = notedata.GetTapNote(t, row); if( tn == TAP_EMPTY ) continue; const int RealRow = (int) roundf(row * OrigBPM / CurBPM); if( RealRow == row ) continue; LOG->Trace("from %i to %i", row, RealRow); notedata.SetTapNote( t, RealRow, tn ); notedata.SetTapNote( t, row, TAP_EMPTY ); } } out.Convert2sAnd3sToHoldNotes(); */ } #endif bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &song ) { LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); MsdFile msd; if( !msd.ReadFile( sPath ) ) RageException::Throw( "Error opening file '%s'.", sPath.c_str() ); int iTickCount = -1; // this is the value we read for TICKCOUNT CStringArray asRows; for( unsigned i=0; iWarn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount ); } NoteData notedata; // read it into here { CString sDir, sFName, sExt; splitrelpath( sPath, sDir, sFName, sExt ); sFName.MakeLower(); out.SetDescription(sFName); if( sFName.Find("crazy")!=-1 ) { out.SetDifficulty(DIFFICULTY_HARD); if(!out.GetMeter()) out.SetMeter(8); } else if( sFName.Find("hard")!=-1 ) { out.SetDifficulty(DIFFICULTY_MEDIUM); if(!out.GetMeter()) out.SetMeter(5); } else if( sFName.Find("easy")!=-1 ) { out.SetDifficulty(DIFFICULTY_EASY); if(!out.GetMeter()) out.SetMeter(2); } else { out.SetDifficulty(DIFFICULTY_MEDIUM); if(!out.GetMeter()) out.SetMeter(5); } notedata.SetNumTracks( 5 ); out.m_StepsType = STEPS_TYPE_PUMP_SINGLE; if( sFName.Find("double") != -1 ) { notedata.SetNumTracks( 10 ); out.m_StepsType = STEPS_TYPE_PUMP_DOUBLE; } else if( sFName.Find("_2") != -1 ) { notedata.SetNumTracks( 10 ); out.m_StepsType = STEPS_TYPE_PUMP_COUPLE; } else if( sFName.Find("halfdouble") != -1 ) { notedata.SetNumTracks( 6 ); out.m_StepsType = STEPS_TYPE_PUMP_HALFDOUBLE; } } int iHoldStartRow[13]; int t; for( t=0; t<13; t++ ) iHoldStartRow[t] = -1; for( unsigned r=0; rWarn("File %s had a RowString with an improper length (\"%s\"); corrupt notes ignored", sPath.c_str(), sRowString.c_str()); return false; } // the length of a note in a row depends on TICKCOUNT float fBeatThisRow = r/(float)iTickCount; int row = BeatToNoteRow(fBeatThisRow); for( int t=0; t < notedata.GetNumTracks(); t++ ) { if( sRowString[t] == '4' ) { /* Remember when each hold starts; ignore the middle. */ if( iHoldStartRow[t] == -1 ) iHoldStartRow[t] = r; continue; } if( iHoldStartRow[t] != -1 ) // this ends the hold { HoldNote hn ( t, /* button */ iHoldStartRow[t]/(float)iTickCount, /* start */ (r-1)/(float)iTickCount /* end */ ); notedata.AddHoldNote( hn ); iHoldStartRow[t] = -1; } TapNote tap; switch(sRowString[t]) { case '0': tap = TAP_EMPTY; break; case '1': tap = TAP_TAP; break; default: ASSERT(0); tap = TAP_EMPTY; break; } notedata.SetTapNote(t, row, tap); } } /* We need to remove holes where the BPM increases. */ // if( song.m_BPMSegments.size() > 1 ) // RemoveHoles( notedata, song ); out.SetNoteData(¬edata); out.TidyUpData(); return true; } void KSFLoader::GetApplicableFiles( CString sPath, CStringArray &out ) { GetDirListing( sPath + CString("*.ksf"), out ); } void KSFLoader::LoadTags( const CString &str, Song &out ) { /* str is either a #TITLE or a directory component. Fill in missing information. * str is either "title", "artist - title", or "artist - title - difficulty". */ CStringArray asBits; split( str, " - ", asBits, false ); /* Ignore the difficulty, since we get that elsewhere. */ if( asBits.size() == 3 && (!stricmp(asBits[2], "double") || !stricmp(asBits[2], "easy") || !stricmp(asBits[2], "normal") || !stricmp(asBits[2], "hard") || !stricmp(asBits[2], "crazy")) ) { asBits.erase(asBits.begin()+2, asBits.begin()+3); } CString title, artist; if( asBits.size() == 2 ) { artist = asBits[0]; title = asBits[1]; } else { title = asBits[0]; } /* Convert, if possible. Most KSFs are in Korean encodings (CP942/EUC-KR). */ if( !ConvertString( title, "korean" ) ) title = ""; if( !ConvertString( artist, "korean" ) ) artist = ""; if( out.m_sMainTitle == "" ) out.m_sMainTitle = title; if( out.m_sArtist == "" ) out.m_sArtist = artist; } bool KSFLoader::LoadGlobalData( const CString &sPath, Song &out ) { MsdFile msd; bool bResult = msd.ReadFile( sPath ); if( !bResult ) RageException::Throw( "Error opening file '%s'.", sPath.c_str() ); float BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1;; for( unsigned i=0; i < msd.GetNumValues(); i++ ) { const MsdFile::value_t &sParams = msd.GetValue(i); CString sValueName = sParams[0]; // handle the data if( 0==stricmp(sValueName,"TITLE") ) LoadTags(sParams[1], out); else if( 0==stricmp(sValueName,"BPM") ) out.AddBPMSegment( BPMSegment(0, (float)atof(sParams[1])) ); else if( 0==stricmp(sValueName,"BPM2") ) BPM2 = float(atof(sParams[1])); else if( 0==stricmp(sValueName,"BPM3") ) BPM3 = float(atof(sParams[1])); else if( 0==stricmp(sValueName,"BUNKI") ) BPMPos2 = float(atof(sParams[1])) / 100.0f; else if( 0==stricmp(sValueName,"BUNKI2") ) BPMPos3 = float(atof(sParams[1])) / 100.0f; else if( 0==stricmp(sValueName,"STARTTIME") ) out.m_fBeat0OffsetInSeconds = -(float)atof(sParams[1])/100; else if( 0==stricmp(sValueName,"TICKCOUNT") || 0==stricmp(sValueName,"STEP") || 0==stricmp(sValueName,"DIFFICULTY")) ; /* Handled in LoadFromKSFFile; don't warn. */ else LOG->Trace( "Unexpected value named '%s'", sValueName.c_str() ); } /* This doesn't work yet: we also need to move the data around, I think, and * we should handle more than one BPM change. */ if( BPM2 > 0 && BPMPos2 > 0 ) { const float BeatsPerSecond = out.GetBPMAtBeat(0) / 60.0f; const float beat = BPMPos2 * BeatsPerSecond; LOG->Trace("BPM %f, BPS %f, BPMPos2 %f, beat %f", out.GetBPMAtBeat(0), BeatsPerSecond, BPMPos2, beat); out.AddBPMSegment( BPMSegment(beat, BPM2) ); } if( BPM3 > 0 && BPMPos3 > 0 ) { const float BeatsPerSecond = out.GetBPMAtBeat(0) / 60.0f; const float beat = BPMPos3 * BeatsPerSecond; LOG->Trace("BPM %f, BPS %f, BPMPos3 %f, beat %f", out.GetBPMAtBeat(0), BeatsPerSecond, BPMPos3, beat); out.AddBPMSegment( BPMSegment(beat, BPM3) ); } /* Try to fill in missing bits of information from the pathname. */ { CStringArray asBits; split( sPath, SLASH, asBits, true); ASSERT(asBits.size() > 1); LoadTags(asBits[asBits.size()-2], out); } // search for music with song in the file name CStringArray arrayPossibleMusic; GetDirListing( out.GetSongDir() + CString("song.mp3"), arrayPossibleMusic ); GetDirListing( out.GetSongDir() + CString("song.ogg"), arrayPossibleMusic ); GetDirListing( out.GetSongDir() + CString("song.wav"), arrayPossibleMusic ); if( !arrayPossibleMusic.empty() ) // we found a match out.m_sMusicFile = arrayPossibleMusic[0]; return true; } bool KSFLoader::LoadFromDir( CString sDir, Song &out ) { LOG->Trace( "Song::LoadFromKSFDir(%s)", sDir.c_str() ); CStringArray arrayKSFFileNames; GetDirListing( sDir + CString("*.ksf"), arrayKSFFileNames ); /* We shouldn't have been called to begin with if there were no KSFs. */ if( arrayKSFFileNames.empty() ) RageException::Throw( "Couldn't find any KSF files in '%s'", sDir.c_str() ); if(!LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[0], out)) return false; // load the Steps from the rest of the KSF files for( unsigned i=0; i