2003-02-16 04:01:45 +00:00
|
|
|
#include "global.h"
|
2002-09-06 23:24:40 +00:00
|
|
|
#include "NotesLoaderBMS.h"
|
|
|
|
|
#include "NoteData.h"
|
|
|
|
|
#include "GameConstantsAndTypes.h"
|
|
|
|
|
#include "RageLog.h"
|
|
|
|
|
#include "GameManager.h"
|
|
|
|
|
#include "RageException.h"
|
2003-07-22 07:56:46 +00:00
|
|
|
#include "RageFile.h"
|
2004-04-18 07:34:37 +00:00
|
|
|
#include "StepsUtil.h"
|
2004-05-31 21:55:14 +00:00
|
|
|
#include "song.h"
|
|
|
|
|
#include "Steps.h"
|
2004-11-02 10:50:38 +00:00
|
|
|
#include "RageUtil_CharConversions.h"
|
2005-01-23 21:55:01 +00:00
|
|
|
#include "NoteTypes.h"
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2003-12-13 03:54:13 +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
|
|
|
|
|
|
2005-10-07 05:00:52 +00:00
|
|
|
BMSes are used for games besides dance and so we're borking up BMSes that are for popn/beat/etc.
|
2003-12-13 03:54:13 +00:00
|
|
|
|
2005-10-07 05:00:52 +00:00
|
|
|
popn-nine: 11-15,22-25
|
|
|
|
|
popn-five: 13-15,21-22
|
|
|
|
|
beat-single5: 11-16
|
|
|
|
|
beat-double5: 11-16,21-26
|
|
|
|
|
beat-single7: 11-16,18-19
|
|
|
|
|
beat-double7: 11-16,18-19,21-26,28-29
|
2003-12-13 03:54:13 +00:00
|
|
|
|
|
|
|
|
So the magics for these are:
|
2005-10-07 05:00:52 +00:00
|
|
|
popn-nine: nothing >5, with 12, 14, 22 and/or 24
|
|
|
|
|
popn-five: nothing >5, with 14 and/or 22
|
|
|
|
|
beat-*: can't tell difference between beat-single and dance-solo
|
|
|
|
|
18/19 marks beat-single7, 28/29 marks beat-double7
|
|
|
|
|
beat-double uses 21-26. */
|
2004-10-24 10:45:30 +00:00
|
|
|
|
2004-09-27 01:46:57 +00:00
|
|
|
enum BmsTrack
|
2004-05-31 22:07:09 +00:00
|
|
|
{
|
2004-09-25 08:00:18 +00:00
|
|
|
BMS_P1_KEY1 = 0,
|
2004-05-31 22:07:09 +00:00
|
|
|
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,
|
2004-10-24 10:45:30 +00:00
|
|
|
// max 4 simultaneous auto keysounds
|
|
|
|
|
BMS_AUTO_KEYSOUND_1,
|
|
|
|
|
BMS_AUTO_KEYSOUND_2,
|
|
|
|
|
BMS_AUTO_KEYSOUND_3,
|
|
|
|
|
BMS_AUTO_KEYSOUND_4,
|
|
|
|
|
BMS_AUTO_KEYSOUND_5,
|
|
|
|
|
BMS_AUTO_KEYSOUND_6,
|
|
|
|
|
BMS_AUTO_KEYSOUND_7,
|
|
|
|
|
BMS_AUTO_KEYSOUND_LAST,
|
2004-09-27 01:46:57 +00:00
|
|
|
NUM_BMS_TRACKS,
|
2004-05-31 22:07:09 +00:00
|
|
|
};
|
|
|
|
|
|
2004-10-24 10:45:30 +00:00
|
|
|
const int NUM_NON_AUTO_KEYSOUND_TRACKS = BMS_AUTO_KEYSOUND_1;
|
|
|
|
|
const int NUM_AUTO_KEYSOUND_TRACKS = NUM_BMS_TRACKS - NUM_NON_AUTO_KEYSOUND_TRACKS;
|
|
|
|
|
|
2004-09-29 07:19:44 +00:00
|
|
|
static bool ConvertRawTrackToTapNote( int iRawTrack, BmsTrack &bmsTrackOut, bool &bIsHoldOut )
|
2004-05-31 22:07:09 +00:00
|
|
|
{
|
2004-09-27 01:46:57 +00:00
|
|
|
if( iRawTrack > 40 )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-09-29 07:19:44 +00:00
|
|
|
bIsHoldOut = true;
|
2004-09-27 01:46:57 +00:00
|
|
|
iRawTrack -= 40;
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2004-09-29 07:19:44 +00:00
|
|
|
bIsHoldOut = false;
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-27 01:46:57 +00:00
|
|
|
switch( iRawTrack )
|
|
|
|
|
{
|
2004-10-24 10:45:30 +00:00
|
|
|
case 1: bmsTrackOut = BMS_AUTO_KEYSOUND_1; break;
|
2004-10-23 23:41:49 +00:00
|
|
|
case 11: bmsTrackOut = BMS_P1_KEY1; break;
|
|
|
|
|
case 12: bmsTrackOut = BMS_P1_KEY2; break;
|
|
|
|
|
case 13: bmsTrackOut = BMS_P1_KEY3; break;
|
|
|
|
|
case 14: bmsTrackOut = BMS_P1_KEY4; break;
|
|
|
|
|
case 15: bmsTrackOut = BMS_P1_KEY5; break;
|
|
|
|
|
case 16: bmsTrackOut = BMS_P1_TURN; break;
|
|
|
|
|
case 18: bmsTrackOut = BMS_P1_KEY6; break;
|
|
|
|
|
case 19: bmsTrackOut = BMS_P1_KEY7; break;
|
|
|
|
|
case 21: bmsTrackOut = BMS_P2_KEY1; break;
|
|
|
|
|
case 22: bmsTrackOut = BMS_P2_KEY2; break;
|
|
|
|
|
case 23: bmsTrackOut = BMS_P2_KEY3; break;
|
|
|
|
|
case 24: bmsTrackOut = BMS_P2_KEY4; break;
|
|
|
|
|
case 25: bmsTrackOut = BMS_P2_KEY5; break;
|
|
|
|
|
case 26: bmsTrackOut = BMS_P2_TURN; break;
|
|
|
|
|
case 28: bmsTrackOut = BMS_P2_KEY6; break;
|
|
|
|
|
case 29: bmsTrackOut = BMS_P2_KEY7; break;
|
2004-09-27 01:46:57 +00:00
|
|
|
default: // unknown track
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-13 17:38:08 +00:00
|
|
|
// Find the largest common substring at the start of both strings.
|
2004-11-13 21:17:27 +00:00
|
|
|
static CString FindLargestInitialSubstring( CString string1, CString string2 )
|
2004-11-13 17:38:08 +00:00
|
|
|
{
|
2004-11-13 21:08:19 +00:00
|
|
|
// First see if the whole first string matches an appropriately-sized
|
|
|
|
|
// substring of the second, then keep chopping off the last character of
|
|
|
|
|
// each until they match.
|
2004-11-13 22:01:59 +00:00
|
|
|
unsigned i;
|
2004-11-13 21:17:27 +00:00
|
|
|
for( i = 0; i < string1.size() && i < string2.size(); ++i )
|
|
|
|
|
if( string1[i] != string2[i] )
|
|
|
|
|
break;
|
2004-11-13 21:08:19 +00:00
|
|
|
|
2004-11-13 21:17:27 +00:00
|
|
|
return string1.substr(0,i);
|
2004-11-13 17:38:08 +00:00
|
|
|
}
|
2004-09-27 01:46:57 +00:00
|
|
|
|
|
|
|
|
static StepsType DetermineStepsType( int iPlayer, const NoteData &nd )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( NUM_BMS_TRACKS == nd.GetNumTracks() );
|
|
|
|
|
|
2004-10-24 10:45:30 +00:00
|
|
|
bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS];
|
2004-09-27 01:46:57 +00:00
|
|
|
ZERO( bTrackHasNote );
|
|
|
|
|
|
2004-09-29 06:43:57 +00:00
|
|
|
int iLastRow = nd.GetLastRow();
|
2004-10-24 10:45:30 +00:00
|
|
|
for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
|
2004-09-27 01:46:57 +00:00
|
|
|
{
|
2004-09-29 06:43:57 +00:00
|
|
|
for( int r=0; r<=iLastRow; r++ )
|
2004-09-27 01:46:57 +00:00
|
|
|
{
|
2004-10-25 03:16:40 +00:00
|
|
|
if( nd.GetTapNote(t, r).type != TapNote::empty )
|
2004-09-27 01:46:57 +00:00
|
|
|
{
|
|
|
|
|
bTrackHasNote[t] = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int iNumNonEmptyTracks = 0;
|
2004-10-24 10:45:30 +00:00
|
|
|
for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
|
2004-09-27 01:46:57 +00:00
|
|
|
if( bTrackHasNote[t] )
|
|
|
|
|
iNumNonEmptyTracks++;
|
|
|
|
|
|
|
|
|
|
switch( iPlayer )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-09-27 01:46:57 +00:00
|
|
|
case 1: // "1 player"
|
|
|
|
|
/* Track counts:
|
2005-10-07 05:00:52 +00:00
|
|
|
4 - dance 4-panel
|
|
|
|
|
5 - pop 5-key
|
|
|
|
|
6 - dance 6-panel, beat 5-key
|
|
|
|
|
8 - beat 7-key
|
|
|
|
|
9 - popn 9-key */
|
2004-09-27 01:46:57 +00:00
|
|
|
switch( iNumNonEmptyTracks )
|
|
|
|
|
{
|
|
|
|
|
case 4: return STEPS_TYPE_DANCE_SINGLE;
|
2005-10-07 05:00:52 +00:00
|
|
|
case 5: return STEPS_TYPE_POPN_FIVE;
|
2004-09-27 01:46:57 +00:00
|
|
|
case 6:
|
|
|
|
|
// FIXME: There's no way to distinguish between these types.
|
2005-10-07 05:00:52 +00:00
|
|
|
// They use the same tracks. Assume it's a Beat type since they
|
2004-09-27 01:46:57 +00:00
|
|
|
// are more common.
|
|
|
|
|
//return STEPS_TYPE_DANCE_SOLO;
|
2005-10-07 05:00:52 +00:00
|
|
|
return STEPS_TYPE_BEAT_SINGLE5;
|
|
|
|
|
case 8: return STEPS_TYPE_BEAT_SINGLE7;
|
|
|
|
|
case 9: return STEPS_TYPE_POPN_NINE;
|
2004-09-27 01:46:57 +00:00
|
|
|
default: return STEPS_TYPE_INVALID;
|
|
|
|
|
}
|
|
|
|
|
case 2: // couple/battle
|
|
|
|
|
return STEPS_TYPE_DANCE_COUPLE;
|
|
|
|
|
case 3: // double
|
|
|
|
|
/* Track counts:
|
2005-10-07 05:00:52 +00:00
|
|
|
8 - dance Double
|
|
|
|
|
12 - beat Double 5-key
|
|
|
|
|
16 - beat Double 7-key */
|
2004-09-27 01:46:57 +00:00
|
|
|
switch( iNumNonEmptyTracks )
|
|
|
|
|
{
|
2005-10-07 05:00:52 +00:00
|
|
|
case 8: return STEPS_TYPE_BEAT_SINGLE7;
|
|
|
|
|
case 12: return STEPS_TYPE_BEAT_DOUBLE5;
|
|
|
|
|
case 16: return STEPS_TYPE_BEAT_DOUBLE7;
|
2004-09-27 01:46:57 +00:00
|
|
|
default: return STEPS_TYPE_INVALID;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
LOG->Warn( "Invalid #PLAYER value %d", iPlayer );
|
|
|
|
|
return STEPS_TYPE_INVALID;
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
float BMSLoader::GetBeatsPerMeasure( const MeasureToTimeSig_t &sigs, int iMeasure )
|
2004-11-01 08:06:56 +00:00
|
|
|
{
|
2004-11-03 09:35:10 +00:00
|
|
|
map<int, float>::const_iterator time_sig = sigs.find( iMeasure );
|
2004-11-08 22:27:34 +00:00
|
|
|
|
|
|
|
|
float fRet = 4.0f;
|
2004-11-03 09:35:10 +00:00
|
|
|
if( time_sig != sigs.end() )
|
2004-11-08 22:27:34 +00:00
|
|
|
{
|
|
|
|
|
fRet *= time_sig->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time_sig = m_TimeSigAdjustments.find( iMeasure );
|
|
|
|
|
if( time_sig != m_TimeSigAdjustments.end() )
|
|
|
|
|
{
|
|
|
|
|
fRet *= time_sig->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fRet;
|
2004-11-01 08:06:56 +00:00
|
|
|
}
|
|
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
int BMSLoader::GetMeasureStartRow( const MeasureToTimeSig_t &sigs, int iMeasureNo )
|
2004-11-01 08:06:56 +00:00
|
|
|
{
|
|
|
|
|
int iRowNo = 0;
|
|
|
|
|
for( int i = 0; i < iMeasureNo; ++i )
|
2004-11-03 09:35:10 +00:00
|
|
|
iRowNo += BeatToNoteRow( GetBeatsPerMeasure(sigs, i) );
|
2004-11-01 08:06:56 +00:00
|
|
|
return iRowNo;
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
|
|
|
|
|
void BMSLoader::SearchForDifficulty( CString sTag, Steps *pOut )
|
|
|
|
|
{
|
|
|
|
|
sTag.ToLower();
|
|
|
|
|
|
|
|
|
|
/* Only match "Light" in parentheses. */
|
|
|
|
|
if( sTag.find( "(light" ) != sTag.npos )
|
2005-04-13 08:45:00 +00:00
|
|
|
{
|
2004-11-03 09:35:10 +00:00
|
|
|
pOut->SetDifficulty( DIFFICULTY_EASY );
|
2005-04-13 08:45:00 +00:00
|
|
|
}
|
2004-11-03 09:35:10 +00:00
|
|
|
else if( sTag.find( "another" ) != sTag.npos )
|
2005-04-13 08:45:00 +00:00
|
|
|
{
|
2004-11-03 09:35:10 +00:00
|
|
|
pOut->SetDifficulty( DIFFICULTY_HARD );
|
2005-04-13 08:45:00 +00:00
|
|
|
}
|
|
|
|
|
else if( sTag.find( "(solo)" ) != sTag.npos )
|
2004-11-03 09:35:10 +00:00
|
|
|
{
|
2004-11-13 21:42:15 +00:00
|
|
|
pOut->SetDescription( "Solo" );
|
2004-11-03 09:35:10 +00:00
|
|
|
pOut->SetDifficulty( DIFFICULTY_EDIT );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG->Trace( "Tag \"%s\" is %s", sTag.c_str(), DifficultyToString(pOut->GetDifficulty()).c_str() );
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
bool BMSLoader::LoadFromBMSFile( const CString &sPath, const NameToData_t &mapNameToData, 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-07 05:04:04 +00:00
|
|
|
|
2004-09-27 01:46:57 +00:00
|
|
|
// BMS player code. Fill in below and use to determine StepsType.
|
|
|
|
|
int iPlayer = -1;
|
2004-11-03 06:54:05 +00:00
|
|
|
CString sData;
|
|
|
|
|
if( GetTagFromMap( mapNameToData, "#player", sData ) )
|
|
|
|
|
iPlayer = atoi(sData);
|
|
|
|
|
if( GetTagFromMap( mapNameToData, "#playlevel", sData ) )
|
|
|
|
|
out.SetMeter( atoi(sData) );
|
|
|
|
|
if( GetTagFromMap( mapNameToData, "#title", sData ) )
|
|
|
|
|
{
|
2004-11-13 21:51:45 +00:00
|
|
|
/* Hack: guess at 6-panel. */
|
2004-11-03 06:54:05 +00:00
|
|
|
|
|
|
|
|
// extract the Steps description (looks like 'Music <BASIC>')
|
2004-11-13 21:51:45 +00:00
|
|
|
size_t iOpenBracket = sData.find_first_of( "<(" );
|
|
|
|
|
size_t iCloseBracket = sData.find_first_of( ">)" );
|
2004-11-03 06:54:05 +00:00
|
|
|
|
2004-11-13 21:51:45 +00:00
|
|
|
if( iOpenBracket != string::npos && iCloseBracket != string::npos && iCloseBracket > iOpenBracket )
|
|
|
|
|
sData = sData.substr( iOpenBracket+1, iCloseBracket-iOpenBracket-1 );
|
2004-11-03 06:54:05 +00:00
|
|
|
|
|
|
|
|
// if there's a 6 in the description, it's probably part of "6panel" or "6-panel"
|
|
|
|
|
if( sData.Find("6") != -1 )
|
|
|
|
|
out.m_StepsType = STEPS_TYPE_DANCE_SOLO;
|
|
|
|
|
}
|
2004-09-27 01:46:57 +00:00
|
|
|
|
2004-10-23 23:41:49 +00:00
|
|
|
NoteData ndNotes;
|
|
|
|
|
ndNotes.SetNumTracks( NUM_BMS_TRACKS );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
/* Read time signatures. Note that these can differ across files in the same
|
|
|
|
|
* song. */
|
|
|
|
|
MeasureToTimeSig_t mapMeasureToTimeSig;
|
|
|
|
|
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
|
|
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
NameToData_t::const_iterator it;
|
|
|
|
|
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
const CString &sName = it->first;
|
|
|
|
|
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
|
|
|
|
|
continue;
|
2004-06-06 21:04:48 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
// this is step or offset data. Looks like "#00705"
|
|
|
|
|
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
|
|
|
|
|
int iRawTrackNum = atoi( sName.substr(4,2).c_str() );
|
2004-11-03 09:35:10 +00:00
|
|
|
int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo );
|
|
|
|
|
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo );
|
2004-11-03 06:54:05 +00:00
|
|
|
const CString &sNoteData = it->second;
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
vector<TapNote> vTapNotes;
|
|
|
|
|
for( int i=0; i+1<sNoteData.GetLength(); i+=2 )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
CString sNoteId = sNoteData.substr(i,2);
|
|
|
|
|
if( sNoteId != "00" )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
TapNote tn = TAP_ORIGINAL_TAP;
|
|
|
|
|
map<CString,int>::const_iterator it = m_mapWavIdToKeysoundIndex.find(sNoteId);
|
|
|
|
|
if( it != m_mapWavIdToKeysoundIndex.end() )
|
2004-09-29 07:19:44 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
tn.bKeysound = true;
|
|
|
|
|
tn.iKeysoundIndex = it->second;
|
2004-09-29 07:19:44 +00:00
|
|
|
}
|
2004-11-03 06:54:05 +00:00
|
|
|
vTapNotes.push_back( tn );
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
2004-11-03 06:54:05 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
vTapNotes.push_back( TAP_EMPTY );
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
const unsigned uNumNotesInThisMeasure = vTapNotes.size();
|
|
|
|
|
for( unsigned j=0; j<uNumNotesInThisMeasure; j++ )
|
|
|
|
|
{
|
|
|
|
|
if( vTapNotes[j].type != TapNote::empty )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
float fPercentThroughMeasure = (float)j/(float)uNumNotesInThisMeasure;
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-08 22:27:34 +00:00
|
|
|
int row = iRowNo + lrintf( fPercentThroughMeasure * fBeatsPerMeasure * ROWS_PER_BEAT );
|
2004-10-24 17:36:13 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
// some BMS files seem to have funky alignment, causing us to write gigantic cache files.
|
|
|
|
|
// Try to correct for this by quantizing.
|
|
|
|
|
row = Quantize( row, ROWS_PER_MEASURE/64 );
|
|
|
|
|
|
|
|
|
|
BmsTrack bmsTrack;
|
|
|
|
|
bool bIsHold;
|
|
|
|
|
if( ConvertRawTrackToTapNote(iRawTrackNum, bmsTrack, bIsHold) )
|
|
|
|
|
{
|
|
|
|
|
TapNote tn = vTapNotes[j];
|
|
|
|
|
if( bmsTrack == BMS_AUTO_KEYSOUND_1 )
|
2004-09-29 07:19:44 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
tn.type = TapNote::autoKeysound;
|
2004-10-24 10:45:30 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
// shift the auto keysound as far right as possible
|
|
|
|
|
int iLastEmptyTrack = -1;
|
|
|
|
|
if( ndNotes.GetTapLastEmptyTrack(row,iLastEmptyTrack) &&
|
|
|
|
|
iLastEmptyTrack >= BMS_AUTO_KEYSOUND_1 )
|
2004-10-23 23:41:49 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
bmsTrack = (BmsTrack)iLastEmptyTrack;
|
2004-10-23 23:41:49 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
// no room for this note. Drop it.
|
|
|
|
|
continue;
|
2004-10-23 23:41:49 +00:00
|
|
|
}
|
2004-09-29 07:19:44 +00:00
|
|
|
}
|
2004-11-03 06:54:05 +00:00
|
|
|
else if( bIsHold )
|
|
|
|
|
{
|
|
|
|
|
tn.type = TapNote::hold_head;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
tn.type = TapNote::tap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ndNotes.SetTapNote( bmsTrack, row, tn );
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2003-12-12 05:37:52 +00:00
|
|
|
|
2004-10-24 10:45:30 +00:00
|
|
|
// we're done reading in all of the BMS values
|
|
|
|
|
|
2004-10-23 23:41:49 +00:00
|
|
|
out.m_StepsType = DetermineStepsType( iPlayer, ndNotes );
|
2003-08-07 06:36:34 +00:00
|
|
|
if( out.m_StepsType == STEPS_TYPE_INVALID )
|
2003-07-12 19:06:04 +00:00
|
|
|
{
|
2004-03-08 05:11:26 +00:00
|
|
|
LOG->Warn( "Couldn't determine note type of file '%s'", sPath.c_str() );
|
2003-07-12 19:06:04 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-10-24 10:45:30 +00:00
|
|
|
// shift all of the autokeysound tracks onto the main tracks
|
|
|
|
|
for( int t=BMS_AUTO_KEYSOUND_1; t<BMS_AUTO_KEYSOUND_1+NUM_AUTO_KEYSOUND_TRACKS; t++ )
|
|
|
|
|
{
|
|
|
|
|
FOREACH_NONEMPTY_ROW_IN_TRACK( ndNotes, t, row )
|
|
|
|
|
{
|
|
|
|
|
TapNote tn = ndNotes.GetTapNote( t, row );
|
|
|
|
|
int iEmptyTrack;
|
|
|
|
|
if( ndNotes.GetTapFirstEmptyTrack(row, iEmptyTrack) )
|
|
|
|
|
{
|
|
|
|
|
ndNotes.SetTapNote( iEmptyTrack, row, tn );
|
|
|
|
|
ndNotes.SetTapNote( t, row, TAP_EMPTY );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LOG->Warn( "No room to shift." );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int iNumNewTracks = GameManager::StepsTypeToNumTracks( out.m_StepsType );
|
|
|
|
|
vector<int> iTransformNewToOld;
|
|
|
|
|
iTransformNewToOld.resize( iNumNewTracks, -1 );
|
2002-09-07 05:04:04 +00:00
|
|
|
|
2003-08-07 06:36:34 +00:00
|
|
|
switch( out.m_StepsType )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2003-08-07 06:16:17 +00:00
|
|
|
case STEPS_TYPE_DANCE_SINGLE:
|
2003-10-28 06:36:11 +00:00
|
|
|
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:
|
2003-10-28 06:36:11 +00:00
|
|
|
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:
|
2005-10-07 05:00:52 +00:00
|
|
|
case STEPS_TYPE_BEAT_SINGLE5:
|
2004-09-27 01:49:18 +00:00
|
|
|
// Hey! Why are these exactly the same? :-)
|
2003-10-28 06:36:11 +00:00
|
|
|
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;
|
2005-10-07 05:00:52 +00:00
|
|
|
case STEPS_TYPE_POPN_FIVE:
|
2003-10-28 06:36:11 +00:00
|
|
|
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;
|
2005-10-07 05:00:52 +00:00
|
|
|
case STEPS_TYPE_POPN_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
|
2003-10-28 06:36:11 +00:00
|
|
|
break;
|
2005-10-07 05:00:52 +00:00
|
|
|
case STEPS_TYPE_BEAT_DOUBLE5:
|
2003-10-28 06:36:11 +00:00
|
|
|
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;
|
2005-10-07 05:00:52 +00:00
|
|
|
case STEPS_TYPE_BEAT_SINGLE7:
|
2003-10-28 06:36:11 +00:00
|
|
|
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;
|
2003-10-29 09:02:14 +00:00
|
|
|
break;
|
2005-10-07 05:00:52 +00:00
|
|
|
case STEPS_TYPE_BEAT_DOUBLE7:
|
2003-10-28 06:36:11 +00:00
|
|
|
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;
|
2003-10-29 09:02:14 +00:00
|
|
|
break;
|
2002-09-06 23:24:40 +00:00
|
|
|
default:
|
2003-07-12 19:06:04 +00:00
|
|
|
ASSERT(0);
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-10-23 23:41:49 +00:00
|
|
|
NoteData noteData2;
|
|
|
|
|
noteData2.SetNumTracks( iNumNewTracks );
|
2004-10-24 19:21:44 +00:00
|
|
|
noteData2.LoadTransformed( ndNotes, iNumNewTracks, &*iTransformNewToOld.begin() );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-10-23 23:41:49 +00:00
|
|
|
out.SetNoteData( noteData2 );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
|
|
|
|
out.TidyUpData();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2005-12-09 21:36:22 +00:00
|
|
|
void BMSLoader::GetApplicableFiles( CString sPath, vector<CString> &out )
|
2002-09-11 05:15:46 +00:00
|
|
|
{
|
|
|
|
|
GetDirListing( sPath + CString("*.bms"), out );
|
2004-09-25 08:00:18 +00:00
|
|
|
GetDirListing( sPath + CString("*.bme"), out );
|
2002-09-11 05:15:46 +00:00
|
|
|
}
|
|
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
bool BMSLoader::ReadBMSFile( const CString &sPath, NameToData_t &mapNameToData )
|
2002-09-06 23:24:40 +00:00
|
|
|
{
|
2004-06-06 21:04:48 +00:00
|
|
|
RageFile file;
|
|
|
|
|
if( !file.Open(sPath) )
|
2004-07-13 00:03:47 +00:00
|
|
|
RageException::Throw( "Failed to open \"%s\" for reading: %s", sPath.c_str(), file.GetError().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
|
|
|
|
|
|
|
|
// BMS value names can be separated by a space or a colon.
|
2004-11-03 06:54:05 +00:00
|
|
|
size_t iIndexOfSeparator = line.find_first_of( ": " );
|
|
|
|
|
CString value_name = line.substr( 0, iIndexOfSeparator );
|
|
|
|
|
CString value_data;
|
|
|
|
|
if( iIndexOfSeparator != line.npos )
|
|
|
|
|
value_data = line.substr( iIndexOfSeparator+1 );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
value_name.MakeLower();
|
2005-06-27 05:24:24 +00:00
|
|
|
mapNameToData.insert( make_pair(value_name, value_data) );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
2003-12-12 05:37:52 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
bool BMSLoader::GetTagFromMap( const NameToData_t &mapNameToData, const CString &sName, CString &sOut )
|
|
|
|
|
{
|
|
|
|
|
NameToData_t::const_iterator it;
|
|
|
|
|
it = mapNameToData.find( sName );
|
|
|
|
|
if( it == mapNameToData.end() )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
sOut = it->second;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-13 22:23:51 +00:00
|
|
|
/* Finds the longest common match for the given tag in all files. If the given tag
|
|
|
|
|
* was found in at least one file, returns true; otherwise returns false. */
|
|
|
|
|
bool BMSLoader::GetCommonTagFromMapList( const vector<NameToData_t> &aBMSData, const CString &sName, CString &sCommonTag )
|
|
|
|
|
{
|
|
|
|
|
bool bFoundOne = false;
|
|
|
|
|
for( unsigned i=0; i < aBMSData.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
CString sTag;
|
|
|
|
|
if( !GetTagFromMap( aBMSData[i], sName, sTag ) )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if( !bFoundOne )
|
|
|
|
|
{
|
|
|
|
|
bFoundOne = true;
|
|
|
|
|
sCommonTag = sTag;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
sCommonTag = FindLargestInitialSubstring( sCommonTag, sTag );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bFoundOne;
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
BMS_TRACK_TIME_SIG = 2,
|
|
|
|
|
BMS_TRACK_BPM = 3,
|
|
|
|
|
BMS_TRACK_BPM_REF = 8,
|
|
|
|
|
BMS_TRACK_STOP = 9
|
|
|
|
|
};
|
|
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
void BMSLoader::ReadGlobalTags( const NameToData_t &mapNameToData, Song &out )
|
|
|
|
|
{
|
|
|
|
|
CString sData;
|
|
|
|
|
if( GetTagFromMap( mapNameToData, "#title", sData ) )
|
2004-11-13 21:46:43 +00:00
|
|
|
GetMainAndSubTitlesFromFullTitle( sData, out.m_sMainTitle, out.m_sSubTitle );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
GetTagFromMap( mapNameToData, "#artist", out.m_sArtist );
|
|
|
|
|
GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile );
|
|
|
|
|
GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
if( GetTagFromMap( mapNameToData, "#bpm", sData ) )
|
|
|
|
|
{
|
2005-01-23 23:17:12 +00:00
|
|
|
BPMSegment newSeg( 0, strtof(sData, NULL) );
|
2004-11-03 06:54:05 +00:00
|
|
|
out.AddBPMSegment( newSeg );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2005-01-23 21:55:01 +00:00
|
|
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(newSeg.m_iStartIndex), newSeg.GetBPM() );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
NameToData_t::const_iterator it;
|
|
|
|
|
for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
const CString &sName = it->first;
|
2003-12-12 05:37:52 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
if( sName.size() != 6 || sName.Left(4) != "#wav" )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// this is keysound file name. Looks like "#WAV1A"
|
|
|
|
|
CString sData = it->second;
|
|
|
|
|
CString sWavID = sName.Right(2);
|
2004-10-26 20:03:04 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
/* Due to bugs in some programs, many BMS files have a "WAV" extension
|
|
|
|
|
* on files in the BMS for files that actually have some other extension.
|
|
|
|
|
* Do a search. Don't do a wildcard search; if sData is "song.wav",
|
|
|
|
|
* we might also have "song.png", which we shouldn't match. */
|
|
|
|
|
if( !IsAFile(out.GetSongDir()+sData) )
|
|
|
|
|
{
|
|
|
|
|
const char *exts[] = { "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
|
|
|
|
|
for( unsigned i = 0; exts[i] != NULL; ++i )
|
2004-10-26 20:03:04 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
CString fn = SetExtension( sData, exts[i] );
|
|
|
|
|
if( IsAFile(out.GetSongDir()+fn) )
|
2004-10-26 20:03:04 +00:00
|
|
|
{
|
2004-11-03 06:54:05 +00:00
|
|
|
sData = fn;
|
|
|
|
|
break;
|
2004-10-26 20:03:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
2004-09-29 07:19:44 +00:00
|
|
|
}
|
2004-11-03 06:54:05 +00:00
|
|
|
if( !IsAFile(out.GetSongDir()+sData) )
|
|
|
|
|
LOG->Trace( "Song \"%s\" references key \"%s\" that can't be found",
|
|
|
|
|
m_sDir.c_str(), sData.c_str() );
|
|
|
|
|
|
|
|
|
|
sWavID.MakeUpper(); // HACK: undo the MakeLower()
|
|
|
|
|
out.m_vsKeysoundFile.push_back( sData );
|
|
|
|
|
m_mapWavIdToKeysoundIndex[ sWavID ] = out.m_vsKeysoundFile.size()-1;
|
2004-12-04 20:33:17 +00:00
|
|
|
LOG->Trace( "Inserting keysound index %u '%s'", unsigned(out.m_vsKeysoundFile.size()-1), sWavID.c_str() );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
/* Time signature tags affect all other global timing tags, so read them first. */
|
2004-11-03 09:35:10 +00:00
|
|
|
MeasureToTimeSig_t mapMeasureToTimeSig;
|
|
|
|
|
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
const CString &sName = it->first;
|
|
|
|
|
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
|
|
|
|
|
continue;
|
|
|
|
|
// this is step or offset data. Looks like "#00705"
|
|
|
|
|
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
|
|
|
|
|
int iBMSTrackNo = atoi( sName.substr(4,2).c_str() );
|
2004-11-03 09:35:10 +00:00
|
|
|
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo );
|
|
|
|
|
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo );
|
2004-11-03 06:54:05 +00:00
|
|
|
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
|
|
|
|
|
|
|
|
|
|
CString sData = it->second;
|
|
|
|
|
int totalPairs = sData.size() / 2;
|
|
|
|
|
for( int i = 0; i < totalPairs; ++i )
|
|
|
|
|
{
|
|
|
|
|
CString sPair = sData.substr(i*2,2);
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
int iVal = 0;
|
|
|
|
|
if( sscanf( sPair, "%x", &iVal ) == 0 || iVal == 0 )
|
|
|
|
|
continue;
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
|
|
|
|
|
float fBeat = NoteRowToBeat( iRow );
|
2003-12-12 05:37:52 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
switch( iBMSTrackNo )
|
|
|
|
|
{
|
|
|
|
|
case BMS_TRACK_BPM:
|
|
|
|
|
out.SetBPMAtBeat( fBeat, (float) iVal );
|
|
|
|
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
|
|
|
|
|
break;
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
case BMS_TRACK_BPM_REF:
|
|
|
|
|
{
|
|
|
|
|
CString sTagToLookFor = ssprintf( "#bpm%02x", iVal );
|
|
|
|
|
CString sBPM;
|
|
|
|
|
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
|
|
|
|
|
{
|
|
|
|
|
float fBPM = strtof( sBPM, NULL );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2005-01-23 21:55:01 +00:00
|
|
|
BPMSegment newSeg;
|
|
|
|
|
newSeg.m_iStartIndex = BeatToNoteRow(fBeat);
|
|
|
|
|
newSeg.SetBPM( fBPM );
|
2004-11-03 06:54:05 +00:00
|
|
|
out.AddBPMSegment( newSeg );
|
2005-01-23 21:55:01 +00:00
|
|
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg.GetBPM() );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case BMS_TRACK_STOP:
|
|
|
|
|
{
|
|
|
|
|
CString sTagToLookFor = ssprintf( "#stop%02x", iVal );
|
|
|
|
|
CString sBeats;
|
|
|
|
|
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
|
|
|
|
|
{
|
|
|
|
|
// find the BPM at the time of this freeze
|
|
|
|
|
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
|
|
|
|
|
float fBeats = strtof(sBeats,NULL) / 48.0f;
|
|
|
|
|
float fFreezeSecs = fBeats / fBPS;
|
|
|
|
|
|
2005-01-23 21:55:01 +00:00
|
|
|
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
|
2004-11-03 06:54:05 +00:00
|
|
|
out.AddStopSegment( newSeg );
|
2005-01-23 21:55:01 +00:00
|
|
|
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch( iBMSTrackNo )
|
|
|
|
|
{
|
|
|
|
|
case BMS_TRACK_BPM_REF:
|
|
|
|
|
{
|
|
|
|
|
// XXX: offset
|
|
|
|
|
int iBPMNo;
|
|
|
|
|
sscanf( sData, "%x", &iBPMNo ); // data is in hexadecimal
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
CString sBPM;
|
|
|
|
|
CString sTagToLookFor = ssprintf( "#bpm%02x", iBPMNo );
|
|
|
|
|
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
|
|
|
|
|
{
|
|
|
|
|
float fBPM = strtof( sBPM, NULL );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2005-01-23 21:55:01 +00:00
|
|
|
BPMSegment newSeg;
|
|
|
|
|
newSeg.m_iStartIndex = iStepIndex;
|
|
|
|
|
newSeg.SetBPM( fBPM );
|
2004-11-03 06:54:05 +00:00
|
|
|
out.AddBPMSegment( newSeg );
|
2005-01-23 21:55:01 +00:00
|
|
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(newSeg.m_iStartIndex), newSeg.GetBPM() );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2004-11-08 22:27:34 +00:00
|
|
|
|
|
|
|
|
/* Now that we're done reading BPMs, factor out weird time signatures. */
|
|
|
|
|
SetTimeSigAdjustments( mapMeasureToTimeSig, &out );
|
2004-11-03 06:54:05 +00:00
|
|
|
}
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
void BMSLoader::ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t &out )
|
|
|
|
|
{
|
|
|
|
|
NameToData_t::const_iterator it;
|
|
|
|
|
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
const CString &sName = it->first;
|
|
|
|
|
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// this is step or offset data. Looks like "#00705"
|
|
|
|
|
const CString &sData = it->second;
|
|
|
|
|
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
|
|
|
|
|
int iBMSTrackNo = atoi( sName.substr(4,2).c_str() );
|
|
|
|
|
if( iBMSTrackNo == BMS_TRACK_TIME_SIG )
|
|
|
|
|
out[iMeasureNo] = (float) atof(sData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-08 22:27:34 +00:00
|
|
|
/*
|
|
|
|
|
* Time signatures are often abused to tweak sync. Real time signatures should
|
|
|
|
|
* cause us to adjust the row offsets so one beat remains one beat. Fake time signatures,
|
|
|
|
|
* like 1.001 or 0.999, should be removed and converted to BPM changes. This is much
|
|
|
|
|
* more accurate, and prevents the whole song from being shifted off of the beat, causing
|
|
|
|
|
* BeatToNoteType to be wrong.
|
|
|
|
|
*
|
|
|
|
|
* Evaluate each time signature, and guess which time signatures should be converted
|
|
|
|
|
* to BPM changes. This isn't perfect, but errors aren't fatal.
|
|
|
|
|
*/
|
|
|
|
|
void BMSLoader::SetTimeSigAdjustments( const MeasureToTimeSig_t &sigs, Song *pOut )
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
m_TimeSigAdjustments.clear();
|
|
|
|
|
|
|
|
|
|
MeasureToTimeSig_t::const_iterator it;
|
|
|
|
|
for( it = sigs.begin(); it != sigs.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
int iMeasure = it->first;
|
|
|
|
|
float fFactor = it->second;
|
|
|
|
|
|
|
|
|
|
#if 1
|
|
|
|
|
static const float ValidFactors[] =
|
|
|
|
|
{
|
|
|
|
|
0.25f, /* 1/4 */
|
|
|
|
|
0.5f, /* 2/4 */
|
|
|
|
|
0.75f, /* 3/4 */
|
|
|
|
|
0.875f, /* 7/8 */
|
|
|
|
|
1.0f,
|
|
|
|
|
1.5f, /* 6/4 */
|
|
|
|
|
1.75f /* 7/4 */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool bValidTimeSignature = false;
|
|
|
|
|
for( unsigned i = 0; i < ARRAYSIZE(ValidFactors); ++i )
|
|
|
|
|
if( fabsf(fFactor-ValidFactors[i]) < 0.001 )
|
|
|
|
|
bValidTimeSignature = true;
|
|
|
|
|
|
|
|
|
|
if( bValidTimeSignature )
|
|
|
|
|
continue;
|
|
|
|
|
#else
|
|
|
|
|
/* Alternate approach that I tried first: see if the ratio is sane. However,
|
|
|
|
|
* some songs have values like "1.4", which comes out to 7/4 and is not a valid
|
|
|
|
|
* time signature. */
|
|
|
|
|
/* Convert the factor to a ratio, and reduce it. */
|
|
|
|
|
int iNum = lrintf( fFactor * 1000 ), iDen = 1000;
|
|
|
|
|
int iDiv = gcd( iNum, iDen );
|
|
|
|
|
iNum /= iDiv;
|
|
|
|
|
iDen /= iDiv;
|
|
|
|
|
|
|
|
|
|
/* Real time signatures usually come down to 1/2, 3/4, 7/8, etc. Bogus
|
|
|
|
|
* signatures that are only there to adjust sync usually look like 99/100. */
|
|
|
|
|
if( iNum <= 8 && iDen <= 8 )
|
|
|
|
|
continue;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* This time signature is bogus. Convert it to a BPM adjustment for this
|
|
|
|
|
* measure. */
|
|
|
|
|
LOG->Trace("Converted time signature %f in measure %i to a BPM segment.", fFactor, iMeasure );
|
|
|
|
|
|
|
|
|
|
/* Note that this GetMeasureStartRow will automatically include any adjustments
|
|
|
|
|
* that we've made previously in this loop; as long as we make the timing
|
|
|
|
|
* adjustment and the BPM adjustment together, everything remains consistent.
|
|
|
|
|
* Adjust m_TimeSigAdjustments first, or fAdjustmentEndBeat will be wrong. */
|
|
|
|
|
m_TimeSigAdjustments[iMeasure] = 1.0f / fFactor;
|
2005-01-23 21:55:01 +00:00
|
|
|
int iAdjustmentStartRow = GetMeasureStartRow( sigs, iMeasure );
|
|
|
|
|
int iAdjustmentEndRow = GetMeasureStartRow( sigs, iMeasure+1 );
|
|
|
|
|
pOut->m_Timing.MultiplyBPMInBeatRange( iAdjustmentStartRow, iAdjustmentEndRow, 1.0f / fFactor );
|
2004-11-08 22:27:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
bool BMSLoader::LoadFromDir( CString sDir, Song &out )
|
|
|
|
|
{
|
|
|
|
|
LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() );
|
2003-12-12 05:37:52 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
ASSERT( out.m_vsKeysoundFile.empty() );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +00:00
|
|
|
m_sDir = sDir;
|
2005-12-09 21:36:22 +00:00
|
|
|
vector<CString> arrayBMSFileNames;
|
2004-11-03 06:54:05 +00:00
|
|
|
GetApplicableFiles( sDir, arrayBMSFileNames );
|
2002-09-06 23:24:40 +00:00
|
|
|
|
2004-11-03 06:54:05 +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
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
/* Read all BMS files. */
|
2004-11-03 06:54:05 +00:00
|
|
|
vector<NameToData_t> aBMSData;
|
|
|
|
|
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
aBMSData.push_back( NameToData_t() );
|
|
|
|
|
ReadBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData.back() );
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-11-13 22:23:51 +00:00
|
|
|
CString commonSubstring;
|
|
|
|
|
GetCommonTagFromMapList( aBMSData, "#title", commonSubstring );
|
2004-11-13 17:38:08 +00:00
|
|
|
|
2004-11-13 22:23:51 +00:00
|
|
|
if( commonSubstring == "" )
|
2004-11-13 17:38:08 +00:00
|
|
|
{
|
2004-11-13 22:23:51 +00:00
|
|
|
// All bets are off; the titles don't match at all.
|
|
|
|
|
// At this rate we're lucky if we even get the title right.
|
|
|
|
|
LOG->Warn("BMS files in %s have inconsistent titles", sDir.c_str() );
|
2004-11-13 17:38:08 +00:00
|
|
|
}
|
2004-11-13 22:23:51 +00:00
|
|
|
|
2004-11-03 09:35:10 +00:00
|
|
|
/* Create a Steps for each. */
|
|
|
|
|
vector<Steps*> apSteps;
|
|
|
|
|
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
|
|
|
|
apSteps.push_back( new Steps );
|
2004-11-13 17:38:08 +00:00
|
|
|
|
|
|
|
|
// Now, with our fancy little substring, trim the titles and
|
|
|
|
|
// figure out where each goes.
|
|
|
|
|
for( unsigned i=0; i<aBMSData.size(); i++ )
|
|
|
|
|
{
|
2004-11-13 20:48:28 +00:00
|
|
|
Steps *pSteps = apSteps[i];
|
|
|
|
|
pSteps->SetDifficulty( DIFFICULTY_MEDIUM );
|
|
|
|
|
CString sTag;
|
2004-11-13 21:01:09 +00:00
|
|
|
if( GetTagFromMap( aBMSData[i], "#title", sTag ) && sTag.size() != commonSubstring.size() )
|
2004-11-13 20:48:28 +00:00
|
|
|
{
|
2004-11-13 21:01:09 +00:00
|
|
|
sTag = sTag.substr( commonSubstring.size(), sTag.size() - commonSubstring.size() );
|
2004-11-13 20:48:28 +00:00
|
|
|
sTag.ToLower();
|
|
|
|
|
|
|
|
|
|
// XXX: We should do this with filenames too, I have plenty of examples.
|
|
|
|
|
// however, filenames will be trickier, as stuffs at the beginning AND
|
2004-11-13 21:17:27 +00:00
|
|
|
// end change per-file, so we'll need a fancier FindLargestInitialSubstring()
|
2004-11-13 20:48:28 +00:00
|
|
|
|
|
|
|
|
// XXX: This matches (double), but I haven't seen it used. Again, MORE EXAMPLES NEEDED
|
2004-11-13 20:56:23 +00:00
|
|
|
if( sTag.find('l') != sTag.npos )
|
2004-11-13 21:51:45 +00:00
|
|
|
{
|
|
|
|
|
unsigned lPos = sTag.find('l');
|
2004-11-13 22:19:21 +00:00
|
|
|
if( lPos > 2 && sTag.substr(lPos-2,4) == "solo" )
|
2004-11-13 21:54:06 +00:00
|
|
|
{
|
|
|
|
|
// (solo) -- an edit, apparently (Thanks Glenn!)
|
|
|
|
|
pSteps->SetDifficulty( DIFFICULTY_EDIT );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Any of [L7] [L14] (LIGHT7) (LIGHT14) (LIGHT) [L] <LIGHT7> <L7>... you get the idea.
|
|
|
|
|
pSteps->SetDifficulty( DIFFICULTY_EASY );
|
|
|
|
|
}
|
2004-11-13 21:51:45 +00:00
|
|
|
}
|
2004-11-13 20:48:28 +00:00
|
|
|
// [A] <A> (A) [ANOTHER] <ANOTHER> (ANOTHER) (ANOTHER7) Another (DP ANOTHER) (Another) -ANOTHER- [A7] [A14] etc etc etc
|
2004-11-13 20:56:23 +00:00
|
|
|
else if( sTag.find('a') != sTag.npos )
|
2004-11-13 20:48:28 +00:00
|
|
|
pSteps->SetDifficulty( DIFFICULTY_HARD );
|
2005-03-12 06:24:41 +00:00
|
|
|
// XXX: Can also match (double), but should match [B] or [B7]
|
|
|
|
|
else if( sTag.find('b') != sTag.npos )
|
|
|
|
|
pSteps->SetDifficulty( DIFFICULTY_BEGINNER );
|
2004-11-13 20:48:28 +00:00
|
|
|
// Other tags I've seen here include (5KEYS) (10KEYS) (7keys) (14keys) (dp) [MIX] [14] (14 Keys Mix)
|
|
|
|
|
// XXX: I'm sure [MIX] means something... anyone know?
|
|
|
|
|
}
|
2004-11-13 17:38:08 +00:00
|
|
|
}
|
2004-11-03 09:35:10 +00:00
|
|
|
|
2004-11-13 20:48:28 +00:00
|
|
|
if( commonSubstring == "" )
|
2004-11-03 09:35:10 +00:00
|
|
|
{
|
2004-11-13 20:48:28 +00:00
|
|
|
// As said before, all bets are off.
|
|
|
|
|
// From here on in, it's nothing but guesswork.
|
2004-11-13 17:38:08 +00:00
|
|
|
|
2004-11-13 20:48:28 +00:00
|
|
|
/* Try to figure out the difficulty of each file. */
|
|
|
|
|
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
// XXX: Is this really effective if Common Substring parsing failed?
|
|
|
|
|
Steps *pSteps = apSteps[i];
|
|
|
|
|
pSteps->SetDifficulty( DIFFICULTY_MEDIUM );
|
|
|
|
|
CString sTag;
|
|
|
|
|
if( GetTagFromMap( aBMSData[i], "#title", sTag ) )
|
|
|
|
|
SearchForDifficulty( sTag, pSteps );
|
|
|
|
|
if( GetTagFromMap( aBMSData[i], "#genre", sTag ) )
|
|
|
|
|
SearchForDifficulty( sTag, pSteps );
|
|
|
|
|
}
|
2004-11-03 09:35:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Prefer to read global tags from a DIFFICULTY_MEDIUM file. These tend to
|
|
|
|
|
* have the least cruft in the #TITLE tag, so it's more likely to get a clean
|
|
|
|
|
* title. */
|
|
|
|
|
int iMainDataIndex = 0;
|
|
|
|
|
for( unsigned i=1; i<apSteps.size(); i++ )
|
2004-11-13 20:48:28 +00:00
|
|
|
if( apSteps[i]->GetDifficulty() == DIFFICULTY_MEDIUM )
|
|
|
|
|
iMainDataIndex = i;
|
2004-11-03 09:35:10 +00:00
|
|
|
|
|
|
|
|
ReadGlobalTags( aBMSData[iMainDataIndex], out );
|
2004-11-13 17:38:08 +00:00
|
|
|
|
|
|
|
|
// Override what that global tag said about the title if we have a good substring.
|
2005-10-08 02:02:03 +00:00
|
|
|
// Prevents clobbering and catches "MySong (7keys)" / "MySong (Another) (7keys)"
|
|
|
|
|
// Also catches "MySong (7keys)" / "MySong (14keys)"
|
2004-11-13 20:48:28 +00:00
|
|
|
if( commonSubstring != "" )
|
|
|
|
|
GetMainAndSubTitlesFromFullTitle( commonSubstring, out.m_sMainTitle, out.m_sSubTitle );
|
2004-09-29 07:19:44 +00:00
|
|
|
|
|
|
|
|
// Now that we've parsed the keysound data, load the Steps from the rest
|
|
|
|
|
// of the .bms files.
|
|
|
|
|
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
|
|
|
|
{
|
2004-11-03 09:35:10 +00:00
|
|
|
Steps* pNewNotes = apSteps[i];
|
2004-11-03 06:54:05 +00:00
|
|
|
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes );
|
2004-09-29 07:19:44 +00:00
|
|
|
if( ok )
|
|
|
|
|
out.AddSteps( pNewNotes );
|
|
|
|
|
else
|
|
|
|
|
delete pNewNotes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SlideDuplicateDifficulties( out );
|
|
|
|
|
|
2004-11-05 00:38:51 +00:00
|
|
|
ConvertString( out.m_sMainTitle, "utf-8,japanese" );
|
|
|
|
|
ConvertString( out.m_sArtist, "utf-8,japanese" );
|
2004-09-29 07:19:44 +00:00
|
|
|
|
|
|
|
|
|
2002-10-11 20:47:28 +00:00
|
|
|
return true;
|
2002-09-06 23:24:40 +00:00
|
|
|
}
|
|
|
|
|
|
2003-12-12 05:37:52 +00:00
|
|
|
|
2004-04-17 01:47:55 +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. */
|
2004-11-24 16:25:47 +00:00
|
|
|
FOREACH_StepsType( st )
|
2004-04-17 01:47:55 +00:00
|
|
|
{
|
2004-11-24 16:25:47 +00:00
|
|
|
FOREACH_Difficulty( dc )
|
2004-04-17 01:47:55 +00:00
|
|
|
{
|
2004-11-24 16:25:47 +00:00
|
|
|
if( dc == DIFFICULTY_EDIT )
|
|
|
|
|
continue;
|
2004-04-17 01:47:55 +00:00
|
|
|
|
|
|
|
|
vector<Steps*> vSteps;
|
|
|
|
|
p.GetSteps( vSteps, st, dc );
|
|
|
|
|
|
2004-04-18 07:34:37 +00:00
|
|
|
StepsUtil::SortNotesArrayByDifficulty( vSteps );
|
2004-04-17 01:47:55 +00:00
|
|
|
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.
|
|
|
|
|
*/
|