Load all BMS data into a map, then parse. Removes extra re-parsing,
fixes stop and BPM changing, fixes stop/BPM change interactions with time sig changing.
This commit is contained in:
+213
-306
@@ -205,7 +205,7 @@ int BMSLoader::GetMeasureStartRow( int iMeasureNo ) const
|
|||||||
return iRowNo;
|
return iRowNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out, const map<CString,int> &mapWavIdToKeysoundIndex )
|
bool BMSLoader::LoadFromBMSFile( const CString &sPath, const NameToData_t &mapNameToData, Steps &out )
|
||||||
{
|
{
|
||||||
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
|
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
|
||||||
|
|
||||||
@@ -213,86 +213,62 @@ bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out, const map<CSt
|
|||||||
|
|
||||||
// BMS player code. Fill in below and use to determine StepsType.
|
// BMS player code. Fill in below and use to determine StepsType.
|
||||||
int iPlayer = -1;
|
int iPlayer = -1;
|
||||||
|
CString sData;
|
||||||
|
if( GetTagFromMap( mapNameToData, "#player", sData ) )
|
||||||
|
iPlayer = atoi(sData);
|
||||||
|
if( GetTagFromMap( mapNameToData, "#playlevel", sData ) )
|
||||||
|
out.SetMeter( atoi(sData) );
|
||||||
|
if( GetTagFromMap( mapNameToData, "#title", sData ) )
|
||||||
|
{
|
||||||
|
sData.MakeLower();
|
||||||
|
|
||||||
|
// extract the Steps description (looks like 'Music <BASIC>')
|
||||||
|
int iPosOpenBracket = sData.Find( "<" );
|
||||||
|
if( iPosOpenBracket == -1 )
|
||||||
|
iPosOpenBracket = sData.Find( "(" );
|
||||||
|
int iPosCloseBracket = sData.Find( ">" );
|
||||||
|
if( iPosCloseBracket == -1 )
|
||||||
|
iPosCloseBracket = sData.Find( ")" );
|
||||||
|
|
||||||
|
if( iPosOpenBracket != -1 && iPosCloseBracket != -1 )
|
||||||
|
sData = sData.substr( iPosOpenBracket+1,
|
||||||
|
iPosCloseBracket-iPosOpenBracket-1 );
|
||||||
|
LOG->Trace( "Steps description found to be '%s'", sData.c_str() );
|
||||||
|
|
||||||
|
out.SetDescription(sData);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
NoteData ndNotes;
|
NoteData ndNotes;
|
||||||
ndNotes.SetNumTracks( NUM_BMS_TRACKS );
|
ndNotes.SetNumTracks( NUM_BMS_TRACKS );
|
||||||
|
|
||||||
RageFile file;
|
NameToData_t::const_iterator it;
|
||||||
if( !file.Open(sPath) )
|
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
|
||||||
RageException::Throw( "Failed to open \"%s\" for reading: %s", sPath.c_str(), file.GetError().c_str() );
|
|
||||||
|
|
||||||
while( !file.AtEOF() )
|
|
||||||
{
|
{
|
||||||
CString line;
|
const CString &sName = it->first;
|
||||||
if( file.GetLine( line ) == -1 )
|
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
|
||||||
{
|
continue;
|
||||||
LOG->Warn( "Error reading \"%s\": %s", sPath.c_str(), file.GetError().c_str() );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
StripCrnl(line);
|
|
||||||
|
|
||||||
// BMS value names can be separated by a space or a colon.
|
|
||||||
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 );
|
|
||||||
|
|
||||||
value_name.MakeLower();
|
|
||||||
|
|
||||||
if( value_name.Find("#player") != -1 )
|
|
||||||
{
|
|
||||||
iPlayer = atoi(value_data);
|
|
||||||
}
|
|
||||||
if( value_name.Find("#title") != -1 )
|
|
||||||
{
|
|
||||||
value_data.MakeLower();
|
|
||||||
|
|
||||||
// extract the Steps description (looks like 'Music <BASIC>')
|
|
||||||
int iPosOpenBracket = value_data.Find( "<" );
|
|
||||||
if( iPosOpenBracket == -1 )
|
|
||||||
iPosOpenBracket = value_data.Find( "(" );
|
|
||||||
int iPosCloseBracket = value_data.Find( ">" );
|
|
||||||
if( iPosCloseBracket == -1 )
|
|
||||||
iPosCloseBracket = value_data.Find( ")" );
|
|
||||||
|
|
||||||
if( iPosOpenBracket != -1 && iPosCloseBracket != -1 )
|
|
||||||
value_data = value_data.substr( iPosOpenBracket+1,
|
|
||||||
iPosCloseBracket-iPosOpenBracket-1 );
|
|
||||||
LOG->Trace( "Steps description found to be '%s'", value_data.c_str() );
|
|
||||||
|
|
||||||
out.SetDescription(value_data);
|
|
||||||
|
|
||||||
// if there's a 6 in the description, it's probably part of "6panel" or "6-panel"
|
|
||||||
if( value_data.Find("6") != -1 )
|
|
||||||
out.m_StepsType = STEPS_TYPE_DANCE_SOLO;
|
|
||||||
}
|
|
||||||
if( value_name.Find("#playlevel") != -1 )
|
|
||||||
{
|
|
||||||
out.SetMeter(atoi(value_data));
|
|
||||||
}
|
|
||||||
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"
|
|
||||||
{
|
|
||||||
int iMeasureNo = atoi( value_name.substr(1,3).c_str() );
|
|
||||||
int iRawTrackNum = atoi( value_name.substr(4,2).c_str() );
|
|
||||||
|
|
||||||
|
// 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() );
|
||||||
int iRowNo = GetMeasureStartRow( iMeasureNo );
|
int iRowNo = GetMeasureStartRow( iMeasureNo );
|
||||||
float fBeatsPerMeasure = GetBeatsPerMeasure( iMeasureNo );
|
float fBeatsPerMeasure = GetBeatsPerMeasure( iMeasureNo );
|
||||||
|
|
||||||
CString &sNoteData = value_data;
|
const CString &sNoteData = it->second;
|
||||||
vector<TapNote> vTapNotes;
|
|
||||||
|
|
||||||
|
vector<TapNote> vTapNotes;
|
||||||
for( int i=0; i+1<sNoteData.GetLength(); i+=2 )
|
for( int i=0; i+1<sNoteData.GetLength(); i+=2 )
|
||||||
{
|
{
|
||||||
CString sNoteId = sNoteData.substr(i,2);
|
CString sNoteId = sNoteData.substr(i,2);
|
||||||
if( sNoteId != "00" )
|
if( sNoteId != "00" )
|
||||||
{
|
{
|
||||||
TapNote tn = TAP_ORIGINAL_TAP;
|
TapNote tn = TAP_ORIGINAL_TAP;
|
||||||
map<CString,int>::const_iterator it = mapWavIdToKeysoundIndex.find(sNoteId);
|
map<CString,int>::const_iterator it = m_mapWavIdToKeysoundIndex.find(sNoteId);
|
||||||
if( it != mapWavIdToKeysoundIndex.end() )
|
if( it != m_mapWavIdToKeysoundIndex.end() )
|
||||||
{
|
{
|
||||||
tn.bKeysound = true;
|
tn.bKeysound = true;
|
||||||
tn.iKeysoundIndex = it->second;
|
tn.iKeysoundIndex = it->second;
|
||||||
@@ -306,7 +282,6 @@ bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out, const map<CSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unsigned uNumNotesInThisMeasure = vTapNotes.size();
|
const unsigned uNumNotesInThisMeasure = vTapNotes.size();
|
||||||
|
|
||||||
for( unsigned j=0; j<uNumNotesInThisMeasure; j++ )
|
for( unsigned j=0; j<uNumNotesInThisMeasure; j++ )
|
||||||
{
|
{
|
||||||
if( vTapNotes[j].type != TapNote::empty )
|
if( vTapNotes[j].type != TapNote::empty )
|
||||||
@@ -324,7 +299,6 @@ bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out, const map<CSt
|
|||||||
if( ConvertRawTrackToTapNote(iRawTrackNum, bmsTrack, bIsHold) )
|
if( ConvertRawTrackToTapNote(iRawTrackNum, bmsTrack, bIsHold) )
|
||||||
{
|
{
|
||||||
TapNote tn = vTapNotes[j];
|
TapNote tn = vTapNotes[j];
|
||||||
|
|
||||||
if( bmsTrack == BMS_AUTO_KEYSOUND_1 )
|
if( bmsTrack == BMS_AUTO_KEYSOUND_1 )
|
||||||
{
|
{
|
||||||
tn.type = TapNote::autoKeysound;
|
tn.type = TapNote::autoKeysound;
|
||||||
@@ -356,12 +330,10 @@ bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out, const map<CSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// we're done reading in all of the BMS values
|
// we're done reading in all of the BMS values
|
||||||
|
|
||||||
out.m_StepsType = DetermineStepsType( iPlayer, ndNotes );
|
out.m_StepsType = DetermineStepsType( iPlayer, ndNotes );
|
||||||
|
|
||||||
if( out.m_StepsType == STEPS_TYPE_INVALID )
|
if( out.m_StepsType == STEPS_TYPE_INVALID )
|
||||||
{
|
{
|
||||||
LOG->Warn( "Couldn't determine note type of file '%s'", sPath.c_str() );
|
LOG->Warn( "Couldn't determine note type of file '%s'", sPath.c_str() );
|
||||||
@@ -395,7 +367,6 @@ bool BMSLoader::LoadFromBMSFile( const CString &sPath, Steps &out, const map<CSt
|
|||||||
|
|
||||||
switch( out.m_StepsType )
|
switch( out.m_StepsType )
|
||||||
{
|
{
|
||||||
// fix PNM &c.
|
|
||||||
case STEPS_TYPE_DANCE_SINGLE:
|
case STEPS_TYPE_DANCE_SINGLE:
|
||||||
iTransformNewToOld[0] = BMS_P1_KEY1;
|
iTransformNewToOld[0] = BMS_P1_KEY1;
|
||||||
iTransformNewToOld[1] = BMS_P1_KEY3;
|
iTransformNewToOld[1] = BMS_P1_KEY3;
|
||||||
@@ -506,25 +477,8 @@ void BMSLoader::GetApplicableFiles( CString sPath, CStringArray &out )
|
|||||||
GetDirListing( sPath + CString("*.bme"), out );
|
GetDirListing( sPath + CString("*.bme"), out );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMSLoader::LoadFromDir( CString sDir, Song &out )
|
bool BMSLoader::ReadBMSFile( const CString &sPath, NameToData_t &mapNameToData )
|
||||||
{
|
{
|
||||||
LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() );
|
|
||||||
|
|
||||||
ASSERT( out.m_vsKeysoundFile.empty() );
|
|
||||||
|
|
||||||
CStringArray arrayBMSFileNames;
|
|
||||||
GetApplicableFiles( sDir, arrayBMSFileNames );
|
|
||||||
|
|
||||||
/* We should have at least one; if we had none, we shouldn't have been
|
|
||||||
* called to begin with. */
|
|
||||||
ASSERT( arrayBMSFileNames.size() );
|
|
||||||
|
|
||||||
// This maps from a BMS wav ID (e.g. "1A") to an entry in the Song's
|
|
||||||
// keysound vector. Fill this in below while parsing the song data.
|
|
||||||
map<CString,int> mapWavIdToKeysoundIndex;
|
|
||||||
|
|
||||||
CString sPath = out.GetSongDir() + arrayBMSFileNames[0];
|
|
||||||
|
|
||||||
RageFile file;
|
RageFile file;
|
||||||
if( !file.Open(sPath) )
|
if( !file.Open(sPath) )
|
||||||
RageException::Throw( "Failed to open \"%s\" for reading: %s", sPath.c_str(), file.GetError().c_str() );
|
RageException::Throw( "Failed to open \"%s\" for reading: %s", sPath.c_str(), file.GetError().c_str() );
|
||||||
@@ -538,289 +492,246 @@ bool BMSLoader::LoadFromDir( CString sDir, Song &out )
|
|||||||
}
|
}
|
||||||
|
|
||||||
StripCrnl(line);
|
StripCrnl(line);
|
||||||
CString value_name; // fill these in
|
|
||||||
CString value_data;
|
|
||||||
|
|
||||||
// BMS value names can be separated by a space or a colon.
|
// BMS value names can be separated by a space or a colon.
|
||||||
int iIndexOfFirstColon = line.Find( ":" );
|
size_t iIndexOfSeparator = line.find_first_of( ": " );
|
||||||
int iIndexOfFirstSpace = line.Find( " " );
|
CString value_name = line.substr( 0, iIndexOfSeparator );
|
||||||
|
CString value_data;
|
||||||
if( iIndexOfFirstColon == -1 )
|
if( iIndexOfSeparator != line.npos )
|
||||||
iIndexOfFirstColon = 10000;
|
value_data = line.substr( iIndexOfSeparator+1 );
|
||||||
if( iIndexOfFirstSpace == -1 )
|
|
||||||
iIndexOfFirstSpace = 10000;
|
|
||||||
|
|
||||||
int iIndexOfSeparator = min( iIndexOfFirstSpace, iIndexOfFirstColon );
|
|
||||||
|
|
||||||
if( iIndexOfSeparator != 10000 )
|
|
||||||
{
|
|
||||||
value_name = line.substr( 0, iIndexOfSeparator );
|
|
||||||
value_data = line; // the rest
|
|
||||||
value_data.erase(0,iIndexOfSeparator+1);
|
|
||||||
}
|
|
||||||
else // no separator
|
|
||||||
{
|
|
||||||
value_name = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
value_name.MakeLower();
|
value_name.MakeLower();
|
||||||
|
mapNameToData.insert( pair<CString,CString>(value_name, value_data) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// handle the data
|
bool BMSLoader::GetTagFromMap( const NameToData_t &mapNameToData, const CString &sName, CString &sOut )
|
||||||
if( value_name == "#title" )
|
{
|
||||||
|
NameToData_t::const_iterator it;
|
||||||
|
it = mapNameToData.find( sName );
|
||||||
|
if( it == mapNameToData.end() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
sOut = it->second;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BMSLoader::ReadGlobalTags( const NameToData_t &mapNameToData, Song &out )
|
||||||
|
{
|
||||||
|
CString sData;
|
||||||
|
if( GetTagFromMap( mapNameToData, "#title", sData ) )
|
||||||
{
|
{
|
||||||
// strip Steps type out of description leaving only song title - looks like 'B4U <BASIC>'
|
// strip Steps type out of description leaving only song title - looks like 'B4U <BASIC>'
|
||||||
size_t iIndex = value_data.find_last_of('<');
|
size_t iIndex = sData.find_last_of('<');
|
||||||
if( iIndex == value_data.npos )
|
if( iIndex == sData.npos )
|
||||||
iIndex = value_data.find_last_of('(');
|
iIndex = sData.find_last_of('(');
|
||||||
if( iIndex != value_data.npos )
|
if( iIndex != sData.npos )
|
||||||
{
|
{
|
||||||
value_data = value_data.Left( iIndex );
|
sData = sData.Left( iIndex );
|
||||||
GetMainAndSubTitlesFromFullTitle( value_data, out.m_sMainTitle,
|
GetMainAndSubTitlesFromFullTitle( sData, out.m_sMainTitle, out.m_sSubTitle );
|
||||||
out.m_sSubTitle );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
out.m_sMainTitle = value_data;
|
out.m_sMainTitle = sData;
|
||||||
}
|
}
|
||||||
else if( value_name == "#artist" )
|
|
||||||
|
GetTagFromMap( mapNameToData, "#artist", out.m_sArtist );
|
||||||
|
GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile );
|
||||||
|
GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile );
|
||||||
|
|
||||||
|
if( GetTagFromMap( mapNameToData, "#bpm", sData ) )
|
||||||
{
|
{
|
||||||
out.m_sArtist = value_data;
|
BPMSegment newSeg( 0, strtof(sData, NULL) );
|
||||||
}
|
|
||||||
else if( value_name == "#bpm" )
|
|
||||||
{
|
|
||||||
BPMSegment newSeg( 0, strtof(value_data, NULL) );
|
|
||||||
out.AddBPMSegment( newSeg );
|
out.AddBPMSegment( newSeg );
|
||||||
|
|
||||||
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
|
||||||
}
|
}
|
||||||
else if( value_name == "#backbmp" )
|
|
||||||
|
NameToData_t::const_iterator it;
|
||||||
|
for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it )
|
||||||
{
|
{
|
||||||
out.m_sBackgroundFile = value_data;
|
const CString &sName = it->first;
|
||||||
}
|
|
||||||
else if( value_name == "#wav" )
|
if( sName.size() != 6 || sName.Left(4) != "#wav" )
|
||||||
{
|
continue;
|
||||||
out.m_sMusicFile = value_data;
|
|
||||||
}
|
// this is keysound file name. Looks like "#WAV1A"
|
||||||
else if( value_name.size() == 6 && value_name.Left(4) == "#wav" ) // this is keysound file name. Looks like "#WAV1A"
|
CString sData = it->second;
|
||||||
{
|
CString sWavID = sName.Right(2);
|
||||||
CString sWavID = value_name.Right(2);
|
|
||||||
|
|
||||||
/* Due to bugs in some programs, many BMS files have a "WAV" extension
|
/* 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.
|
* on files in the BMS for files that actually have some other extension.
|
||||||
* Do a search. Don't do a wildcard search; if value_data is "song.wav",
|
* 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. */
|
* we might also have "song.png", which we shouldn't match. */
|
||||||
if( !IsAFile(out.GetSongDir()+value_data) )
|
if( !IsAFile(out.GetSongDir()+sData) )
|
||||||
{
|
{
|
||||||
const char *exts[] = { "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
|
const char *exts[] = { "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
|
||||||
for( unsigned i = 0; exts[i] != NULL; ++i )
|
for( unsigned i = 0; exts[i] != NULL; ++i )
|
||||||
{
|
{
|
||||||
CString fn = SetExtension( value_data, exts[i] );
|
CString fn = SetExtension( sData, exts[i] );
|
||||||
if( IsAFile(out.GetSongDir()+fn) )
|
if( IsAFile(out.GetSongDir()+fn) )
|
||||||
{
|
{
|
||||||
value_data = fn;
|
sData = fn;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if( !IsAFile(out.GetSongDir()+value_data) )
|
if( !IsAFile(out.GetSongDir()+sData) )
|
||||||
LOG->Trace( "File \"%s\" references key \"%s\" that can't be found",
|
LOG->Trace( "Song \"%s\" references key \"%s\" that can't be found",
|
||||||
sPath.c_str(), value_data.c_str() );
|
m_sDir.c_str(), sData.c_str() );
|
||||||
|
|
||||||
sWavID.MakeUpper(); // HACK: undo the MakeLower()
|
sWavID.MakeUpper(); // HACK: undo the MakeLower()
|
||||||
out.m_vsKeysoundFile.push_back( value_data );
|
out.m_vsKeysoundFile.push_back( sData );
|
||||||
mapWavIdToKeysoundIndex[ sWavID ] = out.m_vsKeysoundFile.size()-1;
|
m_mapWavIdToKeysoundIndex[ sWavID ] = out.m_vsKeysoundFile.size()-1;
|
||||||
LOG->Trace( "Inserting keysound index %lu '%s'", out.m_vsKeysoundFile.size()-1, sWavID.c_str() );
|
LOG->Trace( "Inserting keysound index %lu '%s'", out.m_vsKeysoundFile.size()-1, sWavID.c_str() );
|
||||||
}
|
}
|
||||||
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"
|
|
||||||
{
|
|
||||||
int iMeasureNo = atoi( value_name.substr(1,3).c_str() );
|
|
||||||
int iBMSTrackNo = atoi( value_name.substr(4,2).c_str() );
|
|
||||||
int iStepIndex = GetMeasureStartRow( iMeasureNo );
|
|
||||||
|
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
switch( iBMSTrackNo )
|
BMS_TRACK_TIME_SIG = 2,
|
||||||
|
BMS_TRACK_BPM = 3,
|
||||||
|
BMS_TRACK_BPM_REF = 8,
|
||||||
|
BMS_TRACK_STOP = 9
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Time signature tags affect all other global timing tags, so read them first. */
|
||||||
|
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
|
||||||
{
|
{
|
||||||
/* Don't do this; we handle autoplay tracks properly now. */
|
const CString &sName = it->first;
|
||||||
case 1: { // background music track
|
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
|
||||||
/* float fBeatOffset = fBeatOffset = NoteRowToBeat( (float)iStepIndex );
|
continue;
|
||||||
if( fBeatOffset > 10 ) // some BPMs's play the music again at the end. Why? Who knows...
|
|
||||||
break;
|
// this is step or offset data. Looks like "#00705"
|
||||||
float fBPS;
|
const CString &sData = it->second;
|
||||||
fBPS = out.m_Timing.m_BPMSegments[0].m_fBPM/60.0f;
|
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
|
||||||
out.m_Timing.m_fBeat0OffsetInSeconds = fBeatOffset / fBPS;
|
int iBMSTrackNo = atoi( sName.substr(4,2).c_str() );
|
||||||
*/ break;
|
if( iBMSTrackNo == BMS_TRACK_TIME_SIG )
|
||||||
|
m_MeasureToTimeSig[iMeasureNo] = (float) atof(sData);
|
||||||
}
|
}
|
||||||
case 2:
|
|
||||||
|
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
|
||||||
{
|
{
|
||||||
m_MeasureToTimeSig[iMeasureNo] = (float) atof(value_data);
|
const CString &sName = it->first;
|
||||||
break;
|
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
|
||||||
}
|
continue;
|
||||||
case 3: { // bpm change
|
// this is step or offset data. Looks like "#00705"
|
||||||
// A 03: is a set of hex pairs in the same layout as a normal
|
int iMeasureNo = atoi( sName.substr(1,3).c_str() );
|
||||||
// (note-defining) line, where each non-0 hex pair is a BPM
|
int iBMSTrackNo = atoi( sName.substr(4,2).c_str() );
|
||||||
// change at that point.
|
int iStepIndex = GetMeasureStartRow( iMeasureNo );
|
||||||
float fBeatsPerMeasure = GetBeatsPerMeasure( iMeasureNo );
|
float fBeatsPerMeasure = GetBeatsPerMeasure( iMeasureNo );
|
||||||
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
|
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
|
||||||
|
|
||||||
int totalPairs = value_data.GetLength() / 2;
|
CString sData = it->second;
|
||||||
|
int totalPairs = sData.size() / 2;
|
||||||
for( int i = 0; i < totalPairs; ++i )
|
for( int i = 0; i < totalPairs; ++i )
|
||||||
{
|
{
|
||||||
CString sPair = value_data.substr(i*2,2);
|
CString sPair = sData.substr(i*2,2);
|
||||||
int iBPM = 0;
|
|
||||||
|
int iVal = 0;
|
||||||
|
if( sscanf( sPair, "%x", &iVal ) == 0 || iVal == 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
if( sscanf( sPair, "%x", &iBPM ) == 1 && iBPM != 0 )
|
|
||||||
{
|
|
||||||
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
|
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
|
||||||
float fBeat = NoteRowToBeat( iRow );
|
float fBeat = NoteRowToBeat( iRow );
|
||||||
out.SetBPMAtBeat( fBeat, (float) iBPM );
|
|
||||||
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iBPM );
|
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;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 8: { // indirect bpm
|
case BMS_TRACK_BPM_REF:
|
||||||
/* 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. */
|
|
||||||
int iBPMNo;
|
|
||||||
sscanf( value_data, "%x", &iBPMNo ); // data is in hexadecimal
|
|
||||||
CString sTagToLookFor = ssprintf( "#BPM%02x", iBPMNo );
|
|
||||||
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;
|
|
||||||
if( !file.Open(sPath) )
|
|
||||||
RageException::Throw( "Failed to open %s for reading: %s", sPath.c_str(), file.GetError().c_str() );
|
|
||||||
while (!file.AtEOF())
|
|
||||||
{
|
{
|
||||||
CString line;
|
CString sTagToLookFor = ssprintf( "#bpm%02x", iVal );
|
||||||
file.GetLine( line );
|
CString sBPM;
|
||||||
StripCrnl(line);
|
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
|
||||||
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.substr( 0, iIndexOfSeparator );
|
float fBPM = strtof( sBPM, NULL );
|
||||||
value_data = line; // the rest
|
|
||||||
value_data.erase(0,iIndexOfSeparator+1);
|
|
||||||
}
|
|
||||||
else // no separator
|
|
||||||
value_name = line;
|
|
||||||
|
|
||||||
if( 0==stricmp(value_name, sTagToLookFor) )
|
BPMSegment newSeg( fBeat, fBPM );
|
||||||
{
|
out.AddBPMSegment( newSeg );
|
||||||
fBPM = strtof( value_data, NULL );
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if( fBPM == -1 ) // we didn't find the line we were looking for
|
|
||||||
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), sPath.c_str() );
|
|
||||||
else
|
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;
|
||||||
|
|
||||||
|
StopSegment newSeg( fBeat, fFreezeSecs );
|
||||||
|
out.AddStopSegment( newSeg );
|
||||||
|
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds );
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
CString sBPM;
|
||||||
|
CString sTagToLookFor = ssprintf( "#bpm%02x", iBPMNo );
|
||||||
|
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
|
||||||
|
{
|
||||||
|
float fBPM = strtof( sBPM, NULL );
|
||||||
|
|
||||||
BPMSegment newSeg( NoteRowToBeat(iStepIndex), fBPM );
|
BPMSegment newSeg( NoteRowToBeat(iStepIndex), fBPM );
|
||||||
out.AddBPMSegment( newSeg );
|
out.AddBPMSegment( newSeg );
|
||||||
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
|
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
|
||||||
}
|
}
|
||||||
|
|
||||||
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. */
|
|
||||||
int iStopNo;
|
|
||||||
sscanf( value_data, "%x", &iStopNo ); // data is in hexadecimal
|
|
||||||
CString sTagToLookFor = ssprintf( "#STOP%02x", iStopNo );
|
|
||||||
|
|
||||||
float fFreezeStartBeat = NoteRowToBeat(iStepIndex);
|
|
||||||
float fFreezeSecs = -1;
|
|
||||||
|
|
||||||
|
|
||||||
// open the song file again and and look for this tag's value
|
|
||||||
RageFile file;
|
|
||||||
if( !file.Open(sPath) )
|
|
||||||
RageException::Throw( "Failed to open %s for reading: %s", sPath.c_str(), file.GetError().c_str() );
|
|
||||||
while (!file.AtEOF())
|
|
||||||
{
|
|
||||||
CString line;
|
|
||||||
file.GetLine( line );
|
|
||||||
StripCrnl(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.substr( 0, iIndexOfSeparator );
|
|
||||||
value_data = line; // the rest
|
|
||||||
value_data.erase(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 fBPS = out.m_Timing.GetBPMAtBeat(fFreezeStartBeat) / 60.0f;
|
|
||||||
float fBeats = strtof(value_data,NULL) / 48.0f;
|
|
||||||
fFreezeSecs = fBeats / fBPS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( fFreezeSecs == -1 ) // we didn't find the line we were looking for
|
|
||||||
{
|
|
||||||
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), sPath.c_str() );
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );
|
||||||
StopSegment newSeg( fFreezeStartBeat, fFreezeSecs );
|
|
||||||
out.AddStopSegment( newSeg );
|
|
||||||
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds );
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BMSLoader::LoadFromDir( CString sDir, Song &out )
|
||||||
|
{
|
||||||
|
LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() );
|
||||||
|
|
||||||
|
ASSERT( out.m_vsKeysoundFile.empty() );
|
||||||
|
|
||||||
|
m_sDir = sDir;
|
||||||
|
CStringArray arrayBMSFileNames;
|
||||||
|
GetApplicableFiles( sDir, arrayBMSFileNames );
|
||||||
|
|
||||||
|
/* We should have at least one; if we had none, we shouldn't have been
|
||||||
|
* called to begin with. */
|
||||||
|
ASSERT( arrayBMSFileNames.size() );
|
||||||
|
|
||||||
|
CString sPath = out.GetSongDir() + arrayBMSFileNames[0];
|
||||||
|
|
||||||
|
vector<NameToData_t> aBMSData;
|
||||||
|
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
||||||
|
{
|
||||||
|
aBMSData.push_back( NameToData_t() );
|
||||||
|
ReadBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData.back() );
|
||||||
}
|
}
|
||||||
|
|
||||||
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
|
ReadGlobalTags( aBMSData[0], out );
|
||||||
LOG->Trace( "There is a BPM change at beat %f, BPM %f, index %d",
|
|
||||||
out.m_Timing.m_BPMSegments[i].m_fStartBeat, out.m_Timing.m_BPMSegments[i].m_fBPM, i );
|
|
||||||
|
|
||||||
|
|
||||||
// Now that we've parsed the keysound data, load the Steps from the rest
|
// Now that we've parsed the keysound data, load the Steps from the rest
|
||||||
@@ -828,11 +739,7 @@ bool BMSLoader::LoadFromDir( CString sDir, Song &out )
|
|||||||
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
|
||||||
{
|
{
|
||||||
Steps* pNewNotes = new Steps;
|
Steps* pNewNotes = new Steps;
|
||||||
|
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes );
|
||||||
const bool ok = LoadFromBMSFile(
|
|
||||||
out.GetSongDir() + arrayBMSFileNames[i],
|
|
||||||
*pNewNotes,
|
|
||||||
mapWavIdToKeysoundIndex );
|
|
||||||
if( ok )
|
if( ok )
|
||||||
out.AddSteps( pNewNotes );
|
out.AddSteps( pNewNotes );
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -9,14 +9,21 @@ class Steps;
|
|||||||
|
|
||||||
class BMSLoader: public NotesLoader
|
class BMSLoader: public NotesLoader
|
||||||
{
|
{
|
||||||
bool LoadFromBMSFile( const CString &sPath, Steps &out1, const map<CString,int> &mapWavIdToKeysoundIndex );
|
|
||||||
|
|
||||||
void SlideDuplicateDifficulties( Song &p );
|
void SlideDuplicateDifficulties( Song &p );
|
||||||
|
|
||||||
map<int, float> m_MeasureToTimeSig;
|
map<int, float> m_MeasureToTimeSig;
|
||||||
float GetBeatsPerMeasure( int iMeasure ) const;
|
float GetBeatsPerMeasure( int iMeasure ) const;
|
||||||
int GetMeasureStartRow( int iMeasureNo ) const;
|
int GetMeasureStartRow( int iMeasureNo ) const;
|
||||||
|
|
||||||
|
typedef multimap<CString, CString> NameToData_t;
|
||||||
|
bool ReadBMSFile( const CString &sPath, BMSLoader::NameToData_t &mapNameToData );
|
||||||
|
bool LoadFromBMSFile( const CString &sPath, const NameToData_t &mapNameToData, Steps &out1 );
|
||||||
|
void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out );
|
||||||
|
bool GetTagFromMap( const BMSLoader::NameToData_t &mapNameToData, const CString &sName, CString &sOut );
|
||||||
|
|
||||||
|
CString m_sDir;
|
||||||
|
map<CString,int> m_mapWavIdToKeysoundIndex;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void GetApplicableFiles( CString sPath, CStringArray &out );
|
void GetApplicableFiles( CString sPath, CStringArray &out );
|
||||||
bool LoadFromDir( CString sDir, Song &out );
|
bool LoadFromDir( CString sDir, Song &out );
|
||||||
|
|||||||
Reference in New Issue
Block a user