Move loaders into their own classes.
(These should have a common base class, but that can be done later; I'm doing this to make it a bit more manageable so I can add the new couples format to these.)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
#ifndef NOTES_LOADER_H
|
||||
#define NOTES_LOADER_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
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,562 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "NotesLoaderBMS.h"
|
||||
#include "NotesLoader.h"
|
||||
#include "NoteData.h"
|
||||
#include "GameConstantsAndTypes.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageLog.h"
|
||||
#include "GameManager.h"
|
||||
#include "RageException.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
|
||||
//
|
||||
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 BMSLoader::LoadFromBMSFile( const CString &sPath, Notes &out )
|
||||
{
|
||||
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
|
||||
out.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
|
||||
out.m_NotesType = NOTES_TYPE_DANCE_COUPLE;
|
||||
break;
|
||||
case 3: // double
|
||||
out.m_NotesType = NOTES_TYPE_DANCE_DOUBLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( -1 != value_name.Find("#title") )
|
||||
{
|
||||
out.m_sDescription = value_data;
|
||||
|
||||
// extract the Notes description (looks like 'Music <BASIC>')
|
||||
int iPosOpenBracket = out.m_sDescription.Find( "<" );
|
||||
if( iPosOpenBracket == -1 )
|
||||
iPosOpenBracket = out.m_sDescription.Find( "(" );
|
||||
int iPosCloseBracket = out.m_sDescription.Find( ">" );
|
||||
if( iPosCloseBracket == -1 )
|
||||
iPosCloseBracket = out.m_sDescription.Find( ")" );
|
||||
|
||||
if( iPosOpenBracket != -1 && iPosCloseBracket != -1 )
|
||||
out.m_sDescription = out.m_sDescription.Mid( iPosOpenBracket+1, iPosCloseBracket-iPosOpenBracket-1 );
|
||||
out.m_sDescription.MakeLower();
|
||||
LOG->Trace( "Notes description found to be '%s'", out.m_sDescription );
|
||||
|
||||
// if there's a 6 in the description, it's probably part of "6panel" or "6-panel"
|
||||
if( out.m_sDescription.Find("6") != -1 )
|
||||
out.m_NotesType = NOTES_TYPE_DANCE_SOLO;
|
||||
|
||||
}
|
||||
if( -1 != value_name.Find("#playlevel") )
|
||||
{
|
||||
out.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<bool, bool&> arrayNotes;
|
||||
|
||||
for( int i=0; i<sNoteData.GetLength(); i+=2 )
|
||||
{
|
||||
bool bThisIsANote = sNoteData.Mid(i,2) != "00";
|
||||
arrayNotes.Add( bThisIsANote );
|
||||
}
|
||||
|
||||
const int iNumNotesInThisMeasure = arrayNotes.GetSize();
|
||||
//LOG->Trace( "%s:%s: iMeasureNo = %d, iNoteNum = %d, iNumNotesInThisMeasure = %d",
|
||||
// valuename, sNoteData, iMeasureNo, iNoteNum, iNumNotesInThisMeasure );
|
||||
for( int j=0; j<iNumNotesInThisMeasure; j++ )
|
||||
{
|
||||
if( arrayNotes.GetAt(j) == TRUE )
|
||||
{
|
||||
float fPercentThroughMeasure = (float)j/(float)iNumNotesInThisMeasure;
|
||||
|
||||
const int iNoteIndex = (int) ( (iMeasureNo + fPercentThroughMeasure)
|
||||
* BEATS_PER_MEASURE * ROWS_PER_BEAT );
|
||||
int iColumnNumber;
|
||||
char cNoteChar;
|
||||
mapBMSTrackToDanceNote( iTrackNum, iColumnNumber, cNoteChar );
|
||||
|
||||
if( iColumnNumber != -1 )
|
||||
pNoteData->m_TapNotes[iColumnNumber][iNoteIndex] = cNoteChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( out.m_NotesType == NOTES_TYPE_DANCE_SINGLE ||
|
||||
out.m_NotesType == NOTES_TYPE_DANCE_DOUBLE ||
|
||||
out.m_NotesType == NOTES_TYPE_DANCE_COUPLE// ||
|
||||
//out.m_NotesType == NOTES_TYPE_DANCE_COUPLE_2
|
||||
) // if there are 4 panels, then the Up+Right track really contains the notes for Up
|
||||
{
|
||||
for( int i=0; i<MAX_TAP_NOTE_ROWS; i++ ) // for each TapNote
|
||||
{
|
||||
memcpy(
|
||||
pNoteData->m_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( out.m_NotesType );
|
||||
int iTransformNewToOld[MAX_NOTE_TRACKS];
|
||||
|
||||
switch( out.m_NotesType )
|
||||
{
|
||||
case NOTES_TYPE_DANCE_SINGLE:
|
||||
case NOTES_TYPE_DANCE_COUPLE:
|
||||
// case NOTES_TYPE_DANCE_COUPLE_2:
|
||||
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:
|
||||
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 );
|
||||
|
||||
out.SetNoteData(pNoteData2);
|
||||
|
||||
delete pNoteData;
|
||||
delete pNoteData2;
|
||||
|
||||
out.TidyUpData();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMSLoader::LoadFromBMSDir( CString sDir, Song &out )
|
||||
{
|
||||
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; i<arrayBMSFileNames.GetSize(); i++ )
|
||||
{
|
||||
Notes* pNewNotes = new Notes;
|
||||
LoadFromBMSFile( out.m_sSongDir + arrayBMSFileNames[i], *pNewNotes );
|
||||
out.m_apNotes.Add( pNewNotes );
|
||||
}
|
||||
|
||||
CString sPath = out.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 <BASIC>'
|
||||
int iIndex = value_data.ReverseFind('<');
|
||||
if( iIndex == -1 )
|
||||
iIndex = value_data.ReverseFind('(');
|
||||
if( iIndex != -1 )
|
||||
{
|
||||
value_data = value_data.Left( iIndex );
|
||||
out.GetMainAndSubTitlesFromFullTitle( value_data, out.m_sMainTitle, out.m_sSubTitle );
|
||||
}
|
||||
else
|
||||
out.m_sMainTitle = value_data;
|
||||
}
|
||||
else if( value_name == "#artist" )
|
||||
{
|
||||
out.m_sArtist = value_data;
|
||||
}
|
||||
else if( value_name == "#bpm" )
|
||||
{
|
||||
BPMSegment newSeg( 0, (float)atof(value_data) );
|
||||
out.AddBPMSegment( newSeg );
|
||||
|
||||
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
|
||||
}
|
||||
else if( value_name == "#backbmp" )
|
||||
{
|
||||
out.m_sBackgroundFile = value_data;
|
||||
}
|
||||
else if( value_name == "#wav" )
|
||||
{
|
||||
out.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<int, int> arrayNotes;
|
||||
|
||||
for( int i=0; i<sNoteData.GetLength(); i+=2 )
|
||||
{
|
||||
CString sNote = sNoteData.Mid(i,2);
|
||||
int iNote;
|
||||
sscanf( sNote, "%x", &iNote ); // data is in hexadecimal
|
||||
arrayNotes.Add( iNote );
|
||||
}
|
||||
|
||||
const int iNumNotesInThisMeasure = arrayNotes.GetSize();
|
||||
//LOG->Trace( "%s:%s: iMeasureNo = %d, iBMSTrackNo = %d, iNumNotesInThisMeasure = %d",
|
||||
// valuename, sNoteData, iMeasureNo, iBMSTrackNo, iNumNotesInThisMeasure );
|
||||
for( int j=0; j<iNumNotesInThisMeasure; j++ )
|
||||
{
|
||||
if( arrayNotes[j] == 0 )
|
||||
continue;
|
||||
|
||||
float fPercentThroughMeasure = (float)j/(float)iNumNotesInThisMeasure;
|
||||
|
||||
// index is in quarter beats starting at beat 0
|
||||
int iStepIndex = (int) ( (iMeasureNo + fPercentThroughMeasure)
|
||||
* BEATS_PER_MEASURE * ROWS_PER_BEAT );
|
||||
|
||||
switch( iBMSTrackNo )
|
||||
{
|
||||
case 1: { // background music track
|
||||
float fBeatOffset = fBeatOffset = NoteRowToBeat( (float)iStepIndex );
|
||||
if( fBeatOffset > 10 ) // some BPMs's play the music again at the end. Why? Who knows...
|
||||
break;
|
||||
float fBPS;
|
||||
fBPS = out.m_BPMSegments[0].m_fBPM/60.0f;
|
||||
out.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] );
|
||||
out.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 );
|
||||
out.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<out.m_BPMSegments.GetSize()-1; i++ )
|
||||
{
|
||||
if( out.m_BPMSegments[i].m_fStartBeat <= fFreezeStartBeat &&
|
||||
out.m_BPMSegments[i+1].m_fStartBeat > fFreezeStartBeat )
|
||||
{
|
||||
fBPM = out.m_BPMSegments[i].m_fBPM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// the BPM segment of this beat is the last BPM segment
|
||||
if( fBPM == -1 )
|
||||
fBPM = out.m_BPMSegments[out.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 );
|
||||
out.AddStopSegment( newSeg );
|
||||
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds );
|
||||
}
|
||||
|
||||
file.Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for( i=0; i<out.m_BPMSegments.GetSize(); i++ )
|
||||
LOG->Trace( "There is a BPM change at beat %f, BPM %f, index %d",
|
||||
out.m_BPMSegments[i].m_fStartBeat, out.m_BPMSegments[i].m_fBPM, i );
|
||||
|
||||
file.Close();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef NOTES_LOADER_BMS_H
|
||||
#define NOTES_LOADER_BMS_H
|
||||
|
||||
#include "Song.h"
|
||||
#include "Notes.h"
|
||||
|
||||
class BMSLoader {
|
||||
bool LoadFromBMSFile( const CString &sPath, Notes &out );
|
||||
|
||||
public:
|
||||
bool LoadFromBMSDir( CString sDir, Song &out );
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,341 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "NotesLoaderDWI.h"
|
||||
#include "NotesLoader.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageException.h"
|
||||
#include "MsdFile.h"
|
||||
|
||||
void DWILoader::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 DWILoader::LoadFromDWITokens(
|
||||
CString sMode,
|
||||
CString sDescription,
|
||||
CString sNumFeet,
|
||||
CString sStepData1,
|
||||
CString sStepData2,
|
||||
Notes &out)
|
||||
{
|
||||
LOG->Trace( "Notes::LoadFromDWITokens()" );
|
||||
|
||||
sStepData1.Replace( "\n", "" );
|
||||
sStepData1.Replace( " ", "" );
|
||||
sStepData2.Replace( "\n", "" );
|
||||
sStepData2.Replace( " ", "" );
|
||||
|
||||
if( sMode == "SINGLE" ) out.m_NotesType = NOTES_TYPE_DANCE_SINGLE;
|
||||
else if( sMode == "DOUBLE" ) out.m_NotesType = NOTES_TYPE_DANCE_DOUBLE;
|
||||
else if( sMode == "COUPLE" ) out.m_NotesType = NOTES_TYPE_DANCE_COUPLE;
|
||||
else if( sMode == "SOLO" ) out.m_NotesType = NOTES_TYPE_DANCE_SOLO;
|
||||
else
|
||||
{
|
||||
ASSERT(0); // Unrecognized DWI notes format
|
||||
out.m_NotesType = NOTES_TYPE_DANCE_SINGLE;
|
||||
}
|
||||
|
||||
|
||||
CMap<int, int, int, int> mapDanceNoteToNoteDataColumn;
|
||||
switch( out.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);
|
||||
}
|
||||
|
||||
out.m_sDescription = sDescription;
|
||||
|
||||
out.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<sStepData.GetLength(); )
|
||||
{
|
||||
char c = sStepData[i++];
|
||||
|
||||
switch( c )
|
||||
{
|
||||
// begins a series
|
||||
case '(':
|
||||
fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
|
||||
break;
|
||||
case '[':
|
||||
fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
|
||||
break;
|
||||
case '{':
|
||||
fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
|
||||
break;
|
||||
case '<':
|
||||
fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
|
||||
break;
|
||||
|
||||
// ends a series
|
||||
case ')':
|
||||
case ']':
|
||||
case '}':
|
||||
case '>':
|
||||
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 );
|
||||
|
||||
out.m_sSMNoteData = pNoteData->GetSMNoteDataString();
|
||||
|
||||
delete pNoteData;
|
||||
|
||||
out.TidyUpData();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool DWILoader::LoadFromDWIFile( CString sPath, Song &out )
|
||||
{
|
||||
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; i<msd.m_iNumValues; i++ )
|
||||
{
|
||||
int iNumParams = msd.m_iNumParams[i];
|
||||
CString* sParams = msd.m_sValuesAndParams[i];
|
||||
CString sValueName = sParams[0];
|
||||
|
||||
// handle the data
|
||||
if( 0==stricmp(sValueName,"FILE") )
|
||||
out.m_sMusicFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"TITLE") )
|
||||
out.GetMainAndSubTitlesFromFullTitle( sParams[1], out.m_sMainTitle, out.m_sSubTitle );
|
||||
|
||||
else if( 0==stricmp(sValueName,"ARTIST") )
|
||||
out.m_sArtist = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"CDTITLE") )
|
||||
out.m_sCDTitleFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"BPM") )
|
||||
out.AddBPMSegment( BPMSegment(0, (float)atof(sParams[1])) );
|
||||
|
||||
else if( 0==stricmp(sValueName,"GAP") )
|
||||
// the units of GAP is 1/1000 second
|
||||
out.m_fBeat0OffsetInSeconds = -atoi( sParams[1] ) / 1000.0f;
|
||||
|
||||
else if( 0==stricmp(sValueName,"SAMPLESTART") )
|
||||
out.m_fMusicSampleStartSeconds = TimeToSeconds( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"SAMPLELENGTH") )
|
||||
out.m_fMusicSampleLengthSeconds = TimeToSeconds( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"FREEZE") )
|
||||
{
|
||||
CStringArray arrayFreezeExpressions;
|
||||
split( sParams[1], ",", arrayFreezeExpressions );
|
||||
|
||||
for( int f=0; f<arrayFreezeExpressions.GetSize(); f++ )
|
||||
{
|
||||
CStringArray arrayFreezeValues;
|
||||
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
|
||||
float fIndex = atoi( arrayFreezeValues[0] ) * ROWS_PER_BEAT / 4.0f;
|
||||
float fFreezeBeat = NoteRowToBeat( fIndex );
|
||||
float fFreezeSeconds = (float)atof( arrayFreezeValues[1] ) / 1000.0f;
|
||||
|
||||
out.AddStopSegment( StopSegment(fFreezeBeat, fFreezeSeconds) );
|
||||
LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==stricmp(sValueName,"CHANGEBPM") || 0==stricmp(sValueName,"BPMCHANGE") )
|
||||
{
|
||||
CStringArray arrayBPMChangeExpressions;
|
||||
split( sParams[1], ",", arrayBPMChangeExpressions );
|
||||
|
||||
for( int b=0; b<arrayBPMChangeExpressions.GetSize(); b++ )
|
||||
{
|
||||
CStringArray arrayBPMChangeValues;
|
||||
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
|
||||
float fIndex = atoi( arrayBPMChangeValues[0] ) * ROWS_PER_BEAT / 4.0f;
|
||||
float fBeat = NoteRowToBeat( fIndex );
|
||||
float fNewBPM = (float)atof( arrayBPMChangeValues[1] );
|
||||
|
||||
out.AddBPMSegment( BPMSegment(fBeat, fNewBPM) );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==stricmp(sValueName,"SINGLE") ||
|
||||
0==stricmp(sValueName,"DOUBLE") ||
|
||||
0==stricmp(sValueName,"COUPLE") ||
|
||||
0==stricmp(sValueName,"SOLO"))
|
||||
{
|
||||
Notes* pNewNotes = new Notes;
|
||||
LoadFromDWITokens(
|
||||
sParams[0],
|
||||
sParams[1],
|
||||
sParams[2],
|
||||
sParams[3],
|
||||
(iNumParams==5) ? sParams[4] : "",
|
||||
*pNewNotes
|
||||
);
|
||||
out.m_apNotes.Add( pNewNotes );
|
||||
}
|
||||
else
|
||||
// do nothing. We don't care about this value name
|
||||
;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef NOTES_LOADER_DWI_H
|
||||
#define NOTES_LOADER_DWI_H
|
||||
|
||||
#include "Song.h"
|
||||
#include "Notes.h"
|
||||
#include "GameInput.h"
|
||||
#include "NotesLoader.h"
|
||||
|
||||
#include "Song.h"
|
||||
#include "Notes.h"
|
||||
|
||||
class DWILoader {
|
||||
void DWIcharToNote( char c, GameController i, DanceNote ¬e1Out, DanceNote ¬e2Out );
|
||||
|
||||
bool LoadFromDWITokens(
|
||||
CString sMode,
|
||||
CString sDescription,
|
||||
CString sNumFeet,
|
||||
CString sStepData1,
|
||||
CString sStepData2,
|
||||
Notes &out);
|
||||
|
||||
public:
|
||||
bool LoadFromDWIFile( CString sPath, Song &out );
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "NotesLoaderKSF.h"
|
||||
|
||||
#include "RageException.h"
|
||||
#include "MsdFile.h"
|
||||
#include "RageLog.h"
|
||||
|
||||
bool KSFLoader::LoadFromKSFFile( const CString &sPath, Notes &out )
|
||||
{
|
||||
LOG->Trace( "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; i<msd.m_iNumValues; i++ )
|
||||
{
|
||||
int iNumParams = msd.m_iNumParams[i];
|
||||
CString* sParams = msd.m_sValuesAndParams[i];
|
||||
CString sValueName = sParams[0];
|
||||
|
||||
// handle the data
|
||||
if( 0==stricmp(sValueName,"TICKCOUNT") )
|
||||
iTickCount = atoi(sParams[1]);
|
||||
|
||||
else if( 0==stricmp(sValueName,"STEP") )
|
||||
iStep = sParams[1];
|
||||
else if( 0==stricmp(sValueName,"DIFFICULTY") )
|
||||
out.m_iMeter = atoi(sParams[1]);
|
||||
}
|
||||
|
||||
if( iTickCount == -1 )
|
||||
{
|
||||
iTickCount = 2;
|
||||
LOG->Warn( "%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; r<asRows.GetSize(); r++ )
|
||||
{
|
||||
CString& sRowString = asRows[r];
|
||||
|
||||
if( sRowString == "" )
|
||||
continue; // skip
|
||||
|
||||
/* All 2s indicates the end of the song. */
|
||||
if( sRowString == "2222222222222" )
|
||||
break;
|
||||
|
||||
ASSERT( sRowString.GetLength() == 13 ); // why 13 notes per row. Beats me!
|
||||
|
||||
// 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<13; 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;
|
||||
}
|
||||
|
||||
notedata.m_TapNotes[t][row] = sRowString[t];
|
||||
}
|
||||
}
|
||||
|
||||
CString sDir, sFName, sExt;
|
||||
splitrelpath( sPath, sDir, sFName, sExt );
|
||||
sFName.MakeLower();
|
||||
|
||||
out.m_sDescription = sFName;
|
||||
if( sFName.Find("crazy")!=-1 )
|
||||
{
|
||||
out.m_DifficultyClass = CLASS_HARD;
|
||||
if(!out.m_iMeter) out.m_iMeter = 8;
|
||||
}
|
||||
else if( sFName.Find("hard")!=-1 )
|
||||
{
|
||||
out.m_DifficultyClass = CLASS_MEDIUM;
|
||||
if(!out.m_iMeter) out.m_iMeter = 5;
|
||||
}
|
||||
else if( sFName.Find("easy")!=-1 )
|
||||
{
|
||||
out.m_DifficultyClass = CLASS_EASY;
|
||||
if(!out.m_iMeter) out.m_iMeter = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.m_DifficultyClass = CLASS_MEDIUM;
|
||||
if(!out.m_iMeter) out.m_iMeter = 5;
|
||||
}
|
||||
|
||||
if( sFName.Find("double") != -1 )
|
||||
{
|
||||
notedata.m_iNumTracks = 10;
|
||||
out.m_NotesType = NOTES_TYPE_PUMP_DOUBLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
notedata.m_iNumTracks = 5;
|
||||
out.m_NotesType = NOTES_TYPE_PUMP_SINGLE;
|
||||
}
|
||||
|
||||
out.m_sSMNoteData = notedata.GetSMNoteDataString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool KSFLoader::LoadFromKSFDir( CString sDir, Song &out )
|
||||
{
|
||||
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; i<arrayKSFFileNames.GetSize(); i++ )
|
||||
{
|
||||
if( arrayKSFFileNames[i].Find("_2") != -1 )
|
||||
continue; // any "right-side" files should be loaded by Notes
|
||||
Notes* pNewNotes = new Notes;
|
||||
LoadFromKSFFile( out.m_sSongDir + arrayKSFFileNames[i], *pNewNotes );
|
||||
out.m_apNotes.Add( pNewNotes );
|
||||
}
|
||||
|
||||
CString sPath = out.m_sSongDir + arrayKSFFileNames[0];
|
||||
|
||||
MsdFile msd;
|
||||
bool bResult = msd.ReadFile( sPath );
|
||||
if( !bResult )
|
||||
throw RageException( "Error opening file '%s'.", sPath );
|
||||
|
||||
for( i=0; i<msd.m_iNumValues; i++ )
|
||||
{
|
||||
int iNumParams = msd.m_iNumParams[i];
|
||||
CString* sParams = msd.m_sValuesAndParams[i];
|
||||
CString sValueName = sParams[0];
|
||||
|
||||
// handle the data
|
||||
if( 0==stricmp(sValueName,"TITLE") )
|
||||
{
|
||||
//title is usually in format "artist - songtitle"
|
||||
CStringArray asBits;
|
||||
split( sParams[1], " - ", asBits, false );
|
||||
|
||||
/* It's often "artist - songtitle - difficulty". Ignore
|
||||
* the difficulty, since we get that from the filename. */
|
||||
if( asBits.GetSize() == 3 &&
|
||||
(!stricmp(asBits[2], "double") ||
|
||||
!stricmp(asBits[2], "easy") ||
|
||||
!stricmp(asBits[2], "normal") ||
|
||||
!stricmp(asBits[2], "hard") ||
|
||||
!stricmp(asBits[2], "crazy")) )
|
||||
{
|
||||
asBits.RemoveAt(2);
|
||||
}
|
||||
|
||||
if( asBits.GetSize() == 2 )
|
||||
{
|
||||
out.m_sArtist = asBits[0];
|
||||
out.m_sMainTitle = asBits[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
out.m_sMainTitle = asBits[0];
|
||||
}
|
||||
|
||||
for( int j=0; j<out.m_sMainTitle.GetLength(); j++ )
|
||||
{
|
||||
char c = out.m_sMainTitle[j];
|
||||
if( c < 0 ) // this title has a foreign char
|
||||
{
|
||||
CStringArray asBits;
|
||||
split( sDir, "\\", asBits, true);
|
||||
CString sSongFolderName = asBits[ asBits.GetSize()-1 ];
|
||||
asBits.RemoveAll();
|
||||
|
||||
split( sSongFolderName, " - ", asBits, false );
|
||||
if( asBits.GetSize() == 2 )
|
||||
{
|
||||
out.m_sArtist = asBits[0];
|
||||
out.m_sMainTitle = asBits[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
out.m_sMainTitle = asBits[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==stricmp(sValueName,"BPM") )
|
||||
out.AddBPMSegment( BPMSegment(0, (float)atof(sParams[1])) );
|
||||
|
||||
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 Notes::LoadFromKSFFile; don't warn. */
|
||||
else
|
||||
LOG->Trace( "Unexpected value named '%s'", sValueName );
|
||||
}
|
||||
|
||||
// search for music with song in the file name
|
||||
CStringArray arrayPossibleMusic;
|
||||
GetDirListing( out.m_sSongDir + CString("song.mp3"), arrayPossibleMusic );
|
||||
GetDirListing( out.m_sSongDir + CString("song.ogg"), arrayPossibleMusic );
|
||||
GetDirListing( out.m_sSongDir + CString("song.wav"), arrayPossibleMusic );
|
||||
|
||||
if( arrayPossibleMusic.GetSize() > 0 ) // we found a match
|
||||
out.m_sMusicFile = arrayPossibleMusic[0];
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef NOTES_LOADER_KSF_H
|
||||
#define NOTES_LOADER_KSF_H
|
||||
|
||||
#include "Song.h"
|
||||
#include "Notes.h"
|
||||
|
||||
class KSFLoader {
|
||||
bool LoadFromKSFFile( const CString &sPath, Notes &out );
|
||||
|
||||
public:
|
||||
bool LoadFromKSFDir( CString sDir, Song &out );
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "NotesLoaderSM.h"
|
||||
#include "GameManager.h"
|
||||
#include "RageException.h"
|
||||
#include "MsdFile.h"
|
||||
#include "RageLog.h"
|
||||
|
||||
void SMLoader::LoadFromSMTokens(
|
||||
CString sNotesType,
|
||||
CString sDescription,
|
||||
CString sDifficultyClass,
|
||||
CString sMeter,
|
||||
CString sRadarValues,
|
||||
CString sNoteData,
|
||||
Notes &out
|
||||
)
|
||||
{
|
||||
sNotesType.TrimLeft();
|
||||
sNotesType.TrimRight();
|
||||
sDescription.TrimLeft();
|
||||
sDescription.TrimRight();
|
||||
sDifficultyClass.TrimLeft();
|
||||
sDifficultyClass.TrimRight();
|
||||
|
||||
|
||||
// LOG->Trace( "Notes::LoadFromSMTokens()" );
|
||||
|
||||
out.m_NotesType = GameManager::StringToNotesType(sNotesType);
|
||||
out.m_sDescription = sDescription;
|
||||
out.m_DifficultyClass = StringToDifficultyClass( sDifficultyClass );
|
||||
out.m_iMeter = atoi(sMeter);
|
||||
CStringArray saValues;
|
||||
split( sRadarValues, ",", saValues, true );
|
||||
if( saValues.GetSize() == NUM_RADAR_VALUES )
|
||||
for( int r=0; r<NUM_RADAR_VALUES; r++ )
|
||||
out.m_fRadarValues[r] = (float)atof(saValues[r]);
|
||||
|
||||
out.m_sSMNoteData = sNoteData;
|
||||
|
||||
out.TidyUpData();
|
||||
}
|
||||
|
||||
|
||||
bool SMLoader::LoadFromSMFile( CString sPath, Song &out )
|
||||
{
|
||||
LOG->Trace( "Song::LoadFromSMDir(%s)", sPath );
|
||||
|
||||
out.m_BPMSegments.RemoveAll();
|
||||
out.m_StopSegments.RemoveAll();
|
||||
|
||||
int i;
|
||||
|
||||
MsdFile msd;
|
||||
bool bResult = msd.ReadFile( sPath );
|
||||
if( !bResult )
|
||||
throw RageException( "Error opening file '%s'.", sPath );
|
||||
|
||||
for( i=0; i<msd.m_iNumValues; i++ )
|
||||
{
|
||||
int iNumParams = msd.m_iNumParams[i];
|
||||
CString* sParams = msd.m_sValuesAndParams[i];
|
||||
CString sValueName = sParams[0];
|
||||
|
||||
// handle the data
|
||||
if( 0==stricmp(sValueName,"TITLE") )
|
||||
out.GetMainAndSubTitlesFromFullTitle( sParams[1], out.m_sMainTitle, out.m_sSubTitle );
|
||||
|
||||
else if( 0==stricmp(sValueName,"SUBTITLE") )
|
||||
out.m_sSubTitle = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"ARTIST") )
|
||||
out.m_sArtist = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"CREDIT") )
|
||||
out.m_sCredit = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"BANNER") )
|
||||
out.m_sBannerFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"BACKGROUND") )
|
||||
out.m_sBackgroundFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"CDTITLE") )
|
||||
out.m_sCDTitleFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"MOVIEBACKGROUND") )
|
||||
out.m_sMovieBackgroundFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"MUSIC") )
|
||||
out.m_sMusicFile = sParams[1];
|
||||
|
||||
else if( 0==stricmp(sValueName,"MUSICBYTES") )
|
||||
out.m_iMusicBytes = atoi( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"MUSICLENGTH") )
|
||||
out.m_fMusicLengthSeconds = (float)atof( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"FIRSTBEAT") )
|
||||
out.m_fFirstBeat = (float)atof( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"LASTBEAT") )
|
||||
out.m_fLastBeat = (float)atof( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"SAMPLESTART") )
|
||||
out.m_fMusicSampleStartSeconds = TimeToSeconds( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"SAMPLELENGTH") )
|
||||
out.m_fMusicSampleLengthSeconds = TimeToSeconds( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"OFFSET") )
|
||||
out.m_fBeat0OffsetInSeconds = (float)atof( sParams[1] );
|
||||
|
||||
else if( 0==stricmp(sValueName,"SELECTABLE") )
|
||||
{
|
||||
if(!stricmp(sParams[1],"YES"))
|
||||
out.m_SelectionDisplay = out.SHOW_ALWAYS;
|
||||
else if(!stricmp(sParams[1],"NO"))
|
||||
out.m_SelectionDisplay = out.SHOW_NEVER;
|
||||
else if(!stricmp(sParams[1],"ROULETTE"))
|
||||
out.m_SelectionDisplay = out.SHOW_ROULETTE;
|
||||
else
|
||||
LOG->Warn( "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; f<arrayFreezeExpressions.GetSize(); f++ )
|
||||
{
|
||||
CStringArray arrayFreezeValues;
|
||||
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
|
||||
float fFreezeBeat = (float)atof( arrayFreezeValues[0] );
|
||||
float fFreezeSeconds = (float)atof( arrayFreezeValues[1] );
|
||||
|
||||
StopSegment new_seg;
|
||||
new_seg.m_fStartBeat = fFreezeBeat;
|
||||
new_seg.m_fStopSeconds = fFreezeSeconds;
|
||||
|
||||
LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
|
||||
|
||||
out.AddStopSegment( new_seg );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==stricmp(sValueName,"BPMS") )
|
||||
{
|
||||
CStringArray arrayBPMChangeExpressions;
|
||||
split( sParams[1], ",", arrayBPMChangeExpressions );
|
||||
|
||||
for( int b=0; b<arrayBPMChangeExpressions.GetSize(); b++ )
|
||||
{
|
||||
CStringArray arrayBPMChangeValues;
|
||||
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
|
||||
float fBeat = (float)atof( arrayBPMChangeValues[0] );
|
||||
float fNewBPM = (float)atof( arrayBPMChangeValues[1] );
|
||||
|
||||
BPMSegment new_seg;
|
||||
new_seg.m_fStartBeat = fBeat;
|
||||
new_seg.m_fBPM = fNewBPM;
|
||||
|
||||
out.AddBPMSegment( new_seg );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==stricmp(sValueName,"BGCHANGES") )
|
||||
{
|
||||
CStringArray aBGChangeExpressions;
|
||||
split( sParams[1], ",", aBGChangeExpressions );
|
||||
|
||||
for( int b=0; b<aBGChangeExpressions.GetSize(); b++ )
|
||||
{
|
||||
CStringArray aBGChangeValues;
|
||||
split( aBGChangeExpressions[b], "=", aBGChangeValues );
|
||||
float fBeat = (float)atof( aBGChangeValues[0] );
|
||||
CString sBGName = aBGChangeValues[1];
|
||||
sBGName.MakeLower();
|
||||
|
||||
out.AddBackgroundChange( BackgroundChange(fBeat, sBGName) );
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==stricmp(sValueName,"NOTES") )
|
||||
{
|
||||
Notes* pNewNotes = new Notes;
|
||||
ASSERT( pNewNotes );
|
||||
out.m_apNotes.Add( pNewNotes );
|
||||
|
||||
if( iNumParams != 7 )
|
||||
throw RageException( "The song file '%s' is has %d fields in a #NOTES tag, but should have %d.", sPath, iNumParams, 7 );
|
||||
|
||||
LoadFromSMTokens(
|
||||
sParams[1],
|
||||
sParams[2],
|
||||
sParams[3],
|
||||
sParams[4],
|
||||
sParams[5],
|
||||
sParams[6],
|
||||
*pNewNotes);
|
||||
}
|
||||
|
||||
else
|
||||
LOG->Trace( "Unexpected value named '%s'", sValueName );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef NOTES_LOADER_SM_H
|
||||
#define NOTES_LOADER_SM_H
|
||||
|
||||
#include "Song.h"
|
||||
#include "Notes.h"
|
||||
|
||||
class SMLoader {
|
||||
void LoadFromSMTokens(
|
||||
CString sNotesType,
|
||||
CString sDescription,
|
||||
CString sDifficultyClass,
|
||||
CString sMeter,
|
||||
CString sRadarValues,
|
||||
CString sNoteData,
|
||||
Notes &out);
|
||||
|
||||
public:
|
||||
bool LoadFromSMFile( CString sPath, Song &out );
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user