Files
itgmania212121/stepmania/src/NotesLoaderBMS.cpp
T

806 lines
24 KiB
C++
Raw Normal View History

2003-02-16 04:01:45 +00:00
#include "global.h"
2002-09-06 23:24:40 +00:00
#include "NotesLoaderBMS.h"
#include "NotesLoader.h"
#include "NoteData.h"
#include "GameConstantsAndTypes.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "GameManager.h"
#include "RageException.h"
2003-07-22 07:56:46 +00:00
#include "RageFile.h"
#include "StepsUtil.h"
2004-05-31 21:55:14 +00:00
#include "song.h"
#include "Steps.h"
2002-09-06 23:24:40 +00:00
/* 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-single: 11-16
bm-double: 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. */
2004-05-31 22:07:09 +00:00
static int iTracks[MAX_NOTE_TRACKS];
2004-05-31 22:07:09 +00:00
enum
{
BMS_NULL_COLUMN = 0,
BMS_P1_KEY1,
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,
};
void BMSLoader::ResetTracksMagic()
{
for( int i = 0; i<MAX_NOTE_TRACKS; i++ )
iTracks[i] = 0;
}
2004-05-31 22:07:09 +00:00
void BMSLoader::PushTrackNumForMagic( int iTrackNum )
{
int ix = (iTrackNum < 20) ? (iTrackNum - 11) : (iTrackNum - 12);
iTracks[ix]++;
}
2004-05-31 22:07:09 +00:00
StepsType BMSLoader::CheckTracksMagic()
{
int iTrackCount = 0;
for (int ix = 0; ix<MAX_NOTE_TRACKS; ix++) {
if(iTracks[ix] != 0) iTrackCount++;
}
/* Panel counts:
4 - DDR
5 - PNM 5-key
6 - DDR Solo, BM 5-key
8 - DDR Double. BM 7-key
9 - PNM 9-key, BM 7-key
12 - BM Double 5-key
16 - BM Double 7-key */
switch(iTrackCount) {
case 4:
return STEPS_TYPE_DANCE_SINGLE;
case 5:
return STEPS_TYPE_PNM_FIVE;
case 6:
/* No reason to return STEPS_TYPE_BM_SINGLE here...
...at least, none that I can see. Same data, no way to distinguish.
We also don't need to autogen between them, though. */
return STEPS_TYPE_DANCE_SOLO;
case 8:
// Could also be couple or 7-key.
2003-12-12 05:37:52 +00:00
if (iTracks[7] == 0 && iTracks[8] == 0 && iTracks[1] == 0 && iTracks[3] ==
0)
// these four tracks are IIDX-related
return STEPS_TYPE_DANCE_DOUBLE;
else
return STEPS_TYPE_IIDX_SINGLE7;
case 9:
return STEPS_TYPE_PNM_NINE;
case 12:
return STEPS_TYPE_BM_DOUBLE;
case 16:
return STEPS_TYPE_IIDX_DOUBLE7;
default:
return STEPS_TYPE_INVALID;
}
}
2004-05-31 22:07:09 +00:00
void BMSLoader::mapBMSTrackToDanceNote( int iBMSTrack, int &iDanceColOut, char &cNoteCharOut )
2002-09-06 23:24:40 +00:00
{
if( iBMSTrack > 40 )
{
cNoteCharOut = '2';
iBMSTrack -= 40;
}
else
{
cNoteCharOut = '1';
}
switch( iBMSTrack )
{
case 11: iDanceColOut = BMS_P1_KEY1; break;
case 12: iDanceColOut = BMS_P1_KEY2; break;
case 13: iDanceColOut = BMS_P1_KEY3; break;
case 14: iDanceColOut = BMS_P1_KEY4; break;
case 15: iDanceColOut = BMS_P1_KEY5; break;
case 16: iDanceColOut = BMS_P1_TURN; break;
case 18: iDanceColOut = BMS_P1_KEY6; break;
case 19: iDanceColOut = BMS_P1_KEY7; break;
case 21: iDanceColOut = BMS_P2_KEY1; break;
case 22: iDanceColOut = BMS_P2_KEY2; break;
case 23: iDanceColOut = BMS_P2_KEY3; break;
case 24: iDanceColOut = BMS_P2_KEY4; break;
case 25: iDanceColOut = BMS_P2_KEY5; break;
case 26: iDanceColOut = BMS_P2_TURN; break;
case 28: iDanceColOut = BMS_P2_KEY6; break;
case 29: iDanceColOut = BMS_P2_KEY7; break;
2002-09-06 23:24:40 +00:00
default: iDanceColOut = -1; break;
}
}
2003-08-03 00:13:55 +00:00
bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out )
2002-09-06 23:24:40 +00:00
{
2003-08-03 00:13:55 +00:00
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
2002-09-06 23:24:40 +00:00
2003-08-07 06:36:34 +00:00
out.m_StepsType = STEPS_TYPE_INVALID;
2002-09-06 23:24:40 +00:00
NoteData* pNoteData = new NoteData;
2003-02-01 05:16:38 +00:00
pNoteData->SetNumTracks( MAX_NOTE_TRACKS );
ResetTracksMagic();
2002-09-06 23:24:40 +00:00
2004-03-08 05:11:26 +00:00
RageFile file;
if( !file.Open(sPath) )
RageException::Throw( "Failed to open \"%s\": %s", sPath.c_str(), file.GetError().c_str() );
2003-12-12 05:37:52 +00:00
2004-03-08 05:11:26 +00:00
while( !file.AtEOF() )
2002-09-06 23:24:40 +00:00
{
2004-06-06 21:04:48 +00:00
CString line;
if( file.GetLine( line ) == -1 )
{
LOG->Warn( "Error reading \"%s\": %s", sPath.c_str(), file.GetError().c_str() );
delete pNoteData;
return false;
}
2003-02-16 05:12:37 +00:00
StripCrnl(line);
2002-09-06 23:24:40 +00:00
CString value_name; // fill these in
2003-12-12 05:37:52 +00:00
CString value_data;
2002-09-06 23:24:40 +00:00
// 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;
2003-12-12 05:37:52 +00:00
2002-09-06 23:24:40 +00:00
int iIndexOfSeparator = min( iIndexOfFirstSpace, iIndexOfFirstColon );
if( iIndexOfSeparator != 10000 )
{
2003-01-03 05:29:45 +00:00
value_name = line.substr( 0, iIndexOfSeparator );
2002-09-06 23:24:40 +00:00
value_data = line; // the rest
2003-01-03 05:29:45 +00:00
value_data.erase(0,iIndexOfSeparator+1);
2002-09-06 23:24:40 +00:00
}
else // no separator
{
value_name = line;
}
value_name.MakeLower();
2003-12-12 05:37:52 +00:00
if( -1 != value_name.Find("#player") )
2002-09-06 23:24:40 +00:00
{
switch( atoi(value_data) )
{
case 1: // 4 or 6 single
2003-08-07 06:36:34 +00:00
out.m_StepsType = STEPS_TYPE_DANCE_SINGLE;
/* if the mode should be solo, then we'll update m_DanceStyle below when
we read in step data */
2002-09-06 23:24:40 +00:00
break;
case 2: // couple/battle
2003-08-07 06:36:34 +00:00
out.m_StepsType = STEPS_TYPE_DANCE_COUPLE;
2002-09-06 23:24:40 +00:00
break;
case 3: // double
// Fix it if we find that.
2003-08-07 06:36:34 +00:00
out.m_StepsType = STEPS_TYPE_DANCE_DOUBLE;
2002-09-06 23:24:40 +00:00
break;
default:
2004-03-08 05:11:26 +00:00
LOG->Warn( "Invalid #PLAYER in \"%s\": \"%s\"", sPath.c_str(), value_data.c_str() );
delete pNoteData;
return false;
2002-09-06 23:24:40 +00:00
}
}
if( -1 != value_name.Find("#title") )
{
2003-01-02 22:10:51 +00:00
value_data.MakeLower();
2003-12-12 05:37:52 +00:00
2003-08-03 00:13:55 +00:00
// extract the Steps description (looks like 'Music <BASIC>')
2003-01-02 22:10:51 +00:00
int iPosOpenBracket = value_data.Find( "<" );
2002-09-06 23:24:40 +00:00
if( iPosOpenBracket == -1 )
2003-01-02 22:10:51 +00:00
iPosOpenBracket = value_data.Find( "(" );
int iPosCloseBracket = value_data.Find( ">" );
2002-09-06 23:24:40 +00:00
if( iPosCloseBracket == -1 )
2003-01-02 22:10:51 +00:00
iPosCloseBracket = value_data.Find( ")" );
2002-09-06 23:24:40 +00:00
if( iPosOpenBracket != -1 && iPosCloseBracket != -1 )
2003-12-12 05:37:52 +00:00
value_data = value_data.substr( iPosOpenBracket+1,
iPosCloseBracket-iPosOpenBracket-1 );
2003-08-03 00:13:55 +00:00
LOG->Trace( "Steps description found to be '%s'", value_data.c_str() );
2003-01-02 22:10:51 +00:00
out.SetDescription(value_data);
2002-09-06 23:24:40 +00:00
// if there's a 6 in the description, it's probably part of "6panel" or "6-panel"
2003-01-02 22:10:51 +00:00
if( value_data.Find("6") != -1 )
2003-08-07 06:36:34 +00:00
out.m_StepsType = STEPS_TYPE_DANCE_SOLO;
2002-09-06 23:24:40 +00:00
}
2003-12-12 05:37:52 +00:00
if( -1 != value_name.Find("#playlevel") )
2002-09-06 23:24:40 +00:00
{
2003-01-02 22:10:51 +00:00
out.SetMeter(atoi(value_data));
2002-09-06 23:24:40 +00:00
}
2003-01-03 05:29:45 +00:00
else if( value_name.size() >= 6 && value_name[0] == '#'
&& IsAnInt( value_name.substr(1,3) )
&& IsAnInt( value_name.substr(4,2) ) ) // this is step or offset data. Looks like "#00705"
2002-09-06 23:24:40 +00:00
{
2003-01-03 05:29:45 +00:00
int iMeasureNo = atoi( value_name.substr(1,3).c_str() );
int iTrackNum = atoi( value_name.substr(4,2).c_str() );
2002-09-06 23:24:40 +00:00
/* fix for Pop N' and such, including "if there are six panels, then we have Solo" - check here,
then put the correct step type later */
PushTrackNumForMagic(iTrackNum);
2002-09-06 23:24:40 +00:00
CString &sNoteData = value_data;
2003-01-03 05:56:28 +00:00
vector<bool> arrayNotes;
2002-09-06 23:24:40 +00:00
2003-01-03 05:29:45 +00:00
for( int i=0; i+1<sNoteData.GetLength(); i+=2 )
2002-09-06 23:24:40 +00:00
{
2003-01-03 05:29:45 +00:00
bool bThisIsANote = sNoteData.substr(i,2) != "00";
2002-10-31 04:23:39 +00:00
arrayNotes.push_back( bThisIsANote );
2002-09-06 23:24:40 +00:00
}
const unsigned iNumNotesInThisMeasure = arrayNotes.size();
for( unsigned j=0; j<iNumNotesInThisMeasure; j++ )
2002-09-06 23:24:40 +00:00
{
if( arrayNotes[j] )
2002-09-06 23:24:40 +00:00
{
float fPercentThroughMeasure = (float)j/(float)iNumNotesInThisMeasure;
const int iNoteIndex = (int) ( (iMeasureNo + fPercentThroughMeasure)
* BEATS_PER_MEASURE * ROWS_PER_BEAT );
int iColumnNumber;
char cNoteChar;
2003-12-12 05:37:52 +00:00
2002-09-06 23:24:40 +00:00
mapBMSTrackToDanceNote( iTrackNum, iColumnNumber, cNoteChar );
if( iColumnNumber != -1 )
2002-10-25 04:37:00 +00:00
pNoteData->SetTapNote(iColumnNumber, iNoteIndex, cNoteChar);
2002-09-06 23:24:40 +00:00
}
}
}
}
2003-12-12 05:37:52 +00:00
// dance-couple is the only one we should retain unchanged.
if( out.m_StepsType != STEPS_TYPE_DANCE_COUPLE)
{
out.m_StepsType = CheckTracksMagic();
}
2002-09-06 23:24:40 +00:00
// we're done reading in all of the BMS values
2003-08-07 06:36:34 +00:00
if( out.m_StepsType == STEPS_TYPE_INVALID )
{
2004-03-08 05:11:26 +00:00
LOG->Warn( "Couldn't determine note type of file '%s'", sPath.c_str() );
delete pNoteData;
return false;
}
2003-08-07 06:36:34 +00:00
int iNumNewTracks = GameManager::NotesTypeToNumTracks( out.m_StepsType );
2002-09-06 23:24:40 +00:00
int iTransformNewToOld[MAX_NOTE_TRACKS];
int i;
for( i = 0; i < MAX_NOTE_TRACKS; ++i)
iTransformNewToOld[i] = -1;
2003-08-07 06:36:34 +00:00
switch( out.m_StepsType )
2002-09-06 23:24:40 +00:00
{
// fix PNM &c.
2003-08-07 06:16:17 +00:00
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;
2002-09-06 23:24:40 +00:00
break;
2003-08-07 06:16:17 +00:00
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;
2002-09-06 23:24:40 +00:00
break;
2003-08-07 06:16:17 +00:00
case STEPS_TYPE_DANCE_SOLO:
case STEPS_TYPE_BM_SINGLE:
// Hey! Why the hell 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;
2002-09-06 23:24:40 +00:00
break;
case STEPS_TYPE_PNM_FIVE:
iTransformNewToOld[0] = BMS_P1_KEY3;
iTransformNewToOld[1] = BMS_P1_KEY4;
iTransformNewToOld[2] = BMS_P1_KEY5;
2003-12-12 05:37:52 +00:00
// fix these columns!
iTransformNewToOld[3] = BMS_P2_KEY2;
iTransformNewToOld[4] = BMS_P2_KEY3;
break;
case STEPS_TYPE_PNM_NINE:
2003-12-12 05:37:52 +00:00
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_DOUBLE:
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_IIDX_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_IIDX_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;
2002-09-06 23:24:40 +00:00
default:
ASSERT(0);
2002-09-06 23:24:40 +00:00
}
NoteData* pNoteData2 = new NoteData;
2003-02-01 05:16:38 +00:00
pNoteData2->SetNumTracks( iNumNewTracks );
pNoteData2->LoadTransformed( pNoteData, iNumNewTracks, iTransformNewToOld );
2002-09-06 23:24:40 +00:00
out.SetNoteData(pNoteData2);
delete pNoteData;
delete pNoteData2;
out.TidyUpData();
return true;
}
2002-09-11 05:15:46 +00:00
void BMSLoader::GetApplicableFiles( CString sPath, CStringArray &out )
{
GetDirListing( sPath + CString("*.bms"), out );
}
bool BMSLoader::LoadFromDir( CString sDir, Song &out )
2002-09-06 23:24:40 +00:00
{
2003-04-25 00:01:35 +00:00
LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() );
2002-09-06 23:24:40 +00:00
CStringArray arrayBMSFileNames;
2002-09-11 05:15:46 +00:00
GetApplicableFiles( sDir, arrayBMSFileNames );
2002-09-06 23:24:40 +00:00
/* We should have at least one; if we had none, we shouldn't have been
* called to begin with. */
ASSERT( arrayBMSFileNames.size() );
2002-09-06 23:24:40 +00:00
2003-08-03 00:13:55 +00:00
// load the Steps from the rest of the BMS files
2002-11-16 09:12:55 +00:00
unsigned i;
2003-12-12 05:37:52 +00:00
for( i=0; i<arrayBMSFileNames.size(); i++ )
2002-09-06 23:24:40 +00:00
{
2003-08-03 00:13:55 +00:00
Steps* pNewNotes = new Steps;
2003-12-12 05:37:52 +00:00
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i],
*pNewNotes );
2003-07-15 19:41:13 +00:00
if( ok )
2004-06-05 05:13:23 +00:00
out.AddSteps( pNewNotes );
else
delete pNewNotes;
2002-09-06 23:24:40 +00:00
}
SlideDuplicateDifficulties( out );
2002-12-13 23:41:11 +00:00
CString sPath = out.GetSongDir() + arrayBMSFileNames[0];
2002-09-06 23:24:40 +00:00
2004-06-06 21:04:48 +00:00
RageFile file;
if( !file.Open(sPath) )
2003-04-25 00:01:35 +00:00
RageException::Throw( "Failed to open %s for reading.", sPath.c_str() );
while (!file.AtEOF())
2002-09-06 23:24:40 +00:00
{
2004-06-06 21:04:48 +00:00
CString line;
if( file.GetLine( line ) == -1 )
{
LOG->Warn( "Error reading \"%s\": %s", sPath.c_str(), file.GetError().c_str() );
return false;
}
2003-02-16 05:12:37 +00:00
StripCrnl(line);
2002-09-06 23:24:40 +00:00
CString value_name; // fill these in
2003-12-12 05:37:52 +00:00
CString value_data;
2002-09-06 23:24:40 +00:00
// 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;
2003-12-12 05:37:52 +00:00
2002-09-06 23:24:40 +00:00
int iIndexOfSeparator = min( iIndexOfFirstSpace, iIndexOfFirstColon );
if( iIndexOfSeparator != 10000 )
{
2003-01-03 05:29:45 +00:00
value_name = line.substr( 0, iIndexOfSeparator );
2002-09-06 23:24:40 +00:00
value_data = line; // the rest
2003-01-03 05:29:45 +00:00
value_data.erase(0,iIndexOfSeparator+1);
2002-09-06 23:24:40 +00:00
}
else // no separator
{
value_name = line;
}
value_name.MakeLower();
// handle the data
2003-12-12 05:37:52 +00:00
if( value_name == "#title" )
2002-09-06 23:24:40 +00:00
{
2003-08-03 00:13:55 +00:00
// strip Steps type out of description leaving only song title - looks like 'B4U <BASIC>'
2003-01-03 05:29:45 +00:00
unsigned iIndex = value_data.find_last_of('<');
if( iIndex == value_data.npos )
iIndex = value_data.find_last_of('(');
if( iIndex != value_data.npos )
2002-09-06 23:24:40 +00:00
{
value_data = value_data.Left( iIndex );
2003-12-12 05:37:52 +00:00
GetMainAndSubTitlesFromFullTitle( value_data, out.m_sMainTitle,
out.m_sSubTitle );
2002-09-06 23:24:40 +00:00
}
else
out.m_sMainTitle = value_data;
}
2003-12-12 05:37:52 +00:00
else if( value_name == "#artist" )
2002-09-06 23:24:40 +00:00
{
out.m_sArtist = value_data;
}
2003-12-12 05:37:52 +00:00
else if( value_name == "#bpm" )
2002-09-06 23:24:40 +00:00
{
BPMSegment newSeg( 0, (float)atof(value_data) );
out.AddBPMSegment( newSeg );
2003-12-12 05:37:52 +00:00
2002-09-06 23:24:40 +00:00
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
}
2003-12-12 05:37:52 +00:00
else if( value_name == "#backbmp" )
2002-09-06 23:24:40 +00:00
{
out.m_sBackgroundFile = value_data;
}
2003-12-12 05:37:52 +00:00
else if( value_name == "#wav" )
2002-09-06 23:24:40 +00:00
{
out.m_sMusicFile = value_data;
}
2003-01-03 05:29:45 +00:00
else if( value_name.size() >= 6 && value_name[0] == '#'
&& IsAnInt( value_name.substr(1,3) )
&& IsAnInt( value_name.substr(4,2) ) ) // this is step or offset data. Looks like "#00705"
2002-09-06 23:24:40 +00:00
{
2003-01-03 05:29:45 +00:00
int iMeasureNo = atoi( value_name.substr(1,3).c_str() );
int iBMSTrackNo = atoi( value_name.substr(4,2).c_str() );
2002-09-06 23:24:40 +00:00
CString sNoteData = value_data;
2003-01-03 05:56:28 +00:00
vector<int> arrayNotes;
2002-09-06 23:24:40 +00:00
2003-01-03 05:29:45 +00:00
for( int i=0; i+1<sNoteData.GetLength(); i+=2 )
2002-09-06 23:24:40 +00:00
{
2003-01-03 05:29:45 +00:00
CString sNote = sNoteData.substr(i,2);
2002-09-06 23:24:40 +00:00
int iNote;
sscanf( sNote, "%x", &iNote ); // data is in hexadecimal
2002-10-31 04:23:39 +00:00
arrayNotes.push_back( iNote );
2002-09-06 23:24:40 +00:00
}
const unsigned iNumNotesInThisMeasure = arrayNotes.size();
for( unsigned j=0; j<iNumNotesInThisMeasure; j++ )
2002-09-06 23:24:40 +00:00
{
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;
2003-12-18 04:48:26 +00:00
fBPS = out.m_Timing.m_BPMSegments[0].m_fBPM/60.0f;
out.m_Timing.m_fBeat0OffsetInSeconds = fBeatOffset / fBPS;
2002-09-06 23:24:40 +00:00
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;
}
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. */
2002-09-06 23:24:40 +00:00
CString sTagToLookFor = ssprintf( "#BPM%02x", arrayNotes[j] );
float fBPM = -1;
// open the song file again and and look for this tag's value
/* I don't like this. I think we should just seek back to the beginning
* rather than open the file again. However, I'm not changing the logic,
* only the implementation. -- Steve
*/
RageFile file(sPath); //Why doesn't VC6 bitch here but it does with int??
2003-12-12 05:37:52 +00:00
if (!file.IsOpen())
RageException::Throw( "Failed to open %s for reading.", sPath.c_str() );
while (!file.AtEOF())
2002-09-06 23:24:40 +00:00
{
2004-06-06 21:04:48 +00:00
CString line;
file.GetLine( line );
2003-02-16 05:12:37 +00:00
StripCrnl(line);
2002-09-06 23:24:40 +00:00
CString value_name; // fill these in
2003-12-12 05:37:52 +00:00
CString value_data;
2002-09-06 23:24:40 +00:00
// 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;
2003-12-12 05:37:52 +00:00
2002-09-06 23:24:40 +00:00
int iIndexOfSeparator = min( iIndexOfFirstSpace, iIndexOfFirstColon );
if( iIndexOfSeparator != 10000 )
{
2003-01-03 05:29:45 +00:00
value_name = line.substr( 0, iIndexOfSeparator );
2002-09-06 23:24:40 +00:00
value_data = line; // the rest
2003-01-03 05:29:45 +00:00
value_data.erase(0,iIndexOfSeparator+1);
2002-09-06 23:24:40 +00:00
}
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
2004-03-08 05:11:26 +00:00
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), sPath.c_str() );
2002-09-06 23:24:40 +00:00
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 );
2002-09-06 23:24:40 +00:00
}
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. */
2002-09-06 23:24:40 +00:00
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
RageFile file(sPath); //Why doesn't VC6 bitch here but it does with int??
2003-12-12 05:37:52 +00:00
if (!file.IsOpen())
RageException::Throw( "Failed to open %s for reading.", sPath.c_str() );
while (!file.AtEOF())
2002-09-06 23:24:40 +00:00
{
2004-06-06 21:04:48 +00:00
CString line;
file.GetLine( line );
2003-02-16 05:12:37 +00:00
StripCrnl(line);
2002-09-06 23:24:40 +00:00
CString value_name; // fill these in
2003-12-12 05:37:52 +00:00
CString value_data;
2002-09-06 23:24:40 +00:00
// 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;
2003-12-12 05:37:52 +00:00
2002-09-06 23:24:40 +00:00
int iIndexOfSeparator = min( iIndexOfFirstSpace, iIndexOfFirstColon );
if( iIndexOfSeparator != 10000 )
{
2003-01-03 05:29:45 +00:00
value_name = line.substr( 0, iIndexOfSeparator );
2002-09-06 23:24:40 +00:00
value_data = line; // the rest
2003-01-03 05:29:45 +00:00
value_data.erase(0,iIndexOfSeparator+1);
2002-09-06 23:24:40 +00:00
}
else // no separator
{
value_name = line;
}
if( 0==stricmp(value_name, sTagToLookFor) )
{
// find the BPM at the time of this freeze
float fBPM = -1;
2003-12-18 04:48:26 +00:00
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size()-1; i++ )
2002-09-06 23:24:40 +00:00
{
2003-12-18 04:48:26 +00:00
if( out.m_Timing.m_BPMSegments[i].m_fStartBeat <= fFreezeStartBeat &&
out.m_Timing.m_BPMSegments[i+1].m_fStartBeat > fFreezeStartBeat )
2002-09-06 23:24:40 +00:00
{
2003-12-18 04:48:26 +00:00
fBPM = out.m_Timing.m_BPMSegments[i].m_fBPM;
2002-09-06 23:24:40 +00:00
break;
}
}
// the BPM segment of this beat is the last BPM segment
if( fBPM == -1 )
2003-12-18 04:48:26 +00:00
fBPM = out.m_Timing.m_BPMSegments[out.m_Timing.m_BPMSegments.size()-1].m_fBPM;
2002-09-06 23:24:40 +00:00
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
{
2004-03-08 05:11:26 +00:00
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), sPath.c_str() );
2002-09-06 23:24:40 +00:00
}
else
{
StopSegment newSeg( fFreezeStartBeat, fFreezeSecs );
out.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds );
2002-09-06 23:24:40 +00:00
}
break;
}
}
}
}
}
2003-12-18 04:48:26 +00:00
for( i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
2003-12-12 05:37:52 +00:00
LOG->Trace( "There is a BPM change at beat %f, BPM %f, index %d",
2003-12-18 04:48:26 +00:00
out.m_Timing.m_BPMSegments[i].m_fStartBeat, out.m_Timing.m_BPMSegments[i].m_fBPM, i );
2002-09-06 23:24:40 +00:00
return true;
2002-09-06 23:24:40 +00:00
}
2003-12-12 05:37:52 +00:00
void BMSLoader::SlideDuplicateDifficulties( Song &p )
{
/* BMS files have to guess the Difficulty from the meter; this is inaccurate,
* and often leads to duplicates. Slide duplicate difficulties upwards. We
* only do this with BMS files, since a very common bug was having *all*
* difficulties slid upwards due to (for example) having two beginner steps.
* We do a second pass in Song::TidyUpData to eliminate any remaining duplicates
* after this. */
for( int i=0; i<NUM_STEPS_TYPES; i++ )
{
StepsType st = (StepsType)i;
for( unsigned j=0; j<=DIFFICULTY_CHALLENGE; j++ ) // not DIFFICULTY_EDIT
{
Difficulty dc = (Difficulty)j;
vector<Steps*> vSteps;
p.GetSteps( vSteps, st, dc );
StepsUtil::SortNotesArrayByDifficulty( vSteps );
for( unsigned k=1; k<vSteps.size(); k++ )
{
Steps* pSteps = vSteps[k];
Difficulty dc2 = min( (Difficulty)(dc+1), DIFFICULTY_CHALLENGE );
pSteps->SetDifficulty( dc2 );
}
}
}
}
2004-05-31 21:55:14 +00:00
/*
* (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.
*/