Files
itgmania212121/stepmania/src/NotesLoaderKSF.cpp
T
Glenn Maynard 7003e2b157 Implement RADAR_NUM_HANDS result.
Change hold note contents to rows (from beats).  This makes it
consistent with tap note data.  Row numbers are also generally
more precise, since they're integers, not floats.
2003-12-16 04:00:39 +00:00

371 lines
10 KiB
C++

#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; i<msd.GetNumValues(); i++ )
{
const MsdFile::value_t &sParams = msd.GetValue(i);
CString sValueName = sParams[0];
// handle the data
if( 0==stricmp(sValueName,"TICKCOUNT") )
iTickCount = atoi(sParams[1]);
else if( 0==stricmp(sValueName,"STEP") )
{
CString step = sParams[1];
TrimLeft(step);
split( step, "\n", asRows, true );
}
else if( 0==stricmp(sValueName,"DIFFICULTY") )
out.SetMeter(atoi(sParams[1]));
}
if( iTickCount == -1 )
{
iTickCount = 2;
LOG->Warn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount );
}
NoteData notedata; // read it into here
{
CString sDir, sFName, sExt;
splitpath( 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; r<asRows.size(); r++ )
{
CString& sRowString = asRows[r];
StripCrnl( sRowString );
if( sRowString == "" )
continue; // skip
/* All 2s indicates the end of the song. */
if( sRowString == "2222222222222" )
break;
if(sRowString.size() != 13)
{
LOG->Warn("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 */
BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */
BeatToNoteRow((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(&notedata);
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, "/", 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<arrayKSFFileNames.size(); i++ )
{
Steps* pNewNotes = new Steps;
if(!LoadFromKSFFile( out.GetSongDir() + arrayKSFFileNames[i], *pNewNotes, out ))
{
delete pNewNotes;
continue;
}
out.m_apNotes.push_back( pNewNotes );
}
return true;
}