diff --git a/src/AutoKeysounds.cpp b/src/AutoKeysounds.cpp index aba066200d..79ec773973 100644 --- a/src/AutoKeysounds.cpp +++ b/src/AutoKeysounds.cpp @@ -99,7 +99,7 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain ) if( tn[pn].iKeysoundIndex >= 0 ) { RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex]; - float fSeconds = pSong->m_SongTiming.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency(); + float fSeconds = GAMESTATE->m_pCurSteps[pn]->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency(); float fPan = 0; if( !bSoundIsGlobal ) diff --git a/src/GameState.cpp b/src/GameState.cpp index 4dfbd0ebe6..fd8f2c487b 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -940,14 +940,15 @@ void GameState::ResetStageStatistics() m_iStageSeed = rand(); } -void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp ) +void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp, bool bUpdatePlayers ) { m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp ); - FOREACH_EnabledPlayer( pn ) - if( m_pCurSteps[pn] ) - m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, m_pCurSteps[pn]->m_Timing, timestamp ); + if( bUpdatePlayers ) + FOREACH_EnabledPlayer( pn ) + if( m_pCurSteps[pn] ) + m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, m_pCurSteps[pn]->m_Timing, timestamp ); Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset ); // LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze ); diff --git a/src/GameState.h b/src/GameState.h index 9b192156c3..000219a343 100644 --- a/src/GameState.h +++ b/src/GameState.h @@ -205,7 +205,7 @@ public: static const float MUSIC_SECONDS_INVALID; void ResetMusicStatistics(); // Call this when it's time to play a new song. Clears the values above. - void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp = RageZeroTimer ); + void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp = RageZeroTimer, bool bUpdatePlayers = false ); float GetSongPercent( float beat ) const; bool AllAreInDangerOrWorse() const; diff --git a/src/NotesLoaderBMS.cpp b/src/NotesLoaderBMS.cpp index 9eb58168ed..b05f9d8807 100644 --- a/src/NotesLoaderBMS.cpp +++ b/src/NotesLoaderBMS.cpp @@ -435,9 +435,121 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t static const int BEATS_PER_MEASURE = 4; static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE; -static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, - const MeasureToTimeSig_t &sigAdjustments, const map &idToKeySoundIndex ) +static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex ) { + + // Search for memoized file names: + { + RString nDataToSearchFor = nDataOriginal; + nDataToSearchFor.MakeLower(); + map::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor); + if (it != mapFilenameToKeysoundIndex.end()) { + outKeysoundIndex = it->second; + return true; + } + } + + // FIXME: garbled song names seem to crash the app. + // this might not be the best place to put this code. + if( !utf8_is_valid(nDataOriginal) ) + return false; + + /* 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. */ + RString nData = nDataOriginal; + if( !IsAFile(out.GetSongDir()+nData) ) + { + const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere + for( unsigned i = 0; exts[i] != NULL; ++i ) + { + RString fn = SetExtension( nData, exts[i] ); + if( IsAFile(out.GetSongDir()+fn) ) + { + nData = fn; + break; + } + } + } + + if( !IsAFile(out.GetSongDir()+nData) ) + { + LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() ); + return false; + } + + // Let's again search for memoized file names (we got the normalized one!): + { + RString nDataToSearchFor = nData; + nDataToSearchFor.MakeLower(); + map::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor); + if (it != mapFilenameToKeysoundIndex.end()) { + outKeysoundIndex = it->second; + + { + RString nDataToAdd = nDataOriginal; + nDataToAdd.MakeLower(); + mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex; + } + + return true; + } + } + + // Now this is a new sample. + out.m_vsKeysoundFile.push_back( nData ); + outKeysoundIndex = out.m_vsKeysoundFile.size() - 1; + + { + RString nDataToAdd = nDataOriginal; + nDataToAdd.MakeLower(); + mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex; + } + + { + RString nDataToAdd = nData; + nDataToAdd.MakeLower(); + mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex; + } + + return true; + +} + +static bool SearchForKeysound( const RString &sPath, RString sNoteId, const NameToData_t &mapNameToData, map &mapIdToKeysoundIndex, map &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex ) +{ + + sNoteId.MakeLower(); + { + map::iterator it = mapIdToKeysoundIndex.find(sNoteId); + if (it != mapIdToKeysoundIndex.end()) + { + outKeysoundIndex = it->second; + return outKeysoundIndex >= 0; + } + } + + RString sTagToLookFor = ssprintf( "#wav%s", sNoteId.c_str() ); + RString nDataOriginal; + if( !GetTagFromMap( mapNameToData, sTagToLookFor, nDataOriginal ) ) + { + LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() ); + return false; + } + + bool retval = SearchForKeysound(sPath, nDataOriginal, mapFilenameToKeysoundIndex, out, outKeysoundIndex); + mapIdToKeysoundIndex[sNoteId] = outKeysoundIndex; + return retval; + +} + +static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, Song &outSong, map &mapFilenameToKeysoundIndex ) +{ + + map mapIdToKeysoundIndex; + MeasureToTimeSig_t sigAdjustments; + LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() ); out.m_StepsType = StepsType_Invalid; @@ -453,11 +565,121 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo NoteData ndNotes; ndNotes.SetNumTracks( NUM_BMS_TRACKS ); + // Read BPM + if( GetTagFromMap(mapNameToData, "#bpm", sData) ) + { + const float fBPM = StringToFloat( sData ); + + if( fBPM > 0.0f ) + { + BPMSegment newSeg( 0, fBPM ); + out.m_Timing.AddBPMSegment( newSeg ); + LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM ); + } + else + { + LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %f.", + NoteRowToBeat(0), fBPM ); + } + } + + /* Read time signatures. Note that these can differ across files in the same * song. */ MeasureToTimeSig_t mapMeasureToTimeSig; ReadTimeSigs( mapNameToData, mapMeasureToTimeSig ); + for( NameToData_t::const_iterator it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it ) + { + const RString &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() ); + int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments ); + float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments ); + int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure ); + + RString nData = it->second; + int totalPairs = nData.size() / 2; + for( int i = 0; i < totalPairs; ++i ) + { + RString sPair = nData.substr( i*2, 2 ); + + int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs; + float fBeat = NoteRowToBeat( iRow ); + int iVal = 0; + sscanf( sPair, "%x", &iVal ); + + if (sPair == "00") + { + continue; + } + + switch( iBMSTrackNo ) + { + case BMS_TRACK_BPM: + if( iVal > 0 ) + { + out.m_Timing.SetBPMAtBeat( fBeat, (float) iVal ); + LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal ); + } + else + { + LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %d.", + fBeat, iVal ); + } + break; + + case BMS_TRACK_BPM_REF: + { + RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() ); + RString sBPM; + if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) ) + { + float fBPM = StringToFloat( sBPM ); + out.m_Timing.SetBPMAtBeat( fBeat, fBPM ); + } + else + { + LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() ); + } + break; + } + case BMS_TRACK_STOP: + { + if( iVal == 0 ) + { + break; + } + RString sTagToLookFor = ssprintf( "#stop%02x", iVal ); + RString 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 = StringToFloat( sBeats ) / 48.0f; + float fFreezeSecs = fBeats / fBPS; + + StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs ); + out.m_Timing.AddStopSegment( newSeg ); + LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds ); + } + else + { + LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() ); + } + break; + } + } + } + + } + + // Now that we're done reading BPMs, factor out weird time signatures. + SetTimeSigAdjustments( mapMeasureToTimeSig, outSong, sigAdjustments ); + int iHoldStarts[NUM_BMS_TRACKS]; TapNote iHoldHeads[NUM_BMS_TRACKS]; @@ -487,10 +709,9 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo RString sNoteId = sNoteData.substr( i, 2 ); if( sNoteId != "00" ) { - vTapNotes.push_back( TAP_ORIGINAL_TAP ); - map::const_iterator rInt = idToKeySoundIndex.find( sNoteId ); - if( rInt != idToKeySoundIndex.end() ) - vTapNotes.back().iKeysoundIndex = rInt->second; + TapNote tn = TAP_ORIGINAL_TAP; + SearchForKeysound( sPath, sNoteId, mapNameToData, mapIdToKeysoundIndex, mapFilenameToKeysoundIndex, outSong, tn.iKeysoundIndex ); + vTapNotes.push_back( tn ); } else { @@ -781,7 +1002,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo return true; } -static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map &idToKeySoundIndexOut ) +static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out ) { RString sData; if( GetTagFromMap(mapNameToData, "#title", sData) ) @@ -792,172 +1013,6 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile ); GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile ); - if( GetTagFromMap(mapNameToData, "#bpm", sData) ) - { - const float fBPM = StringToFloat( sData ); - - if( PREFSMAN->m_bQuirksMode ) - { - BPMSegment newSeg( 0, fBPM ); - out.m_SongTiming.AddBPMSegment( newSeg ); - if( fBPM > 0.0f ) - LOG->Trace( "Inserting new positive BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM ); - else - LOG->Trace( "Inserting new negative BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM ); - } - else - { - if( fBPM > 0.0f ) - { - BPMSegment newSeg( 0, fBPM ); - out.m_SongTiming.AddBPMSegment( newSeg ); - LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM ); - } - else - { - LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %f.", - NoteRowToBeat(0), fBPM ); - } - } - } - - NameToData_t::const_iterator it; - for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it ) - { - const RString &sName = it->first; - - if( sName.size() != 6 || sName.Left(4) != "#wav" ) - continue; - - // this is keysound file name. Looks like "#WAV1A" - RString nData = it->second; - RString sWavID = sName.Right(2); - - // FIXME: garbled song names seem to crash the app. - // this might not be the best place to put this code. - if( !utf8_is_valid(nData) ) - continue; - - /* 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()+nData) ) - { - const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere - for( unsigned i = 0; exts[i] != NULL; ++i ) - { - RString fn = SetExtension( nData, exts[i] ); - if( IsAFile(out.GetSongDir()+fn) ) - { - nData = fn; - break; - } - } - } - if( !IsAFile(out.GetSongDir()+nData) ) - LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() ); - - sWavID.MakeUpper(); // HACK: undo the MakeLower() - out.m_vsKeysoundFile.push_back( nData ); - idToKeySoundIndexOut[ sWavID ] = out.m_vsKeysoundFile.size()-1; - LOG->Trace( "Inserting keysound index %u '%s'", unsigned(out.m_vsKeysoundFile.size()-1), sWavID.c_str() ); - } - - // Time signature tags affect all other global timing tags, so read them first. - MeasureToTimeSig_t mapMeasureToTimeSig; - ReadTimeSigs( mapNameToData, mapMeasureToTimeSig ); - - for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it ) - { - const RString &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() ); - int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut ); - float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut ); - int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure ); - - RString nData = it->second; - int totalPairs = nData.size() / 2; - for( int i = 0; i < totalPairs; ++i ) - { - RString sPair = nData.substr( i*2, 2 ); - - int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs; - float fBeat = NoteRowToBeat( iRow ); - int iVal = 0; - sscanf( sPair, "%x", &iVal ); - - if (sPair == "00") - { - continue; - } - - switch( iBMSTrackNo ) - { - case BMS_TRACK_BPM: - if( iVal > 0 ) - { - out.m_SongTiming.SetBPMAtBeat( fBeat, (float) iVal ); - LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal ); - } - else - { - LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %d.", - fBeat, iVal ); - } - break; - - case BMS_TRACK_BPM_REF: - { - RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() ); - RString sBPM; - if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) ) - { - float fBPM = StringToFloat( sBPM ); - out.m_SongTiming.SetBPMAtBeat( fBeat, fBPM ); - } - else - { - LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() ); - } - break; - } - case BMS_TRACK_STOP: - { - if( iVal == 0 ) - { - break; - } - RString sTagToLookFor = ssprintf( "#stop%02x", iVal ); - RString sBeats; - if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) ) - { - // find the BPM at the time of this freeze - float fBPS = out.m_SongTiming.GetBPMAtBeat(fBeat) / 60.0f; - float fBeats = StringToFloat( sBeats ) / 48.0f; - float fFreezeSecs = fBeats / fBPS; - - StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs ); - out.m_SongTiming.AddStopSegment( newSeg ); - LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds ); - } - else - { - LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() ); - } - break; - } - } - } - - } - - // Now that we're done reading BPMs, factor out weird time signatures. - SetTimeSigAdjustments( mapMeasureToTimeSig, out, sigAdjustmentsOut ); } static void SlideDuplicateDifficulties( Song &p ) @@ -1100,9 +1155,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out ) if( apSteps[i]->GetDifficulty() == Difficulty_Medium ) iMainDataIndex = i; - MeasureToTimeSig_t sigAdjustments; - map idToKeysoundIndex; - ReadGlobalTags( aBMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex ); + ReadGlobalTags( aBMSData[iMainDataIndex], out ); // The brackets before the difficulty are in common substring, so remove them if it's found. if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' ) @@ -1124,10 +1177,11 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out ) // Now that we've parsed the keysound data, load the Steps from the rest // of the .bms files. + map mapFilenameToKeysoundIndex; for( unsigned i=0; iGetPositionSeconds( NULL, &tm ); - GAMESTATE->UpdateSongPosition( fSeconds, GAMESTATE->m_pCurSong->m_SongTiming, tm ); + GAMESTATE->UpdateSongPosition( fSeconds, GAMESTATE->m_pCurSong->m_SongTiming, tm, true ); } if( m_EditState == STATE_RECORDING ) @@ -2388,7 +2388,7 @@ void ScreenEdit::TransitionEditState( EditState em ) /* Give a 1 second lead-in. If we're loading Player, this must be done first. */ float fSeconds = m_pSteps->m_Timing.GetElapsedTimeFromBeat( NoteRowToBeat(m_iStartPlayingAt) ) - 1; - GAMESTATE->UpdateSongPosition( fSeconds, m_pSteps->m_Timing ); + GAMESTATE->UpdateSongPosition( fSeconds, m_pSteps->m_Timing, RageZeroTimer, true ); GAMESTATE->m_bGameplayLeadIn.Set( false ); diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp index 041613e914..ffea34a274 100644 --- a/src/ScreenGameplay.cpp +++ b/src/ScreenGameplay.cpp @@ -1423,7 +1423,7 @@ void ScreenGameplay::UpdateSongPosition( float fDeltaTime ) RageTimer tm; const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm ); const float fAdjust = SOUND->GetFrameTimingAdjustment( fDeltaTime ); - GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_SongTiming, tm+fAdjust ); + GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_SongTiming, tm+fAdjust, true ); } void ScreenGameplay::BeginScreen() diff --git a/src/Steps.cpp b/src/Steps.cpp index 3c0cf7cb57..5c2c91f063 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -339,6 +339,7 @@ void Steps::AutogenFrom( const Steps *parent_, StepsType ntTo ) { parent = parent_; m_StepsType = ntTo; + m_Timing = parent->m_Timing; } void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds ) // pSource does not have to be of the same StepsType @@ -348,6 +349,7 @@ void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds pSource->GetNoteData( noteData ); noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks ); parent = NULL; + m_Timing = pSource->m_Timing; this->SetNoteData( noteData ); this->SetDescription( pSource->GetDescription() ); this->SetDifficulty( pSource->GetDifficulty() );